2013-03-18 01:06:52 +04:00
#!/usr/bin/python -tt
#
# Script to set up a Xen guest and kick off an install
#
# Copyright 2005-2006 Red Hat, Inc.
# Jeremy Katz <katzj@redhat.com>
# Option handling added by Andrew Puch <apuch@redhat.com>
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
import os
import sys
import time
import re
import logging
import optparse
2013-08-09 23:00:16 +04:00
import libvirt
2013-03-18 01:06:52 +04:00
import urlgrabber.progress as progress
import virtinst
import virtinst.cli as cli
from virtinst.cli import fail, print_stdout, print_stderr
##############################
# Validation utility helpers #
##############################
install_methods = "--location URL, --cdrom CD/ISO, --pxe, --import, --boot hd|cdrom|..."
install_missing = (_("An install method must be specified\n(%(methods)s)") %
2013-04-13 22:34:52 +04:00
{"methods" : install_methods})
2013-03-18 01:06:52 +04:00
disk_missing = _("--disk storage must be specified (override with --nodisks)")
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def install_specified(location, cdpath, pxe, import_install):
return bool(pxe or cdpath or location or import_install)
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def cdrom_specified(guest, diskopts=None):
2013-04-12 17:39:34 +04:00
disks = guest.get_devices("disk")
for disk in disks:
2013-03-18 01:06:52 +04:00
if disk.device == virtinst.VirtualDisk.DEVICE_CDROM:
return True
# Probably haven't set up disks yet
2013-04-12 17:39:34 +04:00
if not disks and diskopts:
2013-03-18 01:06:52 +04:00
for opts in diskopts:
if opts.count("device=cdrom"):
return True
return False
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def storage_specified(files, disks, nodisks, filesystems):
return bool(files or disks or nodisks or filesystems)
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def supports_pxe(guest):
"""
Return False if we are pretty sure the config doesn't support PXE
"""
for nic in guest.get_devices("interface"):
if nic.type == nic.TYPE_USER:
continue
if nic.type != nic.TYPE_VIRTUAL:
return True
try:
2013-09-24 18:00:01 +04:00
netobj = nic.conn.networkLookupByName(nic.source)
2013-09-23 01:04:22 +04:00
xmlobj = virtinst.Network(nic.conn, parsexml=netobj.XMLDesc(0))
if xmlobj.can_pxe():
2013-03-18 01:06:52 +04:00
return True
except:
logging.debug("Error checking if PXE supported", exc_info=True)
return True
return False
2013-04-21 20:28:14 +04:00
def check_cdrom_option_error(options):
if options.cdrom_short and options.cdrom:
fail("Cannot specify both -c and --cdrom")
if options.cdrom_short:
if "://" in options.cdrom_short:
fail("-c specified with what looks like a URI. Did you mean "
"to use --connect? If not, use --cdrom instead")
options.cdrom = options.cdrom_short
2013-04-04 20:04:07 +04:00
if not options.cdrom:
return
2013-04-21 20:28:14 +04:00
# Catch a strangely common error of users passing -vcpus=2 instead of
# --vcpus=2. The single dash happens to map to enough shortened options
# that things can fail weirdly if --paravirt is also specified.
2013-04-04 20:04:07 +04:00
for vcpu in [o for o in sys.argv if o.startswith("-vcpu")]:
if options.cdrom == vcpu[3:]:
fail("You specified -vcpus, you want --vcpus")
2013-03-18 01:06:52 +04:00
##############################
# Device validation wrappers #
##############################
def get_graphics(guest, options):
graphics = cli.digest_graphics(guest, options)
cli.get_graphics(guest, graphics)
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def get_chardevs(char_type, opts, guest, cb):
for optstr in cli.listify(opts):
try:
dev = cb(guest, optstr)
guest.add_device(dev)
except Exception, e:
fail(_("Error in %(chartype)s device parameters: %(err)s") %
{"chartype": char_type, "err": str(e)})
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def get_watchdog(watchdogs, guest):
for optstr in cli.listify(watchdogs):
try:
dev = cli.parse_watchdog(guest, optstr)
guest.add_device(dev)
except Exception, e:
fail(_("Error in watchdog device parameters: %s") % str(e))
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def get_filesystems(filesystems, guest):
for optstr in cli.listify(filesystems):
try:
dev = cli.parse_filesystem(guest, optstr)
guest.add_device(dev)
except Exception, e:
fail(_("Error in filesystem device parameters: %s") % str(e))
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def get_disk(diskopts, size, sparse, guest, is_file_path):
try:
dev = None
if is_file_path:
path = diskopts
else:
dev, size = cli.parse_disk(guest, diskopts)
path = dev.path
2013-07-13 18:09:00 +04:00
sparse = dev.get_sparse()
2013-03-18 01:06:52 +04:00
d = cli.disk_prompt(guest.conn, path, size, sparse,
origdev=dev)
except ValueError, e:
fail(_("Error with storage parameters: %s" % str(e)))
2013-04-12 17:39:34 +04:00
guest.add_device(d)
2013-03-18 01:06:52 +04:00
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def get_disks(guest, file_paths, disk_paths, size, sparse, need_storage):
is_file_path = (file_paths or (not disk_paths and cli.is_prompt()))
disks = (file_paths or disk_paths)
if not disks and need_storage and cli.is_prompt():
disks = [None]
def padlist(l, padsize):
l = cli.listify(l)
l.extend((padsize - len(l)) * [None])
return l
disklist = padlist(disks, 0)
sizelist = padlist(size, len(disklist))
for idx in range(len(disklist)):
get_disk(disklist[idx], sizelist[idx], sparse, guest, is_file_path)
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def get_networks(guest, options):
networks, macs = cli.digest_networks(guest, options)
cli.get_networks(guest, networks, macs)
########################
# Virt type validation #
########################
def prompt_virt(caps, arch, req_virt_type, req_accel):
supports_hvm = False
supports_pv = False
supports_accel = False
for guest in caps.guests:
if guest.os_type == "hvm":
supports_hvm = True
elif guest.os_type == "xen":
if (len(guest.domains) and
guest.domains[0].hypervisor_type == "kvm"):
# Don't prompt user for PV w/ xenner
continue
supports_pv = True
if not arch:
arch = caps.host.arch
if not req_virt_type:
if supports_hvm and supports_pv:
prompt_txt = _("Would you like a fully virtualized guest "
"(yes or no)? This will allow you to run "
"unmodified operating systems.")
if cli.prompt_for_yes_or_no(prompt_txt, ""):
req_virt_type = "hvm"
else:
req_virt_type = "xen"
elif supports_hvm:
req_virt_type = "hvm"
elif supports_pv:
req_virt_type = "xen"
# See if that domain supports acceleration
accel_type = ""
for guest in caps.guests:
if guest.os_type == req_virt_type and guest.arch == arch:
for dom in guest.domains:
if dom.is_accelerated():
supports_accel = True
accel_type = dom.hypervisor_type.upper()
if supports_accel and not req_accel:
prompt_txt = (_("Would you like to use %s acceleration? "
"(yes or no)") % accel_type)
req_accel = cli.prompt_for_yes_or_no(prompt_txt, "")
return (req_virt_type, req_accel)
2013-04-13 22:34:52 +04:00
2013-07-17 15:53:47 +04:00
def get_guest(conn, options):
2013-03-18 01:06:52 +04:00
# Set up all virt/hypervisor parameters
2013-04-12 00:32:00 +04:00
if sum([bool(f) for f in [options.fullvirt,
options.paravirt,
options.container]]) > 1:
2013-03-18 01:06:52 +04:00
fail(_("Can't do more than one of --hvm, --paravirt, or --container"))
req_accel = True
req_hv_type = options.hv_type and options.hv_type.lower() or None
if options.fullvirt:
req_virt_type = "hvm"
elif options.paravirt:
req_virt_type = "xen"
elif options.container:
req_virt_type = "exe"
else:
# This should force capabilities to give us the most sensible default
req_virt_type = None
if cli.is_prompt():
# User requested prompting but passed no virt type flag, ask for
# needed info
2013-07-06 22:12:13 +04:00
req_virt_type, req_accel = prompt_virt(conn.caps, options.arch,
2013-03-18 01:06:52 +04:00
req_virt_type, req_accel)
logging.debug("Requesting virt method '%s', hv type '%s'.",
(req_virt_type and req_virt_type or _("default")),
(req_hv_type and req_hv_type or _("default")))
arch = options.arch
if re.match("i.86", arch or ""):
arch = "i686"
try:
(capsguest,
2013-07-06 22:12:13 +04:00
capsdomain) = conn.caps.guest_lookup(
2013-03-18 01:06:52 +04:00
os_type=req_virt_type,
arch=arch,
2013-04-12 16:26:21 +04:00
typ=req_hv_type,
2013-03-18 01:06:52 +04:00
accelerated=req_accel,
machine=options.machine)
2013-07-17 15:53:47 +04:00
guest = conn.caps.build_virtinst_guest(conn, capsguest, capsdomain)
guest.os.machine = options.machine
2013-03-18 01:06:52 +04:00
except Exception, e:
fail(e)
if (not req_virt_type and
not req_hv_type and
req_accel and
2013-07-06 04:36:28 +04:00
conn.is_qemu() and
2013-03-18 01:06:52 +04:00
capsguest.arch in ["i686", "x86_64"] and
not capsdomain.is_accelerated()):
logging.warn("KVM acceleration not available, using '%s'",
capsdomain.hypervisor_type)
2013-07-17 15:53:47 +04:00
return guest
2013-03-18 01:06:52 +04:00
##################################
# Install media setup/validation #
##################################
def get_install_media(guest, location, cdpath, need_install):
manual_cdrom = cdrom_specified(guest)
cdinstall = bool(not location and (cdpath or cdrom_specified(guest)))
if not (location or cdpath or manual_cdrom or need_install):
return
try:
if not location and not cdinstall and cli.is_prompt():
media_prompt(guest)
else:
validate_install_media(guest, location, cdpath, cdinstall)
except ValueError, e:
fail(_("Error validating install location: %s" % str(e)))
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def media_prompt(guest):
2013-07-17 15:53:47 +04:00
if guest.os.is_hvm():
2013-03-18 01:06:52 +04:00
prompt_txt = _("What is the install CD-ROM/ISO or URL?")
else:
prompt_txt = _("What is the install URL?")
while 1:
location = None
cdpath = None
media = cli.prompt_for_input("", prompt_txt, None)
if not len(media):
continue
2013-07-17 15:53:47 +04:00
if not guest.os.is_hvm() or media.count(":/"):
2013-03-18 01:06:52 +04:00
location = media
else:
cdpath = media
try:
validate_install_media(guest, location, cdpath)
except Exception, e:
logging.error(str(e))
continue
break
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def validate_install_media(guest, location, cdpath, cdinstall=False):
if cdinstall or cdpath:
guest.installer.cdrom = True
if location or cdpath:
guest.installer.location = (location or cdpath)
2013-07-17 15:53:47 +04:00
guest.installer.check_location(guest.os.arch)
2013-03-18 01:06:52 +04:00
#############################
# General option validation #
#############################
def validate_required_options(options, guest):
# Required config. Don't error right away if nothing is specified,
# aggregate the errors to help first time users get it right
msg = ""
need_storage = False
need_install = False
if not options.name:
msg += "\n" + cli.name_missing
if not options.memory:
msg += "\n" + cli.ram_missing
2013-07-17 15:53:47 +04:00
if (not guest.os.is_container() and
2013-03-18 01:06:52 +04:00
not storage_specified(options.file_paths, options.diskopts,
options.nodisks, options.filesystems)):
msg += "\n" + disk_missing
need_storage = True
2013-07-17 15:53:47 +04:00
if (not guest.os.is_container() and
2013-03-18 01:06:52 +04:00
(not install_specified(options.location, options.cdrom,
options.pxe, options.import_install)) and
(not cdrom_specified(guest, options.diskopts))):
msg += "\n" + install_missing
need_install = True
if msg and not cli.is_prompt():
fail(msg)
return need_storage, need_install
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def check_option_collisions(options, guest):
# Disk collisions
if options.nodisks and (options.file_paths or
options.diskopts or
options.disksize):
fail(_("Cannot specify storage and use --nodisks"))
2013-04-13 22:34:52 +04:00
if ((options.file_paths or options.disksize or not options.sparse) and
2013-03-18 01:06:52 +04:00
options.diskopts):
fail(_("Cannot mix --file, --nonsparse, or --file-size with --disk "
"options. Use --disk PATH[,size=SIZE][,sparse=yes|no]"))
# Network collisions
if options.nonetworks:
if options.mac:
fail(_("Cannot use --mac with --nonetworks"))
if options.bridge:
fail(_("Cannot use --bridge with --nonetworks"))
if options.network:
fail(_("Cannot use --network with --nonetworks"))
return
# Install collisions
2013-04-12 00:32:00 +04:00
if sum([bool(l) for l in [options.pxe, options.location,
options.cdrom, options.import_install]]) > 1:
2013-03-18 01:06:52 +04:00
fail(_("Only one install method can be used (%(methods)s)") %
{"methods" : install_methods})
2013-07-17 15:53:47 +04:00
if (guest.os.is_container() and
2013-03-18 01:06:52 +04:00
install_specified(options.location, options.cdrom,
options.pxe, options.import_install)):
fail(_("Install methods (%s) cannot be specified for "
"container guests") % install_methods)
2013-07-17 15:53:47 +04:00
if guest.os.is_xenpv():
2013-03-18 01:06:52 +04:00
if options.pxe:
fail(_("Network PXE boot is not supported for paravirtualized "
"guests"))
if options.cdrom or options.livecd:
fail(_("Paravirtualized guests cannot install off cdrom media."))
if (options.location and
2013-07-06 04:36:28 +04:00
guest.conn.is_remote() and not
2013-07-17 15:53:47 +04:00
virtinst.support.support_remote_url_install(guest.conn)):
2013-03-18 01:06:52 +04:00
fail(_("Libvirt version does not support remote --location installs"))
if not options.location and options.extra:
fail(_("--extra-args only work if specified with --location."))
if not options.location and options.initrd_injections:
fail(_("--initrd-inject only works if specified with --location."))
##########################
# Guest building helpers #
##########################
2013-07-17 15:53:47 +04:00
def build_installer(options, conn, virt_type):
2013-03-18 01:06:52 +04:00
# Build the Installer instance
if options.livecd:
instclass = virtinst.LiveCDInstaller
elif options.pxe:
if options.nonetworks:
fail(_("Can't use --pxe with --nonetworks"))
instclass = virtinst.PXEInstaller
elif options.cdrom or options.location:
instclass = virtinst.DistroInstaller
elif options.import_install or options.bootopts:
if options.import_install and options.nodisks:
fail(_("A disk device must be specified with --import."))
options.import_install = True
instclass = virtinst.ImportInstaller
elif virt_type == "exe":
instclass = virtinst.ContainerInstaller
else:
instclass = virtinst.DistroInstaller
# Only set installer params here that impact the hw config, not
# anything to do with install media
2013-07-17 00:47:08 +04:00
installer = instclass(conn)
2013-03-18 01:06:52 +04:00
return installer
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def build_guest_instance(conn, options):
2013-07-17 15:53:47 +04:00
guest = get_guest(conn, options)
2013-03-18 01:06:52 +04:00
2013-07-17 15:53:47 +04:00
logging.debug("Received virt method '%s'", guest.type)
logging.debug("Hypervisor name is '%s'", guest.os.os_type)
2013-03-18 01:06:52 +04:00
2013-07-17 15:53:47 +04:00
guest.installer = build_installer(options, conn, guest.os.os_type)
2013-03-18 01:06:52 +04:00
# Guest configuration
cli.get_uuid(options.uuid, guest)
cli.get_vcpus(guest, options.vcpus, options.check_cpu)
cli.parse_numatune(guest, options.numatune)
cli.parse_cpu(guest, options.cpu)
cli.parse_security(guest, options.security)
cli.parse_boot(guest, options.bootopts)
guest.autostart = options.autostart
guest.description = options.description
guest.features["acpi"] = not options.noacpi
guest.features["apic"] = not options.noapic
# Non-default devices
cli.get_controller(guest, options.controller)
cli.get_redirdev(guest, options.redirdev)
cli.get_memballoon(guest, options.memballoon)
if not options.nonetworks:
get_networks(guest, options)
get_graphics(guest, options)
cli.get_video(guest, options.video)
get_watchdog(options.watchdog, guest)
get_filesystems(options.filesystems, guest)
cli.get_sound(options.sound, options.soundhw, guest)
2013-07-16 17:14:37 +04:00
get_chardevs("serial", options.serials, guest, cli.parse_serial)
get_chardevs("parallel", options.parallels, guest, cli.parse_parallel)
get_chardevs("channel", options.channels, guest, cli.parse_channel)
get_chardevs("console", options.consoles, guest, cli.parse_console)
2013-03-18 01:06:52 +04:00
cli.get_hostdevs(options.hostdevs, guest)
cli.get_smartcard(guest, options.smartcard)
2013-06-26 05:45:07 +04:00
cli.get_tpm(guest, options.tpm)
2013-09-18 17:29:29 +04:00
cli.get_rng(guest, options.rng)
2013-03-18 01:06:52 +04:00
2013-07-16 23:59:27 +04:00
if not guest.get_devices("input"):
guest.add_default_input_device()
if not guest.get_devices("console") and not guest.get_devices("serial"):
guest.add_default_console_device()
2013-03-18 01:06:52 +04:00
# Install options
cli.set_os_variant(guest, options.distro_type, options.distro_variant)
2013-07-17 15:53:47 +04:00
guest.installer.extraargs = options.extra
2013-03-18 01:06:52 +04:00
guest.installer.initrd_injections = options.initrd_injections
2013-07-17 15:53:47 +04:00
guest.os.init = options.init
2013-03-18 01:06:52 +04:00
# Do this after setting up all optional parameters, so we report error
# about those first.
need_storage, need_install = validate_required_options(options, guest)
# Actually set required options
cli.get_name(options.name, guest)
cli.get_memory(options.memory, guest)
if not options.nodisks:
get_disks(guest, options.file_paths, options.diskopts,
options.disksize, options.sparse, need_storage)
get_install_media(guest, options.location, options.cdrom, need_install)
# Various little validations about option collisions. Need to do
# this after setting guest.installer at least
check_option_collisions(options, guest)
# Needs to come after setting memory
cli.get_cpuset(guest, options.cpuset, guest.memory)
# Warnings
if options.pxe and not supports_pxe(guest):
logging.warn(_("The guest's network configuration does not support "
"PXE"))
return guest
###########################
# Install process helpers #
###########################
def _run_console(args):
logging.debug("Running: %s", " ".join(args))
child = os.fork()
if child:
return child
os.execvp(args[0], args)
2013-04-13 22:34:52 +04:00
os._exit(1) # pylint: disable=W0212
2013-03-18 01:06:52 +04:00
def vnc_console(dom, uri):
args = ["/usr/bin/virt-viewer",
"--connect", uri,
"--wait", str(dom.ID())]
if not os.path.exists(args[0]):
logging.warn(_("Unable to connect to graphical console: "
"virt-viewer not installed. Please install "
"the 'virt-viewer' package."))
return None
return _run_console(args)
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def txt_console(dom, uri):
args = ["/usr/bin/virsh",
"--connect", uri,
"console", str(dom.ID())]
return _run_console(args)
2013-04-13 22:34:52 +04:00
2013-08-09 23:00:16 +04:00
def domain_is_crashed(domain):
"""
Return True if the created domain object is in a crashed state
"""
if not domain:
return False
dominfo = domain.info()
state = dominfo[0]
return state == libvirt.VIR_DOMAIN_CRASHED
def domain_is_shutdown(domain):
"""
Return True if the created domain object is shutdown
"""
if not domain:
return False
dominfo = domain.info()
state = dominfo[0]
cpu_time = dominfo[4]
if state == libvirt.VIR_DOMAIN_SHUTOFF:
return True
# If 'wait' was specified, the dom object we have was looked up
# before initially shutting down, which seems to bogus up the
# info data (all 0's). So, if it is bogus, assume the domain is
# shutdown. We will catch the error later.
return state == libvirt.VIR_DOMAIN_NOSTATE and cpu_time == 0
def connect_console(domain, consolecb, wait):
"""
Launched the passed console callback for the already defined
domain. If domain isn't running, return an error.
"""
child = None
if consolecb:
child = consolecb(domain)
if not child or not wait:
return
# If we connected the console, wait for it to finish
try:
os.waitpid(child, 0)
2013-09-19 21:12:05 +04:00
except OSError, e:
logging.debug("waitpid: %s: %s", e.errno, e.message)
2013-08-09 23:00:16 +04:00
2013-03-18 01:06:52 +04:00
def start_install(guest, continue_inst, options):
def show_console(dom):
2013-04-12 17:02:12 +04:00
gdev = guest.get_devices("graphics")
if not gdev:
2013-03-18 01:06:52 +04:00
logging.debug("Connecting to text console")
2013-07-14 00:29:43 +04:00
return txt_console(dom, guest.conn.getURI())
2013-03-18 01:06:52 +04:00
2013-04-12 17:02:12 +04:00
gtype = gdev[0].type
if gtype in [virtinst.VirtualGraphics.TYPE_VNC,
virtinst.VirtualGraphics.TYPE_SPICE]:
logging.debug("Launching virt-viewer for graphics type '%s'",
gtype)
2013-07-14 00:29:43 +04:00
return vnc_console(dom, guest.conn.getURI())
2013-04-12 17:02:12 +04:00
else:
logging.debug("No viewer to launch for graphics type '%s'",
gtype)
2013-08-09 23:00:16 +04:00
return None
2013-04-12 17:02:12 +04:00
2013-03-18 01:06:52 +04:00
# There are two main cases we care about:
#
# Scripts: these should specify --wait always, maintaining the
# semantics of virt-install exit implying the domain has finished
# installing.
#
# Interactive: If this is a continue_inst domain, we default to
# waiting. Otherwise, we can exit before the domain has finished
# installing. Passing --wait will give the above semantics.
#
wait_on_install = continue_inst
wait_time = -1
2013-04-13 22:34:52 +04:00
if options.wait is not None:
2013-03-18 01:06:52 +04:00
wait_on_install = True
wait_time = options.wait * 60
# If --wait specified, we don't want the default behavior of waiting
# for virt-viewer to exit, since then we can't exit the app when time
# expires
wait_on_console = not wait_on_install
# --wait 0 implies --noautoconsole
options.autoconsole = (wait_time != 0) and options.autoconsole or False
conscb = options.autoconsole and show_console or None
meter = (options.quiet and
progress.BaseMeter() or
progress.TextMeter(fo=sys.stdout))
logging.debug("Guest.has_install_phase: %s",
guest.installer.has_install_phase())
# we've got everything -- try to start the install
print_stdout(_("\nStarting install..."))
try:
start_time = time.time()
# Do first install phase
2013-08-09 19:25:05 +04:00
dom = guest.start_install(meter=meter, noboot=options.noreboot)
2013-08-09 23:00:16 +04:00
connect_console(dom, conscb, wait_on_console)
2013-03-18 01:06:52 +04:00
dom = check_domain(guest, dom, conscb,
wait_on_install, wait_time, start_time)
if continue_inst:
2013-08-09 19:25:05 +04:00
dom = guest.continue_install(meter=meter)
2013-08-09 23:00:16 +04:00
connect_console(dom, conscb, wait_on_console)
2013-03-18 01:06:52 +04:00
dom = check_domain(guest, dom, conscb,
wait_on_install, wait_time, start_time)
if options.noreboot or not guest.installer.has_install_phase():
print_stdout(
_("Domain creation completed. You can restart your domain by "
"running:\n %s") % cli.virsh_start_cmd(guest))
else:
print_stdout(
_("Guest installation complete... restarting guest."))
dom.create()
2013-08-09 23:00:16 +04:00
connect_console(dom, conscb, wait=True)
2013-03-18 01:06:52 +04:00
except KeyboardInterrupt:
logging.debug("", exc_info=True)
print_stderr(_("Domain install interrupted."))
raise
except RuntimeError, e:
fail(e)
except Exception, e:
fail(e, do_exit=False)
cli.install_fail(guest)
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def check_domain(guest, dom, conscb, wait_for_install, wait_time, start_time):
"""
Make sure domain ends up in expected state, and wait if for install
to complete if requested
"""
wait_forever = (wait_time < 0)
# Wait a bit so info is accurate
def check_domain_state():
dominfo = dom.info()
state = dominfo[0]
2013-08-09 23:00:16 +04:00
if domain_is_crashed(guest.domain):
2013-03-18 01:06:52 +04:00
fail(_("Domain has crashed."))
2013-08-09 23:00:16 +04:00
if domain_is_shutdown(guest.domain):
2013-03-18 01:06:52 +04:00
return dom, state
return None, state
do_sleep = bool(conscb)
try:
ret, state = check_domain_state()
if ret:
return ret
except Exception, e:
# Sometimes we see errors from libvirt here due to races
logging.exception(e)
do_sleep = True
if do_sleep:
# Sleep a bit and try again to be sure the HV has caught up
time.sleep(2)
ret, state = check_domain_state()
if ret:
return ret
# Domain seems to be running
logging.debug("Domain state after install: %s", state)
if not wait_for_install or wait_time == 0:
# User either:
# used --noautoconsole
# used --wait 0
# killed console and guest is still running
if not guest.installer.has_install_phase():
return dom
print_stdout(
_("Domain installation still in progress. You can reconnect"
" to \nthe console to complete the installation process."))
sys.exit(0)
timestr = (not wait_forever and
_("%d minutes ") % (int(wait_time) / 60) or "")
print_stdout(
2013-09-20 20:10:34 +04:00
_("Domain installation still in progress. Waiting "
"%(time_string)s for installation to complete.") %
{"time_string": timestr})
2013-03-18 01:06:52 +04:00
# Wait loop
while True:
2013-08-09 23:00:16 +04:00
if domain_is_shutdown(guest.domain):
2013-03-18 01:06:52 +04:00
print_stdout(_("Domain has shutdown. Continuing."))
try:
# Lookup a new domain object incase current
# one returned bogus data (see comment in
2013-08-09 23:00:16 +04:00
# domain_is_shutdown)
2013-03-18 01:06:52 +04:00
dom = guest.conn.lookupByName(guest.name)
except Exception, e:
raise RuntimeError(_("Could not lookup domain after "
"install: %s" % str(e)))
break
time_elapsed = (time.time() - start_time)
if not wait_forever and time_elapsed >= wait_time:
print_stdout(
_("Installation has exceeded specified time limit. "
"Exiting application."))
sys.exit(1)
time.sleep(2)
return dom
########################
# XML printing helpers #
########################
def xml_to_print(guest, continue_inst, xmlonly, xmlstep, dry):
start_xml, final_xml = guest.start_install(dry=dry, return_xml=True)
second_xml = None
if not start_xml:
start_xml = final_xml
final_xml = None
if continue_inst:
second_xml, final_xml = guest.continue_install(dry=dry,
return_xml=True)
if dry and not (xmlonly or xmlstep):
print_stdout(_("Dry run completed successfully"))
return
# --print-xml
if xmlonly and not xmlstep:
if second_xml or final_xml:
fail(_("--print-xml can only be used with guests that do not have "
"an installation phase (--import, --boot, etc.). To see all"
" generated XML, please use --print-step all."))
return start_xml
# --print-step
if xmlstep == "1":
return start_xml
if xmlstep == "2":
if not (second_xml or final_xml):
fail(_("Requested installation does not have XML step 2"))
return second_xml or final_xml
if xmlstep == "3":
if not second_xml:
fail(_("Requested installation does not have XML step 3"))
return final_xml
# "all" case
xml = start_xml
if second_xml:
xml += second_xml
if final_xml:
xml += final_xml
return xml
#######################
# CLI option handling #
#######################
def parse_args():
2013-06-30 23:03:53 +04:00
parser = cli.setupParser(
"%prog --name NAME --ram RAM STORAGE INSTALL [options]",
_("Create a new virtual machine from specified install media."))
2013-03-18 01:06:52 +04:00
cli.add_connect_option(parser)
geng = optparse.OptionGroup(parser, _("General Options"))
geng.add_option("-n", "--name", dest="name",
help=_("Name of the guest instance"))
geng.add_option("-r", "--ram", type="int", dest="memory",
help=_("Memory to allocate for guest instance in "
"megabytes"))
cli.vcpu_cli_options(geng)
geng.add_option("", "--description", dest="description",
help=_("Human readable description of the VM to store in "
"the generated XML."))
geng.add_option("", "--security", dest="security",
help=_("Set domain security driver configuration."))
geng.add_option("", "--numatune", dest="numatune",
help=_("Tune NUMA policy for the domain process."))
parser.add_option_group(geng)
insg = optparse.OptionGroup(parser, _("Installation Method Options"))
2013-04-21 20:28:14 +04:00
insg.add_option("-c", dest="cdrom_short", help=optparse.SUPPRESS_HELP)
insg.add_option("", "--cdrom", dest="cdrom",
2013-03-18 01:06:52 +04:00
help=_("CD-ROM installation media"))
insg.add_option("-l", "--location", dest="location",
help=_("Installation source (eg, nfs:host:/path, "
"http://host/path, ftp://host/path)"))
insg.add_option("", "--pxe", action="store_true", dest="pxe",
help=_("Boot from the network using the PXE protocol"))
insg.add_option("", "--import", action="store_true", dest="import_install",
help=_("Build guest around an existing disk image"))
insg.add_option("", "--init", dest="init",
help=_("Path to init binary for container guest. Ex:\n"
"--init /path/to/app (to contain an application)\n"
"--init /sbin/init (for a full OS container)"))
insg.add_option("", "--livecd", action="store_true", dest="livecd",
help=_("Treat the CD-ROM media as a Live CD"))
insg.add_option("-x", "--extra-args", dest="extra",
default="",
help=_("Additional arguments to pass to the install kernel "
"booted from --location"))
insg.add_option("", "--initrd-inject", dest="initrd_injections",
action="append",
help=_("Add given file to root of initrd from --location"))
2013-08-11 02:17:37 +04:00
cli.add_distro_options(insg)
2013-03-18 01:06:52 +04:00
insg.add_option("", "--boot", dest="bootopts", default="",
help=_("Optionally configure post-install boot order, "
"menu, permanent kernel boot, etc."))
parser.add_option_group(insg)
stog = optparse.OptionGroup(parser, _("Storage Configuration"))
stog.add_option("", "--disk", dest="diskopts", action="append",
help=_("Specify storage with various options. Ex.\n"
"--disk path=/my/existing/disk\n"
"--disk path=/my/new/disk,size=5 (in gigabytes)\n"
"--disk vol=poolname/volname,device=cdrom,bus=scsi,..."))
stog.add_option("", "--nodisks", action="store_true",
help=_("Don't set up any disks for the guest."))
cli.add_fs_option(stog)
# Deprecated storage options
stog.add_option("-f", "--file", dest="file_paths", action="append",
help=optparse.SUPPRESS_HELP)
stog.add_option("-s", "--file-size", type="float",
action="append", dest="disksize",
help=optparse.SUPPRESS_HELP)
stog.add_option("", "--nonsparse", action="store_false",
default=True, dest="sparse",
help=optparse.SUPPRESS_HELP)
parser.add_option_group(stog)
netg = cli.network_option_group(parser)
netg.add_option("", "--nonetworks", action="store_true",
help=_("Don't create network interfaces for the guest."))
parser.add_option_group(netg)
vncg = cli.graphics_option_group(parser)
vncg.add_option("", "--noautoconsole", action="store_false",
dest="autoconsole", default=True,
help=_("Don't automatically try to connect to the guest "
"console"))
parser.add_option_group(vncg)
devg = optparse.OptionGroup(parser, _("Device Options"))
cli.add_device_options(devg)
# Deprecated
devg.add_option("", "--sound", action="store_true", dest="sound",
default=False,
help=optparse.SUPPRESS_HELP)
parser.add_option_group(devg)
virg = optparse.OptionGroup(parser, _("Virtualization Platform Options"))
virg.add_option("-v", "--hvm", action="store_true", dest="fullvirt",
help=_("This guest should be a fully virtualized guest"))
virg.add_option("-p", "--paravirt", action="store_true", dest="paravirt",
help=_("This guest should be a paravirtualized guest"))
virg.add_option("", "--container", action="store_true", default=False,
dest="container",
help=_("This guest should be a container guest"))
virg.add_option("", "--virt-type", dest="hv_type",
default="",
help=_("Hypervisor name to use (kvm, qemu, xen, ...)"))
virg.add_option("", "--accelerate", action="store_true", default=False,
dest="accelerate", help=optparse.SUPPRESS_HELP)
virg.add_option("", "--arch", dest="arch",
help=_("The CPU architecture to simulate"))
virg.add_option("", "--machine", dest="machine",
help=_("The machine type to emulate"))
virg.add_option("", "--noapic", action="store_true", dest="noapic",
default=False,
help=_("Disables APIC for fully virtualized guest "
2013-08-11 02:17:37 +04:00
"(overrides value in os-variant db)"))
2013-03-18 01:06:52 +04:00
virg.add_option("", "--noacpi", action="store_true", dest="noacpi",
default=False,
help=_("Disables ACPI for fully virtualized guest "
2013-08-11 02:17:37 +04:00
"(overrides value in os-variant db)"))
2013-03-18 01:06:52 +04:00
virg.add_option("-u", "--uuid", dest="uuid",
help=_("UUID for the guest."))
parser.add_option_group(virg)
misc = optparse.OptionGroup(parser, _("Miscellaneous Options"))
misc.add_option("", "--autostart", action="store_true", dest="autostart",
default=False,
help=_("Have domain autostart on host boot up."))
misc.add_option("", "--print-xml", action="store_true", dest="xmlonly",
help=_("Print the generated domain XML rather than define "
"the guest."))
misc.add_option("", "--print-step", type="str", dest="xmlstep",
help=_("Print XML of a specific install step "
"(1, 2, 3, all) rather than define the guest."))
misc.add_option("", "--noreboot", action="store_true", dest="noreboot",
help=_("Don't boot guest after completing install."))
misc.add_option("", "--wait", type="int", dest="wait",
help=_("Time to wait (in minutes)"))
misc.add_option("", "--dry-run", action="store_true", dest="dry",
help=_("Run through install process, but do not "
"create devices or define the guest."))
misc.add_option("", "--force", action="store_true", dest="force",
help=_("Forces 'yes' for any applicable prompts, "
"terminates for all others"))
misc.add_option("-q", "--quiet", action="store_true", dest="quiet",
help=_("Suppress non-error output"))
misc.add_option("", "--prompt", action="store_true", dest="prompt",
default=False,
help=_("Request user input for ambiguous situations or "
"required options."))
misc.add_option("-d", "--debug", action="store_true", dest="debug",
help=_("Print debugging information"))
parser.add_option_group(misc)
(options, cliargs) = parser.parse_args()
return options, cliargs
###################
# main() handling #
###################
def main(conn=None):
cli.earlyLogging()
options, cliargs = parse_args()
# Default setup options
options.quiet = options.xmlstep or options.xmlonly or options.quiet
cli.setupLogging("virt-install", options.debug, options.quiet)
if cliargs:
fail(_("Unknown argument '%s'") % cliargs[0])
2013-04-21 20:28:14 +04:00
check_cdrom_option_error(options)
2013-03-18 01:06:52 +04:00
if options.distro_variant == "list":
logging.debug("OS list requested")
2013-08-11 22:52:30 +04:00
for t in virtinst.osdict.list_os(list_types=True):
for v in virtinst.osdict.list_os(typename=t.name):
print "%-20s : %s" % (v.name, v.label)
2013-03-18 01:06:52 +04:00
return 0
cli.set_force(options.force)
cli.set_prompt(options.prompt)
if conn is None:
conn = cli.getConnection(options.connect)
if options.xmlstep not in [None, "1", "2", "3", "all"]:
fail(_("--print-step must be 1, 2, 3, or all"))
guest = build_guest_instance(conn, options)
continue_inst = guest.get_continue_inst()
if options.xmlstep or options.xmlonly or options.dry:
xml = xml_to_print(guest, continue_inst,
options.xmlonly, options.xmlstep, options.dry)
if xml:
print_stdout(xml, do_force=True)
else:
start_install(guest, continue_inst, options)
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except SystemExit, sys_e:
sys.exit(sys_e.code)
except KeyboardInterrupt:
logging.debug("", exc_info=True)
print_stderr(_("Installation aborted at user request"))
except Exception, main_e:
fail(main_e)