mirror of
https://github.com/virt-manager/virt-manager.git
synced 2024-12-22 13:34:07 +03:00
352 lines
11 KiB
Python
Executable File
352 lines
11 KiB
Python
Executable File
#!/usr/bin/python -tt
|
|
#
|
|
# Copyright 2013 Red Hat, Inc.
|
|
# Cole Robinson <crobinso@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 difflib
|
|
import logging
|
|
import os
|
|
import sys
|
|
|
|
import libvirt
|
|
|
|
import virtinst
|
|
from virtinst import cli
|
|
from virtinst import util
|
|
from virtinst.cli import fail, print_stdout, print_stderr
|
|
|
|
|
|
###################
|
|
# Utility helpers #
|
|
###################
|
|
|
|
def prompt_yes_or_no(msg):
|
|
while 1:
|
|
printmsg = msg + " (y/n): "
|
|
if "VIRTINST_TEST_SUITE" in os.environ:
|
|
printmsg += "\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:
|
|
logging.debug("XML diff:\n%s", ret)
|
|
else:
|
|
logging.debug("No XML diff, didn't generate any change.")
|
|
return ret
|
|
|
|
|
|
def get_domain_and_guest(conn, domstr):
|
|
if not domstr:
|
|
fail("--domain must be specified")
|
|
|
|
try:
|
|
int(domstr)
|
|
isint = True
|
|
except ValueError:
|
|
isint = False
|
|
|
|
try:
|
|
virtinst.util.validate_uuid(domstr)
|
|
isuuid = True
|
|
except ValueError:
|
|
isuuid = False
|
|
|
|
try:
|
|
if isint:
|
|
domain = conn.lookupByID(int(domstr))
|
|
elif isuuid:
|
|
domain = conn.lookupByUUIDString(domstr)
|
|
else:
|
|
domain = conn.lookupByName(domstr)
|
|
except libvirt.libvirtError, e:
|
|
fail(_("Could not find domain '%s': %s") % (domstr, e))
|
|
|
|
def _make_guest(xml):
|
|
# We do this to minimize the diff, removing things like ' -> "
|
|
return virtinst.Guest(conn,
|
|
parsexml=virtinst.Guest(conn, parsexml=xml).get_xml_config())
|
|
|
|
state = domain.info()[0]
|
|
active_xmlobj = None
|
|
inactive_xmlobj = _make_guest(domain.XMLDesc(0))
|
|
if state != libvirt.VIR_DOMAIN_SHUTOFF:
|
|
active_xmlobj = inactive_xmlobj
|
|
inactive_xmlobj = _make_guest(
|
|
domain.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE))
|
|
|
|
return (domain, inactive_xmlobj, active_xmlobj)
|
|
|
|
|
|
################
|
|
# Change logic #
|
|
################
|
|
|
|
def _find_devices_to_edit(guest, action_name, editval, parserobj):
|
|
devlist = guest.get_devices(parserobj.devclass.virtual_device_type)
|
|
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:
|
|
if idx == 0:
|
|
fail(_("Invalid --edit option '%s'") % editval)
|
|
|
|
if not devlist:
|
|
fail(_("No --%s devices found in the XML") %
|
|
parserobj.cli_arg_name)
|
|
if len(devlist) < abs(idx):
|
|
fail(_("--edit %s requested but there's only %s "
|
|
"--%s devices in the XML") %
|
|
(idx, len(devlist), parserobj.cli_arg_name))
|
|
|
|
if idx > 0:
|
|
idx -= 1
|
|
inst = devlist[idx]
|
|
elif editval == "all":
|
|
inst = devlist[:]
|
|
else:
|
|
inst = parserobj.lookup_device_from_option_string(guest, editval)
|
|
if not inst:
|
|
fail(_("No matching devices found for --%s %s") %
|
|
(action_name, editval))
|
|
|
|
return inst
|
|
|
|
|
|
def check_action_collision(options):
|
|
actions = ["edit", "add-device", "remove-device"]
|
|
|
|
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, parsermap):
|
|
collisions = []
|
|
for option_variable_name, parserobj in parsermap.items():
|
|
if getattr(options, option_variable_name):
|
|
collisions.append(parserobj)
|
|
|
|
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_arg_name for c in collisions])
|
|
|
|
return collisions[0]
|
|
|
|
|
|
def action_edit(guest, options, parsermap, parserobj):
|
|
if parserobj.devclass:
|
|
inst = _find_devices_to_edit(guest, "edit", options.edit, parserobj)
|
|
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, parserobj.cli_arg_name))
|
|
|
|
cli.parse_option_strings(parsermap, options, guest, inst, update=True)
|
|
|
|
|
|
def action_add_device(guest, options, parsermap, parserobj):
|
|
if not parserobj.devclass:
|
|
fail(_("Cannot use --add-device with --%s") % parserobj.cli_arg_name)
|
|
cli.parse_option_strings(parsermap, options, guest, None)
|
|
|
|
|
|
def action_remove_device(guest, options, parsermap, parserobj):
|
|
ignore = parsermap
|
|
if not parserobj.devclass:
|
|
fail(_("Cannot use --remove-device with --%s") %
|
|
parserobj.cli_arg_name)
|
|
|
|
devs = _find_devices_to_edit(guest, "remove-device",
|
|
getattr(options, parserobj.option_variable_name)[-1], parserobj)
|
|
|
|
for dev in util.listify(devs):
|
|
guest.remove_device(dev)
|
|
|
|
|
|
#######################
|
|
# CLI option handling #
|
|
#######################
|
|
|
|
def parse_args():
|
|
# XXX: man page: mention introspection if it makes sense
|
|
# XXX: expand usage
|
|
# XXX: notes about the default actions, behavior, etc
|
|
parser = cli.setupParser(
|
|
"%(prog)s [options]",
|
|
_("Edit libvirt XML using command line options."),
|
|
introspection_epilog=True)
|
|
|
|
cli.add_connect_option(parser)
|
|
|
|
actg = parser.add_argument_group(_("Action Options"))
|
|
actg.add_argument("--domain", help=_("Domain name, id, or uuid"))
|
|
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 ..."))
|
|
|
|
g = parser.add_argument_group(_("XML options"))
|
|
cli.add_disk_option(g)
|
|
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)
|
|
cli.add_guest_xml_options(g)
|
|
cli.add_boot_option(g)
|
|
cli.add_fs_option(g)
|
|
cli.add_device_options(g)
|
|
|
|
misc = parser.add_argument_group(_("Miscellaneous Options"))
|
|
cli.add_misc_options(misc, prompt=False, printxml=False, dryrun=False)
|
|
misc.add_argument("--print-diff", action="store_true",
|
|
help=_("Only print the requested change, in diff format"))
|
|
misc.add_argument("--print-xml", action="store_true",
|
|
help=_("Only print the requested change, in full XML format"))
|
|
misc.add_argument("--confirm", action="store_true",
|
|
help=_("Require confirmation before saving any results."))
|
|
misc.add_argument("--define", action="store_true",
|
|
help=_("Force defining the domain, only required if a --print "
|
|
"option was specified."))
|
|
|
|
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:
|
|
options.quiet = False
|
|
if not options.print_xml and not options.print_diff:
|
|
options.define = True
|
|
if options.confirm and not options.print_xml:
|
|
options.print_diff = True
|
|
|
|
cli.setupLogging("virt-xml", options.debug, options.quiet)
|
|
|
|
parsermap = cli.build_parser_map(options)
|
|
if cli.check_option_introspection(options, parsermap):
|
|
return 0
|
|
|
|
if conn is None:
|
|
conn = cli.getConnection(options.connect)
|
|
|
|
domain, inactive_xmlobj, active_xmlobj = get_domain_and_guest(
|
|
conn, options.domain)
|
|
guest = inactive_xmlobj
|
|
origxml = guest.get_xml_config()
|
|
# XXX: do we ever need the domain?
|
|
ignore = domain
|
|
|
|
check_action_collision(options)
|
|
parserobj = check_xmlopt_collision(options, parsermap)
|
|
|
|
if options.edit != -1:
|
|
action_edit(guest, options, parsermap, parserobj)
|
|
elif options.add_device:
|
|
action_add_device(guest, options, parsermap, parserobj)
|
|
elif options.remove_device:
|
|
action_remove_device(guest, options, parsermap, parserobj)
|
|
|
|
newxml = guest.get_xml_config()
|
|
diff = get_diff(origxml, newxml)
|
|
|
|
if options.print_diff:
|
|
if diff:
|
|
print_stdout(diff)
|
|
elif options.print_xml:
|
|
print_stdout(newxml)
|
|
|
|
if not options.define:
|
|
return 0
|
|
|
|
if options.confirm:
|
|
# XXX: Message needs to depend on what action we will take
|
|
if not prompt_yes_or_no(
|
|
_("Define '%s' with the changed XML?" % guest.name)):
|
|
return 0
|
|
|
|
conn.defineXML(guest.get_xml_config())
|
|
print_stdout(_("Domain '%s' defined successfully." % guest.name))
|
|
if active_xmlobj:
|
|
print_stdout(
|
|
_("Changes will take effect after the next domain shutdown."))
|
|
|
|
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(_("Aborted at user request"))
|
|
except Exception, main_e:
|
|
fail(main_e)
|