diff --git a/mistral/actions/generator_factory.py b/mistral/actions/generator_factory.py index 090ad0971..8477f7988 100644 --- a/mistral/actions/generator_factory.py +++ b/mistral/actions/generator_factory.py @@ -24,5 +24,6 @@ def all_generators(): generators.NeutronActionGenerator, generators.CinderActionGenerator, generators.CeilometerActionGenerator, - generators.TroveActionGenerator + generators.TroveActionGenerator, + generators.IronicActionGenerator ] diff --git a/mistral/actions/openstack/action_generator/generators.py b/mistral/actions/openstack/action_generator/generators.py index 60a2cc278..22cfe8f30 100644 --- a/mistral/actions/openstack/action_generator/generators.py +++ b/mistral/actions/openstack/action_generator/generators.py @@ -54,3 +54,8 @@ class CeilometerActionGenerator(base.OpenStackActionGenerator): class TroveActionGenerator(base.OpenStackActionGenerator): action_namespace = "trove" base_action_class = actions.TroveAction + + +class IronicActionGenerator(base.OpenStackActionGenerator): + action_namespace = "ironic" + base_action_class = actions.IronicAction diff --git a/mistral/actions/openstack/actions.py b/mistral/actions/openstack/actions.py index 74f3938c4..dc762bcdd 100644 --- a/mistral/actions/openstack/actions.py +++ b/mistral/actions/openstack/actions.py @@ -16,6 +16,7 @@ from ceilometerclient.v2 import client as ceilometerclient from cinderclient.v2 import client as cinderclient from glanceclient.v2 import client as glanceclient from heatclient.v1 import client as heatclient +from ironicclient.v1 import client as ironicclient from keystoneclient import httpclient from keystoneclient.v3 import client as keystoneclient from neutronclient.v2_0 import client as neutronclient @@ -268,3 +269,24 @@ class TroveAction(base.OpenStackAction): @classmethod def _get_fake_client(cls): return cls._client_class() + + +class IronicAction(base.OpenStackAction): + _client_class = ironicclient.Client + + def _get_client(self): + ctx = context.ctx() + + LOG.debug("Ironic action security context: %s" % ctx) + + ironic_endpoint = keystone_utils.get_endpoint_for_project('ironic') + + return self._client_class( + ironic_endpoint.url, + token=ctx.auth_token, + region_name=ironic_endpoint.region + ) + + @classmethod + def _get_fake_client(cls): + return cls._client_class("http://127.0.0.1:6385/") diff --git a/mistral/actions/openstack/mapping.json b/mistral/actions/openstack/mapping.json index 406d01402..80ebc8e40 100644 --- a/mistral/actions/openstack/mapping.json +++ b/mistral/actions/openstack/mapping.json @@ -869,5 +869,46 @@ "configuration_parameters_get_parameter": "configuration_parameters.get_parameter", "configuration_parameters_parameters_by_version": "configuration_parameters.parameters_by_version", "configuration_parameters_get_parameter_by_version": "configuration_parameters.get_parameter_by_version" + }, + "ironic": { + "_comment": "It uses ironicclient.v1.", + "chassis_create": "chassis.create", + "chassis_delete": "chassis.delete", + "chassis_get": "chassis.get", + "chassis_list": "chassis.list", + "chassis_list_nodes": "chassis.list_nodes", + "chassis_update": "chassis.update", + "driver_delete": "driver.delete", + "driver_get": "driver.get", + "driver_get_vendor_passthru_methods": "driver.get_vendor_passthru_methods", + "driver_list": "driver.list", + "driver_properties": "driver.properties", + "driver_update": "driver.update", + "driver_vendor_passthru": "driver.vendor_passthru", + "node_create": "node.create", + "node_delete": "node.delete", + "node_get": "node.get", + "node_get_boot_device": "node.get_boot_device", + "node_get_by_instance_uuid": "node.get_by_instance_uuid", + "node_get_console": "node.get_console", + "node_get_supported_boot_devices": "node.get_supported_boot_devices", + "node_get_vendor_passthru_methods": "node.get_vendor_passthru_methods", + "node_list": "node.list", + "node_list_ports": "node.list_ports", + "node_set_boot_device": "node.set_boot_device", + "node_set_console_mode": "node.set_console_mode", + "node_set_maintenance": "node.set_maintenance", + "node_set_power_state": "node.set_power_state", + "node_set_provision_state": "node.set_provision_state", + "node_states": "node.states", + "node_update": "node.update", + "node_validate": "node.validate", + "node_vendor_passthru": "node.vendor_passthru", + "port_create": "port.create", + "port_delete": "port.delete", + "port_get": "port.get", + "port_get_by_address": "port.get_by_address", + "port_list": "port.list", + "port_update": "port.update" } } diff --git a/mistral/tests/unit/actions/openstack/test_ironic_generator.py b/mistral/tests/unit/actions/openstack/test_ironic_generator.py new file mode 100644 index 000000000..3373a968c --- /dev/null +++ b/mistral/tests/unit/actions/openstack/test_ironic_generator.py @@ -0,0 +1,32 @@ +# Copyright 2015 - AT&T Services, 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.unit import base + + +class IronicGeneratorTest(base.BaseTest): + def test_generator(self): + action_name = "ironic.node_list" + generator = generators.IronicActionGenerator + action_classes = generator.create_actions() + action = self._assert_single_item( + action_classes, + name=action_name + ) + + self.assertIsNotNone(generator) + self.assertTrue(issubclass(action['class'], actions.IronicAction)) + self.assertEqual("node.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 7348900dc..3d2b16510 100644 --- a/mistral/tests/unit/actions/openstack/test_openstack_actions.py +++ b/mistral/tests/unit/actions/openstack/test_openstack_actions.py @@ -114,3 +114,15 @@ class OpenStackActionTest(base.BaseTestCase): 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): + 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() + + self.assertTrue(mocked().node.get.called) + mocked().node.get.assert_called_once_with(node="1234-abcd") diff --git a/requirements.txt b/requirements.txt index 1d562a4a2..54f8683fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,6 +29,7 @@ python-keystoneclient!=1.8.0,>=1.6.0 python-neutronclient>=2.6.0 python-novaclient!=2.33.0,>=2.29.0 python-troveclient>=1.2.0 +python-ironicclient>=0.8.0 PyYAML>=3.1.0 requests>=2.8.1 retrying!=1.3.0,>=1.2.3 # Apache-2.0 diff --git a/tools/get_action_list.py b/tools/get_action_list.py index 8c67bbe1c..37ebeab29 100644 --- a/tools/get_action_list.py +++ b/tools/get_action_list.py @@ -30,6 +30,8 @@ from novaclient import client as novaclient from novaclient.openstack.common.apiclient import base as nova_base from troveclient import base as trove_base from troveclient.v1 import client as troveclient +from ironicclient.common import base as ironic_base +from ironicclient.v1 import client as ironicclient # TODO(nmakhotkin): Find a rational way to do it for neutron. # TODO(nmakhotkin): Implement recursive way of searching for managers @@ -62,6 +64,7 @@ BASE_NOVA_MANAGER = nova_base.HookableMixin BASE_KEYSTONE_MANAGER = keystone_base.Manager BASE_CINDER_MANAGER = cinder_base.HookableMixin BASE_TROVE_MANAGER = trove_base.Manager +BASE_IRONIC_MANAGER = ironic_base.Manager def get_parser(): parser = argparse.ArgumentParser( @@ -138,6 +141,9 @@ def get_cinder_client(**kwargs): def get_trove_client(**kwargs): return troveclient.Client('username', 'password') +def get_ironic_client(**kwargs): + return ironicclient.Client("http://127.0.0.1:6385/") + CLIENTS = { 'nova': get_nova_client, 'heat': get_heat_client, @@ -146,6 +152,7 @@ CLIENTS = { 'keystone': get_keystone_client, 'glance': get_glance_client, 'trove' : get_trove_client, + 'ironic' : get_ironic_client, # 'neutron': get_nova_client } BASE_MANAGERS = { @@ -156,6 +163,7 @@ BASE_MANAGERS = { 'keystone': BASE_KEYSTONE_MANAGER, 'glance': None, 'trove': BASE_TROVE_MANAGER, + 'ironic': BASE_IRONIC_MANAGER, # 'neutron': BASE_NOVA_MANAGER } NAMESPACES = {