Move jeos_create into utils so it can be imported

The main reason for this move is so that jeos_create can be tested with
the aide of Mox.

Signed-off-by: Jeff Peeler <jpeeler@redhat.com>
This commit is contained in:
Jeff Peeler 2012-04-26 16:51:00 -04:00
parent 2ba3934ce0
commit d4f0c0d25b
2 changed files with 285 additions and 243 deletions

260
bin/heat
View File

@ -20,16 +20,12 @@ belonging to a user. It is a convenience application that talks to the heat
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
@ -51,47 +47,14 @@ else:
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
@ -110,14 +73,14 @@ def template_validate(options, arguments):
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.
@ -127,7 +90,7 @@ def get_template(options, arguments):
pass
@catch_error('create')
@utils.catch_error('create')
def stack_create(options, arguments):
'''
Create a new stack from a template.
@ -150,7 +113,7 @@ def stack_create(options, arguments):
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
@ -166,14 +129,14 @@ def stack_create(options, arguments):
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.
@ -199,7 +162,7 @@ def stack_update(options, arguments):
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()
@ -219,7 +182,7 @@ def stack_update(options, arguments):
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
@ -234,14 +197,14 @@ def stack_delete(options, arguments):
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.
@ -254,14 +217,14 @@ def stack_describe(options, arguments):
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.
@ -279,7 +242,7 @@ def stack_events_list(options, arguments):
print json.dumps(result, indent=2)
@catch_error('list')
@utils.catch_error('list')
def stack_list(options, arguments):
'''
List all running stacks.
@ -291,7 +254,7 @@ def stack_list(options, arguments):
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.
@ -307,196 +270,7 @@ def jeos_create(options, arguments):
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):

View File

@ -1,3 +1,271 @@
# 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."""