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:
name: capsule-example
labels:
- app: web
- nihao: baibai
app: web
nihao: baibai
restart_policy: always
spec:
containers:

View File

@ -13,7 +13,9 @@
# under the License.
from oslo_log import log as logging
from oslo_utils import strutils
import pecan
import six
from zun.api.controllers import base
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')
@api_utils.enforce_content_types(['application/json'])
@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_reason = Column(Text, nullable=True)
meta_labels = Column(JSONEncodedList)
meta_labels = Column(JSONEncodedDict)
meta_name = Column(String(255))
spec = Column(JSONEncodedDict)
containers_uuids = Column(JSONEncodedList)

View File

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

View File

@ -12,9 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from mock import patch
from oslo_utils import uuidutils
from webtest.app import AppError
from zun.common import consts
from zun.common import exception
from zun import objects
from zun.tests.unit.api import base as api_base
@ -34,20 +36,19 @@ class TestCapsuleController(api_base.FunctionalTest):
'"image": "test", "drivers": "cinder", "volumeType": '
'"type1", "driverOptions": "options", '
'"size": "5GB"}]}, '
'"metadata": {"labels": [{"foo0": "bar0"}, '
'{"foo1": "bar1"}], '
'"metadata": {"labels": {"foo0": "bar0", "foo1": "bar1"}, '
'"name": "capsule-example"}}}')
response = self.post('/capsules/',
params=params,
content_type='application/json')
return_value = response.json
expected_meta_name = "capsule-example"
expected_meta_label = [{"foo0": "bar0"}, {"foo1": "bar1"}]
expected_meta_labels = {"foo0": "bar0", "foo1": "bar1"}
expected_container_num = 2
self.assertEqual(len(return_value["containers_uuids"]),
expected_container_num)
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.assertTrue(mock_capsule_create.called)
@ -63,22 +64,21 @@ class TestCapsuleController(api_base.FunctionalTest):
'"image": "test1", "labels": {"app1": "web1"}, '
'"image_driver": "docker", "resources": '
'{"allocation": {"cpu": 1, "memory": 1024}}}]}, '
'"metadata": {"labels": [{"foo0": "bar0"}, '
'{"foo1": "bar1"}], '
'"metadata": {"labels": {"foo0": "bar0", "foo1": "bar1"}, '
'"name": "capsule-example"}}}')
response = self.post('/capsules/',
params=params,
content_type='application/json')
return_value = response.json
expected_meta_name = "capsule-example"
expected_meta_label = [{"foo0": "bar0"}, {"foo1": "bar1"}]
expected_meta_labels = {"foo0": "bar0", "foo1": "bar1"}
expected_container_num = 3
self.assertEqual(len(return_value["containers_uuids"]),
expected_container_num)
self.assertEqual(return_value["meta_name"],
expected_meta_name)
self.assertEqual(return_value["meta_labels"],
expected_meta_label)
expected_meta_labels)
self.assertEqual(202, response.status_int)
self.assertTrue(mock_capsule_create.called)
@ -92,7 +92,7 @@ class TestCapsuleController(api_base.FunctionalTest):
'"image": "test1", "labels": {"app0": "web0"}, '
'"image_driver": "docker", "resources": '
'{"allocation": {"cpu": 1, "memory": 1024}}}]}, '
'"metadata": {"labels": [{"foo0": "bar0"}], '
'"metadata": {"labels": {"foo0": "bar0"}, '
'"name": "capsule-example"}}}')
mock_check_template.side_effect = exception.InvalidCapsuleTemplate(
"kind fields need to be set as capsule or Capsule")
@ -106,7 +106,7 @@ class TestCapsuleController(api_base.FunctionalTest):
mock_capsule_create):
params = ('{"spec": {"kind": "capsule",'
'"spec": {container:[]}, '
'"metadata": {"labels": [{"foo0": "bar0"}], '
'"metadata": {"labels": {"foo0": "bar0"}, '
'"name": "capsule-example"}}}')
mock_check_template.side_effect = exception.InvalidCapsuleTemplate(
"Capsule need to have one container at least")
@ -120,7 +120,7 @@ class TestCapsuleController(api_base.FunctionalTest):
mock_capsule_create):
params = ('{"spec": {"kind": "capsule",'
'"spec": {}, '
'"metadata": {"labels": [{"foo0": "bar0"}], '
'"metadata": {"labels": {"foo0": "bar0"}, '
'"name": "capsule-example"}}}')
mock_check_template.side_effect = exception.InvalidCapsuleTemplate(
"Capsule need to have one container at least")
@ -135,7 +135,7 @@ class TestCapsuleController(api_base.FunctionalTest):
params = ('{"spec": {"kind": "capsule",'
'"spec": {container:[{"environment": '
'{"ROOT_PASSWORD": "foo1"}]}, '
'"metadata": {"labels": [{"foo0": "bar0"}], '
'"metadata": {"labels": {"foo0": "bar0"}, '
'"name": "capsule-example"}}}')
mock_check_template.side_effect = exception.InvalidCapsuleTemplate(
"Container image is needed")
@ -278,3 +278,129 @@ class TestCapsuleController(api_base.FunctionalTest):
self.assertEqual(204, response.status_int)
context = mock_capsule_save.call_args[0][0]
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']),
'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',
'ZunService': '1.1-b1549134bfd5271daec417ca8cabc77e',
'ComputeNode': '1.7-9b700eb146e9978d84e9ccc5849d90e2',
'Capsule': '1.1-bbf2165650900e7d79d1c12a12464b59',
'Capsule': '1.2-1f8b3716ef272c9d9cb55390f6a7cdc3',
}