diff options
author | Cole Robinson <crobinso@redhat.com> | 2020-01-26 17:12:09 -0500 |
---|---|---|
committer | Cole Robinson <crobinso@redhat.com> | 2020-01-26 18:27:20 -0500 |
commit | 8fe0a208dbfa074e56a29397f7b461104baaba41 (patch) | |
tree | 8694501ecef37d6bd7d422014c41d480f404a573 /virt-xml | |
parent | 3538a8df793c6b3652162d5202b49104236444fd (diff) | |
download | virt-manager-8fe0a208dbfa074e56a29397f7b461104baaba41.tar.gz |
Move virt-* code into their modules
This layout is closer to what most python modules have nowadays.
It also simplifies testing and static analysis setup.
Keep virt-* wrappers locally, for ease of running these commands
from a git checkout.
Adjust the wrapper binaries we install on via packaging to be
pure python, which makes things like running gdb easier.
Signed-off-by: Cole Robinson <crobinso@redhat.com>
Diffstat (limited to 'virt-xml')
-rwxr-xr-x | virt-xml | 576 |
1 files changed, 6 insertions, 570 deletions
@@ -1,573 +1,9 @@ -#!/usr/bin/env python3 -# -# Copyright 2013-2014 Red Hat, Inc. -# -# This work is licensed under the GNU GPLv2 or later. -# See the COPYING file in the top-level directory. +#!/usr/bin/python3 +# Convenience wrapper for easily running virt-xml from the git tree -import difflib -import re +import os import sys +sys.path.insert(0, os.path.dirname(__file__)) -import libvirt - -import virtinst -from virtinst import cli -from virtinst import log -from virtinst import xmlutil -from virtinst.cli import fail, print_stdout, print_stderr - - -################### -# Utility helpers # -################### - -def prompt_yes_or_no(msg): - while 1: - printmsg = msg + " (y/n): " - sys.stdout.write(printmsg) - sys.stdout.flush() - inp = sys.stdin.readline().lower().strip() - - if inp in ["y", "yes"]: - return True - elif inp in ["n", "no"]: - return False - else: - print_stdout(_("Please enter 'yes' or 'no'.")) - - -def get_diff(origxml, newxml): - ret = "".join(difflib.unified_diff(origxml.splitlines(1), - newxml.splitlines(1), - fromfile="Original XML", - tofile="Altered XML")) - - if ret: - log.debug("XML diff:\n%s", ret) - else: - log.debug("No XML diff, didn't generate any change.") - return ret - - -def set_os_variant(options, guest): - if options.os_variant is None: - return - - osdata = cli.parse_os_variant(options.os_variant) - if osdata.name: - guest.set_os_name(osdata.name) - - -def get_xmldesc(domain, inactive=False): - flags = libvirt.VIR_DOMAIN_XML_SECURE - if inactive: - flags |= libvirt.VIR_DOMAIN_XML_INACTIVE - return domain.XMLDesc(flags) - - -def get_domain_and_guest(conn, domstr): - try: - int(domstr) - isint = True - except ValueError: - isint = False - - uuidre = "[a-fA-F0-9]{8}[-]([a-fA-F0-9]{4}[-]){3}[a-fA-F0-9]{12}$" - isuuid = bool(re.match(uuidre, domstr)) - - try: - domain = None - try: - domain = conn.lookupByName(domstr) - except Exception: - # In case the VM has a UUID or ID for a name - log.debug("Error looking up domain by name", exc_info=True) - if isint: - domain = conn.lookupByID(int(domstr)) - elif isuuid: - domain = conn.lookupByUUIDString(domstr) - else: - raise - except libvirt.libvirtError as e: - fail(_("Could not find domain '%s': %s") % (domstr, e)) - - state = domain.info()[0] - active_xmlobj = None - inactive_xmlobj = virtinst.Guest(conn, parsexml=get_xmldesc(domain)) - if state != libvirt.VIR_DOMAIN_SHUTOFF: - active_xmlobj = inactive_xmlobj - inactive_xmlobj = virtinst.Guest(conn, - parsexml=get_xmldesc(domain, inactive=True)) - - return (domain, inactive_xmlobj, active_xmlobj) - - -def defined_xml_is_unchanged(conn, domain, original_xml): - rawxml = get_xmldesc(domain, inactive=True) - new_xml = virtinst.Guest(conn, parsexml=rawxml).get_xml() - return new_xml == original_xml - - -################ -# Change logic # -################ - -def _find_objects_to_edit(guest, action_name, editval, parserclass): - objlist = xmlutil.listify(parserclass.lookup_prop(guest)) - idx = None - - if editval is None: - idx = 1 - elif (editval.isdigit() or - editval.startswith("-") and editval[1:].isdigit()): - idx = int(editval) - - if idx is not None: - # Edit device by index - if idx == 0: - fail(_("Invalid --edit option '%s'") % editval) - - if not objlist: - fail(_("No --%s objects found in the XML") % - parserclass.cli_arg_name) - if len(objlist) < abs(idx): - fail(_("--edit %s requested but there's only %s " - "--%s object in the XML") % - (idx, len(objlist), parserclass.cli_arg_name)) - - if idx > 0: - idx -= 1 - inst = objlist[idx] - - elif editval == "all": - # Edit 'all' devices - inst = objlist[:] - - else: - # Lookup device by the passed prop string - parserobj = parserclass(editval, guest=guest) - inst = parserobj.lookup_child_from_option_string() - if not inst: - fail(_("No matching objects found for --%s %s") % - (action_name, editval)) - - return inst - - -def check_action_collision(options): - actions = ["edit", "add-device", "remove-device", "build-xml"] - - collisions = [] - for cliname in actions: - optname = cliname.replace("-", "_") - if getattr(options, optname) not in [False, -1]: - collisions.append(cliname) - - if len(collisions) == 0: - fail(_("One of %s must be specified.") % - ", ".join(["--" + c for c in actions])) - if len(collisions) > 1: - fail(_("Conflicting options %s") % - ", ".join(["--" + c for c in collisions])) - - -def check_xmlopt_collision(options): - collisions = [] - for parserclass in cli.VIRT_PARSERS: - if getattr(options, parserclass.cli_arg_name): - collisions.append(parserclass) - - if len(collisions) == 0: - fail(_("No change specified.")) - if len(collisions) != 1: - fail(_("Only one change operation may be specified " - "(conflicting options %s)") % - [c.cli_flag_name() for c in collisions]) - - return collisions[0] - - -def action_edit(guest, options, parserclass): - if parserclass.guest_propname: - inst = _find_objects_to_edit(guest, "edit", options.edit, parserclass) - else: - inst = guest - if options.edit and options.edit != '1' and options.edit != 'all': - fail(_("'--edit %s' doesn't make sense with --%s, " - "just use empty '--edit'") % - (options.edit, parserclass.cli_arg_name)) - if options.os_variant is not None: - fail(_("--os-variant is not supported with --edit")) - - return cli.parse_option_strings(options, guest, inst, editing=True) - - -def action_add_device(guest, options, parserclass): - if not parserclass.prop_is_list(guest): - fail(_("Cannot use --add-device with --%s") % parserclass.cli_arg_name) - set_os_variant(options, guest) - devs = cli.parse_option_strings(options, guest, None) - devs = xmlutil.listify(devs) - for dev in devs: - dev.set_defaults(guest) - return devs - - -def action_remove_device(guest, options, parserclass): - if not parserclass.prop_is_list(guest): - fail(_("Cannot use --remove-device with --%s") % - parserclass.cli_arg_name) - if options.os_variant is not None: - fail(_("--os-variant is not supported with --remove-device")) - - devs = _find_objects_to_edit(guest, "remove-device", - getattr(options, parserclass.cli_arg_name)[-1], parserclass) - - devs = xmlutil.listify(devs) - for dev in devs: - guest.remove_device(dev) - return devs - - -def action_build_xml(conn, options, parserclass, guest): - if not parserclass.guest_propname: - fail(_("--build-xml not supported for --%s") % - parserclass.cli_arg_name) - if options.os_variant is not None: - fail(_("--os-variant is not supported with --build-xml")) - - inst = parserclass.lookup_prop(guest) - if parserclass.prop_is_list(guest): - inst = inst.new() - else: - inst = inst.__class__(conn) - - devs = cli.parse_option_strings(options, guest, inst) - devs = xmlutil.listify(devs) - for dev in devs: - dev.set_defaults(guest) - return devs - - -def setup_device(dev): - if getattr(dev, "DEVICE_TYPE", None) != "disk": - return - - log.debug("Doing setup for disk=%s", dev) - dev.build_storage(cli.get_meter()) - - -def define_changes(conn, inactive_xmlobj, devs, action, confirm): - if confirm: - if not prompt_yes_or_no( - _("Define '%s' with the changed XML?") % inactive_xmlobj.name): - return False - - if action == "hotplug": - for dev in devs: - setup_device(dev) - - dom = conn.defineXML(inactive_xmlobj.get_xml()) - print_stdout(_("Domain '%s' defined successfully.") % inactive_xmlobj.name) - return dom - - -def start_domain_transient(conn, xmlobj, devs, action, confirm): - if confirm: - if not prompt_yes_or_no( - _("Start '%s' with the changed XML?") % xmlobj.name): - return False - - if action == "hotplug": - for dev in devs: - setup_device(dev) - - try: - dom = conn.createXML(xmlobj.get_xml()) - except libvirt.libvirtError as e: - fail(_("Failed starting domain '%s': %s") % (xmlobj.name, e)) - else: - print_stdout(_("Domain '%s' started successfully.") % xmlobj.name) - return dom - - -def update_changes(domain, devs, action, confirm): - for dev in devs: - xml = dev.get_xml() - - if confirm: - if action == "hotplug": - prep = "to" - elif action == "hotunplug": - prep = "from" - else: - prep = "for" - - msg = ("%s\n\n%s this device %s guest '%s'?" % - (xml, action.capitalize(), prep, domain.name())) - if not prompt_yes_or_no(msg): - continue - - if action == "hotplug": - setup_device(dev) - - try: - if action == "hotplug": - domain.attachDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_LIVE) - elif action == "hotunplug": - domain.detachDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_LIVE) - elif action == "update": - domain.updateDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_LIVE) - except libvirt.libvirtError as e: - fail(_("Error attempting device %s: %s") % (action, e)) - - # Test driver doesn't support device hotplug so we can't reach this - print_stdout(_("Device %s successful.") % action) # pragma: no cover - if confirm: # pragma: no cover - print_stdout("") - - -def prepare_changes(xmlobj, options, parserclass): - origxml = xmlobj.get_xml() - - if options.edit != -1: - devs = action_edit(xmlobj, options, parserclass) - action = "update" - - elif options.add_device: - devs = action_add_device(xmlobj, options, parserclass) - action = "hotplug" - - elif options.remove_device: - devs = action_remove_device(xmlobj, options, parserclass) - action = "hotunplug" - - newxml = xmlobj.get_xml() - diff = get_diff(origxml, newxml) - - if not diff: - log.warning(_("No XML diff was generated. The requested " - "changes will have no effect.")) - - if options.print_diff: - if diff: - print_stdout(diff) - elif options.print_xml: - print_stdout(newxml) - - return devs, action - - -####################### -# CLI option handling # -####################### - -def parse_args(): - parser = cli.setupParser( - "%(prog)s [options]", - _("Edit libvirt XML using command line options."), - introspection_epilog=True) - - cli.add_connect_option(parser, "virt-xml") - - parser.add_argument("domain", nargs='?', - help=_("Domain name, id, or uuid")) - - actg = parser.add_argument_group(_("XML actions")) - actg.add_argument("--edit", nargs='?', default=-1, - help=_("Edit VM XML. Examples:\n" - "--edit --disk ... (edit first disk device)\n" - "--edit 2 --disk ... (edit second disk device)\n" - "--edit all --disk ... (edit all disk devices)\n" - "--edit target=hda --disk ... (edit disk 'hda')\n")) - actg.add_argument("--remove-device", action="store_true", - help=_("Remove specified device. Examples:\n" - "--remove-device --disk 1 (remove first disk)\n" - "--remove-device --disk all (remove all disks)\n" - "--remove-device --disk /some/path")) - actg.add_argument("--add-device", action="store_true", - help=_("Add specified device. Example:\n" - "--add-device --disk ...")) - actg.add_argument("--build-xml", action="store_true", - help=_("Output built device XML. Domain is optional but " - "recommended to ensure optimal defaults.")) - - outg = parser.add_argument_group(_("Output options")) - outg.add_argument("--update", action="store_true", - help=_("Apply changes to the running VM.\n" - "With --add-device, this is a hotplug operation.\n" - "With --remove-device, this is a hotunplug operation.\n" - "With --edit, this is an update device operation.")) - define_g = outg.add_mutually_exclusive_group() - define_g.add_argument("--define", action="store_true", - help=_("Force defining the domain. Only required if a --print " - "option was specified.")) - define_g.add_argument("--no-define", dest='define', action="store_false", - help=_("Force not defining the domain.")) - define_g.set_defaults(define=None) - outg.add_argument("--start", action="store_true", - help=_("Start the domain.")) - outg.add_argument("--print-diff", action="store_true", - help=_("Only print the requested change, in diff format")) - outg.add_argument("--print-xml", action="store_true", - help=_("Only print the requested change, in full XML format")) - outg.add_argument("--confirm", action="store_true", - help=_("Require confirmation before saving any results.")) - - cli.add_os_variant_option(parser, virtinstall=False) - - g = parser.add_argument_group(_("XML options")) - cli.add_disk_option(g, editexample=True) - cli.add_net_option(g) - cli.add_gfx_option(g) - cli.add_metadata_option(g) - cli.add_memory_option(g) - cli.vcpu_cli_options(g, editexample=True) - cli.add_guest_xml_options(g) - cli.add_boot_options(g) - cli.add_device_options(g) - - misc = parser.add_argument_group(_("Miscellaneous Options")) - cli.add_misc_options(misc, prompt=False, printxml=False, dryrun=False) - - cli.autocomplete(parser) - - return parser.parse_args() - - -################### -# main() handling # -################### - -def main(conn=None): - cli.earlyLogging() - options = parse_args() - - if (options.confirm or options.print_xml or - options.print_diff or options.build_xml): - options.quiet = False - cli.setupLogging("virt-xml", options.debug, options.quiet) - - if cli.check_option_introspection(options): - return 0 - - options.stdinxml = None - if not options.domain and not options.build_xml: - if not sys.stdin.closed and not sys.stdin.isatty(): - if options.confirm: - fail(_("Can't use --confirm with stdin input.")) - if options.update: - fail(_("Can't use --update with stdin input.")) - options.stdinxml = sys.stdin.read() - else: - fail(_("A domain must be specified")) - - # Default to --define, unless: - # --no-define explicitly specified - # --print-* option is used - # XML input came from stdin - if not options.print_xml and not options.print_diff: - if options.stdinxml: - if not options.define: - options.print_xml = True - else: - if options.define is None: - options.define = True - if options.confirm and not options.print_xml: - options.print_diff = True - - # Ensure only one of these actions wash specified - # --edit - # --remove-device - # --add-device - # --build-xml - check_action_collision(options) - - # Ensure there wasn't more than one device/xml config option - # specified. So reject '--disk X --network X' - parserclass = check_xmlopt_collision(options) - - if options.update and not parserclass.guest_propname: - fail(_("Don't know how to --update for --%s") % - (parserclass.cli_arg_name)) - - conn = cli.getConnection(options.connect, conn) - - domain = None - active_xmlobj = None - inactive_xmlobj = None - if options.domain: - domain, inactive_xmlobj, active_xmlobj = get_domain_and_guest( - conn, options.domain) - else: - inactive_xmlobj = virtinst.Guest(conn, parsexml=options.stdinxml) - vm_is_running = bool(active_xmlobj) - - if options.build_xml: - devs = action_build_xml(conn, options, parserclass, inactive_xmlobj) - for dev in devs: - # pylint: disable=no-member - print_stdout(dev.get_xml()) - return 0 - - performed_update = False - if options.update: - if options.update and options.start: - fail(_("Cannot mix --update and --start")) - - if vm_is_running: - devs, action = prepare_changes(active_xmlobj, options, parserclass) - update_changes(domain, devs, action, options.confirm) - performed_update = True - else: - log.warning( - _("The VM is not running, --update is inapplicable.")) - if not options.define: - # --update and --no-define passed, so we are done - # It's hard to hit this case with the test suite - return 0 # pragma: no cover - - original_xml = inactive_xmlobj.get_xml() - devs, action = prepare_changes(inactive_xmlobj, options, parserclass) - if not options.define: - if options.start: - start_domain_transient(conn, inactive_xmlobj, devs, - action, options.confirm) - return 0 - - dom = define_changes(conn, inactive_xmlobj, - devs, action, options.confirm) - if not dom: - # --confirm user said 'no' - return 0 - - if options.start: - try: - dom.create() - except libvirt.libvirtError as e: # pragma: no cover - fail(_("Failed starting domain '%s': %s") % ( - inactive_xmlobj.name, e)) - print_stdout(_("Domain '%s' started successfully.") % - inactive_xmlobj.name) - - elif vm_is_running and not performed_update: - print_stdout( - _("Changes will take effect after the domain is fully powered off.")) - elif defined_xml_is_unchanged(conn, domain, original_xml): - log.warning(_("XML did not change after domain define. You may " - "have changed a value that libvirt is setting by default.")) - - return 0 - - -if __name__ == "__main__": # pragma: no cover - try: - sys.exit(main()) - except SystemExit as sys_e: - sys.exit(sys_e.code) - except KeyboardInterrupt: - log.debug("", exc_info=True) - print_stderr(_("Aborted at user request")) - except Exception as main_e: - fail(main_e) +from virtinst import virtxml +virtxml.runcli() |