Implement an "Action in progress" error.
Raises an "Action in progress" error if an action is in progress when another action is issued. Fixes bug #1207032 Change-Id: I2b6fd92cfa4b93ac2531dd3d76bf2dfc01e58c50
This commit is contained in:
parent
05e421b03a
commit
faf984cbb5
@ -246,6 +246,15 @@ class HeatAPINotImplementedError(HeatAPIException):
|
||||
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
|
||||
@ -268,7 +277,8 @@ def map_remote_error(ex):
|
||||
'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)
|
||||
@ -276,6 +286,8 @@ def map_remote_error(ex):
|
||||
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)
|
||||
|
@ -55,6 +55,7 @@ class FaultWrapper(wsgi.Middleware):
|
||||
|
||||
error_map = {
|
||||
'AttributeError': webob.exc.HTTPBadRequest,
|
||||
'ActionInProgress': webob.exc.HTTPConflict,
|
||||
'ValueError': webob.exc.HTTPBadRequest,
|
||||
'StackNotFound': webob.exc.HTTPNotFound,
|
||||
'ResourceNotFound': webob.exc.HTTPNotFound,
|
||||
|
@ -263,6 +263,11 @@ class WatchRuleNotFound(OpenstackException):
|
||||
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")
|
||||
|
||||
|
@ -277,6 +277,10 @@ class EngineService(service.Service):
|
||||
# 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
|
||||
@ -378,8 +382,11 @@ class EngineService(service.Service):
|
||||
"""
|
||||
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()
|
||||
|
@ -821,6 +821,48 @@ class CfnStackControllerTest(HeatTestCase):
|
||||
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"
|
||||
|
@ -957,6 +957,48 @@ class StackControllerTest(ControllerTest, HeatTestCase):
|
||||
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'}
|
||||
|
@ -413,12 +413,8 @@ class StackServiceCreateUpdateDeleteTest(HeatTestCase):
|
||||
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()
|
||||
|
||||
@ -428,16 +424,29 @@ class StackServiceCreateUpdateDeleteTest(HeatTestCase):
|
||||
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'
|
||||
@ -445,6 +454,7 @@ class StackServiceCreateUpdateDeleteTest(HeatTestCase):
|
||||
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)
|
||||
|
||||
@ -477,12 +487,33 @@ class StackServiceCreateUpdateDeleteTest(HeatTestCase):
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user