diff --git a/mistral/actions/generator_factory.py b/mistral/actions/generator_factory.py index c7770b490..88adaa82b 100644 --- a/mistral/actions/generator_factory.py +++ b/mistral/actions/generator_factory.py @@ -21,5 +21,6 @@ def all_generators(): generators.GlanceActionGenerator, generators.KeystoneActionGenerator, generators.HeatActionGenerator, - generators.NeutronActionGenerator + generators.NeutronActionGenerator, + generators.CinderActionGenerator ] \ No newline at end of file diff --git a/mistral/actions/openstack/action_generator/generators.py b/mistral/actions/openstack/action_generator/generators.py index b4afb2613..d1a759b3f 100644 --- a/mistral/actions/openstack/action_generator/generators.py +++ b/mistral/actions/openstack/action_generator/generators.py @@ -39,3 +39,8 @@ class HeatActionGenerator(base.OpenStackActionGenerator): class NeutronActionGenerator(base.OpenStackActionGenerator): action_namespace = "neutron" base_action_class = actions.NeutronAction + + +class CinderActionGenerator(base.OpenStackActionGenerator): + action_namespace = "cinder" + base_action_class = actions.CinderAction diff --git a/mistral/actions/openstack/actions.py b/mistral/actions/openstack/actions.py index 0b878eab0..ff8dfe7ef 100644 --- a/mistral/actions/openstack/actions.py +++ b/mistral/actions/openstack/actions.py @@ -14,6 +14,7 @@ import inspect +from cinderclient.v1 import client as cinderclient from glanceclient.v2 import client as glanceclient from heatclient.v1 import client as heatclient from keystoneclient import httpclient @@ -145,4 +146,32 @@ class NeutronAction(base.OpenStackAction): endpoint_url=keystone_utils.get_endpoint_for_project('neutron'), token=ctx.auth_token, auth_url=CONF.keystone_authtoken.auth_uri - ) \ No newline at end of file + ) + + +class CinderAction(base.OpenStackAction): + _client_class = cinderclient.Client + + def _get_client(self): + ctx = context.ctx() + + LOG.debug("Cinder action security context: %s" % ctx) + cinder_url = keystone_utils.get_endpoint_for_project( + service_type='volume' + ) + cinder_url = cinder_url % {'tenant_id': ctx.project_id} + + client = self._client_class( + ctx.user_name, + ctx.auth_token, + project_id=ctx.project_id, + auth_url=cinder_url, + ) + client.client.auth_token = ctx.auth_token + client.client.management_url = cinder_url + + return client + + @classmethod + def _get_fake_client(cls): + return cls._client_class() \ No newline at end of file diff --git a/mistral/actions/openstack/mapping.json b/mistral/actions/openstack/mapping.json index 7b887d873..607515d2e 100644 --- a/mistral/actions/openstack/mapping.json +++ b/mistral/actions/openstack/mapping.json @@ -516,5 +516,136 @@ "update_subnet": "update_subnet", "update_vip": "update_vip", "update_vpnservice": "update_vpnservice" + }, + "cinder": { + "_comment": "It uses cinderclient.v1.", + "availability_zones_completion_cache": "availability_zones.completion_cache", + "availability_zones_find": "availability_zones.find", + "availability_zones_findall": "availability_zones.findall", + "availability_zones_list": "availability_zones.list", + "availability_zones_write_to_completion_cache": "availability_zones.write_to_completion_cache", + "backups_completion_cache": "backups.completion_cache", + "backups_create": "backups.create", + "backups_delete": "backups.delete", + "backups_export_record": "backups.export_record", + "backups_find": "backups.find", + "backups_findall": "backups.findall", + "backups_get": "backups.get", + "backups_import_record": "backups.import_record", + "backups_list": "backups.list", + "backups_write_to_completion_cache": "backups.write_to_completion_cache", + "get_volume_api_version_from_endpoint": "get_volume_api_version_from_endpoint", + "limits_completion_cache": "limits.completion_cache", + "limits_get": "limits.get", + "limits_write_to_completion_cache": "limits.write_to_completion_cache", + "qos_specs_associate": "qos_specs.associate", + "qos_specs_completion_cache": "qos_specs.completion_cache", + "qos_specs_create": "qos_specs.create", + "qos_specs_delete": "qos_specs.delete", + "qos_specs_disassociate": "qos_specs.disassociate", + "qos_specs_disassociate_all": "qos_specs.disassociate_all", + "qos_specs_find": "qos_specs.find", + "qos_specs_findall": "qos_specs.findall", + "qos_specs_get": "qos_specs.get", + "qos_specs_get_associations": "qos_specs.get_associations", + "qos_specs_list": "qos_specs.list", + "qos_specs_set_keys": "qos_specs.set_keys", + "qos_specs_unset_keys": "qos_specs.unset_keys", + "qos_specs_write_to_completion_cache": "qos_specs.write_to_completion_cache", + "quota_classes_completion_cache": "quota_classes.completion_cache", + "quota_classes_get": "quota_classes.get", + "quota_classes_update": "quota_classes.update", + "quota_classes_write_to_completion_cache": "quota_classes.write_to_completion_cache", + "quotas_completion_cache": "quotas.completion_cache", + "quotas_defaults": "quotas.defaults", + "quotas_delete": "quotas.delete", + "quotas_get": "quotas.get", + "quotas_update": "quotas.update", + "quotas_write_to_completion_cache": "quotas.write_to_completion_cache", + "restores_completion_cache": "restores.completion_cache", + "restores_restore": "restores.restore", + "restores_write_to_completion_cache": "restores.write_to_completion_cache", + "services_completion_cache": "services.completion_cache", + "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", + "services_write_to_completion_cache": "services.write_to_completion_cache", + "transfers_accept": "transfers.accept", + "transfers_completion_cache": "transfers.completion_cache", + "transfers_create": "transfers.create", + "transfers_delete": "transfers.delete", + "transfers_find": "transfers.find", + "transfers_findall": "transfers.findall", + "transfers_get": "transfers.get", + "transfers_list": "transfers.list", + "transfers_write_to_completion_cache": "transfers.write_to_completion_cache", + "volume_encryption_types_completion_cache": "volume_encryption_types.completion_cache", + "volume_encryption_types_create": "volume_encryption_types.create", + "volume_encryption_types_delete": "volume_encryption_types.delete", + "volume_encryption_types_find": "volume_encryption_types.find", + "volume_encryption_types_findall": "volume_encryption_types.findall", + "volume_encryption_types_get": "volume_encryption_types.get", + "volume_encryption_types_list": "volume_encryption_types.list", + "volume_encryption_types_update": "volume_encryption_types.update", + "volume_encryption_types_write_to_completion_cache": "volume_encryption_types.write_to_completion_cache", + "volume_snapshots_completion_cache": "volume_snapshots.completion_cache", + "volume_snapshots_create": "volume_snapshots.create", + "volume_snapshots_delete": "volume_snapshots.delete", + "volume_snapshots_delete_metadata": "volume_snapshots.delete_metadata", + "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_snapshots_reset_state": "volume_snapshots.reset_state", + "volume_snapshots_set_metadata": "volume_snapshots.set_metadata", + "volume_snapshots_update": "volume_snapshots.update", + "volume_snapshots_update_all_metadata": "volume_snapshots.update_all_metadata", + "volume_snapshots_update_snapshot_status": "volume_snapshots.update_snapshot_status", + "volume_snapshots_write_to_completion_cache": "volume_snapshots.write_to_completion_cache", + "volume_types_completion_cache": "volume_types.completion_cache", + "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", + "volume_types_write_to_completion_cache": "volume_types.write_to_completion_cache", + "volumes_attach": "volumes.attach", + "volumes_begin_detaching": "volumes.begin_detaching", + "volumes_completion_cache": "volumes.completion_cache", + "volumes_create": "volumes.create", + "volumes_delete": "volumes.delete", + "volumes_delete_metadata": "volumes.delete_metadata", + "volumes_detach": "volumes.detach", + "volumes_extend": "volumes.extend", + "volumes_find": "volumes.find", + "volumes_findall": "volumes.findall", + "volumes_force_delete": "volumes.force_delete", + "volumes_get": "volumes.get", + "volumes_get_encryption_metadata": "volumes.get_encryption_metadata", + "volumes_initialize_connection": "volumes.initialize_connection", + "volumes_list": "volumes.list", + "volumes_manage": "volumes.manage", + "volumes_migrate_volume": "volumes.migrate_volume", + "volumes_migrate_volume_completion": "volumes.migrate_volume_completion", + "volumes_promote": "volumes.promote", + "volumes_reenable": "volumes.reenable", + "volumes_reserve": "volumes.reserve", + "volumes_reset_state": "volumes.reset_state", + "volumes_retype": "volumes.retype", + "volumes_roll_detaching": "volumes.roll_detaching", + "volumes_set_bootable": "volumes.set_bootable", + "volumes_set_metadata": "volumes.set_metadata", + "volumes_terminate_connection": "volumes.terminate_connection", + "volumes_unmanage": "volumes.unmanage", + "volumes_unreserve": "volumes.unreserve", + "volumes_update": "volumes.update", + "volumes_update_all_metadata": "volumes.update_all_metadata", + "volumes_update_readonly_flag": "volumes.update_readonly_flag", + "volumes_upload_to_image": "volumes.upload_to_image", + "volumes_write_to_completion_cache": "volumes.write_to_completion_cache" } } \ No newline at end of file diff --git a/mistral/tests/unit/actions/openstack/test_cinder_generator.py b/mistral/tests/unit/actions/openstack/test_cinder_generator.py new file mode 100644 index 000000000..58e92dfc0 --- /dev/null +++ b/mistral/tests/unit/actions/openstack/test_cinder_generator.py @@ -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 mistral.actions.openstack.action_generator import generators +from mistral.actions.openstack import actions +from mistral.tests import base + + +class CinderGeneratorTest(base.BaseTest): + def test_generator(self): + action_name = "cinder.volumes_list" + generator = generators.CinderActionGenerator + action_classes = generator.create_actions() + action = self._assert_single_item( + action_classes, + name=action_name + ) + + self.assertIsNotNone(generator) + self.assertTrue(issubclass(action['class'], actions.CinderAction)) + self.assertEqual("volumes.list", action['class'].client_method_name) diff --git a/mistral/tests/unit/actions/openstack/test_openstack_actions.py b/mistral/tests/unit/actions/openstack/test_openstack_actions.py index 08f28089f..c24538a93 100644 --- a/mistral/tests/unit/actions/openstack/test_openstack_actions.py +++ b/mistral/tests/unit/actions/openstack/test_openstack_actions.py @@ -78,3 +78,15 @@ class OpenStackActionTest(base.BaseTestCase): 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): + 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() + + self.assertTrue(mocked().volumes.get.called) + mocked().volumes.get.assert_called_once_with(volume="1234-abcd") diff --git a/mistral/utils/openstack/keystone.py b/mistral/utils/openstack/keystone.py index bebab8885..ec0eafcda 100644 --- a/mistral/utils/openstack/keystone.py +++ b/mistral/utils/openstack/keystone.py @@ -55,11 +55,15 @@ def client_for_trusts(trust_id): return _admin_client(trust_id=trust_id) -def get_endpoint_for_project(service_name): +def get_endpoint_for_project(service_name=None, service_type=None): 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] + + if service_name: + service_id = [s.id for s in service_list if s.name == service_name][0] + elif service_type: + service_id = [s.id for s in service_list if s.type == service_type][0] return keystone_client.endpoints.find(service_id=service_id, interface='public').url diff --git a/requirements.txt b/requirements.txt index f813cb079..7595402bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,7 @@ oslo.config>=1.4.0 # Apache-2.0 oslo.db>=1.0.0 # Apache-2.0 oslo.messaging>=1.4.0 paramiko>=1.13.0 +python-cinderclient>=1.1.0 python-heatclient>=0.2.9 python-keystoneclient>=0.10.0 python-neutronclient>=2.3.6,<3