Merge "Handle OS::Mistral::Workflow resource replacement properly"

This commit is contained in:
Zuul 2020-02-25 18:07:47 +00:00 committed by Gerrit Code Review
commit b377a80ad5
3 changed files with 87 additions and 21 deletions

View File

@ -159,6 +159,9 @@ class Resource(status.ResourceStatus):
# a signal to this resource # a signal to this resource
signal_needs_metadata_updates = True signal_needs_metadata_updates = True
# Whether the resource is always replaced when CHECK_FAILED
always_replace_on_check_failed = True
def __new__(cls, name, definition, stack): def __new__(cls, name, definition, stack):
"""Create a new Resource of the appropriate class for its type.""" """Create a new Resource of the appropriate class for its type."""
@ -1387,7 +1390,9 @@ class Resource(status.ResourceStatus):
prev_resource, check_init_complete=True): prev_resource, check_init_complete=True):
if self.status == self.FAILED: if self.status == self.FAILED:
# always replace when a resource is in CHECK_FAILED # always replace when a resource is in CHECK_FAILED
if self.action == self.CHECK or self.needs_replace_failed(): if ((self.action == self.CHECK and
self.always_replace_on_check_failed) or (
self.needs_replace_failed())):
raise UpdateReplace(self) raise UpdateReplace(self)
if self.state == (self.DELETE, self.COMPLETE): if self.state == (self.DELETE, self.COMPLETE):

View File

@ -46,6 +46,8 @@ class Workflow(signal_responder.SignalResponder,
entity = 'workflows' entity = 'workflows'
always_replace_on_check_failed = False
PROPERTIES = ( PROPERTIES = (
NAME, TYPE, DESCRIPTION, INPUT, OUTPUT, TASKS, PARAMS, NAME, TYPE, DESCRIPTION, INPUT, OUTPUT, TASKS, PARAMS,
TASK_DEFAULTS, USE_REQUEST_BODY_AS_INPUT, TAGS TASK_DEFAULTS, USE_REQUEST_BODY_AS_INPUT, TAGS
@ -596,6 +598,19 @@ class Workflow(signal_responder.SignalResponder,
executions.extend(self.data().get(self.EXECUTIONS).split(',')) executions.extend(self.data().get(self.EXECUTIONS).split(','))
self.data_set(self.EXECUTIONS, ','.join(executions)) self.data_set(self.EXECUTIONS, ','.join(executions))
def needs_replace_failed(self):
if self.resource_id is None:
return True
if self.properties[self.NAME] is None:
return True
with self.client_plugin().ignore_not_found:
self.client().workflows.get(self.resource_id)
return False
self.resource_id_set(None)
return True
def handle_update(self, json_snippet, tmpl_diff, prop_diff): def handle_update(self, json_snippet, tmpl_diff, prop_diff):
if prop_diff: if prop_diff:
props = json_snippet.properties(self.properties_schema, props = json_snippet.properties(self.properties_schema,

View File

@ -15,6 +15,7 @@ import mock
import six import six
import yaml import yaml
from mistralclient.api import base as mistral_base
from mistralclient.api.v2 import executions from mistralclient.api.v2 import executions
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
@ -302,6 +303,21 @@ resources:
result: <% $.hello %> result: <% $.hello %>
""" """
workflow_template_update_replace_failed = """
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 Morning!'
publish:
result: <% $.hello %>
"""
workflow_template_update = """ workflow_template_update = """
heat_template_version: 2013-05-23 heat_template_version: 2013-05-23
resources: resources:
@ -373,12 +389,6 @@ class TestMistralWorkflow(common.HeatTestCase):
def setUp(self): def setUp(self):
super(TestMistralWorkflow, self).setUp() super(TestMistralWorkflow, self).setUp()
self.ctx = utils.dummy_context() 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.mistral = mock.Mock()
self.patchobject(workflow.Workflow, 'client', self.patchobject(workflow.Workflow, 'client',
return_value=self.mistral) return_value=self.mistral)
@ -400,15 +410,20 @@ class TestMistralWorkflow(common.HeatTestCase):
for patch in self.patches: for patch in self.patches:
patch.stop() patch.stop()
def _create_resource(self, name, snippet, stack): def _create_resource(self, name, template=workflow_template):
wf = workflow.Workflow(name, snippet, stack) tmpl = template_format.parse(template)
self.stack = utils.parse_stack(tmpl, stack_name='test_stack')
resource_defns = self.stack.t.resource_definitions(self.stack)
rsrc_defn = resource_defns['workflow']
wf = workflow.Workflow(name, rsrc_defn, self.stack)
self.mistral.workflows.create.return_value = [ self.mistral.workflows.create.return_value = [
FakeWorkflow('test_stack-workflow-b5fiekfci3yc')] FakeWorkflow('test_stack-workflow-b5fiekfci3yc')]
scheduler.TaskRunner(wf.create)() scheduler.TaskRunner(wf.create)()
return wf return wf
def test_create(self): def test_create(self):
wf = self._create_resource('workflow', self.rsrc_defn, self.stack) wf = self._create_resource('workflow')
expected_state = (wf.CREATE, wf.COMPLETE) expected_state = (wf.CREATE, wf.COMPLETE)
self.assertEqual(expected_state, wf.state) self.assertEqual(expected_state, wf.state)
self.assertEqual('test_stack-workflow-b5fiekfci3yc', wf.resource_id) self.assertEqual('test_stack-workflow-b5fiekfci3yc', wf.resource_id)
@ -460,7 +475,7 @@ class TestMistralWorkflow(common.HeatTestCase):
break break
def test_attributes(self): def test_attributes(self):
wf = self._create_resource('workflow', self.rsrc_defn, self.stack) wf = self._create_resource('workflow')
self.mistral.workflows.get.return_value = ( self.mistral.workflows.get.return_value = (
FakeWorkflow('test_stack-workflow-b5fiekfci3yc')) FakeWorkflow('test_stack-workflow-b5fiekfci3yc'))
self.assertEqual({'name': 'test_stack-workflow-b5fiekfci3yc', self.assertEqual({'name': 'test_stack-workflow-b5fiekfci3yc',
@ -521,7 +536,7 @@ class TestMistralWorkflow(common.HeatTestCase):
six.text_type(exc)) six.text_type(exc))
def test_update_replace(self): def test_update_replace(self):
wf = self._create_resource('workflow', self.rsrc_defn, self.stack) wf = self._create_resource('workflow')
t = template_format.parse(workflow_template_update_replace) t = template_format.parse(workflow_template_update_replace)
rsrc_defns = template.Template(t).resource_definitions(self.stack) rsrc_defns = template.Template(t).resource_definitions(self.stack)
@ -538,8 +553,7 @@ class TestMistralWorkflow(common.HeatTestCase):
self.assertEqual(msg, six.text_type(err)) self.assertEqual(msg, six.text_type(err))
def test_update(self): def test_update(self):
wf = self._create_resource('workflow', self.rsrc_defn, wf = self._create_resource('workflow')
self.stack)
t = template_format.parse(workflow_template_update) t = template_format.parse(workflow_template_update)
rsrc_defns = template.Template(t).resource_definitions(self.stack) rsrc_defns = template.Template(t).resource_definitions(self.stack)
new_wf = rsrc_defns['workflow'] new_wf = rsrc_defns['workflow']
@ -550,8 +564,7 @@ class TestMistralWorkflow(common.HeatTestCase):
self.assertEqual((wf.UPDATE, wf.COMPLETE), wf.state) self.assertEqual((wf.UPDATE, wf.COMPLETE), wf.state)
def test_update_input(self): def test_update_input(self):
wf = self._create_resource('workflow', self.rsrc_defn, wf = self._create_resource('workflow')
self.stack)
t = template_format.parse(workflow_template) t = template_format.parse(workflow_template)
t['resources']['workflow']['properties']['input'] = {'foo': 'bar'} t['resources']['workflow']['properties']['input'] = {'foo': 'bar'}
rsrc_defns = template.Template(t).resource_definitions(self.stack) rsrc_defns = template.Template(t).resource_definitions(self.stack)
@ -563,8 +576,7 @@ class TestMistralWorkflow(common.HeatTestCase):
self.assertEqual((wf.UPDATE, wf.COMPLETE), wf.state) self.assertEqual((wf.UPDATE, wf.COMPLETE), wf.state)
def test_update_failed(self): def test_update_failed(self):
wf = self._create_resource('workflow', self.rsrc_defn, wf = self._create_resource('workflow')
self.stack)
t = template_format.parse(workflow_template_update) t = template_format.parse(workflow_template_update)
rsrc_defns = template.Template(t).resource_definitions(self.stack) rsrc_defns = template.Template(t).resource_definitions(self.stack)
new_wf = rsrc_defns['workflow'] new_wf = rsrc_defns['workflow']
@ -573,8 +585,42 @@ class TestMistralWorkflow(common.HeatTestCase):
scheduler.TaskRunner(wf.update, new_wf)) scheduler.TaskRunner(wf.update, new_wf))
self.assertEqual((wf.UPDATE, wf.FAILED), wf.state) self.assertEqual((wf.UPDATE, wf.FAILED), wf.state)
def test_update_failed_no_replace(self):
wf = self._create_resource('workflow',
workflow_template_update_replace)
t = template_format.parse(workflow_template_update_replace_failed)
rsrc_defns = template.Template(t).resource_definitions(self.stack)
new_wf = rsrc_defns['workflow']
self.mistral.workflows.get.return_value = (
FakeWorkflow('test_stack-workflow-b5fiekfci3yc'))
self.mistral.workflows.update.side_effect = [
Exception('boom!'),
[FakeWorkflow('test_stack-workflow-b5fiekfci3yc')]]
self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(wf.update, new_wf))
self.assertEqual((wf.UPDATE, wf.FAILED), wf.state)
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_replace_not_found(self):
wf = self._create_resource('workflow',
workflow_template_update_replace)
t = template_format.parse(workflow_template_update_replace_failed)
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)
self.mistral.workflows.get.side_effect = [
mistral_base.APIException(error_code=404)]
self.assertRaises(resource.UpdateReplace,
scheduler.TaskRunner(wf.update,
new_wf))
def test_delete_super_call_successful(self): def test_delete_super_call_successful(self):
wf = self._create_resource('workflow', self.rsrc_defn, self.stack) wf = self._create_resource('workflow')
scheduler.TaskRunner(wf.delete)() scheduler.TaskRunner(wf.delete)()
self.assertEqual((wf.DELETE, wf.COMPLETE), wf.state) self.assertEqual((wf.DELETE, wf.COMPLETE), wf.state)
@ -582,7 +628,7 @@ class TestMistralWorkflow(common.HeatTestCase):
self.assertEqual(1, self.mistral.workflows.delete.call_count) self.assertEqual(1, self.mistral.workflows.delete.call_count)
def test_delete_executions_successful(self): def test_delete_executions_successful(self):
wf = self._create_resource('workflow', self.rsrc_defn, self.stack) wf = self._create_resource('workflow')
self.mistral.executuions.delete.return_value = None self.mistral.executuions.delete.return_value = None
wf._data = {'executions': '1234,5678'} wf._data = {'executions': '1234,5678'}
@ -594,7 +640,7 @@ class TestMistralWorkflow(common.HeatTestCase):
data_delete.assert_called_once_with('executions') data_delete.assert_called_once_with('executions')
def test_delete_executions_not_found(self): def test_delete_executions_not_found(self):
wf = self._create_resource('workflow', self.rsrc_defn, self.stack) wf = self._create_resource('workflow')
self.mistral.executuions.delete.side_effect = [ self.mistral.executuions.delete.side_effect = [
self.mistral.mistral_base.APIException(error_code=404), self.mistral.mistral_base.APIException(error_code=404),