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"))