From a95ffbc6682f8d77129a5a8866d716af6ae3354a Mon Sep 17 00:00:00 2001 From: Niraj Singh Date: Thu, 8 Aug 2019 09:19:42 +0000 Subject: [PATCH] Implement rpc conductor for vnf packages Added new RPC APIs for uploading and deleting vnf packages. Partial-Implements: blueprint tosca-csar-mgmt-driver Co-Author: Neha Alhat Change-Id: Ia5051f3c1bf2ce5f45c94711a67be5f9a8ef21d0 --- tacker/conductor/conductorrpc/vnf_pkgm_rpc.py | 63 +++++++++++++++ tacker/objects/base.py | 59 ++++++++++++++ .../conductorrpc/test_vnf_pkgm_rpc.py | 81 +++++++++++++++++++ tacker/tests/unit/conductor/fakes.py | 42 ++++++++++ 4 files changed, 245 insertions(+) create mode 100644 tacker/conductor/conductorrpc/vnf_pkgm_rpc.py create mode 100644 tacker/tests/unit/conductor/conductorrpc/test_vnf_pkgm_rpc.py create mode 100644 tacker/tests/unit/conductor/fakes.py diff --git a/tacker/conductor/conductorrpc/vnf_pkgm_rpc.py b/tacker/conductor/conductorrpc/vnf_pkgm_rpc.py new file mode 100644 index 000000000..f8f7d2389 --- /dev/null +++ b/tacker/conductor/conductorrpc/vnf_pkgm_rpc.py @@ -0,0 +1,63 @@ +# Copyright (C) 2019 NTT DATA +# All Rights Reserved. +# +# 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 oslo_messaging + +from tacker.common import rpc +from tacker.common import topics +from tacker.objects import base as objects_base + + +class VNFPackageRPCAPI(object): + + target = oslo_messaging.Target( + exchange='tacker', + topic=topics.TOPIC_CONDUCTOR, + fanout=False, + version='1.0') + + def upload_vnf_package_content(self, context, vnf_package, cast=True): + serializer = objects_base.TackerObjectSerializer() + + client = rpc.get_client(self.target, version_cap=None, + serializer=serializer) + cctxt = client.prepare() + rpc_method = cctxt.cast if cast else cctxt.call + return rpc_method(context, 'upload_vnf_package_content', + vnf_package=vnf_package) + + def upload_vnf_package_from_uri(self, context, vnf_package, + address_information, user_name=None, + password=None, cast=True): + serializer = objects_base.TackerObjectSerializer() + + client = rpc.get_client(self.target, version_cap=None, + serializer=serializer) + cctxt = client.prepare() + rpc_method = cctxt.cast if cast else cctxt.call + return rpc_method(context, 'upload_vnf_package_from_uri', + vnf_package=vnf_package, + address_information=address_information, + user_name=user_name, password=password) + + def delete_vnf_package(self, context, vnf_package, cast=True): + serializer = objects_base.TackerObjectSerializer() + + client = rpc.get_client(self.target, version_cap=None, + serializer=serializer) + cctxt = client.prepare() + rpc_method = cctxt.cast if cast else cctxt.call + return rpc_method(context, 'delete_vnf_package', + vnf_package=vnf_package) diff --git a/tacker/objects/base.py b/tacker/objects/base.py index 1581bdd22..c699b0d68 100644 --- a/tacker/objects/base.py +++ b/tacker/objects/base.py @@ -14,8 +14,10 @@ import datetime +import oslo_messaging as messaging from oslo_utils import versionutils from oslo_versionedobjects import base as ovoo_base +from oslo_versionedobjects import exception as ovoo_exc from tacker import objects from tacker.objects import fields as obj_fields @@ -117,6 +119,63 @@ class TackerObject(ovoo_base.VersionedObject): self._changed_fields.clear() +class TackerObjectSerializer(messaging.NoOpSerializer): + """A TackerObject-aware Serializer. + + This implements the Oslo Serializer interface and provides the + ability to serialize and deserialize TackerObject entities. Any service + that needs to accept or return TackerObjects as arguments or result values + should pass this to its RPCClient and RPCServer objects. + """ + + def _process_object(self, context, objprim): + try: + objinst = TackerObject.obj_from_primitive(objprim, context=context) + except ovoo_exc.IncompatibleObjectVersion: + raise + return objinst + + def _process_iterable(self, context, action_fn, values): + """Process an iterable, taking an action on each value. + + :param:context: Request context + :param:action_fn: Action to take on each item in values + :param:values: Iterable container of things to take action on + :returns: A new container of the same type (except set) with + items from values having had action applied. + """ + iterable = values.__class__ + if issubclass(iterable, dict): + return iterable(**{k: action_fn(context, v) + for k, v in values.items()}) + else: + # NOTE(nirajsingh) A set can't have an unhashable value inside, + # such as a dict. Convert the set to list, which is fine, since we + # can't send them over RPC anyway. We convert it to list as this + # way there will be no semantic change between the fake rpc driver + # used in functional test and a normal rpc driver. + if iterable == set: + iterable = list + return iterable([action_fn(context, value) for value in values]) + + def serialize_entity(self, context, entity): + if isinstance(entity, (tuple, list, set, dict)): + entity = self._process_iterable(context, self.serialize_entity, + entity) + elif (hasattr(entity, 'obj_to_primitive') and + callable(entity.obj_to_primitive)): + entity = entity.obj_to_primitive() + return entity + + def deserialize_entity(self, context, entity): + if isinstance(entity, dict) and 'tacker_object.name' in entity: + entity = self._process_object(context, entity) + elif isinstance(entity, (tuple, list, set, dict)): + entity = self._process_iterable(context, self.deserialize_entity, + entity) + return entity + + class TackerPersistentObject(object): """Mixin class for Persistent objects. diff --git a/tacker/tests/unit/conductor/conductorrpc/test_vnf_pkgm_rpc.py b/tacker/tests/unit/conductor/conductorrpc/test_vnf_pkgm_rpc.py new file mode 100644 index 000000000..bda055215 --- /dev/null +++ b/tacker/tests/unit/conductor/conductorrpc/test_vnf_pkgm_rpc.py @@ -0,0 +1,81 @@ +# Copyright (C) 2019 NTT DATA +# All Rights Reserved. +# +# 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 tacker.common.rpc import BackingOffClient +from tacker.conductor.conductorrpc import vnf_pkgm_rpc +from tacker.objects import vnf_package +from tacker.tests import base +from tacker.tests.unit.conductor import fakes + + +class VnfPackageRPCTestCase(base.BaseTestCase): + + def setUp(self): + super(VnfPackageRPCTestCase, self).setUp() + self.context = self.fake_admin_context() + self.rpc_api = vnf_pkgm_rpc.VNFPackageRPCAPI() + self.cctxt_mock = mock.MagicMock() + + def test_upload_vnf_package_content(self): + + @mock.patch.object(BackingOffClient, 'prepare') + def _test(prepare_mock): + prepare_mock.return_value = self.cctxt_mock + vnf_package_obj = vnf_package.VnfPackage( + self.context, **fakes.VNF_UPLOAD_VNF_PACKAGE_CONTENT) + self.rpc_api.upload_vnf_package_content(self.context, + vnf_package_obj, cast=True) + prepare_mock.assert_called() + self.cctxt_mock.cast.assert_called_once_with( + self.context, 'upload_vnf_package_content', + vnf_package=vnf_package_obj) + _test() + + def test_upload_vnf_package_from_uri(self): + fake_addressInformation = "http://test_csar.zip" + + @mock.patch.object(BackingOffClient, 'prepare') + def _test(prepare_mock): + prepare_mock.return_value = self.cctxt_mock + vnf_package_obj = vnf_package.VnfPackage(self.context, + **fakes.VNF_DATA) + self.rpc_api.upload_vnf_package_from_uri(self.context, + vnf_package_obj, + fake_addressInformation, + cast=True) + prepare_mock.assert_called() + self.cctxt_mock.cast.assert_called_once_with( + self.context, 'upload_vnf_package_from_uri', + vnf_package=vnf_package_obj, + address_information=fake_addressInformation, + password=None, user_name=None) + _test() + + def test_delete_vnf_package(self): + + @mock.patch.object(BackingOffClient, 'prepare') + def _test(prepare_mock): + prepare_mock.return_value = self.cctxt_mock + vnf_package_obj = vnf_package.VnfPackage(self.context, + **fakes.VNF_DATA) + self.rpc_api.delete_vnf_package(self.context, + vnf_package_obj, cast=True) + prepare_mock.assert_called() + self.cctxt_mock.cast.assert_called_once_with( + self.context, 'delete_vnf_package', + vnf_package=vnf_package_obj) + _test() diff --git a/tacker/tests/unit/conductor/fakes.py b/tacker/tests/unit/conductor/fakes.py new file mode 100644 index 000000000..d8ba326d2 --- /dev/null +++ b/tacker/tests/unit/conductor/fakes.py @@ -0,0 +1,42 @@ +# Copyright (C) 2019 NTT DATA +# All Rights Reserved. +# +# 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 tacker.tests import uuidsentinel + + +VNF_UPLOAD_VNF_PACKAGE_CONTENT = { + 'algorithm': 'sha512', 'created_at': '2019-08-16T06:57:09Z', + 'deleted': False, 'deleted_at': None, + 'hash': 'ce48b8ba15bfb060fb70471cf955bef433e4513973b4bac42b37c36f57357dc35' + 'bf788c16545d3a59781914adf19fca26d6984583b7739e55c447383d774356a', + 'id': uuidsentinel.tenant_id, + 'location_glance_store': 'file:///var/lib/glance/images/' + 'd617ea52-b16b-417e-a68c-08dfb69aab9e', + 'onboarding_state': 'PROCESSING', 'operational_state': 'DISABLED', + 'tenant_id': uuidsentinel.tenant_id, + 'updated_at': '2019-08-16T06:57:30Z', + 'usage_state': 'NOT_IN_USE', 'user_data': {'abc': 'xyz'}} + +VNF_DATA = { + 'created_at': '2019-08-16T06:57:09Z', + 'deleted': False, 'deleted_at': None, + 'id': uuidsentinel.id, + 'onboarding_state': 'UPLOADING', + 'operational_state': 'DISABLED', + 'tenant_id': uuidsentinel.tenant_id, + 'updated_at': '2019-08-16T06:57:30Z', + 'usage_state': 'NOT_IN_USE', + 'user_data': {'abc': 'xyz'} +}