2011-09-24 00:36:09 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
#
|
|
|
|
# Live Media Creator
|
|
|
|
#
|
2012-01-26 01:23:25 +00:00
|
|
|
# Copyright (C) 2011-2012 Red Hat, Inc.
|
2011-09-24 00:36:09 +00:00
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
# Author(s): Brian C. Lane <bcl@redhat.com>
|
|
|
|
#
|
|
|
|
import logging
|
|
|
|
log = logging.getLogger("livemedia-creator")
|
|
|
|
program_log = logging.getLogger("program")
|
|
|
|
pylorax_log = logging.getLogger("pylorax")
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import uuid
|
|
|
|
import tempfile
|
|
|
|
import subprocess
|
|
|
|
import socket
|
|
|
|
import threading
|
|
|
|
import SocketServer
|
|
|
|
from time import sleep
|
|
|
|
import shutil
|
|
|
|
import traceback
|
|
|
|
import argparse
|
2012-05-24 22:55:46 +00:00
|
|
|
import hashlib
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
# Use pykickstart to calculate disk image size
|
|
|
|
from pykickstart.parser import KickstartParser
|
|
|
|
from pykickstart.version import makeVersion
|
|
|
|
|
2012-05-24 22:55:46 +00:00
|
|
|
# Use Mako templates for appliance builder descriptions
|
|
|
|
from mako.template import Template
|
|
|
|
from mako.exceptions import text_error_template
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
# Use the Lorax treebuilder branch for iso creation
|
|
|
|
from pylorax.base import DataHolder
|
2012-01-26 01:23:25 +00:00
|
|
|
from pylorax.treebuilder import TreeBuilder, RuntimeBuilder, udev_escape
|
2011-09-24 00:36:09 +00:00
|
|
|
from pylorax.sysutils import joinpaths, remove, linktree
|
2012-05-12 00:35:16 +00:00
|
|
|
from pylorax.imgutils import PartitionMount, mksparse, mkext4img, loop_detach
|
|
|
|
from pylorax.imgutils import get_loop_name, dm_detach
|
2011-09-24 00:36:09 +00:00
|
|
|
from pylorax.executils import execWithRedirect, execWithCapture
|
|
|
|
|
2012-05-10 21:21:42 +00:00
|
|
|
# no-virt mode doesn't need libvirt, so make it optional
|
|
|
|
try:
|
|
|
|
import libvirt
|
|
|
|
except ImportError:
|
|
|
|
libvirt = None
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2012-02-07 19:32:14 +00:00
|
|
|
# Default parameters for rebuilding initramfs, override with --dracut-args
|
2012-06-05 16:12:50 +00:00
|
|
|
DRACUT_DEFAULT = ["--xz", "--add", "livenet dmsquash-live convertfs pollcdrom",
|
|
|
|
"--omit", "plymouth"]
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
class LogRequestHandler(SocketServer.BaseRequestHandler):
|
|
|
|
"""
|
|
|
|
Handle monitoring and saving the logfiles from the virtual install
|
|
|
|
"""
|
|
|
|
def setup(self):
|
|
|
|
if self.server.log_path:
|
|
|
|
self.fp = open(self.server.log_path, "w")
|
|
|
|
else:
|
|
|
|
print "no log_path specified"
|
|
|
|
self.request.settimeout(10)
|
|
|
|
|
|
|
|
def handle(self):
|
|
|
|
"""
|
|
|
|
Handle writing incoming data to a logfile and
|
|
|
|
checking the logs for any Tracebacks or other errors that indicate
|
|
|
|
that the install failed.
|
|
|
|
"""
|
|
|
|
line = ""
|
|
|
|
while True:
|
|
|
|
if self.server.kill:
|
|
|
|
break
|
|
|
|
|
|
|
|
try:
|
|
|
|
data = self.request.recv(4096)
|
|
|
|
self.fp.write(data)
|
|
|
|
self.fp.flush()
|
|
|
|
|
|
|
|
# check the data for errors and set error flag
|
|
|
|
# need to assemble it into lines so we can test for the error
|
|
|
|
# string.
|
|
|
|
while data:
|
|
|
|
more = data.split("\n", 1)
|
|
|
|
line += more[0]
|
|
|
|
if len(more) > 1:
|
|
|
|
self.iserror(line)
|
|
|
|
line = ""
|
|
|
|
data = more[1]
|
|
|
|
else:
|
|
|
|
data = None
|
|
|
|
|
|
|
|
except socket.timeout:
|
|
|
|
pass
|
|
|
|
except:
|
|
|
|
break
|
|
|
|
|
|
|
|
def finish(self):
|
|
|
|
self.fp.close()
|
|
|
|
|
|
|
|
def iserror(self, line):
|
|
|
|
"""
|
|
|
|
Check a line to see if it contains an error indicating install failure
|
|
|
|
"""
|
2011-12-09 01:12:06 +00:00
|
|
|
simple_tests = [ "Traceback (",
|
|
|
|
"Out of memory:",
|
|
|
|
"Call Trace:",
|
|
|
|
"insufficient disk space:" ]
|
|
|
|
for t in simple_tests:
|
|
|
|
if line.find( t ) > -1:
|
|
|
|
self.server.log_error = True
|
|
|
|
return
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
class LogServer(SocketServer.TCPServer):
|
|
|
|
"""
|
|
|
|
Add path to logfile
|
|
|
|
Add log error flag
|
|
|
|
Add a kill switch
|
|
|
|
"""
|
|
|
|
def __init__(self, log_path, *args, **kwargs):
|
|
|
|
self.kill = False
|
|
|
|
self.log_error = False
|
|
|
|
self.log_path = log_path
|
|
|
|
SocketServer.TCPServer.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def log_check(self):
|
|
|
|
return self.log_error
|
|
|
|
|
|
|
|
|
|
|
|
class LogMonitor(object):
|
|
|
|
"""
|
|
|
|
Contains all the stuff needed to setup a thread to listen to the logs
|
|
|
|
from the virtual install
|
|
|
|
"""
|
|
|
|
def __init__(self, log_path, host="localhost", port=0):
|
|
|
|
"""
|
|
|
|
Fire up the thread listening for logs
|
|
|
|
"""
|
|
|
|
self.server = LogServer(log_path, (host, port), LogRequestHandler)
|
|
|
|
self.host, self.port = self.server.server_address
|
|
|
|
self.log_path = log_path
|
|
|
|
self.server_thread = threading.Thread(target=self.server.handle_request)
|
|
|
|
self.server_thread.daemon = True
|
|
|
|
self.server_thread.start()
|
|
|
|
|
|
|
|
def shutdown(self):
|
|
|
|
self.server.kill = True
|
|
|
|
self.server_thread.join()
|
|
|
|
|
|
|
|
|
|
|
|
class IsoMountpoint(object):
|
|
|
|
"""
|
|
|
|
Mount the iso on a temporary directory and check to make sure the
|
|
|
|
vmlinuz and initrd.img files exist
|
2012-01-26 01:23:25 +00:00
|
|
|
Check the iso for a LiveOS directory and set a flag.
|
|
|
|
Extract the iso's label.
|
2011-09-24 00:36:09 +00:00
|
|
|
"""
|
|
|
|
def __init__( self, iso_path ):
|
2012-01-26 01:23:25 +00:00
|
|
|
self.label = None
|
2011-09-24 00:36:09 +00:00
|
|
|
self.iso_path = iso_path
|
|
|
|
self.mount_dir = tempfile.mkdtemp()
|
|
|
|
if not self.mount_dir:
|
|
|
|
raise Exception("Error creating temporary directory")
|
|
|
|
|
|
|
|
cmd = ["mount","-o","loop",self.iso_path,self.mount_dir]
|
|
|
|
execWithRedirect( cmd[0], cmd[1:] )
|
|
|
|
|
|
|
|
self.kernel = self.mount_dir+"/isolinux/vmlinuz"
|
|
|
|
self.initrd = self.mount_dir+"/isolinux/initrd.img"
|
|
|
|
|
|
|
|
if os.path.isdir( self.mount_dir+"/repodata" ):
|
|
|
|
self.repo = self.mount_dir
|
|
|
|
else:
|
|
|
|
self.repo = None
|
2012-01-26 01:23:25 +00:00
|
|
|
self.liveos = os.path.isdir( self.mount_dir+"/LiveOS" )
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
for f in [self.kernel, self.initrd]:
|
|
|
|
if not os.path.isfile(f):
|
|
|
|
raise Exception("Missing file on iso: {0}".format(f))
|
|
|
|
except:
|
|
|
|
self.umount()
|
|
|
|
raise
|
|
|
|
|
2012-01-26 01:23:25 +00:00
|
|
|
self.get_iso_label()
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
def umount( self ):
|
|
|
|
cmd = ["umount", self.mount_dir]
|
|
|
|
execWithRedirect( cmd[0], cmd[1:] )
|
|
|
|
os.rmdir( self.mount_dir )
|
|
|
|
|
2012-01-26 01:23:25 +00:00
|
|
|
def get_iso_label( self ):
|
|
|
|
"""
|
|
|
|
Get the iso's label using isoinfo
|
|
|
|
"""
|
|
|
|
cmd = [ "isoinfo", "-d", "-i", self.iso_path ]
|
|
|
|
isoinfo_output = execWithCapture( cmd[0], cmd[1:] )
|
|
|
|
log.debug( isoinfo_output )
|
|
|
|
for line in isoinfo_output.splitlines():
|
|
|
|
if line.startswith("Volume id: "):
|
|
|
|
self.label = line[11:]
|
|
|
|
return
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
class VirtualInstall( object ):
|
|
|
|
"""
|
|
|
|
Run virt-install using an iso and kickstart(s)
|
|
|
|
"""
|
|
|
|
def __init__( self, iso, ks_paths, disk_img, img_size=2,
|
2012-05-29 18:21:15 +00:00
|
|
|
kernel_args=None, memory=1024, vnc=None, arch=None,
|
2011-09-24 00:36:09 +00:00
|
|
|
log_check=None, virtio_host="127.0.0.1", virtio_port=6080 ):
|
|
|
|
"""
|
|
|
|
|
|
|
|
iso is an instance of IsoMountpoint
|
|
|
|
ks_paths is a list of paths to a kickstart files. All are injected, the
|
|
|
|
first one is the one executed.
|
|
|
|
disk_img is the path to a disk image (doesn't need to exist)
|
|
|
|
img_size is the size, in GiB, of the image if it doesn't exist
|
|
|
|
kernel_args are extra arguments to pass on the kernel cmdline
|
|
|
|
memory is the amount of ram to assign to the virt
|
|
|
|
vnc is passed to the --graphics command verbatim
|
2012-05-29 18:21:15 +00:00
|
|
|
arch is the optional architecture to use in the virt
|
2011-09-24 00:36:09 +00:00
|
|
|
log_check is a method that returns True of the log indicates an error
|
|
|
|
virtio_host and virtio_port are used to communicate with the log monitor
|
|
|
|
"""
|
|
|
|
self.virt_name = "LiveOS-"+str(uuid.uuid4())
|
|
|
|
# add --graphics none later
|
|
|
|
# add whatever serial cmds are needed later
|
|
|
|
cmd = [ "virt-install", "-n", self.virt_name,
|
|
|
|
"-r", str(memory),
|
|
|
|
"--noreboot",
|
|
|
|
"--noautoconsole" ]
|
|
|
|
|
|
|
|
cmd.append("--graphics")
|
|
|
|
if vnc:
|
|
|
|
cmd.append(vnc)
|
|
|
|
else:
|
|
|
|
cmd.append("none")
|
|
|
|
|
|
|
|
for ks in ks_paths:
|
|
|
|
cmd.append("--initrd-inject")
|
|
|
|
cmd.append(ks)
|
2012-01-26 01:23:25 +00:00
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
disk_opts = "path={0}".format(disk_img)
|
|
|
|
disk_opts += ",format=raw"
|
|
|
|
if not os.path.isfile(disk_img):
|
|
|
|
disk_opts += ",size={0}".format(img_size)
|
2012-01-26 01:23:25 +00:00
|
|
|
cmd.append("--disk")
|
|
|
|
cmd.append(disk_opts)
|
|
|
|
|
|
|
|
if iso.liveos:
|
|
|
|
disk_opts = "path={0},device=cdrom".format(iso.iso_path)
|
|
|
|
cmd.append("--disk")
|
|
|
|
cmd.append(disk_opts)
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
extra_args = "ks=file:/{0}".format(os.path.basename(ks_paths[0]))
|
|
|
|
if kernel_args:
|
|
|
|
extra_args += " "+kernel_args
|
2012-01-26 01:23:25 +00:00
|
|
|
if iso.liveos:
|
|
|
|
extra_args += " root=live:CDLABEL={0}".format(udev_escape(iso.label))
|
2012-02-28 22:32:35 +00:00
|
|
|
if not vnc:
|
|
|
|
extra_args += " console=ttyS0"
|
2012-01-26 01:23:25 +00:00
|
|
|
cmd.append("--extra-args")
|
|
|
|
cmd.append(extra_args)
|
|
|
|
|
|
|
|
cmd.append("--location")
|
|
|
|
cmd.append(iso.mount_dir)
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
channel_args = "tcp,host={0}:{1},mode=connect,target_type=virtio" \
|
|
|
|
",name=org.fedoraproject.anaconda.log.0".format(
|
|
|
|
virtio_host, virtio_port)
|
2012-01-26 01:23:25 +00:00
|
|
|
cmd.append("--channel")
|
|
|
|
cmd.append(channel_args)
|
|
|
|
|
2012-05-29 18:21:15 +00:00
|
|
|
if arch:
|
|
|
|
cmd.append("--arch")
|
|
|
|
cmd.append(arch)
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
rc = execWithRedirect( cmd[0], cmd[1:] )
|
|
|
|
if rc:
|
|
|
|
raise Exception("Problem starting virtual install")
|
|
|
|
|
|
|
|
conn = libvirt.openReadOnly(None)
|
|
|
|
dom = conn.lookupByName(self.virt_name)
|
|
|
|
|
|
|
|
# TODO: If vnc has been passed, we should look up the port and print that
|
|
|
|
# for the user at this point
|
|
|
|
|
|
|
|
while dom.isActive() and not log_check():
|
|
|
|
sys.stdout.write(".")
|
|
|
|
sys.stdout.flush()
|
|
|
|
sleep(10)
|
|
|
|
print
|
|
|
|
|
|
|
|
if log_check():
|
|
|
|
log.info( "Installation error detected. See logfile." )
|
|
|
|
else:
|
|
|
|
log.info( "Install finished. Or at least virt shut down." )
|
|
|
|
|
|
|
|
def destroy( self ):
|
|
|
|
"""
|
|
|
|
Make sure the virt has been shut down and destroyed
|
|
|
|
|
|
|
|
Could use libvirt for this instead.
|
|
|
|
"""
|
|
|
|
log.info( "Shutting down {0}".format(self.virt_name) )
|
|
|
|
subprocess.call(["virsh","destroy",self.virt_name])
|
|
|
|
subprocess.call(["virsh","undefine",self.virt_name])
|
|
|
|
|
2012-05-12 00:35:16 +00:00
|
|
|
def is_image_mounted(disk_img):
|
|
|
|
"""
|
|
|
|
Return True if the disk_img is mounted
|
|
|
|
"""
|
|
|
|
with open("/proc/mounts") as mounts:
|
|
|
|
for mount in mounts:
|
|
|
|
fields = mount.split()
|
|
|
|
if len(fields) > 2 and fields[1] == disk_img:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2011-12-09 01:12:06 +00:00
|
|
|
def anaconda_install( disk_img, disk_size, kickstart, repo, args ):
|
|
|
|
"""
|
2012-05-24 22:55:46 +00:00
|
|
|
disk_img Full path of the disk image
|
|
|
|
disk_size Disk size in GB
|
|
|
|
kickstart Full path to kickstart file
|
|
|
|
repo URL of repository
|
|
|
|
args Extra args to pass to anaconda --image install
|
2011-12-09 01:12:06 +00:00
|
|
|
"""
|
|
|
|
# Create the sparse image
|
|
|
|
mksparse( disk_img, disk_size * 1024**3 )
|
|
|
|
|
|
|
|
cmd = [ "anaconda", "--image", disk_img, "--kickstart", kickstart,
|
|
|
|
"--script", "--repo", repo_url ]
|
|
|
|
cmd += args
|
|
|
|
|
|
|
|
return execWithRedirect( cmd[0], cmd[1:] )
|
|
|
|
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
def get_kernels( boot_dir ):
|
|
|
|
"""
|
|
|
|
Examine the vmlinuz-* versions and return a list of them
|
|
|
|
"""
|
|
|
|
files = os.listdir(boot_dir)
|
|
|
|
return [f[8:] for f in files if f.startswith("vmlinuz-")]
|
|
|
|
|
|
|
|
|
2012-05-24 22:55:46 +00:00
|
|
|
def make_appliance(disk_img, name, template, outfile, networks=None, ram=1024,
|
2012-05-29 18:21:15 +00:00
|
|
|
vcpus=1, arch=None, title="Linux", project="Linux",
|
|
|
|
releasever=17):
|
2012-05-24 22:55:46 +00:00
|
|
|
"""
|
|
|
|
Generate an appliance description file
|
|
|
|
|
|
|
|
disk_img Full path of the disk image
|
|
|
|
name Name of the appliance, passed to the template
|
|
|
|
template Full path of Mako template
|
|
|
|
outfile Full path of file to write, using template
|
|
|
|
networks List of networks from the kickstart
|
|
|
|
ram Ram, in MB, passed to template. Default is 1024
|
|
|
|
vcpus CPUs, passed to template. Default is 1
|
2012-05-29 18:21:15 +00:00
|
|
|
arch CPU architecture. Default is 'x86_64'
|
2012-05-24 22:55:46 +00:00
|
|
|
title Title, passed to template. Default is 'Linux'
|
|
|
|
project Project, passed to template. Default is 'Linux'
|
|
|
|
releasever Release version, passed to template. Default is 17
|
|
|
|
"""
|
|
|
|
if not (disk_img and template and outfile):
|
|
|
|
return None
|
|
|
|
|
|
|
|
log.info("Creating appliance definition using ${0}".format(template))
|
|
|
|
|
2012-05-29 18:21:15 +00:00
|
|
|
if not arch:
|
|
|
|
arch = "x86_64"
|
2012-05-24 22:55:46 +00:00
|
|
|
|
|
|
|
log.info("Calculating SHA256 checksum of {0}".format(disk_img))
|
|
|
|
sha256 = hashlib.sha256()
|
|
|
|
with open(disk_img) as f:
|
|
|
|
while True:
|
|
|
|
data = f.read(1024*1024)
|
|
|
|
if not data:
|
|
|
|
break
|
|
|
|
sha256.update(data)
|
|
|
|
log.info("SHA256 of {0} is {1}".format(disk_img, sha256.hexdigest()))
|
|
|
|
disk_info = DataHolder(name=os.path.basename(disk_img), format="raw",
|
|
|
|
checksum_type="sha256", checksum=sha256.hexdigest())
|
|
|
|
try:
|
|
|
|
result = Template(filename=template).render(disks=[disk_info], name=name,
|
|
|
|
arch=arch, memory=ram*1024, vcpus=vcpus, networks=networks,
|
|
|
|
title=title, project=project, releasever=releasever)
|
|
|
|
except Exception:
|
|
|
|
log.error(text_error_template().render())
|
|
|
|
raise
|
|
|
|
|
|
|
|
with open(outfile, "w") as f:
|
|
|
|
f.write(result)
|
|
|
|
|
|
|
|
|
2012-02-28 21:57:17 +00:00
|
|
|
def make_ami( disk_img, ami_img="ami-root.img", ami_label="AMI" ):
|
|
|
|
"""
|
|
|
|
Copy the / partition to an un-partitioned disk image
|
|
|
|
|
|
|
|
ami_img is the filename to write, defaults to ami-root.img
|
|
|
|
ami_label is the FS label to apply to the image
|
|
|
|
|
|
|
|
All other AMI setup is handled by the kickstart's %post
|
|
|
|
"""
|
|
|
|
with PartitionMount( disk_img ) as img_mount:
|
2012-05-22 20:11:53 +00:00
|
|
|
if not img_mount or not img_mount.mount_dir:
|
|
|
|
return None
|
|
|
|
|
2012-02-28 21:57:17 +00:00
|
|
|
work_dir = tempfile.mkdtemp()
|
|
|
|
log.info("working dir is {0}".format(work_dir))
|
|
|
|
log.info("creating {0}".format(ami_img))
|
|
|
|
mkext4img(img_mount.mount_dir, joinpaths(work_dir, ami_img), label=ami_label)
|
|
|
|
return work_dir
|
|
|
|
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
def make_livecd( disk_img, squashfs_args="", templatedir=None,
|
2012-03-08 01:29:31 +00:00
|
|
|
title="Linux", project="Linux", releasever=16, isolabel=None ):
|
2011-09-24 00:36:09 +00:00
|
|
|
"""
|
|
|
|
Take the content from the disk image and make a livecd out of it
|
|
|
|
|
|
|
|
This uses wwood's squashfs live initramfs method:
|
|
|
|
* put the real / into LiveOS/rootfs.img
|
|
|
|
* make a squashfs of the LiveOS/rootfs.img tree
|
|
|
|
* make a simple initramfs with the squashfs.img and /etc/cmdline in it
|
|
|
|
* make a cpio of that tree
|
|
|
|
* append the squashfs.cpio to a dracut initramfs for each kernel installed
|
|
|
|
|
|
|
|
Then on boot dracut reads /etc/cmdline which points to the squashfs.img
|
|
|
|
mounts that and then mounts LiveOS/rootfs.img as /
|
|
|
|
|
|
|
|
"""
|
|
|
|
with PartitionMount( disk_img ) as img_mount:
|
2012-02-28 21:57:17 +00:00
|
|
|
if not img_mount or not img_mount.mount_dir:
|
|
|
|
return None
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
kernel_list = get_kernels( joinpaths( img_mount.mount_dir, "boot" ) )
|
|
|
|
log.debug( "kernel_list = {0}".format(kernel_list) )
|
|
|
|
if kernel_list:
|
|
|
|
kernel_arch = kernel_list[0].split(".")[-1]
|
|
|
|
else:
|
|
|
|
kernel_arch = "i386"
|
|
|
|
log.debug( "kernel_arch = {0}".format(kernel_arch) )
|
|
|
|
|
|
|
|
work_dir = tempfile.mkdtemp()
|
|
|
|
log.info( "working dir is {0}".format(work_dir) )
|
|
|
|
|
|
|
|
runtime = "images/install.img"
|
|
|
|
# This is a mounted image partition, cannot hardlink to it, so just use it
|
|
|
|
installroot = img_mount.mount_dir
|
|
|
|
# symlink installroot/images to work_dir/images so we don't run out of space
|
|
|
|
os.makedirs( joinpaths( work_dir, "images" ) )
|
|
|
|
|
|
|
|
# TreeBuilder expects the config files to be in /tmp/config_files
|
|
|
|
# I think these should be release specific, not from lorax, but for now
|
|
|
|
configdir = joinpaths(templatedir,"config_files/live/")
|
|
|
|
configdir_path = "tmp/config_files"
|
|
|
|
fullpath = joinpaths(installroot, configdir_path)
|
|
|
|
if os.path.exists(fullpath):
|
|
|
|
remove(fullpath)
|
|
|
|
shutil.copytree(configdir, fullpath)
|
|
|
|
|
|
|
|
# Fake yum object
|
|
|
|
fake_yum = DataHolder( conf=DataHolder( installroot=installroot ) )
|
|
|
|
# Fake arch with only basearch set
|
|
|
|
arch = DataHolder( basearch=kernel_arch, libdir=None, buildarch=None )
|
|
|
|
# TODO: Need to get release info from someplace...
|
|
|
|
product = DataHolder( name=project, version=releasever, release="",
|
2012-01-30 19:02:56 +00:00
|
|
|
variant="", bugurl="", isfinal=False )
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
rb = RuntimeBuilder( product, arch, fake_yum )
|
|
|
|
log.info( "Creating runtime" )
|
|
|
|
rb.create_runtime( joinpaths( work_dir, runtime ), size=None )
|
|
|
|
|
|
|
|
# Link /images to work_dir/images to make the templates happy
|
|
|
|
if os.path.islink( joinpaths( installroot, "images" ) ):
|
|
|
|
os.unlink( joinpaths( installroot, "images" ) )
|
2012-07-26 20:57:25 +00:00
|
|
|
cmd = ["/bin/ln", "-s", joinpaths(work_dir, "images"),
|
|
|
|
joinpaths(installroot, "images")]
|
|
|
|
execWithRedirect(cmd[0], cmd[1:])
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2012-03-08 01:29:31 +00:00
|
|
|
isolabel = isolabel or "{0.name} {0.version} {1.basearch}".format(product, arch)
|
|
|
|
if len(isolabel) > 32:
|
|
|
|
isolabel = isolabel[:32]
|
|
|
|
log.error("Truncating isolabel to 32 chars: %s" % (isolabel,))
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
tb = TreeBuilder( product=product, arch=arch,
|
|
|
|
inroot=installroot, outroot=work_dir,
|
2012-03-08 01:29:31 +00:00
|
|
|
runtime=runtime, isolabel=isolabel, templatedir=templatedir)
|
2011-09-24 00:36:09 +00:00
|
|
|
log.info( "Rebuilding initrds" )
|
|
|
|
if not opts.dracut_args:
|
|
|
|
dracut_args = DRACUT_DEFAULT
|
|
|
|
else:
|
|
|
|
dracut_args = []
|
|
|
|
for arg in opts.dracut_args:
|
|
|
|
dracut_args += arg.split(" ", 1)
|
|
|
|
log.info( "dracut args = {0}".format( dracut_args ) )
|
|
|
|
tb.rebuild_initrds( add_args=dracut_args )
|
|
|
|
log.info( "Building boot.iso" )
|
|
|
|
tb.build()
|
|
|
|
|
|
|
|
return work_dir
|
|
|
|
|
|
|
|
|
2012-07-26 20:57:25 +00:00
|
|
|
def setup_logging(opts):
|
|
|
|
# Setup logging to console and to logfile
|
|
|
|
log.setLevel(logging.DEBUG)
|
|
|
|
pylorax_log.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
|
sh = logging.StreamHandler()
|
|
|
|
sh.setLevel(logging.INFO)
|
|
|
|
fmt = logging.Formatter("%(asctime)s: %(message)s")
|
|
|
|
sh.setFormatter(fmt)
|
|
|
|
log.addHandler(sh)
|
|
|
|
pylorax_log.addHandler(sh)
|
|
|
|
|
|
|
|
fh = logging.FileHandler(filename=opts.logfile, mode="w")
|
|
|
|
fh.setLevel(logging.DEBUG)
|
|
|
|
fmt = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
|
|
|
|
fh.setFormatter(fmt)
|
|
|
|
log.addHandler(fh)
|
|
|
|
pylorax_log.addHandler(fh)
|
|
|
|
|
|
|
|
# External program output log
|
|
|
|
program_log.setLevel(logging.DEBUG)
|
|
|
|
logfile = os.path.abspath(os.path.dirname(opts.logfile))+"/program.log"
|
|
|
|
fh = logging.FileHandler(filename=logfile, mode="w")
|
|
|
|
fh.setLevel(logging.DEBUG)
|
|
|
|
program_log.addHandler(fh)
|
|
|
|
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
parser = argparse.ArgumentParser( description="Create Live Install Media",
|
|
|
|
fromfile_prefix_chars="@" )
|
|
|
|
|
2011-12-09 01:12:06 +00:00
|
|
|
# These are mutually exclusive, one is required
|
2011-09-24 00:36:09 +00:00
|
|
|
action = parser.add_mutually_exclusive_group( required=True )
|
|
|
|
action.add_argument( "--make-iso", action="store_true",
|
|
|
|
help="Build a live iso" )
|
|
|
|
action.add_argument( "--make-disk", action="store_true",
|
|
|
|
help="Build a disk image" )
|
|
|
|
action.add_argument( "--make-appliance", action="store_true",
|
|
|
|
help="Build an appliance image and XML description" )
|
|
|
|
action.add_argument( "--make-ami", action="store_true",
|
|
|
|
help="Build an ami image" )
|
|
|
|
|
2011-12-09 01:12:06 +00:00
|
|
|
parser.add_argument( "--iso", type=os.path.abspath,
|
2011-09-24 00:36:09 +00:00
|
|
|
help="Anaconda installation .iso path to use for virt-install" )
|
2011-12-09 01:12:06 +00:00
|
|
|
parser.add_argument( "--disk-image", type=os.path.abspath,
|
2011-09-24 00:36:09 +00:00
|
|
|
help="Path to disk image to use for creating final image" )
|
|
|
|
|
|
|
|
parser.add_argument( "--ks", action="append", type=os.path.abspath,
|
|
|
|
help="Kickstart file defining the install." )
|
2012-05-24 22:55:46 +00:00
|
|
|
parser.add_argument( "--image-name", default=None,
|
|
|
|
help="Name of disk image to create. Default is a random name." )
|
2011-09-24 00:36:09 +00:00
|
|
|
parser.add_argument( "--image-only", action="store_true",
|
|
|
|
help="Exit after creating disk image." )
|
|
|
|
parser.add_argument( "--keep-image", action="store_true",
|
|
|
|
help="Keep raw disk image after .iso creation" )
|
|
|
|
parser.add_argument( "--no-virt", action="store_true",
|
|
|
|
help="Use Anaconda's image install instead of virt-install" )
|
2011-12-09 01:12:06 +00:00
|
|
|
parser.add_argument( "--proxy",
|
|
|
|
help="proxy URL to use for the install" )
|
|
|
|
parser.add_argument( "--anaconda-arg", action="append", dest="anaconda_args",
|
|
|
|
help="Additional argument to pass to anaconda (no-virt "
|
|
|
|
"mode). Pass once for each argument" )
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
parser.add_argument( "--logfile", default="./livemedia.log",
|
|
|
|
type=os.path.abspath,
|
|
|
|
help="Path to logfile" )
|
|
|
|
parser.add_argument( "--lorax-templates", default="/usr/share/lorax/",
|
|
|
|
type=os.path.abspath,
|
|
|
|
help="Path to mako templates for lorax" )
|
2012-05-11 20:12:17 +00:00
|
|
|
parser.add_argument( "--tmp", default="/var/tmp", type=os.path.abspath,
|
2011-09-24 00:36:09 +00:00
|
|
|
help="Top level temporary directory" )
|
|
|
|
parser.add_argument( "--resultdir", default=None, dest="result_dir",
|
|
|
|
type=os.path.abspath,
|
|
|
|
help="Directory to copy the resulting images and iso into. "
|
|
|
|
"Defaults to the temporary working directory")
|
|
|
|
|
2012-05-24 22:55:46 +00:00
|
|
|
# Group of arguments for appliance creation
|
|
|
|
app_group = parser.add_argument_group("appliance arguments")
|
|
|
|
app_group.add_argument( "--app-name", default=None,
|
|
|
|
help="Name of appliance to pass to template")
|
|
|
|
app_group.add_argument( "--app-template", default=None,
|
|
|
|
help="Path to template to use for appliance data.")
|
|
|
|
app_group.add_argument( "--app-file", default="appliance.xml",
|
|
|
|
help="Appliance template results file.")
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
# Group of arguments to pass to virt-install
|
2012-05-10 21:21:42 +00:00
|
|
|
if not libvirt:
|
|
|
|
virt_group = parser.add_argument_group("virt-install arguments (DISABLED -- no libvirt)")
|
|
|
|
else:
|
|
|
|
virt_group = parser.add_argument_group("virt-install arguments")
|
2011-09-24 00:36:09 +00:00
|
|
|
virt_group.add_argument("--ram", metavar="MEMORY", default=1024,
|
|
|
|
help="Memory to allocate for installer in megabytes." )
|
|
|
|
virt_group.add_argument("--vcpus", default=1,
|
|
|
|
help="Passed to --vcpus command" )
|
|
|
|
virt_group.add_argument("--vnc",
|
|
|
|
help="Passed to --graphics command" )
|
2012-05-29 18:21:15 +00:00
|
|
|
virt_group.add_argument("--arch", default=None,
|
2011-09-24 00:36:09 +00:00
|
|
|
help="Passed to --arch command" )
|
|
|
|
virt_group.add_argument( "--kernel-args",
|
|
|
|
help="Additional argument to pass to the installation kernel" )
|
|
|
|
|
|
|
|
# dracut arguments
|
|
|
|
dracut_group = parser.add_argument_group( "dracut arguments" )
|
|
|
|
dracut_group.add_argument( "--dracut-arg", action="append", dest="dracut_args",
|
|
|
|
help="Argument to pass to dracut when "
|
|
|
|
"rebuilding the initramfs. Pass this "
|
|
|
|
"once for each argument. NOTE: this "
|
|
|
|
"overrides the default. (default: %s)" % (DRACUT_DEFAULT,) )
|
|
|
|
|
|
|
|
parser.add_argument( "--title", default="Linux Live Media",
|
|
|
|
help="Substituted for @TITLE@ in bootloader config files" )
|
|
|
|
parser.add_argument( "--project", default="Linux",
|
|
|
|
help="substituted for @PROJECT@ in bootloader config files" )
|
|
|
|
parser.add_argument( "--releasever", type=int, default=16,
|
|
|
|
help="substituted for @VERSION@ in bootloader config files" )
|
2012-03-08 01:29:31 +00:00
|
|
|
parser.add_argument( "--volid", default=None, help="volume id")
|
2011-09-24 00:36:09 +00:00
|
|
|
parser.add_argument( "--squashfs_args",
|
|
|
|
help="additional squashfs args" )
|
|
|
|
|
|
|
|
opts = parser.parse_args()
|
|
|
|
|
2012-07-26 20:57:25 +00:00
|
|
|
setup_logging(opts)
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
log.debug( opts )
|
|
|
|
|
|
|
|
if os.getuid() != 0:
|
|
|
|
log.error("You need to run this as root")
|
|
|
|
sys.exit( 1 )
|
|
|
|
|
2012-02-28 21:57:17 +00:00
|
|
|
if opts.make_iso and not os.path.exists( opts.lorax_templates ):
|
2011-09-24 00:36:09 +00:00
|
|
|
log.error( "The lorax templates directory ({0}) doesn't"
|
|
|
|
" exist.".format( opts.lorax_templates ) )
|
|
|
|
sys.exit( 1 )
|
|
|
|
|
|
|
|
if opts.result_dir and os.path.exists(opts.result_dir):
|
|
|
|
log.error( "The results_dir ({0}) should not exist, please delete or "
|
|
|
|
"move its contents".format( opts.result_dir ))
|
|
|
|
sys.exit( 1 )
|
|
|
|
|
|
|
|
if opts.iso and not os.path.exists( opts.iso ):
|
|
|
|
log.error( "The iso {0} is missing.".format( opts.iso ) )
|
|
|
|
sys.exit( 1 )
|
|
|
|
|
|
|
|
if opts.disk_image and not os.path.exists( opts.disk_image ):
|
|
|
|
log.error( "The disk image {0} is missing.".format( opts.disk_image ) )
|
|
|
|
sys.exit( 1 )
|
|
|
|
|
2011-12-09 01:12:06 +00:00
|
|
|
if not opts.no_virt and not opts.iso and not opts.disk_image:
|
|
|
|
log.error( "virt-install needs an install iso." )
|
|
|
|
sys.exit( 1 )
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2012-03-08 01:29:31 +00:00
|
|
|
if opts.volid and len(opts.volid) > 32:
|
|
|
|
logger.fatal("the volume id cannot be longer than 32 characters")
|
|
|
|
sys.exit(1)
|
|
|
|
|
2012-05-10 21:21:42 +00:00
|
|
|
if not opts.no_virt and not libvirt:
|
2012-07-21 00:44:30 +00:00
|
|
|
log.error("virt-install requires libvirt-python to be installed.")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
if not opts.no_virt and not os.path.exists("/usr/bin/virt-install"):
|
|
|
|
log.error("virt-install requires python-virtinst to be installed.")
|
2012-05-10 21:21:42 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
2012-05-24 22:55:46 +00:00
|
|
|
if opts.no_virt and not os.path.exists("/usr/sbin/anaconda"):
|
|
|
|
log.error("no-virt requires anaconda to be installed.")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
if opts.make_appliance and not opts.app_template:
|
|
|
|
opts.app_template = joinpaths(opts.lorax_templates,
|
|
|
|
"appliance/libvirt.tmpl")
|
|
|
|
|
|
|
|
if opts.make_appliance and not os.path.exists(opts.app_template):
|
|
|
|
log.error("The appliance template ({0}) doesn't "
|
|
|
|
"exist".format(opts.app_template))
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
if opts.image_name and os.path.exists(joinpaths(opts.tmp,opts.image_name)):
|
|
|
|
log.error("The disk image to be created should not exist.")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
if opts.app_file:
|
|
|
|
opts.app_file = joinpaths(opts.tmp, opts.app_file)
|
|
|
|
|
2012-05-11 20:12:17 +00:00
|
|
|
tempfile.tempdir = opts.tmp
|
2012-05-24 22:55:46 +00:00
|
|
|
disk_img = None
|
2012-05-11 20:12:17 +00:00
|
|
|
|
2012-05-24 22:55:46 +00:00
|
|
|
# Parse the kickstart
|
|
|
|
if opts.ks:
|
2011-09-24 00:36:09 +00:00
|
|
|
ks_version = makeVersion()
|
|
|
|
ks = KickstartParser( ks_version, errorsAreFatal=False, missingIncludeIsFatal=False )
|
|
|
|
ks.readKickstart( opts.ks[0] )
|
2012-05-24 22:55:46 +00:00
|
|
|
|
|
|
|
# Make the disk image
|
|
|
|
if not opts.disk_image:
|
|
|
|
if not opts.ks:
|
|
|
|
log.error("Image creation requires a kickstart file")
|
|
|
|
sys.exit(1)
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
disk_size = 1 + (sum( [p.size for p in ks.handler.partition.partitions] ) / 1024)
|
|
|
|
log.info( "disk_size = {0}GB".format(disk_size) )
|
|
|
|
|
2011-12-09 01:12:06 +00:00
|
|
|
if ks.handler.method.method != "url":
|
|
|
|
log.error( "Only url install method is currently supported. Please "
|
|
|
|
"fix your kickstart file." )
|
|
|
|
sys.exit( 1 )
|
|
|
|
repo_url = ks.handler.method.url
|
2012-05-15 20:38:56 +00:00
|
|
|
if ks.handler.displaymode.displayMode is not None:
|
|
|
|
log.error("The kickstart must not set a display mode (text, cmdline, "
|
|
|
|
"graphical), this will interfere with livemedia-creator.")
|
|
|
|
sys.exit(1)
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2012-05-24 22:55:46 +00:00
|
|
|
if opts.image_name:
|
|
|
|
disk_img = joinpaths(opts.tmp, opts.image_name)
|
|
|
|
else:
|
|
|
|
disk_img = tempfile.mktemp( prefix="disk", suffix=".img", dir=opts.tmp )
|
2011-12-09 01:12:06 +00:00
|
|
|
install_log = os.path.abspath(os.path.dirname(opts.logfile))+"/virt-install.log"
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2011-12-09 01:12:06 +00:00
|
|
|
log.info( "disk_img = {0}".format(disk_img) )
|
|
|
|
log.info( "install_log = {0}".format(install_log) )
|
|
|
|
|
|
|
|
if opts.no_virt:
|
|
|
|
anaconda_args = []
|
|
|
|
if opts.anaconda_args:
|
|
|
|
for arg in opts.anaconda_args:
|
|
|
|
anaconda_args += arg.split(" ", 1)
|
|
|
|
if opts.proxy:
|
|
|
|
anaconda_args += [ "--proxy", opts.proxy ]
|
|
|
|
|
|
|
|
# Use anaconda's image install
|
|
|
|
install_error = anaconda_install( disk_img, disk_size, opts.ks[0],
|
|
|
|
repo_url, anaconda_args )
|
|
|
|
|
|
|
|
# Move the anaconda logs over to a log directory
|
|
|
|
log_dir = os.path.abspath(os.path.dirname(opts.logfile))
|
|
|
|
log_anaconda = joinpaths( log_dir, "anaconda" )
|
|
|
|
if not os.path.isdir( log_anaconda ):
|
|
|
|
os.mkdir( log_anaconda )
|
|
|
|
for l in ["anaconda.log", "ifcfg.log", "program.log", "storage.log",
|
|
|
|
"yum.log"]:
|
|
|
|
if os.path.exists( "/tmp/"+l ):
|
|
|
|
shutil.copy2( "/tmp/"+l, log_anaconda )
|
|
|
|
os.unlink( "/tmp/"+l )
|
2012-05-12 00:35:16 +00:00
|
|
|
|
|
|
|
# If anaconda failed the disk image may still be in use by dm
|
|
|
|
dm_name = os.path.splitext(os.path.basename(disk_img))[0]
|
|
|
|
dm_path = "/dev/mapper/"+dm_name
|
|
|
|
if os.path.exists(dm_path):
|
|
|
|
dm_detach(dm_path)
|
|
|
|
loop_detach(get_loop_name(disk_img))
|
|
|
|
|
2011-12-09 01:12:06 +00:00
|
|
|
else:
|
|
|
|
iso_mount = IsoMountpoint( opts.iso )
|
|
|
|
log_monitor = LogMonitor( install_log )
|
|
|
|
|
|
|
|
kernel_args = ""
|
|
|
|
if opts.kernel_args:
|
|
|
|
kernel_args += opts.kernel_args
|
|
|
|
if opts.proxy:
|
|
|
|
kernel_args += " proxy="+opts.proxy
|
|
|
|
|
|
|
|
virt = VirtualInstall( iso_mount, opts.ks, disk_img, disk_size,
|
2012-05-29 18:21:15 +00:00
|
|
|
kernel_args, opts.ram, opts.vnc, opts.arch,
|
2011-12-09 01:12:06 +00:00
|
|
|
log_check = log_monitor.server.log_check,
|
|
|
|
virtio_host = log_monitor.host,
|
|
|
|
virtio_port = log_monitor.port )
|
|
|
|
virt.destroy()
|
|
|
|
log_monitor.shutdown()
|
|
|
|
iso_mount.umount()
|
|
|
|
|
|
|
|
install_error = log_monitor.server.log_check()
|
|
|
|
|
|
|
|
if install_error:
|
2011-09-24 00:36:09 +00:00
|
|
|
log.error( "Install failed" )
|
|
|
|
if not opts.keep_image:
|
|
|
|
log.info( "Removing bad disk image" )
|
|
|
|
os.unlink( disk_img )
|
|
|
|
sys.exit( 1 )
|
|
|
|
else:
|
|
|
|
log.info( "Disk Image install successful" )
|
|
|
|
|
|
|
|
|
|
|
|
result_dir = None
|
|
|
|
if opts.make_iso and not opts.image_only:
|
|
|
|
result_dir = make_livecd( opts.disk_image or disk_img, opts.squashfs_args,
|
|
|
|
opts.lorax_templates,
|
2012-03-08 01:29:31 +00:00
|
|
|
opts.title, opts.project, opts.releasever,
|
|
|
|
opts.volid )
|
2012-05-24 22:55:46 +00:00
|
|
|
# cleanup the mess
|
|
|
|
if disk_img and not opts.keep_image and not opts.disk_image:
|
|
|
|
os.unlink( disk_img )
|
|
|
|
log.info("Disk image erased")
|
|
|
|
disk_img = None
|
2012-02-28 21:57:17 +00:00
|
|
|
elif opts.make_ami and not opts.image_only:
|
|
|
|
result_dir = make_ami(opts.disk_image or disk_img)
|
2012-05-24 22:55:46 +00:00
|
|
|
elif opts.make_appliance and not opts.image_only:
|
|
|
|
if not opts.ks:
|
|
|
|
networks = []
|
|
|
|
else:
|
|
|
|
networks = ks.handler.network.network
|
|
|
|
make_appliance(opts.disk_image or disk_img, opts.app_name,
|
|
|
|
opts.app_template, opts.app_file, networks, opts.ram,
|
|
|
|
opts.vcpus, opts.arch, opts.title, opts.project, opts.releasever)
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2012-02-28 21:57:17 +00:00
|
|
|
if opts.result_dir and result_dir:
|
|
|
|
shutil.copytree( result_dir, opts.result_dir )
|
|
|
|
shutil.rmtree( result_dir )
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
log.info("SUMMARY")
|
|
|
|
log.info("-------")
|
|
|
|
log.info("Logs are in {0}".format(os.path.abspath(os.path.dirname(opts.logfile))))
|
2012-05-24 22:55:46 +00:00
|
|
|
if disk_img:
|
2011-09-24 00:36:09 +00:00
|
|
|
log.info("Disk image is at {0}".format(disk_img))
|
2012-05-24 22:55:46 +00:00
|
|
|
if opts.make_appliance:
|
|
|
|
log.info("Appliance description is in {0}".format(opts.app_file))
|
2011-09-24 00:36:09 +00:00
|
|
|
if result_dir:
|
|
|
|
log.info("Results are in {0}".format(opts.result_dir or result_dir))
|
|
|
|
|
|
|
|
sys.exit( 0 )
|
|
|
|
|