Merge "Add containers dict to capsule"

This commit is contained in:
Zuul 2018-05-01 14:42:48 +00:00 committed by Gerrit Code Review
commit 77c8388e96
12 changed files with 128 additions and 37 deletions

View File

@ -145,8 +145,6 @@ class CapsuleController(base.Controller):
new_capsule.user_id = context.user_id
new_capsule.status = consts.PENDING
new_capsule.create(context)
new_capsule.containers = []
new_capsule.containers_uuids = []
new_capsule.volumes = []
capsule_need_cpu = 0
capsule_need_memory = 0
@ -167,6 +165,9 @@ class CapsuleController(base.Controller):
new_capsule.meta_name = metadata_info.get('name', None)
new_capsule.meta_labels = metadata_info.get('labels', None)
# create the capsule in DB so that it generates a 'id'
new_capsule.save()
extra_spec = {}
az_info = template_json.get('availabilityZone')
if az_info:
@ -178,9 +179,9 @@ class CapsuleController(base.Controller):
sandbox_container.user_id = context.user_id
name = self._generate_name_for_capsule_sandbox(new_capsule)
sandbox_container.name = name
sandbox_container.capsule_uuid = new_capsule.id
sandbox_container.create(context)
new_capsule.containers.append(sandbox_container)
new_capsule.containers_uuids.append(sandbox_container.uuid)
new_capsule.containers_uuids = [sandbox_container.uuid]
container_num = len(containers_spec)
for k in range(container_num):
@ -235,9 +236,9 @@ class CapsuleController(base.Controller):
container_dict['status'] = consts.CREATING
container_dict['interactive'] = True
container_dict['capsule_id'] = new_capsule.id
new_container = objects.Container(context, **container_dict)
new_container.create(context)
new_capsule.containers.append(new_container)
new_capsule.containers_uuids.append(new_container.uuid)
# Deal with the volume support
@ -267,19 +268,6 @@ class CapsuleController(base.Controller):
"""
capsule = _get_capsule(capsule_ident)
check_policy_on_capsule(capsule.as_dict(), "capsule:get")
context = pecan.request.context
compute_api = pecan.request.compute_api
sandbox = utils.get_container(capsule.containers_uuids[0])
try:
container = compute_api.container_show(context, sandbox)
capsule.status = container.status
capsule.save(context)
except Exception as e:
LOG.exception(("Error while show capsule %(uuid)s: "
"%(e)s."),
{'uuid': capsule.uuid, 'e': e})
capsule.status = consts.UNKNOWN
return view.format_capsule(pecan.request.host_url, capsule)
@pecan.expose('json')

View File

@ -34,7 +34,8 @@ _basic_keys = (
'containers_uuids',
'capsule_version',
'memory',
'cpu'
'cpu',
'containers',
)

View File

@ -1148,7 +1148,7 @@ class Manager(periodic_task.PeriodicTasks):
try:
container = \
objects.Container.get_by_uuid(context, uuid)
self.container_delete(context, container, force=True)
self._do_container_delete(context, container, force=True)
except Exception as e:
LOG.exception("Failed to delete container %(uuid0)s because "
"it doesn't exist in the capsule. Stale data "
@ -1161,7 +1161,7 @@ class Manager(periodic_task.PeriodicTasks):
objects.Container.get_by_uuid(context,
capsule.containers_uuids[0])
self._delete_sandbox(context, container, reraise=False)
self.container_delete(context, container, force=True)
self._do_container_delete(context, container, force=True)
except Exception as e:
LOG.exception(e)
capsule.task_state = None

View File

@ -0,0 +1,34 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""add capsule_id to containers
Revision ID: cff60402dd86
Revises: 2b045cb595db
Create Date: 2018-04-29 21:27:00.722445
"""
# revision identifiers, used by Alembic.
revision = 'cff60402dd86'
down_revision = '2b045cb595db'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('container',
sa.Column('capsule_id', sa.Integer(),
nullable=True))

View File

@ -151,7 +151,7 @@ class Connection(object):
def _add_containers_filters(self, query, filters):
filter_names = ['name', 'image', 'project_id', 'user_id',
'memory', 'host', 'task_state', 'status',
'auto_remove', 'uuid']
'auto_remove', 'uuid', 'capsule_id']
return self._add_filters(query, filters=filters,
filter_names=filter_names)

View File

@ -167,6 +167,8 @@ class Container(Base):
runtime = Column(String(32))
disk = Column(Integer, default=0)
auto_heal = Column(Boolean, default=False)
capsule_id = Column(Integer,
ForeignKey('capsule.id', ondelete='CASCADE'))
class VolumeMapping(Base):

View File

@ -12,13 +12,23 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from oslo_versionedobjects import fields
from zun.common import exception
from zun.db import api as dbapi
from zun.objects import base
from zun.objects import container
from zun.objects import fields as z_fields
LOG = logging.getLogger(__name__)
_CAPSULE_OPTIONAL_JOINED_FIELD = ['containers']
CAPSULE_OPTIONAL_ATTRS = _CAPSULE_OPTIONAL_JOINED_FIELD
@base.ZunObjectRegistry.register
class Capsule(base.ZunPersistentObject, base.ZunObject):
# Version 1.0: Initial version
@ -72,8 +82,9 @@ class Capsule(base.ZunPersistentObject, base.ZunObject):
def _from_db_object(capsule, db_capsule):
"""Converts a database entity to a formal object."""
for field in capsule.fields:
if field != 'containers':
setattr(capsule, field, db_capsule[field])
if field in CAPSULE_OPTIONAL_ATTRS:
continue
setattr(capsule, field, db_capsule[field])
capsule.obj_reset_changes()
return capsule
@ -117,7 +128,7 @@ class Capsule(base.ZunPersistentObject, base.ZunObject):
:param marker: pagination marker for large data sets.
:param sort_key: column to sort results by.
:param sort_dir: direction to sort. "asc" or "desc".
:param filters: filters when list containers, the filter name could be
:param filters: filters when list capsules, the filter name could be
'name', 'image', 'project_id', 'user_id', 'memory'.
For example, filters={'image': 'nginx'}
:returns: a list of :class:`Capsule` object.
@ -141,6 +152,10 @@ class Capsule(base.ZunPersistentObject, base.ZunObject):
"""
values = self.obj_get_changes()
if 'containers' in values:
raise exception.ObjectActionError(action='create',
reason='containers assigned')
db_capsule = dbapi.create_capsule(context, values)
self._from_db_object(self, db_capsule)
@ -173,6 +188,35 @@ class Capsule(base.ZunPersistentObject, base.ZunObject):
object, e.g.: Capsule(context)
"""
updates = self.obj_get_changes()
if 'containers' in updates:
raise exception.ObjectActionError(action='save',
reason='containers changed')
dbapi.update_capsule(context, self.uuid, updates)
self.obj_reset_changes()
def as_dict(self):
capsule_dict = super(Capsule, self).as_dict()
capsule_dict['containers'] = [c.as_dict() for c in self.containers]
return capsule_dict
def obj_load_attr(self, attrname):
if attrname not in CAPSULE_OPTIONAL_ATTRS:
raise exception.ObjectActionError(
action='obj_load_attr',
reason='attribute %s not lazy-loadable' % attrname)
if not self._context:
raise exception.OrphanedObjectError(method='obj_load_attr',
objtype=self.obj_name())
LOG.debug("Lazy-loading '%(attr)s' on %(name)s uuid %(uuid)s",
{'attr': attrname,
'name': self.obj_name(),
'uuid': self.uuid,
})
if attrname == 'containers':
self.containers = container.Container.list_by_capsule_id(
self._context, self.id)
self.obj_reset_changes(fields=[attrname])

View File

@ -58,7 +58,8 @@ class Container(base.ZunPersistentObject, base.ZunObject):
# Version 1.27: Make auto_heal field nullable
# Version 1.28: Add 'Dead' to ContainerStatus
# Version 1.29: Add 'Restarting' to ContainerStatus
VERSION = '1.29'
# Version 1.30: Add capsule_id attribute
VERSION = '1.30'
fields = {
'id': fields.IntegerField(),
@ -96,6 +97,7 @@ class Container(base.ZunPersistentObject, base.ZunObject):
nullable=True),
'disk': fields.IntegerField(nullable=True),
'auto_heal': fields.BooleanField(nullable=True),
'capsule_id': fields.IntegerField(nullable=True),
}
@staticmethod
@ -172,6 +174,19 @@ class Container(base.ZunPersistentObject, base.ZunObject):
db_containers = dbapi.list_containers(context, filters={'host': host})
return Container._from_db_object_list(db_containers, cls, context)
@base.remotable_classmethod
def list_by_capsule_id(cls, context, capsule_id):
"""Return a list of Container objects by capsule_id.
:param context: Security context.
:param host: A capsule id.
:returns: a list of :class:`Container` object.
"""
db_containers = dbapi.list_containers(
context, filters={'capsule_id': capsule_id})
return Container._from_db_object_list(db_containers, cls, context)
@base.remotable
def create(self, context):
"""Create a Container record in the DB.

View File

@ -343,7 +343,7 @@ class TestCapsuleController(api_base.FunctionalTest):
mock_container_get_by_uuid.return_value = test_container_obj
mock_container_show.return_value = test_container_obj
test_capsule = utils.get_test_capsule()
test_capsule = utils.create_test_capsule(context=self.context)
test_capsule_obj = objects.Capsule(self.context, **test_capsule)
mock_capsule_get_by_uuid.return_value = test_capsule_obj
@ -366,7 +366,7 @@ class TestCapsuleController(api_base.FunctionalTest):
mock_container_get_by_uuid.return_value = test_container_obj
mock_container_show.return_value = test_container_obj
test_capsule = utils.get_test_capsule()
test_capsule = utils.create_test_capsule(context=self.context)
test_capsule_obj = objects.Capsule(self.context, **test_capsule)
mock_capsule_get_by_uuid.return_value = test_capsule_obj
@ -391,7 +391,7 @@ class TestCapsuleController(api_base.FunctionalTest):
test_container_obj = objects.Container(self.context, **test_container)
mock_container_get_by_uuid.return_value = test_container_obj
test_capsule = utils.get_test_capsule()
test_capsule = utils.create_test_capsule(context=self.context)
test_capsule_obj = objects.Capsule(self.context,
**test_capsule)
mock_capsule_get_by_uuid.return_value = test_capsule_obj
@ -422,7 +422,7 @@ class TestCapsuleController(api_base.FunctionalTest):
test_container_obj = objects.Container(self.context, **test_container)
mock_container_get_by_uuid.return_value = test_container_obj
test_capsule = utils.get_test_capsule()
test_capsule = utils.create_test_capsule(context=self.context)
test_capsule_obj = objects.Capsule(self.context,
**test_capsule)
mock_capsule_get_by_uuid.return_value = test_capsule_obj
@ -455,7 +455,7 @@ class TestCapsuleController(api_base.FunctionalTest):
test_container_obj = objects.Container(self.context, **test_container)
mock_container_get_by_name.return_value = test_container_obj
test_capsule = utils.get_test_capsule()
test_capsule = utils.create_test_capsule(context=self.context)
test_capsule_obj = objects.Capsule(self.context,
**test_capsule)
mock_capsule_get_by_uuid.return_value = test_capsule_obj
@ -482,7 +482,7 @@ class TestCapsuleController(api_base.FunctionalTest):
**test_container)
mock_container_get_by_uuid.return_value = test_container_obj
test_capsule = utils.get_test_capsule()
test_capsule = utils.create_test_capsule(context=self.context)
test_capsule_obj = objects.Capsule(self.context, **test_capsule)
mock_capsule_list.return_value = [test_capsule_obj]
mock_container_show.return_value = test_container_obj
@ -512,7 +512,7 @@ class TestCapsuleController(api_base.FunctionalTest):
**test_container)
mock_container_get_by_uuid.return_value = test_container_obj
test_capsule = utils.get_test_capsule()
test_capsule = utils.create_test_capsule(context=self.context)
test_capsule_obj = objects.Capsule(self.context, **test_capsule)
mock_capsule_list.return_value = [test_capsule_obj]
mock_container_show.return_value = test_container_obj
@ -540,7 +540,7 @@ class TestCapsuleController(api_base.FunctionalTest):
**test_container)
mock_container_get_by_uuid.return_value = test_container_obj
test_capsule = utils.get_test_capsule()
test_capsule = utils.create_test_capsule(context=self.context)
test_capsule_obj = objects.Capsule(self.context, **test_capsule)
mock_capsule_list.return_value = [test_capsule_obj]

View File

@ -99,7 +99,8 @@ def get_test_container(**kwargs):
'auto_remove': kwargs.get('auto_remove', False),
'runtime': kwargs.get('runtime', 'runc'),
'disk': kwargs.get('disk', 20),
'auto_heal': kwargs.get('auto_heal', False)
'auto_heal': kwargs.get('auto_heal', False),
'capsule_id': kwargs.get('capsule_id', 42),
}
@ -122,6 +123,8 @@ def create_test_container(**kwargs):
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kwargs:
del container['id']
if 'capsule_id' not in kwargs:
del container['capsule_id']
dbapi = _get_dbapi()
return dbapi.create_container(kwargs['context'], container)
@ -400,7 +403,7 @@ def get_test_capsule(**kwargs):
'meta_name': kwargs.get('meta_name', "fake-meta-name"),
'meta_labels': kwargs.get('meta_labels', {'key1': 'val1',
'key2': 'val2'}),
'containers': kwargs.get('container'),
'containers': kwargs.get('containers'),
'containers_uuids': kwargs.get(
'containers_uuids', ['ea8e2a25-2901-438d-8157-de7ffd68d051',
'6219e0fb-2935-4db2-a3c7-86a2ac3ac84e']),
@ -434,6 +437,8 @@ def create_test_capsule(**kwargs):
# Let DB generate ID if it isn't specified explicitly
if CONF.database.backend == 'sqlalchemy' and 'id' not in kwargs:
del capsule['id']
if 'containers' not in kwargs:
del capsule['containers']
dbapi = _get_dbapi()
return dbapi.create_capsule(kwargs['context'], capsule)

View File

@ -64,6 +64,7 @@ class TestCapsuleObject(base.DbTestCase):
def test_create(self):
with mock.patch.object(self.dbapi, 'create_capsule',
autospec=True) as mock_create_capsule:
self.fake_capsule.pop('containers')
mock_create_capsule.return_value = self.fake_capsule
capsule = objects.Capsule(self.context, **self.fake_capsule)
capsule.create(self.context)
@ -74,6 +75,7 @@ class TestCapsuleObject(base.DbTestCase):
def test_status_reason_in_fields(self):
with mock.patch.object(self.dbapi, 'create_capsule',
autospec=True) as mock_create_capsule:
self.fake_capsule.pop('containers')
mock_create_capsule.return_value = self.fake_capsule
capsule = objects.Capsule(self.context, **self.fake_capsule)
self.assertTrue(hasattr(capsule, 'status_reason'))

View File

@ -344,7 +344,7 @@ class TestObject(test_base.TestCase, _TestObject):
# For more information on object version testing, read
# https://docs.openstack.org/zun/latest/
object_data = {
'Container': '1.29-3c3e936451ab252e5d52e1331778de5f',
'Container': '1.30-fc17da52173f258245d424c9a5cf99c7',
'VolumeMapping': '1.1-50df6202f7846a136a91444c38eba841',
'Image': '1.1-330e6205c80b99b59717e1cfc6a79935',
'MyObj': '1.0-34c4b1aadefd177b13f9a2f894cc23cd',