Browse Source

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
changes/82/750582/3
Renat Akhmerov 3 weeks ago
parent
commit
f4272e3e3a
6 changed files with 104 additions and 4 deletions
  1. +1
    -1
      lower-constraints.txt
  2. +9
    -1
      mistral_extra/actions/openstack/action_generator/base.py
  3. +0
    -1
      mistral_extra/actions/openstack/actions.py
  4. +26
    -0
      mistral_extra/actions/openstack/base.py
  5. +67
    -0
      mistral_extra/tests/unit/actions/openstack/test_openstack_action_serialization.py
  6. +1
    -1
      requirements.txt

+ 1
- 1
lower-constraints.txt View File

@@ -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


+ 9
- 1
mistral_extra/actions/openstack/action_generator/base.py View File

@@ -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,


+ 0
- 1
mistral_extra/actions/openstack/actions.py View File

@@ -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()


+ 26
- 0
mistral_extra/actions/openstack/base.py View File

@@ -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())

+ 67
- 0
mistral_extra/tests/unit/actions/openstack/test_openstack_action_serialization.py View File

@@ -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)

+ 1
- 1
requirements.txt View File

@@ -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


Loading…
Cancel
Save