Add openstack actions

* Added Generator for OpenStack Actions
 * Added Nova actions
 * Added Keystone actions
 * Added Glance actions
 * Init all needed namespaces in action factory
 * Unit tests of generators and engine

Implements blueprint: mistral-openstack-actions

Change-Id: I2ac24843e6434c513ebc813cbe42f91000309918
This commit is contained in:
Nikolay Mahotkin 2014-08-01 14:14:19 +04:00
parent 92075fa5bb
commit 86a93cc3ad
22 changed files with 911 additions and 3 deletions

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright 2014 - Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -15,9 +13,11 @@
# limitations under the License.
import inspect
from stevedore import extension
from mistral.actions import base
from mistral.actions import generator_factory
from mistral.actions import std_actions
from mistral import exceptions as exc
from mistral import expressions as expr
@ -25,6 +25,7 @@ from mistral.openstack.common import log as logging
from mistral.workbook import actions
from mistral.workbook import tasks
LOG = logging.getLogger(__name__)
_ACTION_CTX_PARAM = 'action_context'
@ -45,6 +46,16 @@ def get_registered_namespaces():
return _NAMESPACES.copy()
def _register_dynamic_action_classes():
all_generators = generator_factory.all_generators()
for name in all_generators:
ns = _find_or_create_namespace(name)
generator = all_generators[name]()
action_classes = generator.create_action_classes()
for action_name, action in action_classes.items():
ns.add(action_name, action)
def _register_action_classes():
mgr = extension.ExtensionManager(
namespace='mistral.actions',
@ -57,6 +68,8 @@ def _register_action_classes():
for ns in _NAMESPACES:
_NAMESPACES[ns].log()
_register_dynamic_action_classes()
def get_action_class(action_full_name):
"""Finds action class by full action name (i.e. 'namespace.action_name').

View File

@ -0,0 +1,30 @@
# 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 class
dynamically.
"""
@abc.abstractmethod
def create_action_classes(self, *args, **kwargs):
"""Constructs classes of needed action.
return: dict of action names and Action classes.
"""
pass

View File

@ -0,0 +1,23 @@
# 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 mistral.actions.openstack.action_generator import generators
def all_generators():
return {
"nova": generators.NovaActionGenerator,
"glance": generators.GlanceActionGenerator,
"keystone": generators.KeystoneActionGenerator
}

View File

View File

@ -0,0 +1,63 @@
# 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
from oslo.config import cfg
import pkg_resources as pkg
from mistral.actions import action_generator
from mistral import version
os_actions_mapping_path = cfg.StrOpt('openstack_actions_mapping_path',
default='actions/openstack/mapping.json')
CONF = cfg.CONF
CONF.register_opt(os_actions_mapping_path)
MAPPING_PATH = CONF.openstack_actions_mapping_path
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
def create_action_class(self, method_name):
if not method_name:
return None
action_class = type(str(method_name), (self.base_action_class,), {})
setattr(action_class, 'client_method', method_name)
return action_class
def create_action_classes(self):
mapping = json.loads(open(pkg.resource_filename(
version.version_info.package,
MAPPING_PATH)).read())
method_dict = mapping[self._action_namespace]
action_classes = {}
for action_name, method_name in method_dict.items():
action_classes[action_name] = self.create_action_class(method_name)
return action_classes

View File

@ -0,0 +1,31 @@
# 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 mistral.actions.openstack.action_generator import base
from mistral.actions.openstack import actions
class NovaActionGenerator(base.OpenStackActionGenerator):
_action_namespace = "nova"
base_action_class = actions.NovaAction
class GlanceActionGenerator(base.OpenStackActionGenerator):
_action_namespace = "glance"
base_action_class = actions.GlanceAction
class KeystoneActionGenerator(base.OpenStackActionGenerator):
_action_namespace = "keystone"
base_action_class = actions.KeystoneAction

View File

@ -0,0 +1,59 @@
# 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 glanceclient.v2 import client as glanceclient
from keystoneclient.v3 import client as keystoneclient
from novaclient.v1_1 import client as novaclient
from oslo.config import cfg
from mistral.actions.openstack import base
from mistral import context
from mistral.utils.openstack import keystone as keystone_utils
CONF = cfg.CONF
class NovaAction(base.OpenStackAction):
_client_class = novaclient.Client
def _get_client(self):
ctx = context.ctx()
auth_url = keystone_utils.get_keystone_url_v2()
return self._client_class(username=ctx.user_name,
auth_token=ctx.auth_token,
tenant_id=ctx.project_id,
auth_url=auth_url)
class GlanceAction(base.OpenStackAction):
_client_class = glanceclient.Client
def _get_client(self):
ctx = context.ctx()
endpoint_url = keystone_utils.get_endpoint_for_project('glance')
return self._client_class(endpoint_url, token=ctx.auth_token)
class KeystoneAction(base.OpenStackAction):
_client_class = keystoneclient.Client
def _get_client(self):
ctx = context.ctx()
auth_url = CONF.keystone_authtoken.auth_uri
return self._client_class(token=ctx.auth_token,
auth_url=auth_url)

View File

@ -0,0 +1,61 @@
# 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
from mistral.actions import base
from mistral import exceptions as exc
class OpenStackAction(base.Action):
"""OpenStack Action.
OpenStack Action is the basis of all OpenStack-specific actions,
which are constructed via OpenStack Action generators.
"""
_kwargs_for_run = {}
_client_class = None
client_method = None
def __init__(self, **kwargs):
self._kwargs_for_run = kwargs
@abc.abstractmethod
def _get_client(self):
"""Returns python-client instance
Gets client instance according to specific OpenStack Service
(e.g. Nova, Glance, Heat, Keystone etc)
"""
pass
def _get_client_method(self):
hierarchy_list = self.client_method.split('.')
attribute = self._get_client()
for attr in hierarchy_list:
attribute = getattr(attribute, attr)
return attribute
def run(self):
try:
method = self._get_client_method()
return method(**self._kwargs_for_run)
except Exception as e:
raise exc.ActionException("%s failed: %s"
% (self.__class__.__name__, e))
def test(self):
return dict(zip(self._kwargs_for_run,
['test'] * len(self._kwargs_for_run)))

View File

@ -0,0 +1,310 @@
{
"_comment": "Mapping OpenStack action namespaces to all its actions. Each action name is mapped to python-client method name in this namespace.",
"nova": {
"_comment": "It uses novaclient.v1_1.",
"agents_create": "agents.create",
"agents_delete": "agents.delete",
"agents_find": "agents.find",
"agents_findall": "agents.findall",
"agents_list": "agents.list",
"agents_update": "agents.update",
"availability_zones_find": "availability_zones.find",
"availability_zones_findall": "availability_zones.findall",
"availability_zones_list": "availability_zones.list",
"availability_zones_return_parameter_name": "availability_zones.return_parameter_name",
"certs_create": "certs.create",
"certs_get": "certs.get",
"dns_domains_create_private": "dns_domains.create_private",
"dns_domains_create_public": "dns_domains.create_public",
"dns_domains_delete": "dns_domains.delete",
"dns_domains_domains": "dns_domains.domains",
"dns_entries_create": "dns_entries.create",
"dns_entries_delete": "dns_entries.delete",
"dns_entries_get": "dns_entries.get",
"dns_entries_get_for_ip": "dns_entries.get_for_ip",
"dns_entries_modify_ip": "dns_entries.modify_ip",
"fixed_ips_get": "fixed_ips.get",
"fixed_ips_reserve": "fixed_ips.reserve",
"fixed_ips_unreserve": "fixed_ips.unreserve",
"flavor_access_add_tenant_access": "flavor_access.add_tenant_access",
"flavor_access_find": "flavor_access.find",
"flavor_access_findall": "flavor_access.findall",
"flavor_access_list": "flavor_access.list",
"flavor_access_remove_tenant_access": "flavor_access.remove_tenant_access",
"flavors_create": "flavors.create",
"flavors_delete": "flavors.delete",
"flavors_find": "flavors.find",
"flavors_findall": "flavors.findall",
"flavors_get": "flavors.get",
"flavors_is_alphanum_id_allowed": "flavors.is_alphanum_id_allowed",
"flavors_list": "flavors.list",
"floating_ip_pools_find": "floating_ip_pools.find",
"floating_ip_pools_findall": "floating_ip_pools.findall",
"floating_ip_pools_list": "floating_ip_pools.list",
"floating_ips_create": "floating_ips.create",
"floating_ips_delete": "floating_ips.delete",
"floating_ips_find": "floating_ips.find",
"floating_ips_findall": "floating_ips.findall",
"floating_ips_get": "floating_ips.get",
"floating_ips_list": "floating_ips.list",
"hosts_find": "hosts.find",
"hosts_findall": "hosts.findall",
"hosts_get": "hosts.get",
"hosts_host_action": "hosts.host_action",
"hosts_list": "hosts.list",
"hosts_list_all": "hosts.list_all",
"hosts_update": "hosts.update",
"hypervisors_find": "hypervisors.find",
"hypervisors_findall": "hypervisors.findall",
"hypervisors_get": "hypervisors.get",
"hypervisors_list": "hypervisors.list",
"hypervisors_search": "hypervisors.search",
"hypervisors_statistics": "hypervisors.statistics",
"hypervisors_uptime": "hypervisors.uptime",
"images_delete": "images.delete",
"images_delete_meta": "images.delete_meta",
"images_find": "images.find",
"images_findall": "images.findall",
"images_get": "images.get",
"images_list": "images.list",
"images_set_meta": "images.set_meta",
"keypairs_create": "keypairs.create",
"keypairs_delete": "keypairs.delete",
"keypairs_find": "keypairs.find",
"keypairs_findall": "keypairs.findall",
"keypairs_get": "keypairs.get",
"keypairs_is_alphanum_id_allowed": "keypairs.is_alphanum_id_allowed",
"keypairs_keypair_prefix": "keypairs.keypair_prefix",
"keypairs_list": "keypairs.list",
"networks_add": "networks.add",
"networks_associate_host": "networks.associate_host",
"networks_associate_project": "networks.associate_project",
"networks_create": "networks.create",
"networks_delete": "networks.delete",
"networks_disassociate": "networks.disassociate",
"networks_find": "networks.find",
"networks_findall": "networks.findall",
"networks_get": "networks.get",
"networks_list": "networks.list",
"quotas_defaults": "quotas.defaults",
"quotas_delete": "quotas.delete",
"quotas_get": "quotas.get",
"quotas_update": "quotas.update",
"security_groups_create": "security_groups.create",
"security_groups_delete": "security_groups.delete",
"security_groups_find": "security_groups.find",
"security_groups_findall": "security_groups.findall",
"security_groups_get": "security_groups.get",
"security_groups_list": "security_groups.list",
"security_groups_update": "security_groups.update",
"servers_add_fixed_ip": "servers.add_fixed_ip",
"servers_add_floating_ip": "servers.add_floating_ip",
"servers_add_security_group": "servers.add_security_group",
"servers_backup": "servers.backup",
"servers_change_password": "servers.change_password",
"servers_clear_password": "servers.clear_password",
"servers_confirm_resize": "servers.confirm_resize",
"servers_create": "servers.create",
"servers_create_image": "servers.create_image",
"servers_delete": "servers.delete",
"servers_delete_meta": "servers.delete_meta",
"servers_diagnostics": "servers.diagnostics",
"servers_evacuate": "servers.evacuate",
"servers_find": "servers.find",
"servers_findall": "servers.findall",
"servers_force_delete": "servers.force_delete",
"servers_get": "servers.get",
"servers_get_console_output": "servers.get_console_output",
"servers_get_password": "servers.get_password",
"servers_get_rdp_console": "servers.get_rdp_console",
"servers_get_spice_console": "servers.get_spice_console",
"servers_get_vnc_console": "servers.get_vnc_console",
"servers_interface_attach": "servers.interface_attach",
"servers_interface_detach": "servers.interface_detach",
"servers_interface_list": "servers.interface_list",
"servers_list": "servers.list",
"servers_list_security_group": "servers.list_security_group",
"servers_live_migrate": "servers.live_migrate",
"servers_lock": "servers.lock",
"servers_migrate": "servers.migrate",
"servers_pause": "servers.pause",
"servers_reboot": "servers.reboot",
"servers_rebuild": "servers.rebuild",
"servers_remove_fixed_ip": "servers.remove_fixed_ip",
"servers_remove_floating_ip": "servers.remove_floating_ip",
"servers_remove_security_group": "servers.remove_security_group",
"servers_rescue": "servers.rescue",
"servers_reset_network": "servers.reset_network",
"servers_reset_state": "servers.reset_state",
"servers_resize": "servers.resize",
"servers_restore": "servers.restore",
"servers_resume": "servers.resume",
"servers_revert_resize": "servers.revert_resize",
"servers_set_meta": "servers.set_meta",
"servers_set_meta_item": "servers.set_meta_item",
"servers_shelve": "servers.shelve",
"servers_shelve_offload": "servers.shelve_offload",
"servers_start": "servers.start",
"servers_stop": "servers.stop",
"servers_suspend": "servers.suspend",
"servers_unlock": "servers.unlock",
"servers_unpause": "servers.unpause",
"servers_unrescue": "servers.unrescue",
"servers_unshelve": "servers.unshelve",
"servers_update": "servers.update",
"services_delete": "services.delete",
"services_disable": "services.disable",
"services_disable_log_reason": "services.disable_log_reason",
"services_enable": "services.enable",
"services_find": "services.find",
"services_findall": "services.findall",
"services_list": "services.list",
"usage_find": "usage.find",
"usage_findall": "usage.findall",
"usage_get": "usage.get",
"usage_list": "usage.list",
"virtual_interfaces_find": "virtual_interfaces.find",
"virtual_interfaces_findall": "virtual_interfaces.findall",
"virtual_interfaces_list": "virtual_interfaces.list",
"volume_snapshots_create": "volume_snapshots.create",
"volume_snapshots_delete": "volume_snapshots.delete",
"volume_snapshots_find": "volume_snapshots.find",
"volume_snapshots_findall": "volume_snapshots.findall",
"volume_snapshots_get": "volume_snapshots.get",
"volume_snapshots_list": "volume_snapshots.list",
"volume_types_create": "volume_types.create",
"volume_types_delete": "volume_types.delete",
"volume_types_find": "volume_types.find",
"volume_types_findall": "volume_types.findall",
"volume_types_get": "volume_types.get",
"volume_types_list": "volume_types.list",
"volumes_create": "volumes.create",
"volumes_create_server_volume": "volumes.create_server_volume",
"volumes_delete": "volumes.delete",
"volumes_delete_server_volume": "volumes.delete_server_volume",
"volumes_find": "volumes.find",
"volumes_findall": "volumes.findall",
"volumes_get": "volumes.get",
"volumes_get_server_volume": "volumes.get_server_volume",
"volumes_get_server_volumes": "volumes.get_server_volumes",
"volumes_list": "volumes.list",
"volumes_update_server_volume": "volumes.update_server_volume"
},
"glance": {
"_comment": "It uses glanceclient.v2.",
"image_members_create": "image_members.create",
"image_members_delete": "image_members.delete",
"image_members_list": "image_members.list",
"image_members_model": "image_members.model",
"image_members_update": "image_members.update",
"image_tags_delete": "image_tags.delete",
"image_tags_model": "image_tags.model",
"image_tags_update": "image_tags.update",
"images_add_location": "images.add_location",
"images_create": "images.create",
"images_data": "images.data",
"images_delete": "images.delete",
"images_delete_locations": "images.delete_locations",
"images_get": "images.get",
"images_list": "images.list",
"images_model": "images.model",
"images_update": "images.update",
"images_update_location": "images.update_location",
"images_upload": "images.upload",
"schemas_get": "schemas.get"
},
"keystone": {
"_comment": "It uses keystoneclient.v3.",
"credentials_create": "credentials.create",
"credentials_delete": "credentials.delete",
"credentials_find": "credentials.find",
"credentials_get": "credentials.get",
"credentials_list": "credentials.list",
"credentials_update": "credentials.update",
"domains_create": "domains.create",
"domains_delete": "domains.delete",
"domains_find": "domains.find",
"domains_get": "domains.get",
"domains_list": "domains.list",
"domains_update": "domains.update",
"endpoints_create": "endpoints.create",
"endpoints_delete": "endpoints.delete",
"endpoints_find": "endpoints.find",
"endpoints_get": "endpoints.get",
"endpoints_list": "endpoints.list",
"endpoints_update": "endpoints.update",
"federation_identity_providers": "federation.identity_providers",
"federation_mappings": "federation.mappings",
"federation_protocols": "federation.protocols",
"groups_create": "groups.create",
"groups_delete": "groups.delete",
"groups_find": "groups.find",
"groups_get": "groups.get",
"groups_list": "groups.list",
"groups_update": "groups.update",
"oauth1_access_tokens": "oauth1.access_tokens",
"oauth1_consumers": "oauth1.consumers",
"oauth1_request_tokens": "oauth1.request_tokens",
"policies_create": "policies.create",
"policies_delete": "policies.delete",
"policies_find": "policies.find",
"policies_get": "policies.get",
"policies_list": "policies.list",
"policies_update": "policies.update",
"projects_create": "projects.create",
"projects_delete": "projects.delete",
"projects_find": "projects.find",
"projects_get": "projects.get",
"projects_list": "projects.list",
"projects_update": "projects.update",
"regions_create": "regions.create",
"regions_delete": "regions.delete",
"regions_find": "regions.find",
"regions_get": "regions.get",
"regions_list": "regions.list",
"regions_update": "regions.update",
"roles_check": "roles.check",
"roles_client": "roles.client",
"roles_create": "roles.create",
"roles_delete": "roles.delete",
"roles_find": "roles.find",
"roles_get": "roles.get",
"roles_grant": "roles.grant",
"roles_list": "roles.list",
"roles_revoke": "roles.revoke",
"roles_update": "roles.update",
"service_catalog_catalog": "service_catalog.catalog",
"service_catalog_factory": "service_catalog.factory",
"service_catalog_get_data": "service_catalog.get_data",
"service_catalog_get_endpoints": "service_catalog.get_endpoints",
"service_catalog_get_token": "service_catalog.get_token",
"service_catalog_get_urls": "service_catalog.get_urls",
"service_catalog_is_valid": "service_catalog.is_valid",
"service_catalog_region_name": "service_catalog.region_name",
"service_catalog_url_for": "service_catalog.url_for",
"services_create": "services.create",
"services_delete": "services.delete",
"services_find": "services.find",
"services_get": "services.get",
"services_list": "services.list",
"services_update": "services.update",
"trusts_create": "trusts.create",
"trusts_delete": "trusts.delete",
"trusts_find": "trusts.find",
"trusts_get": "trusts.get",
"trusts_list": "trusts.list",
"trusts_update": "trusts.update",
"users_add_to_group": "users.add_to_group",
"users_check_in_group": "users.check_in_group",
"users_create": "users.create",
"users_delete": "users.delete",
"users_find": "users.find",
"users_get": "users.get",
"users_list": "users.list",
"users_put": "users.put",
"users_remove_from_group": "users.remove_from_group",
"users_resource_class": "users.resource_class",
"users_update": "users.update",
"users_update_password": "users.update_password"
}
}

View File

@ -0,0 +1,4 @@
Workflow:
tasks:
glance_image_list:
action: glance.images_list

View File

@ -0,0 +1,4 @@
Workflow:
tasks:
keystone_user_list:
action: keystone.users_list

View File

@ -0,0 +1,4 @@
Workflow:
tasks:
nova_server_list:
action: nova.servers_list

View File

@ -0,0 +1,32 @@
# 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 oslotest import base
from mistral.actions import generator_factory
from mistral.actions.openstack import actions
class GlanceGeneratorTest(base.BaseTestCase):
def test_generator(self):
action_name = "glance.images_list"
generator = generator_factory.all_generators()["glance"]()
action_classes = generator.create_action_classes()
short_action_name = action_name.split(".")[1]
action_class = action_classes[short_action_name]
self.assertIsNotNone(generator)
self.assertIn(short_action_name, action_classes)
self.assertTrue(issubclass(action_class, actions.GlanceAction))
self.assertEqual("images.list", action_class.client_method)

View File

@ -0,0 +1,32 @@
# 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 oslotest import base
from mistral.actions import generator_factory
from mistral.actions.openstack import actions
class KeystoneGeneratorTest(base.BaseTestCase):
def test_generator(self):
action_name = "keystone.users_create"
generator = generator_factory.all_generators()["keystone"]()
action_classes = generator.create_action_classes()
short_action_name = action_name.split(".")[1]
action_class = action_classes[short_action_name]
self.assertIsNotNone(generator)
self.assertIn(short_action_name, action_classes)
self.assertTrue(issubclass(action_class, actions.KeystoneAction))
self.assertEqual("users.create", action_class.client_method)

View File

@ -0,0 +1,32 @@
# 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 oslotest import base
from mistral.actions import generator_factory
from mistral.actions.openstack import actions
class NovaGeneratorTest(base.BaseTestCase):
def test_generator(self):
action_name = "nova.servers_get"
generator = generator_factory.all_generators()["nova"]()
action_classes = generator.create_action_classes()
short_action_name = action_name.split(".")[1]
action_class = action_classes[short_action_name]
self.assertIsNotNone(generator)
self.assertIn(short_action_name, action_classes)
self.assertTrue(issubclass(action_class, actions.NovaAction))
self.assertEqual("servers.get", action_class.client_method)

View File

@ -0,0 +1,56 @@
# 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 oslotest import base
from mistral.actions.openstack import actions
class OpenStackActionTest(base.BaseTestCase):
@mock.patch.object(actions.NovaAction, '_get_client')
def test_nova_action(self, mocked):
method_name = "servers.get"
action_class = actions.NovaAction
action_class.client_method = method_name
params = {'server': '1234-abcd'}
action = action_class(**params)
action.run()
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):
method_name = "images.delete"
action_class = actions.GlanceAction
action_class.client_method = method_name
params = {'image': '1234-abcd'}
action = action_class(**params)
action.run()
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):
method_name = "users.get"
action_class = actions.KeystoneAction
action_class.client_method = method_name
params = {'user': '1234-abcd'}
action = action_class(**params)
action.run()
self.assertTrue(mocked().users.get.called)
mocked().users.get.assert_called_once_with(user="1234-abcd")

View File

@ -93,13 +93,27 @@ class ActionFactoryTest(base.BaseTest):
def test_register_standard_actions(self):
namespaces = a_f.get_registered_namespaces()
self.assertEqual(1, len(namespaces))
self.assertIn("nova", namespaces)
self.assertIn("glance", namespaces)
self.assertIn("keystone", namespaces)
self.assertIn("std", namespaces)
std_ns = namespaces["std"]
nova_ns = namespaces["nova"]
keystone_ns = namespaces["keystone"]
glance_ns = namespaces["glance"]
self.assertEqual(5, len(std_ns))
self.assertTrue(nova_ns.contains_action_name("servers_get"))
self.assertTrue(nova_ns.contains_action_name("volumes_delete"))
self.assertTrue(keystone_ns.contains_action_name("users_list"))
self.assertTrue(keystone_ns.contains_action_name("trusts_create"))
self.assertTrue(glance_ns.contains_action_name("images_list"))
self.assertTrue(glance_ns.contains_action_name("images_delete"))
self.assertTrue(std_ns.contains_action_name("echo"))
self.assertTrue(std_ns.contains_action_name("http"))
self.assertTrue(std_ns.contains_action_name("mistral_http"))

View File

@ -0,0 +1,124 @@
# 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 oslo.config import cfg
from mistral.actions.openstack import actions
from mistral.db import api as db_api
from mistral import engine
from mistral.engine.drivers.default import engine as concrete_engine
from mistral.engine import states
from mistral.openstack.common import log as logging
from mistral.tests import base
LOG = logging.getLogger(__name__)
# Use the set_default method to set value otherwise in certain test cases
# the change in value is not permanent.
cfg.CONF.set_default('auth_enable', False, group='pecan')
def create_workbook(definition_path):
return db_api.workbook_create({
'name': 'my_workbook',
'definition': base.get_resource(definition_path)
})
@mock.patch.object(
engine.EngineClient, 'start_workflow_execution',
mock.MagicMock(side_effect=base.EngineTestCase.mock_start_workflow))
@mock.patch.object(
engine.EngineClient, 'convey_task_result',
mock.MagicMock(side_effect=base.EngineTestCase.mock_task_result))
@mock.patch.object(
concrete_engine.DefaultEngine, '_run_task',
mock.MagicMock(side_effect=base.EngineTestCase.mock_run_task))
class OpenStackActionsEngineTest(base.EngineTestCase):
@mock.patch.object(actions.GlanceAction, 'run',
mock.Mock(return_value="images"))
def test_glance_action(self):
context = {}
wb = create_workbook('openstack_tasks/glance.yaml')
task_name = 'glance_image_list'
execution = self.engine.start_workflow_execution(wb['name'],
task_name,
context)
# We have to reread execution to get its latest version.
execution = db_api.execution_get(execution['id'])
self.assertEqual(states.SUCCESS, execution['state'])
tasks = db_api.tasks_get(workbook_name=wb['name'],
execution_id=execution['id'])
self.assertEqual(1, len(tasks))
task = self._assert_single_item(tasks, name=task_name)
self.assertEqual(states.SUCCESS, task['state'])
self.assertEqual("images", task['output']['task'][task_name])
@mock.patch.object(actions.KeystoneAction, 'run',
mock.Mock(return_value="users"))
def test_keystone_action(self):
context = {}
wb = create_workbook('openstack_tasks/keystone.yaml')
task_name = 'keystone_user_list'
execution = self.engine.start_workflow_execution(wb['name'],
task_name,
context)
# We have to reread execution to get its latest version.
execution = db_api.execution_get(execution['id'])
self.assertEqual(states.SUCCESS, execution['state'])
tasks = db_api.tasks_get(workbook_name=wb['name'],
execution_id=execution['id'])
self.assertEqual(1, len(tasks))
task = self._assert_single_item(tasks, name=task_name)
self.assertEqual(states.SUCCESS, task['state'])
self.assertEqual("users", task['output']['task'][task_name])
@mock.patch.object(actions.NovaAction, 'run',
mock.Mock(return_value="servers"))
def test_nova_action(self):
context = {}
wb = create_workbook('openstack_tasks/nova.yaml')
task_name = 'nova_server_list'
execution = self.engine.start_workflow_execution(wb['name'],
task_name,
context)
# We have to reread execution to get its latest version.
execution = db_api.execution_get(execution['id'])
self.assertEqual(states.SUCCESS, execution['state'])
tasks = db_api.tasks_get(workbook_name=wb['name'],
execution_id=execution['id'])
self.assertEqual(1, len(tasks))
task = self._assert_single_item(tasks, name=task_name)
self.assertEqual(states.SUCCESS, task['state'])
self.assertEqual("servers", task['output']['task'][task_name])

View File

@ -53,3 +53,17 @@ def client_for_admin(project_name):
def client_for_trusts(trust_id):
return _admin_client(trust_id=trust_id)
def get_endpoint_for_project(service_name):
admin_project_name = CONF.keystone_authtoken.admin_tenant_name
keystone_client = _admin_client(project_name=admin_project_name)
service_list = keystone_client.services.list()
service_id = [s.id for s in service_list if s.name == service_name][0]
return keystone_client.endpoints.find(service_id=service_id,
interface='public').url
def get_keystone_url_v2():
return get_endpoint_for_project('keystone')

View File

@ -16,6 +16,8 @@ oslo.db>=0.2.0 # Apache-2.0
oslo.messaging>=1.3.0
paramiko>=1.13.0
python-keystoneclient>=0.9.0
python-novaclient>=2.17
python-glanceclient>=0.13
networkx>=1.8
six>=1.7.0
SQLAlchemy>=0.7.8,!=0.9.5,<=0.9.99