]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Tidy up Resource creation and deletion
authorZane Bitter <zbitter@redhat.com>
Tue, 5 Jun 2012 07:46:10 +0000 (09:46 +0200)
committerZane Bitter <zbitter@redhat.com>
Fri, 15 Jun 2012 09:36:33 +0000 (11:36 +0200)
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 <zbitter@redhat.com>
heat/engine/cloud_watch.py
heat/engine/eip.py
heat/engine/instance.py
heat/engine/manager.py
heat/engine/parser.py
heat/engine/resources.py
heat/engine/security_group.py
heat/engine/user.py
heat/engine/volume.py
heat/engine/wait_condition.py
heat/tests/test_stacks.py

index e0e9015df28e3f74a0e0702b1facb410a7d661e0..5470b263f152f22e208a67417fb6c826e630dd11 100644 (file)
@@ -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)
index 1bd757eeac13ae16b2a8aa9da6454a8a609ae258..cc9b32ff65a5110a2b779011141440d7a333c76a 100644 (file)
@@ -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)
index 97c6ef4acdadb64a3a7eabbe8662d115b1d6cba0..57928c344c80119cbcfb4059b445985ae74b01ff 100644 (file)
@@ -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)
index 8f7ae77e228672796f922edbfa7c194fcaff2063..5a9b285650e1482a10d650a780535b516d71bbc8 100644 (file)
@@ -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
 
index 6001900882f6d00629962b68aa69b15762a10da2..8d874eca56029a2af9d08519fc186981dabab3a7 100644 (file)
@@ -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,98 +169,80 @@ 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))
-
-                    try:
-                        self.update_parsed_template()
-                    except Exception as ex:
-                        logger.exception('update_parsed_template')
-
+                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')
+
+                    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.status_set(stack_status, reason)
-
-    def create(self):
+        self.state_set(stack_status, reason)
 
-        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))
-
-        self.status_set(failed and self.DELETE_FAILED or self.DELETE_COMPLETE)
-        if not failed:
-            db_api.stack_delete(self.context, self.name)
+            result = res.delete()
+            if result:
+                failures.append(str(res))
 
-    def delete(self):
-        pool = eventlet.GreenPool()
-        pool.spawn_n(self.delete_blocking)
+        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 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:
index 5a0f0d0117f6ddc5b603a09008292f982fcb5be7..75f6c288a339618646068804bbd89b2237c6d323 100644 (file)
@@ -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))
-
-        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
+        elif new_state in (self.CREATE_COMPLETE, self.CREATE_FAILED):
+            self._create_db()
 
-    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'])
index 4f1ebb6c25b9519d3fd797cfd599c99f3c945e01..7e4214b4522d4fbd02ff4c5f693c75a3bf737a07 100644 (file)
@@ -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)
index 70f1d7504144a6d380d3eb58ebf5e551d59fd732..d0c3cb6e6b50138dd0f468d3b83d497e62be76e1 100644 (file)
@@ -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
index b871d8c9507516d21620840647e2131ded7c7807..d06c77726ca6e1cb054f78751a241f82c225cdab 100644 (file)
@@ -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)
index 48d314eba26fb0892e5685dde9db2dbcb8537119..67d9ad1f9c60443b3e23407f7a2bc95dfcef5af4 100644 (file)
@@ -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()
index 0c419bbc2a504647b0d52f7e0529656b91fbf6f5..e9b437db7763b1bbf67cb9d2319db226ccb99679 100644 (file)
@@ -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())