23 changed files with 1329 additions and 427 deletions
@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python |
||||
# 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. |
||||
|
||||
""" |
||||
Heat Engine Server |
||||
""" |
||||
|
||||
import gettext |
||||
import os |
||||
import sys |
||||
|
||||
# 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) |
||||
|
||||
gettext.install('heat', unicode=1) |
||||
|
||||
from heat.common import config |
||||
from heat.common import wsgi |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
try: |
||||
conf = config.HeatConfigOpts() |
||||
conf() |
||||
|
||||
app = config.load_paste_app(conf) |
||||
|
||||
server = wsgi.Server() |
||||
server.start(app, conf, default_port=config.DEFAULT_PORT+1) |
||||
server.wait() |
||||
except RuntimeError, e: |
||||
sys.exit("ERROR: %s" % e) |
@ -0,0 +1,36 @@
|
||||
# Default minimal pipeline |
||||
[pipeline:heat-engine] |
||||
pipeline = context engineapp |
||||
|
||||
# Use the following pipeline for keystone auth |
||||
# i.e. in heat-engine.conf: |
||||
# [paste_deploy] |
||||
# flavor = keystone |
||||
# |
||||
[pipeline:heat-engine-keystone] |
||||
pipeline = authtoken auth-context engineapp |
||||
|
||||
[app:engineapp] |
||||
paste.app_factory = heat.common.wsgi:app_factory |
||||
heat.app_factory = heat.engine.api.v1:API |
||||
|
||||
[filter:context] |
||||
paste.filter_factory = heat.common.wsgi:filter_factory |
||||
heat.filter_factory = heat.common.context:ContextMiddleware |
||||
|
||||
[filter:authtoken] |
||||
paste.filter_factory = keystone.middleware.auth_token:filter_factory |
||||
service_protocol = http |
||||
service_host = 127.0.0.1 |
||||
service_port = 5000 |
||||
auth_host = 127.0.0.1 |
||||
auth_port = 35357 |
||||
auth_protocol = http |
||||
auth_uri = http://127.0.0.1:5000/ |
||||
admin_tenant_name = %SERVICE_TENANT_NAME% |
||||
admin_user = %SERVICE_USER% |
||||
admin_password = %SERVICE_PASSWORD% |
||||
|
||||
[filter:auth-context] |
||||
paste.filter_factory = heat.common.wsgi:filter_factory |
||||
heat.filter_factory = keystone.middleware.heat_auth_token:KeystoneContextMiddleware |
@ -0,0 +1,25 @@
|
||||
[DEFAULT] |
||||
# Show more verbose log output (sets INFO log level output) |
||||
verbose = True |
||||
|
||||
# Show debugging output in logs (sets DEBUG log level output) |
||||
debug = True |
||||
|
||||
# Address to bind the server to |
||||
bind_host = 0.0.0.0 |
||||
|
||||
# Port the bind the server to |
||||
bind_port = 8001 |
||||
|
||||
# Log to this file. Make sure the user running heat-api has |
||||
# permissions to write to this file! |
||||
log_file = /var/log/heat/engine.log |
||||
|
||||
# ================= Syslog Options ============================ |
||||
|
||||
# Send logs to syslog (/dev/log) instead of to file specified |
||||
# by `log_file` |
||||
use_syslog = False |
||||
|
||||
# Facility to use. If unset defaults to LOG_USER. |
||||
# syslog_log_facility = LOG_LOCAL0 |
@ -0,0 +1,19 @@
|
||||
# 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. |
||||
|
||||
SUPPORTED_PARAMS = ('StackName', 'TemplateBody', 'TemplateUrl','NotificationARNs', 'Parameters', |
||||
'Version', 'SignatureVersion', 'Timestamp', 'AWSAccessKeyId', |
||||
'Signature') |
||||
|
@ -0,0 +1,17 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2010-2011 OpenStack, LLC |
||||
# All Rights Reserved. |
||||
# |
||||
# 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. |
||||
|
@ -0,0 +1,15 @@
|
||||
# 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. |
||||
|
@ -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. |
||||
|
||||
""" |
||||
Reference implementation stacks server WSGI controller |
||||
""" |
||||
import json |
||||
import logging |
||||
|
||||
import webob |
||||
from webob.exc import (HTTPNotFound, |
||||
HTTPConflict, |
||||
HTTPBadRequest) |
||||
|
||||
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 |
||||
|
||||
|
||||
logger = logging.getLogger('heat.engine.api.v1.stacks') |
||||
|
||||
stack_db = {} |
||||
|
||||
class Controller(object): |
||||
''' |
||||
bla |
||||
''' |
||||
|
||||
def __init__(self, conf): |
||||
self.conf = conf |
||||
self.listener = capelistener.CapeEventListener() |
||||
|
||||
|
||||
def index(self, req, format='json'): |
||||
logger.info('format is %s' % format) |
||||
res = {'stacks': [] } |
||||
for s in stack_db: |
||||
mem = {} |
||||
mem['StackId'] = stack_db[s]['StackId'] |
||||
mem['StackName'] = s |
||||
mem['CreationTime'] = 'now' |
||||
try: |
||||
mem['TemplateDescription'] = stack_db[s]['Description'] |
||||
mem['StackStatus'] = stack_db[s]['StackStatus'] |
||||
except: |
||||
mem['TemplateDescription'] = 'No description' |
||||
mem['StackStatus'] = 'unknown' |
||||
res['stacks'].append(mem) |
||||
|
||||
return res |
||||
|
||||
def show(self, req, id): |
||||
res = {'stacks': [] } |
||||
if stack_db.has_key(id): |
||||
mem = {} |
||||
mem['StackId'] = stack_db[id]['StackId'] |
||||
mem['StackName'] = id |
||||
mem['CreationTime'] = 'TODO' |
||||
mem['LastUpdatedTime'] = 'TODO' |
||||
mem['NotificationARNs'] = 'TODO' |
||||
mem['Outputs'] = [{'Description': 'TODO', 'OutputKey': 'TODO', 'OutputValue': 'TODO' }] |
||||
mem['Parameters'] = stack_db[id]['Parameters'] |
||||
mem['StackStatusReason'] = 'TODO' |
||||
mem['TimeoutInMinutes'] = 'TODO' |
||||
try: |
||||
mem['TemplateDescription'] = stack_db[id]['Description'] |
||||
mem['StackStatus'] = stack_db[id]['StackStatus'] |
||||
except: |
||||
mem['TemplateDescription'] = 'No description' |
||||
mem['StackStatus'] = 'unknown' |
||||
res['stacks'].append(mem) |
||||
else: |
||||
return webob.exc.HTTPNotFound('No stack by that name') |
||||
|
||||
return res |
||||
|
||||
def create(self, req, body=None): |
||||
|
||||
if body is None: |
||||
msg = _("TemplateBody or TemplateUrl were not given.") |
||||
return webob.exc.HTTPBadRequest(explanation=msg) |
||||
|
||||
if stack_db.has_key(body['StackName']): |
||||
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']) |
||||
|
||||
return {'stack': {'id': body['StackName']}} |
||||
|
||||
def delete(self, req, id): |
||||
if not stack_db.has_key(id): |
||||
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 |
||||
|
||||
def create_resource(conf): |
||||
"""Stacks resource factory method.""" |
||||
deserializer = wsgi.JSONRequestDeserializer() |
||||
serializer = wsgi.JSONResponseSerializer() |
||||
return wsgi.Resource(Controller(conf), deserializer, serializer) |
@ -0,0 +1,61 @@
|
||||
# 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 logging |
||||
import os |
||||
import stat |
||||
|
||||
class CapeEventListener: |
||||
|
||||
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 cape_event_handle(self, sock, client_addr): |
||||
while True: |
||||
x = sock.recv(4096) |
||||
# TODO(asalkeld) format this event "nicely" |
||||
logger.info('%s' % x.strip('\n')) |
||||
if not x: break |
||||
|
||||
|
||||
|
@ -0,0 +1,167 @@
|
||||
# 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. |
||||
|
||||
""" |
||||
Simple client class to speak with any RESTful service that implements |
||||
the heat Engine API |
||||
""" |
||||
|
||||
import json |
||||
|
||||
from heat.common.client import BaseClient |
||||
from heat.common import crypt |
||||
from heat.common import config |
||||
from openstack.common import cfg |
||||
|
||||
from heat.cloudformations import * |
||||
|
||||
_CLIENT_CREDS = None |
||||
_CLIENT_HOST = None |
||||
_CLIENT_PORT = None |
||||
_CLIENT_KWARGS = {} |
||||
# AES key used to encrypt 'location' metadata |
||||
_METADATA_ENCRYPTION_KEY = None |
||||
|
||||
|
||||
engine_addr_opts = [ |
||||
cfg.StrOpt('engine_host', default='0.0.0.0'), |
||||
cfg.IntOpt('engine_port', default=8001), |
||||
] |
||||
engine_client_opts = [ |
||||
cfg.StrOpt('engine_client_protocol', default='http'), |
||||
cfg.StrOpt('engine_client_key_file'), |
||||
cfg.StrOpt('engine_client_cert_file'), |
||||
cfg.StrOpt('engine_client_ca_file'), |
||||
cfg.StrOpt('metadata_encryption_key'), |
||||
] |
||||
|
||||
class EngineClient(BaseClient): |
||||
|
||||
"""A client for the Engine stack metadata service""" |
||||
|
||||
DEFAULT_PORT = 8001 |
||||
|
||||
def __init__(self, host=None, port=None, metadata_encryption_key=None, |
||||
**kwargs): |
||||
""" |
||||
:param metadata_encryption_key: Key used to encrypt 'location' metadata |
||||
""" |
||||
self.metadata_encryption_key = metadata_encryption_key |
||||
# NOTE (dprince): by default base client overwrites host and port |
||||
# settings when using keystone. configure_via_auth=False disables |
||||
# this behaviour to ensure we still send requests to the Engine API |
||||
BaseClient.__init__(self, host, port, configure_via_auth=False, |
||||
**kwargs) |
||||
|
||||
def get_stacks(self, **kwargs): |
||||
""" |
||||
Returns a list of stack id/name mappings from Engine |
||||
|
||||
:param filters: dict of keys & expected values to filter results |
||||
:param marker: stack id after which to start page |
||||
:param limit: max number of stacks to return |
||||
:param sort_key: results will be ordered by this stack attribute |
||||
:param sort_dir: direction in which to to order results (asc, desc) |
||||
""" |
||||
params = self._extract_params(kwargs, SUPPORTED_PARAMS) |
||||
res = self.do_request("GET", "/stacks", params=params) |
||||
return json.loads(res.read())['stacks'] |
||||
|
||||
def show_stack(self, stack_id): |
||||
"""Returns a mapping of stack metadata from Engine""" |
||||
res = self.do_request("GET", "/stacks/%s" % stack_id) |
||||
data = json.loads(res.read())['stacks'] |
||||
return data |
||||
|
||||
|
||||
def create_stack(self, template): |
||||
""" |
||||
Tells engine about an stack's metadata |
||||
""" |
||||
headers = { |
||||
'Content-Type': 'application/json', |
||||
} |
||||
|
||||
res = self.do_request("POST", "/stacks", json.dumps(template), headers=headers) |
||||
data = json.loads(res.read()) |
||||
return data |
||||
|
||||
def update_stack(self, stack_id, template): |
||||
""" |
||||
Updates Engine's information about an stack |
||||
""" |
||||
headers = { |
||||
'Content-Type': 'application/json', |
||||
} |
||||
|
||||
res = self.do_request("PUT", "/stacks/%s" % (stack_id), json.dumps(template), headers) |
||||
data = json.loads(res.read()) |
||||
stack = data['stack'] |
||||
return stack |
||||
|
||||
def delete_stack(self, stack_name): |
||||
""" |
||||
Deletes Engine's information about an stack |
||||
""" |
||||
res = self.do_request("DELETE", "/stacks/%s" % stack_name) |
||||
return res |
||||
|
||||
def get_engine_addr(conf): |
||||
conf.register_opts(engine_addr_opts) |
||||
return (conf.engine_host, conf.engine_port) |
||||
|
||||
|
||||
def configure_engine_client(conf): |
||||
""" |
||||
Sets up a engine client for use in engine lookups |
||||
|
||||
:param conf: Configuration options coming from controller |
||||
""" |
||||
global _CLIENT_KWARGS, _CLIENT_HOST, _CLIENT_PORT, _METADATA_ENCRYPTION_KEY |
||||
try: |
||||
host, port = get_engine_addr(conf) |
||||
except cfg.ConfigFileValueError: |
||||
msg = _("Configuration option was not valid") |
||||
logger.error(msg) |
||||
raise exception.BadEngineConnectionConfiguration(msg) |
||||
except IndexError: |
||||
msg = _("Could not find required configuration option") |
||||
logger.error(msg) |
||||
raise exception.BadEngineConnectionConfiguration(msg) |
||||
|
||||
conf.register_opts(engine_client_opts) |
||||
|
||||
_CLIENT_HOST = host |
||||
_CLIENT_PORT = port |
||||
_METADATA_ENCRYPTION_KEY = conf.metadata_encryption_key |
||||
_CLIENT_KWARGS = { |
||||
'use_ssl': conf.engine_client_protocol.lower() == 'https', |
||||
'key_file': conf.engine_client_key_file, |
||||
'cert_file': conf.engine_client_cert_file, |
||||
'ca_file': conf.engine_client_ca_file |
||||
} |
||||
|
||||
|
||||
|
||||
def get_engine_client(cxt): |
||||
global _CLIENT_CREDS, _CLIENT_KWARGS, _CLIENT_HOST, _CLIENT_PORT |
||||
global _METADATA_ENCRYPTION_KEY |
||||
kwargs = _CLIENT_KWARGS.copy() |
||||
kwargs['auth_tok'] = cxt.auth_tok |
||||
if _CLIENT_CREDS: |
||||
kwargs['creds'] = _CLIENT_CREDS |
||||
return EngineClient(_CLIENT_HOST, _CLIENT_PORT, |
||||
_METADATA_ENCRYPTION_KEY, **kwargs) |
||||
|
||||
|
@ -0,0 +1,234 @@
|
||||
# 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" } |
||||
|