diff --git a/man/virt-clone.pod b/man/virt-clone.pod index 972edcf6a..dd04747f5 100644 --- a/man/virt-clone.pod +++ b/man/virt-clone.pod @@ -139,6 +139,10 @@ Show the help message and exit Show program's version number and exit +=item B<--check> + +Enable or disable some validation checks. See L for more details. + =item B<-q> =item B<--quiet> diff --git a/man/virt-install.pod b/man/virt-install.pod index cf730acfc..fdd5568a1 100644 --- a/man/virt-install.pod +++ b/man/virt-install.pod @@ -1476,6 +1476,10 @@ change host device configuration, or actually teach libvirt about the guest. virt-install may still fetch install media, since this is required to properly detect the OS to install. +=item B<--check> + +Enable or disable some validation checks. Some examples are warning about using a disk that's already assigned to another VM (--check path_in_use=on|off), or warning about potentially running out of space during disk allocation (--check disk_size=on|off). Most checks are performed by default. + =item B<-q> =item B<--quiet> diff --git a/tests/clitest.py b/tests/clitest.py index e6bcc48f9..0d9452f98 100644 --- a/tests/clitest.py +++ b/tests/clitest.py @@ -96,6 +96,8 @@ test_files = { 'AUTOMANAGEIMG' : "/some/new/pool/dir/new", 'EXISTIMG1' : "/dev/default-pool/testvol1.img", 'EXISTIMG2' : "/dev/default-pool/testvol2.img", + 'EXISTIMG3' : exist_images[0], + 'EXISTIMG4' : exist_images[1], 'EXISTUPPER' : "/dev/default-pool/UPPER", 'POOL' : "default-pool", 'VOL' : "testvol1.img", @@ -638,6 +640,7 @@ c.add_valid("--disk path=%(MANAGEDNEW1)s,format=raw,size=.0000001") # Managed f c.add_valid("--disk path=%(MANAGEDNEW1)s,format=qcow2,size=.0000001") # Managed file using format qcow2 c.add_valid("--disk %(EXISTIMG1)s") # Not specifying path= c.add_valid("--disk %(NEWIMG1)s,format=raw,size=.0000001") # Not specifying path= but creating storage +c.add_valid("--disk %(COLLIDE)s --check path_in_use=off") # Colliding storage with --check c.add_valid("--disk %(COLLIDE)s --force") # Colliding storage with --force c.add_valid("--disk %(SHARE)s,perms=sh") # Colliding shareable storage c.add_valid("--disk path=%(EXISTIMG1)s,device=cdrom --disk path=%(EXISTIMG1)s,device=cdrom") # Two IDE cds @@ -645,7 +648,7 @@ c.add_valid("--disk %(EXISTIMG1)s,driver_name=qemu,driver_type=qcow2") # Driver c.add_valid("--disk /dev/zero") # Referencing a local unmanaged /dev node c.add_valid("--disk pool=default,size=.00001") # Building 'default' pool c.add_valid("--disk %(AUTOMANAGEIMG)s,size=.1") # autocreate the pool -c.add_invalid("--disk %(NEWIMG1)s,sparse=true,size=100000000000 --force") # Don't warn about fully allocated file exceeding disk space +c.add_valid("--disk %(NEWIMG1)s,sparse=true,size=100000000 --check disk_size=off") # Don't warn about fully allocated file exceeding disk space c.add_invalid("--file %(NEWIMG1)s --file-size 100000 --nonsparse") # Nonexisting file, size too big c.add_invalid("--file %(NEWIMG1)s --file-size 100000") # Huge file, sparse, but no prompting c.add_invalid("--file %(NEWIMG1)s") # Nonexisting file, no size @@ -828,7 +831,7 @@ c.add_invalid("--mac 22:22:33:12:34:AB") # Colliding macaddr c = vinst.add_category("storage-back-compat", "--pxe --noautoconsole") c.add_valid("--file %(EXISTIMG1)s --nonsparse --file-size 4") # Existing file, other opts c.add_valid("--file %(EXISTIMG1)s") # Existing file, no opts -c.add_valid("--file %(EXISTIMG1)s --file virt-clone --file virt-clone") # Multiple existing files +c.add_valid("--file %(EXISTIMG1)s --file %(EXISTIMG1)s") # Multiple existing files c.add_valid("--file %(NEWIMG1)s --file-size .00001 --nonsparse") # Nonexistent file @@ -936,20 +939,22 @@ c.add_compare("--remove-device --host-device 0x04b3:0x4485", "remove-hostdev-nam vclon = App("virt-clone") c = vclon.add_category("remote", "--connect %(REMOTEURI)s") c.add_valid("-o test --auto-clone") # Auto flag, no storage -c.add_valid("--original-xml %(CLONE_STORAGE_XML)s --auto-clone") # Auto flag w/ managed storage, -c.add_invalid("--original-xml %(CLONE_DISK_XML)s --auto-clone") # Auto flag w/ storage, +c.add_valid("--original-xml %(CLONE_STORAGE_XML)s --auto-clone") # Auto flag w/ managed storage +c.add_invalid("--original-xml %(CLONE_DISK_XML)s --auto-clone") # Auto flag w/ local storage, which is invalid for remote connection c = vclon.add_category("misc", "") c.add_compare("--connect %(KVMURI)s -o test-for-clone --auto-clone --clone-running", "clone-auto1", compare_check=support.SUPPORT_CONN_LOADER_ROM) c.add_compare("-o test-clone-simple --name newvm --auto-clone --clone-running", "clone-auto2", compare_check=support.SUPPORT_CONN_LOADER_ROM) c.add_valid("-o test --auto-clone") # Auto flag, no storage -c.add_valid("--original-xml %(CLONE_DISK_XML)s --auto-clone") # Auto flag w/ storage, -c.add_valid("--original-xml %(CLONE_STORAGE_XML)s --auto-clone") # Auto flag w/ managed storage, +c.add_valid("--original-xml %(CLONE_STORAGE_XML)s --auto-clone") # Auto flag w/ managed storage +c.add_valid("--original-xml %(CLONE_DISK_XML)s --auto-clone") # Auto flag w/ local storage c.add_valid("-o test-for-clone --auto-clone --clone-running") # Auto flag, actual VM, skip state check -c.add_valid("-o test-clone-simple -n newvm --preserve-data --file /dev/default-pool/default-vol --clone-running --force") # Preserve data shouldn't complain about existing volume +c.add_valid("-o test-clone-simple -n newvm --preserve-data --file %(EXISTIMG1)s --clone-running") # Preserve data shouldn't complain about existing volume +c.add_valid("-n clonetest --original-xml %(CLONE_DISK_XML)s --file %(EXISTIMG3)s --file %(EXISTIMG4)s --check path_exists=off") # Skip existing file check c.add_invalid("--auto-clone") # Just the auto flag c.add_invalid("-o test-for-clone --auto-clone") +c.add_invalid("-o test-clone-simple -n newvm --file %(EXISTIMG1)s --clone-running") # Should complain about overwriting existing file c = vclon.add_category("general", "-n clonetest") diff --git a/virt-clone b/virt-clone index 3fc6a17a8..176509d9f 100755 --- a/virt-clone +++ b/virt-clone @@ -24,8 +24,6 @@ import argparse import logging import sys -import urlgrabber.progress as progress - import virtinst.cli as cli from virtinst import Cloner from virtinst.cli import fail, print_stdout, print_stderr @@ -163,8 +161,9 @@ def main(conn=None): options.quiet = options.quiet or options.xmlonly cli.setupLogging("virt-clone", options.debug, options.quiet) + cli.convert_old_force(options) + cli.parse_check(options.check) cli.set_prompt(options.prompt) - cli.set_force(options.force) if conn is None: conn = cli.getConnection(options.connect) @@ -200,9 +199,7 @@ def main(conn=None): if options.xmlonly: print_stdout(design.clone_xml, do_force=True) else: - # start cloning - meter = progress.TextMeter(fo=sys.stdout) - design.start_duplicate(meter) + design.start_duplicate(cli.get_meter()) print_stdout("") print_stdout(_("Clone '%s' created successfully.") % design.clone_name) diff --git a/virt-install b/virt-install index 9f6cfe1e8..f64bd96e2 100755 --- a/virt-install +++ b/virt-install @@ -24,7 +24,6 @@ import sys import time import libvirt -import urlgrabber.progress as progress import virtinst from virtinst import cli @@ -418,7 +417,7 @@ def validate_required_options(options, guest): msg = "" if not options.name: - msg += "\n" + cli.name_missing + msg += "\n" + _("--name is required") if not options.memory: msg += "\n" + _("--memory amount in MiB is required") @@ -739,9 +738,7 @@ def start_install(guest, continue_inst, options): wait_on_install = True wait_time = -1 - meter = (options.quiet and - progress.BaseMeter() or - progress.TextMeter(fo=sys.stdout)) + meter = cli.get_meter() logging.debug("Guest.has_install_phase: %s", guest.installer.has_install_phase()) @@ -1048,7 +1045,8 @@ def main(conn=None): check_cdrom_option_error(options) - cli.set_force(options.force) + cli.convert_old_force(options) + cli.parse_check(options.check) cli.set_prompt(options.prompt) parsermap = cli.build_parser_map(options) diff --git a/virt-xml b/virt-xml index d3f48a836..0c92026a8 100755 --- a/virt-xml +++ b/virt-xml @@ -24,7 +24,6 @@ import os import sys import libvirt -import urlgrabber.progress as progress import virtinst from virtinst import cli @@ -241,11 +240,7 @@ def setup_device(dev): logging.debug("Doing setup for disk=%s", dev) - meter = ((cli.quiet or "VIRTINST_TEST_SUITE" in os.environ) and - progress.BaseMeter() or - progress.TextMeter(fo=sys.stdout)) - - dev.setup(meter) + dev.setup(cli.get_meter()) dev.virt_xml_setup = True diff --git a/virtinst/cli.py b/virtinst/cli.py index 7ca5e75ae..56cf2adbd 100644 --- a/virtinst/cli.py +++ b/virtinst/cli.py @@ -29,6 +29,7 @@ import sys import traceback import libvirt +from urlgrabber import progress from virtcli import CLIConfig @@ -58,8 +59,38 @@ from .osxml import OSXML from .storage import StoragePool, StorageVolume -force = False -quiet = False +########################## +# Global option handling # +########################## + +class _GlobalState(object): + def __init__(self): + self.quiet = False + + self.all_checks = None + self._validation_checks = {} + + def set_validation_check(self, checkname, val): + self._validation_checks[checkname] = val + + def get_validation_check(self, checkname): + if self.all_checks is not None: + return self.all_checks + + # Default to True for all checks + return self._validation_checks.get(checkname, True) + + +_globalstate = None + + +def get_global_state(): + return _globalstate + + +def _reset_global_state(): + global _globalstate + _globalstate = _GlobalState() #################### @@ -136,8 +167,8 @@ def earlyLogging(): def setupLogging(appname, debug_stdout, do_quiet, cli_app=True): - global quiet - quiet = do_quiet + _reset_global_state() + get_global_state().quiet = do_quiet vi_dir = None logfile = None @@ -192,7 +223,7 @@ def setupLogging(appname, debug_stdout, do_quiet, cli_app=True): elif not cli_app: streamHandler = None else: - if quiet: + if get_global_state().quiet: level = logging.ERROR else: level = logging.WARN @@ -270,7 +301,7 @@ def fail(msg, do_exit=True): def print_stdout(msg, do_force=False): - if do_force or not quiet: + if do_force or not get_global_state().quiet: print msg @@ -303,27 +334,22 @@ def install_fail(guest): sys.exit(1) -def set_force(val=True): - global force - force = val - - def set_prompt(prompt): # Set whether we allow prompts, or fail if a prompt pops up if prompt: logging.warning("--prompt mode is no longer supported.") -name_missing = _("--name is required") - - def validate_disk(dev, warn_overwrite=False): - def _optional_fail(msg): - if force: - logging.debug("--force skipping error condition '%s'", msg) - logging.warn(msg) - else: - fail(msg + _(" (Use --force to override)")) + def _optional_fail(msg, checkname): + do_check = get_global_state().get_validation_check(checkname) + if do_check: + fail(msg + (_(" (Use --check %s=off or " + "--check all=off to override)") % checkname)) + + logging.debug("Skipping --check %s error condition '%s'", + checkname, msg) + logging.warn(msg) def check_path_exists(dev): """ @@ -331,10 +357,11 @@ def validate_disk(dev, warn_overwrite=False): """ if not warn_overwrite: return - if VirtualDisk.path_definitely_exists(dev.conn, dev.path): - _optional_fail( - _("This will overwrite the existing path '%s'" % dev.path)) - + if not VirtualDisk.path_definitely_exists(dev.conn, dev.path): + return + _optional_fail( + _("This will overwrite the existing path '%s'" % dev.path), + "path_exists") def check_inuse_conflict(dev): """ @@ -345,7 +372,8 @@ def validate_disk(dev, warn_overwrite=False): return _optional_fail(_("Disk %s is already in use by other guests %s." % - (dev.path, names))) + (dev.path, names)), + "path_in_use") def check_size_conflict(dev): """ @@ -354,7 +382,7 @@ def validate_disk(dev, warn_overwrite=False): isfatal, errmsg = dev.is_size_conflict() # The isfatal case should have already caused us to fail if not isfatal and errmsg: - _optional_fail(errmsg) + _optional_fail(errmsg, "disk_size") def check_path_search(dev): user, broken_paths = dev.check_path_search(dev.conn, dev.path) @@ -446,6 +474,12 @@ def get_console_cb(guest): return _gfx_console +def get_meter(): + if get_global_state().quiet or "VIRTINST_TEST_SUITE" in os.environ: + return progress.BaseMeter() + return progress.TextMeter(fo=sys.stdout) + + ########################### # Common CLI option/group # ########################### @@ -509,6 +543,11 @@ def add_misc_options(grp, prompt=False, replace=False, help=_("Run through install process, but do not " "create devices or define the guest.")) + if prompt: + grp.add_argument("--check", + help=_("Enable or disable validation checks. Example:\n" + "--check path_in_use=off\n" + "--check all=off")) grp.add_argument("-q", "--quiet", action="store_true", help=_("Suppress non-error output")) grp.add_argument("-d", "--debug", action="store_true", @@ -1100,6 +1139,41 @@ class VirtCLIParser(object): raise NotImplementedError() +################### +# --check parsing # +################### + +def convert_old_force(options): + if options.force: + if not options.check: + options.check = "all=off" + del(options.force) + + +class ParseCLICheck(VirtCLIParser): + # This sets properties on the _GlobalState objects + + def _init_params(self): + def _set_check(opts, inst, cliname, val): + ignore = opts + inst.set_validation_check(cliname, val) + + self.set_param(None, "path_in_use", + is_onoff=True, setter_cb=_set_check) + self.set_param(None, "disk_size", + is_onoff=True, setter_cb=_set_check) + self.set_param(None, "path_exists", + is_onoff=True, setter_cb=_set_check) + + self.set_param("all_checks", "all", is_onoff=True) + + +def parse_check(checkstr): + # Overwrite this for each parse, + parser = ParseCLICheck("check") + parser.parse(None, checkstr, get_global_state()) + + ###################### # --metadata parsing # ######################