virt-install: Add --reinstall=DOMAIN option

This allows using virt-install's install logic on an existing VM

Signed-off-by: Cole Robinson <crobinso@redhat.com>
This commit is contained in:
Cole Robinson 2020-02-03 07:23:05 -05:00
parent 9385e8689b
commit 150b6ac95e
8 changed files with 353 additions and 21 deletions

View File

@ -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]

View File

@ -0,0 +1,63 @@
<domain type="test">
<name>test-cdrom</name>
<uuid>4a64cc71-aaaa-2fd0-2323-3050941ea3c3</uuid>
<memory unit="KiB">8388608</memory>
<currentMemory unit="KiB">2097152</currentMemory>
<vcpu placement="static">2</vcpu>
<os>
<type arch="i686">hvm</type>
<boot dev="cdrom"/>
</os>
<clock offset="utc"/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>destroy</on_reboot>
<on_crash>destroy</on_crash>
<devices>
<disk type="file" device="cdrom">
<target dev="hdc" bus="ide"/>
<readonly/>
<address type="drive" controller="0" bus="1" target="0" unit="0"/>
<source file="tests/data/cli/fake-win7.iso"/>
</disk>
<controller type="ide" index="0"/>
<disk type="file" device="cdrom">
<source file="/VIRTINST-TESTSUITE/unattended.iso"/>
<target dev="hdd" bus="ide"/>
<readonly/>
</disk>
</devices>
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://microsoft.com/win/7"/>
</libosinfo:libosinfo>
</metadata>
</domain>
<domain type="test">
<name>test-cdrom</name>
<uuid>4a64cc71-aaaa-2fd0-2323-3050941ea3c3</uuid>
<memory unit="KiB">8388608</memory>
<currentMemory unit="KiB">2097152</currentMemory>
<vcpu placement="static">2</vcpu>
<os>
<type arch="i686">hvm</type>
<boot dev="hd"/>
</os>
<clock offset="utc"/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<devices>
<disk type="file" device="cdrom">
<target dev="hdc" bus="ide"/>
<readonly/>
<address type="drive" controller="0" bus="1" target="0" unit="0"/>
<source file="tests/data/cli/fake-win7.iso"/>
</disk>
<controller type="ide" index="0"/>
</devices>
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://microsoft.com/win/7"/>
</libosinfo:libosinfo>
</metadata>
</domain>

View File

@ -0,0 +1,100 @@
<domain type="test">
<name>test-clone-simple</name>
<uuid>12345678-1234-ffff-1234-12345678ffff</uuid>
<memory unit="KiB">409600</memory>
<currentMemory unit="KiB">204800</currentMemory>
<vcpu placement="static">5</vcpu>
<os>
<type arch="i686">hvm</type>
<loader type="rom">/usr/lib/xen/boot/hvmloader</loader>
<kernel>/VIRTINST-TESTSUITE/vmlinuz</kernel>
<initrd>/VIRTINST-TESTSUITE/initrd.img</initrd>
<cmdline>method=http://example.com</cmdline>
</os>
<features>
<acpi/>
<apic/>
</features>
<clock offset="utc"/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>destroy</on_reboot>
<on_crash>destroy</on_crash>
<devices>
<emulator>/usr/lib/xen/bin/qemu-dm</emulator>
<disk type="file" device="disk">
<driver type="qcow2"/>
<source file="/dev/default-pool/test-clone-simple.img"/>
<target dev="hda" bus="ide"/>
<address type="drive" controller="0" bus="0" target="0" unit="0"/>
</disk>
<controller type="ide" index="0"/>
<interface type="user">
<mac address="22:11:11:11:11:11"/>
<target dev="testnet0"/>
</interface>
<parallel type="file">
<source path="/tmp/foo.log"/>
<target port="0"/>
</parallel>
<graphics type="vnc" port="5912" autoport="no">
<listen type="address"/>
</graphics>
<video>
<model type="cirrus" vram="16384" heads="1" primary="yes"/>
</video>
</devices>
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://redhat.com/rhel/6.0"/>
</libosinfo:libosinfo>
</metadata>
</domain>
<domain type="test">
<name>test-clone-simple</name>
<uuid>12345678-1234-ffff-1234-12345678ffff</uuid>
<memory unit="KiB">409600</memory>
<currentMemory unit="KiB">204800</currentMemory>
<vcpu placement="static">5</vcpu>
<os>
<type arch="i686">hvm</type>
<loader type="rom">/usr/lib/xen/boot/hvmloader</loader>
<boot dev="hd"/>
</os>
<features>
<acpi/>
<apic/>
</features>
<clock offset="utc"/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<devices>
<emulator>/usr/lib/xen/bin/qemu-dm</emulator>
<disk type="file" device="disk">
<driver type="qcow2"/>
<source file="/dev/default-pool/test-clone-simple.img"/>
<target dev="hda" bus="ide"/>
<address type="drive" controller="0" bus="0" target="0" unit="0"/>
</disk>
<controller type="ide" index="0"/>
<interface type="user">
<mac address="22:11:11:11:11:11"/>
<target dev="testnet0"/>
</interface>
<parallel type="file">
<source path="/tmp/foo.log"/>
<target port="0"/>
</parallel>
<graphics type="vnc" port="5912" autoport="no">
<listen type="address"/>
</graphics>
<video>
<model type="cirrus" vram="16384" heads="1" primary="yes"/>
</video>
</devices>
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://redhat.com/rhel/6.0"/>
</libosinfo:libosinfo>
</metadata>
</domain>

View File

@ -0,0 +1,89 @@
<domain type="test">
<name>test-clone-simple</name>
<uuid>12345678-1234-ffff-1234-12345678ffff</uuid>
<memory unit="KiB">409600</memory>
<currentMemory unit="KiB">204800</currentMemory>
<vcpu placement="static">5</vcpu>
<os>
<type arch="i686">hvm</type>
<loader type="rom">/usr/lib/xen/boot/hvmloader</loader>
<boot dev="network"/>
<boot dev="hd"/>
</os>
<features>
<acpi/>
<apic/>
</features>
<clock offset="utc"/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>destroy</on_reboot>
<on_crash>destroy</on_crash>
<devices>
<emulator>/usr/lib/xen/bin/qemu-dm</emulator>
<disk type="file" device="disk">
<driver type="qcow2"/>
<source file="/dev/default-pool/test-clone-simple.img"/>
<target dev="hda" bus="ide"/>
<address type="drive" controller="0" bus="0" target="0" unit="0"/>
</disk>
<controller type="ide" index="0"/>
<interface type="user">
<mac address="22:11:11:11:11:11"/>
<target dev="testnet0"/>
</interface>
<parallel type="file">
<source path="/tmp/foo.log"/>
<target port="0"/>
</parallel>
<graphics type="vnc" port="5912" autoport="no">
<listen type="address"/>
</graphics>
<video>
<model type="cirrus" vram="16384" heads="1" primary="yes"/>
</video>
</devices>
</domain>
<domain type="test">
<name>test-clone-simple</name>
<uuid>12345678-1234-ffff-1234-12345678ffff</uuid>
<memory unit="KiB">409600</memory>
<currentMemory unit="KiB">204800</currentMemory>
<vcpu placement="static">5</vcpu>
<os>
<type arch="i686">hvm</type>
<loader type="rom">/usr/lib/xen/boot/hvmloader</loader>
<boot dev="hd"/>
</os>
<features>
<acpi/>
<apic/>
</features>
<clock offset="utc"/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<devices>
<emulator>/usr/lib/xen/bin/qemu-dm</emulator>
<disk type="file" device="disk">
<driver type="qcow2"/>
<source file="/dev/default-pool/test-clone-simple.img"/>
<target dev="hda" bus="ide"/>
<address type="drive" controller="0" bus="0" target="0" unit="0"/>
</disk>
<controller type="ide" index="0"/>
<interface type="user">
<mac address="22:11:11:11:11:11"/>
<target dev="testnet0"/>
</interface>
<parallel type="file">
<source path="/tmp/foo.log"/>
<target port="0"/>
</parallel>
<graphics type="vnc" port="5912" autoport="no">
<listen type="address"/>
</graphics>
<video>
<model type="cirrus" vram="16384" heads="1" primary="yes"/>
</video>
</devices>
</domain>

View File

@ -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
####################

View File

@ -37,6 +37,30 @@
</domain>
<domain type='test' xmlns:test='http://libvirt.org/schemas/domain/test/1.0'>
<name>test-cdrom</name>
<uuid>4a64cc71-aaaa-2fd0-2323-3050941ea3c3</uuid>
<memory>8388608</memory>
<currentMemory>2097152</currentMemory>
<test:runstate>5</test:runstate>
<vcpu>2</vcpu>
<os>
<type arch='i686'>hvm</type>
<boot dev='hd'/>
</os>
<clock offset='utc'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<devices>
<disk type="file" device="cdrom">
<target dev="hdc" bus="ide"/>
<readonly/>
</disk>
</devices>
</domain>
<domain type='test'>
<name>test-collide</name>
<currentMemory>204800</currentMemory>

View File

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

View File

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