Add capsule method list

Part of blueprint introduce-compose

Change-Id: I574a0cb9630c4f789c620e2d498373cd927976ba
Signed-off-by: Kevin Zhao <kevin.zhao@arm.com>
This commit is contained in:
Kevin Zhao 2017-09-21 17:54:54 +08:00 committed by Hongbin Lu
parent 24cd5b021d
commit d5983c4d28
8 changed files with 263 additions and 18 deletions

View File

@ -5,8 +5,8 @@ kind: capsule
metadata: metadata:
name: capsule-example name: capsule-example
labels: labels:
- app: web app: web
- nihao: baibai nihao: baibai
restart_policy: always restart_policy: always
spec: spec:
containers: containers:

View File

@ -13,7 +13,9 @@
# under the License. # under the License.
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import strutils
import pecan import pecan
import six
from zun.api.controllers import base from zun.api.controllers import base
from zun.api.controllers.experimental import collection from zun.api.controllers.experimental import collection
@ -76,6 +78,70 @@ class CapsuleController(base.Controller):
} }
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def get_all(self, **kwargs):
'''Retrieve a list of capsules.'''
context = pecan.request.context
policy.enforce(context, "capsule:get_all",
action="capsule:get_all")
return self._get_capsules_collection(**kwargs)
def _get_capsules_collection(self, **kwargs):
context = pecan.request.context
all_tenants = kwargs.get('all_tenants')
if all_tenants:
try:
all_tenants = strutils.bool_from_string(all_tenants, True)
except ValueError as err:
raise exception.InvalidInput(six.text_type(err))
else:
# If no value, it's considered to disable all_tenants
all_tenants = False
if all_tenants:
context.all_tenants = True
compute_api = pecan.request.compute_api
limit = api_utils.validate_limit(kwargs.get('limit'))
sort_dir = api_utils.validate_sort_dir(kwargs.get('sort_dir', 'asc'))
sort_key = kwargs.get('sort_key', 'id')
resource_url = kwargs.get('resource_url')
expand = kwargs.get('expand')
filters = None
marker_obj = None
marker = kwargs.get('marker')
if marker:
marker_obj = objects.Capsule.get_by_uuid(context,
marker)
capsules = objects.Capsule.list(context,
limit,
marker_obj,
sort_key,
sort_dir,
filters=filters)
# Sync status for container inside capsule
for i, capsule in enumerate(capsules):
try:
containers_list = capsule.containers_uuids
if containers_list is not None:
# Capsule is depending on infra container status
uuid = containers_list[0]
container = utils.get_container(uuid)
container = compute_api.container_show(context, container)
capsule.status = container.status
capsule.save(context)
except Exception as e:
LOG.exception(("Error while list capsule %(uuid)s: "
"%(e)s."),
{'uuid': capsule.uuid, 'e': e})
capsules[i].status = consts.UNKNOWN
return CapsuleCollection.convert_with_links(capsules, limit,
url=resource_url,
expand=expand,
sort_key=sort_key,
sort_dir=sort_dir)
@pecan.expose('json') @pecan.expose('json')
@api_utils.enforce_content_types(['application/json']) @api_utils.enforce_content_types(['application/json'])
@exception.wrap_pecan_controller_exception @exception.wrap_pecan_controller_exception

View File

@ -0,0 +1,37 @@
# Copyright 2017 Arm Limited.
#
# 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.
"""change the properties of meta_labels
Revision ID: fc27c7415d9c
Revises: bcd6410d645e
Create Date: 2017-09-07 10:56:07.489031
"""
# revision identifiers, used by Alembic.
revision = 'fc27c7415d9c'
down_revision = 'bcd6410d645e'
branch_labels = None
depends_on = None
from alembic import op
import zun
def upgrade():
op.alter_column('capsule', 'meta_labels',
type_=zun.db.sqlalchemy.models.JSONEncodedDict()
)

View File

@ -340,7 +340,7 @@ class Capsule(Base):
status = Column(String(20)) status = Column(String(20))
status_reason = Column(Text, nullable=True) status_reason = Column(Text, nullable=True)
meta_labels = Column(JSONEncodedList) meta_labels = Column(JSONEncodedDict)
meta_name = Column(String(255)) meta_name = Column(String(255))
spec = Column(JSONEncodedDict) spec = Column(JSONEncodedDict)
containers_uuids = Column(JSONEncodedList) containers_uuids = Column(JSONEncodedList)

View File

@ -23,7 +23,8 @@ from zun.objects import fields as z_fields
class Capsule(base.ZunPersistentObject, base.ZunObject): class Capsule(base.ZunPersistentObject, base.ZunObject):
# Version 1.0: Initial version # Version 1.0: Initial version
# Version 1.1: Add host to capsule # Version 1.1: Add host to capsule
VERSION = '1.1' # Version 1.2: Change the properties of meta_labels
VERSION = '1.2'
fields = { fields = {
'capsule_version': fields.StringField(nullable=True), 'capsule_version': fields.StringField(nullable=True),
@ -48,7 +49,7 @@ class Capsule(base.ZunPersistentObject, base.ZunObject):
'spec': z_fields.JsonField(nullable=True), 'spec': z_fields.JsonField(nullable=True),
'meta_name': fields.StringField(nullable=True), 'meta_name': fields.StringField(nullable=True),
'meta_labels': z_fields.JsonField(nullable=True), 'meta_labels': fields.DictOfStringsField(nullable=True),
'containers': fields.ListOfObjectsField('Container', nullable=True), 'containers': fields.ListOfObjectsField('Container', nullable=True),
'containers_uuids': fields.ListOfStringsField(nullable=True), 'containers_uuids': fields.ListOfStringsField(nullable=True),
'host': fields.StringField(nullable=True), 'host': fields.StringField(nullable=True),

View File

@ -12,9 +12,11 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import mock
from mock import patch from mock import patch
from oslo_utils import uuidutils from oslo_utils import uuidutils
from webtest.app import AppError from webtest.app import AppError
from zun.common import consts
from zun.common import exception from zun.common import exception
from zun import objects from zun import objects
from zun.tests.unit.api import base as api_base from zun.tests.unit.api import base as api_base
@ -34,20 +36,19 @@ class TestCapsuleController(api_base.FunctionalTest):
'"image": "test", "drivers": "cinder", "volumeType": ' '"image": "test", "drivers": "cinder", "volumeType": '
'"type1", "driverOptions": "options", ' '"type1", "driverOptions": "options", '
'"size": "5GB"}]}, ' '"size": "5GB"}]}, '
'"metadata": {"labels": [{"foo0": "bar0"}, ' '"metadata": {"labels": {"foo0": "bar0", "foo1": "bar1"}, '
'{"foo1": "bar1"}], '
'"name": "capsule-example"}}}') '"name": "capsule-example"}}}')
response = self.post('/capsules/', response = self.post('/capsules/',
params=params, params=params,
content_type='application/json') content_type='application/json')
return_value = response.json return_value = response.json
expected_meta_name = "capsule-example" expected_meta_name = "capsule-example"
expected_meta_label = [{"foo0": "bar0"}, {"foo1": "bar1"}] expected_meta_labels = {"foo0": "bar0", "foo1": "bar1"}
expected_container_num = 2 expected_container_num = 2
self.assertEqual(len(return_value["containers_uuids"]), self.assertEqual(len(return_value["containers_uuids"]),
expected_container_num) expected_container_num)
self.assertEqual(return_value["meta_name"], expected_meta_name) self.assertEqual(return_value["meta_name"], expected_meta_name)
self.assertEqual(return_value["meta_labels"], expected_meta_label) self.assertEqual(return_value["meta_labels"], expected_meta_labels)
self.assertEqual(202, response.status_int) self.assertEqual(202, response.status_int)
self.assertTrue(mock_capsule_create.called) self.assertTrue(mock_capsule_create.called)
@ -63,22 +64,21 @@ class TestCapsuleController(api_base.FunctionalTest):
'"image": "test1", "labels": {"app1": "web1"}, ' '"image": "test1", "labels": {"app1": "web1"}, '
'"image_driver": "docker", "resources": ' '"image_driver": "docker", "resources": '
'{"allocation": {"cpu": 1, "memory": 1024}}}]}, ' '{"allocation": {"cpu": 1, "memory": 1024}}}]}, '
'"metadata": {"labels": [{"foo0": "bar0"}, ' '"metadata": {"labels": {"foo0": "bar0", "foo1": "bar1"}, '
'{"foo1": "bar1"}], '
'"name": "capsule-example"}}}') '"name": "capsule-example"}}}')
response = self.post('/capsules/', response = self.post('/capsules/',
params=params, params=params,
content_type='application/json') content_type='application/json')
return_value = response.json return_value = response.json
expected_meta_name = "capsule-example" expected_meta_name = "capsule-example"
expected_meta_label = [{"foo0": "bar0"}, {"foo1": "bar1"}] expected_meta_labels = {"foo0": "bar0", "foo1": "bar1"}
expected_container_num = 3 expected_container_num = 3
self.assertEqual(len(return_value["containers_uuids"]), self.assertEqual(len(return_value["containers_uuids"]),
expected_container_num) expected_container_num)
self.assertEqual(return_value["meta_name"], self.assertEqual(return_value["meta_name"],
expected_meta_name) expected_meta_name)
self.assertEqual(return_value["meta_labels"], self.assertEqual(return_value["meta_labels"],
expected_meta_label) expected_meta_labels)
self.assertEqual(202, response.status_int) self.assertEqual(202, response.status_int)
self.assertTrue(mock_capsule_create.called) self.assertTrue(mock_capsule_create.called)
@ -92,7 +92,7 @@ class TestCapsuleController(api_base.FunctionalTest):
'"image": "test1", "labels": {"app0": "web0"}, ' '"image": "test1", "labels": {"app0": "web0"}, '
'"image_driver": "docker", "resources": ' '"image_driver": "docker", "resources": '
'{"allocation": {"cpu": 1, "memory": 1024}}}]}, ' '{"allocation": {"cpu": 1, "memory": 1024}}}]}, '
'"metadata": {"labels": [{"foo0": "bar0"}], ' '"metadata": {"labels": {"foo0": "bar0"}, '
'"name": "capsule-example"}}}') '"name": "capsule-example"}}}')
mock_check_template.side_effect = exception.InvalidCapsuleTemplate( mock_check_template.side_effect = exception.InvalidCapsuleTemplate(
"kind fields need to be set as capsule or Capsule") "kind fields need to be set as capsule or Capsule")
@ -106,7 +106,7 @@ class TestCapsuleController(api_base.FunctionalTest):
mock_capsule_create): mock_capsule_create):
params = ('{"spec": {"kind": "capsule",' params = ('{"spec": {"kind": "capsule",'
'"spec": {container:[]}, ' '"spec": {container:[]}, '
'"metadata": {"labels": [{"foo0": "bar0"}], ' '"metadata": {"labels": {"foo0": "bar0"}, '
'"name": "capsule-example"}}}') '"name": "capsule-example"}}}')
mock_check_template.side_effect = exception.InvalidCapsuleTemplate( mock_check_template.side_effect = exception.InvalidCapsuleTemplate(
"Capsule need to have one container at least") "Capsule need to have one container at least")
@ -120,7 +120,7 @@ class TestCapsuleController(api_base.FunctionalTest):
mock_capsule_create): mock_capsule_create):
params = ('{"spec": {"kind": "capsule",' params = ('{"spec": {"kind": "capsule",'
'"spec": {}, ' '"spec": {}, '
'"metadata": {"labels": [{"foo0": "bar0"}], ' '"metadata": {"labels": {"foo0": "bar0"}, '
'"name": "capsule-example"}}}') '"name": "capsule-example"}}}')
mock_check_template.side_effect = exception.InvalidCapsuleTemplate( mock_check_template.side_effect = exception.InvalidCapsuleTemplate(
"Capsule need to have one container at least") "Capsule need to have one container at least")
@ -135,7 +135,7 @@ class TestCapsuleController(api_base.FunctionalTest):
params = ('{"spec": {"kind": "capsule",' params = ('{"spec": {"kind": "capsule",'
'"spec": {container:[{"environment": ' '"spec": {container:[{"environment": '
'{"ROOT_PASSWORD": "foo1"}]}, ' '{"ROOT_PASSWORD": "foo1"}]}, '
'"metadata": {"labels": [{"foo0": "bar0"}], ' '"metadata": {"labels": {"foo0": "bar0"}, '
'"name": "capsule-example"}}}') '"name": "capsule-example"}}}')
mock_check_template.side_effect = exception.InvalidCapsuleTemplate( mock_check_template.side_effect = exception.InvalidCapsuleTemplate(
"Container image is needed") "Container image is needed")
@ -278,3 +278,129 @@ class TestCapsuleController(api_base.FunctionalTest):
self.assertEqual(204, response.status_int) self.assertEqual(204, response.status_int)
context = mock_capsule_save.call_args[0][0] context = mock_capsule_save.call_args[0][0]
self.assertIs(False, context.all_tenants) self.assertIs(False, context.all_tenants)
@patch('zun.compute.api.API.container_show')
@patch('zun.objects.Capsule.list')
@patch('zun.objects.Container.get_by_uuid')
def test_get_all_capsules(self, mock_container_get_by_uuid,
mock_capsule_list,
mock_container_show):
test_container = utils.get_test_container()
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_obj = objects.Capsule(self.context, **test_capsule)
mock_capsule_list.return_value = [test_capsule_obj]
mock_container_show.return_value = test_container_obj
response = self.app.get('/capsules/')
mock_capsule_list.assert_called_once_with(mock.ANY,
1000, None, 'id', 'asc',
filters=None)
context = mock_capsule_list.call_args[0][0]
self.assertIs(False, context.all_tenants)
self.assertEqual(200, response.status_int)
actual_capsules = response.json['capsules']
self.assertEqual(1, len(actual_capsules))
self.assertEqual(test_capsule['uuid'],
actual_capsules[0].get('uuid'))
@patch('zun.compute.api.API.container_show')
@patch('zun.objects.Capsule.list')
@patch('zun.objects.Container.get_by_uuid')
def test_get_all_capsules_all_tenants(self,
mock_container_get_by_uuid,
mock_capsule_list,
mock_container_show):
test_container = utils.get_test_container()
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_obj = objects.Capsule(self.context, **test_capsule)
mock_capsule_list.return_value = [test_capsule_obj]
mock_container_show.return_value = test_container_obj
response = self.app.get('/capsules/?all_tenants=1')
mock_capsule_list.assert_called_once_with(mock.ANY,
1000, None, 'id', 'asc',
filters=None)
context = mock_capsule_list.call_args[0][0]
self.assertIs(True, context.all_tenants)
self.assertEqual(200, response.status_int)
actual_capsules = response.json['capsules']
self.assertEqual(1, len(actual_capsules))
self.assertEqual(test_capsule['uuid'],
actual_capsules[0].get('uuid'))
@patch('zun.compute.api.API.container_show')
@patch('zun.objects.Capsule.list')
@patch('zun.objects.Container.get_by_uuid')
def test_get_all_capsules_with_exception(self,
mock_container_get_by_uuid,
mock_capsule_list,
mock_container_show):
test_container = utils.get_test_container()
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_obj = objects.Capsule(self.context, **test_capsule)
mock_capsule_list.return_value = [test_capsule_obj]
mock_container_show.side_effect = Exception
response = self.app.get('/capsules/')
mock_capsule_list.assert_called_once_with(mock.ANY,
1000, None, 'id', 'asc',
filters=None)
context = mock_capsule_list.call_args[0][0]
self.assertIs(False, context.all_tenants)
self.assertEqual(200, response.status_int)
actual_capsules = response.json['capsules']
self.assertEqual(1, len(actual_capsules))
self.assertEqual(test_capsule['uuid'],
actual_capsules[0].get('uuid'))
self.assertEqual(consts.UNKNOWN,
actual_capsules[0].get('status'))
@patch('zun.compute.api.API.container_show')
@patch('zun.objects.Capsule.list')
@patch('zun.objects.Capsule.save')
@patch('zun.objects.Container.get_by_uuid')
def test_get_all_capsules_with_pagination_marker(
self,
mock_container_get_by_uuid,
mock_capsule_save,
mock_capsule_list,
mock_container_show):
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context,
**test_container)
mock_container_get_by_uuid.return_value = test_container_obj
capsule_list = []
for id_ in range(4):
test_capsule = utils.create_test_capsule(
id=id_, uuid=uuidutils.generate_uuid(),
name='capsule' + str(id_), context=self.context)
capsule_list.append(objects.Capsule(self.context,
**test_capsule))
mock_capsule_list.return_value = capsule_list[-1:]
mock_container_show.return_value = capsule_list[-1]
mock_capsule_save.return_value = True
response = self.app.get('/capsules/?limit=3&marker=%s'
% capsule_list[2].uuid)
self.assertEqual(200, response.status_int)
actual_capsules = response.json['capsules']
self.assertEqual(1, len(actual_capsules))
self.assertEqual(actual_capsules[-1].get('uuid'),
actual_capsules[0].get('uuid'))

View File

@ -379,3 +379,18 @@ def get_test_capsule(**kwargs):
'6219e0fb-2935-4db2-a3c7-86a2ac3ac84e']), '6219e0fb-2935-4db2-a3c7-86a2ac3ac84e']),
'host': kwargs.get('host', 'localhost'), 'host': kwargs.get('host', 'localhost'),
} }
def create_test_capsule(**kwargs):
"""Create test capsule entry in DB and return Capsule DB object.
Function to be used to create test Capsule objects in the database.
:param kwargs: kwargs with overriding values for capsule's attributes.
:returns: Test Capsule DB object.
"""
capsule = get_test_capsule(**kwargs)
# Let DB generate ID if it isn't specified explicitly
if CONF.db_type == 'sql' and 'id' not in kwargs:
del capsule['id']
dbapi = db_api._get_dbdriver_instance()
return dbapi.create_capsule(kwargs['context'], capsule)

View File

@ -354,7 +354,7 @@ object_data = {
'ResourceProvider': '1.0-92b427359d5a4cf9ec6c72cbe630ee24', 'ResourceProvider': '1.0-92b427359d5a4cf9ec6c72cbe630ee24',
'ZunService': '1.1-b1549134bfd5271daec417ca8cabc77e', 'ZunService': '1.1-b1549134bfd5271daec417ca8cabc77e',
'ComputeNode': '1.7-9b700eb146e9978d84e9ccc5849d90e2', 'ComputeNode': '1.7-9b700eb146e9978d84e9ccc5849d90e2',
'Capsule': '1.1-bbf2165650900e7d79d1c12a12464b59', 'Capsule': '1.2-1f8b3716ef272c9d9cb55390f6a7cdc3',
} }