diff --git a/etc/heat/policy.json b/etc/heat/policy.json index 62143febff..f95144aacb 100644 --- a/etc/heat/policy.json +++ b/etc/heat/policy.json @@ -8,6 +8,7 @@ "cloudformation:DescribeStacks": "rule:deny_stack_user", "cloudformation:DeleteStack": "rule:deny_stack_user", "cloudformation:UpdateStack": "rule:deny_stack_user", + "cloudformation:CancelUpdateStack": "rule:deny_stack_user", "cloudformation:DescribeStackEvents": "rule:deny_stack_user", "cloudformation:ValidateTemplate": "rule:deny_stack_user", "cloudformation:GetTemplate": "rule:deny_stack_user", diff --git a/heat/api/cfn/v1/__init__.py b/heat/api/cfn/v1/__init__.py index 83582f1529..5ccd6ca3a2 100644 --- a/heat/api/cfn/v1/__init__.py +++ b/heat/api/cfn/v1/__init__.py @@ -31,6 +31,7 @@ class API(wsgi.Router): 'describe': 'DescribeStacks', 'delete': 'DeleteStack', 'update': 'UpdateStack', + 'cancel_update': 'CancelUpdateStack', 'events_list': 'DescribeStackEvents', 'validate_template': 'ValidateTemplate', 'get_template': 'GetTemplate', diff --git a/heat/api/cfn/v1/stacks.py b/heat/api/cfn/v1/stacks.py index bb1b5f6ed8..43c24ac745 100644 --- a/heat/api/cfn/v1/stacks.py +++ b/heat/api/cfn/v1/stacks.py @@ -365,6 +365,20 @@ class StackController(object): return api_utils.format_response(action, response) + def cancel_update(self, req): + action = 'CancelUpdateStack' + self._enforce(req, action) + con = req.context + stack_name = req.params['StackName'] + stack_identity = self._get_identity(con, stack_name) + try: + self.rpc_client.stack_cancel_update( + con, stack_identity=stack_identity) + except Exception as ex: + return exception.map_remote_error(ex) + + return api_utils.format_response(action, {}) + def get_template(self, req): """ Implements the GetTemplate API action. diff --git a/heat/tests/test_api_cfn_v1.py b/heat/tests/test_api_cfn_v1.py index 2a4d98a531..747242a65f 100644 --- a/heat/tests/test_api_cfn_v1.py +++ b/heat/tests/test_api_cfn_v1.py @@ -941,6 +941,40 @@ class CfnStackControllerTest(HeatTestCase): self.assertEqual(expected, response) + def test_cancel_update(self): + # Format a dummy request + stack_name = "wordpress" + params = {'Action': 'CancelUpdateStack', 'StackName': stack_name} + dummy_req = self._dummy_GET_request(params) + self._stub_enforce(dummy_req, 'CancelUpdateStack') + + # Stub out the RPC call to the engine with a pre-canned response + identity = dict(identifier.HeatIdentifier('t', stack_name, '1')) + + self.m.StubOutWithMock(rpc_client.EngineClient, 'call') + rpc_client.EngineClient.call( + dummy_req.context, + ('identify_stack', {'stack_name': stack_name}) + ).AndReturn(identity) + + rpc_client.EngineClient.call( + dummy_req.context, + ('stack_cancel_update', + {'stack_identity': identity}) + ).AndReturn(identity) + + self.m.ReplayAll() + + response = self.controller.cancel_update(dummy_req) + + expected = { + 'CancelUpdateStackResponse': { + 'CancelUpdateStackResult': {} + } + } + + self.assertEqual(response, expected) + def test_update_bad_name(self): stack_name = "wibble" json_template = json.dumps(self.template)