From 3b91d100a6c6424961e1ec06cf7afd9a1c9cf49c Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Tue, 5 Jun 2012 09:46:10 +0200 Subject: [PATCH] Tidy up Resource creation and deletion Most of this code is common between resources, so put it in the parent Resource class and have subclasses provide handle_create()/handle_delete() methods for all their extra needs. Change-Id: I14c6afa9fdd1ecc065036fa93bde2a693b6c3eb2 Signed-off-by: Zane Bitter --- heat/engine/cloud_watch.py | 23 +---- heat/engine/eip.py | 37 +------- heat/engine/instance.py | 70 +++++---------- heat/engine/manager.py | 8 +- heat/engine/parser.py | 128 +++++++++++---------------- heat/engine/resources.py | 161 ++++++++++++++++++++++------------ heat/engine/security_group.py | 24 +---- heat/engine/user.py | 35 +------- heat/engine/volume.py | 47 +--------- heat/engine/wait_condition.py | 40 +-------- heat/tests/test_stacks.py | 15 ++-- 11 files changed, 211 insertions(+), 377 deletions(-) diff --git a/heat/engine/cloud_watch.py b/heat/engine/cloud_watch.py index e0e9015df2..5470b263f1 100644 --- a/heat/engine/cloud_watch.py +++ b/heat/engine/cloud_watch.py @@ -54,22 +54,13 @@ class CloudWatchAlarm(Resource): strict_dependency = False - def __init__(self, name, json_snippet, stack): - super(CloudWatchAlarm, self).__init__(name, json_snippet, stack) - self.instance_id = '' - def validate(self): ''' Validate the Properties ''' return Resource.validate(self) - def create(self): - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - Resource.create(self) - + def handle_create(self): wr_values = { 'name': self.name, 'rule': self.parsed_template()['Properties'], @@ -80,21 +71,11 @@ class CloudWatchAlarm(Resource): wr = db_api.watch_rule_create(self.stack.context, wr_values) self.instance_id = wr.id - self.state_set(self.CREATE_COMPLETE) - - def delete(self): - if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: - return - - self.state_set(self.DELETE_IN_PROGRESS) - Resource.delete(self) - + def handle_delete(self): try: db_api.watch_rule_delete(self.stack.context, self.name) except Exception as ex: pass - self.state_set(self.DELETE_COMPLETE) - def FnGetRefId(self): return unicode(self.name) diff --git a/heat/engine/eip.py b/heat/engine/eip.py index 1bd757eeac..cc9b32ff65 100644 --- a/heat/engine/eip.py +++ b/heat/engine/eip.py @@ -41,18 +41,12 @@ class ElasticIp(Resource): self.ipaddress = ips.ip return self.ipaddress or '' - def create(self): + def handle_create(self): """Allocate a floating IP for the current tenant.""" - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - super(ElasticIp, self).create() - ips = self.nova().floating_ips.create() logger.info('ElasticIp create %s' % str(ips)) self.ipaddress = ips.ip self.instance_id_set(ips.id) - self.state_set(self.CREATE_COMPLETE) def validate(self): ''' @@ -60,19 +54,11 @@ class ElasticIp(Resource): ''' return Resource.validate(self) - def delete(self): + def handle_delete(self): """De-allocate a floating IP.""" - if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: - return - - self.state_set(self.DELETE_IN_PROGRESS) - Resource.delete(self) - if self.instance_id is not None: self.nova().floating_ips.delete(self.instance_id) - self.state_set(self.DELETE_COMPLETE) - def FnGetRefId(self): return unicode(self._ipaddress()) @@ -106,14 +92,8 @@ class ElasticIpAssociation(Resource): ''' return Resource.validate(self) - def create(self): + def handle_create(self): """Add a floating IP address to a server.""" - - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - super(ElasticIpAssociation, self).create() - logger.debug('ElasticIpAssociation %s.add_floating_ip(%s)' % (self.properties['InstanceId'], self.properties['EIP'])) @@ -121,17 +101,8 @@ class ElasticIpAssociation(Resource): server = self.nova().servers.get(self.properties['InstanceId']) server.add_floating_ip(self.properties['EIP']) self.instance_id_set(self.properties['EIP']) - self.state_set(self.CREATE_COMPLETE) - def delete(self): + def handle_delete(self): """Remove a floating IP address from a server.""" - if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: - return - - self.state_set(self.DELETE_IN_PROGRESS) - Resource.delete(self) - server = self.nova().servers.get(self.properties['InstanceId']) server.remove_floating_ip(self.properties['EIP']) - - self.state_set(self.DELETE_COMPLETE) diff --git a/heat/engine/instance.py b/heat/engine/instance.py index 97c6ef4acd..57928c344c 100644 --- a/heat/engine/instance.py +++ b/heat/engine/instance.py @@ -45,30 +45,19 @@ class Restarter(Resource): properties_schema = {'InstanceId': {'Type': 'String', 'Required': True}} - def __init__(self, name, json_snippet, stack): - super(Restarter, self).__init__(name, json_snippet, stack) - - def create(self): - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - Resource.create(self) - self.state_set(self.CREATE_COMPLETE) - - def delete(self): - if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: - return - self.state_set(self.DELETE_IN_PROGRESS) - Resource.delete(self) - self.state_set(self.DELETE_COMPLETE) + def _find_resource(self, instance_id): + ''' + Return the resource with the specified instance ID, or None if it + cannot be found. + ''' + for resource in self.stack: + if resource.instance_id == instance_id: + return resource + return None def alarm(self): self.calculate_properties() - victim = None - for rname, r in self.stack.resources.items(): - if r.instance_id == self.properties['InstanceId']: - victim = r - break + victim = self._find_resource(self.properties['InstanceId']) if victim is None: logger.info('%s Alarm, can not find instance %s' % @@ -137,6 +126,15 @@ class Instance(Resource): 'cc2.8xlarge': 'm1.large', 'cg1.4xlarge': 'm1.large'} + def _set_ipaddress(self, networks): + ''' + Read the server's IP address from a list of networks provided by Nova + ''' + # Just record the first ipaddress + for n in networks: + self.ipaddress = networks[n][0] + break + def _ipaddress(self): ''' Return the server's IP address, fetching it from Nova if necessary @@ -147,9 +145,7 @@ class Instance(Resource): except NotFound as ex: logger.warn('Instance IP address not found (%s)' % str(ex)) else: - for n in server.networks: - self.ipaddress = server.networks[n][0] - break + self._set_ipaddress(server.networks) return self.ipaddress or '0.0.0.0' @@ -211,18 +207,7 @@ class Instance(Resource): return self.mime_string - def create(self): - def _null_callback(p, n, out): - """ - Method to silence the default M2Crypto.RSA.gen_key output. - """ - pass - - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - Resource.create(self) - + def handle_create(self): security_groups = self.properties.get('SecurityGroups') userdata = self.properties['UserData'] flavor = self.itype_oflavor[self.properties['InstanceType']] @@ -259,11 +244,7 @@ class Instance(Resource): eventlet.sleep(1) if server.status == 'ACTIVE': self.instance_id_set(server.id) - self.state_set(self.CREATE_COMPLETE) - # just record the first ipaddress - for n in server.networks: - self.ipaddress = server.networks[n][0] - break + self._set_ipaddress(server.networks) else: raise exception.Error('%s instance[%s] status[%s]' % ('nova reported unexpected', @@ -288,11 +269,7 @@ class Instance(Resource): 'Provided KeyName is not registered with nova'} return None - def delete(self): - if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: - return - self.state_set(self.DELETE_IN_PROGRESS) - Resource.delete(self) + def handle_delete(self): try: server = self.nova().servers.get(self.instance_id) except NotFound: @@ -300,4 +277,3 @@ class Instance(Resource): else: server.delete() self.instance_id = None - self.state_set(self.DELETE_COMPLETE) diff --git a/heat/engine/manager.py b/heat/engine/manager.py index 8f7ae77e22..5a9b285650 100644 --- a/heat/engine/manager.py +++ b/heat/engine/manager.py @@ -21,6 +21,7 @@ import webob import json import urlparse import httplib +import eventlet from heat import manager from heat.db import api as db_api @@ -36,6 +37,7 @@ from novaclient.exceptions import NotFound from novaclient.exceptions import AuthorizationFailure logger = logging.getLogger('heat.engine.manager') +greenpool = eventlet.GreenPool() class EngineManager(manager.Manager): @@ -228,7 +230,7 @@ class EngineManager(manager.Manager): new_pt = db_api.parsed_template_create(None, pt) stack.parsed_template_id = new_pt.id - stack.create() + greenpool.spawn_n(stack.create) return {'stack': {'id': new_s.id, 'name': new_s.name, 'CreationTime': str(new_s.created_at)}} @@ -297,7 +299,7 @@ class EngineManager(manager.Manager): ps = parser.Stack(context, st.name, st.raw_template.parsed_template.template, st.id, params) - ps.delete() + greenpool.spawn_n(ps.delete) return None # Helper for list_events. It's here so we can use it in tests. @@ -464,7 +466,7 @@ class EngineManager(manager.Manager): s.raw_template.parsed_template.template, s.id) for a in wr.rule[action_map[new_state]]: - ps[a].alarm() + greenpool.spawn_n(ps[a].alarm) wr.last_evaluated = now diff --git a/heat/engine/parser.py b/heat/engine/parser.py index 6001900882..8d874eca56 100644 --- a/heat/engine/parser.py +++ b/heat/engine/parser.py @@ -42,7 +42,6 @@ class Stack(object): self.t = template self.maps = self.t.get('Mappings', {}) self.outputs = self.t.get('Outputs', {}) - self.timeout = self.t.get('Timeout', None) self.res = {} self.doc = None self.name = stack_name @@ -170,99 +169,81 @@ class Stack(object): logger.warn('Cant find parsed template to update %d' % self.parsed_template_id) - def status_set(self, new_status, reason='change in resource state'): - + def state_set(self, new_status, reason='change in resource state'): self.t['stack_status'] = new_status self.t['stack_status_reason'] = reason self.update_parsed_template() - def create_blocking(self): + def _timeout(self): + '''Return the stack creation timeout in seconds''' + if 'Timeout' in self.t: + try: + # Timeout is in minutes + return int(self.t['Timeout']) * 60 + except ValueError: + logger.exception('create timeout conversion') + + # Default to 1 hour + return 60 * 60 + + def create(self): ''' Create the stack and all of the resources. ''' - self.status_set(self.IN_PROGRESS, 'Stack creation started') + self.state_set(self.IN_PROGRESS, 'Stack creation started') stack_status = self.CREATE_COMPLETE reason = 'Stack successfully created' - # Timeout is in minutes (default to 1 hour) - secs_tmo = 60 * 60 - if self.timeout: + with eventlet.Timeout(self._timeout()) as tmo: try: - secs_tmo = int(self.timeout) * 60 - except ValueError as ve: - logger.exception('create timeout conversion') - tmo = eventlet.Timeout(secs_tmo) - try: - for res in self: - if stack_status != self.CREATE_FAILED: - try: - res.create() - except Exception as ex: - logger.exception('create') - stack_status = self.CREATE_FAILED - reason = 'resource %s failed with: %s' % (res.name, - str(ex)) - res.state_set(res.CREATE_FAILED, str(ex)) + for res in self: + if stack_status != self.CREATE_FAILED: + result = res.create() + if result: + stack_status = self.CREATE_FAILED + reason = 'Resource %s failed with: %s' % (str(res), + result) - try: - self.update_parsed_template() - except Exception as ex: - logger.exception('update_parsed_template') + try: + self.update_parsed_template() + except Exception as ex: + logger.exception('update_parsed_template') + else: + res.state_set(res.CREATE_FAILED, + 'Stack creation aborted') + + except eventlet.Timeout, t: + if t is tmo: + stack_status = self.CREATE_FAILED + reason = 'Timed out waiting for %s' % (res.name) else: - res.state_set(res.CREATE_FAILED) + # not my timeout + raise - except eventlet.Timeout, t: - if t is not tmo: - # not my timeout - raise - else: - stack_status = self.CREATE_FAILED - reason = 'Timed out waiting for %s' % (res.name) - finally: - tmo.cancel() + self.state_set(stack_status, reason) - self.status_set(stack_status, reason) - - def create(self): - - pool = eventlet.GreenPool() - pool.spawn_n(self.create_blocking) - - def delete_blocking(self): + def delete(self): ''' Delete all of the resources, and then the stack itself. ''' - failed = False - self.status_set(self.DELETE_IN_PROGRESS) + self.state_set(self.DELETE_IN_PROGRESS) + + failures = [] for res in reversed(self): - try: - res.delete() - except Exception as ex: - failed = True - res.state_set(res.DELETE_FAILED) - logger.error('delete: %s' % str(ex)) - else: - try: - db_api.resource_get(self.context, res.id).delete() - except Exception as ex: - # don't fail the delete if the db entry has - # not been created yet. - if 'not found' not in str(ex): - failed = True - res.state_set(res.DELETE_FAILED) - logger.error('delete: %s' % str(ex)) + result = res.delete() + if result: + failures.append(str(res)) - self.status_set(failed and self.DELETE_FAILED or self.DELETE_COMPLETE) - if not failed: + if failures: + self.state_set(self.DELETE_FAILED, + 'Failed to delete ' + ', '.join(failures)) + else: + self.state_set(self.DELETE_COMPLETE, 'Deleted successfully') db_api.stack_delete(self.context, self.name) - def delete(self): - pool = eventlet.GreenPool() - pool.spawn_n(self.delete_blocking) - def get_outputs(self): outputs = self.resolve_runtime_data(self.outputs) @@ -274,7 +255,7 @@ class Stack(object): return [output_dict(key) for key in outputs] - def restart_resource_blocking(self, resource_name): + def restart_resource(self, resource_name): ''' stop resource_name and all that depend on it start resource_name and all that depend on it @@ -295,7 +276,6 @@ class Stack(object): re.delete() except Exception as ex: failed = True - res.state_set(res.DELETE_FAILED) logger.error('delete: %s' % str(ex)) for res in deps: @@ -305,22 +285,16 @@ class Stack(object): except Exception as ex: logger.exception('create') failed = True - res.state_set(res.CREATE_FAILED, str(ex)) try: self.update_parsed_template() except Exception as ex: logger.exception('update_parsed_template') - else: res.state_set(res.CREATE_FAILED) # TODO(asalkeld) if any of this fails we Should # restart the whole stack - def restart_resource(self, resource_name): - pool = eventlet.GreenPool() - pool.spawn_n(self.restart_resource_blocking, resource_name) - def _apply_user_parameters(self, parms): for p in parms: if 'Parameters.member.' in p and 'ParameterKey' in p: diff --git a/heat/engine/resources.py b/heat/engine/resources.py index 5a0f0d0117..75f6c288a3 100644 --- a/heat/engine/resources.py +++ b/heat/engine/resources.py @@ -32,7 +32,7 @@ logger = logging.getLogger('heat.engine.resources') class Resource(object): - CREATE_IN_PROGRESS = 'CREATE_IN_PROGRESS' + CREATE_IN_PROGRESS = 'IN_PROGRESS' CREATE_FAILED = 'CREATE_FAILED' CREATE_COMPLETE = 'CREATE_COMPLETE' DELETE_IN_PROGRESS = 'DELETE_IN_PROGRESS' @@ -140,12 +140,31 @@ class Resource(object): self.properties[p] = v def create(self): - logger.info('creating %s name:%s' % (self.t['Type'], self.name)) - self.calculate_properties() - self.properties.validate() + ''' + Create the resource. Subclasses should provide a handle_create() method + to customise creation. + ''' + if self.state in (self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE): + return 'Resource creation already requested' + + logger.info('creating %s' % str(self)) + + self.state_set(self.CREATE_IN_PROGRESS) + + try: + self.calculate_properties() + self.properties.validate() + if callable(getattr(self, 'handle_create', None)): + self.handle_create() + except Exception as ex: + logger.exception('create %s', str(self)) + self.state_set(self.CREATE_FAILED, str(ex)) + return str(ex) + else: + self.state_set(self.CREATE_COMPLETE) def validate(self): - logger.info('validating %s name:%s' % (self.t['Type'], self.name)) + logger.info('Validating %s' % str(self)) try: self.calculate_properties() @@ -153,59 +172,98 @@ class Resource(object): return str(ex) return self.properties.validate() + def delete(self): + ''' + Delete the resource. Subclasses should provide a handle_delete() method + to customise deletion. + ''' + if self.state == self.DELETE_COMPLETE: + return + if self.state == self.DELETE_IN_PROGRESS: + return 'Resource deletion already in progress' + + logger.info('deleting %s (inst:%s db_id:%s)' % + (str(self), self.instance_id, str(self.id))) + self.state_set(self.DELETE_IN_PROGRESS) + + try: + if callable(getattr(self, 'handle_delete', None)): + self.handle_delete() + except Exception as ex: + logger.exception('Delete %s', str(self)) + self.state_set(self.DELETE_FAILED, str(ex)) + return str(ex) + else: + try: + db_api.resource_get(self.stack.context, self.id).delete() + except Exception as ex: + # Don't fail on delete if the db entry has + # not been created yet. + if 'not found' not in str(ex): + self.state_set(self.DELETE_FAILED) + logger.exception('Delete %s from DB' % str(self)) + return str(ex) + + self.state_set(self.DELETE_COMPLETE) + def instance_id_set(self, inst): self.instance_id = inst + def _create_db(self): + '''Create the resource in the database''' + try: + rs = {'state': self.state, + 'stack_id': self.stack.id, + 'parsed_template_id': self.stack.parsed_template_id, + 'nova_instance': self.instance_id, + 'name': self.name, + 'stack_name': self.stack.name} + + new_rs = db_api.resource_create(self.stack.context, rs) + self.id = new_rs.id + + if new_rs.stack: + new_rs.stack.update_and_save({'updated_at': datetime.utcnow()}) + + except Exception as ex: + logger.error('DB error %s' % str(ex)) + + def _add_event(self, new_state, reason): + '''Add a state change event to the database''' + self.calculate_properties() + ev = {'logical_resource_id': self.name, + 'physical_resource_id': self.instance_id, + 'stack_id': self.stack.id, + 'stack_name': self.stack.name, + 'resource_status': new_state, + 'name': new_state, + 'resource_status_reason': reason, + 'resource_type': self.t['Type'], + 'resource_properties': dict(self.properties)} + try: + db_api.event_create(self.stack.context, ev) + except Exception as ex: + logger.error('DB error %s' % str(ex)) + def state_set(self, new_state, reason="state changed"): + self.state, old_state = new_state, self.state + if self.id is not None: try: rs = db_api.resource_get(self.stack.context, self.id) - rs.update_and_save({'state': new_state, + rs.update_and_save({'state': self.state, 'nova_instance': self.instance_id}) + if rs.stack: rs.stack.update_and_save({'updated_at': datetime.utcnow()}) except Exception as ex: - logger.warn('db error %s' % str(ex)) - elif new_state in [self.CREATE_COMPLETE, self.CREATE_FAILED]: - try: - rs = {} - rs['state'] = new_state - rs['stack_id'] = self.stack.id - rs['parsed_template_id'] = self.stack.parsed_template_id - rs['nova_instance'] = self.instance_id - rs['name'] = self.name - rs['stack_name'] = self.stack.name - new_rs = db_api.resource_create(self.stack.context, rs) - self.id = new_rs.id - if new_rs.stack: - new_rs.stack.update_and_save({'updated_at': - datetime.utcnow()}) + logger.error('DB error %s' % str(ex)) - except Exception as ex: - logger.warn('db error %s' % str(ex)) + elif new_state in (self.CREATE_COMPLETE, self.CREATE_FAILED): + self._create_db() - if new_state != self.state: - ev = {} - ev['logical_resource_id'] = self.name - ev['physical_resource_id'] = self.instance_id - ev['stack_id'] = self.stack.id - ev['stack_name'] = self.stack.name - ev['resource_status'] = new_state - ev['name'] = new_state - ev['resource_status_reason'] = reason - ev['resource_type'] = self.t['Type'] - self.calculate_properties() - ev['resource_properties'] = dict(self.properties) - try: - db_api.event_create(self.stack.context, ev) - except Exception as ex: - logger.warn('db error %s' % str(ex)) - self.state = new_state - - def delete(self): - logger.info('deleting %s name:%s inst:%s db_id:%s' % - (self.t['Type'], self.name, - self.instance_id, str(self.id))) + if new_state != old_state: + self._add_event(new_state, reason) def FnGetRefId(self): ''' @@ -235,13 +293,6 @@ class Resource(object): class GenericResource(Resource): properties_schema = {} - def __init__(self, name, json_snippet, stack): - super(GenericResource, self).__init__(name, json_snippet, stack) - - def create(self): - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - super(GenericResource, self).create() - logger.info('creating GenericResource %s' % self.name) - self.state_set(self.CREATE_COMPLETE) + def handle_create(self): + logger.warning('Creating generic resource (Type "%s")' % + self.t['Type']) diff --git a/heat/engine/security_group.py b/heat/engine/security_group.py index 4f1ebb6c25..7e4214b452 100644 --- a/heat/engine/security_group.py +++ b/heat/engine/security_group.py @@ -34,11 +34,7 @@ class SecurityGroup(Resource): def __init__(self, name, json_snippet, stack): super(SecurityGroup, self).__init__(name, json_snippet, stack) - def create(self): - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - Resource.create(self) + def handle_create(self): sec = None groups = self.nova().security_groups.list() @@ -69,21 +65,7 @@ class SecurityGroup(Resource): # unexpected error raise - self.state_set(self.CREATE_COMPLETE) - - def validate(self): - ''' - Validate the security group - ''' - return Resource.validate(self) - - def delete(self): - if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: - return - - self.state_set(self.DELETE_IN_PROGRESS) - Resource.delete(self) - + def handle_delete(self): if self.instance_id is not None: try: sec = self.nova().security_groups.get(self.instance_id) @@ -99,7 +81,5 @@ class SecurityGroup(Resource): self.nova().security_groups.delete(sec) self.instance_id = None - self.state_set(self.DELETE_COMPLETE) - def FnGetRefId(self): return unicode(self.name) diff --git a/heat/engine/user.py b/heat/engine/user.py index 70f1d75041..d0c3cb6e6b 100644 --- a/heat/engine/user.py +++ b/heat/engine/user.py @@ -44,12 +44,7 @@ class User(Resource): def __init__(self, name, json_snippet, stack): super(User, self).__init__(name, json_snippet, stack) - def create(self): - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - super(User, self).create() - + def handle_create(self): passwd = '' if 'LoginProfile' in self.properties: if self.properties['LoginProfile'] and \ @@ -62,14 +57,8 @@ class User(Resource): tenant_id=tenant_id, enabled=True) self.instance_id_set(user.id) - self.state_set(self.CREATE_COMPLETE) - - def delete(self): - if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: - return - self.state_set(self.DELETE_IN_PROGRESS) - super(User, self).delete() + def handle_delete(self): try: user = self.keystone().users.get(DummyId(self.instance_id)) except Exception as ex: @@ -78,8 +67,6 @@ class User(Resource): else: user.delete() - self.state_set(self.DELETE_COMPLETE) - def FnGetRefId(self): return unicode(self.name) @@ -116,12 +103,7 @@ class AccessKey(Resource): return u return None - def create(self): - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - super(AccessKey, self).create() - + def handle_create(self): user = self._user_from_name(self.properties['UserName']) if user is None: raise exception.NotFound('could not find user %s' % @@ -132,20 +114,11 @@ class AccessKey(Resource): self.instance_id_set(cred.access) self._secret = cred.secret - self.state_set(self.CREATE_COMPLETE) - - def delete(self): - if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: - return - self.state_set(self.DELETE_IN_PROGRESS) - super(AccessKey, self).delete() - + def handle_delete(self): user = self._user_from_name(self.properties['UserName']) if user and self.instance_id: self.keystone().ec2.delete(user.id, self.instance_id) - self.state_set(self.DELETE_COMPLETE) - def _secret_accesskey(self): ''' Return the user's access key, fetching it from keystone if necessary diff --git a/heat/engine/volume.py b/heat/engine/volume.py index b871d8c950..d06c77726c 100644 --- a/heat/engine/volume.py +++ b/heat/engine/volume.py @@ -33,12 +33,7 @@ class Volume(Resource): def __init__(self, name, json_snippet, stack): super(Volume, self).__init__(name, json_snippet, stack) - def create(self): - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - super(Volume, self).create() - + def handle_create(self): vol = self.nova('volume').volumes.create(self.properties['Size'], display_name=self.name, display_description=self.name) @@ -48,32 +43,17 @@ class Volume(Resource): vol.get() if vol.status == 'available': self.instance_id_set(vol.id) - self.state_set(self.CREATE_COMPLETE) else: raise exception.Error(vol.status) - def validate(self): - ''' - Validate the volume - ''' - return Resource.validate(self) - - def delete(self): - if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: - return - + def handle_delete(self): if self.instance_id is not None: vol = self.nova('volume').volumes.get(self.instance_id) if vol.status == 'in-use': logger.warn('cant delete volume when in-use') raise exception.Error("Volume in use") - self.state_set(self.DELETE_IN_PROGRESS) - Resource.delete(self) - - if self.instance_id is not None: self.nova('volume').volumes.delete(self.instance_id) - self.state_set(self.DELETE_COMPLETE) class VolumeAttachment(Resource): @@ -88,12 +68,7 @@ class VolumeAttachment(Resource): def __init__(self, name, json_snippet, stack): super(VolumeAttachment, self).__init__(name, json_snippet, stack) - def create(self): - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - super(VolumeAttachment, self).create() - + def handle_create(self): server_id = self.properties['InstanceId'] volume_id = self.properties['VolumeId'] logger.warn('Attaching InstanceId %s VolumeId %s Device %s' % @@ -109,22 +84,10 @@ class VolumeAttachment(Resource): vol.get() if vol.status == 'in-use': self.instance_id_set(va.id) - self.state_set(self.CREATE_COMPLETE) else: raise exception.Error(vol.status) - def validate(self): - ''' - Validate the mountpoint device - ''' - return Resource.validate(self) - - def delete(self): - if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: - return - self.state_set(self.DELETE_IN_PROGRESS) - Resource.delete(self) - + def handle_delete(self): server_id = self.properties['InstanceId'] volume_id = self.properties['VolumeId'] logger.info('VolumeAttachment un-attaching %s %s' % @@ -146,5 +109,3 @@ class VolumeAttachment(Resource): except Exception: pass vol.get() - - self.state_set(self.DELETE_COMPLETE) diff --git a/heat/engine/wait_condition.py b/heat/engine/wait_condition.py index 48d314eba2..67d9ad1f9c 100644 --- a/heat/engine/wait_condition.py +++ b/heat/engine/wait_condition.py @@ -37,27 +37,12 @@ class WaitConditionHandle(Resource): def __init__(self, name, json_snippet, stack): super(WaitConditionHandle, self).__init__(name, json_snippet, stack) - def create(self): - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - Resource.create(self) - + def handle_create(self): self.instance_id = '%s/stacks/%s/resources/%s' % \ (self.stack.metadata_server, self.stack.name, self.name) - self.state_set(self.CREATE_COMPLETE) - - def delete(self): - if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: - return - - self.state_set(self.DELETE_IN_PROGRESS) - Resource.delete(self) - self.state_set(self.DELETE_COMPLETE) - class WaitCondition(Resource): properties_schema = {'Handle': {'Type': 'String', @@ -82,12 +67,7 @@ class WaitCondition(Resource): self.resource_id = handle_url.split('/')[-1] return self.resource_id - def create(self): - if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: - return - self.state_set(self.CREATE_IN_PROGRESS) - Resource.create(self) - + def handle_create(self): self._get_handle_resource_id() # keep polling our Metadata to see if the cfn-signal has written @@ -128,23 +108,9 @@ class WaitCondition(Resource): finally: tmo.cancel() - if status == 'SUCCESS': - self.state_set(self.CREATE_COMPLETE, - '%s: %s' % (self.name, reason)) - elif status == 'DELETED': - # don't try write to the db as it's gone. - pass - else: + if status != 'SUCCESS': raise exception.Error(reason) - def delete(self): - if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: - return - - self.state_set(self.DELETE_IN_PROGRESS) - Resource.delete(self) - self.state_set(self.DELETE_COMPLETE) - def FnGetAtt(self, key): res = None self._get_handle_resource_id() diff --git a/heat/tests/test_stacks.py b/heat/tests/test_stacks.py index 0c419bbc2a..e9b437db77 100644 --- a/heat/tests/test_stacks.py +++ b/heat/tests/test_stacks.py @@ -59,7 +59,7 @@ class stacksTest(unittest.TestCase): def test_wordpress_single_instance_stack_create(self): stack = self.start_wordpress_stack('test_stack') self.m.ReplayAll() - stack.create_blocking() + stack.create() assert(stack.resources['WebServer'] is not None) assert(stack.resources['WebServer'].instance_id > 0) assert(stack.resources['WebServer'].ipaddress != '0.0.0.0') @@ -85,10 +85,10 @@ class stacksTest(unittest.TestCase): pt['template'] = stack.t pt['raw_template_id'] = new_rt.id new_pt = db_api.parsed_template_create(None, pt) - stack.create_blocking() + stack.create() assert(stack.resources['WebServer'] is not None) assert(stack.resources['WebServer'].instance_id > 0) - stack.delete_blocking() + stack.delete() assert(stack.resources['WebServer'].state == 'DELETE_COMPLETE') assert(stack.t['stack_status'] == 'DELETE_COMPLETE') @@ -113,7 +113,7 @@ class stacksTest(unittest.TestCase): pt['template'] = stack.t pt['raw_template_id'] = new_rt.id new_pt = db_api.parsed_template_create(None, pt) - stack.create_blocking() + stack.create() assert(stack.resources['WebServer'] is not None) assert(stack.resources['WebServer'].instance_id > 0) @@ -123,9 +123,8 @@ class stacksTest(unittest.TestCase): result = m.parse_event(ev) assert(result['EventId'] > 0) assert(result['StackName'] == "test_event_list_stack") - # This is one of CREATE_COMPLETE or CREATE_IN_PROGRESS, - # just did this to make it easy. - assert(result['ResourceStatus'].find('CREATE') != -1) + assert(result['ResourceStatus'] in ('IN_PROGRESS', + 'CREATE_COMPLETE')) assert(result['ResourceType'] == 'AWS::EC2::Instance') assert(result['ResourceStatusReason'] == 'state changed') assert(result['LogicalResourceId'] == 'WebServer') @@ -166,7 +165,7 @@ class stacksTest(unittest.TestCase): new_pt = db_api.parsed_template_create(ctx, pt) instances.Instance.nova().AndReturn(self.fc) self.m.ReplayAll() - stack.create_blocking() + stack.create() f = open("%s/WordPress_Single_Instance_gold.template" % self.path) t = json.loads(f.read())