3216f6126f
The functional gate is also failing due to a recent change introduced in devstack -> I613f1bdc6673f0c4bfe29aaab7b514348a617a8c This fixes both the gates, functional and unit tests. Co-Authored-By: Thomas Herve <therve@redhat.com> Closes-Bug: #1473398 Change-Id: Ia77a1fd884c6346edfb90777e91a51d279ccd92a
520 lines
20 KiB
Python
520 lines
20 KiB
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.
|
|
|
|
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
|
|
|
|
mistral_client = importutils.try_import('mistralclient.api.base')
|
|
executions = importutils.try_import('mistralclient.api.v2.executions')
|
|
|
|
workflow_template = """
|
|
heat_template_version: 2013-05-23
|
|
resources:
|
|
workflow:
|
|
type: OS::Mistral::Workflow
|
|
properties:
|
|
type: direct
|
|
tasks:
|
|
- name: hello
|
|
action: std.echo output='Good morning!'
|
|
publish:
|
|
result: <% $.hello %>
|
|
"""
|
|
|
|
workflow_template_with_params = """
|
|
heat_template_version: 2013-05-23
|
|
resources:
|
|
workflow:
|
|
type: OS::Mistral::Workflow
|
|
properties:
|
|
params: {'test':'param_value'}
|
|
type: direct
|
|
tasks:
|
|
- name: hello
|
|
action: std.echo output='Good morning!'
|
|
publish:
|
|
result: <% $.hello %>
|
|
"""
|
|
workflow_template_with_params_override = """
|
|
heat_template_version: 2013-05-23
|
|
resources:
|
|
workflow:
|
|
type: OS::Mistral::Workflow
|
|
properties:
|
|
params: {'test':'param_value_override','test1':'param_value_override_1'}
|
|
type: direct
|
|
tasks:
|
|
- name: hello
|
|
action: std.echo output='Good morning!'
|
|
publish:
|
|
result: <% $.hello %>
|
|
"""
|
|
|
|
workflow_template_full = """
|
|
heat_template_version: 2013-05-23
|
|
resources:
|
|
create_vm:
|
|
type: OS::Mistral::Workflow
|
|
properties:
|
|
name: create_vm
|
|
type: direct
|
|
input:
|
|
name: create_test_server
|
|
image: 31d8eeaf-686e-4e95-bb27-765014b9f20b
|
|
flavor: 2
|
|
output:
|
|
vm_id: <% $.vm_id %>
|
|
tasks:
|
|
- name: create_server
|
|
action: |
|
|
nova.servers_create name=<% $.name %> image=<% $.image %>
|
|
flavor=<% $.flavor %>
|
|
publish:
|
|
vm_id: <% $.create_server.id %>
|
|
on_success:
|
|
- check_server_exists
|
|
- name: check_server_exists
|
|
action: nova.servers_get server=<% $.vm_id %>
|
|
publish:
|
|
server_exists: True
|
|
on_success:
|
|
- wait_instance
|
|
- name: wait_instance
|
|
action: nova.servers_find id=<% $.vm_id %> status='ACTIVE'
|
|
policies:
|
|
retry:
|
|
delay: 5
|
|
count: 15
|
|
"""
|
|
|
|
workflow_template_bad = """
|
|
heat_template_version: 2013-05-23
|
|
resources:
|
|
workflow:
|
|
type: OS::Mistral::Workflow
|
|
properties:
|
|
type: direct
|
|
tasks:
|
|
- name: second_task
|
|
action: std.noop
|
|
requires: [first_task]
|
|
- name: first_task
|
|
action: std.noop
|
|
"""
|
|
|
|
workflow_template_bad_reverse = """
|
|
heat_template_version: 2013-05-23
|
|
resources:
|
|
workflow:
|
|
type: OS::Mistral::Workflow
|
|
properties:
|
|
type: reverse
|
|
tasks:
|
|
- name: second_task
|
|
action: std.noop
|
|
requires: [first_task]
|
|
- name: first_task
|
|
action: std.noop
|
|
"""
|
|
|
|
workflow_template_update_replace = """
|
|
heat_template_version: 2013-05-23
|
|
resources:
|
|
workflow:
|
|
type: OS::Mistral::Workflow
|
|
properties:
|
|
name: hello_action
|
|
type: direct
|
|
tasks:
|
|
- name: hello
|
|
action: std.echo output='Good evening!'
|
|
publish:
|
|
result: <% $.hello %>
|
|
"""
|
|
|
|
workflow_template_update = """
|
|
heat_template_version: 2013-05-23
|
|
resources:
|
|
workflow:
|
|
type: OS::Mistral::Workflow
|
|
properties:
|
|
type: direct
|
|
description: just testing workflow resource
|
|
tasks:
|
|
- name: hello
|
|
action: std.echo output='Good evening!'
|
|
publish:
|
|
result: <% $.hello %>
|
|
"""
|
|
|
|
|
|
class FakeWorkflow(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class MistralWorkFlowTestResource(workflow.Workflow):
|
|
@classmethod
|
|
def is_service_available(cls, context):
|
|
return True
|
|
|
|
|
|
class TestMistralWorkflow(common.HeatTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestMistralWorkflow, self).setUp()
|
|
resources.initialise()
|
|
utils.setup_dummy_db()
|
|
self.ctx = utils.dummy_context()
|
|
tmpl = template_format.parse(workflow_template)
|
|
self.stack = utils.parse_stack(tmpl, stack_name='test_stack')
|
|
|
|
resource_defns = self.stack.t.resource_definitions(self.stack)
|
|
self.rsrc_defn = resource_defns['workflow']
|
|
|
|
self.mistral = mock.Mock()
|
|
self.patchobject(MistralWorkFlowTestResource, 'mistral',
|
|
return_value=self.mistral)
|
|
|
|
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 = MistralWorkFlowTestResource(name, snippet, stack)
|
|
self.mistral.workflows.create.return_value = [
|
|
FakeWorkflow('test_stack-workflow-b5fiekfci3yc')]
|
|
scheduler.TaskRunner(wf.create)()
|
|
return wf
|
|
|
|
def test_create(self):
|
|
wf = self._create_resource('workflow', self.rsrc_defn, self.stack)
|
|
expected_state = (wf.CREATE, wf.COMPLETE)
|
|
self.assertEqual(expected_state, wf.state)
|
|
self.assertEqual('test_stack-workflow-b5fiekfci3yc', wf.resource_id)
|
|
|
|
def test_create_with_name(self):
|
|
tmpl = template_format.parse(workflow_template_full)
|
|
stack = utils.parse_stack(tmpl)
|
|
|
|
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
|
|
|
wf = MistralWorkFlowTestResource('create_vm', rsrc_defns, stack)
|
|
self.mistral.workflows.create.return_value = [
|
|
FakeWorkflow('create_vm')]
|
|
scheduler.TaskRunner(wf.create)()
|
|
|
|
expected_state = (wf.CREATE, wf.COMPLETE)
|
|
self.assertEqual(expected_state, wf.state)
|
|
self.assertEqual('create_vm', wf.resource_id)
|
|
|
|
def test_attributes(self):
|
|
wf = self._create_resource('workflow', self.rsrc_defn, self.stack)
|
|
self.assertEqual({'name': 'test_stack-workflow-b5fiekfci3yc',
|
|
'input': None}, wf.FnGetAtt('data'))
|
|
self.assertEqual([], wf.FnGetAtt('executions'))
|
|
|
|
def test_direct_workflow_validation_error(self):
|
|
error_msg = ("Mistral resource validation error: "
|
|
"workflow.properties.tasks.second_task.requires: "
|
|
"task second_task contains property 'requires' "
|
|
"in case of direct workflow. Only reverse workflows "
|
|
"can contain property 'requires'.")
|
|
self._test_validation_failed(workflow_template_bad, error_msg)
|
|
|
|
def test_wrong_params_using(self):
|
|
error_msg = ("Mistral resource validation error: "
|
|
"workflow.properties.params: 'task_name' is not assigned "
|
|
"in 'params' in case of reverse type workflow.")
|
|
self._test_validation_failed(workflow_template_bad_reverse, error_msg)
|
|
|
|
def _test_validation_failed(self, templatem, error_msg):
|
|
tmpl = template_format.parse(templatem)
|
|
stack = utils.parse_stack(tmpl)
|
|
|
|
rsrc_defns = stack.t.resource_definitions(stack)['workflow']
|
|
|
|
wf = MistralWorkFlowTestResource('workflow', rsrc_defns, stack)
|
|
|
|
exc = self.assertRaises(exception.StackValidationFailed,
|
|
wf.validate)
|
|
self.assertEqual(error_msg, six.text_type(exc))
|
|
|
|
def test_create_wrong_definition(self):
|
|
tmpl = template_format.parse(workflow_template)
|
|
stack = utils.parse_stack(tmpl)
|
|
|
|
rsrc_defns = stack.t.resource_definitions(stack)['workflow']
|
|
|
|
wf = MistralWorkFlowTestResource('workflow', rsrc_defns, stack)
|
|
|
|
self.mistral.workflows.create.side_effect = Exception('boom!')
|
|
|
|
exc = self.assertRaises(exception.ResourceFailure,
|
|
scheduler.TaskRunner(wf.create))
|
|
expected_state = (wf.CREATE, wf.FAILED)
|
|
self.assertEqual(expected_state, wf.state)
|
|
self.assertIn('Exception: resources.workflow: boom!',
|
|
six.text_type(exc))
|
|
|
|
def test_update_replace(self):
|
|
wf = self._create_resource('workflow', self.rsrc_defn, self.stack)
|
|
|
|
t = template_format.parse(workflow_template_update_replace)
|
|
rsrc_defns = template.Template(t).resource_definitions(self.stack)
|
|
new_workflow = rsrc_defns['workflow']
|
|
|
|
new_workflows = [FakeWorkflow('hello_action')]
|
|
self.mistral.workflows.update.return_value = new_workflows
|
|
self.mistral.workflows.delete.return_value = None
|
|
|
|
err = self.assertRaises(resource.UpdateReplace,
|
|
scheduler.TaskRunner(wf.update,
|
|
new_workflow))
|
|
msg = 'The Resource workflow requires replacement.'
|
|
self.assertEqual(msg, six.text_type(err))
|
|
|
|
def test_update(self):
|
|
wf = self._create_resource('workflow', self.rsrc_defn,
|
|
self.stack)
|
|
t = template_format.parse(workflow_template_update)
|
|
rsrc_defns = template.Template(t).resource_definitions(self.stack)
|
|
new_wf = rsrc_defns['workflow']
|
|
self.mistral.workflows.update.return_value = [
|
|
FakeWorkflow('test_stack-workflow-b5fiekfci3yc')]
|
|
scheduler.TaskRunner(wf.update, new_wf)()
|
|
self.assertTrue(self.mistral.workflows.update.called)
|
|
self.assertEqual((wf.UPDATE, wf.COMPLETE), wf.state)
|
|
|
|
def test_update_failed(self):
|
|
wf = self._create_resource('workflow', self.rsrc_defn,
|
|
self.stack)
|
|
t = template_format.parse(workflow_template_update)
|
|
rsrc_defns = template.Template(t).resource_definitions(self.stack)
|
|
new_wf = rsrc_defns['workflow']
|
|
self.mistral.workflows.update.side_effect = Exception('boom!')
|
|
self.assertRaises(exception.ResourceFailure,
|
|
scheduler.TaskRunner(wf.update, new_wf))
|
|
self.assertEqual((wf.UPDATE, wf.FAILED), wf.state)
|
|
|
|
def test_delete(self):
|
|
wf = self._create_resource('workflow', self.rsrc_defn, self.stack)
|
|
|
|
scheduler.TaskRunner(wf.delete)()
|
|
self.assertEqual((wf.DELETE, wf.COMPLETE), wf.state)
|
|
|
|
def test_delete_no_data(self):
|
|
wf = self._create_resource('workflow', self.rsrc_defn, self.stack)
|
|
|
|
wf.data_delete('executions')
|
|
self.assertEqual([], wf.FnGetAtt('executions'))
|
|
scheduler.TaskRunner(wf.delete)()
|
|
self.assertEqual((wf.DELETE, wf.COMPLETE), wf.state)
|
|
|
|
def test_delete_not_found(self):
|
|
wf = self._create_resource('workflow', self.rsrc_defn, self.stack)
|
|
|
|
self.mistral.workflows.delete.side_effect = (
|
|
self.mistral.mistral_base.APIException(error_code=404))
|
|
|
|
scheduler.TaskRunner(wf.delete)()
|
|
self.assertEqual((wf.DELETE, wf.COMPLETE), wf.state)
|
|
|
|
@mock.patch.object(resource.Resource, 'client_plugin')
|
|
def test_delete_other_errors(self, mock_plugin):
|
|
"""We mock client_plugin for returning correct mistral client."""
|
|
mock_plugin.return_value = self.client
|
|
client.mistral_base.APIException = exception.Error
|
|
wf = self._create_resource('workflow', self.rsrc_defn, self.stack)
|
|
|
|
self.mistral.workflows.delete.side_effect = (Exception('boom!'))
|
|
|
|
exc = self.assertRaises(exception.ResourceFailure,
|
|
scheduler.TaskRunner(wf.delete))
|
|
self.assertEqual((wf.DELETE, wf.FAILED), wf.state)
|
|
self.assertIn('boom!', six.text_type(exc))
|
|
|
|
def test_resource_mapping(self):
|
|
mapping = workflow.resource_mapping()
|
|
self.assertEqual(1, len(mapping))
|
|
self.assertEqual(workflow.Workflow,
|
|
mapping['OS::Mistral::Workflow'])
|
|
|
|
def test_signal_failed(self):
|
|
tmpl = template_format.parse(workflow_template_full)
|
|
stack = utils.parse_stack(tmpl)
|
|
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
|
wf = MistralWorkFlowTestResource('create_vm', rsrc_defns, stack)
|
|
self.mistral.workflows.create.return_value = [
|
|
FakeWorkflow('create_vm')]
|
|
scheduler.TaskRunner(wf.create)()
|
|
details = {'input': {'flavor': '3'}}
|
|
self.mistral.executions.create.side_effect = Exception('boom!')
|
|
err = self.assertRaises(exception.ResourceFailure,
|
|
scheduler.TaskRunner(wf.signal, details))
|
|
self.assertEqual('Exception: resources.create_vm: boom!',
|
|
six.text_type(err))
|
|
|
|
def test_signal_wrong_input_and_params_type(self):
|
|
tmpl = template_format.parse(workflow_template_full)
|
|
stack = utils.parse_stack(tmpl)
|
|
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
|
wf = MistralWorkFlowTestResource('create_vm', rsrc_defns, stack)
|
|
self.mistral.workflows.create.return_value = [
|
|
FakeWorkflow('create_vm')]
|
|
scheduler.TaskRunner(wf.create)()
|
|
details = {'input': '3'}
|
|
err = self.assertRaises(exception.ResourceFailure,
|
|
scheduler.TaskRunner(wf.signal, details))
|
|
error_message = ("StackValidationFailed: resources.create_vm: "
|
|
"Signal data error: Input in"
|
|
" signal data must be a map, find a <type 'str'>")
|
|
self.assertEqual(error_message, six.text_type(err))
|
|
details = {'params': '3'}
|
|
err = self.assertRaises(exception.ResourceFailure,
|
|
scheduler.TaskRunner(wf.signal, details))
|
|
error_message = ("StackValidationFailed: resources.create_vm: "
|
|
"Signal data error: Params "
|
|
"must be a map, find a <type 'str'>")
|
|
self.assertEqual(error_message, six.text_type(err))
|
|
|
|
def test_signal_wrong_input_key(self):
|
|
tmpl = template_format.parse(workflow_template_full)
|
|
stack = utils.parse_stack(tmpl)
|
|
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
|
wf = MistralWorkFlowTestResource('create_vm', rsrc_defns, stack)
|
|
self.mistral.workflows.create.return_value = [
|
|
FakeWorkflow('create_vm')]
|
|
scheduler.TaskRunner(wf.create)()
|
|
details = {'input': {'1': '3'}}
|
|
err = self.assertRaises(exception.ResourceFailure,
|
|
scheduler.TaskRunner(wf.signal, details))
|
|
error_message = ("StackValidationFailed: resources.create_vm: "
|
|
"Signal data error: 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)
|
|
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
|
wf = MistralWorkFlowTestResource('create_vm', rsrc_defns, stack)
|
|
self.mistral.workflows.create.return_value = [
|
|
FakeWorkflow('create_vm')]
|
|
scheduler.TaskRunner(wf.create)()
|
|
details = {'input': {'flavor': '3'}}
|
|
execution = mock.Mock()
|
|
execution.id = '12345'
|
|
# Invoke the real create method (bug 1453539)
|
|
exec_manager = executions.ExecutionManager(wf.client('mistral'))
|
|
self.mistral.executions.create.side_effect = (
|
|
lambda *args, **kw: exec_manager.create(*args, **kw))
|
|
self.patchobject(exec_manager, '_create', return_value=execution)
|
|
scheduler.TaskRunner(wf.signal, details)()
|
|
self.assertEqual({'executions': '12345'}, wf.data())
|
|
scheduler.TaskRunner(wf.delete)()
|
|
self.assertEqual(1, self.mistral.executions.delete.call_count)
|
|
self.assertEqual((wf.DELETE, wf.COMPLETE), wf.state)
|
|
|
|
def test_workflow_params(self):
|
|
tmpl = template_format.parse(workflow_template_full)
|
|
stack = utils.parse_stack(tmpl)
|
|
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
|
wf = MistralWorkFlowTestResource('create_vm', rsrc_defns, stack)
|
|
self.mistral.workflows.create.return_value = [
|
|
FakeWorkflow('create_vm')]
|
|
scheduler.TaskRunner(wf.create)()
|
|
details = {'input': {'flavor': '3'},
|
|
'params': {'test': 'param_value', 'test1': 'param_value_1'}}
|
|
execution = mock.Mock()
|
|
execution.id = '12345'
|
|
self.mistral.executions.create.side_effect = (
|
|
lambda *args, **kw: self.verify_params(*args, **kw))
|
|
scheduler.TaskRunner(wf.signal, details)()
|
|
|
|
def test_workflow_params_merge(self):
|
|
tmpl = template_format.parse(workflow_template_with_params)
|
|
stack = utils.parse_stack(tmpl)
|
|
rsrc_defns = stack.t.resource_definitions(stack)['workflow']
|
|
wf = MistralWorkFlowTestResource('workflow', rsrc_defns, stack)
|
|
self.mistral.workflows.create.return_value = [
|
|
FakeWorkflow('workflow')]
|
|
scheduler.TaskRunner(wf.create)()
|
|
details = {'params': {'test1': 'param_value_1'}}
|
|
execution = mock.Mock()
|
|
execution.id = '12345'
|
|
self.mistral.executions.create.side_effect = (
|
|
lambda *args, **kw: self.verify_params(*args, **kw))
|
|
scheduler.TaskRunner(wf.signal, details)()
|
|
|
|
def test_workflow_params_override(self):
|
|
tmpl = template_format.parse(workflow_template_with_params_override)
|
|
stack = utils.parse_stack(tmpl)
|
|
rsrc_defns = stack.t.resource_definitions(stack)['workflow']
|
|
wf = MistralWorkFlowTestResource('workflow', rsrc_defns, stack)
|
|
self.mistral.workflows.create.return_value = [
|
|
FakeWorkflow('workflow')]
|
|
scheduler.TaskRunner(wf.create)()
|
|
details = {'params': {'test': 'param_value', 'test1': 'param_value_1'}}
|
|
execution = mock.Mock()
|
|
execution.id = '12345'
|
|
self.mistral.executions.create.side_effect = (
|
|
lambda *args, **kw: self.verify_params(*args, **kw))
|
|
scheduler.TaskRunner(wf.signal, details)()
|
|
|
|
def verify_params(self, workflow_name, workflow_input=None, **params):
|
|
self.assertEqual({'test': 'param_value', 'test1': 'param_value_1'},
|
|
params)
|
|
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)
|