Initial standalone heat-jeos bin

This commit is contained in:
Tomas Sedovic
2012-06-05 12:54:58 +02:00
parent 9f2150701c
commit 7490cc4b0e

439
bin/heat-jeos Executable file
View File

@@ -0,0 +1,439 @@
#!/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.
"""
Tools to generate JEOS TDLs, build the images and register them in Glance.
"""
import base64
import gettext
import json
import logging
from lxml import etree
import optparse
import os
import os.path
import re
import sys
import time
from glance.common import exception
from glance import client as glance_client
# TODO(shadower): fix the cmdline options
# TODO(shadower): split the options to TDL/image/glance
# TODO(shadower): use Oz as a library, don't shell out
# TODO(shadower): remove jeos-create from the old binary, lib
# TODO(shadower): update the getting started guide
# TODO(shadower): update the tests
# TODO(shadower): once this is in a separate repo/package, prolly needs fixing
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
'..'))
jeos_path = ''
cfntools_path = ''
if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')):
sys.path.insert(0, possible_topdir)
jeos_path = '%s/heat/%s/' % (possible_topdir, "jeos")
cfntools_path = '%s/heat/%s/' % (possible_topdir, "cfntools")
jeos_path = os.path.join(possible_topdir, 'jeos')
cfntools_path = os.path.join(possible_topdir, 'cfntools')
else:
for p in sys.path:
jeos_path = os.path.join(p, 'heat', 'jeos')
cfntools_path = os.path.join(p, 'heat', 'cfntools')
if os.access(jeos_path, os.R_OK):
break
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.
'''
# 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)
src_arch = 'i386'
fedora_match = re.match('F(1[6-7])', distro)
if fedora_match:
if arch == 'x86_64':
src_arch = 'x86_64'
version = fedora_match.group(1)
iso = '%s/Fedora-%s-%s-DVD.iso' % (images_dir, version, arch)
elif distro == 'U10':
if arch == 'amd64':
src_arch = 'x86_64'
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_file = '%s-%s-%s-jeos.tdl' % (distro, arch, instance_type)
tdl_path = os.path.join(jeos_path, tdl_file)
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 = etree.parse(tdl_path)
cfn_tools = ['cfn-init', 'cfn-hup', 'cfn-signal',
'cfn-get-metadata', 'cfn_helper.py', 'cfn-push-stats']
for cfnname in cfn_tools:
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.xpath(cfnpath)[0].text = cfscript_e64
# TODO(sdake) INSECURE
tdl_xml.write('/tmp/tdl', xml_declaration=True)
tdl_path = '/tmp/tdl'
dsk_filename = '%s/%s-%s-%s-jeos.dsk' % (images_dir, distro,
src_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 = options.yes and 'y' or 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 -c -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'.")
def create_options(parser):
"""
Sets up the CLI and config-file options that may be
parsed and program commands.
:param parser: The option parser
"""
parser.add_option('-v', '--verbose', default=False, action="store_true",
help="Print more verbose output")
parser.add_option('-d', '--debug', default=False, action="store_true",
help="Print more verbose output")
parser.add_option('-y', '--yes', default=False, action="store_true",
help="Don't prompt for user input; assume the answer to "
"every question is 'yes'.")
parser.add_option('-A', '--auth_token', dest="auth_token",
metavar="TOKEN", default=None,
help="Authentication token to use to identify the "
"client to the heat server")
parser.add_option('-I', '--username', dest="username",
metavar="USER", default=None,
help="User name used to acquire an authentication token")
parser.add_option('-K', '--password', dest="password",
metavar="PASSWORD", default=None,
help="Password used to acquire an authentication token")
parser.add_option('-T', '--tenant', dest="tenant",
metavar="TENANT", default=None,
help="Tenant name used for Keystone authentication")
parser.add_option('-R', '--region', dest="region",
metavar="REGION", default=None,
help="Region name. When using keystone authentication "
"version 2.0 or later this identifies the region "
"name to use when selecting the service endpoint. A "
"region name must be provided if more than one "
"region endpoint is available")
parser.add_option('-N', '--auth_url', dest="auth_url",
metavar="AUTH_URL", default=None,
help="Authentication URL")
parser.add_option('-S', '--auth_strategy', dest="auth_strategy",
metavar="STRATEGY", default=None,
help="Authentication strategy (keystone or noauth)")
def credentials_from_env():
return dict(username=os.getenv('OS_USERNAME'),
password=os.getenv('OS_PASSWORD'),
tenant=os.getenv('OS_TENANT_NAME'),
auth_url=os.getenv('OS_AUTH_URL'),
auth_strategy=os.getenv('OS_AUTH_STRATEGY'))
def parse_options(parser, cli_args):
"""
Returns the parsed CLI options, command to run and its arguments, merged
with any same-named options found in a configuration file
:param parser: The option parser
"""
if not cli_args:
cli_args.append('-h') # Show options in usage output...
(options, args) = parser.parse_args(cli_args)
env_opts = credentials_from_env()
for option, env_val in env_opts.items():
if not getattr(options, option):
setattr(options, option, env_val)
if not options.auth_strategy:
options.auth_strategy = 'noauth'
# HACK(sirp): Make the parser available to the print_help method
# print_help is a command, so it only accepts (options, args); we could
# one-off have it take (parser, options, args), however, for now, I think
# this little hack will suffice
options.__parser = parser
if not args:
parser.print_usage()
sys.exit(0)
command_name = args.pop(0)
command = lookup_command(parser, command_name)
if options.debug:
logging.basicConfig(format='%(levelname)s:%(message)s',\
level=logging.DEBUG)
logging.debug("Debug level logging enabled")
elif options.verbose:
logging.basicConfig(format='%(levelname)s:%(message)s',\
level=logging.INFO)
else:
logging.basicConfig(format='%(levelname)s:%(message)s',\
level=logging.WARNING)
return (options, command, args)
def print_help(options, args):
"""
Print help specific to a command
"""
parser = options.__parser
if not args:
parser.print_usage()
subst = {'prog': os.path.basename(sys.argv[0])}
docs = [lookup_command(parser, cmd).__doc__ % subst for cmd in args]
print '\n\n'.join(docs)
def lookup_command(parser, command_name):
base_commands = {'help': print_help}
stack_commands = {
'jeos-create': jeos_create}
commands = {}
for command_set in (base_commands, stack_commands):
commands.update(command_set)
try:
command = commands[command_name]
except KeyError:
parser.print_usage()
sys.exit("Unknown command: %s" % command_name)
return command
def main():
'''
'''
usage = """
%prog <command> [options] [args]
Commands:
help <command> Output help for one of the commands below
jeos-create Create a JEOS image
"""
oparser = optparse.OptionParser(version='%%prog %s'
% '0.0.1',
usage=usage.strip())
create_options(oparser)
(opts, cmd, args) = parse_options(oparser, sys.argv[1:])
try:
start_time = time.time()
result = cmd(opts, args)
end_time = time.time()
logging.debug("Completed in %-0.4f sec." % (end_time - start_time))
sys.exit(result)
except (RuntimeError,
NotImplementedError), ex:
oparser.print_usage()
logging.error("ERROR: " % ex)
sys.exit(1)
if __name__ == '__main__':
main()