livemedia-creator: Use qemu instead of virt-install

Switching to using qemu directly allows lmc to be more flexible. It can
now run from inside a mock chroot for creation of all image types,
inculding disk images, and can take advantage of KVM on the host system
if /dev/kvm device is present inside the mock.

It should also be possible to create cross-arch images, but without kvm
available this is likely to be a very slow option.
This commit is contained in:
Brian C. Lane 2016-01-05 16:49:40 -08:00
parent 8e47b8a96a
commit f8316a7b89
3 changed files with 277 additions and 174 deletions

View File

@ -226,7 +226,7 @@ Passed to --vcpus command
.TP .TP
\fB\-\-vnc VNC\fR \fB\-\-vnc VNC\fR
Passed to --graphics command Passed to qemu -display command. eg. vnc=127.0.0.1:5, default is to choose the first unused vnc port.
.TP .TP
\fB\-\-arch ARCH\fR \fB\-\-arch ARCH\fR

View File

@ -12,8 +12,8 @@ isos, bootable (partitioned) disk images, tarfiles, and filesystem images for
use with virtualization and container solutions like libvirt, docker, and use with virtualization and container solutions like libvirt, docker, and
OpenStack. OpenStack.
The general idea is to use virt-install with kickstart and an Anaconda boot.iso The general idea is to use qemu with kickstart and an Anaconda boot.iso to
to install into a disk image and then use the disk image to create the bootable install into a disk image and then use the disk image to create the bootable
media. media.
livemedia-creator --help will describe all of the options available. At the livemedia-creator --help will describe all of the options available. At the
@ -21,19 +21,14 @@ minimum you need:
``--make-iso`` to create a final bootable .iso or one of the other ``--make-*`` options. ``--make-iso`` to create a final bootable .iso or one of the other ``--make-*`` options.
``--iso`` to specify the Anaconda install media to use with virt-install ``--iso`` to specify the Anaconda install media to use with qemu.
``--ks`` to select the kickstart file describing what to install. ``--ks`` to select the kickstart file describing what to install.
To use livemedia-creator with virt-install you will need to install the To use livemedia-creator with virtualization you will need to have qemu installed.
following packages, as well as have libvirtd setup correctly.
* ``virt-install``
* ``libvirt-python``
If you are going to be using Anaconda directly, with ``--no-virt`` mode, make sure If you are going to be using Anaconda directly, with ``--no-virt`` mode, make sure
you have the anaconda package installed. You can use the anaconda-tui package you have the anaconda-tui package installed.
to save a bit of space on the build system.
Conventions used in this document: Conventions used in this document:
@ -58,8 +53,8 @@ You can run it directly from the lorax git repo like this::
--make-iso --iso=/extra/iso/boot.iso \ --make-iso --iso=/extra/iso/boot.iso \
--ks=./docs/fedora-livemedia.ks --lorax-templates=./share/ --ks=./docs/fedora-livemedia.ks --lorax-templates=./share/
If you want to watch the install you can pass ``--vnc vnc`` and use a vnc client You can observe the installation using vnc. The logs will show what port was
to connect to localhost:0 chosen, or you can use a specific port by passing it. eg. ``--vnc vnc:127.0.0.1:5``
This is usually a good idea when testing changes to the kickstart. lmc tries This is usually a good idea when testing changes to the kickstart. lmc tries
to monitor the logs for fatal errors, but may not catch everything. to monitor the logs for fatal errors, but may not catch everything.
@ -74,7 +69,7 @@ Normally you would run both stages, but it is possible to stop after the
install stage, by using ``--image-only``, or to skip the install stage and use install stage, by using ``--image-only``, or to skip the install stage and use
a previously created disk image by passing ``--disk-image`` or ``--fs-image`` a previously created disk image by passing ``--disk-image`` or ``--fs-image``
When creating an iso virt-install boots using the passed Anaconda installer iso When creating an iso qemu boots using the passed Anaconda installer iso
and installs the system based on the kickstart. The ``%post`` section of the and installs the system based on the kickstart. The ``%post`` section of the
kickstart is used to customize the installed system in the same way that kickstart is used to customize the installed system in the same way that
current spin-kickstarts do. current spin-kickstarts do.
@ -82,24 +77,22 @@ current spin-kickstarts do.
livemedia-creator monitors the install process for problems by watching the livemedia-creator monitors the install process for problems by watching the
install logs. They are written to the current directory or to the base install logs. They are written to the current directory or to the base
directory specified by the --logfile command. You can also monitor the install directory specified by the --logfile command. You can also monitor the install
by passing ``--vnc vnc`` and using a vnc client. This is recommended when first by using a vnc client. This is recommended when first modifying a kickstart,
modifying a kickstart, since there are still places where Anaconda may get since there are still places where Anaconda may get stuck without the log
stuck without the log monitor catching it. monitor catching it.
The output from this process is a partitioned disk image. kpartx can be used The output from this process is a partitioned disk image. kpartx can be used
to mount and examine it when there is a problem with the install. It can also to mount and examine it when there is a problem with the install. It can also
be booted using kvm. be booted using kvm.
When creating an iso the disk image's / partition is copied into a formatted When creating an iso the disk image's / partition is copied into a formatted
disk image which is then used as the input to lorax for creation of the final filesystem image which is then used as the input to lorax for creation of the
media. final media.
The final image is created by lorax, using the templates in /usr/share/lorax/ The final image is created by lorax, using the templates in /usr/share/lorax/live/
or the directory specified by ``--lorax-templates`` or the live directory below the directory specified by ``--lorax-templates``. The
templates are written using the Mako template system with some extra commands
Currently the standard lorax templates are used to make a bootable iso, but added by lorax.
it should be possible to modify them to output other results. They are
written using the Mako template system which is very flexible.
.. note:: .. note::
The output from --make-iso includes the artifacts used to create the boot.iso; The output from --make-iso includes the artifacts used to create the boot.iso;
@ -163,10 +156,10 @@ changes. Here are the steps I used to convert the Fedora XFCE spin.
memtest86+ memtest86+
syslinux syslinux
One drawback to using virt-install is that it pulls the packages from One drawback to using qemu is that it pulls the packages from the repo each
the repo each time you run it. To speed things up you either need a local time you run it. To speed things up you either need a local mirror of the
mirror of the packages, or you can use a caching proxy. When using a proxy packages, or you can use a caching proxy. When using a proxy you pass it to
you pass it to livemedia-creator like this: livemedia-creator like this:
``--proxy=http://proxy.yourdomain.com:3128`` ``--proxy=http://proxy.yourdomain.com:3128``
@ -175,16 +168,16 @@ packages will get cached, so your kickstart url would look like:
``url --url="http://dl.fedoraproject.org/pub/fedora/linux/development/rawhide/x86_64/os/"`` ``url --url="http://dl.fedoraproject.org/pub/fedora/linux/development/rawhide/x86_64/os/"``
You can also add an update repo, but don't name it updates. Add --proxy to You can also add an update repo, but don't name it updates. Add --proxy to it
it as well. as well.
Anaconda image install (no-virt) Anaconda image install (no-virt)
-------------------------------- --------------------------------
You can create images without using virt-install by passing ``--no-virt`` on the You can create images without using qemu by passing ``--no-virt`` on the
cmdline. This will use Anaconda's directory install feature to handle the install. cmdline. This will use Anaconda's directory install feature to handle the
There are a couple of things to keep in mind when doing this: install. There are a couple of things to keep in mind when doing this:
1. It will be most reliable when building images for the same release that the 1. It will be most reliable when building images for the same release that the
host is running. Because Anaconda has expectations about the system it is host is running. Because Anaconda has expectations about the system it is
@ -302,9 +295,9 @@ You can also create qcow2 appliance images using ``--image-type=qcow2``, for exa
Filesystem Image Creation Filesystem Image Creation
------------------------- -------------------------
livemedia-creator can be used to create un-partitined filesystem images using the livemedia-creator can be used to create un-partitined filesystem images using
``--make-fsimage`` option. As of version 21.8 this works with both virt-install and no-virt modes the ``--make-fsimage`` option. As of version 21.8 this works with both qemu and
of operation. Previously it was only available with no-virt. no-virt modes of operation. Previously it was only available with no-virt.
Kickstarts should have a single / partition with no extra mountpoints. Kickstarts should have a single / partition with no extra mountpoints.
@ -347,8 +340,8 @@ using Atomic installer iso with local repo included in the image can be found
in docs/rhel-atomic-pxe-live.ks. in docs/rhel-atomic-pxe-live.ks.
Using Mock to Create Images Using Mock and --no-virt to Create Images
--------------------------- -----------------------------------------
As of lorax version 22.2 you can use livemedia-creator and anaconda version As of lorax version 22.2 you can use livemedia-creator and anaconda version
22.15 inside of a mock chroot with --make-iso and --make-fsimage. 22.15 inside of a mock chroot with --make-iso and --make-fsimage.
@ -404,6 +397,66 @@ located at ~/results/try-1/images/boot.iso, and the ~/results/try-1/
directory tree will also contain the vmlinuz, initrd, etc. directory tree will also contain the vmlinuz, initrd, etc.
Using Mock and qemu to Create Images
------------------------------------
Version 25.0 of livemedia-creator switches to using qemu for virtualization.
This allows creation of all image types, and use of the KVM on the host if
/dev/kvm is present in the mock environment.
On the host system:
1. yum install -y mock
2. Add a user to the mock group to use for running mock. eg. builder
3. Create a new /etc/mock/ config file based on the rawhide one, or modify the
existing one so that the following options are setup::
config_opts['chroot_setup_cmd'] = 'install @buildsys-build lorax qemu'
# build results go into /home/builder/results/
config_opts['plugin_conf']['bind_mount_opts']['dirs'].append(('/home/builder/results','/results/'))
If you are creating images for a branched release of Fedora you should also enable
the updates-testing repository so that you get the latest builds in your mock chroot.
The following steps are run as the builder user who is a member of the mock
group.
4. Make a directory for results matching the bind mount above
``mkdir ~/results/``
5. Copy the example kickstarts
``cp /usr/share/docs/lorax/*ks .``
6. Make sure tar and dracut-network are in the %packages section and that the
``url points to the correct repo``
7. Init the mock
``mock -r fedora-rawhide-x86_64 --init``
8. Copy the kickstart inside the mock
``mock -r fedora-rawhide-x86_64 --copyin ./fedora-minimal.ks /root/``
9. Copy the Anaconda boot.iso inside the mock
``mock -r fedora-rawhide-x86_64 --copyin ./boot.iso /root/``
10. Make a minimal iso::
mock -r fedora-rawhide-x86_64 --chroot -- livemedia-creator \
--resultdir=/results/try-1 --logfile=/results/logs/try-1/try-1.log \
--make-iso --ks /root/fedora-minimal.ks --iso /root/boot.iso
Results will be in ./results/try-1 and logs under /results/logs/try-1/
including anaconda logs and livemedia-creator logs. The new iso will be
located at ~/results/try-1/images/boot.iso, and the ~/results/try-1/
directory tree will also contain the vmlinuz, initrd, etc.
This will run qemu without kvm support, which is going to be very slow. You can
add ``mknod /dev/kvm c 10 232;`` to create the device node before running lmc.
OpenStack Image Creation OpenStack Image Creation
------------------------ ------------------------
@ -528,29 +581,27 @@ Make sure that the kickstart you are using creates a /boot/efi partition by incl
Debugging problems Debugging problems
------------------ ------------------
Sometimes an installation will get stuck. When using virt-install the logs will Sometimes an installation will get stuck. When using qemu the logs will
be written to ./virt-install.log and most of the time any problems that happen be written to ./virt-install.log and most of the time any problems that happen
will be near the end of the file. lmc tries to detect common errors and will will be near the end of the file. lmc tries to detect common errors and will
cancel the installation when they happen. But not everything can be caught. cancel the installation when they happen. But not everything can be caught.
When creating a new kickstart it is helpful to use the ``--vnc vnc`` command so When creating a new kickstart it is helpful to use vnc so that you can monitor
that you can monitor the installation as it happens, and if it gets stuck the installation as it happens, and if it gets stuck without lmc detecting the
without lmc detecting the problem you can switch to tty1 and examine the system problem you can switch to tty1 and examine the system directly.
directly.
If it does get stuck the best way to cancel is to use virsh to destroy the domain. If it does get stuck the best way to cancel is to use kill -9 on the qemu pid,
lmc will detect that the process died and cleanup.
1. Use ``sudo virsh list`` to show the name of the virt. It will start with LiveOS and contain a UUID.
2. Run ``sudo virsh destroy <name>`` to destroy the domain.
3. Wait 20 seconds or so for lmc to detect that the domain vanished. It should handle cleanup.
If lmc didn't handle the cleanup for some reason you can do this: If lmc didn't handle the cleanup for some reason you can do this:
1. ``sudo virsh undefine <name>`` 1. ``sudo umount /tmp/lmc-XXXX`` to unmount the iso from its mountpoint.
2. ``sudo umount /tmp/tmpXXXX`` to unmount the iso from its mountpoint. 2. ``sudo rm -rf /tmp/lmc-XXXX``
3. ``sudo rm -rf /tmp/tmpXXXX`` 3. ``sudo rm /var/tmp/lmc-disk-XXXXX`` to remove the disk image.
4. ``sudo rm /var/tmp/diskXXXXX`` to remove the disk image.
The logs from the virt-install run are stored in virt-install.log, Note that lmc uses the lmc- prefix for all of its temporary files and
logs from livemedia-creator are in livemedia.log and program.log directories to make it easier to find and clean up leftovers.
The logs from the qemu run are stored in virt-install.log, logs from
livemedia-creator are in livemedia.log and program.log
You can add ``--image-only`` to skip the .iso creation and examine the resulting You can add ``--image-only`` to skip the .iso creation and examine the resulting
disk image. Or you can pass ``--keep-image`` to keep it around after the iso has disk image. Or you can pass ``--keep-image`` to keep it around after the iso has

View File

@ -24,16 +24,15 @@ log = logging.getLogger("livemedia-creator")
import os import os
import sys import sys
import uuid
import tempfile import tempfile
import subprocess import subprocess
from time import sleep
import shutil import shutil
import argparse import argparse
import hashlib import hashlib
import glob import glob
import json import json
from math import ceil from math import ceil
import socket
# Use pykickstart to calculate disk image size # Use pykickstart to calculate disk image size
from pykickstart.parser import KickstartParser from pykickstart.parser import KickstartParser
@ -53,17 +52,11 @@ from pylorax.sysutils import joinpaths, remove
from pylorax.imgutils import PartitionMount, mksparse, mkext4img, loop_detach from pylorax.imgutils import PartitionMount, mksparse, mkext4img, loop_detach
from pylorax.imgutils import get_loop_name, dm_detach, mount, umount, Mount from pylorax.imgutils import get_loop_name, dm_detach, mount, umount, Mount
from pylorax.imgutils import mksquashfs, mkqemu_img, mktar, mkrootfsimg from pylorax.imgutils import mksquashfs, mkqemu_img, mktar, mkrootfsimg
from pylorax.imgutils import copytree from pylorax.imgutils import copytree, mkcpio
from pylorax.executils import execWithRedirect, execReadlines, runcmd from pylorax.executils import execWithRedirect, execReadlines, runcmd
from pylorax.monitor import LogMonitor from pylorax.monitor import LogMonitor
from pylorax.mount import IsoMountpoint from pylorax.mount import IsoMountpoint
# no-virt mode doesn't need libvirt, so make it optional
try:
import libvirt
except ImportError:
libvirt = None
# Default parameters for rebuilding initramfs, override with --dracut-args # Default parameters for rebuilding initramfs, override with --dracut-args
DRACUT_DEFAULT = ["--xz", "--add", "livenet dmsquash-live convertfs pollcdrom qemu qemu-net", DRACUT_DEFAULT = ["--xz", "--add", "livenet dmsquash-live convertfs pollcdrom qemu qemu-net",
"--omit", "plymouth", "--no-hostonly", "--debug", "--no-early-microcode"] "--omit", "plymouth", "--no-hostonly", "--debug", "--no-early-microcode"]
@ -92,11 +85,76 @@ class FakeDNF(object):
pass pass
class VirtualInstall(object): def find_free_port(start=5900, end=5999, host="127.0.0.1"):
""" Return first free port in range.
:param int start: Starting port number
:param int end: Ending port number
:param str host: Host IP to search
:returns: First free port or -1 if none found
:rtype: int
""" """
Run virt-install using an iso and a kickstart s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
for port in range(start, end+1):
try:
s.bind((host, port))
s.close()
return port
except OSError:
pass
return -1
def append_initrd(initrd, files):
""" Append files to an initrd.
:param str initrd: Path to initrd
:param list files: list of file paths to add
:returns: Path to a new initrd
:rtype: str
The files are added to the initrd by creating a cpio image
of the files (stored at /) and writing the cpio to the end of a
copy of the initrd.
The initrd is not changed, a copy is made before appending the
cpio archive.
""" """
def __init__(self, iso, ks_paths, disk_img, img_size=2048, qemu_initrd = tempfile.mktemp(prefix="lmc-initrd-", suffix=".img")
shutil.copy2(initrd, qemu_initrd)
ks_dir = tempfile.mkdtemp(prefix="lmc-ksdir-")
for ks in files:
shutil.copy2(ks, ks_dir)
ks_initrd = tempfile.mktemp(prefix="lmc-ks-", suffix=".img")
mkcpio(ks_dir, ks_initrd)
shutil.rmtree(ks_dir)
with open(qemu_initrd, "ab") as initrd_fp:
with open(ks_initrd, "rb") as ks_fp:
while True:
data = ks_fp.read(1024**2)
if not data:
break
initrd_fp.write(data)
os.unlink(ks_initrd)
return qemu_initrd
class QEMUInstall(object):
"""
Run qemu using an iso and a kickstart
"""
# Mapping of arch to qemu command
QEMU_CMDS = {"x86_64": "qemu-system-x86_64",
"i386": "qemu-system-i386",
"arm": "qemu-system-arm",
"aarch64": "qemu-system-aarch64",
"ppc": "qemu-system-ppc",
"ppc64": "qemu-system-ppc64"
}
def __init__(self, opts, iso, ks_paths, disk_img, img_size=2048,
kernel_args=None, memory=1024, vcpus=None, vnc=None, arch=None, kernel_args=None, memory=1024, vcpus=None, vnc=None, arch=None,
log_check=None, virtio_host="127.0.0.1", virtio_port=6080, log_check=None, virtio_host="127.0.0.1", virtio_port=6080,
image_type=None, boot_uefi=False, ovmf_path=None): image_type=None, boot_uefi=False, ovmf_path=None):
@ -112,7 +170,7 @@ class VirtualInstall(object):
:param str kernel_args: Extra kernel arguments to pass on the kernel cmdline :param str kernel_args: Extra kernel arguments to pass on the kernel cmdline
:param int memory: Amount of RAM to assign to the virt, in MiB :param int memory: Amount of RAM to assign to the virt, in MiB
:param int vcpus: Number of virtual cpus :param int vcpus: Number of virtual cpus
:param str vnc: Arguments to pass to virt-install --graphics :param str vnc: Arguments to pass to qemu -display
:param str arch: Optional architecture to use in the virt :param str arch: Optional architecture to use in the virt
:param log_check: Method that returns True if the installation fails :param log_check: Method that returns True if the installation fails
:type log_check: method :type log_check: method
@ -122,102 +180,97 @@ class VirtualInstall(object):
:param bool boot_uefi: Use OVMF to boot the VM in UEFI mode :param bool boot_uefi: Use OVMF to boot the VM in UEFI mode
:param str ovmf_path: Path to the OVMF firmware :param str ovmf_path: Path to the OVMF firmware
""" """
self.virt_name = "LiveOS-"+str(uuid.uuid4()) # Lookup qemu-system- for arch if passed, or try to guess using host arch
# add --graphics none later qemu_cmd = [self.QEMU_CMDS.get(arch or os.uname().machine, "qemu-system-"+os.uname().machine)]
# add whatever serial cmds are needed later if not os.path.exists("/usr/bin/"+qemu_cmd[0]):
args = ["-n", self.virt_name, raise InstallError("%s does not exist, cannot run qemu" % qemu_cmd[0])
"-r", str(memory),
"--noreboot",
"--noautoconsole"]
args.append("--graphics") qemu_cmd += ["-nodefconfig"]
if vnc: qemu_cmd += ["-m", str(memory)]
args.append(vnc) if vcpus:
else: qemu_cmd += ["-smp", str(vcpus)]
args.append("none")
for ks in ks_paths: if not opts.no_kvm and os.path.exists("/dev/kvm"):
args.append("--initrd-inject") qemu_cmd += ["--machine", "accel=kvm"]
args.append(ks)
disk_opts = "path={0}".format(disk_img) # Copy the initrd from the iso, create a cpio archive of the kickstart files
disk_opts += ",cache=unsafe,discard=unmap" # and append it to the temporary initrd.
if image_type: qemu_initrd = append_initrd(iso.initrd, ks_paths)
disk_opts += ",format={0}".format(image_type) qemu_cmd += ["-kernel", iso.kernel]
else: qemu_cmd += ["-initrd", qemu_initrd]
disk_opts += ",format=raw"
# Add the disk and cdrom
if not os.path.isfile(disk_img): if not os.path.isfile(disk_img):
mksparse(disk_img, img_size * 1024**2) mksparse(disk_img, img_size * 1024**2)
args.append("--disk") drive_args = "file=%s" % disk_img
args.append(disk_opts) drive_args += ",cache=unsafe,discard=unmap"
if image_type:
drive_args += ",format=%s" % image_type
else:
drive_args += ",format=raw"
qemu_cmd += ["-drive", drive_args]
if iso.stage2: drive_args = "file=%s,media=cdrom,readonly=on" % iso.iso_path
disk_opts = "path={0},device=cdrom,readonly=on,shareable=on".format(iso.iso_path) qemu_cmd += ["-drive", drive_args]
args.append("--disk")
args.append(disk_opts)
extra_args = "ks=file:/{0}".format(os.path.basename(ks_paths[0])) # Setup the cmdline args
if not vnc: # ======================
extra_args += " inst.cmdline" cmdline_args = "ks=file:/%s" % os.path.basename(ks_paths[0])
cmdline_args += " inst.stage2=hd:LABEL=%s" % udev_escape(iso.label)
if opts.proxy:
cmdline_args += " inst.proxy=%s" % opts.proxy
if kernel_args: if kernel_args:
extra_args += " "+kernel_args cmdline_args += " "+kernel_args
if iso.stage2: cmdline_args += " inst.text inst.cmdline"
extra_args += " stage2=hd:LABEL={0}".format(udev_escape(iso.label))
args.append("--extra-args")
args.append(extra_args)
args.append("--location") qemu_cmd += ["-append", cmdline_args]
args.append(iso.mount_dir)
channel_args = "tcp,host={0}:{1},mode=connect,target_type=virtio" \ if not opts.vnc:
",name=org.fedoraproject.anaconda.log.0".format( vnc_port = find_free_port()
virtio_host, virtio_port) if vnc_port == -1:
args.append("--channel") raise InstallError("No free VNC ports")
args.append(channel_args) display_args = "vnc=127.0.0.1:%d" % (vnc_port - 5900)
else:
display_args = opts.vnc
log.info("qemu %s", display_args)
qemu_cmd += ["-nographic", "-display", display_args ]
if arch: # Setup the virtio log port
args.append("--arch") qemu_cmd += ["-device", "virtio-serial-pci,id=virtio-serial0"]
args.append(arch) qemu_cmd += ["-device", "virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0"
",id=channel0,name=org.fedoraproject.anaconda.log.0"]
qemu_cmd += ["-chardev", "socket,id=charchannel0,host=%s,port=%s" % (virtio_host, virtio_port)]
if vcpus: # PAss through rng from host
args.append("--vcpus") qemu_cmd += ["-object", "rng-random,id=virtio-rng0,filename=/dev/random"]
args.append(str(vcpus)) qemu_cmd += ["-device", "virtio-rng-pci,rng=virtio-rng0,id=rng0,bus=pci.0,addr=0x9"]
if boot_uefi and ovmf_path: if boot_uefi and ovmf_path:
args.append("--boot") qemu_cmd += ["-drive", "file=%s/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on" % ovmf_path]
args.append(UEFI_FIRMWARE.format(ovmf_path)) qemu_cmd += ["-drive", "file=%sOVMF_VARS.fd,if=pflash,format=raw,unit=1" % ovmf_path]
log.info("Running virt-install.") log.info("Running qemu")
log.debug(qemu_cmd)
try: try:
execWithRedirect("virt-install", args, raise_err=True) execWithRedirect(qemu_cmd[0], qemu_cmd[1:], reset_lang=False, raise_err=True,
callback=lambda p: not log_check())
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
raise InstallError("Problem starting virtual install: %s" % e) log.error("Running qemu failed:")
log.error("cmd: %s", " ".join(e.cmd))
conn = libvirt.openReadOnly(None) log.error("output: %s", e.output or "")
dom = conn.lookupByName(self.virt_name) raise InstallError("QEMUInstall failed")
except (OSError, KeyboardInterrupt) as e:
# TODO: If vnc has been passed, we should look up the port and print that log.error("Running qemu failed: %s", str(e))
# for the user at this point raise InstallError("QEMUInstall failed")
while dom.isActive() and not log_check(): finally:
sys.stdout.write(".") os.unlink(qemu_initrd)
sys.stdout.flush()
sleep(10)
print()
if log_check(): if log_check():
log.info("Installation error detected. See logfile.") log.error("Installation error detected. See logfile for details.")
raise InstallError("QEMUInstall failed")
else: else:
log.info("Install finished. Or at least virt shut down.") log.info("Installation finished without errors.")
def destroy(self):
"""
Make sure the virt has been shut down and destroyed
Could use libvirt for this instead.
"""
log.info("Shutting down %s", self.virt_name)
subprocess.call(["virsh", "destroy", self.virt_name])
subprocess.call(["virsh", "undefine", self.virt_name])
def is_image_mounted(disk_img): def is_image_mounted(disk_img):
""" """
@ -775,18 +828,22 @@ def novirt_install(opts, disk_img, disk_size):
def virt_install(opts, install_log, disk_img, disk_size): def virt_install(opts, install_log, disk_img, disk_size):
""" """
Use virt-install to install to a disk image Use qemu to install to a disk image
:param opts: options passed to livemedia-creator :param opts: options passed to livemedia-creator
:type opts: argparse options :type opts: argparse options
:param str install_log: The path to write the log from virt-install :param str install_log: The path to write the log from qemu
:param str disk_img: The full path to the disk image to be created :param str disk_img: The full path to the disk image to be created
:param int disk_size: The size of the disk_img in MiB :param int disk_size: The size of the disk_img in MiB
This uses virt-install with a boot.iso and a kickstart to create a disk This uses qemu with a boot.iso and a kickstart to create a disk
image and then optionally, based on the opts passed, creates tarfile. image and then optionally, based on the opts passed, creates tarfile.
""" """
iso_mount = IsoMountpoint(opts.iso, opts.location) iso_mount = IsoMountpoint(opts.iso, opts.location)
if not iso_mount.stage2:
iso_mount.umount()
raise InstallError("ISO is missing stage2, cannot continue")
log_monitor = LogMonitor(install_log, timeout=opts.timeout) log_monitor = LogMonitor(install_log, timeout=opts.timeout)
kernel_args = "" kernel_args = ""
@ -796,7 +853,6 @@ def virt_install(opts, install_log, disk_img, disk_size):
kernel_args += " proxy="+opts.proxy kernel_args += " proxy="+opts.proxy
if opts.image_type and not opts.make_fsimage: if opts.image_type and not opts.make_fsimage:
# virt-install can't take all the qcow2 options so create the image first
qemu_args = [] qemu_args = []
for arg in opts.qemu_args: for arg in opts.qemu_args:
qemu_args += arg.split(" ", 1) qemu_args += arg.split(" ", 1)
@ -811,15 +867,13 @@ def virt_install(opts, install_log, disk_img, disk_size):
diskimg_path = disk_img diskimg_path = disk_img
try: try:
virt = VirtualInstall(iso_mount, opts.ks, diskimg_path, disk_size, QEMUInstall(opts, iso_mount, opts.ks, diskimg_path, disk_size,
kernel_args, opts.ram, opts.vcpus, opts.vnc, opts.arch, kernel_args, opts.ram, opts.vcpus, opts.vnc, opts.arch,
log_check = log_monitor.server.log_check, log_check = log_monitor.server.log_check,
virtio_host = log_monitor.host, virtio_host = log_monitor.host,
virtio_port = log_monitor.port, virtio_port = log_monitor.port,
image_type=opts.image_type, boot_uefi=opts.virt_uefi, image_type=opts.image_type, boot_uefi=opts.virt_uefi,
ovmf_path=opts.ovmf_path) ovmf_path=opts.ovmf_path)
virt.destroy()
log_monitor.shutdown() log_monitor.shutdown()
except InstallError as e: except InstallError as e:
log.error("VirtualInstall failed: %s", e) log.error("VirtualInstall failed: %s", e)
@ -931,7 +985,7 @@ def make_image(opts, ks):
:returns: Path of the image created :returns: Path of the image created
:rtype: str :rtype: str
Use virt-install or anaconda to install to a disk image. Use qemu+boot.iso or anaconda to install to a disk image.
""" """
# Disk size for a filesystem image should only be the size of / # Disk size for a filesystem image should only be the size of /
# to prevent surprises when using the same kickstart for different installations. # to prevent surprises when using the same kickstart for different installations.
@ -1049,7 +1103,7 @@ def main():
help="Build a Vagrant Box image") help="Build a Vagrant Box image")
parser.add_argument("--iso", type=os.path.abspath, parser.add_argument("--iso", type=os.path.abspath,
help="Anaconda installation .iso path to use for virt-install") help="Anaconda installation .iso path to use for qemu")
parser.add_argument("--iso-only", action="store_true", parser.add_argument("--iso-only", action="store_true",
help="Remove all iso creation artifacts except the boot.iso, " help="Remove all iso creation artifacts except the boot.iso, "
"combine with --iso-name to rename the boot.iso") "combine with --iso-name to rename the boot.iso")
@ -1061,7 +1115,7 @@ def main():
help="Exit after creating fs/disk image.") help="Exit after creating fs/disk image.")
parser.add_argument("--no-virt", action="store_true", parser.add_argument("--no-virt", action="store_true",
help="Use Anaconda's image install instead of virt-install") help="Run anaconda directly on host instead of using qemu")
parser.add_argument("--proxy", parser.add_argument("--proxy",
help="proxy URL to use for the install") help="proxy URL to use for the install")
parser.add_argument("--anaconda-arg", action="append", dest="anaconda_args", parser.add_argument("--anaconda-arg", action="append", dest="anaconda_args",
@ -1072,8 +1126,8 @@ def main():
"i.e., highbank, mvebu, omap, tegra, etc.") "i.e., highbank, mvebu, omap, tegra, etc.")
parser.add_argument("--location", default=None, type=os.path.abspath, parser.add_argument("--location", default=None, type=os.path.abspath,
help="location of iso directory tree with initrd.img " help="location of iso directory tree with initrd.img "
"and vmlinuz. Used to run virt-install with a " "and vmlinuz. Used to run qemu with a newer initrd "
"newer initrd than the iso.") "than the iso.")
parser.add_argument("--logfile", default="./livemedia.log", parser.add_argument("--logfile", default="./livemedia.log",
type=os.path.abspath, type=os.path.abspath,
@ -1126,25 +1180,26 @@ def main():
app_group.add_argument("--app-file", default="appliance.xml", app_group.add_argument("--app-file", default="appliance.xml",
help="Appliance template results file.") help="Appliance template results file.")
# Group of arguments to pass to virt-install # Group of arguments to pass to qemu
if not libvirt: virt_group = parser.add_argument_group("qemu arguments")
virt_group = parser.add_argument_group("virt-install arguments (DISABLED -- no libvirt)")
else:
virt_group = parser.add_argument_group("virt-install arguments")
virt_group.add_argument("--ram", metavar="MEMORY", type=int, default=1024, virt_group.add_argument("--ram", metavar="MEMORY", type=int, default=1024,
help="Memory to allocate for installer in megabytes.") help="Memory to allocate for installer in megabytes.")
virt_group.add_argument("--vcpus", type=int, default=None, virt_group.add_argument("--vcpus", type=int, default=None,
help="Passed to --vcpus command") help="Passed to qemu -smp command")
virt_group.add_argument("--vnc", virt_group.add_argument("--vnc",
help="Passed to --graphics command") help="Passed to qemu -display command. eg. vnc=127.0.0.1:5, default is to "
"choose the first unused vnc port.")
virt_group.add_argument("--arch", default=None, virt_group.add_argument("--arch", default=None,
help="Passed to --arch command") help="System arch to build for. Used to select qemu-system-* command. "
"Defaults to qemu-system-<arch>")
virt_group.add_argument("--kernel-args", virt_group.add_argument("--kernel-args",
help="Additional argument to pass to the installation kernel") help="Additional argument to pass to the installation kernel")
virt_group.add_argument("--ovmf-path", default="/usr/share/OVMF/", virt_group.add_argument("--ovmf-path", default="/usr/share/OVMF/",
help="Path to OVMF firmware") help="Path to OVMF firmware")
virt_group.add_argument("--virt-uefi", action="store_true", default=False, virt_group.add_argument("--virt-uefi", action="store_true", default=False,
help="Use OVMF firmware to boot the VM in UEFI mode") help="Use OVMF firmware to boot the VM in UEFI mode")
virt_group.add_argument("--no-kvm", action="store_true", default=False,
help="Skip using kvm with qemu even if it is available.")
# dracut arguments # dracut arguments
dracut_group = parser.add_argument_group("dracut arguments") dracut_group = parser.add_argument_group("dracut arguments")
@ -1232,12 +1287,9 @@ def main():
if opts.volid and len(opts.volid) > 32: if opts.volid and len(opts.volid) > 32:
errors.append("the volume id cannot be longer than 32 characters") errors.append("the volume id cannot be longer than 32 characters")
if is_install and not opts.no_virt and not libvirt:
errors.append("virt install requires libvirt-python3 to be installed.")
if is_install and not opts.no_virt \ if is_install and not opts.no_virt \
and not os.path.exists("/usr/bin/virt-install"): and not any(glob.glob("/usr/bin/qemu-system-*")):
errors.append("virt-install needs to be installed.") errors.append("qemu needs to be installed.")
if is_install and opts.no_virt \ if is_install and opts.no_virt \
and not os.path.exists("/usr/sbin/anaconda"): and not os.path.exists("/usr/sbin/anaconda"):