API server.
"""
-import functools
import gettext
import optparse
import os
import sys
import time
import json
-import base64
-import libxml2
-import re
import logging
from urlparse import urlparse
gettext.install('heat', unicode=1)
-from glance import client as glance_client
from heat import client as heat_client
from heat import version
from heat.common import config
from heat.common import exception
+from heat import utils
-SUCCESS = 0
-FAILURE = 1
-
-
-def catch_error(action):
- """Decorator to provide sensible default error handling for actions."""
- def wrap(func):
- @functools.wraps(func)
- def wrapper(*arguments, **kwargs):
- try:
- ret = func(*arguments, **kwargs)
- return SUCCESS if ret is None else ret
- except exception.NotAuthorized:
- logging.error("Not authorized to make this request. Check " +\
- "your credentials (OS_USERNAME, OS_PASSWORD, " +\
- "OS_TENANT_NAME, OS_AUTH_URL and OS_AUTH_STRATEGY).")
- return FAILURE
- except exception.ClientConfigurationError:
- raise
- except Exception, e:
- options = arguments[0]
- if options.debug:
- raise
- logging.error("Failed to %s. Got error:" % action)
- pieces = unicode(e).split('\n')
- for piece in pieces:
- logging.error(piece)
- return FAILURE
-
- return wrapper
- return wrap
-
-
-@catch_error('validate')
+@utils.catch_error('validate')
def template_validate(options, arguments):
'''
Validate a template. This command parses a template and verifies that
parameters['TemplateUrl'] = options.template_url
else:
logging.error('Please specify a template file or url')
- return FAILURE
+ return utils.FAILURE
client = get_client(options)
result = client.validate_template(**parameters)
print json.dumps(result, indent=2)
-@catch_error('gettemplate')
+@utils.catch_error('gettemplate')
def get_template(options, arguments):
'''
Gets an existing stack template.
pass
-@catch_error('create')
+@utils.catch_error('create')
def stack_create(options, arguments):
'''
Create a new stack from a template.
except IndexError:
logging.error("Please specify the stack name you wish to create")
logging.error("as the first argument")
- return FAILURE
+ return utils.FAILURE
if options.parameters:
count = 1
parameters['TemplateUrl'] = options.template_url
else:
logging.error('Please specify a template file or url')
- return FAILURE
+ return utils.FAILURE
c = get_client(options)
result = c.create_stack(**parameters)
print json.dumps(result, indent=2)
-@catch_error('update')
+@utils.catch_error('update')
def stack_update(options, arguments):
'''
Update an existing stack.
except IndexError:
logging.error("Please specify the stack name you wish to update")
logging.error("as the first argument")
- return FAILURE
+ return utils.FAILURE
if options.template_file:
parameters['TemplateBody'] = open(options.template_file).read()
print json.dumps(result, indent=2)
-@catch_error('delete')
+@utils.catch_error('delete')
def stack_delete(options, arguments):
'''
Delete an existing stack. This shuts down all VMs associated with
except IndexError:
logging.error("Please specify the stack name you wish to delete")
logging.error("as the first argument")
- return FAILURE
+ return utils.FAILURE
c = get_client(options)
result = c.delete_stack(**parameters)
print json.dumps(result, indent=2)
-@catch_error('describe')
+@utils.catch_error('describe')
def stack_describe(options, arguments):
'''
Describes an existing stack.
except IndexError:
logging.error("Please specify the stack name you wish to describe")
logging.error("as the first argument")
- return FAILURE
+ return utils.FAILURE
c = get_client(options)
result = c.describe_stacks(**parameters)
print json.dumps(result, indent=2)
-@catch_error('events_list')
+@utils.catch_error('events_list')
def stack_events_list(options, arguments):
'''
List events associated with the given stack.
print json.dumps(result, indent=2)
-@catch_error('list')
+@utils.catch_error('list')
def stack_list(options, arguments):
'''
List all running stacks.
print json.dumps(result, indent=2)
-@catch_error('jeos_create')
+@utils.catch_error('jeos_create')
def jeos_create(options, arguments):
'''
Create a new JEOS (Just Enough Operating System) image.
The command must be run as root in order for libvirt to have permissions
to create virtual machines and read the raw DVDs.
'''
- global jeos_path
- global cfntools_path
-
- # if not running as root, return EPERM to command line
- if os.geteuid() != 0:
- logging.error("jeos_create must be run as root")
- sys.exit(1)
- if len(arguments) < 3:
- print '\n Please provide the distro, arch, and instance type.'
- print ' Usage:'
- print ' heat jeos_create <distro> <arch> <instancetype>'
- print ' instance type can be:'
- print ' gold builds a base image where userdata is used to' \
- ' initialize the instance'
- print ' cfntools builds a base image where AWS CloudFormation' \
- ' tools are present'
- sys.exit(1)
-
- distro = arguments.pop(0)
- arch = arguments.pop(0)
- instance_type = arguments.pop(0)
- images_dir = '/var/lib/libvirt/images'
-
- arches = ('x86_64', 'i386', 'amd64')
- arches_str = " | ".join(arches)
- instance_types = ('gold', 'cfntools')
- instances_str = " | ".join(instance_types)
-
- if not arch in arches:
- logging.error('arch %s not supported' % arch)
- logging.error('try: heat jeos_create %s [ %s ]' % (distro, arches_str))
- sys.exit(1)
-
- if not instance_type in instance_types:
- logging.error('A JEOS instance type of %s not supported' %\
- instance_type)
- logging.error('try: heat jeos_create %s %s [ %s ]' %\
- (distro, arch, instances_str))
- sys.exit(1)
-
- fedora_match = re.match('F(1[6-7])', distro)
- if fedora_match:
- version = fedora_match.group(1)
- iso = '%s/Fedora-%s-%s-DVD.iso' % (images_dir, version, arch)
- elif distro == 'U10':
- iso = '%s/ubuntu-10.04.3-server-%s.iso' % (images_dir, arch)
- else:
- logging.error('distro %s not supported' % distro)
- logging.error('try: F16, F17 or U10')
- sys.exit(1)
-
- if not os.access(iso, os.R_OK):
- logging.error('*** %s does not exist.' % (iso))
- sys.exit(1)
-
- tdl_path = '%s%s-%s-%s-jeos.tdl' % (jeos_path, distro, arch, instance_type)
- if options.debug:
- print "Using tdl: %s" % tdl_path
-
- # Load the cfntools into the cfntool image by encoding them in base64
- # and injecting them into the TDL at the appropriate place
- if instance_type == 'cfntools':
- tdl_xml = libxml2.parseFile(tdl_path)
- for cfnname in ['cfn-init', 'cfn-hup', 'cfn-signal', 'cfn_helper.py']:
- f = open('%s/%s' % (cfntools_path, cfnname), 'r')
- cfscript_e64 = base64.b64encode(f.read())
- f.close()
- cfnpath = "/template/files/file[@name='/opt/aws/bin/%s']" % cfnname
- tdl_xml.xpathEval(cfnpath)[0].setContent(cfscript_e64)
-
- # TODO(sdake) INSECURE
- tdl_xml.saveFormatFile('/tmp/tdl', format=1)
- tdl_path = '/tmp/tdl'
-
- dsk_filename = '%s/%s-%s-%s-jeos.dsk' % (images_dir, distro,
- arch, instance_type)
- qcow2_filename = '%s/%s-%s-%s-jeos.qcow2' % (images_dir, distro,
- arch, instance_type)
- image_name = '%s-%s-%s' % (distro, arch, instance_type)
-
- if not os.access(tdl_path, os.R_OK):
- logging.error('The tdl for that disto/arch is not available')
- sys.exit(1)
-
- creds = dict(username=options.username,
- password=options.password,
- tenant=options.tenant,
- auth_url=options.auth_url,
- strategy=options.auth_strategy)
-
- client = glance_client.Client(host="0.0.0.0", port=9292,
- use_ssl=False, auth_tok=None, creds=creds)
-
- parameters = {
- "filters": {},
- "limit": 10,
- }
- images = client.get_images(**parameters)
-
- image_registered = False
- for image in images:
- if image['name'] == distro + '-' + arch + '-' + instance_type:
- image_registered = True
-
- runoz = None
- if os.access(qcow2_filename, os.R_OK):
- while runoz not in ('y', 'n'):
- runoz = raw_input('An existing JEOS was found on disk.' \
- ' Do you want to build a fresh JEOS?' \
- ' (y/n) ').lower()
- if runoz == 'y':
- os.remove(qcow2_filename)
- os.remove(dsk_filename)
- if image_registered:
- client.delete_image(image['id'])
- elif runoz == 'n':
- answer = None
- while answer not in ('y', 'n'):
- answer = raw_input('Do you want to register your existing' \
- ' JEOS file with glance? (y/n) ').lower()
- if answer == 'n':
- logging.info('No action taken')
- sys.exit(0)
- elif answer == 'y' and image_registered:
- answer = None
- while answer not in ('y', 'n'):
- answer = raw_input('Do you want to delete the ' \
- 'existing JEOS in glance?' \
- ' (y/n) ').lower()
- if answer == 'n':
- logging.info('No action taken')
- sys.exit(0)
- elif answer == 'y':
- client.delete_image(image['id'])
-
- if runoz == None or runoz == 'y':
- logging.info('Creating JEOS image (%s) - '\
- 'this takes approximately 10 minutes.' % image_name)
- extra_opts = ' '
- if options.debug:
- extra_opts = ' -d 3 '
-
- ozcmd = "oz-install %s -t 50000 -u %s -x /dev/null" % (extra_opts,
- tdl_path)
- logging.debug("Running : %s" % ozcmd)
- res = os.system(ozcmd)
- if res == 256:
- sys.exit(1)
- if not os.access(dsk_filename, os.R_OK):
- logging.error('oz-install did not create the image,' \
- ' check your oz installation.')
- sys.exit(1)
-
- logging.info('Converting raw disk image to a qcow2 image.')
- os.system("qemu-img convert -O qcow2 %s %s" % (dsk_filename,
- qcow2_filename))
-
- logging.info('Registering JEOS image (%s) ' \
- 'with OpenStack Glance.' % image_name)
-
- image_meta = {'name': image_name,
- 'is_public': True,
- 'disk_format': 'qcow2',
- 'min_disk': 0,
- 'min_ram': 0,
- 'owner': options.username,
- 'container_format': 'bare'}
-
- try:
- with open(qcow2_filename) as ifile:
- image_meta = client.add_image(image_meta, ifile)
- image_id = image_meta['id']
- logging.debug(" Added new image with ID: %s" % image_id)
- logging.debug(" Returned the following metadata for the new image:")
- for k, v in sorted(image_meta.items()):
- logging.debug(" %(k)30s => %(v)s" % locals())
- except exception.ClientConnectionError, e:
- logging.error((" Failed to connect to the Glance API server." +\
- " Is the server running?" % locals()))
- pieces = unicode(e).split('\n')
- for piece in pieces:
- logging.error(piece)
- sys.exit(1)
- except Exception, e:
- logging.error(" Failed to add image. Got error:")
- pieces = unicode(e).split('\n')
- for piece in pieces:
- logging.error(piece)
- logging.warning(" Note: Your image metadata may still be in the " +\
- "registry, but the image's status will likely be 'killed'.")
+ utils.jeos_create(options, arguments)
def get_client(options):
+# 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 functools
+import os
+import sys
+import base64
+import libxml2
+import re
+import logging
+
+from glance import client as glance_client
+from heat.common import exception
+
+
+SUCCESS = 0
+FAILURE = 1
+
+
+def catch_error(action):
+ """Decorator to provide sensible default error handling for CLI actions."""
+ def wrap(func):
+ @functools.wraps(func)
+ def wrapper(*arguments, **kwargs):
+ try:
+ ret = func(*arguments, **kwargs)
+ return SUCCESS if ret is None else ret
+ except exception.NotAuthorized:
+ logging.error("Not authorized to make this request. Check " +\
+ "your credentials (OS_USERNAME, OS_PASSWORD, " +\
+ "OS_TENANT_NAME, OS_AUTH_URL and OS_AUTH_STRATEGY).")
+ return FAILURE
+ except exception.ClientConfigurationError:
+ raise
+ except Exception, e:
+ options = arguments[0]
+ if options.debug:
+ raise
+ logging.error("Failed to %s. Got error:" % action)
+ pieces = unicode(e).split('\n')
+ for piece in pieces:
+ logging.error(piece)
+ return FAILURE
+
+ return wrapper
+ return wrap
+
+
+@catch_error('jeos_create')
+def jeos_create(options, arguments):
+ '''
+ Create a new JEOS (Just Enough Operating System) image.
+
+ Usage: heat jeos_create <distribution> <architecture> <image type>
+
+ Distribution: Distribution such as 'F16', 'F17', 'U10', 'D6'.
+ Architecture: Architecture such as 'i386' 'i686' or 'x86_64'.
+ Image Type: Image type such as 'gold' or 'cfntools'.
+ 'gold' is a basic gold JEOS.
+ 'cfntools' contains the cfntools helper scripts.
+
+ The command must be run as root in order for libvirt to have permissions
+ to create virtual machines and read the raw DVDs.
+ '''
+ global jeos_path
+ global cfntools_path
+
+ # if not running as root, return EPERM to command line
+ if os.geteuid() != 0:
+ logging.error("jeos_create must be run as root")
+ sys.exit(1)
+ if len(arguments) < 3:
+ print '\n Please provide the distro, arch, and instance type.'
+ print ' Usage:'
+ print ' heat jeos_create <distro> <arch> <instancetype>'
+ print ' instance type can be:'
+ print ' gold builds a base image where userdata is used to' \
+ ' initialize the instance'
+ print ' cfntools builds a base image where AWS CloudFormation' \
+ ' tools are present'
+ sys.exit(1)
+
+ distro = arguments.pop(0)
+ arch = arguments.pop(0)
+ instance_type = arguments.pop(0)
+ images_dir = '/var/lib/libvirt/images'
+
+ arches = ('x86_64', 'i386', 'amd64')
+ arches_str = " | ".join(arches)
+ instance_types = ('gold', 'cfntools')
+ instances_str = " | ".join(instance_types)
+
+ if not arch in arches:
+ logging.error('arch %s not supported' % arch)
+ logging.error('try: heat jeos_create %s [ %s ]' % (distro, arches_str))
+ sys.exit(1)
+
+ if not instance_type in instance_types:
+ logging.error('A JEOS instance type of %s not supported' %\
+ instance_type)
+ logging.error('try: heat jeos_create %s %s [ %s ]' %\
+ (distro, arch, instances_str))
+ sys.exit(1)
+
+ fedora_match = re.match('F(1[6-7])', distro)
+ if fedora_match:
+ version = fedora_match.group(1)
+ iso = '%s/Fedora-%s-%s-DVD.iso' % (images_dir, version, arch)
+ elif distro == 'U10':
+ iso = '%s/ubuntu-10.04.3-server-%s.iso' % (images_dir, arch)
+ else:
+ logging.error('distro %s not supported' % distro)
+ logging.error('try: F16, F17 or U10')
+ sys.exit(1)
+
+ if not os.access(iso, os.R_OK):
+ logging.error('*** %s does not exist.' % (iso))
+ sys.exit(1)
+
+ tdl_path = '%s%s-%s-%s-jeos.tdl' % (jeos_path, distro, arch, instance_type)
+ if options.debug:
+ print "Using tdl: %s" % tdl_path
+
+ # Load the cfntools into the cfntool image by encoding them in base64
+ # and injecting them into the TDL at the appropriate place
+ if instance_type == 'cfntools':
+ tdl_xml = libxml2.parseFile(tdl_path)
+ for cfnname in ['cfn-init', 'cfn-hup', 'cfn-signal', 'cfn_helper.py']:
+ f = open('%s/%s' % (cfntools_path, cfnname), 'r')
+ cfscript_e64 = base64.b64encode(f.read())
+ f.close()
+ cfnpath = "/template/files/file[@name='/opt/aws/bin/%s']" % cfnname
+ tdl_xml.xpathEval(cfnpath)[0].setContent(cfscript_e64)
+
+ # TODO(sdake) INSECURE
+ tdl_xml.saveFormatFile('/tmp/tdl', format=1)
+ tdl_path = '/tmp/tdl'
+
+ dsk_filename = '%s/%s-%s-%s-jeos.dsk' % (images_dir, distro,
+ arch, instance_type)
+ qcow2_filename = '%s/%s-%s-%s-jeos.qcow2' % (images_dir, distro,
+ arch, instance_type)
+ image_name = '%s-%s-%s' % (distro, arch, instance_type)
+
+ if not os.access(tdl_path, os.R_OK):
+ logging.error('The tdl for that disto/arch is not available')
+ sys.exit(1)
+
+ creds = dict(username=options.username,
+ password=options.password,
+ tenant=options.tenant,
+ auth_url=options.auth_url,
+ strategy=options.auth_strategy)
+
+ client = glance_client.Client(host="0.0.0.0", port=9292,
+ use_ssl=False, auth_tok=None, creds=creds)
+
+ parameters = {
+ "filters": {},
+ "limit": 10,
+ }
+ images = client.get_images(**parameters)
+
+ image_registered = False
+ for image in images:
+ if image['name'] == distro + '-' + arch + '-' + instance_type:
+ image_registered = True
+
+ runoz = None
+ if os.access(qcow2_filename, os.R_OK):
+ while runoz not in ('y', 'n'):
+ runoz = raw_input('An existing JEOS was found on disk.' \
+ ' Do you want to build a fresh JEOS?' \
+ ' (y/n) ').lower()
+ if runoz == 'y':
+ os.remove(qcow2_filename)
+ os.remove(dsk_filename)
+ if image_registered:
+ client.delete_image(image['id'])
+ elif runoz == 'n':
+ answer = None
+ while answer not in ('y', 'n'):
+ answer = raw_input('Do you want to register your existing' \
+ ' JEOS file with glance? (y/n) ').lower()
+ if answer == 'n':
+ logging.info('No action taken')
+ sys.exit(0)
+ elif answer == 'y' and image_registered:
+ answer = None
+ while answer not in ('y', 'n'):
+ answer = raw_input('Do you want to delete the ' \
+ 'existing JEOS in glance?' \
+ ' (y/n) ').lower()
+ if answer == 'n':
+ logging.info('No action taken')
+ sys.exit(0)
+ elif answer == 'y':
+ client.delete_image(image['id'])
+
+ if runoz == None or runoz == 'y':
+ logging.info('Creating JEOS image (%s) - '\
+ 'this takes approximately 10 minutes.' % image_name)
+ extra_opts = ' '
+ if options.debug:
+ extra_opts = ' -d 3 '
+
+ ozcmd = "oz-install %s -t 50000 -u %s -x /dev/null" % (extra_opts,
+ tdl_path)
+ logging.debug("Running : %s" % ozcmd)
+ res = os.system(ozcmd)
+ if res == 256:
+ sys.exit(1)
+ if not os.access(dsk_filename, os.R_OK):
+ logging.error('oz-install did not create the image,' \
+ ' check your oz installation.')
+ sys.exit(1)
+
+ logging.info('Converting raw disk image to a qcow2 image.')
+ os.system("qemu-img convert -O qcow2 %s %s" % (dsk_filename,
+ qcow2_filename))
+
+ logging.info('Registering JEOS image (%s) ' \
+ 'with OpenStack Glance.' % image_name)
+
+ image_meta = {'name': image_name,
+ 'is_public': True,
+ 'disk_format': 'qcow2',
+ 'min_disk': 0,
+ 'min_ram': 0,
+ 'owner': options.username,
+ 'container_format': 'bare'}
+
+ try:
+ with open(qcow2_filename) as ifile:
+ image_meta = client.add_image(image_meta, ifile)
+ image_id = image_meta['id']
+ logging.debug(" Added new image with ID: %s" % image_id)
+ logging.debug(" Returned the following metadata for the new image:")
+ for k, v in sorted(image_meta.items()):
+ logging.debug(" %(k)30s => %(v)s" % locals())
+ except exception.ClientConnectionError, e:
+ logging.error((" Failed to connect to the Glance API server." +\
+ " Is the server running?" % locals()))
+ pieces = unicode(e).split('\n')
+ for piece in pieces:
+ logging.error(piece)
+ sys.exit(1)
+ except Exception, e:
+ logging.error(" Failed to add image. Got error:")
+ pieces = unicode(e).split('\n')
+ for piece in pieces:
+ logging.error(piece)
+ logging.warning(" Note: Your image metadata may still be in the " +\
+ "registry, but the image's status will likely be 'killed'.")
+
+
class LazyPluggable(object):
"""A pluggable backend loaded lazily based on some value."""