Split the resourses up into seperate files.

Signed-off-by: Angus Salkeld <asalkeld@redhat.com>
This commit is contained in:
Angus Salkeld 2012-05-03 14:34:13 +10:00
parent 9de4d852dc
commit 20fc3c76a2
7 changed files with 606 additions and 481 deletions

126
heat/engine/eip.py Normal file
View File

@ -0,0 +1,126 @@
# 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)

208
heat/engine/instance.py Normal file
View File

@ -0,0 +1,208 @@
# 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)

View File

@ -19,9 +19,15 @@ import logging
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):
@ -42,6 +48,7 @@ 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",
@ -66,22 +73,23 @@ class Stack(object):
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,

View File

@ -20,11 +20,6 @@ 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.v1_1 import client
from novaclient.exceptions import BadRequest
@ -34,7 +29,7 @@ from heat.common import exception
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]),
@ -203,442 +198,3 @@ class GenericResource(Resource):
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)

View File

@ -0,0 +1,98 @@
# 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)

128
heat/engine/volume.py Normal file
View File

@ -0,0 +1,128 @@
# 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)

View File

@ -12,6 +12,7 @@ from nose import with_setup
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
@ -42,11 +43,11 @@ class ResourcesTest(unittest.TestCase):
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']
@ -56,10 +57,10 @@ class ResourcesTest(unittest.TestCase):
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,\
@ -67,16 +68,16 @@ class ResourcesTest(unittest.TestCase):
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')
@ -93,11 +94,11 @@ class ResourcesTest(unittest.TestCase):
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']
@ -107,10 +108,10 @@ class ResourcesTest(unittest.TestCase):
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,\
@ -118,18 +119,18 @@ class ResourcesTest(unittest.TestCase):
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__':