You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
10 KiB
263 lines
10 KiB
# 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 |
|
import json |
|
import sys |
|
from email.mime.multipart import MIMEMultipart |
|
from email.mime.text import MIMEText |
|
from novaclient.exceptions import NotFound |
|
|
|
import heat |
|
from heat.engine.resources import Resource |
|
from heat.common import exception |
|
|
|
logger = logging.getLogger('heat.engine.instance') |
|
|
|
|
|
class Restarter(Resource): |
|
properties_schema = {'InstanceId': {'Type': 'String', |
|
'Required': True}} |
|
|
|
def _find_resource(self, instance_id): |
|
''' |
|
Return the resource with the specified instance ID, or None if it |
|
cannot be found. |
|
''' |
|
for resource in self.stack: |
|
if resource.instance_id == instance_id: |
|
return resource |
|
return None |
|
|
|
def alarm(self): |
|
self.calculate_properties() |
|
victim = self._find_resource(self.properties['InstanceId']) |
|
|
|
if victim is None: |
|
logger.info('%s Alarm, can not find instance %s' % |
|
(self.name, self.properties['InstanceId'])) |
|
return |
|
|
|
logger.info('%s Alarm, restarting resource: %s' % |
|
(self.name, victim.name)) |
|
self.stack.restart_resource(victim.name) |
|
|
|
|
|
class Instance(Resource): |
|
# AWS does not require KeyName and InstanceType but we seem to |
|
properties_schema = {'ImageId': {'Type': 'String', |
|
'Required': True}, |
|
'InstanceType': {'Type': 'String', |
|
'Required': True}, |
|
'KeyName': {'Type': 'String', |
|
'Required': True}, |
|
'AvailabilityZone': {'Type': 'String', |
|
'Default': 'nova'}, |
|
'DisableApiTermination': {'Type': 'String', |
|
'Implemented': False}, |
|
'KernelId': {'Type': 'String', |
|
'Implemented': False}, |
|
'Monitoring': {'Type': 'Boolean', |
|
'Implemented': False}, |
|
'PlacementGroupName': {'Type': 'String', |
|
'Implemented': False}, |
|
'PrivateIpAddress': {'Type': 'String', |
|
'Implemented': False}, |
|
'RamDiskId': {'Type': 'String', |
|
'Implemented': False}, |
|
'SecurityGroups': {'Type': 'List'}, |
|
'SecurityGroupIds': {'Type': 'List', |
|
'Implemented': False}, |
|
'SourceDestCheck': {'Type': 'Boolean', |
|
'Implemented': False}, |
|
'SubnetId': {'Type': 'String', |
|
'Implemented': False}, |
|
'Tags': {'Type': 'List', |
|
'Implemented': False}, |
|
'Tenancy': {'Type': 'String', |
|
'AllowedValues': ['dedicated', 'default'], |
|
'Implemented': False}, |
|
'UserData': {'Type': 'String'}, |
|
'Volumes': {'Type': 'List', |
|
'Implemented': False}} |
|
|
|
def __init__(self, name, json_snippet, stack): |
|
super(Instance, self).__init__(name, json_snippet, stack) |
|
self.ipaddress = None |
|
self.mime_string = None |
|
|
|
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 _set_ipaddress(self, networks): |
|
''' |
|
Read the server's IP address from a list of networks provided by Nova |
|
''' |
|
# Just record the first ipaddress |
|
for n in networks: |
|
self.ipaddress = networks[n][0] |
|
break |
|
|
|
def _ipaddress(self): |
|
''' |
|
Return the server's IP address, fetching it from Nova if necessary |
|
''' |
|
if self.ipaddress is None: |
|
try: |
|
server = self.nova().servers.get(self.instance_id) |
|
except NotFound as ex: |
|
logger.warn('Instance IP address not found (%s)' % str(ex)) |
|
else: |
|
self._set_ipaddress(server.networks) |
|
|
|
return self.ipaddress or '0.0.0.0' |
|
|
|
def FnGetAtt(self, key): |
|
res = None |
|
if key == 'AvailabilityZone': |
|
res = self.properties['AvailabilityZone'] |
|
elif key == 'PublicIp': |
|
res = self._ipaddress() |
|
elif key == 'PrivateDnsName': |
|
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): |
|
if not self.mime_string: |
|
# Build mime multipart data blob for cloudinit userdata |
|
|
|
def make_subpart(content, filename, subtype=None): |
|
if subtype is None: |
|
subtype = os.path.splitext(filename)[0] |
|
msg = MIMEText(content, _subtype=subtype) |
|
msg.add_header('Content-Disposition', 'attachment', |
|
filename=filename) |
|
return msg |
|
|
|
def read_cloudinit_file(fn): |
|
with open(os.path.join(heat.__path__[0], 'cloudinit', fn), |
|
'r') as fp: |
|
return fp.read() |
|
|
|
attachments = [(read_cloudinit_file('config'), 'cloud-config'), |
|
(read_cloudinit_file('part-handler.py'), |
|
'part-handler.py'), |
|
(userdata, 'startup', 'x-shellscript')] |
|
|
|
if 'Metadata' in self.t: |
|
metadata = self.parsed_template()['Metadata'] |
|
attachments.append((json.dumps(metadata), |
|
'cfn-init-data', 'x-cfninitdata')) |
|
|
|
if self.stack.metadata_server: |
|
attachments.append((self.stack.metadata_server, |
|
'cfn-metadata-server', 'x-cfninitdata')) |
|
|
|
subparts = [make_subpart(*args) for args in attachments] |
|
mime_blob = MIMEMultipart(_subparts=subparts) |
|
|
|
self.mime_string = mime_blob.as_string() |
|
|
|
return self.mime_string |
|
|
|
def handle_create(self): |
|
security_groups = self.properties.get('SecurityGroups') |
|
userdata = self.properties['UserData'] |
|
flavor = self.itype_oflavor[self.properties['InstanceType']] |
|
key_name = self.properties['KeyName'] |
|
|
|
keypairs = [k.name for k in self.nova().keypairs.list()] |
|
if key_name not in keypairs: |
|
raise exception.UserKeyPairMissing(key_name=key_name) |
|
|
|
image_name = self.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._set_ipaddress(server.networks) |
|
else: |
|
raise exception.Error('%s instance[%s] status[%s]' % |
|
('nova reported unexpected', |
|
self.name, server.status)) |
|
|
|
def validate(self): |
|
''' |
|
Validate any of the provided params |
|
''' |
|
res = Resource.validate(self) |
|
if res: |
|
return res |
|
#check validity of key |
|
if self.stack.parms['KeyName']: |
|
keypairs = self.nova().keypairs.list() |
|
valid_key = False |
|
for k in keypairs: |
|
if k.name == self.stack.parms['KeyName']: |
|
valid_key = True |
|
if not valid_key: |
|
return {'Error': |
|
'Provided KeyName is not registered with nova'} |
|
return None |
|
|
|
def handle_delete(self): |
|
try: |
|
server = self.nova().servers.get(self.instance_id) |
|
except NotFound: |
|
pass |
|
else: |
|
server.delete() |
|
self.instance_id = None
|
|
|