From 0398d438376048b064ea98a7d97d3118580b48a0 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Mon, 13 Feb 2017 15:59:00 -0600 Subject: [PATCH] Add resource provider objects Spec: https://review.openstack.org/#/c/427007/ Change-Id: Iadeb659e6f778f480a99b0bd7d418dc044d236cd Implements: blueprint expose-host-capabilities --- zun/objects/__init__.py | 11 +- zun/objects/resource_provider.py | 161 ++++++++++++++++++ .../unit/objects/test_resource_provider.py | 137 +++++++++++++++ 3 files changed, 306 insertions(+), 3 deletions(-) create mode 100644 zun/objects/resource_provider.py create mode 100644 zun/tests/unit/objects/test_resource_provider.py diff --git a/zun/objects/__init__.py b/zun/objects/__init__.py index 85222e4b9..bb384eec1 100644 --- a/zun/objects/__init__.py +++ b/zun/objects/__init__.py @@ -13,12 +13,17 @@ from zun.objects import container from zun.objects import image +from zun.objects import resource_provider from zun.objects import zun_service Container = container.Container ZunService = zun_service.ZunService Image = image.Image +ResourceProvider = resource_provider.ResourceProvider -__all__ = (Container, - ZunService, - Image) +__all__ = ( + Container, + ZunService, + Image, + ResourceProvider, +) diff --git a/zun/objects/resource_provider.py b/zun/objects/resource_provider.py new file mode 100644 index 000000000..8396506af --- /dev/null +++ b/zun/objects/resource_provider.py @@ -0,0 +1,161 @@ +# 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. + +from oslo_versionedobjects import fields + +from zun.db import api as dbapi +from zun.objects import base + + +@base.ZunObjectRegistry.register +class ResourceProvider(base.ZunPersistentObject, base.ZunObject): + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'id': fields.IntegerField(read_only=True), + 'uuid': fields.UUIDField(nullable=False), + 'name': fields.StringField(nullable=False), + 'root_provider': fields.UUIDField(nullable=False), + 'parent_provider': fields.UUIDField(nullable=True), + 'can_host': fields.IntegerField(default=0), + } + + @staticmethod + def _from_db_object(provider, db_provider): + """Converts a database entity to a formal object.""" + for field in provider.fields: + setattr(provider, field, db_provider[field]) + + provider.obj_reset_changes() + return provider + + @staticmethod + def _from_db_object_list(db_objects, cls, context): + """Converts a list of database entities to a list of formal objects.""" + return [ResourceProvider._from_db_object(cls(context), obj) + for obj in db_objects] + + @base.remotable_classmethod + def get_by_uuid(cls, context, uuid): + """Find a resource provider based on uuid. + + :param uuid: the uuid of a resource provider. + :param context: Security context + :returns: a :class:`ResourceProvider` object. + """ + db_provider = dbapi.get_resource_provider(context, uuid) + provider = ResourceProvider._from_db_object(cls(context), db_provider) + return provider + + @base.remotable_classmethod + def get_by_name(cls, context, name): + """Find a resource provider based on name. + + :param name: the logical name of a resource provider. + :param context: Security context + :returns: a :class:`ResourceProvider` object. + """ + db_provider = dbapi.get_resource_provider(context, name) + provider = ResourceProvider._from_db_object(cls(context), db_provider) + return provider + + @base.remotable_classmethod + def list(cls, context, limit=None, marker=None, + sort_key=None, sort_dir=None, filters=None): + """Return a list of ResourceProvider objects. + + :param context: Security context. + :param limit: maximum number of resources to return in a single result. + :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 resource providers. + :returns: a list of :class:`ResourceProvider` object. + + """ + db_providers = dbapi.list_resource_providers( + context, limit=limit, marker=marker, sort_key=sort_key, + sort_dir=sort_dir, filters=filters) + return ResourceProvider._from_db_object_list( + db_providers, cls, context) + + @base.remotable + def create(self, context): + """Create a ResourceProvider record in the DB. + + :param context: Security context. NOTE: This should only + be used internally by the indirection_api. + Unfortunately, RPC requires context as the first + argument, even though we don't use it. + A context should be set when instantiating the + object, e.g.: ResourceProvider(context) + + """ + values = self.obj_get_changes() + db_provider = dbapi.create_resource_provider(context, values) + self._from_db_object(self, db_provider) + + @base.remotable + def destroy(self, context=None): + """Delete the ResourceProvider from the DB. + + :param context: Security context. NOTE: This should only + be used internally by the indirection_api. + Unfortunately, RPC requires context as the first + argument, even though we don't use it. + A context should be set when instantiating the + object, e.g.: ResourceProvider(context) + """ + dbapi.destroy_resource_provider(context, self.uuid) + self.obj_reset_changes() + + @base.remotable + def save(self, context=None): + """Save updates to this ResourceProvider. + + Updates will be made column by column based on the result + of self.what_changed(). + + :param context: Security context. NOTE: This should only + be used internally by the indirection_api. + Unfortunately, RPC requires context as the first + argument, even though we don't use it. + A context should be set when instantiating the + object, e.g.: ResourceProvider(context) + """ + updates = self.obj_get_changes() + dbapi.update_resource_provider(context, self.uuid, updates) + + self.obj_reset_changes() + + @base.remotable + def refresh(self, context=None): + """Loads updates for this ResourceProvider. + + Loads a resource provider with the same uuid from the database and + checks for updated attributes. Updates are applied from + the loaded resource provider column by column, if there are any + updates. + + :param context: Security context. NOTE: This should only + be used internally by the indirection_api. + Unfortunately, RPC requires context as the first + argument, even though we don't use it. + A context should be set when instantiating the + object, e.g.: ResourceProvider(context) + """ + current = self.__class__.get_by_uuid(self._context, uuid=self.uuid) + for field in self.fields: + if self.obj_attr_is_set(field) and \ + getattr(self, field) != getattr(current, field): + setattr(self, field, getattr(current, field)) diff --git a/zun/tests/unit/objects/test_resource_provider.py b/zun/tests/unit/objects/test_resource_provider.py new file mode 100644 index 000000000..56b3415da --- /dev/null +++ b/zun/tests/unit/objects/test_resource_provider.py @@ -0,0 +1,137 @@ +# 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. + +import mock + +from oslo_utils import uuidutils +from testtools.matchers import HasLength + +from zun import objects +from zun.tests.unit.db import base +from zun.tests.unit.db import utils + + +class TestResourceProviderObject(base.DbTestCase): + + def setUp(self): + super(TestResourceProviderObject, self).setUp() + self.fake_provider = utils.get_test_resource_provider() + + def test_get_by_uuid(self): + uuid = self.fake_provider['uuid'] + with mock.patch.object(self.dbapi, 'get_resource_provider', + autospec=True) as mock_get_resource_provider: + mock_get_resource_provider.return_value = self.fake_provider + provider = objects.ResourceProvider.get_by_uuid(self.context, uuid) + mock_get_resource_provider.assert_called_once_with( + self.context, uuid) + self.assertEqual(self.context, provider._context) + + def test_get_by_name(self): + name = self.fake_provider['name'] + with mock.patch.object(self.dbapi, 'get_resource_provider', + autospec=True) as mock_get_resource_provider: + mock_get_resource_provider.return_value = self.fake_provider + provider = objects.ResourceProvider.get_by_name(self.context, name) + mock_get_resource_provider.assert_called_once_with( + self.context, name) + self.assertEqual(self.context, provider._context) + + def test_list(self): + with mock.patch.object(self.dbapi, 'list_resource_providers', + autospec=True) as mock_get_list: + mock_get_list.return_value = [self.fake_provider] + providers = objects.ResourceProvider.list(self.context) + self.assertEqual(1, mock_get_list.call_count) + self.assertThat(providers, HasLength(1)) + self.assertIsInstance(providers[0], objects.ResourceProvider) + self.assertEqual(self.context, providers[0]._context) + + def test_list_with_filters(self): + with mock.patch.object(self.dbapi, 'list_resource_providers', + autospec=True) as mock_get_list: + mock_get_list.return_value = [self.fake_provider] + filt = {'name': 'testprovider'} + providers = objects.ResourceProvider.list( + self.context, filters=filt) + self.assertEqual(1, mock_get_list.call_count) + self.assertThat(providers, HasLength(1)) + self.assertIsInstance(providers[0], objects.ResourceProvider) + self.assertEqual(self.context, providers[0]._context) + mock_get_list.assert_called_once_with( + self.context, filters=filt, limit=None, marker=None, + sort_key=None, sort_dir=None) + + def test_create(self): + with mock.patch.object(self.dbapi, 'create_resource_provider', + autospec=True) as mock_create: + mock_create.return_value = self.fake_provider + provider = objects.ResourceProvider( + self.context, **self.fake_provider) + provider.create(self.context) + mock_create.assert_called_once_with( + self.context, self.fake_provider) + self.assertEqual(self.context, provider._context) + + def test_destroy(self): + uuid = self.fake_provider['uuid'] + with mock.patch.object(self.dbapi, 'get_resource_provider', + autospec=True) as mock_get_resource_provider: + mock_get_resource_provider.return_value = self.fake_provider + with mock.patch.object(self.dbapi, 'destroy_resource_provider', + autospec=True) as mock_destroy: + provider = objects.ResourceProvider.get_by_uuid( + self.context, uuid) + provider.destroy() + mock_get_resource_provider.assert_called_once_with( + self.context, uuid) + mock_destroy.assert_called_once_with(None, uuid) + self.assertEqual(self.context, provider._context) + + def test_save(self): + uuid = self.fake_provider['uuid'] + with mock.patch.object(self.dbapi, 'get_resource_provider', + autospec=True) as mock_get_resource_provider: + mock_get_resource_provider.return_value = self.fake_provider + with mock.patch.object(self.dbapi, 'update_resource_provider', + autospec=True) as mock_update: + provider = objects.ResourceProvider.get_by_uuid( + self.context, uuid) + provider.name = 'provider2' + provider.root_provider = '09d0fcb9-155e-434a-ad76-3620b6382a37' + provider.save() + + mock_get_resource_provider.assert_called_once_with( + self.context, uuid) + mock_update.assert_called_once_with( + None, uuid, + {'name': 'provider2', + 'root_provider': '09d0fcb9-155e-434a-ad76-3620b6382a37'}) + self.assertEqual(self.context, provider._context) + + def test_refresh(self): + uuid = self.fake_provider['uuid'] + new_uuid = uuidutils.generate_uuid() + returns = [dict(self.fake_provider, uuid=uuid), + dict(self.fake_provider, uuid=new_uuid)] + expected = [mock.call(self.context, uuid), + mock.call(self.context, uuid)] + with mock.patch.object(self.dbapi, 'get_resource_provider', + side_effect=returns, + autospec=True) as mock_get_resource_provider: + provider = objects.ResourceProvider.get_by_uuid(self.context, uuid) + self.assertEqual(uuid, provider.uuid) + provider.refresh() + self.assertEqual(new_uuid, provider.uuid) + self.assertEqual( + expected, mock_get_resource_provider.call_args_list) + self.assertEqual(self.context, provider._context)