2013-03-18 05:06:52 +08:00
|
|
|
#
|
2015-03-31 23:59:06 +08:00
|
|
|
# Copyright 2013, 2015 Red Hat, Inc.
|
2013-03-18 05:06:52 +08:00
|
|
|
# Copyright(c) FUJITSU Limited 2007.
|
|
|
|
#
|
|
|
|
# Cloning a virtual machine module.
|
|
|
|
#
|
2018-04-04 21:35:41 +08:00
|
|
|
# This work is licensed under the GNU GPLv2 or later.
|
2018-03-21 03:00:02 +08:00
|
|
|
# See the COPYING file in the top-level directory.
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
import re
|
|
|
|
import os
|
|
|
|
|
|
|
|
import libvirt
|
|
|
|
|
2019-06-08 06:16:53 +08:00
|
|
|
from . import generatename
|
2019-06-08 05:32:51 +08:00
|
|
|
from . import progress
|
2014-09-13 03:59:22 +08:00
|
|
|
from .guest import Guest
|
2018-03-21 00:27:37 +08:00
|
|
|
from .devices import DeviceInterface
|
|
|
|
from .devices import DeviceDisk
|
2019-06-17 09:12:39 +08:00
|
|
|
from .logger import log
|
2014-09-13 03:59:22 +08:00
|
|
|
from .storage import StorageVolume
|
2018-03-21 00:27:37 +08:00
|
|
|
from .devices import DeviceChannel
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-04-14 02:34:52 +08:00
|
|
|
|
2020-01-28 01:13:23 +08:00
|
|
|
def _replace_vm(conn, name):
|
|
|
|
"""
|
|
|
|
Remove the existing VM with the same name if requested
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
vm = conn.lookupByName(name)
|
|
|
|
except libvirt.libvirtError:
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
log.debug("Explicitly replacing guest '%s'", name)
|
|
|
|
if vm.ID() != -1:
|
|
|
|
log.debug("Destroying guest '%s'", name)
|
|
|
|
vm.destroy()
|
|
|
|
|
|
|
|
log.debug("Undefining guest '%s'", name)
|
|
|
|
vm.undefine()
|
|
|
|
except libvirt.libvirtError as e: # pragma: no cover
|
2020-07-12 05:31:40 +08:00
|
|
|
raise RuntimeError(
|
2020-07-14 15:41:49 +08:00
|
|
|
_("Could not remove old vm '%(vm)s': %(error)s") % {
|
|
|
|
"vm": name,
|
|
|
|
"error": str(e),
|
|
|
|
})
|
2020-01-28 01:13:23 +08:00
|
|
|
|
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def _generate_clone_name(conn, basename):
|
|
|
|
"""
|
|
|
|
If the orig name is "foo-clone", we don't want the clone to be
|
|
|
|
"foo-clone-clone", we want "foo-clone1"
|
|
|
|
"""
|
|
|
|
match = re.search("-clone[1-9]*$", basename)
|
|
|
|
start_num = 1
|
|
|
|
force_num = False
|
|
|
|
if match:
|
|
|
|
num_match = re.search("[1-9]+$", match.group())
|
|
|
|
if num_match:
|
|
|
|
start_num = int(str(num_match.group())) + 1
|
|
|
|
force_num = True
|
|
|
|
basename = basename.replace(match.group(), "")
|
|
|
|
|
|
|
|
def cb(n):
|
|
|
|
return generatename.check_libvirt_collision(
|
|
|
|
conn.lookupByName, n)
|
|
|
|
basename = basename + "-clone"
|
|
|
|
return generatename.generate_name(basename, cb,
|
|
|
|
sep="", start_num=start_num, force_num=force_num)
|
|
|
|
|
|
|
|
|
|
|
|
def _generate_clone_disk_path(conn, origname, newname, origpath):
|
|
|
|
"""
|
|
|
|
Generate desired cloned disk path name, derived from the
|
|
|
|
original path, original VM name, and proposed new VM name
|
|
|
|
"""
|
|
|
|
if origpath is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
path = origpath
|
|
|
|
suffix = ""
|
|
|
|
|
|
|
|
# Try to split the suffix off the existing disk name. Ex.
|
|
|
|
# foobar.img -> foobar-clone.img
|
|
|
|
#
|
|
|
|
# If the suffix is greater than 7 characters, assume it isn't
|
|
|
|
# a file extension and is part of the disk name, at which point
|
|
|
|
# just stick '-clone' on the end.
|
|
|
|
if "." in origpath and len(origpath.rsplit(".", 1)[1]) <= 7:
|
|
|
|
path, suffix = origpath.rsplit(".", 1)
|
|
|
|
suffix = "." + suffix
|
|
|
|
|
|
|
|
dirname = os.path.dirname(path)
|
|
|
|
basename = os.path.basename(path)
|
|
|
|
|
|
|
|
clonebase = basename + "-clone"
|
|
|
|
if origname and basename == origname:
|
|
|
|
clonebase = newname
|
|
|
|
|
|
|
|
clonebase = os.path.join(dirname, clonebase)
|
|
|
|
def cb(p):
|
|
|
|
return DeviceDisk.path_definitely_exists(conn, p)
|
|
|
|
return generatename.generate_name(clonebase, cb, suffix=suffix)
|
|
|
|
|
|
|
|
|
|
|
|
def _lookup_vm(conn, name):
|
|
|
|
try:
|
|
|
|
return conn.lookupByName(name)
|
|
|
|
except libvirt.libvirtError:
|
|
|
|
e = ValueError(_("Domain '%s' was not found.") % str(name))
|
|
|
|
raise e from None
|
|
|
|
|
|
|
|
|
|
|
|
def _build_clone_vol_install(orig_disk, clone_disk):
|
|
|
|
vol_install = DeviceDisk.build_vol_install(
|
|
|
|
orig_disk.conn, os.path.basename(clone_disk.path),
|
|
|
|
clone_disk.get_parent_pool(), .000001, False)
|
|
|
|
vol_install.input_vol = orig_disk.get_vol_object()
|
|
|
|
|
|
|
|
# Source and dest are managed. If they share the same pool,
|
|
|
|
# replace vol_install with a CloneVolume instance, otherwise
|
|
|
|
# simply set input_vol on the dest vol_install
|
|
|
|
if (vol_install.pool.name() ==
|
|
|
|
orig_disk.get_parent_pool().name()):
|
|
|
|
vol_install.sync_input_vol()
|
|
|
|
else:
|
|
|
|
# Cross pool cloning
|
|
|
|
# Sync only the format of the image.
|
|
|
|
vol_install.sync_input_vol(only_format=True)
|
|
|
|
|
|
|
|
return vol_install
|
|
|
|
|
|
|
|
|
|
|
|
def _build_clone_disk(orig_disk, clonepath, allow_create, sparse):
|
|
|
|
conn = orig_disk.conn
|
|
|
|
device = DeviceDisk.DEVICE_DISK
|
|
|
|
if not clonepath:
|
|
|
|
device = DeviceDisk.DEVICE_CDROM
|
|
|
|
|
|
|
|
clone_disk = DeviceDisk(conn)
|
|
|
|
clone_disk.path = clonepath
|
|
|
|
clone_disk.device = device
|
|
|
|
|
|
|
|
if not allow_create:
|
|
|
|
clone_disk.validate()
|
|
|
|
return clone_disk
|
|
|
|
|
|
|
|
if clone_disk.get_vol_object():
|
|
|
|
# Special case: non remote cloning of a guest using
|
|
|
|
# managed block devices: fall back to local cloning if
|
|
|
|
# we have permissions to do so. This validation check
|
|
|
|
# caused a few bug reports in a short period of time,
|
|
|
|
# so must be a common case.
|
|
|
|
if (conn.is_remote() or
|
|
|
|
clone_disk.type != clone_disk.TYPE_BLOCK or
|
|
|
|
not orig_disk.path or
|
|
|
|
not os.access(orig_disk.path, os.R_OK) or
|
|
|
|
not clone_disk.path or
|
|
|
|
not os.access(clone_disk.path, os.W_OK)):
|
|
|
|
raise RuntimeError(
|
|
|
|
_("Clone onto existing storage volume is not "
|
|
|
|
"currently supported: '%s'") % clone_disk.path)
|
|
|
|
|
|
|
|
if (orig_disk.get_vol_object() and
|
|
|
|
clone_disk.wants_storage_creation()):
|
|
|
|
vol_install = _build_clone_vol_install(orig_disk, clone_disk)
|
|
|
|
if not sparse:
|
|
|
|
vol_install.allocation = vol_install.capacity
|
|
|
|
clone_disk.set_vol_install(vol_install)
|
|
|
|
elif orig_disk.path:
|
|
|
|
clone_disk.set_local_disk_to_clone(orig_disk, sparse)
|
|
|
|
|
|
|
|
clone_disk.validate()
|
|
|
|
return clone_disk
|
|
|
|
|
|
|
|
|
|
|
|
class _CloneDiskInfo:
|
|
|
|
"""
|
|
|
|
Class that tracks some additional information about how we want
|
|
|
|
to default handle each disk of the source VM
|
|
|
|
"""
|
|
|
|
def __init__(self, srcdisk):
|
|
|
|
self.disk = DeviceDisk(srcdisk.conn, parsexml=srcdisk.get_xml())
|
|
|
|
self._do_clone = self._do_we_clone_default()
|
|
|
|
self.clone_disk = None
|
|
|
|
|
|
|
|
def is_clone_requested(self):
|
|
|
|
return self._do_clone
|
|
|
|
def set_clone_requested(self, val):
|
|
|
|
self._do_clone = val
|
|
|
|
|
|
|
|
def _do_we_clone_default(self):
|
|
|
|
if not self.disk.path:
|
|
|
|
return False
|
|
|
|
if self.disk.read_only:
|
|
|
|
return False
|
|
|
|
if self.disk.shareable:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def check_clonable(self):
|
|
|
|
try:
|
|
|
|
# This forces DeviceDisk to resolve the storage backend
|
|
|
|
self.disk.path = self.disk.path
|
|
|
|
if self.disk.wants_storage_creation():
|
|
|
|
raise ValueError(
|
|
|
|
_("Disk path '%s' does not exist.") % self.disk.path)
|
|
|
|
except Exception as e:
|
|
|
|
log.debug("Exception processing clone original path", exc_info=True)
|
|
|
|
err = _("Could not determine original disk information: %s" % str(e))
|
|
|
|
raise ValueError(err) from None
|
|
|
|
|
|
|
|
def set_clone_path(self, path, allow_create, sparse):
|
|
|
|
if allow_create:
|
|
|
|
self.check_clonable()
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.clone_disk = _build_clone_disk(
|
|
|
|
self.disk, path, allow_create, sparse)
|
|
|
|
except Exception as e:
|
|
|
|
log.debug("Error setting clone path.", exc_info=True)
|
|
|
|
raise ValueError(
|
|
|
|
_("Could not use path '%(path)s' for cloning: %(error)s") % {
|
|
|
|
"path": path,
|
|
|
|
"error": str(e),
|
|
|
|
})
|
|
|
|
|
|
|
|
|
2013-07-03 06:30:46 +08:00
|
|
|
class Cloner(object):
|
2020-09-04 00:03:38 +08:00
|
|
|
@staticmethod
|
|
|
|
def generate_clone_name(conn, basename):
|
|
|
|
return _generate_clone_name(conn, basename)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
@staticmethod
|
|
|
|
def generate_clone_disk_path(conn, origname, newname, origpath):
|
|
|
|
return _generate_clone_disk_path(conn, origname, newname, origpath)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def __init__(self, conn, src_name=None, src_xml=None):
|
2013-07-03 06:30:46 +08:00
|
|
|
self.conn = conn
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
self._src_guest = None
|
|
|
|
self._new_guest = None
|
|
|
|
self._diskinfos = []
|
|
|
|
self._init_src(src_name, src_xml)
|
|
|
|
|
|
|
|
self._new_nvram_path = None
|
2017-03-06 16:43:10 +08:00
|
|
|
self._nvram_disk = None
|
2013-07-03 06:30:46 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
self._sparse = True
|
|
|
|
self._overwrite = True
|
2013-08-10 02:16:59 +08:00
|
|
|
self._replace = False
|
2015-02-07 10:18:05 +08:00
|
|
|
self._reflink = False
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
#################
|
|
|
|
# Init routines #
|
|
|
|
#################
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def _init_src(self, src_name, src_xml):
|
|
|
|
"""
|
|
|
|
Set up the source VM info we are cloning, from passed in VM name
|
|
|
|
or full XML
|
|
|
|
"""
|
|
|
|
if not src_xml:
|
|
|
|
dom = _lookup_vm(self.conn, src_name)
|
|
|
|
status = dom.info()[0]
|
|
|
|
if status not in [libvirt.VIR_DOMAIN_SHUTOFF]:
|
|
|
|
raise RuntimeError(_("Domain to clone must be shutoff."))
|
|
|
|
flags = libvirt.VIR_DOMAIN_XML_SECURE
|
|
|
|
src_xml = dom.XMLDesc(flags)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
log.debug("Original XML:\n%s", src_xml)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
self._src_guest = Guest(self.conn, parsexml=src_xml)
|
|
|
|
self._new_guest = Guest(self.conn, parsexml=src_xml)
|
|
|
|
self._init_new_guest()
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
# Collect disk info for every disk to determine if we will
|
|
|
|
# default to cloning or not
|
|
|
|
for disk in self._src_guest.devices.disk:
|
|
|
|
self._diskinfos.append(_CloneDiskInfo(disk))
|
|
|
|
for diskinfo in [d for d in self._diskinfos if d.is_clone_requested()]:
|
|
|
|
disk = diskinfo.disk
|
|
|
|
log.debug("Wants cloning: size=%s path=%s",
|
|
|
|
disk.get_size(), disk.path)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def _init_new_guest(self):
|
|
|
|
"""
|
|
|
|
Perform the series of unconditional new VM changes we always make
|
|
|
|
"""
|
|
|
|
self._new_guest.id = None
|
|
|
|
self._new_guest.title = None
|
|
|
|
self._new_guest.uuid = None
|
|
|
|
self._new_guest.uuid = Guest.generate_uuid(self.conn)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
for dev in self._new_guest.devices.graphics:
|
|
|
|
if dev.port and dev.port != -1:
|
|
|
|
log.warning(_("Setting the graphics device port to autoport, "
|
|
|
|
"in order to avoid conflicting."))
|
|
|
|
dev.port = -1
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
for iface in self._new_guest.devices.interface:
|
|
|
|
iface.target_dev = None
|
|
|
|
iface.macaddr = DeviceInterface.generate_mac(self.conn)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
# For guest agent channel, remove a path to generate a new one with
|
|
|
|
# new guest name
|
|
|
|
for channel in self._new_guest.devices.channel:
|
|
|
|
if (channel.type == DeviceChannel.TYPE_UNIX and
|
|
|
|
channel.target_name and channel.source.path and
|
|
|
|
channel.target_name in channel.source.path):
|
|
|
|
channel.source.path = None
|
2013-07-03 06:30:46 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
new_name = Cloner.generate_clone_name(self.conn, self.src_name)
|
|
|
|
log.debug("Auto-generated clone name '%s'", new_name)
|
|
|
|
self.set_clone_name(new_name)
|
2018-02-23 09:44:09 +08:00
|
|
|
|
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
##############
|
|
|
|
# Properties #
|
|
|
|
##############
|
2015-02-07 10:18:05 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
@property
|
|
|
|
def src_name(self):
|
|
|
|
"""
|
|
|
|
The name of the original VM we are cloning
|
|
|
|
"""
|
|
|
|
return self._src_guest.name
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
@property
|
|
|
|
def new_guest(self):
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
2020-09-04 00:03:38 +08:00
|
|
|
The Guest instance of the new XML we will create
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
2020-09-04 00:03:38 +08:00
|
|
|
return self._new_guest
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def set_clone_name(self, name):
|
|
|
|
self._new_guest.name = name
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def set_clone_uuid(self, uuid):
|
|
|
|
"""
|
|
|
|
Override the new VMs generated UUId
|
|
|
|
"""
|
|
|
|
self._new_guest.uuid = uuid
|
|
|
|
|
|
|
|
def set_replace(self, val):
|
|
|
|
"""
|
|
|
|
If True, don't check for clone name collision, simply undefine
|
|
|
|
any conflicting guest.
|
|
|
|
"""
|
|
|
|
self._replace = bool(val)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def set_reflink(self, reflink):
|
|
|
|
"""
|
|
|
|
If true, use COW lightweight copy
|
|
|
|
"""
|
|
|
|
self._reflink = reflink
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def set_sparse(self, flg):
|
|
|
|
"""
|
|
|
|
If True, attempt sparse allocation during cloning
|
|
|
|
"""
|
|
|
|
self._sparse = flg
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def get_diskinfos(self):
|
|
|
|
"""
|
|
|
|
Return the list of _CloneDiskInfo instances
|
|
|
|
"""
|
|
|
|
return self._diskinfos[:]
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def get_diskinfos_to_clone(self):
|
|
|
|
"""
|
|
|
|
Return a list of _CloneDiskInfo that are tagged for cloning
|
|
|
|
"""
|
|
|
|
return [di for di in self.get_diskinfos() if di.is_clone_requested()]
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def set_nvram_path(self, val):
|
|
|
|
"""
|
|
|
|
If the VM needs to have nvram content cloned, this overrides the
|
|
|
|
destination path
|
|
|
|
"""
|
|
|
|
self._new_nvram_path = val
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def set_overwrite(self, flg):
|
2013-04-24 22:47:56 +08:00
|
|
|
"""
|
2020-09-04 00:03:38 +08:00
|
|
|
If False, no data is copied to the destination disks by default.
|
|
|
|
Storage may be created, but it is empty.
|
2013-04-24 22:47:56 +08:00
|
|
|
"""
|
2020-09-04 00:03:38 +08:00
|
|
|
self._overwrite = flg
|
2014-12-06 10:09:26 +08:00
|
|
|
|
2013-04-24 22:47:56 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
######################
|
|
|
|
# Functional methods #
|
|
|
|
######################
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2017-03-06 16:43:10 +08:00
|
|
|
def _prepare_nvram(self):
|
2020-09-04 00:03:38 +08:00
|
|
|
new_nvram_path = self._new_nvram_path
|
|
|
|
if new_nvram_path is None:
|
|
|
|
nvram_dir = os.path.dirname(self._new_guest.os.nvram)
|
|
|
|
new_nvram_path = os.path.join(
|
|
|
|
nvram_dir, "%s_VARS.fd" % self._new_guest.name)
|
2017-03-06 16:43:10 +08:00
|
|
|
|
2019-03-28 21:27:04 +08:00
|
|
|
old_nvram = DeviceDisk(self.conn)
|
2020-09-04 00:03:38 +08:00
|
|
|
old_nvram.path = self._new_guest.os.nvram
|
2018-03-21 00:18:35 +08:00
|
|
|
nvram = DeviceDisk(self.conn)
|
2020-09-04 00:03:38 +08:00
|
|
|
nvram.path = new_nvram_path
|
|
|
|
diskinfo = _CloneDiskInfo(old_nvram)
|
|
|
|
allow_create = self._overwrite
|
2017-03-06 16:43:10 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
if (allow_create and
|
2019-03-28 21:27:04 +08:00
|
|
|
nvram.wants_storage_creation() and
|
|
|
|
old_nvram.get_vol_object()):
|
2020-09-04 00:03:38 +08:00
|
|
|
# We only run validation if there's some existing nvram we
|
|
|
|
# can copy. It's valid for nvram to not exist at VM define
|
|
|
|
# time, libvirt will create it for us
|
|
|
|
diskinfo.set_clone_path(new_nvram_path, allow_create, self._sparse)
|
|
|
|
self._nvram_disk = diskinfo.clone_disk
|
|
|
|
self._nvram_disk.get_vol_install().reflink = self._reflink
|
2017-03-06 16:43:10 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
self._new_guest.os.nvram = nvram.path
|
2017-03-06 16:43:10 +08:00
|
|
|
|
2019-03-28 21:27:04 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
def prepare(self):
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
|
|
|
Validate and set up all parameters needed for the new (clone) VM
|
|
|
|
"""
|
2020-09-04 00:03:38 +08:00
|
|
|
try:
|
|
|
|
Guest.validate_name(self.conn, self._new_guest.name,
|
|
|
|
check_collision=not self._replace,
|
|
|
|
validate=False)
|
|
|
|
except ValueError as e:
|
|
|
|
raise ValueError(_("Invalid name for new guest: %s") % e)
|
2020-01-30 04:31:21 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
for diskinfo in self.get_diskinfos_to_clone():
|
|
|
|
orig_disk = diskinfo.disk
|
2014-07-21 19:54:38 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
if not diskinfo.clone_disk:
|
|
|
|
# User didn't set a path, generate one
|
|
|
|
newpath = Cloner.generate_clone_disk_path(
|
|
|
|
self.conn, self.src_name,
|
|
|
|
self.new_guest.name,
|
|
|
|
orig_disk.path)
|
|
|
|
diskinfo.set_clone_path(newpath,
|
|
|
|
self._overwrite, self._sparse)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
clone_disk = diskinfo.clone_disk
|
|
|
|
assert clone_disk
|
|
|
|
log.debug("Cloning srcpath=%s dstpath=%s",
|
|
|
|
orig_disk.path, clone_disk.path)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
if self._reflink:
|
|
|
|
vol_install = clone_disk.get_vol_install()
|
|
|
|
vol_install.reflink = self._reflink
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
for disk in self._new_guest.devices.disk:
|
2013-03-18 05:06:52 +08:00
|
|
|
if disk.target == orig_disk.target:
|
|
|
|
xmldisk = disk
|
|
|
|
|
|
|
|
# Change the XML
|
|
|
|
xmldisk.path = None
|
|
|
|
xmldisk.type = clone_disk.type
|
2013-07-13 22:09:00 +08:00
|
|
|
xmldisk.driver_name = orig_disk.driver_name
|
2013-03-18 05:06:52 +08:00
|
|
|
xmldisk.driver_type = orig_disk.driver_type
|
2013-07-13 22:09:00 +08:00
|
|
|
xmldisk.path = clone_disk.path
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
if self._new_guest.os.nvram:
|
2017-03-06 16:43:10 +08:00
|
|
|
self._prepare_nvram()
|
|
|
|
|
2013-03-18 05:06:52 +08:00
|
|
|
# Save altered clone xml
|
2020-09-04 00:03:38 +08:00
|
|
|
log.debug("Clone guest xml is\n%s", self._new_guest.get_xml())
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-07-03 06:30:46 +08:00
|
|
|
def start_duplicate(self, meter=None):
|
|
|
|
"""
|
|
|
|
Actually perform the duplication: cloning disks if needed and defining
|
|
|
|
the new clone xml.
|
|
|
|
"""
|
2019-06-17 09:12:39 +08:00
|
|
|
log.debug("Starting duplicate.")
|
2019-06-08 05:32:51 +08:00
|
|
|
meter = progress.ensure_meter(meter)
|
2013-07-03 06:30:46 +08:00
|
|
|
|
|
|
|
dom = None
|
|
|
|
try:
|
|
|
|
# Replace orig VM if required
|
2020-09-04 00:03:38 +08:00
|
|
|
if self._replace:
|
|
|
|
_replace_vm(self.conn, self._new_guest.name)
|
2013-07-03 06:30:46 +08:00
|
|
|
|
|
|
|
# Define domain early to catch any xml errors before duping storage
|
2020-09-04 00:03:38 +08:00
|
|
|
dom = self.conn.defineXML(self._new_guest.get_xml())
|
2013-07-03 06:30:46 +08:00
|
|
|
|
2020-09-04 00:03:38 +08:00
|
|
|
if self._overwrite:
|
|
|
|
diskinfos = self.get_diskinfos_to_clone()
|
|
|
|
for dst_dev in [d.clone_disk for d in diskinfos]:
|
2018-09-04 04:44:38 +08:00
|
|
|
dst_dev.build_storage(meter)
|
2017-03-06 16:43:10 +08:00
|
|
|
if self._nvram_disk:
|
2018-09-04 04:44:38 +08:00
|
|
|
self._nvram_disk.build_storage(meter)
|
2017-05-06 00:47:21 +08:00
|
|
|
except Exception as e:
|
2019-06-17 09:12:39 +08:00
|
|
|
log.debug("Duplicate failed: %s", str(e))
|
2013-07-03 06:30:46 +08:00
|
|
|
if dom:
|
|
|
|
dom.undefine()
|
|
|
|
raise
|
|
|
|
|
2019-06-17 09:12:39 +08:00
|
|
|
log.debug("Duplicating finished.")
|