Merge "heat engine changes for abandon-stack"
This commit is contained in:
commit
fd23550b3b
@ -663,6 +663,21 @@ class Stack(collections.Mapping):
|
|||||||
self.clients.nova().availability_zones.list(detailed=False)]
|
self.clients.nova().availability_zones.list(detailed=False)]
|
||||||
return self._zones
|
return self._zones
|
||||||
|
|
||||||
|
def set_deletion_policy(self, policy):
|
||||||
|
for res in self.resources.values():
|
||||||
|
res.set_deletion_policy(policy)
|
||||||
|
|
||||||
|
def get_abandon_data(self):
|
||||||
|
return {
|
||||||
|
'name': self.name,
|
||||||
|
'id': self.id,
|
||||||
|
'action': self.action,
|
||||||
|
'status': self.status,
|
||||||
|
'template': self.t.t,
|
||||||
|
'resources': dict((res.name, res.get_abandon_data())
|
||||||
|
for res in self.resources.values())
|
||||||
|
}
|
||||||
|
|
||||||
def resolve_static_data(self, snippet):
|
def resolve_static_data(self, snippet):
|
||||||
return resolve_static_data(self.t, self, self.parameters, snippet)
|
return resolve_static_data(self.t, self, self.parameters, snippet)
|
||||||
|
|
||||||
|
@ -445,6 +445,21 @@ class Resource(object):
|
|||||||
self.name)
|
self.name)
|
||||||
return self._do_action(action, self.properties.validate)
|
return self._do_action(action, self.properties.validate)
|
||||||
|
|
||||||
|
def set_deletion_policy(self, policy):
|
||||||
|
self.t['DeletionPolicy'] = policy
|
||||||
|
|
||||||
|
def get_abandon_data(self):
|
||||||
|
return {
|
||||||
|
'name': self.name,
|
||||||
|
'resource_id': self.resource_id,
|
||||||
|
'type': self.type(),
|
||||||
|
'action': self.action,
|
||||||
|
'status': self.status,
|
||||||
|
'metadata': self.metadata,
|
||||||
|
'resource_data': dict((r.key, r.value)
|
||||||
|
for r in db_api.resource_data_get_all(self))
|
||||||
|
}
|
||||||
|
|
||||||
def update(self, after, before=None):
|
def update(self, after, before=None):
|
||||||
'''
|
'''
|
||||||
update the resource. Subclasses should provide a handle_update() method
|
update the resource. Subclasses should provide a handle_update() method
|
||||||
|
@ -459,13 +459,33 @@ class EngineService(service.Service):
|
|||||||
|
|
||||||
stack = parser.Stack.load(cnxt, stack=st)
|
stack = parser.Stack.load(cnxt, stack=st)
|
||||||
|
|
||||||
|
self._delete_stack_on_thread(st, stack)
|
||||||
|
|
||||||
|
def _delete_stack_on_thread(self, st, stack):
|
||||||
# Kill any pending threads by calling ThreadGroup.stop()
|
# Kill any pending threads by calling ThreadGroup.stop()
|
||||||
if st.id in self.stg:
|
if st.id in self.stg:
|
||||||
self.stg[st.id].stop()
|
self.stg[st.id].stop()
|
||||||
del self.stg[st.id]
|
del self.stg[st.id]
|
||||||
# use the service ThreadGroup for deletes
|
# use the service ThreadGroup for deletes
|
||||||
self.tg.add_thread(stack.delete)
|
self.tg.add_thread(stack.delete)
|
||||||
return None
|
|
||||||
|
@request_context
|
||||||
|
def abandon_stack(self, cnxt, stack_identity):
|
||||||
|
"""
|
||||||
|
The abandon_stack method abandons a given stack.
|
||||||
|
:param cnxt: RPC context.
|
||||||
|
:param stack_identity: Name of the stack you want to abandon.
|
||||||
|
"""
|
||||||
|
st = self._get_stack(cnxt, stack_identity)
|
||||||
|
logger.info(_('abandoning stack %s') % st.name)
|
||||||
|
stack = parser.Stack.load(cnxt, stack=st)
|
||||||
|
|
||||||
|
# Get stack details before deleting it.
|
||||||
|
stack_info = stack.get_abandon_data()
|
||||||
|
# Set deletion policy to 'Retain' for all resources in the stack.
|
||||||
|
stack.set_deletion_policy(resource.RETAIN)
|
||||||
|
self._delete_stack_on_thread(st, stack)
|
||||||
|
return stack_info
|
||||||
|
|
||||||
def list_resource_types(self, cnxt):
|
def list_resource_types(self, cnxt):
|
||||||
"""
|
"""
|
||||||
|
@ -246,6 +246,12 @@ class StackResource(resource.Resource):
|
|||||||
|
|
||||||
return done
|
return done
|
||||||
|
|
||||||
|
def set_deletion_policy(self, policy):
|
||||||
|
self.nested().set_deletion_policy(policy)
|
||||||
|
|
||||||
|
def get_abandon_data(self):
|
||||||
|
return self.nested().get_abandon_data()
|
||||||
|
|
||||||
def get_output(self, op):
|
def get_output(self, op):
|
||||||
'''
|
'''
|
||||||
Return the specified Output value from the nested stack.
|
Return the specified Output value from the nested stack.
|
||||||
|
@ -173,6 +173,18 @@ class EngineClient(heat.openstack.common.rpc.proxy.RpcProxy):
|
|||||||
self.make_msg('delete_stack',
|
self.make_msg('delete_stack',
|
||||||
stack_identity=stack_identity))
|
stack_identity=stack_identity))
|
||||||
|
|
||||||
|
def abandon_stack(self, ctxt, stack_identity):
|
||||||
|
"""
|
||||||
|
The abandon_stack method deletes a given stack but
|
||||||
|
resources would not be deleted.
|
||||||
|
|
||||||
|
:param ctxt: RPC context.
|
||||||
|
:param stack_identity: Name of the stack you want to abandon.
|
||||||
|
"""
|
||||||
|
return self.call(ctxt,
|
||||||
|
self.make_msg('abandon_stack',
|
||||||
|
stack_identity=stack_identity))
|
||||||
|
|
||||||
def list_resource_types(self, ctxt):
|
def list_resource_types(self, ctxt):
|
||||||
"""
|
"""
|
||||||
Get a list of valid resource types.
|
Get a list of valid resource types.
|
||||||
|
@ -1154,6 +1154,31 @@ class StackServiceTest(HeatTestCase):
|
|||||||
filters
|
filters
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@stack_context('service_abandon_stack')
|
||||||
|
def test_abandon_stack(self):
|
||||||
|
self.m.StubOutWithMock(parser.Stack, 'load')
|
||||||
|
parser.Stack.load(self.ctx,
|
||||||
|
stack=mox.IgnoreArg()).AndReturn(self.stack)
|
||||||
|
expected_res = {
|
||||||
|
u'WebServer': {
|
||||||
|
'action': 'CREATE',
|
||||||
|
'metadata': {},
|
||||||
|
'name': u'WebServer',
|
||||||
|
'resource_data': {},
|
||||||
|
'resource_id': 9999,
|
||||||
|
'status': 'COMPLETE',
|
||||||
|
'type': u'AWS::EC2::Instance'}}
|
||||||
|
self.m.ReplayAll()
|
||||||
|
ret = self.eng.abandon_stack(self.ctx, self.stack.identifier())
|
||||||
|
self.assertEqual(6, len(ret))
|
||||||
|
self.assertEqual('CREATE', ret['action'])
|
||||||
|
self.assertEqual('COMPLETE', ret['status'])
|
||||||
|
self.assertEqual('service_abandon_stack', ret['name'])
|
||||||
|
self.assertTrue('id' in ret)
|
||||||
|
self.assertEqual(expected_res, ret['resources'])
|
||||||
|
self.assertEqual(self.stack.t.t, ret['template'])
|
||||||
|
self.m.VerifyAll()
|
||||||
|
|
||||||
def test_stack_describe_nonexistent(self):
|
def test_stack_describe_nonexistent(self):
|
||||||
non_exist_identifier = identifier.HeatIdentifier(
|
non_exist_identifier = identifier.HeatIdentifier(
|
||||||
self.ctx.tenant_id, 'wibble',
|
self.ctx.tenant_id, 'wibble',
|
||||||
|
@ -766,6 +766,49 @@ class StackTest(HeatTestCase):
|
|||||||
self.assertTrue(identifier.stack_id)
|
self.assertTrue(identifier.stack_id)
|
||||||
self.assertFalse(identifier.path)
|
self.assertFalse(identifier.path)
|
||||||
|
|
||||||
|
@utils.stack_delete_after
|
||||||
|
def test_get_stack_abandon_data(self):
|
||||||
|
tpl = {'Resources':
|
||||||
|
{'A': {'Type': 'GenericResourceType'},
|
||||||
|
'B': {'Type': 'GenericResourceType'}}}
|
||||||
|
resources = '''{'A': {'status': 'COMPLETE', 'name': 'A',
|
||||||
|
'resource_data': {}, 'resource_id': None, 'action': 'INIT',
|
||||||
|
'type': 'GenericResourceType', 'metadata': {}},
|
||||||
|
'B': {'status': 'COMPLETE', 'name': 'B', 'resource_data': {},
|
||||||
|
'resource_id': None, 'action': 'INIT', 'type': 'GenericResourceType',
|
||||||
|
'metadata': {}}}'''
|
||||||
|
self.stack = parser.Stack(self.ctx, 'stack_details_test',
|
||||||
|
parser.Template(tpl))
|
||||||
|
self.stack.store()
|
||||||
|
info = self.stack.get_abandon_data()
|
||||||
|
self.assertEqual(None, info['action'])
|
||||||
|
self.assertTrue('id' in info)
|
||||||
|
self.assertEqual('stack_details_test', info['name'])
|
||||||
|
self.assertTrue(resources, info['resources'])
|
||||||
|
self.assertEqual(None, info['status'])
|
||||||
|
self.assertEqual(tpl, info['template'])
|
||||||
|
|
||||||
|
@utils.stack_delete_after
|
||||||
|
def test_set_stack_res_deletion_policy(self):
|
||||||
|
tpl = {'Resources':
|
||||||
|
{'A': {'Type': 'GenericResourceType'},
|
||||||
|
'B': {'Type': 'GenericResourceType'}}}
|
||||||
|
resources = '''{'A': {'status': 'COMPLETE', 'name': 'A',
|
||||||
|
'resource_data': {}, 'resource_id': None, 'action': 'INIT',
|
||||||
|
'type': 'GenericResourceType', 'metadata': {}},
|
||||||
|
'B': {'status': 'COMPLETE', 'name': 'B', 'resource_data': {},
|
||||||
|
'resource_id': None, 'action': 'INIT', 'type': 'GenericResourceType',
|
||||||
|
'metadata': {}}}'''
|
||||||
|
stack = parser.Stack(self.ctx,
|
||||||
|
'stack_details_test',
|
||||||
|
parser.Template(tpl))
|
||||||
|
stack.store()
|
||||||
|
stack.set_deletion_policy(resource.RETAIN)
|
||||||
|
self.assertEqual(resource.RETAIN,
|
||||||
|
stack.resources['A'].t['DeletionPolicy'])
|
||||||
|
self.assertEqual(resource.RETAIN,
|
||||||
|
stack.resources['B'].t['DeletionPolicy'])
|
||||||
|
|
||||||
@utils.stack_delete_after
|
@utils.stack_delete_after
|
||||||
def test_set_param_id(self):
|
def test_set_param_id(self):
|
||||||
self.stack = parser.Stack(self.ctx, 'param_arn_test',
|
self.stack = parser.Stack(self.ctx, 'param_arn_test',
|
||||||
|
@ -82,6 +82,29 @@ class ResourceTest(HeatTestCase):
|
|||||||
self.assertEqual(res.state, (res.CREATE, res.COMPLETE))
|
self.assertEqual(res.state, (res.CREATE, res.COMPLETE))
|
||||||
self.assertEqual(res.status_reason, 'wibble')
|
self.assertEqual(res.status_reason, 'wibble')
|
||||||
|
|
||||||
|
def test_set_deletion_policy(self):
|
||||||
|
tmpl = {'Type': 'Foo'}
|
||||||
|
res = generic_rsrc.GenericResource('test_resource', tmpl, self.stack)
|
||||||
|
res.set_deletion_policy(resource.RETAIN)
|
||||||
|
self.assertEqual(resource.RETAIN, res.t['DeletionPolicy'])
|
||||||
|
res.set_deletion_policy(resource.DELETE)
|
||||||
|
self.assertEqual(resource.DELETE, res.t['DeletionPolicy'])
|
||||||
|
|
||||||
|
def test_get_abandon_data(self):
|
||||||
|
tmpl = {'Type': 'Foo'}
|
||||||
|
res = generic_rsrc.GenericResource('test_resource', tmpl, self.stack)
|
||||||
|
expected = {
|
||||||
|
'action': 'INIT',
|
||||||
|
'metadata': {},
|
||||||
|
'name': 'test_resource',
|
||||||
|
'resource_data': {},
|
||||||
|
'resource_id': None,
|
||||||
|
'status': 'COMPLETE',
|
||||||
|
'type': 'Foo'
|
||||||
|
}
|
||||||
|
actual = res.get_abandon_data()
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
def test_state_set_invalid(self):
|
def test_state_set_invalid(self):
|
||||||
tmpl = {'Type': 'Foo'}
|
tmpl = {'Type': 'Foo'}
|
||||||
res = generic_rsrc.GenericResource('test_resource', tmpl, self.stack)
|
res = generic_rsrc.GenericResource('test_resource', tmpl, self.stack)
|
||||||
|
@ -114,6 +114,31 @@ class StackResourceTest(HeatTestCase):
|
|||||||
self.assertEqual(self.templ, self.stack.t.t)
|
self.assertEqual(self.templ, self.stack.t.t)
|
||||||
self.assertEqual(self.stack.id, self.parent_resource.resource_id)
|
self.assertEqual(self.stack.id, self.parent_resource.resource_id)
|
||||||
|
|
||||||
|
@utils.stack_delete_after
|
||||||
|
def test_set_deletion_policy(self):
|
||||||
|
self.parent_resource.create_with_template(self.templ,
|
||||||
|
{"KeyName": "key"})
|
||||||
|
self.stack = self.parent_resource.nested()
|
||||||
|
self.parent_resource.set_deletion_policy(resource.RETAIN)
|
||||||
|
for res in self.stack.resources.values():
|
||||||
|
self.assertEqual(resource.RETAIN, res.t['DeletionPolicy'])
|
||||||
|
|
||||||
|
@utils.stack_delete_after
|
||||||
|
def test_get_abandon_data(self):
|
||||||
|
self.parent_resource.create_with_template(self.templ,
|
||||||
|
{"KeyName": "key"})
|
||||||
|
ret = self.parent_resource.get_abandon_data()
|
||||||
|
# check abandoned data contains all the necessary information.
|
||||||
|
# (no need to check stack/resource IDs, because they are
|
||||||
|
# randomly generated uuids)
|
||||||
|
self.assertEqual(6, len(ret))
|
||||||
|
self.assertEqual('CREATE', ret['action'])
|
||||||
|
self.assertTrue('name' in ret)
|
||||||
|
self.assertTrue('id' in ret)
|
||||||
|
self.assertTrue('resources' in ret)
|
||||||
|
self.assertEqual(template_format.parse(param_template),
|
||||||
|
ret['template'])
|
||||||
|
|
||||||
@utils.stack_delete_after
|
@utils.stack_delete_after
|
||||||
def test_create_with_template_validates(self):
|
def test_create_with_template_validates(self):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user