Add a Timeout (-t) to the heat options (and make -f for templates).
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>
This commit is contained in:
parent
7e02a65f79
commit
aca71be770
7
bin/heat
7
bin/heat
@ -137,6 +137,8 @@ def stack_create(options, arguments):
|
|||||||
parameters['Parameters.member.%d.ParameterValue' % count] = v
|
parameters['Parameters.member.%d.ParameterValue' % count] = v
|
||||||
count = count + 1
|
count = count + 1
|
||||||
|
|
||||||
|
parameters['Timeout'] = options.timeout
|
||||||
|
|
||||||
if options.template_file:
|
if options.template_file:
|
||||||
parameters['TemplateBody'] = open(options.template_file).read()
|
parameters['TemplateBody'] = open(options.template_file).read()
|
||||||
elif options.template_url:
|
elif options.template_url:
|
||||||
@ -367,8 +369,11 @@ def create_options(parser):
|
|||||||
|
|
||||||
parser.add_option('-u', '--template-url', metavar="template_url",
|
parser.add_option('-u', '--template-url', metavar="template_url",
|
||||||
default=None, help="URL of template. Default: None")
|
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")
|
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,
|
parser.add_option('-P', '--parameters', metavar="parameters", default=None,
|
||||||
help="Parameter values used to create the stack.")
|
help="Parameter values used to create the stack.")
|
||||||
|
@ -34,7 +34,7 @@ heat command\&.
|
|||||||
.RE
|
.RE
|
||||||
.SH "OPTIONS"
|
.SH "OPTIONS"
|
||||||
.PP
|
.PP
|
||||||
\fB\-t\fR, \fB\-\-template\fR
|
\fB\-f\fR, \fB\-\-template\fR
|
||||||
.RS 4
|
.RS 4
|
||||||
The template to use set up a stack\&.
|
The template to use set up a stack\&.
|
||||||
.RE
|
.RE
|
||||||
|
@ -138,6 +138,8 @@ class StackController(object):
|
|||||||
msg = _("The Template must be a JSON document.")
|
msg = _("The Template must be a JSON document.")
|
||||||
return webob.exc.HTTPBadRequest(explanation=msg)
|
return webob.exc.HTTPBadRequest(explanation=msg)
|
||||||
stack['StackName'] = req.params['StackName']
|
stack['StackName'] = req.params['StackName']
|
||||||
|
if 'Timeout' in req.params:
|
||||||
|
stack['Timeout'] = req.params['Timeout']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return rpc.call(con, 'engine',
|
return rpc.call(con, 'engine',
|
||||||
|
@ -16,4 +16,4 @@
|
|||||||
SUPPORTED_PARAMS = ('StackName', 'TemplateBody', 'TemplateUrl',
|
SUPPORTED_PARAMS = ('StackName', 'TemplateBody', 'TemplateUrl',
|
||||||
'NotificationARNs', 'Parameters', 'Version',
|
'NotificationARNs', 'Parameters', 'Version',
|
||||||
'SignatureVersion', 'Timestamp', 'AWSAccessKeyId',
|
'SignatureVersion', 'Timestamp', 'AWSAccessKeyId',
|
||||||
'Signature', 'KeyStoneCreds')
|
'Signature', 'KeyStoneCreds', 'Timeout')
|
||||||
|
@ -115,11 +115,12 @@ class EngineManager(manager.Manager):
|
|||||||
mem['updated_at'] = str(s.updated_at)
|
mem['updated_at'] = str(s.updated_at)
|
||||||
mem['NotificationARNs'] = 'TODO'
|
mem['NotificationARNs'] = 'TODO'
|
||||||
mem['Parameters'] = ps.t['Parameters']
|
mem['Parameters'] = ps.t['Parameters']
|
||||||
mem['StackStatusReason'] = 'TODO'
|
mem['TimeoutInMinutes'] = ps.t.get('Timeout', '60')
|
||||||
mem['TimeoutInMinutes'] = 'TODO'
|
|
||||||
mem['TemplateDescription'] = ps.t.get('Description',
|
mem['TemplateDescription'] = ps.t.get('Description',
|
||||||
'No description')
|
'No description')
|
||||||
mem['StackStatus'] = ps.t.get('stack_status', 'unknown')
|
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
|
# only show the outputs on a completely created stack
|
||||||
if ps.t['stack_status'] == ps.CREATE_COMPLETE:
|
if ps.t['stack_status'] == ps.CREATE_COMPLETE:
|
||||||
|
@ -40,6 +40,7 @@ class Stack(object):
|
|||||||
self.t = template
|
self.t = template
|
||||||
self.maps = self.t.get('Mappings', {})
|
self.maps = self.t.get('Mappings', {})
|
||||||
self.outputs = self.t.get('Outputs', {})
|
self.outputs = self.t.get('Outputs', {})
|
||||||
|
self.timeout = self.t.get('Timeout', None)
|
||||||
self.res = {}
|
self.res = {}
|
||||||
self.doc = None
|
self.doc = None
|
||||||
self.name = stack_name
|
self.name = stack_name
|
||||||
@ -174,6 +175,7 @@ class Stack(object):
|
|||||||
def status_set(self, new_status, reason='change in resource state'):
|
def status_set(self, new_status, reason='change in resource state'):
|
||||||
|
|
||||||
self.t['stack_status'] = new_status
|
self.t['stack_status'] = new_status
|
||||||
|
self.t['stack_status_reason'] = reason
|
||||||
self.update_parsed_template()
|
self.update_parsed_template()
|
||||||
|
|
||||||
def create_blocking(self):
|
def create_blocking(self):
|
||||||
@ -181,28 +183,51 @@ class Stack(object):
|
|||||||
create all the resources in the order specified by get_create_order
|
create all the resources in the order specified by get_create_order
|
||||||
'''
|
'''
|
||||||
order = self.get_create_order()
|
order = self.get_create_order()
|
||||||
failed = False
|
self.status_set(self.IN_PROGRESS, 'Stack creation started')
|
||||||
self.status_set(self.IN_PROGRESS)
|
|
||||||
|
|
||||||
for r in order:
|
stack_status = self.CREATE_COMPLETE
|
||||||
res = self.resources[r]
|
reason = 'Stack successfully created'
|
||||||
if not failed:
|
|
||||||
try:
|
|
||||||
res.create()
|
|
||||||
except Exception as ex:
|
|
||||||
logger.exception('create')
|
|
||||||
failed = True
|
|
||||||
res.state_set(res.CREATE_FAILED, str(ex))
|
|
||||||
|
|
||||||
try:
|
# Timeout is in minutes (default to 1 hour)
|
||||||
self.update_parsed_template()
|
secs_tmo = 60 * 60
|
||||||
except Exception as ex:
|
if self.timeout:
|
||||||
logger.exception('update_parsed_template')
|
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:
|
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):
|
def create(self):
|
||||||
|
|
||||||
@ -222,12 +247,20 @@ class Stack(object):
|
|||||||
res = self.resources[r]
|
res = self.resources[r]
|
||||||
try:
|
try:
|
||||||
res.delete()
|
res.delete()
|
||||||
re = db_api.resource_get(self.context, self.resources[r].id)
|
|
||||||
re.delete()
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
failed = True
|
failed = True
|
||||||
res.state_set(res.DELETE_FAILED)
|
res.state_set(res.DELETE_FAILED)
|
||||||
logger.error('delete: %s' % str(ex))
|
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)
|
self.status_set(failed and self.DELETE_FAILED or self.DELETE_COMPLETE)
|
||||||
if not failed:
|
if not failed:
|
||||||
|
Loading…
Reference in New Issue
Block a user