err_type = "Server"
+class HeatInvalidStateError(HeatAPIException):
+ '''
+ Cannot perform action on stack in its current state
+ '''
+ code = 400
+ title = 'InvalidAction'
+ explanation = "Cannot perform action on stack in its current state"
+
+
def map_remote_error(ex):
"""
Map rpc_common.RemoteError exceptions returned by the engine
'UserParameterMissing',
)
denied_errors = ('Forbidden', 'NotAuthorized')
- already_exists_errors = ('StackExists')
+ already_exists_errors = ('StackExists',)
+ invalid_state_errors = ('ActionInProgress',)
if ex.exc_type in inval_param_errors:
return HeatInvalidParameterValueError(detail=ex.value)
return HeatAccessDeniedError(detail=ex.value)
elif ex.exc_type in already_exists_errors:
return AlreadyExistsError(detail=ex.value)
+ elif ex.exc_type in invalid_state_errors:
+ return HeatInvalidStateError(detail=ex.value)
else:
# Map everything else to internal server error for now
return HeatInternalFailureError(detail=ex.value)
error_map = {
'AttributeError': webob.exc.HTTPBadRequest,
+ 'ActionInProgress': webob.exc.HTTPConflict,
'ValueError': webob.exc.HTTPBadRequest,
'StackNotFound': webob.exc.HTTPNotFound,
'ResourceNotFound': webob.exc.HTTPNotFound,
message = _("The Watch Rule (%(watch_name)s) could not be found.")
+class ActionInProgress(OpenstackException):
+ message = _("Stack %(stack_name)s already has an action (%(action)s) "
+ "in progress")
+
+
class ResourceFailure(OpenstackException):
message = _("%(exc_type)s: %(message)s")
# Get the database representation of the existing stack
db_stack = self._get_stack(cnxt, stack_identity)
+ if db_stack.status != parser.Stack.COMPLETE:
+ raise exception.ActionInProgress(stack_name=db_stack.name,
+ action=db_stack.action)
+
current_stack = parser.Stack.load(cnxt, stack=db_stack)
# Now parse the template and any parameters for the updated
"""
st = self._get_stack(cnxt, stack_identity)
- logger.info('deleting stack %s' % st.name)
+ if st.status not in (parser.Stack.COMPLETE, parser.Stack.FAILED):
+ raise exception.ActionInProgress(stack_name=st.name,
+ action=st.action)
+ logger.info('deleting stack %s' % st.name)
stack = parser.Stack.load(cnxt, stack=st)
# Kill any pending threads by calling ThreadGroup.stop()
exception.AlreadyExistsError)
self.m.VerifyAll()
+ def test_invalid_state_err(self):
+ '''
+ Test that an ActionInProgress exception results in a
+ HeatInvalidStateError.
+
+ '''
+ # Format a dummy request
+ stack_name = "wordpress"
+ template = {u'Foo': u'bar'}
+ json_template = json.dumps(template)
+ params = {'Action': 'CreateStack', 'StackName': stack_name,
+ 'TemplateBody': '%s' % json_template,
+ 'TimeoutInMinutes': 30,
+ 'Parameters.member.1.ParameterKey': 'InstanceType',
+ 'Parameters.member.1.ParameterValue': 'm1.xlarge'}
+ engine_parms = {u'InstanceType': u'm1.xlarge'}
+ engine_args = {'timeout_mins': u'30'}
+ dummy_req = self._dummy_GET_request(params)
+
+ # Insert an engine RPC error and ensure we map correctly to the
+ # heat exception type
+ self.m.StubOutWithMock(rpc, 'call')
+
+ rpc.call(dummy_req.context, self.topic,
+ {'namespace': None,
+ 'method': 'create_stack',
+ 'args': {'stack_name': stack_name,
+ 'template': template,
+ 'params': engine_parms,
+ 'files': {},
+ 'args': engine_args},
+ 'version': self.api_version}, None
+ ).AndRaise(rpc_common.RemoteError("ActionInProgress"))
+
+ self.m.ReplayAll()
+
+ result = self.controller.create(dummy_req)
+
+ self.assertEqual(type(result),
+ exception.HeatInvalidStateError)
+ self.m.VerifyAll()
+
def test_create_err_engine(self):
# Format a dummy request
stack_name = "wordpress"
body=body)
self.m.VerifyAll()
+ def test_update_in_progress_err(self):
+ '''
+ Tests that the ActionInProgress exception results in an HTTPConflict.
+
+ '''
+ identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6')
+ template = {u'Foo': u'bar'}
+ parameters = {u'InstanceType': u'm1.xlarge'}
+ body = {'template': template,
+ 'parameters': parameters,
+ 'files': {},
+ 'timeout_mins': 30}
+
+ req = self._put('/stacks/%(stack_name)s/%(stack_id)s' % identity,
+ json.dumps(body))
+
+ self.m.StubOutWithMock(rpc, 'call')
+ error = to_remote_error(heat_exc.ActionInProgress(stack_name="foo",
+ action="UPDATE"))
+ rpc.call(req.context, self.topic,
+ {'namespace': None,
+ 'method': 'update_stack',
+ 'args': {'stack_identity': dict(identity),
+ 'template': template,
+ 'params': {'parameters': parameters},
+ 'files': {},
+ 'args': {'timeout_mins': 30}},
+ 'version': self.api_version},
+ None).AndRaise(error)
+ self.m.ReplayAll()
+
+ resp = request_with_middleware(fault.FaultWrapper,
+ self.controller.update,
+ req, tenant_id=identity.tenant,
+ stack_name=identity.stack_name,
+ stack_id=identity.stack_id,
+ body=body)
+ self.assertEqual(resp.json['code'], 409)
+ self.assertEqual(resp.json['title'], 'Conflict')
+ self.assertEqual(resp.json['error']['type'], 'ActionInProgress')
+ self.m.VerifyAll()
+
def test_update_bad_name(self):
identity = identifier.HeatIdentifier(self.tenant, 'wibble', '6')
template = {u'Foo': u'bar'}
def test_stack_delete(self):
stack_name = 'service_delete_test_stack'
stack = get_wordpress_stack(stack_name, self.ctx)
- sid = stack.store()
-
- s = db_api.stack_get(self.ctx, sid)
- self.m.StubOutWithMock(parser.Stack, 'load')
-
- parser.Stack.load(self.ctx, stack=s).AndReturn(stack)
+ stack.status = "COMPLETE"
+ stack.store()
self.man.tg = DummyThreadGroup()
self.man.delete_stack(self.ctx, stack.identifier()))
self.m.VerifyAll()
+ def test_stack_delete_action_in_progress_err(self):
+ '''
+ Test that deleting a stack with an action in progress results in
+ an ActionInProgress exception.
+
+ '''
+ stack_name = 'service_delete_action_in_progress_err'
+ stack = get_wordpress_stack(stack_name, self.ctx)
+ stack.status = "IN_PROGRESS"
+ stack.store()
+
+ self.assertRaises(exception.ActionInProgress,
+ self.man.delete_stack,
+ self.ctx,
+ stack.identifier())
+
def test_stack_delete_nonexist(self):
stack_name = 'service_delete_nonexist_test_stack'
stack = get_wordpress_stack(stack_name, self.ctx)
- self.m.ReplayAll()
-
self.assertRaises(exception.StackNotFound,
self.man.delete_stack,
self.ctx, stack.identifier())
- self.m.VerifyAll()
def test_stack_update(self):
stack_name = 'service_update_test_stack'
template = '{ "Template": "data" }'
old_stack = get_wordpress_stack(stack_name, self.ctx)
+ old_stack.status = 'COMPLETE'
sid = old_stack.store()
s = db_api.stack_get(self.ctx, sid)
self.assertTrue(result['stack_id'])
self.m.VerifyAll()
+ def test_stack_update_action_in_progress_err(self):
+ '''
+ Test that updating a stack with an action in progress results
+ in an ActionInProgress exception.
+
+ '''
+ stack_name = 'service_update_action_in_progress_err_test_stack'
+ params = {'foo': 'bar'}
+ template = '{ "Template": "data" }'
+
+ old_stack = get_wordpress_stack(stack_name, self.ctx)
+ old_stack.status = 'IN_PROGRESS'
+ old_stack.store()
+
+ self.assertRaises(
+ exception.ActionInProgress,
+ self.man.update_stack,
+ self.ctx, old_stack.identifier(),
+ template, params, None, {})
+
def test_stack_update_verify_err(self):
stack_name = 'service_update_verify_err_test_stack'
params = {'foo': 'bar'}
template = '{ "Template": "data" }'
old_stack = get_wordpress_stack(stack_name, self.ctx)
+ old_stack.status = 'COMPLETE'
old_stack.store()
sid = old_stack.store()
s = db_api.stack_get(self.ctx, sid)