From f4272e3e3aba13cd829aea5fdd667de995d6adcc Mon Sep 17 00:00:00 2001 From: Renat Akhmerov Date: Wed, 9 Sep 2020 14:20:20 +0700 Subject: [PATCH] Fix serialization of OpenStack actions * In the recent version 2.3.0 of mistral-lib there has been added the new serialization mechanism for actions since the communication schema between Mistral engine and Mistral remote executor will soon require it (once merged). All classes for OpenStack actions are eventually dynamically generated based on the static classes like NovaAction, HeatAction etc., so to make the serialization work correctly we have to take this into account. The newly added class OpenStackActionSerializer takes care of that. Change-Id: I8d7e2db0b17fb8f055f77363667ca4ab2c501b34 --- lower-constraints.txt | 2 +- .../openstack/action_generator/base.py | 10 ++- mistral_extra/actions/openstack/actions.py | 1 - mistral_extra/actions/openstack/base.py | 26 +++++++ .../test_openstack_action_serialization.py | 67 +++++++++++++++++++ requirements.txt | 2 +- 6 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 mistral_extra/tests/unit/actions/openstack/test_openstack_action_serialization.py diff --git a/lower-constraints.txt b/lower-constraints.txt index a770a01..1a86d9e 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -5,7 +5,7 @@ doc8==0.6.0 fixtures==3.0.0 hacking==1.1.0 keystoneauth1===3.18.0 -mistral-lib==1.4.0 +mistral-lib==2.3.0 oslo.log==3.36.0 oslotest==3.2.0 pbr==2.0.0 diff --git a/mistral_extra/actions/openstack/action_generator/base.py b/mistral_extra/actions/openstack/action_generator/base.py index 758e55e..56bbce9 100644 --- a/mistral_extra/actions/openstack/action_generator/base.py +++ b/mistral_extra/actions/openstack/action_generator/base.py @@ -33,14 +33,17 @@ def get_mapping(): for key, value in map_part.items(): if isinstance(value, dict): delete_comment(value) + if '_comment' in map_part: del map_part['_comment'] + package = version.version_info.package if os.path.isabs(CONF.openstack_actions_mapping_path): mapping_file_path = CONF.openstack_actions_mapping_path else: path = CONF.openstack_actions_mapping_path + mapping_file_path = pkg.resource_filename(package, path) LOG.info( @@ -99,11 +102,13 @@ class OpenStackActionGenerator(action_generator.ActionGenerator): return ", ".join(added) inputs = [i.strip() for i in origin_inputs.split(',')] + kwarg_index = None for index, input in enumerate(inputs): if "=" in input: kwarg_index = index + if "**" in input: kwarg_index = index - 1 @@ -144,14 +149,17 @@ class OpenStackActionGenerator(action_generator.ActionGenerator): except Exception: LOG.exception( "Failed to create action: %s.%s", - cls.action_namespace, action_name + cls.action_namespace, + action_name ) + continue arg_list = i_u.get_arg_list_as_str(client_method) # Support specifying region for OpenStack actions. modules = CONF.openstack_actions.modules_support_region + if cls.action_namespace in modules: arg_list = cls.prepare_action_inputs( arg_list, diff --git a/mistral_extra/actions/openstack/actions.py b/mistral_extra/actions/openstack/actions.py index 8fe769c..af52ee1 100644 --- a/mistral_extra/actions/openstack/actions.py +++ b/mistral_extra/actions/openstack/actions.py @@ -187,7 +187,6 @@ class HeatAction(base.OpenStackAction): return heatclient.Client def _create_client(self, context): - LOG.debug("Heat action security context: %s", context) heat_endpoint = self.get_service_endpoint() diff --git a/mistral_extra/actions/openstack/base.py b/mistral_extra/actions/openstack/base.py index bee79dc..463356e 100644 --- a/mistral_extra/actions/openstack/base.py +++ b/mistral_extra/actions/openstack/base.py @@ -22,6 +22,9 @@ from mistral_extra.actions.openstack.utils import exceptions as exc from mistral_extra.actions.openstack.utils import keystone as \ keystone_utils from mistral_lib import actions +from mistral_lib.actions import base as ml_actions_base +from mistral_lib import serialization + LOG = log.getLogger(__name__) @@ -39,6 +42,8 @@ class OpenStackAction(actions.Action): _client_class = None def __init__(self, **kwargs): + super(OpenStackAction, self).__init__() + self._kwargs_for_run = kwargs self.action_region = self._kwargs_for_run.pop('action_region', None) @@ -135,3 +140,24 @@ class OpenStackAction(actions.Action): return dict( zip(self._kwargs_for_run, ['test'] * len(self._kwargs_for_run)) ) + + @classmethod + def get_serialization_key(cls): + return "%s.%s" % (OpenStackAction.__module__, OpenStackAction.__name__) + + +class OpenStackActionSerializer(ml_actions_base.ActionSerializer): + def serialize_to_dict(self, entity): + res = super(OpenStackActionSerializer, self).serialize_to_dict(entity) + + # Since all OpenStack actions are dynamically generated with the + # function type() we need to take the base class of the action + # class (i.e. NovaAction) and write it to the result dict. + base_cls = type(entity).__bases__[0] + + res['cls'] = '%s.%s' % (base_cls.__module__, base_cls.__name__) + + return res + + +serialization.register_serializer(OpenStackAction, OpenStackActionSerializer()) diff --git a/mistral_extra/tests/unit/actions/openstack/test_openstack_action_serialization.py b/mistral_extra/tests/unit/actions/openstack/test_openstack_action_serialization.py new file mode 100644 index 0000000..4ae19d2 --- /dev/null +++ b/mistral_extra/tests/unit/actions/openstack/test_openstack_action_serialization.py @@ -0,0 +1,67 @@ +# Copyright 2020 Nokia Software. +# +# 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 unittest import mock + +from mistral_extra.actions.openstack import actions +from mistral_extra.actions.openstack import base as actions_base + +from oslotest import base + + +class OpenStackActionSerializationTest(base.BaseTestCase): + @mock.patch.object(actions.NovaAction, '_get_client') + def test_nova_action_serialization(self, mocked): + mock_ctx = mock.Mock() + + method_name = "servers.get" + + # Create a dynamic action class. + action_class = type( + str(method_name), + (actions.NovaAction,), + {'client_method_name': method_name} + ) + + params = {'server': '1234-abcd'} + + # Just in case let's just make sure the action is functioning. + action = action_class(**params) + + action.run(mock_ctx) + + self.assertTrue(mocked().servers.get.called) + + mocked().servers.get.assert_called_once_with(server="1234-abcd") + + # Now let's serialize the action. + serializer = actions_base.OpenStackActionSerializer() + + serialized_action = serializer.serialize(action) + + self.assertIsNotNone(serialized_action) + + deserialized_action = serializer.deserialize(serialized_action) + + # Make sure that the action class is dynamic and it's still + # functioning. + self.assertNotEqual(actions.NovaAction, type(deserialized_action)) + + deserialized_action.run(mock_ctx) + + self.assertTrue(mocked().servers.get.called) + + mocked().servers.get.assert_called_with(server="1234-abcd") + + self.assertEqual(2, mocked().servers.get.call_count) diff --git a/requirements.txt b/requirements.txt index a6ce566..1359a2a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 Babel!=2.4.0,>=2.3.4 # BSD oslo.log>=3.36.0 # Apache-2.0 -mistral-lib>=1.4.0 # Apache-2.0 +mistral-lib>=2.3.0 # Apache-2.0 aodhclient>=0.9.0 # Apache-2.0 gnocchiclient>=3.3.1 # Apache-2.0 python-barbicanclient>=4.5.2 # Apache-2.0