summaryrefslogtreecommitdiff
path: root/virt-xml
diff options
context:
space:
mode:
authorCole Robinson <crobinso@redhat.com>2020-01-26 17:12:09 -0500
committerCole Robinson <crobinso@redhat.com>2020-01-26 18:27:20 -0500
commit8fe0a208dbfa074e56a29397f7b461104baaba41 (patch)
tree8694501ecef37d6bd7d422014c41d480f404a573 /virt-xml
parent3538a8df793c6b3652162d5202b49104236444fd (diff)
downloadvirt-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-xvirt-xml576
1 files changed, 6 insertions, 570 deletions
diff --git a/virt-xml b/virt-xml
index 7b0174c9..75844613 100755
--- a/virt-xml
+++ b/virt-xml
@@ -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()