]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Add a Timeout (-t) to the heat options (and make -f for templates).
authorAngus Salkeld <asalkeld@redhat.com>
Thu, 7 Jun 2012 01:41:03 +0000 (11:41 +1000)
committerAngus Salkeld <asalkeld@redhat.com>
Thu, 7 Jun 2012 01:41:03 +0000 (11:41 +1000)
This makes the cli more consistent with AWS and implements
the timeout feature.

Note: the timeout is in minutes and defaults to 60 minutes.

Change-Id: I41dea75170c871c1ee47948643311752d9d5e41e
Signed-off-by: Angus Salkeld <asalkeld@redhat.com>
bin/heat
docs/man/man1/heat.1
heat/api/v1/stacks.py
heat/cloudformations.py
heat/engine/manager.py
heat/engine/parser.py

index 8273d61c168862bed1b226ef9f1779659ce829ec..5442f8a440aef0c643de0caab1677c8e96673974 100755 (executable)
--- a/bin/heat
+++ b/bin/heat
@@ -137,6 +137,8 @@ def stack_create(options, arguments):
             parameters['Parameters.member.%d.ParameterValue' % count] = v
             count = count + 1
 
+    parameters['Timeout'] = options.timeout
+
     if options.template_file:
         parameters['TemplateBody'] = open(options.template_file).read()
     elif options.template_url:
@@ -367,8 +369,11 @@ def create_options(parser):
 
     parser.add_option('-u', '--template-url', metavar="template_url",
                       default=None, help="URL of template. Default: None")
-    parser.add_option('-t', '--template-file', metavar="template_file",
+    parser.add_option('-f', '--template-file', metavar="template_file",
                       default=None, help="Path to the template. Default: None")
+    parser.add_option('-t', '--timeout', metavar="timeout",
+                      default='60',
+                      help='Stack creation timeout in minutes. Default: 60')
 
     parser.add_option('-P', '--parameters', metavar="parameters", default=None,
                       help="Parameter values used to create the stack.")
index 4eaeca37fc9ea04bc3c4bf1a97d4206fabe159f0..727b428fcb398b0f90bef36cd68c165f952b9a42 100644 (file)
@@ -34,7 +34,7 @@ heat command\&.
 .RE
 .SH "OPTIONS"
 .PP
-\fB\-t\fR, \fB\-\-template\fR
+\fB\-f\fR, \fB\-\-template\fR
 .RS 4
 The template to use set up a stack\&.
 .RE
index 8bc32b094645156d32d3243e2d24f9d9fb2f77bc..06eeabb81533b0cd8b4c89f832271f02c3c87c0a 100644 (file)
@@ -138,6 +138,8 @@ class StackController(object):
             msg = _("The Template must be a JSON document.")
             return webob.exc.HTTPBadRequest(explanation=msg)
         stack['StackName'] = req.params['StackName']
+        if 'Timeout' in req.params:
+            stack['Timeout'] = req.params['Timeout']
 
         try:
             return rpc.call(con, 'engine',
index 2bb00e03d424e3dc4726c09946543f038775bd54..c6ef2644558a9496ebc2e92c10d1edb47211e3c2 100644 (file)
@@ -16,4 +16,4 @@
 SUPPORTED_PARAMS = ('StackName', 'TemplateBody', 'TemplateUrl',
                     'NotificationARNs', 'Parameters', 'Version',
                     'SignatureVersion', 'Timestamp', 'AWSAccessKeyId',
-                    'Signature', 'KeyStoneCreds')
+                    'Signature', 'KeyStoneCreds', 'Timeout')
index 00f04f7b5c127bcad61b2232589bcb3acd8a33b9..e220a0fd8643b3e4e8431255c6f26658730274ce 100644 (file)
@@ -115,11 +115,12 @@ class EngineManager(manager.Manager):
             mem['updated_at'] = str(s.updated_at)
             mem['NotificationARNs'] = 'TODO'
             mem['Parameters'] = ps.t['Parameters']
-            mem['StackStatusReason'] = 'TODO'
-            mem['TimeoutInMinutes'] = 'TODO'
+            mem['TimeoutInMinutes'] = ps.t.get('Timeout', '60')
             mem['TemplateDescription'] = ps.t.get('Description',
                                                   'No description')
             mem['StackStatus'] = ps.t.get('stack_status', 'unknown')
+            mem['StackStatusReason'] = ps.t.get('stack_status_reason',
+                                                'State changed')
 
             # only show the outputs on a completely created stack
             if ps.t['stack_status'] == ps.CREATE_COMPLETE:
index b37a79ae9a0bd8e1e1327436efa9d8f01689652f..0041bd8ccc1714b4f5da2dfa34b102ea39092d48 100644 (file)
@@ -40,6 +40,7 @@ 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
@@ -174,6 +175,7 @@ class Stack(object):
     def status_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):
@@ -181,28 +183,51 @@ class Stack(object):
         create all the resources in the order specified by get_create_order
         '''
         order = self.get_create_order()
-        failed = False
-        self.status_set(self.IN_PROGRESS)
+        self.status_set(self.IN_PROGRESS, 'Stack creation started')
 
-        for r in order:
-            res = self.resources[r]
-            if not failed:
-                try:
-                    res.create()
-                except Exception as ex:
-                    logger.exception('create')
-                    failed = True
-                    res.state_set(res.CREATE_FAILED, str(ex))
+        stack_status = self.CREATE_COMPLETE
+        reason = 'Stack successfully created'
 
-                try:
-                    self.update_parsed_template()
-                except Exception as ex:
-                    logger.exception('update_parsed_template')
+        # Timeout is in minutes (default to 1 hour)
+        secs_tmo = 60 * 60
+        if self.timeout:
+            try:
+                secs_tmo = int(self.timeout) * 60
+            except ValueError as ve:
+                logger.exception('create timeout conversion')
+        tmo = eventlet.Timeout(secs_tmo)
+        try:
+            for r in order:
+                res = self.resources[r]
+                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')
 
+                else:
+                    res.state_set(res.CREATE_FAILED)
+
+        except eventlet.Timeout, t:
+            if t is not tmo:
+                # not my timeout
+                raise
             else:
-                res.state_set(res.CREATE_FAILED)
+                stack_status = self.CREATE_FAILED
+                reason = 'Timed out waiting for %s' % (res.name)
+        finally:
+            tmo.cancel()
 
-        self.status_set(failed and self.CREATE_FAILED or self.CREATE_COMPLETE)
+        self.status_set(stack_status, reason)
 
     def create(self):
 
@@ -222,12 +247,20 @@ class Stack(object):
             res = self.resources[r]
             try:
                 res.delete()
-                re = db_api.resource_get(self.context, self.resources[r].id)
-                re.delete()
             except Exception as ex:
                 failed = True
                 res.state_set(res.DELETE_FAILED)
                 logger.error('delete: %s' % str(ex))
+            try:
+                re = db_api.resource_get(self.context, self.resources[r].id)
+                re.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: