Begin the change to a python only implementation.

- Don't start pacemaker-cloud cape, instead start the
  resources (soon to be implemented).
- kill off systemctl, capelistener and json2capexml

Signed-off-by: Angus Salkeld <asalkeld@redhat.com>
This commit is contained in:
Angus Salkeld 2012-03-26 16:45:06 +11:00
parent 88b77d2bed
commit 91ee085a0d
8 changed files with 333 additions and 698 deletions

View File

@ -27,7 +27,7 @@ from webob.exc import (HTTPNotFound,
from heat.common import exception
from heat.common import wsgi
from heat.engine import capelistener
from heat.engine import parser
from heat.engine import simpledb
logger = logging.getLogger('heat.engine.api.v1.events')
@ -41,8 +41,6 @@ class EventsController(object):
def __init__(self, conf):
self.conf = conf
self.event_db = {}
self.listener = capelistener.CapeEventListener()
def index(self, req, stack_id):
return simpledb.events_get(stack_id)

View File

@ -27,9 +27,7 @@ from webob.exc import (HTTPNotFound,
from heat.common import exception
from heat.common import wsgi
from heat.engine import capelistener
from heat.engine import json2capexml
from heat.engine import systemctl
from heat.engine import parser
logger = logging.getLogger('heat.engine.api.v1.stacks')
@ -43,8 +41,6 @@ class StacksController(object):
def __init__(self, conf):
self.conf = conf
self.listener = capelistener.CapeEventListener()
def index(self, req, format='json'):
logger.info('format is %s' % format)
@ -99,16 +95,8 @@ class StacksController(object):
msg = _("Stack already exists with that name.")
return webob.exc.HTTPConflict(msg)
stack = body
stack['StackId'] = body['StackName']
stack['StackStatus'] = 'CREATE_COMPLETE'
# TODO self._apply_user_parameters(req, stack)
stack_db[body['StackName']] = stack
cape_transformer = json2capexml.Json2CapeXml(stack, body['StackName'])
cape_transformer.convert_and_write()
systemctl.systemctl('start', 'pcloud-cape-sshd', body['StackName'])
stack_db[body['StackName']] = parser.Stack(body['StackName'], body)
stack_db[body['StackName']].start()
return {'stack': {'id': body['StackName']}}
@ -117,7 +105,6 @@ class StacksController(object):
return webob.exc.HTTPNotFound('No stack by that name')
logger.info('deleting stack %s' % id)
systemctl.systemctl('stop', 'pcloud-cape-sshd', id)
del stack_db[id]
return None

View File

@ -1,103 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import errno
import eventlet
from eventlet.green import socket
import fcntl
import libxml2
import logging
import os
import stat
from heat.engine import simpledb
logger = logging.getLogger('heat.engine.capelistener')
class CapeEventListener(object):
def __init__(self):
self.backlog = 50
self.file = 'pacemaker-cloud-cped'
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
flags = fcntl.fcntl(sock, fcntl.F_GETFD)
fcntl.fcntl(sock, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
st = os.stat(self.file)
except OSError, err:
if err.errno != errno.ENOENT:
raise
else:
if stat.S_ISSOCK(st.st_mode):
os.remove(self.file)
else:
raise ValueError("File %s exists and is not a socket",
self.file)
sock.bind(self.file)
sock.listen(self.backlog)
os.chmod(self.file, 0600)
eventlet.spawn_n(self.cape_event_listner, sock)
def cape_event_listner(self, sock):
eventlet.serve(sock, self.cape_event_handle)
def store(self, xml_event):
try:
doc = libxml2.parseDoc(xml_event)
except:
return
event = {'EventId': ''}
root = doc.getRootElement()
child = root.children
while child is not None:
if child.type != "element":
child = child.next
elif child.name == 'event':
child = child.children
elif child.name == 'application':
event['StackId'] = child.prop('name')
event['StackName'] = child.prop('name')
child = child.children
elif child.name == 'node':
event['ResourceType'] = 'AWS::EC2::Instance'
event['LogicalResourceId'] = child.prop('name')
child = child.children
elif child.name == 'resource':
event['ResourceType'] = 'ORG::HA::Service'
event['LogicalResourceId'] = child.prop('name')
child = child.children
elif child.name == 'state':
event['ResourceStatus'] = child.content
child = child.next
elif child.name == 'reason':
event['ResourceStatusReason'] = child.content
child = child.next
else:
child = child.next
simpledb.event_append(event)
doc.freeDoc()
def cape_event_handle(self, sock, client_addr):
while True:
x = sock.recv(4096)
self.store(x.strip('\n'))
if not x: break

View File

@ -1,234 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import libxml2
import logging
from heat.common import utils
logger = logging.getLogger('heat.engine.json2capexml')
class Json2CapeXml:
def __init__(self, template, stack_name):
self.t = template
self.parms = self.t['Parameters']
self.maps = self.t['Mappings']
self.res = {}
self.doc = None
self.name = stack_name
self.parms['AWS::Region'] = {"Description" : "AWS Regions", "Type" : "String", "Default" : "ap-southeast-1",
"AllowedValues" : ["us-east-1","us-west-1","us-west-2","sa-east-1","eu-west-1","ap-southeast-1","ap-northeast-1"],
"ConstraintDescription" : "must be a valid EC2 instance type." }
# expected user parameters
self.parms['AWS::StackName'] = {'Default': stack_name}
self.parms['KeyName'] = {'Default': 'harry-45-5-34-5'}
for r in self.t['Resources']:
# fake resource instance references
self.parms[r] = {'Default': utils.generate_uuid()}
self.resolve_static_refs(self.t['Resources'])
self.resolve_find_in_map(self.t['Resources'])
#self.resolve_attributes(self.t['Resources'])
self.resolve_joins(self.t['Resources'])
self.resolve_base64(self.t['Resources'])
#print json.dumps(self.t['Resources'], indent=2)
def convert(self):
self.doc = libxml2.newDoc("1.0")
dep = self.doc.newChild(None, "deployable", None)
dep.setProp("name", self.name)
dep.setProp("uuid", 'bogus')
dep.setProp("username", 'nobody-yet')
n_asses = dep.newChild(None, "assemblies", None)
for r in self.t['Resources']:
type = self.t['Resources'][r]['Type']
if type != 'AWS::EC2::Instance':
print 'ignoring Resource %s (%s)' % (r, type)
continue
n_ass = n_asses.newChild(None, 'assembly', None)
n_ass.setProp("name", r)
n_ass.setProp("uuid", self.parms[r]['Default'])
props = self.t['Resources'][r]['Properties']
for p in props:
if p == 'ImageId':
n_ass.setProp("image_name", props[p])
elif p == 'UserData':
new_script = []
script_lines = props[p].split('\n')
for l in script_lines:
if '#!/' in l:
new_script.append(l)
self.insert_package_and_services(self.t['Resources'][r], new_script)
else:
new_script.append(l)
startup = n_ass.newChild(None, 'startup', '\n'.join(new_script))
try:
con = self.t['Resources'][r]['Metadata']["AWS::CloudFormation::Init"]['config']
n_services = n_ass.newChild(None, 'services', None)
for st in con['services']:
for s in con['services'][st]:
n_service = n_services.newChild(None, 'service', None)
n_service.setProp("name", '%s_%s' % (r, s))
n_service.setProp("type", s)
n_service.setProp("provider", 'pacemaker')
n_service.setProp("class", 'lsb')
n_service.setProp("monitor_interval", '30s')
n_service.setProp("escalation_period", '1000')
n_service.setProp("escalation_failures", '3')
except KeyError as e:
# if there is no config then no services.
pass
def get_xml(self):
str = self.doc.serialize(None, 1)
self.doc.freeDoc()
self.doc = None
return str
def convert_and_write(self):
self.convert()
try:
filename = '/var/run/%s.xml' % self.name
open(filename, 'w').write(self.doc.serialize(None, 1))
self.doc.freeDoc()
self.doc = None
except IOError as e:
logger.error('couldn\'t write to /var/run/ error %s' % e)
def insert_package_and_services(self, r, new_script):
try:
con = r['Metadata']["AWS::CloudFormation::Init"]['config']
except KeyError as e:
return
for pt in con['packages']:
if pt == 'yum':
for p in con['packages']['yum']:
new_script.append('yum install -y %s' % p)
for st in con['services']:
if st == 'systemd':
for s in con['services']['systemd']:
v = con['services']['systemd'][s]
if v['enabled'] == 'true':
new_script.append('systemctl enable %s.service' % s)
if v['ensureRunning'] == 'true':
new_script.append('systemctl start %s.service' % s)
elif st == 'sysvinit':
for s in con['services']['sysvinit']:
v = con['services']['systemd'][s]
if v['enabled'] == 'true':
new_script.append('chkconfig %s on' % s)
if v['ensureRunning'] == 'true':
new_script.append('/etc/init.d/start %s' % s)
def resolve_static_refs(self, s):
'''
looking for { "Ref": "str" }
'''
if isinstance(s, dict):
for i in s:
if i == 'Ref' and isinstance(s[i], (basestring, unicode)) and \
self.parms.has_key(s[i]):
if self.parms[s[i]] == None:
print 'None Ref: %s' % str(s[i])
elif self.parms[s[i]].has_key('Default'):
# note the "ref: values" are in a dict of
# size one, so return is fine.
#print 'Ref: %s == %s' % (s[i], self.parms[s[i]]['Default'])
return self.parms[s[i]]['Default']
else:
print 'missing Ref: %s' % str(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 resolve_find_in_map(self, s):
'''
looking for { "Ref": "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 resolve_joins(self, s):
'''
looking for { "Fn::join": [] }
'''
if isinstance(s, dict):
for i in s:
if i == 'Fn::Join':
return s[i][0].join(s[i][1])
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 resolve_base64(self, s):
'''
looking for { "Fn::join": [] }
'''
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

View File

@ -16,290 +16,13 @@
import json
import logging
from heat.engine import resources
logger = logging.getLogger('heat.engine.parser')
parse_debug = False
#parse_debug = True
class Resource(object):
CREATE_IN_PROGRESS = 'CREATE_IN_PROGRESS'
CREATE_FAILED = 'CREATE_FAILED'
CREATE_COMPLETE = 'CREATE_COMPLETE'
DELETE_IN_PROGRESS = 'DELETE_IN_PROGRESS'
DELETE_FAILED = 'DELETE_FAILED'
DELETE_COMPLETE = 'DELETE_COMPLETE'
UPDATE_IN_PROGRESS = 'UPDATE_IN_PROGRESS'
UPDATE_FAILED = 'UPDATE_FAILED'
UPDATE_COMPLETE = 'UPDATE_COMPLETE'
def __init__(self, name, json_snippet, stack):
self.t = json_snippet
self.depends_on = []
self.references = []
self.references_resolved = False
self.state = None
self.stack = stack
self.name = name
self.instance_id = None
stack.resolve_static_refs(self.t)
stack.resolve_find_in_map(self.t)
def start(self):
for c in self.depends_on:
#print '%s->%s.start()' % (self.name, self.stack.resources[c].name)
self.stack.resources[c].start()
self.stack.resolve_attributes(self.t)
self.stack.resolve_joins(self.t)
self.stack.resolve_base64(self.t)
def stop(self):
pass
def reload(self):
pass
def FnGetRefId(self):
'''
http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html
'''
if self.instance_id != None:
return unicode(self.instance_id)
else:
return unicode(self.name)
def FnGetAtt(self, key):
'''
http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html
'''
print '%s.GetAtt(%s)' % (self.name, key)
return unicode('not-this-surely')
class GenericResource(Resource):
def __init__(self, name, json_snippet, stack):
super(GenericResource, self).__init__(name, json_snippet, stack)
def start(self):
if self.state != None:
return
self.state = self.CREATE_IN_PROGRESS
super(GenericResource, self).start()
print 'Starting GenericResource %s' % self.name
class ElasticIp(Resource):
def __init__(self, name, json_snippet, stack):
super(ElasticIp, self).__init__(name, json_snippet, stack)
self.instance_id = ''
if self.t.has_key('Properties') and self.t['Properties'].has_key('Domain'):
print '*** can\'t support Domain %s yet' % (self.t['Properties']['Domain'])
def start(self):
if self.state != None:
return
self.state = Resource.CREATE_IN_PROGRESS
super(ElasticIp, self).start()
self.instance_id = 'eip-000003'
def FnGetRefId(self):
return unicode('0.0.0.0')
def FnGetAtt(self, key):
return unicode(self.instance_id)
class ElasticIpAssociation(Resource):
def __init__(self, name, json_snippet, stack):
super(ElasticIpAssociation, self).__init__(name, json_snippet, stack)
# note we only support already assigned ipaddress
#
# Done with:
# nova-manage floating create 172.31.0.224/28
# euca-allocate-address
#
if not self.t['Properties'].has_key('EIP'):
print '*** can\'t support this yet'
if self.t['Properties'].has_key('AllocationId'):
print '*** can\'t support AllocationId %s yet' % (self.t['Properties']['AllocationId'])
def FnGetRefId(self):
if not self.t['Properties'].has_key('EIP'):
return unicode('0.0.0.0')
else:
return unicode(self.t['Properties']['EIP'])
def start(self):
if self.state != None:
return
self.state = Resource.CREATE_IN_PROGRESS
super(ElasticIpAssociation, self).start()
print '$ euca-associate-address -i %s %s' % (self.t['Properties']['InstanceId'],
self.t['Properties']['EIP'])
class Volume(Resource):
def __init__(self, name, json_snippet, stack):
super(Volume, self).__init__(name, json_snippet, stack)
def start(self):
if self.state != None:
return
self.state = Resource.CREATE_IN_PROGRESS
super(Volume, self).start()
# TODO start the volume here
# of size -> self.t['Properties']['Size']
# and set self.instance_id to the volume id
print '$ euca-create-volume -s %s -z nova' % self.t['Properties']['Size']
self.instance_id = 'vol-4509854'
class VolumeAttachment(Resource):
def __init__(self, name, json_snippet, stack):
super(VolumeAttachment, self).__init__(name, json_snippet, stack)
def start(self):
if self.state != None:
return
self.state = Resource.CREATE_IN_PROGRESS
super(VolumeAttachment, self).start()
# TODO attach the volume with an id of:
# self.t['Properties']['VolumeId']
# to the vm of instance:
# self.t['Properties']['InstanceId']
# and make sure that the mountpoint is:
# self.t['Properties']['Device']
print '$ euca-attach-volume %s -i %s -d %s' % (self.t['Properties']['VolumeId'],
self.t['Properties']['InstanceId'],
self.t['Properties']['Device'])
class Instance(Resource):
def __init__(self, name, json_snippet, stack):
super(Instance, self).__init__(name, json_snippet, stack)
if not self.t['Properties'].has_key('AvailabilityZone'):
self.t['Properties']['AvailabilityZone'] = 'nova'
self.itype_oflavor = {'t1.micro': 'm1.tiny',
'm1.small': 'm1.small',
'm1.medium': 'm1.medium',
'm1.large': 'm1.large',
'm2.xlarge': 'm1.large',
'm2.2xlarge': 'm1.large',
'm2.4xlarge': 'm1.large',
'c1.medium': 'm1.medium',
'c1.4xlarge': 'm1.large',
'cc2.8xlarge': 'm1.large',
'cg1.4xlarge': 'm1.large'}
def FnGetAtt(self, key):
print '%s.GetAtt(%s)' % (self.name, key)
if key == 'AvailabilityZone':
return unicode(self.t['Properties']['AvailabilityZone'])
else:
# TODO PrivateDnsName, PublicDnsName, PrivateIp, PublicIp
return unicode('not-this-surely')
def start(self):
if self.state != None:
return
self.state = Resource.CREATE_IN_PROGRESS
Resource.start(self)
props = self.t['Properties']
if not props.has_key('KeyName'):
props['KeyName'] = 'default-key-name'
if not props.has_key('InstanceType'):
props['InstanceType'] = 's1.large'
if not props.has_key('ImageId'):
props['ImageId'] = 'F16-x86_64'
for p in props:
if p == 'UserData':
new_script = []
script_lines = props[p].split('\n')
for l in script_lines:
if '#!/' in l:
new_script.append(l)
self.insert_package_and_services(self.t, new_script)
else:
new_script.append(l)
if parse_debug:
print '----------------------'
try:
print '\n'.join(new_script)
except:
print str(new_script)
raise
print '----------------------'
try:
con = self.t['Metadata']["AWS::CloudFormation::Init"]['config']
for st in con['services']:
for s in con['services'][st]:
print 'service start %s_%s' % (self.name, s)
except KeyError as e:
# if there is no config then no services.
pass
# TODO start the instance here.
# and set self.instance_id
print '$ euca-run-instances -k %s -t %s %s' % (self.t['Properties']['KeyName'],
self.t['Properties']['InstanceType'],
self.t['Properties']['ImageId'])
# Convert AWS instance type to OpenStack flavor
# TODO(sdake)
# heat API should take care of these conversions and feed them into
# heat engine in an openstack specific json format
flavor = self.itype_oflavor[self.t['Properties']['InstanceType']]
self.instance_id = 'i-734509008'
def insert_package_and_services(self, r, new_script):
try:
con = r['Metadata']["AWS::CloudFormation::Init"]['config']
except KeyError as e:
return
if con.has_key('packages'):
for pt in con['packages']:
if pt == 'yum':
for p in con['packages']['yum']:
new_script.append('yum install -y %s' % p)
if con.has_key('services'):
for st in con['services']:
if st == 'systemd':
for s in con['services']['systemd']:
v = con['services']['systemd'][s]
if v['enabled'] == 'true':
new_script.append('systemctl enable %s.service' % s)
if v['ensureRunning'] == 'true':
new_script.append('systemctl start %s.service' % s)
elif st == 'sysvinit':
for s in con['services']['sysvinit']:
v = con['services']['sysvinit'][s]
if v['enabled'] == 'true':
new_script.append('chkconfig %s on' % s)
if v['ensureRunning'] == 'true':
new_script.append('/etc/init.d/start %s' % s)
class Stack:
def __init__(self, template, stack_name):
def __init__(self, stack_name, template):
self.t = template
if self.t.has_key('Parameters'):
@ -318,27 +41,31 @@ class Stack:
"AllowedValues" : ["us-east-1","us-west-1","us-west-2","sa-east-1","eu-west-1","ap-southeast-1","ap-northeast-1"],
"ConstraintDescription" : "must be a valid EC2 instance type." }
######
# stack['StackId'] = body['StackName']
# stack['StackStatus'] = 'CREATE_COMPLETE'
# # TODO self._apply_user_parameters(req, stack)
# stack_db[body['StackName']] = stack
######
self.resources = {}
for r in self.t['Resources']:
type = self.t['Resources'][r]['Type']
if type == 'AWS::EC2::Instance':
self.resources[r] = Instance(r, self.t['Resources'][r], self)
self.resources[r] = resources.Instance(r, self.t['Resources'][r], self)
elif type == 'AWS::EC2::Volume':
self.resources[r] = Volume(r, self.t['Resources'][r], self)
self.resources[r] = resources.Volume(r, self.t['Resources'][r], self)
elif type == 'AWS::EC2::VolumeAttachment':
self.resources[r] = VolumeAttachment(r, self.t['Resources'][r], self)
self.resources[r] = resources.VolumeAttachment(r, self.t['Resources'][r], self)
elif type == 'AWS::EC2::EIP':
self.resources[r] = ElasticIp(r, self.t['Resources'][r], self)
self.resources[r] = resources.ElasticIp(r, self.t['Resources'][r], self)
elif type == 'AWS::EC2::EIPAssociation':
self.resources[r] = ElasticIpAssociation(r, self.t['Resources'][r], self)
self.resources[r] = resources.ElasticIpAssociation(r, self.t['Resources'][r], self)
else:
self.resources[r] = GenericResource(r, self.t['Resources'][r], self)
self.resources[r] = resources.GenericResource(r, self.t['Resources'][r], self)
self.calulate_dependancies(self.t['Resources'][r], self.resources[r])
#print json.dumps(self.t['Resources'], indent=2)
if parse_debug:
for r in self.t['Resources']:
print '%s -> %s' % (r, self.resources[r].depends_on)
def start(self):
# start Volumes first.

308
heat/engine/resources.py Normal file
View File

@ -0,0 +1,308 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from heat.engine import simpledb
logger = logging.getLogger('heat.engine.resources')
class Resource(object):
CREATE_IN_PROGRESS = 'CREATE_IN_PROGRESS'
CREATE_FAILED = 'CREATE_FAILED'
CREATE_COMPLETE = 'CREATE_COMPLETE'
DELETE_IN_PROGRESS = 'DELETE_IN_PROGRESS'
DELETE_FAILED = 'DELETE_FAILED'
DELETE_COMPLETE = 'DELETE_COMPLETE'
UPDATE_IN_PROGRESS = 'UPDATE_IN_PROGRESS'
UPDATE_FAILED = 'UPDATE_FAILED'
UPDATE_COMPLETE = 'UPDATE_COMPLETE'
def __init__(self, name, json_snippet, stack):
self.t = json_snippet
self.depends_on = []
self.references = []
self.references_resolved = False
self.state = None
self.stack = stack
self.name = name
self.instance_id = None
stack.resolve_static_refs(self.t)
stack.resolve_find_in_map(self.t)
def start(self):
for c in self.depends_on:
#print '%s->%s.start()' % (self.name, self.stack.resources[c].name)
self.stack.resources[c].start()
self.stack.resolve_attributes(self.t)
self.stack.resolve_joins(self.t)
self.stack.resolve_base64(self.t)
def state_set(self, new_state, reason="state changed"):
if new_state != self.state:
ev = {}
ev['LogicalResourceId'] = self.name
ev['PhysicalResourceId'] = self.name
ev['StackId'] = self.stack.name
ev['StackName'] = self.stack.name
ev['ResourceStatus'] = new_state
ev['ResourceStatusReason'] = reason
ev['ResourceType'] = self.t['Type']
ev['ResourceProperties'] = self.t['Properties']
simpledb.event_append(ev)
self.state = new_state
def stop(self):
pass
def reload(self):
pass
def FnGetRefId(self):
'''
http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html
'''
if self.instance_id != None:
return unicode(self.instance_id)
else:
return unicode(self.name)
def FnGetAtt(self, key):
'''
http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html
'''
print '%s.GetAtt(%s)' % (self.name, key)
return unicode('not-this-surely')
class GenericResource(Resource):
def __init__(self, name, json_snippet, stack):
super(GenericResource, self).__init__(name, json_snippet, stack)
def start(self):
if self.state != None:
return
self.state_set(self.CREATE_IN_PROGRESS)
super(GenericResource, self).start()
print 'Starting GenericResource %s' % self.name
class ElasticIp(Resource):
def __init__(self, name, json_snippet, stack):
super(ElasticIp, self).__init__(name, json_snippet, stack)
self.instance_id = ''
if self.t.has_key('Properties') and self.t['Properties'].has_key('Domain'):
logger.warn('*** can\'t support Domain %s yet' % (self.t['Properties']['Domain']))
def start(self):
if self.state != None:
return
self.state_set(self.CREATE_IN_PROGRESS)
super(ElasticIp, self).start()
self.instance_id = 'eip-000003'
def FnGetRefId(self):
return unicode('0.0.0.0')
def FnGetAtt(self, key):
return unicode(self.instance_id)
class ElasticIpAssociation(Resource):
def __init__(self, name, json_snippet, stack):
super(ElasticIpAssociation, self).__init__(name, json_snippet, stack)
# note we only support already assigned ipaddress
#
# Done with:
# nova-manage floating create 172.31.0.224/28
# euca-allocate-address
#
if not self.t['Properties'].has_key('EIP'):
logger.warn('*** can\'t support this yet')
if self.t['Properties'].has_key('AllocationId'):
logger.warn('*** can\'t support AllocationId %s yet' % (self.t['Properties']['AllocationId']))
def FnGetRefId(self):
if not self.t['Properties'].has_key('EIP'):
return unicode('0.0.0.0')
else:
return unicode(self.t['Properties']['EIP'])
def start(self):
if self.state != None:
return
self.state_set(self.CREATE_IN_PROGRESS)
super(ElasticIpAssociation, self).start()
logger.info('$ euca-associate-address -i %s %s' % (self.t['Properties']['InstanceId'],
self.t['Properties']['EIP']))
class Volume(Resource):
def __init__(self, name, json_snippet, stack):
super(Volume, self).__init__(name, json_snippet, stack)
def start(self):
if self.state != None:
return
self.state_set(self.CREATE_IN_PROGRESS)
super(Volume, self).start()
# TODO start the volume here
# of size -> self.t['Properties']['Size']
# and set self.instance_id to the volume id
logger.info('$ euca-create-volume -s %s -z nova' % self.t['Properties']['Size'])
self.instance_id = 'vol-4509854'
class VolumeAttachment(Resource):
def __init__(self, name, json_snippet, stack):
super(VolumeAttachment, self).__init__(name, json_snippet, stack)
def start(self):
if self.state != None:
return
self.state_set(self.CREATE_IN_PROGRESS)
super(VolumeAttachment, self).start()
# TODO attach the volume with an id of:
# self.t['Properties']['VolumeId']
# to the vm of instance:
# self.t['Properties']['InstanceId']
# and make sure that the mountpoint is:
# self.t['Properties']['Device']
logger.info('$ euca-attach-volume %s -i %s -d %s' % (self.t['Properties']['VolumeId'],
self.t['Properties']['InstanceId'],
self.t['Properties']['Device']))
class Instance(Resource):
def __init__(self, name, json_snippet, stack):
super(Instance, self).__init__(name, json_snippet, stack)
if not self.t['Properties'].has_key('AvailabilityZone'):
self.t['Properties']['AvailabilityZone'] = 'nova'
self.itype_oflavor = {'t1.micro': 'm1.tiny',
'm1.small': 'm1.small',
'm1.medium': 'm1.medium',
'm1.large': 'm1.large',
'm2.xlarge': 'm1.large',
'm2.2xlarge': 'm1.large',
'm2.4xlarge': 'm1.large',
'c1.medium': 'm1.medium',
'c1.4xlarge': 'm1.large',
'cc2.8xlarge': 'm1.large',
'cg1.4xlarge': 'm1.large'}
def FnGetAtt(self, key):
print '%s.GetAtt(%s)' % (self.name, key)
if key == 'AvailabilityZone':
return unicode(self.t['Properties']['AvailabilityZone'])
else:
# TODO PrivateDnsName, PublicDnsName, PrivateIp, PublicIp
return unicode('not-this-surely')
def start(self):
if self.state != None:
return
self.state_set(self.CREATE_IN_PROGRESS)
Resource.start(self)
props = self.t['Properties']
if not props.has_key('KeyName'):
props['KeyName'] = 'default-key-name'
if not props.has_key('InstanceType'):
props['InstanceType'] = 's1.large'
if not props.has_key('ImageId'):
props['ImageId'] = 'F16-x86_64'
for p in props:
if p == 'UserData':
new_script = []
script_lines = props[p].split('\n')
for l in script_lines:
if '#!/' in l:
new_script.append(l)
self.insert_package_and_services(self.t, new_script)
else:
new_script.append(l)
print '----------------------'
try:
print '\n'.join(new_script)
except:
print str(new_script)
raise
print '----------------------'
try:
con = self.t['Metadata']["AWS::CloudFormation::Init"]['config']
for st in con['services']:
for s in con['services'][st]:
print 'service start %s_%s' % (self.name, s)
except KeyError as e:
# if there is no config then no services.
pass
# TODO start the instance here.
# and set self.instance_id
logger.info('$ euca-run-instances -k %s -t %s %s' % (self.t['Properties']['KeyName'],
self.t['Properties']['InstanceType'],
self.t['Properties']['ImageId']))
# Convert AWS instance type to OpenStack flavor
# TODO(sdake)
# heat API should take care of these conversions and feed them into
# heat engine in an openstack specific json format
flavor = self.itype_oflavor[self.t['Properties']['InstanceType']]
self.instance_id = 'i-734509008'
def insert_package_and_services(self, r, new_script):
try:
con = r['Metadata']["AWS::CloudFormation::Init"]['config']
except KeyError as e:
return
if con.has_key('packages'):
for pt in con['packages']:
if pt == 'yum':
for p in con['packages']['yum']:
new_script.append('yum install -y %s' % p)
if con.has_key('services'):
for st in con['services']:
if st == 'systemd':
for s in con['services']['systemd']:
v = con['services']['systemd'][s]
if v['enabled'] == 'true':
new_script.append('systemctl enable %s.service' % s)
if v['ensureRunning'] == 'true':
new_script.append('systemctl start %s.service' % s)
elif st == 'sysvinit':
for s in con['services']['sysvinit']:
v = con['services']['sysvinit'][s]
if v['enabled'] == 'true':
new_script.append('chkconfig %s on' % s)
if v['ensureRunning'] == 'true':
new_script.append('/etc/init.d/start %s' % s)

View File

@ -17,6 +17,10 @@ import anydbm
import json
def event_append(event):
'''
EventId The unique ID of this event.
Timestamp Time the status was updated.
'''
name = event['StackName']
d = anydbm.open('/var/lib/heat/%s.events.db' % name, 'c')
if d.has_key('lastid'):

View File

@ -1,52 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Start and Stop systemd services
"""
import dbus
import logging
logger = logging.getLogger('heat.engine.systemctl')
def systemctl(method, name, instance=None):
bus = dbus.SystemBus()
sysd = bus.get_object('org.freedesktop.systemd1',
'/org/freedesktop/systemd1')
actual_method = ''
if method == 'start':
actual_method = 'StartUnit'
elif method == 'stop':
actual_method = 'StopUnit'
else:
raise
m = sysd.get_dbus_method(actual_method, 'org.freedesktop.systemd1.Manager')
if instance == None:
service = '%s.service' % (name)
else:
service = '%s@%s.service' % (name, instance)
try:
result = m(service, 'replace')
except dbus.DBusException as e:
logger.error('couldn\'t %s %s error: %s' % (method, name, e))
return None
return result