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:
Nikolay Mahotkin 2014-08-07 16:00:43 +04:00
parent 86a93cc3ad
commit 9f78c6f251
14 changed files with 165 additions and 25 deletions

View File

@ -48,9 +48,8 @@ def get_registered_namespaces():
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]()
for generator in all_generators:
ns = _find_or_create_namespace(generator.action_namespace)
action_classes = generator.create_action_classes()
for action_name, action in action_classes.items():
ns.add(action_name, action)

View File

@ -1,6 +1,6 @@
# 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 obtain a copy of the License at
#
@ -16,8 +16,9 @@ from mistral.actions.openstack.action_generator import generators
def all_generators():
return {
"nova": generators.NovaActionGenerator,
"glance": generators.GlanceActionGenerator,
"keystone": generators.KeystoneActionGenerator
}
return [
generators.NovaActionGenerator,
generators.GlanceActionGenerator,
generators.KeystoneActionGenerator,
generators.HeatActionGenerator
]

View File

@ -37,27 +37,29 @@ class OpenStackActionGenerator(action_generator.ActionGenerator):
specific python-client and sets needed arguments
to actions.
"""
_action_namespace = None
action_namespace = None
base_action_class = None
def create_action_class(self, method_name):
@classmethod
def create_action_class(cls, method_name):
if not method_name:
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)
return action_class
def create_action_classes(self):
@classmethod
def create_action_classes(cls):
mapping = json.loads(open(pkg.resource_filename(
version.version_info.package,
MAPPING_PATH)).read())
method_dict = mapping[self._action_namespace]
method_dict = mapping[cls.action_namespace]
action_classes = {}
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

View File

@ -17,15 +17,20 @@ from mistral.actions.openstack import actions
class NovaActionGenerator(base.OpenStackActionGenerator):
_action_namespace = "nova"
action_namespace = "nova"
base_action_class = actions.NovaAction
class GlanceActionGenerator(base.OpenStackActionGenerator):
_action_namespace = "glance"
action_namespace = "glance"
base_action_class = actions.GlanceAction
class KeystoneActionGenerator(base.OpenStackActionGenerator):
_action_namespace = "keystone"
action_namespace = "keystone"
base_action_class = actions.KeystoneAction
class HeatActionGenerator(base.OpenStackActionGenerator):
action_namespace = "heat"
base_action_class = actions.HeatAction

View File

@ -12,13 +12,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import inspect
from glanceclient.v2 import client as glanceclient
from heatclient.v1 import client as heatclient
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 import exceptions as exc
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,
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))

View File

@ -306,5 +306,37 @@
"users_resource_class": "users.resource_class",
"users_update": "users.update",
"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"
}
}

View File

@ -0,0 +1,4 @@
Workflow:
tasks:
heat_stack_list:
action: heat.stacks_list

View File

@ -14,14 +14,14 @@
from oslotest import base
from mistral.actions import generator_factory
from mistral.actions.openstack.action_generator import generators
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"]()
generator = generators.GlanceActionGenerator
action_classes = generator.create_action_classes()
short_action_name = action_name.split(".")[1]
action_class = action_classes[short_action_name]

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 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))

View File

@ -14,14 +14,14 @@
from oslotest import base
from mistral.actions import generator_factory
from mistral.actions.openstack.action_generator import generators
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"]()
generator = generators.KeystoneActionGenerator
action_classes = generator.create_action_classes()
short_action_name = action_name.split(".")[1]
action_class = action_classes[short_action_name]
@ -29,4 +29,4 @@ class KeystoneGeneratorTest(base.BaseTestCase):
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)
self.assertEqual("users.create", action_class.client_method)

View File

@ -14,14 +14,14 @@
from oslotest import base
from mistral.actions import generator_factory
from mistral.actions.openstack.action_generator import generators
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"]()
generator = generators.NovaActionGenerator
action_classes = generator.create_action_classes()
short_action_name = action_name.split(".")[1]
action_class = action_classes[short_action_name]

View File

@ -54,3 +54,15 @@ class OpenStackActionTest(base.BaseTestCase):
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):
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")

View File

@ -122,3 +122,28 @@ class OpenStackActionsEngineTest(base.EngineTestCase):
self.assertEqual(states.SUCCESS, task['state'])
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])

View File

@ -15,6 +15,7 @@ oslo.config>=1.2.1
oslo.db>=0.2.0 # Apache-2.0
oslo.messaging>=1.3.0
paramiko>=1.13.0
python-heatclient>=0.2.9
python-keystoneclient>=0.9.0
python-novaclient>=2.17
python-glanceclient>=0.13