Merge "Add containers dict to capsule"
This commit is contained in:
commit
77c8388e96
@ -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')
|
||||
|
@ -34,7 +34,8 @@ _basic_keys = (
|
||||
'containers_uuids',
|
||||
'capsule_version',
|
||||
'memory',
|
||||
'cpu'
|
||||
'cpu',
|
||||
'containers',
|
||||
)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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))
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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])
|
||||
|
@ -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.
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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'))
|
||||
|
@ -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',
|
||||
|
Loading…
x
Reference in New Issue
Block a user