def __init__(self, name, json_snippet, stack):
super(Instance, self).__init__(name, json_snippet, stack)
- self.ipaddress = '0.0.0.0'
+ self.ipaddress = None
self.mime_string = None
self.itype_oflavor = {'t1.micro': 'm1.tiny',
'cc2.8xlarge': 'm1.large',
'cg1.4xlarge': 'm1.large'}
- def FnGetAtt(self, key):
+ def _ipaddress(self):
+ '''
+ Return the server's IP address, fetching it from Nova if necessary
+ '''
+ if self.ipaddress is None:
+ try:
+ server = self.nova().servers.get(self.instance_id)
+ 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
+
+ return self.ipaddress or '0.0.0.0'
+ def FnGetAtt(self, key):
res = None
if key == 'AvailabilityZone':
res = self.properties['AvailabilityZone']
elif key == 'PublicIp':
- res = self.ipaddress
+ res = self._ipaddress()
elif key == 'PrivateDnsName':
- res = self.ipaddress
+ res = self._ipaddress()
else:
raise exception.InvalidTemplateAttribute(resource=self.name,
key=key)
'Provided KeyName is not registered with nova'}
return None
- def reload(self):
- '''
- re-read the server's ipaddress so FnGetAtt works.
- '''
- try:
- server = self.nova().servers.get(self.instance_id)
- for n in server.networks:
- self.ipaddress = server.networks[n][0]
- except NotFound:
- self.ipaddress = '0.0.0.0'
-
- Resource.reload(self)
-
def delete(self):
if self.state == self.DELETE_IN_PROGRESS or \
self.state == self.DELETE_COMPLETE:
import eventlet
import json
+import itertools
import logging
from heat.common import exception
from heat.engine import checkeddict
res = Resource(rname, rdesc, self)
self.resources[rname] = res
- self.calulate_dependencies(rdesc, res)
+ self.calulate_dependencies(res.t, res)
def validate(self):
'''
pool.spawn_n(self.delete_blocking)
def get_outputs(self):
+ outputs = self.resolve_runtime_data(self.outputs)
- for r in self.resources:
- self.resources[r].reload()
+ def output_dict(k):
+ return {'Description': outputs[k].get('Description',
+ 'No description given'),
+ 'OutputKey': k,
+ 'OutputValue': outputs[k].get('Value', '')}
- self.resolve_attributes(self.outputs)
- self.resolve_joins(self.outputs)
-
- outs = []
- for o in self.outputs:
- out = {}
- out['Description'] = self.outputs[o].get('Description',
- 'No description given')
- out['OutputKey'] = o
- out['OutputValue'] = self.outputs[o].get('Value', '')
- outs.append(out)
-
- return outs
+ return [output_dict(key) for key in outputs]
def restart_resource_blocking(self, resource_name):
'''
except ValueError:
raise exception.UserParameterMissing(key=key)
- def resolve_static_refs(self, s):
+ def _resolve_static_refs(self, s):
'''
- looking for { "Ref": "str" }
+ looking for { "Ref" : "str" }
'''
- if isinstance(s, dict):
- for i in s:
- if i == 'Ref' and \
- isinstance(s[i], (basestring, unicode)) and \
- s[i] in self.parms:
- return self.parameter_get(s[i])
- else:
- s[i] = self.resolve_static_refs(s[i])
- elif isinstance(s, list):
- for index, item in enumerate(s):
- #print 'resolve_static_refs %d %s' % (index, item)
- s[index] = self.resolve_static_refs(item)
- return s
+ def match(key, value):
+ return (key == 'Ref' and
+ isinstance(value, basestring) and
+ value in self.parms)
- def resolve_find_in_map(self, s):
- '''
- looking for { "Fn::FindInMap": ["str", "str"] }
- '''
- if isinstance(s, dict):
- for i in s:
- if i == 'Fn::FindInMap':
- obj = self.maps
- if isinstance(s[i], list):
- #print 'map list: %s' % s[i]
- for index, item in enumerate(s[i]):
- if isinstance(item, dict):
- item = self.resolve_find_in_map(item)
- #print 'map item dict: %s' % (item)
- else:
- pass
- #print 'map item str: %s' % (item)
- obj = obj[item]
- else:
- obj = obj[s[i]]
- return obj
- else:
- s[i] = self.resolve_find_in_map(s[i])
- elif isinstance(s, list):
- for index, item in enumerate(s):
- s[index] = self.resolve_find_in_map(item)
- return s
+ def handle(ref):
+ return self.parameter_get(ref)
+
+ return _resolve(match, handle, s)
- def resolve_attributes(self, s):
+ def _resolve_find_in_map(self, s):
+ def handle(args):
+ try:
+ name, key, value = args
+ return self.maps[name][key][value]
+ except (ValueError, TypeError) as ex:
+ raise KeyError(str(ex))
+
+ return _resolve(lambda k, v: k == 'Fn::FindInMap', handle, s)
+
+ def _resolve_attributes(self, s):
'''
looking for something like:
- {"Fn::GetAtt" : ["DBInstance", "Endpoint.Address"]}
+ { "Fn::GetAtt" : [ "DBInstance", "Endpoint.Address" ] }
'''
- if isinstance(s, dict):
- for i in s:
- if i == 'Ref' and s[i] in self.resources:
- return self.resources[s[i]].FnGetRefId()
- elif i == 'Fn::GetAtt':
- resource_name = s[i][0]
- key_name = s[i][1]
- res = self.resources.get(resource_name)
- rc = None
- if res:
- return res.FnGetAtt(key_name)
- else:
- raise exception.InvalidTemplateAttribute(
- resource=resource_name, key=key_name)
- return rc
- else:
- s[i] = self.resolve_attributes(s[i])
- elif isinstance(s, list):
- for index, item in enumerate(s):
- s[index] = self.resolve_attributes(item)
- return s
+ def match_ref(key, value):
+ return key == 'Ref' and value in self.resources
+
+ def handle_ref(arg):
+ return self.resources[arg].FnGetRefId()
+
+ def handle_getatt(args):
+ resource, att = args
+ try:
+ return self.resources[resource].FnGetAtt(att)
+ except KeyError:
+ raise exception.InvalidTemplateAttribute(resource=resource,
+ key=att)
+
+ return _resolve(lambda k, v: k == 'Fn::GetAtt', handle_getatt,
+ _resolve(match_ref, handle_ref, s))
- def resolve_joins(self, s):
+ @staticmethod
+ def _resolve_joins(s):
'''
- looking for { "Fn::join": []}
+ looking for { "Fn::Join" : [] }
'''
- if isinstance(s, dict):
- for i in s:
- if i == 'Fn::Join':
- j = None
- try:
- j = s[i][0].join(s[i][1])
- except Exception:
- logger.error('Could not join %s' % str(s[i]))
- return j
- else:
- s[i] = self.resolve_joins(s[i])
- elif isinstance(s, list):
- for index, item in enumerate(s):
- s[index] = self.resolve_joins(item)
- return s
+ def handle(args):
+ delim, strings = args
+ return delim.join(strings)
+
+ return _resolve(lambda k, v: k == 'Fn::Join', handle, s)
- def resolve_base64(self, s):
+ @staticmethod
+ def _resolve_base64(s):
'''
- looking for { "Fn::join": [] }
+ looking for { "Fn::Base64" : "" }
'''
- if isinstance(s, dict):
- for i in s:
- if i == 'Fn::Base64':
- return s[i]
- else:
- s[i] = self.resolve_base64(s[i])
- elif isinstance(s, list):
- for index, item in enumerate(s):
- s[index] = self.resolve_base64(item)
- return s
+ return _resolve(lambda k, v: k == 'Fn::Base64', lambda d: d, s)
+
+ def resolve_static_data(self, snippet):
+ return transform(snippet, [self._resolve_static_refs,
+ self._resolve_find_in_map])
+
+ def resolve_runtime_data(self, snippet):
+ return transform(snippet, [self._resolve_attributes,
+ self._resolve_joins,
+ self._resolve_base64])
+
+
+def transform(data, transformations):
+ '''
+ Apply each of the transformation functions in the supplied list to the data
+ in turn.
+ '''
+ for t in transformations:
+ data = t(data)
+ return data
+
+
+def _resolve(match, handle, snippet):
+ '''
+ Resolve constructs in a snippet of a template. The supplied match function
+ should return True if a particular key-value pair should be substituted,
+ and the handle function should return the correct substitution when passed
+ the argument list as parameters.
+
+ Returns a copy of the original snippet with the substitutions performed.
+ '''
+ recurse = lambda k: _resolve(match, handle, snippet[k])
+
+ if isinstance(snippet, dict):
+ should_handle = lambda k: match(k, snippet[k])
+ matches = itertools.imap(recurse,
+ itertools.ifilter(should_handle, snippet))
+ try:
+ args = next(matches)
+ except StopIteration:
+ # No matches
+ return dict((k, recurse(k)) for k in snippet)
+ else:
+ return handle(args)
+ elif isinstance(snippet, list):
+ return [recurse(i) for i in range(len(snippet))]
+ return snippet
return ResourceClass(name, json, stack)
def __init__(self, name, json_snippet, stack):
- self.t = json_snippet
self.depends_on = []
self.references = []
self.stack = stack
self.name = name
+ self.t = stack.resolve_static_data(json_snippet)
self.properties = checkeddict.Properties(name, self.properties_schema)
- if not 'Properties' in self.t:
+ if 'Properties' not in self.t:
# make a dummy entry to prevent having to check all over the
# place for it.
self.t['Properties'] = {}
self.id = None
self._nova = {}
- stack.resolve_static_refs(self.t)
- stack.resolve_find_in_map(self.t)
-
def nova(self, service_type='compute'):
if service_type in self._nova:
return self._nova[service_type]
service_name=service_name)
return self._nova[service_type]
+ def calculate_properties(self):
+ template = self.stack.resolve_runtime_data(self.t)
+
+ for p, v in template['Properties'].items():
+ self.properties[p] = v
+
def create(self):
logger.info('creating %s name:%s' % (self.t['Type'], self.name))
-
- self.stack.resolve_attributes(self.t)
- self.stack.resolve_joins(self.t)
- self.stack.resolve_base64(self.t)
- for p in self.t['Properties']:
- self.properties[p] = self.t['Properties'][p]
+ self.calculate_properties()
self.properties.validate()
def validate(self):
logger.info('validating %s name:%s' % (self.t['Type'], self.name))
- self.stack.resolve_attributes(self.t)
- self.stack.resolve_joins(self.t)
- self.stack.resolve_base64(self.t)
-
try:
- for p in self.t['Properties']:
- self.properties[p] = self.t['Properties'][p]
+ self.calculate_properties()
except ValueError as ex:
return {'Error': '%s' % str(ex)}
self.properties.validate()
ev['name'] = new_state
ev['resource_status_reason'] = reason
ev['resource_type'] = self.t['Type']
- ev['resource_properties'] = self.t['Properties']
+ self.calculate_properties()
+ ev['resource_properties'] = dict(self.properties)
try:
db_api.event_create(None, ev)
except Exception as ex:
self.state = new_state
def delete(self):
- self.reload()
logger.info('deleting %s name:%s inst:%s db_id:%s' %
(self.t['Type'], self.name,
self.instance_id, str(self.id)))
- def reload(self):
- '''
- The point of this function is to get the Resource instance back
- into the state that it was just after it was created. So we
- need to retrieve things like ipaddresses and other variables
- used by FnGetAtt and FnGetRefId. classes inheriting from Resource
- might need to override this, but still call it.
- This is currently used by stack.get_outputs()
- '''
- logger.info('reloading %s name:%s instance_id:%s' %
- (self.t['Type'], self.name, self.instance_id))
- self.stack.resolve_attributes(self.t)
-
def FnGetRefId(self):
'''
http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/ \