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:
parent
95d9f899db
commit
8bdf341af7
16
.zuul.yaml
16
.zuul.yaml
@ -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:
|
||||
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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
|
||||
}
|
||||
)
|
@ -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
@ -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
@ -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)
|
||||
|
@ -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():
|
||||
|
@ -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)
|
||||
|
@ -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__)
|
||||
|
||||
|
@ -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=''):
|
||||
|
@ -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 %>
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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)
|
@ -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)
|
@ -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)
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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})
|
@ -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'
|
||||
)
|
@ -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)
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Move Mistral actions for OpenStack to mistral-extra library
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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))
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user