Implement update_stack

This would definitely be desirable for shade users. Specifically an
ansible module should to a update_stack if the stack already exists. If
there are no changes the operation would be idempotent.

Change-Id: I833027883a4eed11f4568760d44ffec87889021f
This commit is contained in:
Steve Baker 2016-04-04 09:04:27 +12:00
parent 395cf48cbf
commit 40b30a6178
5 changed files with 137 additions and 0 deletions

View File

@ -0,0 +1,4 @@
---
features:
- Implement update_stack to perform the update action on existing
orchestration stacks.

View File

@ -764,6 +764,11 @@ class StackCreate(task_manager.Task):
return client.heat_client.stacks.create(**self.args)
class StackUpdate(task_manager.Task):
def main(self, client):
return client.heat_client.stacks.update(**self.args)
class StackDelete(task_manager.Task):
def main(self, client):
return client.heat_client.stacks.delete(self.args['id'])

View File

@ -877,6 +877,71 @@ class OpenStackCloud(object):
action='CREATE')
return self.get_stack(name)
def update_stack(
self, name_or_id,
template_file=None, template_url=None,
template_object=None, files=None,
rollback=True,
wait=False, timeout=3600,
environment_files=None,
**parameters):
"""Update a Heat Stack.
:param string name_or_id: Name or id of the stack to update.
:param string template_file: Path to the template.
:param string template_url: URL of template.
:param string template_object: URL to retrieve template object.
:param dict files: dict of additional file content to include.
:param boolean rollback: Enable rollback on update failure.
:param boolean wait: Whether to wait for the delete to finish.
:param int timeout: Stack update timeout in seconds.
:param list environment_files: Paths to environment files to apply.
Other arguments will be passed as stack parameters which will take
precedence over any parameters specified in the environments.
Only one of template_file, template_url, template_object should be
specified.
:returns: a dict containing the stack description
:raises: ``OpenStackCloudException`` if something goes wrong during
the openstack API calls
"""
envfiles, env = template_utils.process_multiple_environments_and_files(
env_paths=environment_files)
tpl_files, template = template_utils.get_template_contents(
template_file=template_file,
template_url=template_url,
template_object=template_object,
files=files)
params = dict(
stack_id=name_or_id,
disable_rollback=not rollback,
parameters=parameters,
template=template,
files=dict(list(tpl_files.items()) + list(envfiles.items())),
environment=env,
timeout_mins=timeout // 60,
)
if wait:
# find the last event to use as the marker
events = event_utils.get_events(self.heat_client,
name_or_id,
event_args={'sort_dir': 'desc',
'limit': 1})
marker = events[0].id if events else None
with _utils.heat_exceptions("Error updating stack {name}".format(
name=name_or_id)):
self.manager.submitTask(_tasks.StackUpdate(**params))
if wait:
event_utils.poll_for_events(self.heat_client,
name_or_id,
action='UPDATE',
marker=marker)
return self.get_stack(name_or_id)
def delete_stack(self, name_or_id):
"""Delete a Heat Stack

View File

@ -120,6 +120,29 @@ class TestStack(base.TestCase):
stack_ids = [s['id'] for s in stacks]
self.assertIn(stack['id'], stack_ids)
# update with no changes
stack = self.cloud.update_stack(self.stack_name,
template_file=test_template.name,
wait=True)
# assert no change in updated stack
self.assertEqual('UPDATE_COMPLETE', stack['stack_status'])
rand = stack['outputs'][0]['output_value']
self.assertEqual(rand, stack['outputs'][0]['output_value'])
# update with changes
stack = self.cloud.update_stack(self.stack_name,
template_file=test_template.name,
wait=True,
length=12)
# assert changed output in updated stack
stack = self.cloud.get_stack(self.stack_name)
self.assertEqual('UPDATE_COMPLETE', stack['stack_status'])
new_rand = stack['outputs'][0]['output_value']
self.assertNotEqual(rand, new_rand)
self.assertEqual(12, len(new_rand))
def test_stack_nested(self):
test_template = tempfile.NamedTemporaryFile(

View File

@ -148,6 +148,46 @@ class TestStack(base.TestCase):
self.assertEqual(1, mock_poll.call_count)
self.assertEqual(stack, ret)
@mock.patch.object(template_utils, 'get_template_contents')
@mock.patch.object(shade.OpenStackCloud, 'heat_client')
def test_update_stack(self, mock_heat, mock_template):
mock_template.return_value = ({}, {})
self.cloud.update_stack('stack_name')
self.assertTrue(mock_template.called)
mock_heat.stacks.update.assert_called_once_with(
stack_id='stack_name',
disable_rollback=False,
environment={},
parameters={},
template={},
files={},
timeout_mins=60,
)
@mock.patch.object(event_utils, 'poll_for_events')
@mock.patch.object(template_utils, 'get_template_contents')
@mock.patch.object(shade.OpenStackCloud, 'get_stack')
@mock.patch.object(shade.OpenStackCloud, 'heat_client')
def test_update_stack_wait(self, mock_heat, mock_get, mock_template,
mock_poll):
stack = {'id': 'stack_id', 'name': 'stack_name'}
mock_template.return_value = ({}, {})
mock_get.return_value = stack
ret = self.cloud.update_stack('stack_name', wait=True)
self.assertTrue(mock_template.called)
mock_heat.stacks.update.assert_called_once_with(
stack_id='stack_name',
disable_rollback=False,
environment={},
parameters={},
template={},
files={},
timeout_mins=60,
)
self.assertEqual(1, mock_get.call_count)
self.assertEqual(1, mock_poll.call_count)
self.assertEqual(stack, ret)
@mock.patch.object(shade.OpenStackCloud, 'heat_client')
def test_get_stack(self, mock_heat):
stack = fakes.FakeStack('azerty', 'stack',)