--- /dev/null
+# 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 eventlet
+import logging
+import os
+
+from heat.common import exception
+from heat.engine.resources import Resource
+
+logger = logging.getLogger(__file__)
+
+
+class ElasticIp(Resource):
+ def __init__(self, name, json_snippet, stack):
+ super(ElasticIp, self).__init__(name, json_snippet, stack)
+ self.ipaddress = ''
+
+ if 'Domain' in self.t['Properties']:
+ logger.warn('*** can\'t support Domain %s yet' % \
+ (self.t['Properties']['Domain']))
+
+ def create(self):
+ """Allocate a floating IP for the current tenant."""
+ if self.state != None:
+ 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 reload(self):
+ '''
+ get the ipaddress here
+ '''
+ if self.instance_id != None:
+ try:
+ ips = self.nova().floating_ips.get(self.instance_id)
+ self.ipaddress = ips.ip
+ except Exception as ex:
+ logger.warn("Error getting floating IPs: %s" % str(ex))
+
+ Resource.reload(self)
+
+ def delete(self):
+ """De-allocate a floating IP."""
+ if self.state == self.DELETE_IN_PROGRESS or \
+ self.state == self.DELETE_COMPLETE:
+ return
+
+ self.state_set(self.DELETE_IN_PROGRESS)
+ Resource.delete(self)
+
+ if self.instance_id != None:
+ self.nova().floating_ips.delete(self.instance_id)
+
+ self.state_set(self.DELETE_COMPLETE)
+
+ def FnGetRefId(self):
+ return unicode(self.ipaddress)
+
+ def FnGetAtt(self, key):
+ if key == 'AllocationId':
+ return unicode(self.instance_id)
+ else:
+ raise exception.InvalidTemplateAttribute(resource=self.name,
+ key=key)
+
+
+class ElasticIpAssociation(Resource):
+ def __init__(self, name, json_snippet, stack):
+ super(ElasticIpAssociation, self).__init__(name, json_snippet, stack)
+
+ def FnGetRefId(self):
+ if not 'EIP' in self.t['Properties']:
+ return unicode('0.0.0.0')
+ else:
+ return unicode(self.t['Properties']['EIP'])
+
+ def create(self):
+ """Add a floating IP address to a server."""
+
+ if self.state != None:
+ return
+ self.state_set(self.CREATE_IN_PROGRESS)
+ super(ElasticIpAssociation, self).create()
+ logger.debug('ElasticIpAssociation %s.add_floating_ip(%s)' % \
+ (self.t['Properties']['InstanceId'],
+ self.t['Properties']['EIP']))
+
+ server = self.nova().servers.get(self.t['Properties']['InstanceId'])
+ server.add_floating_ip(self.t['Properties']['EIP'])
+ self.instance_id_set(self.t['Properties']['EIP'])
+ self.state_set(self.CREATE_COMPLETE)
+
+ def delete(self):
+ """Remove a floating IP address from a server."""
+ if self.state == self.DELETE_IN_PROGRESS or \
+ self.state == self.DELETE_COMPLETE:
+ return
+
+ self.state_set(self.DELETE_IN_PROGRESS)
+ Resource.delete(self)
+
+ server = self.nova().servers.get(self.t['Properties']['InstanceId'])
+ server.remove_floating_ip(self.t['Properties']['EIP'])
+
+ self.state_set(self.DELETE_COMPLETE)
+
+
--- /dev/null
+# 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 base64
+import eventlet
+import logging
+import os
+import string
+import json
+import sys
+from email import encoders
+from email.message import Message
+from email.mime.base import MIMEBase
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+from novaclient.exceptions import NotFound
+
+from heat.engine.resources import Resource
+from heat.common import exception
+
+logger = logging.getLogger(__file__)
+# If ../heat/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+ cloudinit_path = '%s/heat/%s/' % (possible_topdir, "cloudinit")
+else:
+ for p in sys.path:
+ if 'heat' in p:
+ cloudinit_path = '%s/heat/%s/' % (p, "cloudinit")
+ break
+
+
+
+class Instance(Resource):
+
+ def __init__(self, name, json_snippet, stack):
+ super(Instance, self).__init__(name, json_snippet, stack)
+ self.ipaddress = '0.0.0.0'
+
+ if not 'AvailabilityZone' in self.t['Properties']:
+ self.t['Properties']['AvailabilityZone'] = 'nova'
+ self.itype_oflavor = {'t1.micro': 'm1.tiny',
+ 'm1.small': 'm1.small',
+ 'm1.medium': 'm1.medium',
+ 'm1.large': 'm1.large',
+ 'm1.xlarge': 'm1.tiny', # TODO(sdake)
+ 'm2.xlarge': 'm1.xlarge',
+ '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):
+
+ res = None
+ if key == 'AvailabilityZone':
+ res = self.t['Properties']['AvailabilityZone']
+ elif key == 'PublicIp':
+ res = self.ipaddress
+ else:
+ raise exception.InvalidTemplateAttribute(resource=self.name,
+ key=key)
+
+ # TODO(asalkeld) PrivateDnsName, PublicDnsName & PrivateIp
+
+ logger.info('%s.GetAtt(%s) == %s' % (self.name, key, res))
+ return unicode(res)
+
+ def _build_userdata(self, userdata):
+ # Build mime multipart data blob for cloudinit userdata
+ mime_blob = MIMEMultipart()
+ fp = open('%s/%s' % (cloudinit_path, 'config'), 'r')
+ msg = MIMEText(fp.read(), _subtype='cloud-config')
+ fp.close()
+ msg.add_header('Content-Disposition', 'attachment',
+ filename='cloud-config')
+ mime_blob.attach(msg)
+
+ fp = open('%s/%s' % (cloudinit_path, 'part-handler.py'), 'r')
+ msg = MIMEText(fp.read(), _subtype='part-handler')
+ fp.close()
+ msg.add_header('Content-Disposition', 'attachment',
+ filename='part-handler.py')
+ mime_blob.attach(msg)
+
+ msg = MIMEText(json.dumps(self.t['Metadata']),
+ _subtype='x-cfninitdata')
+ msg.add_header('Content-Disposition', 'attachment',
+ filename='cfn-init-data')
+ mime_blob.attach(msg)
+
+ msg = MIMEText(userdata, _subtype='x-shellscript')
+ msg.add_header('Content-Disposition', 'attachment', filename='startup')
+ mime_blob.attach(msg)
+ return mime_blob.as_string()
+
+ def create(self):
+ def _null_callback(p, n, out):
+ """
+ Method to silence the default M2Crypto.RSA.gen_key output.
+ """
+ pass
+
+ if self.state != None:
+ return
+ self.state_set(self.CREATE_IN_PROGRESS)
+ Resource.create(self)
+ props = self.t['Properties']
+ if not 'KeyName' in props:
+ raise exception.UserParameterMissing(key='KeyName')
+ if not 'InstanceType' in props:
+ raise exception.UserParameterMissing(key='InstanceType')
+ if not 'ImageId' in props:
+ raise exception.UserParameterMissing(key='ImageId')
+
+ security_groups = props.get('SecurityGroups')
+
+ userdata = self.t['Properties']['UserData']
+
+ flavor = self.itype_oflavor[self.t['Properties']['InstanceType']]
+ key_name = self.t['Properties']['KeyName']
+
+ keypairs = self.nova().keypairs.list()
+ key_exists = False
+ for k in keypairs:
+ if k.name == key_name:
+ # cool it exists
+ key_exists = True
+ break
+ if not key_exists:
+ raise exception.UserKeyPairMissing(key_name=key_name)
+
+ image_name = self.t['Properties']['ImageId']
+ image_id = None
+ image_list = self.nova().images.list()
+ for o in image_list:
+ if o.name == image_name:
+ image_id = o.id
+
+ if image_id is None:
+ logger.info("Image %s was not found in glance" % image_name)
+ raise exception.ImageNotFound(image_name=image_name)
+
+ flavor_list = self.nova().flavors.list()
+ for o in flavor_list:
+ if o.name == flavor:
+ flavor_id = o.id
+
+ server_userdata = self._build_userdata(userdata)
+ server = self.nova().servers.create(name=self.name, image=image_id,
+ flavor=flavor_id,
+ key_name=key_name,
+ security_groups=security_groups,
+ userdata=server_userdata)
+ while server.status == 'BUILD':
+ server.get()
+ 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
+ else:
+ self.state_set(self.CREATE_FAILED)
+
+ 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:
+ return
+ self.state_set(self.DELETE_IN_PROGRESS)
+ Resource.delete(self)
+ server = self.nova().servers.get(self.instance_id)
+ server.delete()
+ self.instance_id = None
+ self.state_set(self.DELETE_COMPLETE)
from heat.common import exception
from heat.engine import resources
+from heat.engine import instance
+from heat.engine import volume
+from heat.engine import eip
+from heat.engine import security_group
+from heat.engine import wait_condition
+
from heat.db import api as db_api
-logger = logging.getLogger('heat.engine.parser')
+logger = logging.getLogger(__file__)
class Stack(object):
self.doc = None
self.name = stack_name
self.parsed_template_id = 0
+ self.metadata_server = 'http://10.0.0.1'
self.parms['AWS::StackName'] = {"Description": "AWS StackName",
"Type": "String",
for r in self.t['Resources']:
type = self.t['Resources'][r]['Type']
if type == 'AWS::EC2::Instance':
- self.resources[r] = resources.Instance(r,
+ self.resources[r] = instance.Instance(r,
self.t['Resources'][r], self)
elif type == 'AWS::EC2::Volume':
- self.resources[r] = resources.Volume(r,
+ self.resources[r] = volume.Volume(r,
self.t['Resources'][r], self)
elif type == 'AWS::EC2::VolumeAttachment':
- self.resources[r] = resources.VolumeAttachment(r,
+ self.resources[r] = volume.VolumeAttachment(r,
self.t['Resources'][r], self)
elif type == 'AWS::EC2::EIP':
- self.resources[r] = resources.ElasticIp(r,
+ self.resources[r] = eip.ElasticIp(r,
self.t['Resources'][r], self)
elif type == 'AWS::EC2::EIPAssociation':
- self.resources[r] = resources.ElasticIpAssociation(r,
+ self.resources[r] = eip.ElasticIpAssociation(r,
self.t['Resources'][r], self)
elif type == 'AWS::EC2::SecurityGroup':
- self.resources[r] = resources.SecurityGroup(r,
+ self.resources[r] = security_group.SecurityGroup(r,
+ self.t['Resources'][r], self)
self.t['Resources'][r], self)
else:
self.resources[r] = resources.GenericResource(r,
import string
import json
import sys
-from email import encoders
-from email.message import Message
-from email.mime.base import MIMEBase
-from email.mime.multipart import MIMEMultipart
-from email.mime.text import MIMEText
from novaclient.v1_1 import client
from novaclient.exceptions import BadRequest
from heat.db import api as db_api
from heat.common.config import HeatEngineConfigOpts
-logger = logging.getLogger('heat.engine.resources')
+logger = logging.getLogger(__file__)
# If ../heat/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
super(GenericResource, self).create()
logger.info('creating GenericResource %s' % self.name)
self.state_set(self.CREATE_COMPLETE)
-
-
-class SecurityGroup(Resource):
-
- def __init__(self, name, json_snippet, stack):
- super(SecurityGroup, self).__init__(name, json_snippet, stack)
- self.instance_id = ''
-
- if 'GroupDescription' in self.t['Properties']:
- self.description = self.t['Properties']['GroupDescription']
- else:
- self.description = ''
-
- def create(self):
- if self.state != None:
- return
- self.state_set(self.CREATE_IN_PROGRESS)
- Resource.create(self)
- sec = None
-
- groups = self.nova().security_groups.list()
- for group in groups:
- if group.name == self.name:
- sec = group
- break
-
- if not sec:
- sec = self.nova().security_groups.create(self.name,
- self.description)
-
- self.instance_id_set(sec.id)
-
- if 'SecurityGroupIngress' in self.t['Properties']:
- rules_client = self.nova().security_group_rules
- for i in self.t['Properties']['SecurityGroupIngress']:
- try:
- rule = rules_client.create(sec.id,
- i['IpProtocol'],
- i['FromPort'],
- i['ToPort'],
- i['CidrIp'])
- except BadRequest as ex:
- if ex.message.find('already exists') >= 0:
- # no worries, the rule is already there
- pass
- else:
- # unexpected error
- raise
-
- self.state_set(self.CREATE_COMPLETE)
-
- def delete(self):
- if self.state == self.DELETE_IN_PROGRESS or \
- self.state == self.DELETE_COMPLETE:
- return
-
- self.state_set(self.DELETE_IN_PROGRESS)
- Resource.delete(self)
-
- if self.instance_id != None:
- sec = self.nova().security_groups.get(self.instance_id)
-
- for rule in sec.rules:
- self.nova().security_group_rules.delete(rule['id'])
-
- self.nova().security_groups.delete(sec)
- self.instance_id = None
-
- self.state_set(self.DELETE_COMPLETE)
-
- def FnGetRefId(self):
- return unicode(self.name)
-
-
-class ElasticIp(Resource):
- def __init__(self, name, json_snippet, stack):
- super(ElasticIp, self).__init__(name, json_snippet, stack)
- self.ipaddress = ''
-
- if 'Domain' in self.t['Properties']:
- logger.warn('*** can\'t support Domain %s yet' % \
- (self.t['Properties']['Domain']))
-
- def create(self):
- """Allocate a floating IP for the current tenant."""
- if self.state != None:
- 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 reload(self):
- '''
- get the ipaddress here
- '''
- if self.instance_id != None:
- try:
- ips = self.nova().floating_ips.get(self.instance_id)
- self.ipaddress = ips.ip
- except Exception as ex:
- logger.warn("Error getting floating IPs: %s" % str(ex))
-
- Resource.reload(self)
-
- def delete(self):
- """De-allocate a floating IP."""
- if self.state == self.DELETE_IN_PROGRESS or \
- self.state == self.DELETE_COMPLETE:
- return
-
- self.state_set(self.DELETE_IN_PROGRESS)
- Resource.delete(self)
-
- if self.instance_id != None:
- self.nova().floating_ips.delete(self.instance_id)
-
- self.state_set(self.DELETE_COMPLETE)
-
- def FnGetRefId(self):
- return unicode(self.ipaddress)
-
- def FnGetAtt(self, key):
- if key == 'AllocationId':
- return unicode(self.instance_id)
- else:
- raise exception.InvalidTemplateAttribute(resource=self.name,
- key=key)
-
-
-class ElasticIpAssociation(Resource):
- def __init__(self, name, json_snippet, stack):
- super(ElasticIpAssociation, self).__init__(name, json_snippet, stack)
-
- def FnGetRefId(self):
- if not 'EIP' in self.t['Properties']:
- return unicode('0.0.0.0')
- else:
- return unicode(self.t['Properties']['EIP'])
-
- def create(self):
- """Add a floating IP address to a server."""
-
- if self.state != None:
- return
- self.state_set(self.CREATE_IN_PROGRESS)
- super(ElasticIpAssociation, self).create()
- logger.debug('ElasticIpAssociation %s.add_floating_ip(%s)' % \
- (self.t['Properties']['InstanceId'],
- self.t['Properties']['EIP']))
-
- server = self.nova().servers.get(self.t['Properties']['InstanceId'])
- server.add_floating_ip(self.t['Properties']['EIP'])
- self.instance_id_set(self.t['Properties']['EIP'])
- self.state_set(self.CREATE_COMPLETE)
-
- def delete(self):
- """Remove a floating IP address from a server."""
- if self.state == self.DELETE_IN_PROGRESS or \
- self.state == self.DELETE_COMPLETE:
- return
-
- self.state_set(self.DELETE_IN_PROGRESS)
- Resource.delete(self)
-
- server = self.nova().servers.get(self.t['Properties']['InstanceId'])
- server.remove_floating_ip(self.t['Properties']['EIP'])
-
- self.state_set(self.DELETE_COMPLETE)
-
-
-class Volume(Resource):
- def __init__(self, name, json_snippet, stack):
- super(Volume, self).__init__(name, json_snippet, stack)
-
- def create(self):
- if self.state != None:
- return
- self.state_set(self.CREATE_IN_PROGRESS)
- super(Volume, self).create()
-
- vol = self.nova('volume').volumes.create(self.t['Properties']['Size'],
- display_name=self.name,
- display_description=self.name)
-
- while vol.status == 'creating':
- eventlet.sleep(1)
- vol.get()
- if vol.status == 'available':
- self.instance_id_set(vol.id)
- self.state_set(self.CREATE_COMPLETE)
- else:
- self.state_set(self.CREATE_FAILED)
-
- def delete(self):
- if self.state == self.DELETE_IN_PROGRESS or \
- self.state == self.DELETE_COMPLETE:
- return
-
- if self.instance_id != None:
- vol = self.nova('volume').volumes.get(self.instance_id)
- if vol.status == 'in-use':
- logger.warn('cant delete volume when in-use')
- return
-
- self.state_set(self.DELETE_IN_PROGRESS)
- Resource.delete(self)
-
- if self.instance_id != None:
- self.nova('volume').volumes.delete(self.instance_id)
- self.state_set(self.DELETE_COMPLETE)
-
-
-class VolumeAttachment(Resource):
- def __init__(self, name, json_snippet, stack):
- super(VolumeAttachment, self).__init__(name, json_snippet, stack)
-
- def create(self):
-
- if self.state != None:
- return
- self.state_set(self.CREATE_IN_PROGRESS)
- super(VolumeAttachment, self).create()
-
- server_id = self.t['Properties']['InstanceId']
- volume_id = self.t['Properties']['VolumeId']
- logger.warn('Attaching InstanceId %s VolumeId %s Device %s' %
- (server_id, volume_id, self.t['Properties']['Device']))
- volapi = self.nova().volumes
- va = volapi.create_server_volume(server_id=server_id,
- volume_id=volume_id,
- device=self.t['Properties']['Device'])
-
- vol = self.nova('volume').volumes.get(va.id)
- while vol.status == 'available' or vol.status == 'attaching':
- eventlet.sleep(1)
- vol.get()
- if vol.status == 'in-use':
- self.instance_id_set(va.id)
- self.state_set(self.CREATE_COMPLETE)
- else:
- self.state_set(self.CREATE_FAILED)
-
- def delete(self):
- if self.state == self.DELETE_IN_PROGRESS or \
- self.state == self.DELETE_COMPLETE:
- return
- self.state_set(self.DELETE_IN_PROGRESS)
- Resource.delete(self)
-
- server_id = self.t['Properties']['InstanceId']
- volume_id = self.t['Properties']['VolumeId']
- logger.info('VolumeAttachment un-attaching %s %s' % \
- (server_id, volume_id))
-
- volapi = self.nova().volumes
- volapi.delete_server_volume(server_id,
- volume_id)
-
- vol = self.nova('volume').volumes.get(volume_id)
- logger.info('un-attaching %s, status %s' % (volume_id, vol.status))
- while vol.status == 'in-use':
- logger.info('trying to un-attach %s, but still %s' %
- (volume_id, vol.status))
- eventlet.sleep(1)
- try:
- volapi.delete_server_volume(server_id,
- volume_id)
- except Exception:
- pass
- vol.get()
-
- self.state_set(self.DELETE_COMPLETE)
-
-
-class Instance(Resource):
-
- def __init__(self, name, json_snippet, stack):
- super(Instance, self).__init__(name, json_snippet, stack)
- self.ipaddress = '0.0.0.0'
-
- if not 'AvailabilityZone' in self.t['Properties']:
- self.t['Properties']['AvailabilityZone'] = 'nova'
- self.itype_oflavor = {'t1.micro': 'm1.tiny',
- 'm1.small': 'm1.small',
- 'm1.medium': 'm1.medium',
- 'm1.large': 'm1.large',
- 'm1.xlarge': 'm1.tiny', # TODO(sdake)
- 'm2.xlarge': 'm1.xlarge',
- '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):
-
- res = None
- if key == 'AvailabilityZone':
- res = self.t['Properties']['AvailabilityZone']
- elif key == 'PublicIp':
- res = self.ipaddress
- else:
- raise exception.InvalidTemplateAttribute(resource=self.name,
- key=key)
-
- # TODO(asalkeld) PrivateDnsName, PublicDnsName & PrivateIp
-
- logger.info('%s.GetAtt(%s) == %s' % (self.name, key, res))
- return unicode(res)
-
- def _build_userdata(self, userdata):
- # Build mime multipart data blob for cloudinit userdata
- mime_blob = MIMEMultipart()
- fp = open('%s/%s' % (cloudinit_path, 'config'), 'r')
- msg = MIMEText(fp.read(), _subtype='cloud-config')
- fp.close()
- msg.add_header('Content-Disposition', 'attachment',
- filename='cloud-config')
- mime_blob.attach(msg)
-
- fp = open('%s/%s' % (cloudinit_path, 'part-handler.py'), 'r')
- msg = MIMEText(fp.read(), _subtype='part-handler')
- fp.close()
- msg.add_header('Content-Disposition', 'attachment',
- filename='part-handler.py')
- mime_blob.attach(msg)
-
- msg = MIMEText(json.dumps(self.t['Metadata']),
- _subtype='x-cfninitdata')
- msg.add_header('Content-Disposition', 'attachment',
- filename='cfn-init-data')
- mime_blob.attach(msg)
-
- msg = MIMEText(userdata, _subtype='x-shellscript')
- msg.add_header('Content-Disposition', 'attachment', filename='startup')
- mime_blob.attach(msg)
- return mime_blob.as_string()
-
- def create(self):
- def _null_callback(p, n, out):
- """
- Method to silence the default M2Crypto.RSA.gen_key output.
- """
- pass
-
- if self.state != None:
- return
- self.state_set(self.CREATE_IN_PROGRESS)
- Resource.create(self)
- props = self.t['Properties']
- if not 'KeyName' in props:
- raise exception.UserParameterMissing(key='KeyName')
- if not 'InstanceType' in props:
- raise exception.UserParameterMissing(key='InstanceType')
- if not 'ImageId' in props:
- raise exception.UserParameterMissing(key='ImageId')
-
- security_groups = props.get('SecurityGroups')
-
- userdata = self.t['Properties']['UserData']
-
- flavor = self.itype_oflavor[self.t['Properties']['InstanceType']]
- distro_name = self.stack.parameter_get('LinuxDistribution')
- key_name = self.t['Properties']['KeyName']
-
- keypairs = self.nova().keypairs.list()
- key_exists = False
- for k in keypairs:
- if k.name == key_name:
- # cool it exists
- key_exists = True
- break
- if not key_exists:
- raise exception.UserKeyPairMissing(key_name=key_name)
-
- image_name = self.t['Properties']['ImageId']
- image_id = None
- image_list = self.nova().images.list()
- for o in image_list:
- if o.name == image_name:
- image_id = o.id
-
- if image_id is None:
- logger.info("Image %s was not found in glance" % image_name)
- raise exception.ImageNotFound(image_name=image_name)
-
- flavor_list = self.nova().flavors.list()
- for o in flavor_list:
- if o.name == flavor:
- flavor_id = o.id
-
- server_userdata = self._build_userdata(userdata)
- server = self.nova().servers.create(name=self.name, image=image_id,
- flavor=flavor_id,
- key_name=key_name,
- security_groups=security_groups,
- userdata=server_userdata)
- while server.status == 'BUILD':
- server.get()
- 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
- else:
- self.state_set(self.CREATE_FAILED)
-
- 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:
- return
- self.state_set(self.DELETE_IN_PROGRESS)
- Resource.delete(self)
- server = self.nova().servers.get(self.instance_id)
- server.delete()
- self.instance_id = None
- self.state_set(self.DELETE_COMPLETE)
--- /dev/null
+# 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 eventlet
+import logging
+import os
+
+from novaclient.exceptions import BadRequest
+from heat.common import exception
+from heat.engine.resources import Resource
+
+logger = logging.getLogger(__file__)
+
+
+class SecurityGroup(Resource):
+
+ def __init__(self, name, json_snippet, stack):
+ super(SecurityGroup, self).__init__(name, json_snippet, stack)
+ self.instance_id = ''
+
+ if 'GroupDescription' in self.t['Properties']:
+ self.description = self.t['Properties']['GroupDescription']
+ else:
+ self.description = ''
+
+ def create(self):
+ if self.state != None:
+ return
+ self.state_set(self.CREATE_IN_PROGRESS)
+ Resource.create(self)
+ sec = None
+
+ groups = self.nova().security_groups.list()
+ for group in groups:
+ if group.name == self.name:
+ sec = group
+ break
+
+ if not sec:
+ sec = self.nova().security_groups.create(self.name,
+ self.description)
+
+ self.instance_id_set(sec.id)
+
+ if 'SecurityGroupIngress' in self.t['Properties']:
+ rules_client = self.nova().security_group_rules
+ for i in self.t['Properties']['SecurityGroupIngress']:
+ try:
+ rule = rules_client.create(sec.id,
+ i['IpProtocol'],
+ i['FromPort'],
+ i['ToPort'],
+ i['CidrIp'])
+ except BadRequest as ex:
+ if ex.message.find('already exists') >= 0:
+ # no worries, the rule is already there
+ pass
+ else:
+ # unexpected error
+ raise
+
+ self.state_set(self.CREATE_COMPLETE)
+
+ def delete(self):
+ if self.state == self.DELETE_IN_PROGRESS or \
+ self.state == self.DELETE_COMPLETE:
+ return
+
+ self.state_set(self.DELETE_IN_PROGRESS)
+ Resource.delete(self)
+
+ if self.instance_id != None:
+ sec = self.nova().security_groups.get(self.instance_id)
+
+ for rule in sec.rules:
+ self.nova().security_group_rules.delete(rule['id'])
+
+ self.nova().security_groups.delete(sec)
+ self.instance_id = None
+
+ self.state_set(self.DELETE_COMPLETE)
+
+ def FnGetRefId(self):
+ return unicode(self.name)
+
+
--- /dev/null
+# 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 eventlet
+import logging
+import os
+
+from heat.common import exception
+from heat.engine.resources import Resource
+
+logger = logging.getLogger(__file__)
+
+
+class Volume(Resource):
+ def __init__(self, name, json_snippet, stack):
+ super(Volume, self).__init__(name, json_snippet, stack)
+
+ def create(self):
+ if self.state != None:
+ return
+ self.state_set(self.CREATE_IN_PROGRESS)
+ super(Volume, self).create()
+
+ vol = self.nova('volume').volumes.create(self.t['Properties']['Size'],
+ display_name=self.name,
+ display_description=self.name)
+
+ while vol.status == 'creating':
+ eventlet.sleep(1)
+ vol.get()
+ if vol.status == 'available':
+ self.instance_id_set(vol.id)
+ self.state_set(self.CREATE_COMPLETE)
+ else:
+ self.state_set(self.CREATE_FAILED)
+
+ def delete(self):
+ if self.state == self.DELETE_IN_PROGRESS or \
+ self.state == self.DELETE_COMPLETE:
+ return
+
+ if self.instance_id != None:
+ vol = self.nova('volume').volumes.get(self.instance_id)
+ if vol.status == 'in-use':
+ logger.warn('cant delete volume when in-use')
+ return
+
+ self.state_set(self.DELETE_IN_PROGRESS)
+ Resource.delete(self)
+
+ if self.instance_id != None:
+ self.nova('volume').volumes.delete(self.instance_id)
+ self.state_set(self.DELETE_COMPLETE)
+
+
+class VolumeAttachment(Resource):
+ def __init__(self, name, json_snippet, stack):
+ super(VolumeAttachment, self).__init__(name, json_snippet, stack)
+
+ def create(self):
+
+ if self.state != None:
+ return
+ self.state_set(self.CREATE_IN_PROGRESS)
+ super(VolumeAttachment, self).create()
+
+ server_id = self.t['Properties']['InstanceId']
+ volume_id = self.t['Properties']['VolumeId']
+ logger.warn('Attaching InstanceId %s VolumeId %s Device %s' %
+ (server_id, volume_id, self.t['Properties']['Device']))
+ volapi = self.nova().volumes
+ va = volapi.create_server_volume(server_id=server_id,
+ volume_id=volume_id,
+ device=self.t['Properties']['Device'])
+
+ vol = self.nova('volume').volumes.get(va.id)
+ while vol.status == 'available' or vol.status == 'attaching':
+ eventlet.sleep(1)
+ vol.get()
+ if vol.status == 'in-use':
+ self.instance_id_set(va.id)
+ self.state_set(self.CREATE_COMPLETE)
+ else:
+ self.state_set(self.CREATE_FAILED)
+
+ def delete(self):
+ if self.state == self.DELETE_IN_PROGRESS or \
+ self.state == self.DELETE_COMPLETE:
+ return
+ self.state_set(self.DELETE_IN_PROGRESS)
+ Resource.delete(self)
+
+ server_id = self.t['Properties']['InstanceId']
+ volume_id = self.t['Properties']['VolumeId']
+ logger.info('VolumeAttachment un-attaching %s %s' % \
+ (server_id, volume_id))
+
+ volapi = self.nova().volumes
+ volapi.delete_server_volume(server_id,
+ volume_id)
+
+ vol = self.nova('volume').volumes.get(volume_id)
+ logger.info('un-attaching %s, status %s' % (volume_id, vol.status))
+ while vol.status == 'in-use':
+ logger.info('trying to un-attach %s, but still %s' %
+ (volume_id, vol.status))
+ eventlet.sleep(1)
+ try:
+ volapi.delete_server_volume(server_id,
+ volume_id)
+ except Exception:
+ pass
+ vol.get()
+
+ self.state_set(self.DELETE_COMPLETE)
+
from heat.tests.v1_1 import fakes
from heat.engine import resources
+from heat.engine import instance
import heat.db as db_api
from heat.engine import parser
db_api.resource_get_by_name_and_stack(None, 'test_resource_name',\
stack).AndReturn(None)
- self.m.StubOutWithMock(resources.Instance, 'nova')
- resources.Instance.nova().AndReturn(self.fc)
- resources.Instance.nova().AndReturn(self.fc)
- resources.Instance.nova().AndReturn(self.fc)
- resources.Instance.nova().AndReturn(self.fc)
+ self.m.StubOutWithMock(instance.Instance, 'nova')
+ instance.Instance.nova().AndReturn(self.fc)
+ instance.Instance.nova().AndReturn(self.fc)
+ instance.Instance.nova().AndReturn(self.fc)
+ instance.Instance.nova().AndReturn(self.fc)
#Need to find an easier way
userdata = t['Resources']['WebServer']['Properties']['UserData']
t['Resources']['WebServer']['Properties']['ImageId'] = 'CentOS 5.2'
t['Resources']['WebServer']['Properties']['InstanceType'] = \
'256 MB Server'
- instance = resources.Instance('test_resource_name',\
- t['Resources']['WebServer'], stack)
+ inst = instance.Instance('test_resource_name',\
+ t['Resources']['WebServer'], stack)
- server_userdata = instance._build_userdata(json.dumps(userdata))
+ server_userdata = inst._build_userdata(json.dumps(userdata))
self.m.StubOutWithMock(self.fc.servers, 'create')
self.fc.servers.create(image=1, flavor=1, key_name='test',\
name='test_resource_name', security_groups=None,\
AndReturn(self.fc.servers.list()[1])
self.m.ReplayAll()
- instance.itype_oflavor['256 MB Server'] = '256 MB Server'
- instance.create()
+ inst.itype_oflavor['256 MB Server'] = '256 MB Server'
+ inst.create()
self.m.ReplayAll()
- instance.itype_oflavor['256 MB Server'] = '256 MB Server'
- instance.create()
+ inst.itype_oflavor['256 MB Server'] = '256 MB Server'
+ inst.create()
# this makes sure the auto increment worked on instance creation
- assert(instance.id > 0)
+ assert(inst.id > 0)
def test_initialize_instance_from_template_and_delete(self):
f = open('../../templates/WordPress_Single_Instance_gold.template')
db_api.resource_get_by_name_and_stack(None, 'test_resource_name',\
stack).AndReturn(None)
- self.m.StubOutWithMock(resources.Instance, 'nova')
- resources.Instance.nova().AndReturn(self.fc)
- resources.Instance.nova().AndReturn(self.fc)
- resources.Instance.nova().AndReturn(self.fc)
- resources.Instance.nova().AndReturn(self.fc)
+ self.m.StubOutWithMock(instance.Instance, 'nova')
+ instance.Instance.nova().AndReturn(self.fc)
+ instance.Instance.nova().AndReturn(self.fc)
+ instance.Instance.nova().AndReturn(self.fc)
+ instance.Instance.nova().AndReturn(self.fc)
#Need to find an easier way
userdata = t['Resources']['WebServer']['Properties']['UserData']
t['Resources']['WebServer']['Properties']['ImageId'] = 'CentOS 5.2'
t['Resources']['WebServer']['Properties']['InstanceType'] = \
'256 MB Server'
- instance = resources.Instance('test_resource_name',\
- t['Resources']['WebServer'], stack)
+ inst = instance.Instance('test_resource_name',\
+ t['Resources']['WebServer'], stack)
- server_userdata = instance._build_userdata(json.dumps(userdata))
+ server_userdata = inst._build_userdata(json.dumps(userdata))
self.m.StubOutWithMock(self.fc.servers, 'create')
self.fc.servers.create(image=1, flavor=1, key_name='test',\
name='test_resource_name', security_groups=None,\
AndReturn(self.fc.servers.list()[1])
self.m.ReplayAll()
- instance.itype_oflavor['256 MB Server'] = '256 MB Server'
- instance.create()
+ inst.itype_oflavor['256 MB Server'] = '256 MB Server'
+ inst.create()
self.m.ReplayAll()
- instance.instance_id = 1234
- instance.itype_oflavor['256 MB Server'] = '256 MB Server'
- instance.create()
+ inst.instance_id = 1234
+ inst.itype_oflavor['256 MB Server'] = '256 MB Server'
+ inst.create()
- instance.delete()
- assert(instance.instance_id == None)
- assert(instance.state == instance.DELETE_COMPLETE)
+ inst.delete()
+ assert(inst.instance_id == None)
+ assert(inst.state == inst.DELETE_COMPLETE)
# allows testing of the test directly, shown below
if __name__ == '__main__':