Add heat actions
* Added heat actions and action generator * Modified base action generator * Unit tests Implements blueprint: mistral-openstack-actions Change-Id: I043e8f61e00a3fcf81d096790e3486fe0c052da9
This commit is contained in:
parent
86a93cc3ad
commit
9f78c6f251
@ -48,9 +48,8 @@ def get_registered_namespaces():
|
|||||||
|
|
||||||
def _register_dynamic_action_classes():
|
def _register_dynamic_action_classes():
|
||||||
all_generators = generator_factory.all_generators()
|
all_generators = generator_factory.all_generators()
|
||||||
for name in all_generators:
|
for generator in all_generators:
|
||||||
ns = _find_or_create_namespace(name)
|
ns = _find_or_create_namespace(generator.action_namespace)
|
||||||
generator = all_generators[name]()
|
|
||||||
action_classes = generator.create_action_classes()
|
action_classes = generator.create_action_classes()
|
||||||
for action_name, action in action_classes.items():
|
for action_name, action in action_classes.items():
|
||||||
ns.add(action_name, action)
|
ns.add(action_name, action)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Copyright 2014 - Mirantis, Inc.
|
# Copyright 2014 - Mirantis, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
@ -16,8 +16,9 @@ from mistral.actions.openstack.action_generator import generators
|
|||||||
|
|
||||||
|
|
||||||
def all_generators():
|
def all_generators():
|
||||||
return {
|
return [
|
||||||
"nova": generators.NovaActionGenerator,
|
generators.NovaActionGenerator,
|
||||||
"glance": generators.GlanceActionGenerator,
|
generators.GlanceActionGenerator,
|
||||||
"keystone": generators.KeystoneActionGenerator
|
generators.KeystoneActionGenerator,
|
||||||
}
|
generators.HeatActionGenerator
|
||||||
|
]
|
@ -37,27 +37,29 @@ class OpenStackActionGenerator(action_generator.ActionGenerator):
|
|||||||
specific python-client and sets needed arguments
|
specific python-client and sets needed arguments
|
||||||
to actions.
|
to actions.
|
||||||
"""
|
"""
|
||||||
_action_namespace = None
|
action_namespace = None
|
||||||
base_action_class = None
|
base_action_class = None
|
||||||
|
|
||||||
def create_action_class(self, method_name):
|
@classmethod
|
||||||
|
def create_action_class(cls, method_name):
|
||||||
if not method_name:
|
if not method_name:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
action_class = type(str(method_name), (self.base_action_class,), {})
|
action_class = type(str(method_name), (cls.base_action_class,), {})
|
||||||
setattr(action_class, 'client_method', method_name)
|
setattr(action_class, 'client_method', method_name)
|
||||||
|
|
||||||
return action_class
|
return action_class
|
||||||
|
|
||||||
def create_action_classes(self):
|
@classmethod
|
||||||
|
def create_action_classes(cls):
|
||||||
mapping = json.loads(open(pkg.resource_filename(
|
mapping = json.loads(open(pkg.resource_filename(
|
||||||
version.version_info.package,
|
version.version_info.package,
|
||||||
MAPPING_PATH)).read())
|
MAPPING_PATH)).read())
|
||||||
method_dict = mapping[self._action_namespace]
|
method_dict = mapping[cls.action_namespace]
|
||||||
|
|
||||||
action_classes = {}
|
action_classes = {}
|
||||||
|
|
||||||
for action_name, method_name in method_dict.items():
|
for action_name, method_name in method_dict.items():
|
||||||
action_classes[action_name] = self.create_action_class(method_name)
|
action_classes[action_name] = cls.create_action_class(method_name)
|
||||||
|
|
||||||
return action_classes
|
return action_classes
|
||||||
|
@ -17,15 +17,20 @@ from mistral.actions.openstack import actions
|
|||||||
|
|
||||||
|
|
||||||
class NovaActionGenerator(base.OpenStackActionGenerator):
|
class NovaActionGenerator(base.OpenStackActionGenerator):
|
||||||
_action_namespace = "nova"
|
action_namespace = "nova"
|
||||||
base_action_class = actions.NovaAction
|
base_action_class = actions.NovaAction
|
||||||
|
|
||||||
|
|
||||||
class GlanceActionGenerator(base.OpenStackActionGenerator):
|
class GlanceActionGenerator(base.OpenStackActionGenerator):
|
||||||
_action_namespace = "glance"
|
action_namespace = "glance"
|
||||||
base_action_class = actions.GlanceAction
|
base_action_class = actions.GlanceAction
|
||||||
|
|
||||||
|
|
||||||
class KeystoneActionGenerator(base.OpenStackActionGenerator):
|
class KeystoneActionGenerator(base.OpenStackActionGenerator):
|
||||||
_action_namespace = "keystone"
|
action_namespace = "keystone"
|
||||||
base_action_class = actions.KeystoneAction
|
base_action_class = actions.KeystoneAction
|
||||||
|
|
||||||
|
|
||||||
|
class HeatActionGenerator(base.OpenStackActionGenerator):
|
||||||
|
action_namespace = "heat"
|
||||||
|
base_action_class = actions.HeatAction
|
||||||
|
@ -12,13 +12,17 @@
|
|||||||
# 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 inspect
|
||||||
|
|
||||||
from glanceclient.v2 import client as glanceclient
|
from glanceclient.v2 import client as glanceclient
|
||||||
|
from heatclient.v1 import client as heatclient
|
||||||
from keystoneclient.v3 import client as keystoneclient
|
from keystoneclient.v3 import client as keystoneclient
|
||||||
from novaclient.v1_1 import client as novaclient
|
from novaclient.v1_1 import client as novaclient
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from mistral.actions.openstack import base
|
from mistral.actions.openstack import base
|
||||||
from mistral import context
|
from mistral import context
|
||||||
|
from mistral import exceptions as exc
|
||||||
from mistral.utils.openstack import keystone as keystone_utils
|
from mistral.utils.openstack import keystone as keystone_utils
|
||||||
|
|
||||||
|
|
||||||
@ -57,3 +61,27 @@ class KeystoneAction(base.OpenStackAction):
|
|||||||
|
|
||||||
return self._client_class(token=ctx.auth_token,
|
return self._client_class(token=ctx.auth_token,
|
||||||
auth_url=auth_url)
|
auth_url=auth_url)
|
||||||
|
|
||||||
|
|
||||||
|
class HeatAction(base.OpenStackAction):
|
||||||
|
_client_class = heatclient.Client
|
||||||
|
|
||||||
|
def _get_client(self):
|
||||||
|
ctx = context.ctx()
|
||||||
|
endpoint_url_tmpl = keystone_utils.get_endpoint_for_project('heat')
|
||||||
|
endpoint_url = endpoint_url_tmpl % {'tenant_id': ctx.project_id}
|
||||||
|
|
||||||
|
return self._client_class(endpoint_url,
|
||||||
|
token=ctx.auth_token,
|
||||||
|
username=ctx.user_name)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
method = self._get_client_method()
|
||||||
|
result = method(**self._kwargs_for_run)
|
||||||
|
if inspect.isgenerator(result):
|
||||||
|
return [v for v in result]
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
raise exc.ActionException("%s failed: %s"
|
||||||
|
% (self.__class__.__name__, e))
|
@ -306,5 +306,37 @@
|
|||||||
"users_resource_class": "users.resource_class",
|
"users_resource_class": "users.resource_class",
|
||||||
"users_update": "users.update",
|
"users_update": "users.update",
|
||||||
"users_update_password": "users.update_password"
|
"users_update_password": "users.update_password"
|
||||||
|
},
|
||||||
|
"heat": {
|
||||||
|
"_comment": "It uses heatclient.v2.",
|
||||||
|
"actions_resume": "actions.resume",
|
||||||
|
"actions_suspend": "actions.suspend",
|
||||||
|
"build_info_build_info": "build_info.build_info",
|
||||||
|
"events_get": "events.get",
|
||||||
|
"events_list": "events.list",
|
||||||
|
"resources_generate_template": "resources.generate_template",
|
||||||
|
"resources_get": "resources.get",
|
||||||
|
"resources_list": "resources.list",
|
||||||
|
"resources_metadata": "resources.metadata",
|
||||||
|
"resources_signal": "resources.signal",
|
||||||
|
"resource_types_get": "resource_types.get",
|
||||||
|
"resource_types_list": "resource_types.list",
|
||||||
|
"software_configs_create": "software_configs.create",
|
||||||
|
"software_configs_delete": "software_configs.delete",
|
||||||
|
"software_configs_get": "software_configs.get",
|
||||||
|
"software_deployments_create": "software_deployments.create",
|
||||||
|
"software_deployments_delete": "software_deployments.delete",
|
||||||
|
"software_deployments_get": "software_deployments.get",
|
||||||
|
"software_deployments_list": "software_deployments.list",
|
||||||
|
"software_deployments_metadata": "software_deployments.metadata",
|
||||||
|
"software_deployments_update": "software_deployments.update",
|
||||||
|
"stacks_abandon": "stacks.abandon",
|
||||||
|
"stacks_create": "stacks.create",
|
||||||
|
"stacks_delete": "stacks.delete",
|
||||||
|
"stacks_get": "stacks.get",
|
||||||
|
"stacks_list": "stacks.list",
|
||||||
|
"stacks_template": "stacks.template",
|
||||||
|
"stacks_update": "stacks.update",
|
||||||
|
"stacks_validate": "stacks.validate"
|
||||||
}
|
}
|
||||||
}
|
}
|
4
mistral/tests/resources/openstack_tasks/heat.yaml
Normal file
4
mistral/tests/resources/openstack_tasks/heat.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Workflow:
|
||||||
|
tasks:
|
||||||
|
heat_stack_list:
|
||||||
|
action: heat.stacks_list
|
@ -14,14 +14,14 @@
|
|||||||
|
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
|
|
||||||
from mistral.actions import generator_factory
|
from mistral.actions.openstack.action_generator import generators
|
||||||
from mistral.actions.openstack import actions
|
from mistral.actions.openstack import actions
|
||||||
|
|
||||||
|
|
||||||
class GlanceGeneratorTest(base.BaseTestCase):
|
class GlanceGeneratorTest(base.BaseTestCase):
|
||||||
def test_generator(self):
|
def test_generator(self):
|
||||||
action_name = "glance.images_list"
|
action_name = "glance.images_list"
|
||||||
generator = generator_factory.all_generators()["glance"]()
|
generator = generators.GlanceActionGenerator
|
||||||
action_classes = generator.create_action_classes()
|
action_classes = generator.create_action_classes()
|
||||||
short_action_name = action_name.split(".")[1]
|
short_action_name = action_name.split(".")[1]
|
||||||
action_class = action_classes[short_action_name]
|
action_class = action_classes[short_action_name]
|
||||||
|
31
mistral/tests/unit/actions/openstack/test_heat_generator.py
Normal file
31
mistral/tests/unit/actions/openstack/test_heat_generator.py
Normal 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 oslotest import base
|
||||||
|
|
||||||
|
from mistral.actions.openstack.action_generator import generators
|
||||||
|
from mistral.actions.openstack import actions
|
||||||
|
|
||||||
|
|
||||||
|
class HeatGeneratorTest(base.BaseTestCase):
|
||||||
|
def test_generator(self):
|
||||||
|
action_name = "heat.stacks_list"
|
||||||
|
generator = generators.HeatActionGenerator
|
||||||
|
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.HeatAction))
|
@ -14,14 +14,14 @@
|
|||||||
|
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
|
|
||||||
from mistral.actions import generator_factory
|
from mistral.actions.openstack.action_generator import generators
|
||||||
from mistral.actions.openstack import actions
|
from mistral.actions.openstack import actions
|
||||||
|
|
||||||
|
|
||||||
class KeystoneGeneratorTest(base.BaseTestCase):
|
class KeystoneGeneratorTest(base.BaseTestCase):
|
||||||
def test_generator(self):
|
def test_generator(self):
|
||||||
action_name = "keystone.users_create"
|
action_name = "keystone.users_create"
|
||||||
generator = generator_factory.all_generators()["keystone"]()
|
generator = generators.KeystoneActionGenerator
|
||||||
action_classes = generator.create_action_classes()
|
action_classes = generator.create_action_classes()
|
||||||
short_action_name = action_name.split(".")[1]
|
short_action_name = action_name.split(".")[1]
|
||||||
action_class = action_classes[short_action_name]
|
action_class = action_classes[short_action_name]
|
||||||
@ -29,4 +29,4 @@ class KeystoneGeneratorTest(base.BaseTestCase):
|
|||||||
self.assertIsNotNone(generator)
|
self.assertIsNotNone(generator)
|
||||||
self.assertIn(short_action_name, action_classes)
|
self.assertIn(short_action_name, action_classes)
|
||||||
self.assertTrue(issubclass(action_class, actions.KeystoneAction))
|
self.assertTrue(issubclass(action_class, actions.KeystoneAction))
|
||||||
self.assertEqual("users.create", action_class.client_method)
|
self.assertEqual("users.create", action_class.client_method)
|
||||||
|
@ -14,14 +14,14 @@
|
|||||||
|
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
|
|
||||||
from mistral.actions import generator_factory
|
from mistral.actions.openstack.action_generator import generators
|
||||||
from mistral.actions.openstack import actions
|
from mistral.actions.openstack import actions
|
||||||
|
|
||||||
|
|
||||||
class NovaGeneratorTest(base.BaseTestCase):
|
class NovaGeneratorTest(base.BaseTestCase):
|
||||||
def test_generator(self):
|
def test_generator(self):
|
||||||
action_name = "nova.servers_get"
|
action_name = "nova.servers_get"
|
||||||
generator = generator_factory.all_generators()["nova"]()
|
generator = generators.NovaActionGenerator
|
||||||
action_classes = generator.create_action_classes()
|
action_classes = generator.create_action_classes()
|
||||||
short_action_name = action_name.split(".")[1]
|
short_action_name = action_name.split(".")[1]
|
||||||
action_class = action_classes[short_action_name]
|
action_class = action_classes[short_action_name]
|
||||||
|
@ -54,3 +54,15 @@ class OpenStackActionTest(base.BaseTestCase):
|
|||||||
|
|
||||||
self.assertTrue(mocked().users.get.called)
|
self.assertTrue(mocked().users.get.called)
|
||||||
mocked().users.get.assert_called_once_with(user="1234-abcd")
|
mocked().users.get.assert_called_once_with(user="1234-abcd")
|
||||||
|
|
||||||
|
@mock.patch.object(actions.HeatAction, '_get_client')
|
||||||
|
def test_heat_action(self, mocked):
|
||||||
|
method_name = "stacks.get"
|
||||||
|
action_class = actions.HeatAction
|
||||||
|
action_class.client_method = method_name
|
||||||
|
params = {'id': '1234-abcd'}
|
||||||
|
action = action_class(**params)
|
||||||
|
action.run()
|
||||||
|
|
||||||
|
self.assertTrue(mocked().stacks.get.called)
|
||||||
|
mocked().stacks.get.assert_called_once_with(id="1234-abcd")
|
||||||
|
@ -122,3 +122,28 @@ class OpenStackActionsEngineTest(base.EngineTestCase):
|
|||||||
|
|
||||||
self.assertEqual(states.SUCCESS, task['state'])
|
self.assertEqual(states.SUCCESS, task['state'])
|
||||||
self.assertEqual("servers", task['output']['task'][task_name])
|
self.assertEqual("servers", task['output']['task'][task_name])
|
||||||
|
|
||||||
|
@mock.patch.object(actions.HeatAction, 'run',
|
||||||
|
mock.Mock(return_value="stacks"))
|
||||||
|
def test_heat_action(self):
|
||||||
|
context = {}
|
||||||
|
wb = create_workbook('openstack_tasks/heat.yaml')
|
||||||
|
task_name = 'heat_stack_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("stacks", task['output']['task'][task_name])
|
||||||
|
@ -15,6 +15,7 @@ oslo.config>=1.2.1
|
|||||||
oslo.db>=0.2.0 # Apache-2.0
|
oslo.db>=0.2.0 # Apache-2.0
|
||||||
oslo.messaging>=1.3.0
|
oslo.messaging>=1.3.0
|
||||||
paramiko>=1.13.0
|
paramiko>=1.13.0
|
||||||
|
python-heatclient>=0.2.9
|
||||||
python-keystoneclient>=0.9.0
|
python-keystoneclient>=0.9.0
|
||||||
python-novaclient>=2.17
|
python-novaclient>=2.17
|
||||||
python-glanceclient>=0.13
|
python-glanceclient>=0.13
|
||||||
|
Loading…
Reference in New Issue
Block a user