Move mistral resources in-tree

This change relocates the mistral resources from the contrib area into the main
resource tree. It was originally added to contrib/ because of the project's
incubation status, and more specifically because the client is not in the
global-requirements.txt file. However, when this was discussed at the summit in
Vancouver, the decision was to move the resources to main tree but skip
registration if the client is not installed. This will save users from the
trouble of installing it as a plugin.

Change-Id: I6eeef5fa2b080df610e52620d2b935450d8d49e3
This commit is contained in:
Miguel Grinberg 2015-06-03 22:45:35 -07:00
parent 364a864d03
commit c31a9533fa
14 changed files with 102 additions and 95 deletions

View File

@ -1,18 +0,0 @@
Mistral plugin for OpenStack Heat
================================
This plugin enables using Mistral resources in a Heat template.
### 1. Install the Mistral plugin in Heat
NOTE: These instructions assume the value of heat.conf plugin_dirs includes the
default directory /usr/lib/heat.
To install the plugin, from this directory run:
sudo python ./setup.py install
### 2. Restart heat
Only the process "heat-engine" needs to be restarted to load the newly installed
plugin.

View File

@ -1 +0,0 @@
python-mistralclient

View File

@ -1,34 +0,0 @@
[metadata]
name = heat-contrib-mistral
summary = Heat resources for Mistral
description-file =
README.md
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 2.6
[files]
packages =
heat_mistral
# Copy to /usr/lib/heat for plugin loading
data_files =
lib/heat/mistral = heat_mistral/resources/*
[entry_points]
heat.clients =
mistral = heat_mistral.client:MistralClientPlugin
[global]
setup-hooks =
pbr.hooks.setup_hook

View File

@ -1,29 +0,0 @@
#!/usr/bin/env python
#
# 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.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr'],
pbr=True)

View File

@ -21,6 +21,10 @@ mistral_client = importutils.try_import('mistralclient.api.client')
class MistralClientPlugin(client_plugin.ClientPlugin):
@staticmethod
def is_available():
return mistral_base is not None
def _create(self):
endpoint_type = self._get_client_option('mistral', 'endpoint_type')
endpoint = self.url_for(service_type='workflowv2',

View File

@ -13,6 +13,7 @@
from heat.common.i18n import _
from heat.engine import attributes
from heat.engine import clients
from heat.engine import properties
from heat.engine import resource
from heat.engine import support
@ -126,3 +127,10 @@ def resource_mapping():
return {
'OS::Mistral::CronTrigger': CronTrigger,
}
def available_resource_mapping():
if not clients.has_client('mistral'):
return {}
return resource_mapping()

View File

@ -0,0 +1,32 @@
#
# 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 oslo_utils import importutils
import testtools
from heat.tests import common
from heat.tests import utils
mistral_client = importutils.try_import('mistralclient.api.base')
class MistralClientPluginTests(common.HeatTestCase):
@testtools.skipIf(mistral_client is None, 'Tests the mistral client')
def test_create(self):
context = utils.dummy_context()
plugin = context.clients.client_plugin('mistral')
client = plugin.client()
self.assertIsNotNone(client.workflows)
self.assertEqual('http://server.test:5000/v3',
client.http_client.base_url)

View File

@ -12,13 +12,20 @@
# under the License.
import mock
from oslo_utils import importutils
import testtools
from heat.common import exception
from heat.common import template_format
from heat.engine import resources
from heat.engine.resources.openstack.mistral import cron_trigger
from heat.engine import scheduler
from heat.engine import stack as stack_parser
from heat.engine import template
from heat.tests import common
from heat.tests import utils
from ..resources import cron_trigger # noqa
mistral_client = importutils.try_import('mistralclient.api.base')
stack_template = '''
heat_template_version: 2013-05-23
@ -43,10 +50,11 @@ class FakeCronTrigger(object):
self.remaining_executions = 3
class CronTriggerTest(common.HeatTestCase):
class MistralCronTriggerTest(common.HeatTestCase):
def setUp(self):
super(CronTriggerTest, self).setUp()
super(MistralCronTriggerTest, self).setUp()
resources.initialise()
utils.setup_dummy_db()
self.ctx = utils.dummy_context()
@ -107,3 +115,10 @@ class CronTriggerTest(common.HeatTestCase):
self.assertEqual((ct.DELETE, ct.COMPLETE), ct.state)
self.client.cron_triggers.delete.assert_called_once_with(
ct.resource_id)
@testtools.skipIf(mistral_client is not None,
'Tests mistral client not installed')
def test_no_client(self):
tmpl = template.Template((template_format.parse(stack_template)))
stack = stack_parser.Stack(utils.dummy_context(), 'foo', tmpl)
self.assertRaises(exception.ResourceTypeNotFound, stack.validate)

View File

@ -12,21 +12,26 @@
# under the License.
import mock
from oslo_utils import importutils
import six
import testtools
from heat.common import exception
from heat.common import template_format
from heat.engine.clients.os import mistral as client
from heat.engine import resource
from heat.engine import resources
from heat.engine.resources.openstack.mistral import workflow
from heat.engine.resources import signal_responder
from heat.engine.resources import stack_user
from heat.engine import scheduler
from heat.engine import stack as stack_parser
from heat.engine import template
from heat.tests import common
from heat.tests import utils
from mistralclient.api.v2 import executions
from .. import client # noqa
from ..resources import workflow # noqa
mistral_client = importutils.try_import('mistralclient.api.base')
executions = importutils.try_import('mistralclient.api.v2.executions')
workflow_template = """
heat_template_version: 2013-05-23
@ -174,9 +179,11 @@ class FakeWorkflow(object):
self.name = name
class TestWorkflow(common.HeatTestCase):
class TestMistralWorkflow(common.HeatTestCase):
def setUp(self):
super(TestWorkflow, self).setUp()
super(TestMistralWorkflow, self).setUp()
resources.initialise()
utils.setup_dummy_db()
self.ctx = utils.dummy_context()
tmpl = template_format.parse(workflow_template)
@ -188,13 +195,26 @@ class TestWorkflow(common.HeatTestCase):
self.mistral = mock.Mock()
self.patchobject(workflow.Workflow, 'mistral',
return_value=self.mistral)
mock.patch.object(stack_user.StackUser, '_create_user').start()
mock.patch.object(signal_responder.SignalResponder,
'_create_keypair').start()
mock.patch.object(client, 'mistral_base').start()
mock.patch.object(client.MistralClientPlugin, '_create').start()
self.patches = []
self.patches.append(mock.patch.object(stack_user.StackUser,
'_create_user'))
self.patches.append(mock.patch.object(signal_responder.SignalResponder,
'_create_keypair'))
self.patches.append(mock.patch.object(client,
'mistral_base'))
self.patches.append(mock.patch.object(client.MistralClientPlugin,
'_create'))
for patch in self.patches:
patch.start()
self.client = client.MistralClientPlugin(self.ctx)
def tearDown(self):
super(TestMistralWorkflow, self).tearDown()
for patch in self.patches:
patch.stop()
def _create_resource(self, name, snippet, stack):
wf = workflow.Workflow(name, snippet, stack)
self.mistral.workflows.create.return_value = [
@ -404,6 +424,8 @@ class TestWorkflow(common.HeatTestCase):
" Unknown input 1")
self.assertEqual(error_message, six.text_type(err))
@testtools.skipIf(executions is None,
'Uses the actual mistral client')
def test_signal_and_delete_with_executions(self):
tmpl = template_format.parse(workflow_template_full)
stack = utils.parse_stack(tmpl)
@ -478,3 +500,10 @@ class TestWorkflow(common.HeatTestCase):
execution = mock.Mock()
execution.id = '12345'
return execution
@testtools.skipIf(mistral_client is not None,
'Tests mistral client not installed')
def test_no_client(self):
tmpl = template.Template((template_format.parse(workflow_template)))
stack = stack_parser.Stack(utils.dummy_context(), 'foo', tmpl)
self.assertRaises(exception.ResourceTypeNotFound, stack.validate)

View File

@ -53,6 +53,7 @@ heat.clients =
heat = heat.engine.clients.os.heat_plugin:HeatClientPlugin
keystone = heat.engine.clients.os.keystone:KeystoneClientPlugin
manila = heat.engine.clients.os.manila:ManilaClientPlugin
mistral = heat.engine.clients.os.mistral:MistralClientPlugin
nova = heat.engine.clients.os.nova:NovaClientPlugin
neutron = heat.engine.clients.os.neutron:NeutronClientPlugin
swift = heat.engine.clients.os.swift:SwiftClientPlugin