From 150b6ac95e617fffa1fbb91e2ff19c1fa45a1062 Mon Sep 17 00:00:00 2001 From: Cole Robinson Date: Mon, 3 Feb 2020 07:23:05 -0500 Subject: [PATCH] virt-install: Add --reinstall=DOMAIN option This allows using virt-install's install logic on an existing VM Signed-off-by: Cole Robinson --- man/virt-install.pod | 14 +++ .../compare/virt-install-reinstall-cdrom.xml | 63 +++++++++++ .../virt-install-reinstall-location.xml | 100 ++++++++++++++++++ .../compare/virt-install-reinstall-pxe.xml | 89 ++++++++++++++++ tests/test_cli.py | 5 + tests/testsuite.xml | 24 +++++ virtinst/install/installer.py | 33 ++++-- virtinst/virtinstall.py | 46 +++++--- 8 files changed, 353 insertions(+), 21 deletions(-) create mode 100644 tests/data/cli/compare/virt-install-reinstall-cdrom.xml create mode 100644 tests/data/cli/compare/virt-install-reinstall-location.xml create mode 100644 tests/data/cli/compare/virt-install-reinstall-pxe.xml diff --git a/man/virt-install.pod b/man/virt-install.pod index 24a558458..12840253d 100644 --- a/man/virt-install.pod +++ b/man/virt-install.pod @@ -594,6 +594,20 @@ options. The deprecated B<--live> option is the same as =back +=item B<--reinstall DOMAIN> + +Reinstall an existing VM. DOMAIN can be a VM name, UUID, or ID number. +virt-install will fetch the domain XML from libvirt, apply the specified +install config changes, boot the VM for the install process, and then +revert to roughly the same starting XML. + +Only install related options are processed, all other VM configuration +options like --name, --disk, etc. are completely ignored. + +If --reinstall is used with --cdrom, an existing CDROM attached to +the VM will be used if one is available, otherwise a permanent CDROM +device will be added. + =item B<--unattended> [OPTIONS] diff --git a/tests/data/cli/compare/virt-install-reinstall-cdrom.xml b/tests/data/cli/compare/virt-install-reinstall-cdrom.xml new file mode 100644 index 000000000..b7618dc68 --- /dev/null +++ b/tests/data/cli/compare/virt-install-reinstall-cdrom.xml @@ -0,0 +1,63 @@ + + test-cdrom + 4a64cc71-aaaa-2fd0-2323-3050941ea3c3 + 8388608 + 2097152 + 2 + + hvm + + + + destroy + destroy + destroy + + + + +
+ + + + + + + + + + + + + + + + + test-cdrom + 4a64cc71-aaaa-2fd0-2323-3050941ea3c3 + 8388608 + 2097152 + 2 + + hvm + + + + destroy + restart + destroy + + + + +
+ + + + + + + + + + diff --git a/tests/data/cli/compare/virt-install-reinstall-location.xml b/tests/data/cli/compare/virt-install-reinstall-location.xml new file mode 100644 index 000000000..9b33ad4ff --- /dev/null +++ b/tests/data/cli/compare/virt-install-reinstall-location.xml @@ -0,0 +1,100 @@ + + test-clone-simple + 12345678-1234-ffff-1234-12345678ffff + 409600 + 204800 + 5 + + hvm + /usr/lib/xen/boot/hvmloader + /VIRTINST-TESTSUITE/vmlinuz + /VIRTINST-TESTSUITE/initrd.img + method=http://example.com + + + + + + + destroy + destroy + destroy + + /usr/lib/xen/bin/qemu-dm + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + test-clone-simple + 12345678-1234-ffff-1234-12345678ffff + 409600 + 204800 + 5 + + hvm + /usr/lib/xen/boot/hvmloader + + + + + + + + destroy + restart + destroy + + /usr/lib/xen/bin/qemu-dm + + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/cli/compare/virt-install-reinstall-pxe.xml b/tests/data/cli/compare/virt-install-reinstall-pxe.xml new file mode 100644 index 000000000..c806aa988 --- /dev/null +++ b/tests/data/cli/compare/virt-install-reinstall-pxe.xml @@ -0,0 +1,89 @@ + + test-clone-simple + 12345678-1234-ffff-1234-12345678ffff + 409600 + 204800 + 5 + + hvm + /usr/lib/xen/boot/hvmloader + + + + + + + + + destroy + destroy + destroy + + /usr/lib/xen/bin/qemu-dm + + + + +
+ + + + + + + + + + + + + + + + + + test-clone-simple + 12345678-1234-ffff-1234-12345678ffff + 409600 + 204800 + 5 + + hvm + /usr/lib/xen/boot/hvmloader + + + + + + + + destroy + restart + destroy + + /usr/lib/xen/bin/qemu-dm + + + + +
+ + + + + + + + + + + + + + + + diff --git a/tests/test_cli.py b/tests/test_cli.py index 509c30e91..740e390ca 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -902,6 +902,11 @@ c.add_invalid("--hvm --nodisks --pxe foobar") # Positional arguments error c.add_invalid("--nodisks --pxe --name test") # Colliding name c.add_compare("--os-type linux --cdrom %(EXISTIMG1)s --disk size=1 --disk %(EXISTIMG2)s,device=cdrom", "cdrom-double") # ensure --disk device=cdrom is ordered after --cdrom, this is important for virtio-win installs with a driver ISO c.add_valid("--connect %s --pxe --disk size=1" % utils.URIs.test_defaultpool_collision) # testdriver already has a pool using the 'default' path, make sure we don't error +c.add_compare("--connect %(URI-KVM)s --reinstall test-clone-simple --pxe", "reinstall-pxe") # compare --reinstall with --pxe +c.add_compare("--connect %(URI-KVM)s --reinstall test-clone-simple --location http://example.com", "reinstall-location") # compare --reinstall with --location +c.add_compare("--reinstall test-cdrom --cdrom %(ISO-WIN7)s --unattended", "reinstall-cdrom") # compare --reinstall with --cdrom handling +c.add_invalid("--reinstall test --cdrom %(ISO-WIN7)s", grep="already active") # trying to reinstall an active VM should fail +c.add_invalid("--reinstall test", grep="install method must be specified") # missing install method #################### diff --git a/tests/testsuite.xml b/tests/testsuite.xml index b9a969b75..fd2551386 100644 --- a/tests/testsuite.xml +++ b/tests/testsuite.xml @@ -37,6 +37,30 @@ + + test-cdrom + 4a64cc71-aaaa-2fd0-2323-3050941ea3c3 + 8388608 + 2097152 + 5 + 2 + + hvm + + + + destroy + restart + destroy + + + + + + + + + test-collide 204800 diff --git a/virtinst/install/installer.py b/virtinst/install/installer.py index 24920f875..33bfff4bb 100644 --- a/virtinst/install/installer.py +++ b/virtinst/install/installer.py @@ -50,7 +50,7 @@ class Installer(object): def __init__(self, conn, cdrom=None, location=None, install_bootdev=None, location_kernel=None, location_initrd=None, install_kernel=None, install_initrd=None, install_kernel_args=None, - no_install=None): + no_install=None, is_reinstall=False): self.conn = conn # Entry point for virt-manager 'Customize' wizard to change autostart @@ -65,6 +65,8 @@ class Installer(object): self._install_bootdev = install_bootdev self._no_install = no_install + self._is_reinstall = is_reinstall + self._pre_reinstall_xml = None self._treemedia = None self._treemedia_bootconfig = None @@ -103,9 +105,19 @@ class Installer(object): if not bool(self._cdrom_path()): return - dev = self._make_cdrom_device(self._cdrom_path()) self._install_cdrom_device_added = True + if self._is_reinstall: + cdroms = [d for d in guest.devices.disk if d.is_cdrom()] + if cdroms: + dev = cdroms[0] + dev.path = self._cdrom_path() + return + + dev = self._make_cdrom_device(self._cdrom_path()) + if self._is_reinstall: + dev.set_defaults(guest) + # Insert the CDROM before any other CDROM, so boot=cdrom picks # it as the priority for idx, disk in enumerate(guest.devices.disk): @@ -271,6 +283,9 @@ class Installer(object): os_media, os_tree, injection_method) def _prepare(self, guest, meter): + if self._is_reinstall: + self._pre_reinstall_xml = guest.get_xml() + unattended_scripts = None if self._unattended_data: unattended_scripts = self._prepare_unattended_scripts(guest, meter) @@ -349,6 +364,8 @@ class Installer(object): """ if self._defaults_are_set: return + if self._is_reinstall: + self._pre_reinstall_xml = guest.get_xml() self._add_install_cdrom_device(guest) @@ -356,7 +373,8 @@ class Installer(object): bootdev = self._get_postinstall_bootdev(guest) guest.os.bootorder = self._build_boot_order(guest, bootdev) - guest.set_defaults(None) + if not self._is_reinstall: + guest.set_defaults(None) self._defaults_are_set = True def get_search_paths(self, guest): @@ -481,7 +499,7 @@ class Installer(object): install_xml = None if self.has_install_phase(): install_xml = self._get_install_xml(guest, meter) - final_xml = guest.get_xml() + final_xml = self._pre_reinstall_xml or guest.get_xml() log.debug("Generated install XML: %s", (install_xml and ("\n" + install_xml) or "None required")) @@ -524,7 +542,7 @@ class Installer(object): meter.start(size=None, text=meter_label) needs_boot = doboot or self.has_install_phase() - if guest.type == "vz": + if guest.type == "vz" and not self._is_reinstall: if transient: raise RuntimeError(_("Domain type 'vz' doesn't support " "transient installs.")) @@ -570,13 +588,14 @@ class Installer(object): :param return_xml: Don't create the guest, just return generated XML """ - guest.validate_name(guest.conn, guest.name) + if not self._is_reinstall and not return_xml: + guest.validate_name(guest.conn, guest.name) self.set_install_defaults(guest) try: self._prepare(guest, meter) - if not dry: + if not dry and not self._is_reinstall: for dev in guest.devices.disk: dev.build_storage(meter) diff --git a/virtinst/virtinstall.py b/virtinst/virtinstall.py index efa553189..18c7293b3 100644 --- a/virtinst/virtinstall.py +++ b/virtinst/virtinstall.py @@ -305,12 +305,13 @@ def validate_required_options(options, guest, installer): # aggregate the errors to help first time users get it right msg = "" - if not memory_specified(guest): - msg += "\n" + _("--memory amount in MiB is required") + if not options.reinstall: + if not memory_specified(guest): + msg += "\n" + _("--memory amount in MiB is required") - if not storage_specified(options, guest): - msg += "\n" + ( - _("--disk storage must be specified (override with --disk none)")) + if not storage_specified(options, guest): + msg += "\n" + ( + _("--disk storage must be specified (override with --disk none)")) if not guest.os.is_container() and not installer.options_specified(): msg += "\n" + ( @@ -384,6 +385,7 @@ def build_installer(options, guest, installdata): location = None location_kernel = None location_initrd = None + is_reinstall = bool(options.reinstall) unattended_data = None extra_args = options.extra_args @@ -431,7 +433,8 @@ def build_installer(options, guest, installdata): install_kernel=install_kernel, install_initrd=install_initrd, install_kernel_args=install_kernel_args, - no_install=no_install) + no_install=no_install, + is_reinstall=is_reinstall) if unattended_data: installer.set_unattended_data(unattended_data) @@ -541,7 +544,7 @@ def installer_detect_distro(guest, installer, osdata): fail(_("Error validating install location: %s") % str(e)) -def build_guest_instance(conn, options): +def _build_options_guest(conn, options): guest = Guest(conn) guest.skip_default_osinfo = True @@ -554,25 +557,37 @@ def build_guest_instance(conn, options): # However we want to do it after parse_option_strings to ensure # we are operating on any arch/os/type values passed in with --boot guest.set_capabilities_defaults() + return guest + +def build_guest_instance(conn, options): installdata = cli.parse_install(options.install) - installer = build_installer(options, guest, installdata) - - # Set guest osname, from commandline or detected from media osdata = cli.parse_os_variant(options.os_variant) if installdata.os: osdata.set_installdata_name(installdata.os) + + if options.reinstall: + dummy, guest, dummy = cli.get_domain_and_guest(conn, options.reinstall) + else: + guest = _build_options_guest(conn, options) + + installer = build_installer(options, guest, installdata) + + # Set guest osname, from commandline or detected from media guest.set_default_os_name() installer_detect_distro(guest, installer, osdata) - set_cli_defaults(options, guest) + if not options.reinstall: + set_cli_defaults(options, guest) + installer.set_install_defaults(guest) for path in installer.get_search_paths(guest): cli.check_path_search(guest.conn, path) - # cli specific disk validation - for disk in guest.devices.disk: - cli.validate_disk(disk) + if not options.reinstall: + # cli specific disk validation + for disk in guest.devices.disk: + cli.validate_disk(disk) validate_required_options(options, guest, installer) show_guest_warnings(options, guest, osdata) @@ -867,6 +882,9 @@ def parse_args(): help=_("Perform an unattended installation")) insg.add_argument("--install", help=_("Specify fine grained install options")) + insg.add_argument("--reinstall", metavar="DOMAIN", + help=_("Reinstall existing VM. Only install options are applied, " + "all other VM configuration options are ignored.")) insg.add_argument("--cloud-init", nargs="?", const=1, help=_("Perform a cloud image installation, configuring cloud-init"))