Merge "heat engine changes for abandon-stack"

This commit is contained in:
Jenkins 2013-12-06 00:46:18 +00:00 committed by Gerrit Code Review
commit fd23550b3b
9 changed files with 185 additions and 1 deletions

View File

@ -663,6 +663,21 @@ class Stack(collections.Mapping):
self.clients.nova().availability_zones.list(detailed=False)]
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):
return resolve_static_data(self.t, self, self.parameters, snippet)

View File

@ -445,6 +445,21 @@ class Resource(object):
self.name)
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):
'''
update the resource. Subclasses should provide a handle_update() method

View File

@ -459,13 +459,33 @@ class EngineService(service.Service):
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()
if st.id in self.stg:
self.stg[st.id].stop()
del self.stg[st.id]
# use the service ThreadGroup for deletes
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):
"""

View File

@ -246,6 +246,12 @@ class StackResource(resource.Resource):
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):
'''
Return the specified Output value from the nested stack.

View File

@ -173,6 +173,18 @@ class EngineClient(heat.openstack.common.rpc.proxy.RpcProxy):
self.make_msg('delete_stack',
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):
"""
Get a list of valid resource types.

View File

@ -1154,6 +1154,31 @@ class StackServiceTest(HeatTestCase):
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):
non_exist_identifier = identifier.HeatIdentifier(
self.ctx.tenant_id, 'wibble',

View File

@ -766,6 +766,49 @@ class StackTest(HeatTestCase):
self.assertTrue(identifier.stack_id)
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
def test_set_param_id(self):
self.stack = parser.Stack(self.ctx, 'param_arn_test',

View File

@ -82,6 +82,29 @@ class ResourceTest(HeatTestCase):
self.assertEqual(res.state, (res.CREATE, res.COMPLETE))
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):
tmpl = {'Type': 'Foo'}
res = generic_rsrc.GenericResource('test_resource', tmpl, self.stack)

View File

@ -114,6 +114,31 @@ class StackResourceTest(HeatTestCase):
self.assertEqual(self.templ, self.stack.t.t)
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
def test_create_with_template_validates(self):
"""