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
|
USE_PYTHON3: true
|
||||||
required-projects:
|
required-projects:
|
||||||
- openstack/rally-openstack
|
- openstack/rally-openstack
|
||||||
|
- openstack/mistral-lib
|
||||||
|
- openstack/mistral-extra
|
||||||
|
|
||||||
- job:
|
- job:
|
||||||
name: mistral-docker-buildimage
|
name: mistral-docker-buildimage
|
||||||
@ -46,6 +48,7 @@
|
|||||||
timeout: 3600
|
timeout: 3600
|
||||||
required-projects:
|
required-projects:
|
||||||
- openstack/mistral-lib
|
- openstack/mistral-lib
|
||||||
|
- openstack/mistral-extra
|
||||||
|
|
||||||
- job:
|
- job:
|
||||||
name: mistral-tox-unit-postgresql
|
name: mistral-tox-unit-postgresql
|
||||||
@ -60,6 +63,7 @@
|
|||||||
timeout: 3600
|
timeout: 3600
|
||||||
required-projects:
|
required-projects:
|
||||||
- openstack/mistral-lib
|
- openstack/mistral-lib
|
||||||
|
- openstack/mistral-extra
|
||||||
|
|
||||||
- project:
|
- project:
|
||||||
templates:
|
templates:
|
||||||
@ -78,12 +82,23 @@
|
|||||||
- ^releasenotes/.*$
|
- ^releasenotes/.*$
|
||||||
required-projects:
|
required-projects:
|
||||||
- openstack/mistral-lib
|
- openstack/mistral-lib
|
||||||
|
- openstack/mistral-extra
|
||||||
- openstack-tox-py36:
|
- openstack-tox-py36:
|
||||||
required-projects:
|
required-projects:
|
||||||
- openstack/mistral-lib
|
- openstack/mistral-lib
|
||||||
|
- openstack/mistral-extra
|
||||||
- openstack-tox-py37:
|
- openstack-tox-py37:
|
||||||
required-projects:
|
required-projects:
|
||||||
- openstack/mistral-lib
|
- 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
|
||||||
- mistral-devstack-tempest-ipv6-only
|
- mistral-devstack-tempest-ipv6-only
|
||||||
- mistral-devstack-non-apache-tempest-ipv6-only
|
- mistral-devstack-non-apache-tempest-ipv6-only
|
||||||
@ -103,6 +118,7 @@
|
|||||||
- openstack-tox-lower-constraints:
|
- openstack-tox-lower-constraints:
|
||||||
required-projects:
|
required-projects:
|
||||||
- openstack/mistral-lib
|
- openstack/mistral-lib
|
||||||
|
- openstack/mistral-extra
|
||||||
gate:
|
gate:
|
||||||
queue: mistral
|
queue: mistral
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -161,6 +161,13 @@ function install_mistral_lib {
|
|||||||
fi
|
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
|
# start_mistral - Start running processes
|
||||||
function start_mistral {
|
function start_mistral {
|
||||||
# If the site is not enabled then we are in a grenade scenario
|
# 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"
|
echo_summary "Installing mistral"
|
||||||
install_mistral
|
install_mistral
|
||||||
install_mistral_lib
|
install_mistral_lib
|
||||||
|
install_mistral_extra
|
||||||
install_mistral_pythonclient
|
install_mistral_pythonclient
|
||||||
elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
|
elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
|
||||||
echo_summary "Configuring mistral"
|
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_BRANCH=${MISTRAL_LIB_BRANCH:-master}
|
||||||
MISTRAL_LIB_DIR=${DEST}/mistral-lib
|
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
|
GITDIR["python-mistralclient"]=${DEST}/python-mistralclient
|
||||||
GITREPO["python-mistralclient"]=${MISTRALCLIENT_REPO:-${GIT_BASE}/openstack/python-mistralclient.git}
|
GITREPO["python-mistralclient"]=${MISTRALCLIENT_REPO:-${GIT_BASE}/openstack/python-mistralclient.git}
|
||||||
GITBRANCH["python-mistralclient"]=${MISTRALCLIENT_BRANCH:-master}
|
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-httpdomain>=1.3.0 # BSD
|
||||||
sphinxcontrib-pecanwsme>=0.8.0 # Apache-2.0
|
sphinxcontrib-pecanwsme>=0.8.0 # Apache-2.0
|
||||||
openstackdocstheme>=1.30.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
|
reno>=2.5.0 # Apache-2.0
|
||||||
|
@ -50,7 +50,7 @@ logutils==0.3.5
|
|||||||
Mako==0.4.0
|
Mako==0.4.0
|
||||||
MarkupSafe==1.0
|
MarkupSafe==1.0
|
||||||
mccabe==0.2.1
|
mccabe==0.2.1
|
||||||
mistral-lib==1.2.0
|
mistral-lib==1.4.0
|
||||||
mock==2.0.0
|
mock==2.0.0
|
||||||
monotonic==0.6
|
monotonic==0.6
|
||||||
mox3==0.20.0
|
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 itertools
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
|
|
||||||
from keystoneauth1 import loading
|
from keystoneauth1 import loading
|
||||||
from oslo_config import cfg
|
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 = [
|
yaql_opts = [
|
||||||
cfg.IntOpt(
|
cfg.IntOpt(
|
||||||
@ -692,7 +658,6 @@ EXECUTION_EXPIRATION_POLICY_GROUP = 'execution_expiration_policy'
|
|||||||
ACTION_HEARTBEAT_GROUP = 'action_heartbeat'
|
ACTION_HEARTBEAT_GROUP = 'action_heartbeat'
|
||||||
PROFILER_GROUP = profiler.list_opts()[0][0]
|
PROFILER_GROUP = profiler.list_opts()[0][0]
|
||||||
KEYCLOAK_OIDC_GROUP = "keycloak_oidc"
|
KEYCLOAK_OIDC_GROUP = "keycloak_oidc"
|
||||||
OPENSTACK_ACTIONS_GROUP = 'openstack_actions'
|
|
||||||
YAQL_GROUP = "yaql"
|
YAQL_GROUP = "yaql"
|
||||||
KEYSTONE_GROUP = "keystone"
|
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(coordination_opts, group=COORDINATION_GROUP)
|
||||||
CONF.register_opts(profiler_opts, group=PROFILER_GROUP)
|
CONF.register_opts(profiler_opts, group=PROFILER_GROUP)
|
||||||
CONF.register_opts(keycloak_oidc_opts, group=KEYCLOAK_OIDC_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)
|
CONF.register_opts(yaql_opts, group=YAQL_GROUP)
|
||||||
loading.register_session_conf_options(CONF, KEYSTONE_GROUP)
|
loading.register_session_conf_options(CONF, KEYSTONE_GROUP)
|
||||||
|
|
||||||
@ -773,7 +737,6 @@ def list_opts():
|
|||||||
(EXECUTION_EXPIRATION_POLICY_GROUP, execution_expiration_policy_opts),
|
(EXECUTION_EXPIRATION_POLICY_GROUP, execution_expiration_policy_opts),
|
||||||
(PROFILER_GROUP, profiler_opts),
|
(PROFILER_GROUP, profiler_opts),
|
||||||
(KEYCLOAK_OIDC_GROUP, keycloak_oidc_opts),
|
(KEYCLOAK_OIDC_GROUP, keycloak_oidc_opts),
|
||||||
(OPENSTACK_ACTIONS_GROUP, openstack_actions_opts),
|
|
||||||
(YAQL_GROUP, yaql_opts),
|
(YAQL_GROUP, yaql_opts),
|
||||||
(ACTION_HEARTBEAT_GROUP, action_heartbeat_opts),
|
(ACTION_HEARTBEAT_GROUP, action_heartbeat_opts),
|
||||||
(None, default_group_opts)
|
(None, default_group_opts)
|
||||||
|
@ -24,7 +24,6 @@ from oslo_utils import importutils
|
|||||||
import six
|
import six
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from mistral import config
|
|
||||||
from mistral.services import action_manager
|
from mistral.services import action_manager
|
||||||
from mistral.services import workflows
|
from mistral.services import workflows
|
||||||
|
|
||||||
@ -127,7 +126,6 @@ command_opt = cfg.SubCommandOpt('command',
|
|||||||
handler=add_command_parsers)
|
handler=add_command_parsers)
|
||||||
|
|
||||||
CONF.register_cli_opt(command_opt)
|
CONF.register_cli_opt(command_opt)
|
||||||
CONF.register_cli_opt(config.os_actions_mapping_path)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -20,8 +20,8 @@ import jsonschema
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from mistral import exceptions as exc
|
from mistral import exceptions as exc
|
||||||
from mistral.utils import inspect_utils
|
|
||||||
from mistral.workflow import data_flow
|
from mistral.workflow import data_flow
|
||||||
|
from mistral_lib.utils import inspect_utils
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
@ -24,7 +24,7 @@ from mistral import exceptions as exc
|
|||||||
from mistral.executors import base
|
from mistral.executors import base
|
||||||
from mistral.rpc import clients as rpc
|
from mistral.rpc import clients as rpc
|
||||||
from mistral.services import action_heartbeat_sender
|
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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -18,12 +18,11 @@ from oslo_log import log as logging
|
|||||||
from stevedore import extension
|
from stevedore import extension
|
||||||
|
|
||||||
from mistral.actions import action_factory
|
from mistral.actions import action_factory
|
||||||
from mistral.actions import generator_factory
|
|
||||||
from mistral.db.v2 import api as db_api
|
from mistral.db.v2 import api as db_api
|
||||||
from mistral import exceptions as exc
|
from mistral import exceptions as exc
|
||||||
from mistral.services import actions
|
from mistral.services import actions
|
||||||
from mistral.utils import inspect_utils as i_utils
|
|
||||||
from mistral_lib import utils
|
from mistral_lib import utils
|
||||||
|
from mistral_lib.utils import inspect_utils as i_utils
|
||||||
|
|
||||||
|
|
||||||
# TODO(rakhmerov): Make methods more consistent and granular.
|
# TODO(rakhmerov): Make methods more consistent and granular.
|
||||||
@ -84,25 +83,32 @@ def sync_db():
|
|||||||
|
|
||||||
|
|
||||||
def _register_dynamic_action_classes(namespace=''):
|
def _register_dynamic_action_classes(namespace=''):
|
||||||
for generator in generator_factory.all_generators():
|
extensions = extension.ExtensionManager(
|
||||||
actions = generator.create_actions()
|
namespace='mistral.generators',
|
||||||
|
invoke_on_load=True
|
||||||
|
)
|
||||||
|
|
||||||
module = generator.base_action_class.__module__
|
for ext in extensions:
|
||||||
class_name = generator.base_action_class.__name__
|
for generator in ext.obj:
|
||||||
|
_register_actions(generator, namespace)
|
||||||
|
|
||||||
action_class_str = "%s.%s" % (module, class_name)
|
|
||||||
|
|
||||||
for action in actions:
|
def _register_actions(generator, namespace):
|
||||||
attrs = i_utils.get_public_fields(action['class'])
|
module = generator.base_action_class.__module__
|
||||||
|
class_name = generator.base_action_class.__name__
|
||||||
|
action_class_str = "%s.%s" % (module, class_name)
|
||||||
|
|
||||||
register_action_class(
|
for action in generator.create_actions():
|
||||||
action['name'],
|
attrs = i_utils.get_public_fields(action['class'])
|
||||||
action_class_str,
|
|
||||||
attrs,
|
register_action_class(
|
||||||
action['description'],
|
action['name'],
|
||||||
action['arg_list'],
|
action_class_str,
|
||||||
namespace=namespace
|
attrs,
|
||||||
)
|
action['description'],
|
||||||
|
action['arg_list'],
|
||||||
|
namespace=namespace
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def register_action_classes(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.ssh")
|
||||||
self._assert_single_item(action_list, name="std.javascript")
|
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):
|
def test_get_action_class(self):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
issubclass(a_m.get_action_class("std.echo"), std.EchoAction)
|
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
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from mistral.api.controllers.v2 import action_execution
|
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 import api as db_api
|
||||||
from mistral.db.v2.sqlalchemy import models
|
from mistral.db.v2.sqlalchemy import models
|
||||||
from mistral import exceptions as exc
|
from mistral import exceptions as exc
|
||||||
@ -581,7 +582,8 @@ class TestActionExecutionsController(base.APITest):
|
|||||||
self.assertEqual(1, len(resp.json['action_executions']))
|
self.assertEqual(1, len(resp.json['action_executions']))
|
||||||
self.assertDictEqual(ACTION_EX, resp.json['action_executions'][0])
|
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):
|
def test_get_all_without_output(self, mock_get_all):
|
||||||
resp = self.app.get('/v2/action_executions')
|
resp = self.app.get('/v2/action_executions')
|
||||||
|
|
||||||
@ -594,7 +596,8 @@ class TestActionExecutionsController(base.APITest):
|
|||||||
resource_function
|
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):
|
def test_get_all_with_output(self, mock_get_all):
|
||||||
resp = self.app.get('/v2/action_executions?include_output=true')
|
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 webtest import app as webtest_app
|
||||||
|
|
||||||
from mistral.api.controllers.v2 import execution
|
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 import api as db_api
|
||||||
from mistral.db.v2.sqlalchemy import api as sql_db_api
|
from mistral.db.v2.sqlalchemy import api as sql_db_api
|
||||||
from mistral.db.v2.sqlalchemy import models
|
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(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):
|
def test_get_all_executions_with_output(self, mock_get_all):
|
||||||
resp = self.app.get('/v2/executions?include_output=true')
|
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(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):
|
def test_get_all_executions_without_output(self, mock_get_all):
|
||||||
resp = self.app.get('/v2/executions')
|
resp = self.app.get('/v2/executions')
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ from oslo_log import log as logging
|
|||||||
from oslotest import base
|
from oslotest import base
|
||||||
import testtools.matchers as ttm
|
import testtools.matchers as ttm
|
||||||
|
|
||||||
from mistral import config
|
|
||||||
from mistral import context as auth_context
|
from mistral import context as auth_context
|
||||||
from mistral.db.sqlalchemy import base as db_sa_base
|
from mistral.db.sqlalchemy import base as db_sa_base
|
||||||
from mistral.db.sqlalchemy import sqlite_lock
|
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 action_manager
|
||||||
from mistral.services import security
|
from mistral.services import security
|
||||||
from mistral.tests.unit import config as test_config
|
from mistral.tests.unit import config as test_config
|
||||||
from mistral.utils import inspect_utils as i_utils
|
|
||||||
from mistral import version
|
from mistral import version
|
||||||
|
from mistral_lib.utils import inspect_utils as i_utils
|
||||||
|
|
||||||
RESOURCES_PATH = 'tests/resources/'
|
RESOURCES_PATH = 'tests/resources/'
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -265,14 +264,6 @@ class DbTestCase(BaseTest):
|
|||||||
if cfg.CONF.database.connection.startswith('sqlite'):
|
if cfg.CONF.database.connection.startswith('sqlite'):
|
||||||
cfg.CONF.set_default('connection', 'sqlite://', group='database')
|
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_overflow', -1, group='database')
|
||||||
cfg.CONF.set_default('max_pool_size', 1000, group='database')
|
cfg.CONF.set_default('max_pool_size', 1000, group='database')
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import six
|
|||||||
|
|
||||||
from mistral import exceptions
|
from mistral import exceptions
|
||||||
from mistral.tests.unit import base
|
from mistral.tests.unit import base
|
||||||
from mistral.utils import inspect_utils
|
from mistral_lib.utils import inspect_utils
|
||||||
|
|
||||||
|
|
||||||
class ExceptionTest(base.BaseTest):
|
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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import keystoneauth1.identity.generic as auth_plugins
|
|
||||||
from keystoneauth1 import loading
|
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 client as ks_client
|
||||||
from keystoneclient.v3 import endpoints as ks_endpoints
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import timeutils
|
|
||||||
import six
|
|
||||||
|
|
||||||
from mistral import context
|
from mistral import context
|
||||||
from mistral import exceptions
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
@ -46,50 +38,12 @@ def client():
|
|||||||
return cl
|
return cl
|
||||||
|
|
||||||
|
|
||||||
def _determine_verify(ctx):
|
def client_for_admin():
|
||||||
if ctx.insecure:
|
return _admin_client()
|
||||||
return False
|
|
||||||
elif ctx.auth_cacert:
|
|
||||||
return ctx.auth_cacert
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def get_session_and_auth(ctx, **kwargs):
|
def client_for_trusts(trust_id):
|
||||||
"""Get session and auth parameters.
|
return _admin_client(trust_id=trust_id)
|
||||||
|
|
||||||
: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 _admin_client(trust_id=None):
|
def _admin_client(trust_id=None):
|
||||||
@ -138,170 +92,3 @@ def _admin_client(trust_id=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return ks_client.Client(session=sess)
|
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 exceptions as exc
|
||||||
from mistral import expressions as expr
|
from mistral import expressions as expr
|
||||||
from mistral.lang import parser as spec_parser
|
from mistral.lang import parser as spec_parser
|
||||||
from mistral.utils import inspect_utils
|
|
||||||
from mistral.workflow import states
|
from mistral.workflow import states
|
||||||
from mistral_lib import utils
|
from mistral_lib import utils
|
||||||
|
from mistral_lib.utils import inspect_utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
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.
|
# process, which may cause wedges in the gate later.
|
||||||
|
|
||||||
alembic>=0.8.10 # MIT
|
alembic>=0.8.10 # MIT
|
||||||
aodhclient>=0.9.0 # Apache-2.0
|
|
||||||
Babel!=2.4.0,>=2.3.4 # BSD
|
Babel!=2.4.0,>=2.3.4 # BSD
|
||||||
croniter>=0.3.4 # MIT License
|
croniter>=0.3.4 # MIT License
|
||||||
cachetools>=2.0.0 # MIT License
|
cachetools>=2.0.0 # MIT License
|
||||||
dogpile.cache>=0.6.2 # BSD
|
dogpile.cache>=0.6.2 # BSD
|
||||||
eventlet!=0.20.1,!=0.21.0,!=0.23.0,!=0.25.0,>=0.20.0 # MIT
|
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)
|
Jinja2>=2.10 # BSD License (3 clause)
|
||||||
#jsonschema>=2.6.0 # MIT
|
jsonschema>=2.6.0 # MIT
|
||||||
keystonemiddleware>=4.18.0 # Apache-2.0
|
keystonemiddleware>=4.18.0 # Apache-2.0
|
||||||
kombu!=4.0.2,>=4.6.1 # BSD
|
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,>=1.10;python_version<'3.0' # BSD
|
||||||
networkx>=2.3;python_version>='3.4' # BSD
|
networkx>=2.3;python_version>='3.4' # BSD
|
||||||
oslo.concurrency>=3.26.0 # Apache-2.0
|
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+
|
paramiko>=2.0.0 # LGPLv2.1+
|
||||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||||
pecan>=1.2.1 # BSD
|
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
|
PyJWT>=1.5 # MIT
|
||||||
PyYAML>=5.1 # MIT
|
PyYAML>=5.1 # MIT
|
||||||
requests>=2.14.2 # Apache-2.0
|
requests>=2.14.2 # Apache-2.0
|
||||||
|
@ -4,26 +4,16 @@
|
|||||||
hacking>=1.1.0 # Apache-2.0
|
hacking>=1.1.0 # Apache-2.0
|
||||||
|
|
||||||
coverage!=4.4,>=4.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
|
doc8>=0.6.0 # Apache-2.0
|
||||||
|
Pygments>=2.2.0 # BSD license
|
||||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
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
|
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
|
nose>=1.3.7 # LGPL
|
||||||
oslotest>=3.2.0 # Apache-2.0
|
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
|
requests-mock>=1.2.0 # Apache-2.0
|
||||||
tooz>=1.58.0 # Apache-2.0
|
tooz>=1.58.0 # Apache-2.0
|
||||||
tempest>=17.1.0 # Apache-2.0
|
tempest>=17.1.0 # Apache-2.0
|
||||||
stestr>=2.0.0 # Apache-2.0
|
stestr>=2.0.0 # Apache-2.0
|
||||||
testtools>=2.2.0 # MIT
|
testtools>=2.2.0 # MIT
|
||||||
unittest2>=1.1.0 # BSD
|
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():
|
for group, opts in keystonemw_opts.list_auth_token_opts():
|
||||||
CONF.register_opts(opts, group=group)
|
CONF.register_opts(opts, group=group)
|
||||||
|
|
||||||
CONF.register_cli_opt(config.os_actions_mapping_path)
|
|
||||||
|
|
||||||
logging.register_options(CONF)
|
logging.register_options(CONF)
|
||||||
|
|
||||||
config.parse_args()
|
config.parse_args()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user