Remove OpenStack actions from mistral

Depends-on: https://review.opendev.org/#/c/703296/
Depends-On: https://review.opendev.org/#/c/704280/
Change-Id: Id62fdabe7699e7c3b2977166e253cfc77779e467
This commit is contained in:
Eyal 2020-01-21 16:53:34 +02:00
parent 95d9f899db
commit 8bdf341af7
39 changed files with 79 additions and 4666 deletions

View File

@ -9,6 +9,8 @@
USE_PYTHON3: true
required-projects:
- openstack/rally-openstack
- openstack/mistral-lib
- openstack/mistral-extra
- job:
name: mistral-docker-buildimage
@ -46,6 +48,7 @@
timeout: 3600
required-projects:
- openstack/mistral-lib
- openstack/mistral-extra
- job:
name: mistral-tox-unit-postgresql
@ -60,6 +63,7 @@
timeout: 3600
required-projects:
- openstack/mistral-lib
- openstack/mistral-extra
- project:
templates:
@ -78,12 +82,23 @@
- ^releasenotes/.*$
required-projects:
- openstack/mistral-lib
- openstack/mistral-extra
- openstack-tox-py36:
required-projects:
- openstack/mistral-lib
- openstack/mistral-extra
- openstack-tox-py37:
required-projects:
- openstack/mistral-lib
- openstack/mistral-extra
- openstack-tox-py38:
required-projects:
- openstack/mistral-lib
- openstack/mistral-extra
- openstack-tox-docs:
required-projects:
- openstack/mistral-lib
- openstack/mistral-extra
- mistral-devstack
- mistral-devstack-tempest-ipv6-only
- mistral-devstack-non-apache-tempest-ipv6-only
@ -103,6 +118,7 @@
- openstack-tox-lower-constraints:
required-projects:
- openstack/mistral-lib
- openstack/mistral-extra
gate:
queue: mistral
jobs:

View File

@ -161,6 +161,13 @@ function install_mistral_lib {
fi
}
function install_mistral_extra {
if use_library_from_git "mistral-extra"; then
git_clone $MISTRAL_EXTRA_REPO $MISTRAL_EXTRA_DIR $MISTRAL_EXTRA_BRANCH
setup_develop $MISTRAL_EXTRA_DIR
fi
}
# start_mistral - Start running processes
function start_mistral {
# If the site is not enabled then we are in a grenade scenario
@ -255,6 +262,7 @@ if is_service_enabled mistral; then
echo_summary "Installing mistral"
install_mistral
install_mistral_lib
install_mistral_extra
install_mistral_pythonclient
elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
echo_summary "Configuring mistral"

View File

@ -23,6 +23,10 @@ MISTRAL_LIB_REPO=${MISTRAL_LIB_REPO:-${GIT_BASE}/openstack/mistral-lib.git}
MISTRAL_LIB_BRANCH=${MISTRAL_LIB_BRANCH:-master}
MISTRAL_LIB_DIR=${DEST}/mistral-lib
MISTRAL_EXTRA_REPO=${MISTRAL_EXTRA_REPO:-${GIT_BASE}/openstack/mistral-extra.git}
MISTRAL_EXTRA_BRANCH=${MISTRAL_EXTRA_BRANCH:-master}
MISTRAL_EXTRA_DIR=${DEST}/mistral-extra
GITDIR["python-mistralclient"]=${DEST}/python-mistralclient
GITREPO["python-mistralclient"]=${MISTRALCLIENT_REPO:-${GIT_BASE}/openstack/python-mistralclient.git}
GITBRANCH["python-mistralclient"]=${MISTRALCLIENT_BRANCH:-master}

View File

@ -2,4 +2,5 @@ sphinx>=1.8.0,!=2.1.0;python_version>='3.4' # BSD
sphinxcontrib-httpdomain>=1.3.0 # BSD
sphinxcontrib-pecanwsme>=0.8.0 # Apache-2.0
openstackdocstheme>=1.30.0 # Apache-2.0
os-api-ref>=1.4.0 # Apache-2.0
reno>=2.5.0 # Apache-2.0

View File

@ -50,7 +50,7 @@ logutils==0.3.5
Mako==0.4.0
MarkupSafe==1.0
mccabe==0.2.1
mistral-lib==1.2.0
mistral-lib==1.4.0
mock==2.0.0
monotonic==0.6
mox3==0.20.0

View File

@ -1,31 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
#
# 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 abc
class ActionGenerator(object):
"""Action generator.
Action generator uses some data to build Action classes
dynamically.
"""
@abc.abstractmethod
def create_actions(self, *args, **kwargs):
"""Constructs classes of needed action.
return: list of actions dicts containing name, class,
description and parameter info.
"""
pass

View File

@ -1,91 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
#
# 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 abc
import warnings
warnings.warn(
"mistral.actions.Action is deprecated as of the 5.0.0 release in favor of "
"mistral_lib. It will be removed in a future release.", DeprecationWarning
)
class Action(object):
"""Action.
Action is a means in Mistral to perform some useful work associated with
a workflow during its execution. Every workflow task is configured with
an action and when the task runs it eventually delegates to the action.
When it happens task parameters get evaluated (calculating expressions,
if any) and are treated as action parameters. So in a regular general
purpose languages terminology action is a method declaration and task is
a method call.
Base action class initializer doesn't have arguments. However, concrete
action classes may have any number of parameters defining action behavior.
These parameters must correspond to parameters declared in action
specification (e.g. using DSL or others).
Action initializer may have a conventional argument with name
"action_context". If it presents then action factory will fill it with
a dictionary containing contextual information like execution identifier,
workbook name and other that may be needed for some specific action
implementations.
"""
@abc.abstractmethod
def run(self):
"""Run action logic.
:return: Result of the action. Note that for asynchronous actions
it should always be None, however, if even it's not None it will be
ignored by a caller.
Result can be of two types:
1) Any serializable value meaningful from a user perspective (such
as string, number or dict).
2) Instance of {mistral.workflow.utils.Result} which has field "data"
for success result and field "error" for keeping so called "error
result" like HTTP error code and similar. Using the second type
allows to communicate a result even in case of error and hence to have
conditions in "on-error" clause of direct workflows. Depending on
particular action semantics one or another option may be preferable.
In case if action failed and there's no need to communicate any error
result this method should throw a ActionException.
"""
pass
@abc.abstractmethod
def test(self):
"""Returns action test result.
This method runs in test mode as a test version of method run() to
generate and return a representative test result. It's basically a
contract for action 'dry-run' behavior specifically useful for
testing and workflow designing purposes.
:return: Representative action result.
"""
pass
def is_sync(self):
"""Returns True if the action is synchronous, otherwise False.
:return: True if the action is synchronous and method run() returns
final action result. Otherwise returns False which means that
a result of method run() should be ignored and a real action
result is supposed to be delivered in an asynchronous manner
using public API. By default, if a concrete implementation
doesn't override this method then the action is synchronous.
"""
return True

View File

@ -1,43 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
#
# 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_utils import importutils
from mistral.actions.openstack.action_generator import base
SUPPORTED_MODULES = [
'Nova', 'Glance', 'Keystone', 'Heat', 'Neutron', 'Cinder',
'Trove', 'Ironic', 'Baremetal Introspection', 'Swift', 'SwiftService',
'Zaqar', 'Barbican', 'Mistral', 'Designate', 'Magnum', 'Murano', 'Tacker',
'Aodh', 'Gnocchi', 'Glare', 'Vitrage', 'Senlin', 'Zun', 'Qinling', 'Manila'
]
def all_generators():
for mod_name in SUPPORTED_MODULES:
prefix = mod_name.replace(' ', '')
mod_namespace = mod_name.lower().replace(' ', '_')
mod_cls_name = 'mistral.actions.openstack.actions.%sAction' % prefix
mod_action_cls = importutils.import_class(mod_cls_name)
generator_cls_name = '%sActionGenerator' % prefix
yield type(
generator_cls_name,
(base.OpenStackActionGenerator,),
{
'action_namespace': mod_namespace,
'base_action_class': mod_action_cls
}
)

View File

@ -1,172 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
#
# 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 json
import os
from oslo_config import cfg
from oslo_log import log as logging
import pkg_resources as pkg
from mistral.actions import action_generator
from mistral.utils import inspect_utils as i_u
from mistral import version
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
def get_mapping():
def delete_comment(map_part):
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(
"Processing OpenStack action mapping from file: %s",
mapping_file_path
)
with open(mapping_file_path) as fh:
mapping = json.load(fh)
for k, v in mapping.items():
if isinstance(v, dict):
delete_comment(v)
return mapping
class OpenStackActionGenerator(action_generator.ActionGenerator):
"""OpenStackActionGenerator.
Base generator for all OpenStack actions,
creates a client method declaration using
specific python-client and sets needed arguments
to actions.
"""
action_namespace = None
base_action_class = None
@classmethod
def prepare_action_inputs(cls, origin_inputs, added=()):
"""Modify action input string.
Sometimes we need to change the default action input definition for
OpenStack actions in order to make the workflow more powerful.
Examples::
>>> prepare_action_inputs('a,b,c', added=['region=RegionOne'])
a, b, c, region=RegionOne
>>> prepare_action_inputs('a,b,c=1', added=['region=RegionOne'])
a, b, region=RegionOne, c=1
>>> prepare_action_inputs('a,b,c=1,**kwargs',
added=['region=RegionOne'])
a, b, region=RegionOne, c=1, **kwargs
>>> prepare_action_inputs('**kwargs', added=['region=RegionOne'])
region=RegionOne, **kwargs
>>> prepare_action_inputs('', added=['region=RegionOne'])
region=RegionOne
:param origin_inputs: A string consists of action inputs, separated by
comma.
:param added: (Optional) A list of params to add to input string.
:return: The new action input string.
"""
if not origin_inputs:
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
kwarg_index = len(inputs) if kwarg_index is None else kwarg_index
kwarg_index = kwarg_index + 1 if kwarg_index < 0 else kwarg_index
for a in added:
if "=" not in a:
inputs.insert(0, a)
kwarg_index += 1
else:
inputs.insert(kwarg_index, a)
return ", ".join(inputs)
@classmethod
def create_action_class(cls, method_name):
if not method_name:
return None
action_class = type(str(method_name), (cls.base_action_class,),
{'client_method_name': method_name})
return action_class
@classmethod
def create_actions(cls):
mapping = get_mapping()
method_dict = mapping.get(cls.action_namespace, {})
action_classes = []
for action_name, method_name in method_dict.items():
class_ = cls.create_action_class(method_name)
try:
client_method = class_.get_fake_client_method()
except Exception:
LOG.exception(
"Failed to create action: %s.%s",
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,
added=['action_region=""']
)
description = i_u.get_docstring(client_method)
action_classes.append(
{
'class': class_,
'name': "%s.%s" % (cls.action_namespace, action_name),
'description': description,
'arg_list': arg_list,
}
)
return action_classes

File diff suppressed because it is too large Load Diff

View File

@ -1,136 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
#
# 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 abc
import inspect
import traceback
from oslo_log import log
from mistral import exceptions as exc
from mistral.utils.openstack import keystone as keystone_utils
from mistral_lib import actions
LOG = log.getLogger(__name__)
class OpenStackAction(actions.Action):
"""OpenStack Action.
OpenStack Action is the basis of all OpenStack-specific actions,
which are constructed via OpenStack Action generators.
"""
_kwargs_for_run = {}
client_method_name = None
_service_name = None
_service_type = None
_client_class = None
def __init__(self, **kwargs):
self._kwargs_for_run = kwargs
self.action_region = self._kwargs_for_run.pop('action_region', None)
@abc.abstractmethod
def _create_client(self, context):
"""Creates client required for action operation."""
return None
@classmethod
def _get_client_class(cls):
return cls._client_class
@classmethod
def _get_client_method(cls, client):
hierarchy_list = cls.client_method_name.split('.')
attribute = client
for attr in hierarchy_list:
attribute = getattr(attribute, attr)
return attribute
@classmethod
def _get_fake_client(cls):
"""Returns python-client instance which initiated via wrong args.
It is needed for getting client-method args and description for
saving into DB.
"""
# Default is simple _get_client_class instance
return cls._get_client_class()()
@classmethod
def get_fake_client_method(cls):
return cls._get_client_method(cls._get_fake_client())
def _get_client(self, context):
"""Returns python-client instance via cache or creation
Gets client instance according to specific OpenStack Service
(e.g. Nova, Glance, Heat, Keystone etc)
"""
return self._create_client(context)
def get_session_and_auth(self, context):
"""Get keystone session and auth parameters.
:param context: the action context
:return: dict that can be used to initialize service clients
"""
return keystone_utils.get_session_and_auth(
service_name=self._service_name,
service_type=self._service_type,
region_name=self.action_region,
ctx=context)
def get_service_endpoint(self):
"""Get OpenStack service endpoint.
'service_name' and 'service_type' are defined in specific OpenStack
service action.
"""
endpoint = keystone_utils.get_endpoint_for_project(
service_name=self._service_name,
service_type=self._service_type,
region_name=self.action_region
)
return endpoint
def run(self, context):
try:
method = self._get_client_method(self._get_client(context))
result = method(**self._kwargs_for_run)
if inspect.isgenerator(result):
return [v for v in result]
return result
except Exception as e:
# Print the traceback for the last exception so that we can see
# where the issue comes from.
LOG.warning(traceback.format_exc())
raise exc.ActionException(
"%s.%s failed: %s" %
(self.__class__.__name__, self.client_method_name, str(e))
)
def test(self, context):
return dict(
zip(self._kwargs_for_run, ['test'] * len(self._kwargs_for_run))
)

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,6 @@ Configuration options registration and useful routines.
import itertools
import json
import os
from keystoneauth1 import loading
from oslo_config import cfg
@ -558,39 +557,6 @@ keycloak_oidc_opts = [
)
]
openstack_actions_opts = [
cfg.StrOpt(
'os-actions-endpoint-type',
default=os.environ.get('OS_ACTIONS_ENDPOINT_TYPE', 'public'),
choices=['public', 'admin', 'internal'],
deprecated_group='DEFAULT',
help=_('Type of endpoint in identity service catalog to use for'
' communication with OpenStack services.')
),
cfg.ListOpt(
'modules-support-region',
default=['nova', 'glance', 'heat', 'neutron', 'cinder',
'trove', 'ironic', 'designate', 'murano', 'tacker', 'senlin',
'aodh', 'gnocchi'],
help=_('List of module names that support region in actions.')
),
cfg.StrOpt(
'default_region',
help=_('Default region name for openstack actions supporting region.')
),
]
# note: this command line option is used only from sync_db and
# mistral-db-manage
os_actions_mapping_path = cfg.StrOpt(
'openstack_actions_mapping_path',
short='m',
metavar='MAPPING_PATH',
default='actions/openstack/mapping.json',
help='Path to openstack action mapping json file.'
'It could be relative to mistral package '
'directory or absolute.'
)
yaql_opts = [
cfg.IntOpt(
@ -692,7 +658,6 @@ EXECUTION_EXPIRATION_POLICY_GROUP = 'execution_expiration_policy'
ACTION_HEARTBEAT_GROUP = 'action_heartbeat'
PROFILER_GROUP = profiler.list_opts()[0][0]
KEYCLOAK_OIDC_GROUP = "keycloak_oidc"
OPENSTACK_ACTIONS_GROUP = 'openstack_actions'
YAQL_GROUP = "yaql"
KEYSTONE_GROUP = "keystone"
@ -725,7 +690,6 @@ CONF.register_opts(pecan_opts, group=PECAN_GROUP)
CONF.register_opts(coordination_opts, group=COORDINATION_GROUP)
CONF.register_opts(profiler_opts, group=PROFILER_GROUP)
CONF.register_opts(keycloak_oidc_opts, group=KEYCLOAK_OIDC_GROUP)
CONF.register_opts(openstack_actions_opts, group=OPENSTACK_ACTIONS_GROUP)
CONF.register_opts(yaql_opts, group=YAQL_GROUP)
loading.register_session_conf_options(CONF, KEYSTONE_GROUP)
@ -773,7 +737,6 @@ def list_opts():
(EXECUTION_EXPIRATION_POLICY_GROUP, execution_expiration_policy_opts),
(PROFILER_GROUP, profiler_opts),
(KEYCLOAK_OIDC_GROUP, keycloak_oidc_opts),
(OPENSTACK_ACTIONS_GROUP, openstack_actions_opts),
(YAQL_GROUP, yaql_opts),
(ACTION_HEARTBEAT_GROUP, action_heartbeat_opts),
(None, default_group_opts)

View File

@ -24,7 +24,6 @@ from oslo_utils import importutils
import six
import sys
from mistral import config
from mistral.services import action_manager
from mistral.services import workflows
@ -127,7 +126,6 @@ command_opt = cfg.SubCommandOpt('command',
handler=add_command_parsers)
CONF.register_cli_opt(command_opt)
CONF.register_cli_opt(config.os_actions_mapping_path)
def main():

View File

@ -20,8 +20,8 @@ import jsonschema
import six
from mistral import exceptions as exc
from mistral.utils import inspect_utils
from mistral.workflow import data_flow
from mistral_lib.utils import inspect_utils
@six.add_metaclass(abc.ABCMeta)

View File

@ -24,7 +24,7 @@ from mistral import exceptions as exc
from mistral.executors import base
from mistral.rpc import clients as rpc
from mistral.services import action_heartbeat_sender
from mistral.utils import inspect_utils as i_u
from mistral_lib.utils import inspect_utils as i_u
LOG = logging.getLogger(__name__)

View File

@ -18,12 +18,11 @@ from oslo_log import log as logging
from stevedore import extension
from mistral.actions import action_factory
from mistral.actions import generator_factory
from mistral.db.v2 import api as db_api
from mistral import exceptions as exc
from mistral.services import actions
from mistral.utils import inspect_utils as i_utils
from mistral_lib import utils
from mistral_lib.utils import inspect_utils as i_utils
# TODO(rakhmerov): Make methods more consistent and granular.
@ -84,25 +83,32 @@ def sync_db():
def _register_dynamic_action_classes(namespace=''):
for generator in generator_factory.all_generators():
actions = generator.create_actions()
extensions = extension.ExtensionManager(
namespace='mistral.generators',
invoke_on_load=True
)
module = generator.base_action_class.__module__
class_name = generator.base_action_class.__name__
for ext in extensions:
for generator in ext.obj:
_register_actions(generator, namespace)
action_class_str = "%s.%s" % (module, class_name)
for action in actions:
attrs = i_utils.get_public_fields(action['class'])
def _register_actions(generator, namespace):
module = generator.base_action_class.__module__
class_name = generator.base_action_class.__name__
action_class_str = "%s.%s" % (module, class_name)
register_action_class(
action['name'],
action_class_str,
attrs,
action['description'],
action['arg_list'],
namespace=namespace
)
for action in generator.create_actions():
attrs = i_utils.get_public_fields(action['class'])
register_action_class(
action['name'],
action_class_str,
attrs,
action['description'],
action['arg_list'],
namespace=namespace
)
def register_action_classes(namespace=''):

View File

@ -1,53 +0,0 @@
---
version: '2.0'
name: action_collection
workflows:
keystone:
type: direct
tasks:
projects_list:
action: keystone.projects_list
publish:
result: <% task().result %>
nova:
type: direct
tasks:
flavors_list:
action: nova.flavors_list
publish:
result: <% task().result %>
glance:
type: direct
tasks:
images_list:
action: glance.images_list
publish:
result: <% task().result %>
heat:
type: direct
tasks:
stacks_list:
action: heat.stacks_list
publish:
result: <% task().result %>
neutron:
type: direct
tasks:
list_subnets:
action: neutron.list_subnets
publish:
result: <% task().result %>
cinder:
type: direct
tasks:
volumes_list:
action: cinder.volumes_list
publish:
result: <% task().result %>

View File

@ -1,16 +0,0 @@
{
"_comment": "Mapping OpenStack action namespaces to all its actions. Each action name is mapped to python-client method name in this namespace.",
"nova": {
"servers_get": "servers.get",
"servers_find": "servers.find",
"volumes_delete_server_volume": "volumes.delete_server_volume"
},
"keystone": {
"users_list": "users.list",
"trusts_create": "trusts.create"
},
"glance": {
"images_list": "images.list",
"images_delete": "images.delete"
}
}

View File

@ -1,193 +0,0 @@
#
# 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 contextlib
import os
from oslo_config import cfg
import mock
from mistral.actions import generator_factory
from mistral.actions.openstack.action_generator import base as generator_base
from mistral.actions.openstack import actions
from mistral import config
from mistral.tests.unit import base
ABSOLUTE_TEST_MAPPING_PATH = os.path.realpath(
os.path.join(os.path.dirname(__file__),
"../../../resources/openstack/test_mapping.json")
)
RELATIVE_TEST_MAPPING_PATH = "tests/resources/openstack/test_mapping.json"
MODULE_MAPPING = {
'nova': ['nova.servers_get', actions.NovaAction],
'glance': ['glance.images_list', actions.GlanceAction],
'keystone': ['keystone.users_create', actions.KeystoneAction],
'heat': ['heat.stacks_list', actions.HeatAction],
'neutron': ['neutron.show_network', actions.NeutronAction],
'cinder': ['cinder.volumes_list', actions.CinderAction],
'trove': ['trove.instances_list', actions.TroveAction],
'ironic': ['ironic.node_list', actions.IronicAction],
'baremetal_introspection': ['baremetal_introspection.introspect',
actions.BaremetalIntrospectionAction],
'swift': ['swift.head_account', actions.SwiftAction],
'swiftservice': ['swiftservice.delete', actions.SwiftServiceAction],
'zaqar': ['zaqar.queue_messages', actions.ZaqarAction],
'barbican': ['barbican.orders_list', actions.BarbicanAction],
'mistral': ['mistral.workflows_get', actions.MistralAction],
'designate': ['designate.quotas_list', actions.DesignateAction],
'manila': ['manila.shares_list', actions.ManilaAction],
'magnum': ['magnum.bays_list', actions.MagnumAction],
'murano': ['murano.deployments_list', actions.MuranoAction],
'tacker': ['tacker.list_vims', actions.TackerAction],
'senlin': ['senlin.get_profile', actions.SenlinAction],
'aodh': ['aodh.alarm_list', actions.AodhAction],
'gnocchi': ['gnocchi.metric_list', actions.GnocchiAction],
'glare': ['glare.artifacts_list', actions.GlareAction],
'vitrage': ['vitrage.alarm_get', actions.VitrageAction],
'zun': ['zun.containers_list', actions.ZunAction],
'qinling': ['qinling.runtimes_list', actions.QinlingAction]
}
EXTRA_MODULES = ['neutron', 'swift', 'zaqar', 'tacker', 'senlin']
CONF = cfg.CONF
CONF.register_opt(config.os_actions_mapping_path)
class GeneratorTest(base.BaseTest):
def setUp(self):
super(GeneratorTest, self).setUp()
# The baremetal inspector client expects the service to be running
# when it is initialised and attempts to connect. This mocks out this
# service only and returns a simple function that can be used by the
# inspection utils.
self.baremetal_patch = mock.patch.object(
actions.BaremetalIntrospectionAction,
"get_fake_client_method",
return_value=lambda x: None)
self.baremetal_patch.start()
self.addCleanup(self.baremetal_patch.stop)
# Do the same for the Designate client.
self.designate_patch = mock.patch.object(
actions.DesignateAction,
"get_fake_client_method",
return_value=lambda x: None)
self.designate_patch.start()
self.addCleanup(self.designate_patch.stop)
def test_generator(self):
for generator_cls in generator_factory.all_generators():
action_classes = generator_cls.create_actions()
action_name = MODULE_MAPPING[generator_cls.action_namespace][0]
action_cls = MODULE_MAPPING[generator_cls.action_namespace][1]
method_name_pre = action_name.split('.')[1]
method_name = (
method_name_pre
if generator_cls.action_namespace in EXTRA_MODULES
else method_name_pre.replace('_', '.')
)
action = self._assert_single_item(
action_classes,
name=action_name
)
self.assertTrue(issubclass(action['class'], action_cls))
self.assertEqual(method_name, action['class'].client_method_name)
modules = CONF.openstack_actions.modules_support_region
if generator_cls.action_namespace in modules:
self.assertIn('action_region', action['arg_list'])
def test_missing_module_from_mapping(self):
with _patch_openstack_action_mapping_path(RELATIVE_TEST_MAPPING_PATH):
for generator_cls in generator_factory.all_generators():
action_classes = generator_cls.create_actions()
action_names = [action['name'] for action in action_classes]
cls = MODULE_MAPPING.get(generator_cls.action_namespace)[1]
if cls == actions.NovaAction:
self.assertIn('nova.servers_get', action_names)
self.assertEqual(3, len(action_names))
elif cls not in (actions.GlanceAction, actions.KeystoneAction):
self.assertEqual([], action_names)
def test_absolute_mapping_path(self):
with _patch_openstack_action_mapping_path(ABSOLUTE_TEST_MAPPING_PATH):
self.assertTrue(os.path.isabs(ABSOLUTE_TEST_MAPPING_PATH),
"Mapping path is relative: %s" %
ABSOLUTE_TEST_MAPPING_PATH)
for generator_cls in generator_factory.all_generators():
action_classes = generator_cls.create_actions()
action_names = [action['name'] for action in action_classes]
cls = MODULE_MAPPING.get(generator_cls.action_namespace)[1]
if cls == actions.NovaAction:
self.assertIn('nova.servers_get', action_names)
self.assertEqual(3, len(action_names))
elif cls not in (actions.GlanceAction, actions.KeystoneAction):
self.assertEqual([], action_names)
def test_prepare_action_inputs(self):
inputs = generator_base.OpenStackActionGenerator.prepare_action_inputs(
'a,b,c',
added=['region=RegionOne']
)
self.assertEqual('a, b, c, region=RegionOne', inputs)
inputs = generator_base.OpenStackActionGenerator.prepare_action_inputs(
'a,b,c=1',
added=['region=RegionOne']
)
self.assertEqual('a, b, region=RegionOne, c=1', inputs)
inputs = generator_base.OpenStackActionGenerator.prepare_action_inputs(
'a,b,c=1,**kwargs',
added=['region=RegionOne']
)
self.assertEqual('a, b, region=RegionOne, c=1, **kwargs', inputs)
inputs = generator_base.OpenStackActionGenerator.prepare_action_inputs(
'**kwargs',
added=['region=RegionOne']
)
self.assertEqual('region=RegionOne, **kwargs', inputs)
inputs = generator_base.OpenStackActionGenerator.prepare_action_inputs(
'',
added=['region=RegionOne']
)
self.assertEqual('region=RegionOne', inputs)
@contextlib.contextmanager
def _patch_openstack_action_mapping_path(path):
original_path = CONF.openstack_actions_mapping_path
CONF.set_default("openstack_actions_mapping_path", path)
yield
CONF.set_default("openstack_actions_mapping_path", original_path)

View File

@ -1,411 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
#
# 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 mistral.actions.openstack import actions
from oslo_config import cfg
from oslo_utils import importutils
from oslotest import base
CONF = cfg.CONF
class FakeEndpoint(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
class OpenStackActionTest(base.BaseTestCase):
def tearDown(self):
super(OpenStackActionTest, self).tearDown()
cfg.CONF.set_default('auth_enable', False, group='pecan')
@mock.patch.object(actions.NovaAction, '_get_client')
def test_nova_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "servers.get"
action_class = actions.NovaAction
action_class.client_method_name = method_name
params = {'server': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().servers.get.called)
mocked().servers.get.assert_called_once_with(server="1234-abcd")
@mock.patch.object(actions.GlanceAction, '_get_client')
def test_glance_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "images.delete"
action_class = actions.GlanceAction
action_class.client_method_name = method_name
params = {'image': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().images.delete.called)
mocked().images.delete.assert_called_once_with(image="1234-abcd")
@mock.patch.object(actions.KeystoneAction, '_get_client')
def test_keystone_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "users.get"
action_class = actions.KeystoneAction
action_class.client_method_name = method_name
params = {'user': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().users.get.called)
mocked().users.get.assert_called_once_with(user="1234-abcd")
@mock.patch.object(actions.HeatAction, '_get_client')
def test_heat_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "stacks.get"
action_class = actions.HeatAction
action_class.client_method_name = method_name
params = {'id': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().stacks.get.called)
mocked().stacks.get.assert_called_once_with(id="1234-abcd")
@mock.patch.object(actions.NeutronAction, '_get_client')
def test_neutron_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "show_network"
action_class = actions.NeutronAction
action_class.client_method_name = method_name
params = {'id': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().show_network.called)
mocked().show_network.assert_called_once_with(id="1234-abcd")
@mock.patch.object(actions.CinderAction, '_get_client')
def test_cinder_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "volumes.get"
action_class = actions.CinderAction
action_class.client_method_name = method_name
params = {'volume': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().volumes.get.called)
mocked().volumes.get.assert_called_once_with(volume="1234-abcd")
@mock.patch.object(actions.TroveAction, '_get_client')
def test_trove_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "instances.get"
action_class = actions.TroveAction
action_class.client_method_name = method_name
params = {'instance': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().instances.get.called)
mocked().instances.get.assert_called_once_with(instance="1234-abcd")
@mock.patch.object(actions.IronicAction, '_get_client')
def test_ironic_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "node.get"
action_class = actions.IronicAction
action_class.client_method_name = method_name
params = {'node': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().node.get.called)
mocked().node.get.assert_called_once_with(node="1234-abcd")
@mock.patch.object(actions.BaremetalIntrospectionAction, '_get_client')
def test_baremetal_introspector_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "get_status"
action_class = actions.BaremetalIntrospectionAction
action_class.client_method_name = method_name
params = {'uuid': '1234'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().get_status.called)
mocked().get_status.assert_called_once_with(uuid="1234")
@mock.patch.object(actions.MistralAction, '_get_client')
def test_mistral_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "workflows.get"
action_class = actions.MistralAction
action_class.client_method_name = method_name
params = {'name': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().workflows.get.called)
mocked().workflows.get.assert_called_once_with(name="1234-abcd")
@mock.patch.object(actions.MistralAction, 'get_session_and_auth')
def test_integrated_mistral_action(self, mocked):
CONF.set_default('auth_enable', True, group='pecan')
mock_endpoint = mock.Mock()
mock_endpoint.endpoint = 'http://testendpoint.com:8989/v2'
mocked.return_value = {'auth': mock_endpoint, 'session': None}
mock_ctx = mock.Mock()
action_class = actions.MistralAction
params = {'identifier': '1234-abcd'}
action = action_class(**params)
client = action._get_client(mock_ctx)
self.assertEqual(client.workbooks.http_client.base_url,
mock_endpoint.endpoint)
def test_standalone_mistral_action(self):
CONF.set_default('auth_enable', False, group='pecan')
mock_ctx = mock.Mock()
action_class = actions.MistralAction
params = {'identifier': '1234-abcd'}
action = action_class(**params)
client = action._get_client(mock_ctx)
base_url = 'http://{}:{}/v2'.format(CONF.api.host, CONF.api.port)
self.assertEqual(client.workbooks.http_client.base_url, base_url)
@mock.patch.object(actions.SwiftAction, '_get_client')
def test_swift_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "get_object"
action_class = actions.SwiftAction
action_class.client_method_name = method_name
params = {'container': 'foo', 'object': 'bar'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().get_object.called)
mocked().get_object.assert_called_once_with(container='foo',
object='bar')
@mock.patch.object(actions.SwiftServiceAction, '_get_client')
def test_swift_service_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "list"
action_class = actions.SwiftServiceAction
action_class.client_method_name = method_name
action = action_class()
action.run(mock_ctx)
self.assertTrue(mocked().list.called)
mocked().list.assert_called_once_with()
@mock.patch.object(actions.ZaqarAction, '_get_client')
def test_zaqar_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "queue_messages"
action_class = actions.ZaqarAction
action_class.client_method_name = method_name
params = {'queue_name': 'foo'}
action = action_class(**params)
action.run(mock_ctx)
mocked().queue.assert_called_once_with('foo')
mocked().queue().messages.assert_called_once_with()
@mock.patch.object(actions.BarbicanAction, '_get_client')
def test_barbican_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "orders_list"
action_class = actions.BarbicanAction
action_class.client_method_name = method_name
params = {'limit': 5}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().orders_list.called)
mocked().orders_list.assert_called_once_with(limit=5)
@mock.patch.object(actions.DesignateAction, '_get_client')
def test_designate_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "domain.get"
action_class = actions.DesignateAction
action_class.client_method_name = method_name
params = {'domain': 'example.com'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().domain.get.called)
mocked().domain.get.assert_called_once_with(domain="example.com")
@mock.patch.object(actions.MagnumAction, '_get_client')
def test_magnum_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "baymodels.get"
action_class = actions.MagnumAction
action_class.client_method_name = method_name
params = {'id': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().baymodels.get.called)
mocked().baymodels.get.assert_called_once_with(id="1234-abcd")
@mock.patch.object(actions.MuranoAction, '_get_client')
def test_murano_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "categories.get"
action_class = actions.MuranoAction
action_class.client_method_name = method_name
params = {'category_id': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().categories.get.called)
mocked().categories.get.assert_called_once_with(
category_id="1234-abcd"
)
@mock.patch.object(actions.TackerAction, '_get_client')
def test_tacker_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "show_vim"
action_class = actions.TackerAction
action_class.client_method_name = method_name
params = {'vim_id': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().show_vim.called)
mocked().show_vim.assert_called_once_with(
vim_id="1234-abcd"
)
@mock.patch.object(actions.SenlinAction, '_get_client')
def test_senlin_action(self, mocked):
mock_ctx = mock.Mock()
action_class = actions.SenlinAction
action_class.client_method_name = "get_cluster"
action = action_class(cluster_id='1234-abcd')
action.run(mock_ctx)
self.assertTrue(mocked().get_cluster.called)
mocked().get_cluster.assert_called_once_with(
cluster_id="1234-abcd"
)
@mock.patch.object(actions.AodhAction, '_get_client')
def test_aodh_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "alarm.get"
action_class = actions.AodhAction
action_class.client_method_name = method_name
params = {'alarm_id': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().alarm.get.called)
mocked().alarm.get.assert_called_once_with(alarm_id="1234-abcd")
@mock.patch.object(actions.GnocchiAction, '_get_client')
def test_gnocchi_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "metric.get"
action_class = actions.GnocchiAction
action_class.client_method_name = method_name
params = {'metric_id': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().metric.get.called)
mocked().metric.get.assert_called_once_with(metric_id="1234-abcd")
@mock.patch.object(actions.GlareAction, '_get_client')
def test_glare_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "artifacts.get"
action_class = actions.GlareAction
action_class.client_method_name = method_name
params = {'artifact_id': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().artifacts.get.called)
mocked().artifacts.get.assert_called_once_with(artifact_id="1234-abcd")
@mock.patch.object(actions.VitrageAction, '_get_client')
def test_vitrage_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "alarm.get"
action_class = actions.VitrageAction
action_class.client_method_name = method_name
params = {'vitrage_id': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().alarm.get.called)
mocked().alarm.get.assert_called_once_with(vitrage_id="1234-abcd")
@mock.patch.object(actions.ZunAction, '_get_client')
def test_zun_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "containers.get"
action_class = actions.ZunAction
action_class.client_method_name = method_name
params = {'container_id': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().containers.get.called)
mocked().containers.get.assert_called_once_with(
container_id="1234-abcd"
)
@mock.patch.object(actions.QinlingAction, '_get_client')
def test_qinling_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "runtimes.get"
action_class = actions.QinlingAction
action_class.client_method_name = method_name
params = {'id': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().runtimes.get.called)
mocked().runtimes.get.assert_called_once_with(id="1234-abcd")
@mock.patch.object(actions.ManilaAction, '_get_client')
def test_manila_action(self, mocked):
mock_ctx = mock.Mock()
method_name = "shares.get"
action_class = actions.ManilaAction
action_class.client_method_name = method_name
params = {'share': '1234-abcd'}
action = action_class(**params)
action.run(mock_ctx)
self.assertTrue(mocked().shares.get.called)
mocked().shares.get.assert_called_once_with(share="1234-abcd")
class TestImport(base.BaseTestCase):
@mock.patch.object(importutils, 'try_import')
def test_try_import_fails(self, mocked):
mocked.side_effect = Exception('Exception when importing module')
bad_module = actions._try_import('raiser')
self.assertIsNone(bad_module)

View File

@ -30,24 +30,6 @@ class ActionManagerTest(base.DbTestCase):
self._assert_single_item(action_list, name="std.ssh")
self._assert_single_item(action_list, name="std.javascript")
self._assert_single_item(action_list, name="nova.servers_get")
self._assert_single_item(
action_list,
name="nova.volumes_delete_server_volume"
)
server_find_action = self._assert_single_item(
action_list,
name="nova.servers_find"
)
self.assertIn('**', server_find_action.input)
self._assert_single_item(action_list, name="keystone.users_list")
self._assert_single_item(action_list, name="keystone.trusts_create")
self._assert_single_item(action_list, name="glance.images_list")
self._assert_single_item(action_list, name="glance.images_delete")
def test_get_action_class(self):
self.assertTrue(
issubclass(a_m.get_action_class("std.echo"), std.EchoAction)

View File

@ -24,6 +24,7 @@ from oslo_messaging import exceptions as oslo_exc
import sqlalchemy as sa
from mistral.api.controllers.v2 import action_execution
from mistral.api.controllers.v2 import resources
from mistral.db.v2 import api as db_api
from mistral.db.v2.sqlalchemy import models
from mistral import exceptions as exc
@ -581,7 +582,8 @@ class TestActionExecutionsController(base.APITest):
self.assertEqual(1, len(resp.json['action_executions']))
self.assertDictEqual(ACTION_EX, resp.json['action_executions'][0])
@mock.patch.object(rest_utils, 'get_all')
@mock.patch.object(rest_utils, 'get_all',
return_value=resources.ActionExecutions())
def test_get_all_without_output(self, mock_get_all):
resp = self.app.get('/v2/action_executions')
@ -594,7 +596,8 @@ class TestActionExecutionsController(base.APITest):
resource_function
)
@mock.patch.object(rest_utils, 'get_all')
@mock.patch.object(rest_utils, 'get_all',
return_value=resources.ActionExecutions())
def test_get_all_with_output(self, mock_get_all):
resp = self.app.get('/v2/action_executions?include_output=true')

View File

@ -28,6 +28,7 @@ import sqlalchemy as sa
from webtest import app as webtest_app
from mistral.api.controllers.v2 import execution
from mistral.api.controllers.v2 import resources
from mistral.db.v2 import api as db_api
from mistral.db.v2.sqlalchemy import api as sql_db_api
from mistral.db.v2.sqlalchemy import models
@ -903,7 +904,8 @@ class TestExecutionsController(base.APITest):
)
@mock.patch.object(db_api, 'get_workflow_executions', MOCK_WF_EXECUTIONS)
@mock.patch.object(rest_utils, 'get_all')
@mock.patch.object(rest_utils, 'get_all',
return_value=resources.Executions())
def test_get_all_executions_with_output(self, mock_get_all):
resp = self.app.get('/v2/executions?include_output=true')
@ -918,7 +920,8 @@ class TestExecutionsController(base.APITest):
)
@mock.patch.object(db_api, 'get_workflow_executions', MOCK_WF_EXECUTIONS)
@mock.patch.object(rest_utils, 'get_all')
@mock.patch.object(rest_utils, 'get_all',
return_value=resources.Executions())
def test_get_all_executions_without_output(self, mock_get_all):
resp = self.app.get('/v2/executions')

View File

@ -25,7 +25,6 @@ from oslo_log import log as logging
from oslotest import base
import testtools.matchers as ttm
from mistral import config
from mistral import context as auth_context
from mistral.db.sqlalchemy import base as db_sa_base
from mistral.db.sqlalchemy import sqlite_lock
@ -34,8 +33,8 @@ from mistral.lang import parser as spec_parser
from mistral.services import action_manager
from mistral.services import security
from mistral.tests.unit import config as test_config
from mistral.utils import inspect_utils as i_utils
from mistral import version
from mistral_lib.utils import inspect_utils as i_utils
RESOURCES_PATH = 'tests/resources/'
LOG = logging.getLogger(__name__)
@ -265,14 +264,6 @@ class DbTestCase(BaseTest):
if cfg.CONF.database.connection.startswith('sqlite'):
cfg.CONF.set_default('connection', 'sqlite://', group='database')
# This option is normally registered in sync_db.py so we have to
# register it here specifically for tests.
cfg.CONF.register_opt(config.os_actions_mapping_path)
cfg.CONF.set_default(
'openstack_actions_mapping_path',
'tests/resources/openstack/test_mapping.json'
)
cfg.CONF.set_default('max_overflow', -1, group='database')
cfg.CONF.set_default('max_pool_size', 1000, group='database')

View File

@ -16,7 +16,7 @@ import six
from mistral import exceptions
from mistral.tests.unit import base
from mistral.utils import inspect_utils
from mistral_lib.utils import inspect_utils
class ExceptionTest(base.BaseTest):

View File

@ -1,69 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
#
# 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 time
from mistral.actions import std_actions
from mistral.tests.unit import base
from mistral.utils import inspect_utils as i_u
from mistral.workflow import commands
class ClassWithProperties(object):
a = 1
@property
def prop(self):
pass
class InspectUtilsTest(base.BaseTest):
def test_get_parameters_str(self):
action_class = std_actions.HTTPAction
parameters_str = i_u.get_arg_list_as_str(action_class.__init__)
http_action_params = (
'url, method="GET", params=null, body=null, '
'json=null, headers=null, cookies=null, auth=null, '
'timeout=null, allow_redirects=null, '
'proxies=null, verify=null'
)
self.assertEqual(http_action_params, parameters_str)
def test_get_parameters_str_all_mandatory(self):
clazz = commands.RunTask
parameters_str = i_u.get_arg_list_as_str(clazz.__init__)
self.assertEqual(
'wf_ex, wf_spec, task_spec, ctx, triggered_by=null,'
' handles_error=false',
parameters_str
)
def test_get_parameters_str_with_function_parameter(self):
def test_func(foo, bar=None, test_func=time.sleep):
pass
parameters_str = i_u.get_arg_list_as_str(test_func)
self.assertEqual("foo, bar=null", parameters_str)
def test_get_public_fields(self):
attrs = i_u.get_public_fields(ClassWithProperties)
self.assertEqual(attrs, {'a': 1})

View File

@ -1,61 +0,0 @@
# Copyright 2015 - Mirantis, Inc.
#
# 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 mistral import context as auth_context
from mistral import exceptions
from mistral.tests.unit import base
from mistral.utils.openstack import keystone
class KeystoneUtilsTest(base.BaseTest):
def setUp(self):
super(KeystoneUtilsTest, self).setUp()
self.values = {'id': 'my_id'}
def test_format_url_dollar_sign(self):
url_template = "http://host:port/v1/$(id)s"
expected = "http://host:port/v1/my_id"
self.assertEqual(
expected,
keystone.format_url(url_template, self.values)
)
def test_format_url_percent_sign(self):
url_template = "http://host:port/v1/%(id)s"
expected = "http://host:port/v1/my_id"
self.assertEqual(
expected,
keystone.format_url(url_template, self.values)
)
@mock.patch.object(keystone, 'client')
def test_get_endpoint_for_project_noauth(self, client):
client().tokens.get_token_data.return_value = {'token': None}
# service_catalog is not set by default.
auth_context.set_ctx(base.get_context())
self.addCleanup(auth_context.set_ctx, None)
self.assertRaises(
exceptions.UnauthorizedException,
keystone.get_endpoint_for_project,
'keystone'
)

View File

@ -1,94 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
#
# 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 inspect
import json
import six
def get_public_fields(obj):
"""Returns only public fields from object or class."""
public_attributes = [attr for attr in dir(obj)
if not attr.startswith("_")]
public_fields = {}
for attribute_str in public_attributes:
attr = getattr(obj, attribute_str)
is_field = not (inspect.isbuiltin(attr)
or inspect.isfunction(attr)
or inspect.ismethod(attr)
or isinstance(attr, property))
if is_field:
public_fields[attribute_str] = attr
return public_fields
def get_docstring(obj):
return inspect.getdoc(obj)
def get_arg_list(func):
argspec = get_args_spec(func)
args = argspec.args
if 'self' in args:
args.remove('self')
return args
def get_arg_list_as_str(func):
args = getattr(func, "__arguments__", None)
if args:
return args
argspec = get_args_spec(func)
defs = list(argspec.defaults or [])
args = get_arg_list(func)
diff_args_defs = len(args) - len(defs)
arg_str_list = []
for index, default in enumerate(args):
if index >= diff_args_defs:
try:
arg_str_list.append(
"%s=%s" % (
args[index],
json.dumps(defs[index - diff_args_defs])
)
)
except TypeError:
pass
else:
arg_str_list.append("%s" % args[index])
keywords = argspec.keywords if six.PY2 else argspec.varkw
if keywords:
arg_str_list.append("**%s" % keywords)
return ", ".join(arg_str_list)
def get_args_spec(func):
if six.PY2:
return inspect.getargspec(func)
return inspect.getfullargspec(func)

View File

@ -13,19 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import keystoneauth1.identity.generic as auth_plugins
from keystoneauth1 import loading
from keystoneauth1 import session as ks_session
from keystoneauth1.token_endpoint import Token
from keystoneclient import service_catalog as ks_service_catalog
from keystoneclient.v3 import client as ks_client
from keystoneclient.v3 import endpoints as ks_endpoints
from oslo_config import cfg
from oslo_utils import timeutils
import six
from mistral import context
from mistral import exceptions
CONF = cfg.CONF
@ -46,50 +38,12 @@ def client():
return cl
def _determine_verify(ctx):
if ctx.insecure:
return False
elif ctx.auth_cacert:
return ctx.auth_cacert
else:
return True
def client_for_admin():
return _admin_client()
def get_session_and_auth(ctx, **kwargs):
"""Get session and auth parameters.
:param ctx: action context
:return: dict to be used as kwargs for client service initialization
"""
if not ctx:
raise AssertionError('context is mandatory')
project_endpoint = get_endpoint_for_project(**kwargs)
endpoint = format_url(
project_endpoint.url,
{
'tenant_id': ctx.project_id,
'project_id': ctx.project_id
}
)
auth = Token(endpoint=endpoint, token=ctx.auth_token)
auth_uri = ctx.auth_uri or CONF.keystone_authtoken.www_authenticate_uri
ks_auth = Token(
endpoint=auth_uri,
token=ctx.auth_token
)
session = ks_session.Session(
auth=ks_auth,
verify=_determine_verify(ctx)
)
return {
"session": session,
"auth": auth
}
def client_for_trusts(trust_id):
return _admin_client(trust_id=trust_id)
def _admin_client(trust_id=None):
@ -138,170 +92,3 @@ def _admin_client(trust_id=None):
)
return ks_client.Client(session=sess)
def client_for_admin():
return _admin_client()
def client_for_trusts(trust_id):
return _admin_client(trust_id=trust_id)
def get_endpoint_for_project(service_name=None, service_type=None,
region_name=None):
if service_name is None and service_type is None:
raise exceptions.MistralException(
"Either 'service_name' or 'service_type' must be provided."
)
ctx = context.ctx()
service_catalog = obtain_service_catalog(ctx)
# When region_name is not passed, first get from context as region_name
# could be passed to rest api in http header ('X-Region-Name'). Otherwise,
# just get region from mistral configuration.
region = (region_name or ctx.region_name)
if service_name == 'keystone':
# Determining keystone endpoint should be done using
# keystone_authtoken section as this option is special for keystone.
region = region or CONF.keystone_authtoken.region_name
else:
region = region or CONF.openstack_actions.default_region
service_endpoints = service_catalog.get_endpoints(
service_name=service_name,
service_type=service_type,
region_name=region
)
endpoint = None
os_actions_endpoint_type = CONF.openstack_actions.os_actions_endpoint_type
for endpoints in six.itervalues(service_endpoints):
for ep in endpoints:
# is V3 interface?
if 'interface' in ep:
interface_type = ep['interface']
if os_actions_endpoint_type in interface_type:
endpoint = ks_endpoints.Endpoint(
None,
ep,
loaded=True
)
break
# is V2 interface?
if 'publicURL' in ep:
endpoint_data = {
'url': ep['publicURL'],
'region': ep['region']
}
endpoint = ks_endpoints.Endpoint(
None,
endpoint_data,
loaded=True
)
break
if not endpoint:
raise exceptions.MistralException(
"No endpoints found [service_name=%s, service_type=%s,"
" region_name=%s]"
% (service_name, service_type, region)
)
else:
return endpoint
def obtain_service_catalog(ctx):
token = ctx.auth_token
if ctx.is_trust_scoped and is_token_trust_scoped(token):
if ctx.trust_id is None:
raise Exception(
"'trust_id' must be provided in the admin context."
)
# trust_client = client_for_trusts(ctx.trust_id)
# Using trust client, it can't validate token
# when cron trigger running because keystone policy
# don't allow do this. So we need use admin client to
# get token data
token_data = _admin_client().tokens.get_token_data(
token,
include_catalog=True
)
response = token_data['token']
else:
response = ctx.service_catalog
# Target service catalog may not be passed via API.
# If we don't have the catalog yet, it should be requested.
if not response:
response = client().tokens.get_token_data(
token,
include_catalog=True
)['token']
if not response:
raise exceptions.UnauthorizedException()
service_catalog = ks_service_catalog.ServiceCatalog.factory(response)
return service_catalog
def get_keystone_endpoint():
return get_endpoint_for_project('keystone', service_type='identity')
def get_keystone_url():
return get_endpoint_for_project('keystone', service_type='identity').url
def format_url(url_template, values):
# Since we can't use keystone module, we can do similar thing:
# see https://github.com/openstack/keystone/blob/master/keystone/
# catalog/core.py#L42-L60
return url_template.replace('$(', '%(') % values
def is_token_trust_scoped(auth_token):
return 'OS-TRUST:trust' in client_for_admin().tokens.validate(auth_token)
def get_admin_session():
"""Returns a keystone session from Mistral's service credentials."""
if CONF.keystone_authtoken.auth_type is None:
auth = auth_plugins.Password(
CONF.keystone_authtoken.www_authenticate_uri,
username=CONF.keystone_authtoken.admin_user,
password=CONF.keystone_authtoken.admin_password,
project_name=CONF.keystone_authtoken.admin_tenant_name,
# NOTE(jaosorior): Once mistral supports keystone v3 properly, we
# can fetch the following values from the configuration.
user_domain_name='Default',
project_domain_name='Default')
return ks_session.Session(auth=auth)
else:
auth = loading.load_auth_from_conf_options(
CONF,
'keystone_authtoken'
)
return loading.load_session_from_conf_options(
CONF,
'keystone',
auth=auth
)
def will_expire_soon(expires_at):
if not expires_at:
return False
stale_duration = CONF.expiration_token_duration
assert stale_duration, "expiration_token_duration must be specified"
expires = timeutils.parse_isotime(expires_at)
return timeutils.is_soon(expires, stale_duration)

View File

@ -22,9 +22,9 @@ from mistral.db.v2.sqlalchemy import models
from mistral import exceptions as exc
from mistral import expressions as expr
from mistral.lang import parser as spec_parser
from mistral.utils import inspect_utils
from mistral.workflow import states
from mistral_lib import utils
from mistral_lib.utils import inspect_utils
LOG = logging.getLogger(__name__)
CONF = cfg.CONF

View File

@ -0,0 +1,3 @@
---
features:
- Move Mistral actions for OpenStack to mistral-extra library

View File

@ -3,18 +3,16 @@
# process, which may cause wedges in the gate later.
alembic>=0.8.10 # MIT
aodhclient>=0.9.0 # Apache-2.0
Babel!=2.4.0,>=2.3.4 # BSD
croniter>=0.3.4 # MIT License
cachetools>=2.0.0 # MIT License
dogpile.cache>=0.6.2 # BSD
eventlet!=0.20.1,!=0.21.0,!=0.23.0,!=0.25.0,>=0.20.0 # MIT
gnocchiclient>=3.3.1 # Apache-2.0
Jinja2>=2.10 # BSD License (3 clause)
#jsonschema>=2.6.0 # MIT
jsonschema>=2.6.0 # MIT
keystonemiddleware>=4.18.0 # Apache-2.0
kombu!=4.0.2,>=4.6.1 # BSD
mistral-lib>=1.2.0 # Apache-2.0
mistral-lib>=1.4.0 # Apache-2.0
networkx<2.3,>=1.10;python_version<'3.0' # BSD
networkx>=2.3;python_version>='3.4' # BSD
oslo.concurrency>=3.26.0 # Apache-2.0
@ -33,29 +31,6 @@ osprofiler>=1.4.0 # Apache-2.0
paramiko>=2.0.0 # LGPLv2.1+
pbr!=2.1.0,>=2.0.0 # Apache-2.0
pecan>=1.2.1 # BSD
python-barbicanclient>=4.5.2 # Apache-2.0
python-cinderclient!=4.0.0,>=3.3.0 # Apache-2.0
python-zaqarclient>=1.0.0 # Apache-2.0
python-designateclient>=2.7.0 # Apache-2.0
python-glanceclient>=2.8.0 # Apache-2.0
python-glareclient>=0.3.0 # Apache-2.0
python-heatclient>=1.10.0 # Apache-2.0
python-keystoneclient>=3.8.0 # Apache-2.0
python-mistralclient!=3.2.0,>=3.1.0 # Apache-2.0
python-manilaclient>=1.23.0 # Apache-2.0
python-magnumclient>=2.1.0 # Apache-2.0
python-muranoclient>=0.8.2 # Apache-2.0
python-neutronclient>=6.7.0 # Apache-2.0
python-novaclient>=9.1.0 # Apache-2.0
python-senlinclient>=1.1.0 # Apache-2.0
python-swiftclient>=3.2.0 # Apache-2.0
python-tackerclient>=0.8.0 # Apache-2.0
python-troveclient>=2.2.0 # Apache-2.0
python-ironicclient!=2.7.1,!=3.0.0,>=2.7.0 # Apache-2.0
python-ironic-inspector-client>=1.5.0 # Apache-2.0
python-vitrageclient>=2.0.0 # Apache-2.0
python-zunclient>=3.4.0 # Apache-2.0
python-qinlingclient>=1.0.0 # Apache-2.0
PyJWT>=1.5 # MIT
PyYAML>=5.1 # MIT
requests>=2.14.2 # Apache-2.0

View File

@ -4,26 +4,16 @@
hacking>=1.1.0 # Apache-2.0
coverage!=4.4,>=4.0 # Apache-2.0
croniter>=0.3.4 # MIT License
doc8>=0.6.0 # Apache-2.0
Pygments>=2.2.0 # BSD license
fixtures>=3.0.0 # Apache-2.0/BSD
keystonemiddleware>=4.18.0 # Apache-2.0
mistral-lib>=1.2.0 # Apache-2.0
mock>=2.0.0 # BSD
networkx<2.3,>=1.10;python_version<'3.0' # BSD
networkx>=2.3;python_version>='3.4' # BSD
nose>=1.3.7 # LGPL
oslotest>=3.2.0 # Apache-2.0
oslo.db>=4.27.0 # Apache-2.0
oslo.messaging>=5.29.0 # Apache-2.0
oslo.policy>=1.30.0 # Apache-2.0
osprofiler>=1.4.0 # Apache-2.0
os-api-ref>=1.4.0 # Apache-2.0
oauthlib>=0.6.2 # BSD
requests-mock>=1.2.0 # Apache-2.0
tooz>=1.58.0 # Apache-2.0
tempest>=17.1.0 # Apache-2.0
stestr>=2.0.0 # Apache-2.0
testtools>=2.2.0 # MIT
unittest2>=1.1.0 # BSD
WSME>=0.8.0 # MIT

View File

@ -1,356 +0,0 @@
# Copyright 2015 - Mirantis, Inc.
#
# 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 argparse
import collections
import inspect
import json
import os
from aodhclient.v2 import base as aodh_base
from aodhclient.v2 import client as aodhclient
from barbicanclient import base as barbican_base
from barbicanclient import client as barbicanclient
from cinderclient.apiclient import base as cinder_base
from cinderclient.v2 import client as cinderclient
from designateclient import client as designateclient
from glanceclient.v2 import client as glanceclient
from glareclient.v1 import client as glareclient
from gnocchiclient.v1 import base as gnocchi_base
from gnocchiclient.v1 import client as gnocchiclient
from heatclient.common import base as heat_base
from heatclient.v1 import client as heatclient
from ironicclient.common import base as ironic_base
from ironicclient.v1 import client as ironicclient
from keystoneclient import base as keystone_base
from keystoneclient.v3 import client as keystoneclient
from magnumclient.common import base as magnum_base
from magnumclient.v1 import client as magnumclient
from manilaclient import base as manila_base
from manilaclient.v2 import client as manilaclient
from mistralclient.api import base as mistral_base
from mistralclient.api.v2 import client as mistralclient
from muranoclient.common import base as murano_base
from muranoclient.v1 import client as muranoclient
from novaclient import base as nova_base
from novaclient import client as novaclient
from troveclient import base as trove_base
from troveclient.v1 import client as troveclient
# TODO(nmakhotkin): Find a rational way to do it for neutron.
# TODO(nmakhotkin): Implement recursive way of searching for managers
# TODO(nmakhotkin): (e.g. keystone).
# TODO(dprince): Need to update ironic_inspector_client before we can
# plug it in cleanly here.
# TODO(dprince): Swiftclient doesn't currently support discovery
# like we do in this class.
# TODO(therve): Zaqarclient doesn't currently support discovery
# like we do in this class.
# TODO(sa709c): Tackerclient doesn't currently support discovery
# like we do in this class.
"""It is simple CLI tool which allows to see and update mapping.json file
if needed. mapping.json contains all allowing OpenStack actions sorted by
service name. Usage example:
python tools/get_action_list.py nova
The result will be simple JSON containing action name as a key and method
path as a value. For updating mapping.json it is need to copy all keys and
values of the result to corresponding section of mapping.json:
...mapping.json...
"nova": {
<put it here>
},
...mapping.json...
Note: in case of Keystone service, correct OS_AUTH_URL v3 and the rest auth
info must be provided. It can be provided either via environment variables
or CLI arguments. See --help for details.
"""
BASE_HEAT_MANAGER = heat_base.HookableMixin
BASE_NOVA_MANAGER = nova_base.HookableMixin
BASE_KEYSTONE_MANAGER = keystone_base.Manager
BASE_CINDER_MANAGER = cinder_base.HookableMixin
BASE_MISTRAL_MANAGER = mistral_base.ResourceManager
BASE_TROVE_MANAGER = trove_base.Manager
BASE_IRONIC_MANAGER = ironic_base.Manager
BASE_BARBICAN_MANAGER = barbican_base.BaseEntityManager
BASE_MANILA_MANAGER = manila_base.Manager
BASE_MAGNUM_MANAGER = magnum_base.Manager
BASE_MURANO_MANAGER = murano_base.Manager
BASE_AODH_MANAGER = aodh_base.Manager
BASE_GNOCCHI_MANAGER = gnocchi_base.Manager
def get_parser():
parser = argparse.ArgumentParser(
description='Gets All needed methods of OpenStack clients.',
usage="python get_action_list.py <service_name>"
)
parser.add_argument(
'service',
choices=CLIENTS.keys(),
help='Service name which methods need to be found.'
)
parser.add_argument(
'--os-username',
dest='username',
default=os.environ.get('OS_USERNAME', 'admin'),
help='Authentication username (Env: OS_USERNAME)'
)
parser.add_argument(
'--os-password',
dest='password',
default=os.environ.get('OS_PASSWORD', 'openstack'),
help='Authentication password (Env: OS_PASSWORD)'
)
parser.add_argument(
'--os-tenant-name',
dest='tenant_name',
default=os.environ.get('OS_TENANT_NAME', 'Default'),
help='Authentication tenant name (Env: OS_TENANT_NAME)'
)
parser.add_argument(
'--os-auth-url',
dest='auth_url',
default=os.environ.get('OS_AUTH_URL'),
help='Authentication URL (Env: OS_AUTH_URL)'
)
return parser
GLANCE_NAMESPACE_LIST = [
'image_members', 'image_tags', 'images', 'schemas', 'tasks',
'metadefs_resource_type', 'metadefs_property', 'metadefs_object',
'metadefs_tag', 'metadefs_namespace', 'versions'
]
DESIGNATE_NAMESPACE_LIST = [
'diagnostics', 'domains', 'quotas', 'records', 'reports', 'servers',
'sync', 'touch'
]
GLARE_NAMESPACE_LIST = ['artifacts', 'versions']
def get_nova_client(**kwargs):
return novaclient.Client(2)
def get_keystone_client(**kwargs):
return keystoneclient.Client(**kwargs)
def get_glance_client(**kwargs):
return glanceclient.Client(kwargs.get('auth_url'))
def get_heat_client(**kwargs):
return heatclient.Client('')
def get_cinder_client(**kwargs):
return cinderclient.Client()
def get_mistral_client(**kwargs):
return mistralclient.Client()
def get_trove_client(**kwargs):
return troveclient.Client('username', 'password')
def get_ironic_client(**kwargs):
return ironicclient.Client("http://127.0.0.1:6385/")
def get_barbican_client(**kwargs):
return barbicanclient.Client(
project_id="1",
endpoint="http://127.0.0.1:9311"
)
def get_designate_client(**kwargs):
return designateclient.Client('2')
def get_magnum_client(**kwargs):
return magnumclient.Client()
def get_murano_client(**kwargs):
return muranoclient.Client('')
def get_aodh_client(**kwargs):
return aodhclient.Client('')
def get_gnocchi_client(**kwargs):
return gnocchiclient.Client()
def get_glare_client(**kwargs):
return glareclient.Client('')
def get_manila_client(**kwargs):
return manilaclient.Client(
input_auth_token='token',
service_catalog_url='http://127.0.0.1:8786'
)
CLIENTS = {
'nova': get_nova_client,
'heat': get_heat_client,
'cinder': get_cinder_client,
'keystone': get_keystone_client,
'glance': get_glance_client,
'trove': get_trove_client,
'ironic': get_ironic_client,
'barbican': get_barbican_client,
'mistral': get_mistral_client,
'designate': get_designate_client,
'magnum': get_magnum_client,
'murano': get_murano_client,
'aodh': get_aodh_client,
'gnocchi': get_gnocchi_client,
'glare': get_glare_client,
'manila': get_manila_client,
# 'neutron': get_nova_client
# 'baremetal_introspection': ...
# 'swift': ...
# 'zaqar': ...
}
BASE_MANAGERS = {
'nova': BASE_NOVA_MANAGER,
'heat': BASE_HEAT_MANAGER,
'cinder': BASE_CINDER_MANAGER,
'keystone': BASE_KEYSTONE_MANAGER,
'glance': None,
'trove': BASE_TROVE_MANAGER,
'ironic': BASE_IRONIC_MANAGER,
'barbican': BASE_BARBICAN_MANAGER,
'mistral': BASE_MISTRAL_MANAGER,
'designate': None,
'magnum': BASE_MAGNUM_MANAGER,
'murano': BASE_MURANO_MANAGER,
'aodh': BASE_AODH_MANAGER,
'gnocchi': BASE_GNOCCHI_MANAGER,
'glare': None,
'manila': BASE_MANILA_MANAGER,
# 'neutron': BASE_NOVA_MANAGER
# 'baremetal_introspection': ...
# 'swift': ...
# 'zaqar': ...
}
NAMESPACES = {
'glance': GLANCE_NAMESPACE_LIST,
'designate': DESIGNATE_NAMESPACE_LIST,
'glare': GLARE_NAMESPACE_LIST
}
ALLOWED_ATTRS = ['service_catalog', 'catalog']
FORBIDDEN_METHODS = [
'add_hook', 'alternate_service_type', 'completion_cache', 'run_hooks',
'write_to_completion_cache', 'model', 'build_key_only_query', 'build_url',
'head', 'put', 'unvalidated_model'
]
def get_public_attrs(obj):
all_attrs = dir(obj)
return [a for a in all_attrs if not a.startswith('_')]
def get_public_methods(attr, client):
hierarchy_list = attr.split('.')
attribute = client
for attr in hierarchy_list:
attribute = getattr(attribute, attr)
all_attributes_list = get_public_attrs(attribute)
methods = []
for a in all_attributes_list:
allowed = a in ALLOWED_ATTRS
forbidden = a in FORBIDDEN_METHODS
if (not forbidden and
(allowed or inspect.ismethod(getattr(attribute, a)))):
methods.append(a)
return methods
def get_manager_list(service_name, client):
base_manager = BASE_MANAGERS[service_name]
if not base_manager:
return NAMESPACES[service_name]
public_attrs = get_public_attrs(client)
manager_list = []
for attr in public_attrs:
if (isinstance(getattr(client, attr), base_manager)
or attr in ALLOWED_ATTRS):
manager_list.append(attr)
return manager_list
def get_mapping_for_service(service, client):
mapping = collections.OrderedDict()
for man in get_manager_list(service, client):
public_methods = get_public_methods(man, client)
for method in public_methods:
key = "%s_%s" % (man, method)
value = "%s.%s" % (man, method)
mapping[key] = value
return mapping
def print_mapping(mapping):
print(json.dumps(mapping, indent=8, separators=(',', ': ')))
if __name__ == "__main__":
args = get_parser().parse_args()
auth_info = {
'username': args.username,
'tenant_name': args.tenant_name,
'password': args.password,
'auth_url': args.auth_url
}
service = args.service
client = CLIENTS.get(service)(**auth_info)
print("Find methods for service: %s..." % service)
print_mapping(get_mapping_for_service(service, client))

View File

@ -36,8 +36,6 @@ def main():
for group, opts in keystonemw_opts.list_auth_token_opts():
CONF.register_opts(opts, group=group)
CONF.register_cli_opt(config.os_actions_mapping_path)
logging.register_options(CONF)
config.parse_args()