2013-03-18 01:06:52 +04:00
#
2015-03-31 18:59:06 +03:00
# Copyright 2013, 2015 Red Hat, Inc.
2013-03-18 01:06:52 +04:00
# Copyright(c) FUJITSU Limited 2007.
#
# Cloning a virtual machine module.
#
2018-04-04 16:35:41 +03:00
# This work is licensed under the GNU GPLv2 or later.
2018-03-20 22:00:02 +03:00
# See the COPYING file in the top-level directory.
2013-03-18 01:06:52 +04:00
import re
import os
2022-11-30 19:01:08 +03:00
from itertools import chain
2013-03-18 01:06:52 +04:00
import libvirt
2019-06-08 01:16:53 +03:00
from . import generatename
2019-06-08 00:32:51 +03:00
from . import progress
2020-09-05 15:31:43 +03:00
from . import xmlutil
2014-09-12 23:59:22 +04:00
from . guest import Guest
2018-03-20 19:27:37 +03:00
from . devices import DeviceInterface
from . devices import DeviceDisk
2019-06-17 04:12:39 +03:00
from . logger import log
2018-03-20 19:27:37 +03:00
from . devices import DeviceChannel
2022-11-30 19:01:08 +03:00
from . devices import DeviceSerial
2013-03-18 01:06:52 +04:00
2013-04-13 22:34:52 +04:00
2020-01-27 20:13:23 +03: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-09-15 19:33:31 +03:00
msg = ( _ ( " Could not remove old vm ' %(vm)s ' : %(error)s " ) % {
" vm " : name , " error " : str ( e ) } )
raise RuntimeError ( msg ) from None
2020-01-27 20:13:23 +03:00
2020-09-03 19:03:38 +03: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 "
"""
2020-09-05 15:51:31 +03:00
regex = r " -clone[1-9]*$ "
match = re . search ( regex , basename )
2020-09-03 19:03:38 +03:00
start_num = 1
force_num = False
if match :
num_match = re . search ( " [1-9]+$ " , match . group ( ) )
2020-09-05 15:51:31 +03:00
force_num = True
2020-09-03 19:03:38 +03:00
if num_match :
start_num = int ( str ( num_match . group ( ) ) ) + 1
2020-09-05 15:51:31 +03:00
basename = basename [ : match . start ( ) ]
2020-09-03 19:03:38 +03:00
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 )
2022-11-30 19:01:08 +03:00
def _generate_clone_path ( origname , newname , origpath , cb_exists ) :
2020-09-03 19:03:38 +03:00
"""
2022-11-30 19:01:08 +03:00
Generate desired cloned path for auxiliary files , derived from the
2020-09-03 19:03:38 +03:00
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 )
2022-11-30 19:01:08 +03:00
return generatename . generate_name ( clonebase , cb_exists , suffix = suffix )
2020-09-03 19:03:38 +03:00
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
2020-09-04 20:54:54 +03:00
def _build_clone_vol_install ( orig_disk , new_disk ) :
2020-09-04 17:41:17 +03:00
# We set a stub size for initial creation
# set_input_vol will overwrite it
size = .000001
sparse = False
2020-09-03 19:03:38 +03:00
vol_install = DeviceDisk . build_vol_install (
2020-11-11 23:38:34 +03:00
orig_disk . conn , os . path . basename ( new_disk . get_source_path ( ) ) ,
2020-09-04 20:54:54 +03:00
new_disk . get_parent_pool ( ) , size , sparse )
2020-09-04 17:41:17 +03:00
vol_install . set_input_vol ( orig_disk . get_vol_object ( ) )
2020-09-03 19:03:38 +03:00
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
2020-09-04 20:54:54 +03:00
new_disk = DeviceDisk ( conn )
2020-11-11 23:38:34 +03:00
new_disk . set_source_path ( clonepath )
2020-09-04 20:54:54 +03:00
new_disk . device = device
2020-09-03 19:03:38 +03:00
if not allow_create :
2020-09-04 20:54:54 +03:00
new_disk . validate ( )
return new_disk
2020-09-03 19:03:38 +03:00
2020-09-04 20:54:54 +03:00
if new_disk . get_vol_object ( ) :
2020-09-03 19:03:38 +03:00
# 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
2020-09-04 20:54:54 +03:00
new_disk . type != new_disk . TYPE_BLOCK or
2020-11-11 23:38:34 +03:00
not orig_disk . get_source_path ( ) or
not os . access ( orig_disk . get_source_path ( ) , os . R_OK ) or
not new_disk . get_source_path ( ) or
not os . access ( new_disk . get_source_path ( ) , os . W_OK ) ) :
2020-09-03 19:03:38 +03:00
raise RuntimeError (
_ ( " Clone onto existing storage volume is not "
2020-11-11 23:38:34 +03:00
" currently supported: ' %s ' " ) % new_disk . get_source_path ( ) )
2020-09-03 19:03:38 +03:00
if ( orig_disk . get_vol_object ( ) and
2020-09-04 20:54:54 +03:00
new_disk . wants_storage_creation ( ) ) :
vol_install = _build_clone_vol_install ( orig_disk , new_disk )
2020-09-03 19:03:38 +03:00
if not sparse :
vol_install . allocation = vol_install . capacity
2020-09-04 20:54:54 +03:00
new_disk . set_vol_install ( vol_install )
2020-11-11 23:38:34 +03:00
elif orig_disk . get_source_path ( ) :
2020-09-04 20:54:54 +03:00
new_disk . set_local_disk_to_clone ( orig_disk , sparse )
2020-09-03 19:03:38 +03:00
2020-09-04 20:54:54 +03:00
new_disk . validate ( )
return new_disk
2020-09-03 19:03:38 +03:00
2020-09-04 22:18:26 +03:00
def _get_cloneable_msg ( disk ) :
2020-09-04 21:13:52 +03:00
"""
2020-09-04 22:18:26 +03:00
If the disk storage is not cloneable , return a string explaining why
2020-09-04 21:13:52 +03:00
"""
if disk . wants_storage_creation ( ) :
2020-11-11 23:38:34 +03:00
return _ ( " Disk path ' %s ' does not exist. " ) % disk . get_source_path ( )
2020-11-11 00:10:35 +03:00
if disk . type == " network " :
proto = disk . source . protocol
2022-01-27 00:39:47 +03:00
if proto == " rbd " :
2020-11-11 00:10:35 +03:00
# This case, rbd with managed storage, is implementable. It
# requires open coding a bunch of work in cloner, or reworking
# other disk code to add unique URIs for rbd volumes and pools
2022-01-27 00:39:47 +03:00
return (
_ ( " Cloning rbd volumes is not yet supported. " ) +
" https://github.com/virt-manager/virt-manager/issues/177 " )
return _ ( " Disk network type ' %s ' is not cloneable. " ) % proto
2020-09-04 21:13:52 +03:00
def _get_shareable_msg ( disk ) :
2020-11-11 23:38:34 +03:00
if disk . is_empty ( ) :
2020-09-04 21:13:52 +03:00
return _ ( " No storage to clone. " )
if disk . read_only :
return _ ( " Read Only " )
2021-06-17 11:40:07 +03:00
if disk . shareable or disk . transient_shareBacking :
2020-09-04 21:13:52 +03:00
return _ ( " Marked as shareable " )
2020-09-03 19:03:38 +03:00
class _CloneDiskInfo :
"""
Class that tracks some additional information about how we want
to default handle each disk of the source VM
2020-09-04 21:13:52 +03:00
For any source disk there ' s 3 main scenarios:
* clone : Copy contents from src to dst . If dst path doesn ' t
exist we attempt to create it . If it exists we overwrite it
* preserve : Destination path is an existing , and no copying is performed .
* share : Original disk XML is used unchanged for the new disk
2020-09-03 19:03:38 +03:00
"""
2020-09-04 21:13:52 +03:00
_ACTION_SHARE = 1
_ACTION_CLONE = 2
_ACTION_PRESERVE = 3
2020-09-03 19:03:38 +03:00
def __init__ ( self , srcdisk ) :
self . disk = DeviceDisk ( srcdisk . conn , parsexml = srcdisk . get_xml ( ) )
2020-09-04 21:13:52 +03:00
self . disk . set_backend_for_existing_path ( )
2020-09-04 20:54:54 +03:00
self . new_disk = None
2020-09-03 19:03:38 +03:00
2020-09-04 22:34:13 +03:00
self . _share_msg = _get_shareable_msg ( self . disk )
self . _cloneable_msg = - 1
self . _newpath_msg = None
2020-09-04 21:13:52 +03:00
self . _action = None
self . set_clone_requested ( )
2020-09-04 22:34:13 +03:00
if self . get_share_msg ( ) :
2020-09-04 21:13:52 +03:00
self . set_share_requested ( )
2020-09-03 19:03:38 +03:00
def is_clone_requested ( self ) :
2020-09-04 21:13:52 +03:00
return self . _action in [ self . _ACTION_CLONE ]
def is_share_requested ( self ) :
return self . _action in [ self . _ACTION_SHARE ]
def is_preserve_requested ( self ) :
return self . _action in [ self . _ACTION_PRESERVE ]
2020-09-04 22:34:13 +03:00
def _set_action ( self , action ) :
if action != self . _action :
self . _action = action
2020-09-04 21:13:52 +03:00
def set_clone_requested ( self ) :
2020-09-04 22:34:13 +03:00
self . _set_action ( self . _ACTION_CLONE )
2020-09-04 21:13:52 +03:00
def set_share_requested ( self ) :
2020-09-04 22:34:13 +03:00
self . _set_action ( self . _ACTION_SHARE )
2020-09-04 21:13:52 +03:00
def set_preserve_requested ( self ) :
2020-09-04 22:34:13 +03:00
self . _set_action ( self . _ACTION_PRESERVE )
2020-09-03 19:03:38 +03:00
2020-09-04 21:13:52 +03:00
def set_new_path ( self , path , sparse ) :
allow_create = not self . is_preserve_requested ( )
2020-09-03 19:03:38 +03:00
if allow_create :
2020-09-04 22:34:13 +03:00
msg = self . get_cloneable_msg ( )
if msg :
return
2020-09-03 19:03:38 +03:00
try :
2020-09-04 20:54:54 +03:00
self . new_disk = Cloner . build_clone_disk (
2020-09-03 19:03:38 +03:00
self . disk , path , allow_create , sparse )
except Exception as e :
log . debug ( " Error setting clone path. " , exc_info = True )
2020-09-04 22:34:13 +03:00
err = ( _ ( " Could not use path ' %(path)s ' for cloning: %(error)s " ) %
{ " path " : path , " error " : str ( e ) } )
self . _newpath_msg = err
def get_share_msg ( self ) :
return self . _share_msg
def get_cloneable_msg ( self ) :
if self . _cloneable_msg == - 1 :
self . _cloneable_msg = _get_cloneable_msg ( self . disk )
return self . _cloneable_msg
def get_newpath_msg ( self ) :
return self . _newpath_msg
def raise_error ( self ) :
if self . is_clone_requested ( ) and self . get_cloneable_msg ( ) :
msg = self . get_cloneable_msg ( )
err = _ ( " Could not determine original disk information: %s " % msg )
raise ValueError ( err )
if self . is_share_requested ( ) :
return
if self . get_newpath_msg ( ) :
msg = self . get_newpath_msg ( )
raise ValueError ( msg )
2020-09-03 19:03:38 +03:00
2013-07-03 02:30:46 +04:00
class Cloner ( object ) :
2020-09-03 19:03:38 +03:00
@staticmethod
def generate_clone_name ( conn , basename ) :
return _generate_clone_name ( conn , basename )
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03:00
@staticmethod
def generate_clone_disk_path ( conn , origname , newname , origpath ) :
2022-11-30 19:01:08 +03:00
def cb_exists ( p ) :
return DeviceDisk . path_definitely_exists ( conn , p )
return _generate_clone_path ( origname , newname , origpath , cb_exists )
2013-03-18 01:06:52 +04:00
2020-09-04 17:41:17 +03:00
@staticmethod
def build_clone_disk ( orig_disk , clonepath , allow_create , sparse ) :
return _build_clone_disk ( orig_disk , clonepath , allow_create , sparse )
2020-09-03 19:03:38 +03:00
def __init__ ( self , conn , src_name = None , src_xml = None ) :
2013-07-03 02:30:46 +04:00
self . conn = conn
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03:00
self . _src_guest = None
self . _new_guest = None
self . _diskinfos = [ ]
2020-09-04 21:13:52 +03:00
self . _nvram_diskinfo = None
2020-09-03 19:03:38 +03:00
self . _init_src ( src_name , src_xml )
self . _new_nvram_path = None
2013-07-03 02:30:46 +04:00
2020-09-03 19:03:38 +03:00
self . _sparse = True
2013-08-09 22:16:59 +04:00
self . _replace = False
2015-02-07 05:18:05 +03:00
self . _reflink = False
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03:00
#################
# Init routines #
#################
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03: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 01:06:52 +04:00
2020-09-03 19:03:38 +03:00
log . debug ( " Original XML: \n %s " , src_xml )
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03: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 01:06:52 +04:00
2020-09-03 19:03:38 +03: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 " ,
2020-11-11 23:38:34 +03:00
disk . get_size ( ) , disk . get_source_path ( ) )
2013-03-18 01:06:52 +04:00
2020-09-04 21:13:52 +03:00
if self . _src_guest . os . nvram :
old_nvram = DeviceDisk ( self . conn )
2020-11-11 23:38:34 +03:00
old_nvram . set_source_path ( self . _new_guest . os . nvram )
2020-09-04 21:13:52 +03:00
self . _nvram_diskinfo = _CloneDiskInfo ( old_nvram )
2020-09-03 19:03:38 +03: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
2022-08-21 23:21:10 +03:00
self . set_clone_uuid ( Guest . generate_uuid ( self . conn ) )
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03: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 01:06:52 +04:00
2020-09-03 19:03:38 +03:00
for iface in self . _new_guest . devices . interface :
iface . target_dev = None
iface . macaddr = DeviceInterface . generate_mac ( self . conn )
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03: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 02:30:46 +04:00
2020-09-03 19:03:38 +03: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 04:44:09 +03:00
2020-09-03 19:03:38 +03:00
##############
# Properties #
##############
2015-02-07 05:18:05 +03:00
2020-09-03 19:03:38 +03:00
@property
def src_name ( self ) :
"""
The name of the original VM we are cloning
"""
return self . _src_guest . name
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03:00
@property
def new_guest ( self ) :
2013-03-18 01:06:52 +04:00
"""
2020-09-03 19:03:38 +03:00
The Guest instance of the new XML we will create
2013-03-18 01:06:52 +04:00
"""
2020-09-03 19:03:38 +03:00
return self . _new_guest
2013-03-18 01:06:52 +04:00
2020-09-04 21:13:52 +03:00
@property
def nvram_diskinfo ( self ) :
return self . _nvram_diskinfo
2020-09-03 19:03:38 +03:00
def set_clone_name ( self , name ) :
self . _new_guest . name = name
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03:00
def set_clone_uuid ( self , uuid ) :
"""
Override the new VMs generated UUId
"""
self . _new_guest . uuid = uuid
2022-08-21 23:21:10 +03:00
for sysinfo in self . _new_guest . sysinfo :
if sysinfo . system_uuid :
sysinfo . system_uuid = uuid
2020-09-03 19:03:38 +03:00
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 01:06:52 +04:00
2020-09-03 19:03:38 +03:00
def set_reflink ( self , reflink ) :
"""
If true , use COW lightweight copy
"""
self . _reflink = reflink
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03:00
def set_sparse ( self , flg ) :
"""
If True , attempt sparse allocation during cloning
"""
self . _sparse = flg
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03:00
def get_diskinfos ( self ) :
"""
Return the list of _CloneDiskInfo instances
"""
return self . _diskinfos [ : ]
2013-03-18 01:06:52 +04:00
2020-09-04 21:13:52 +03:00
def get_nonshare_diskinfos ( self ) :
2020-09-03 19:03:38 +03:00
"""
Return a list of _CloneDiskInfo that are tagged for cloning
"""
2020-09-04 21:13:52 +03:00
return [ di for di in self . get_diskinfos ( ) if
not di . is_share_requested ( ) ]
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03: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 01:06:52 +04:00
2013-04-24 18:47:56 +04:00
2020-09-03 19:03:38 +03:00
######################
# Functional methods #
######################
2013-03-18 01:06:52 +04:00
2022-11-30 19:01:08 +03:00
def _prepare_serial_files ( self ) :
for serial in chain ( self . _new_guest . devices . console ,
self . _new_guest . devices . serial ) :
if serial . type != DeviceSerial . TYPE_FILE :
continue
def cb_exists ( path ) :
# Ignore the check for now
return False
serial . source . path = _generate_clone_path ( self . src_name ,
self . new_guest . name ,
serial . source . path ,
cb_exists = cb_exists )
2017-03-06 11:43:10 +03:00
def _prepare_nvram ( self ) :
2020-09-04 21:13:52 +03:00
if not self . _nvram_diskinfo :
return
2020-09-03 19:03:38 +03: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 11:43:10 +03:00
2020-09-04 21:13:52 +03:00
diskinfo = self . _nvram_diskinfo
new_nvram = DeviceDisk ( self . conn )
2020-11-11 23:38:34 +03:00
new_nvram . set_source_path ( new_nvram_path )
2019-03-28 16:27:04 +03:00
old_nvram = DeviceDisk ( self . conn )
2020-11-11 23:38:34 +03:00
old_nvram . set_source_path ( diskinfo . disk . get_source_path ( ) )
2020-09-04 21:13:52 +03:00
if ( diskinfo . is_clone_requested ( ) and
new_nvram . wants_storage_creation ( ) and
diskinfo . disk . get_vol_object ( ) ) :
2020-09-03 19:03:38 +03: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
2020-09-04 21:13:52 +03:00
diskinfo . set_new_path ( new_nvram_path , self . _sparse )
2020-09-04 22:34:13 +03:00
diskinfo . raise_error ( )
2020-09-04 21:13:52 +03:00
diskinfo . new_disk . get_vol_install ( ) . reflink = self . _reflink
else :
# There's no action to perform for this case, so drop it
self . _nvram_diskinfo = None
2017-03-06 11:43:10 +03:00
2020-11-11 23:38:34 +03:00
self . _new_guest . os . nvram = new_nvram . get_source_path ( )
2017-03-06 11:43:10 +03:00
2019-03-28 16:27:04 +03:00
2020-09-03 19:03:38 +03:00
def prepare ( self ) :
2013-03-18 01:06:52 +04:00
"""
Validate and set up all parameters needed for the new ( clone ) VM
"""
2020-09-03 19:03:38 +03:00
try :
Guest . validate_name ( self . conn , self . _new_guest . name ,
check_collision = not self . _replace ,
validate = False )
except ValueError as e :
2020-09-15 19:33:31 +03:00
msg = _ ( " Invalid name for new guest: %s " ) % e
raise ValueError ( msg ) from None
2020-01-29 23:31:21 +03:00
2020-09-04 21:13:52 +03:00
for diskinfo in self . get_nonshare_diskinfos ( ) :
2020-09-03 19:03:38 +03:00
orig_disk = diskinfo . disk
2014-07-21 15:54:38 +04:00
2020-09-04 20:54:54 +03:00
if not diskinfo . new_disk :
2020-09-03 19:03:38 +03:00
# User didn't set a path, generate one
newpath = Cloner . generate_clone_disk_path (
self . conn , self . src_name ,
self . new_guest . name ,
2020-11-11 23:38:34 +03:00
orig_disk . get_source_path ( ) )
2020-09-04 21:13:52 +03:00
diskinfo . set_new_path ( newpath , self . _sparse )
2020-09-04 22:34:13 +03:00
if not diskinfo . new_disk :
# We hit an error, clients will raise it later
continue
2013-03-18 01:06:52 +04:00
2020-09-04 20:54:54 +03:00
new_disk = diskinfo . new_disk
assert new_disk
2020-09-03 19:03:38 +03:00
log . debug ( " Cloning srcpath= %s dstpath= %s " ,
2020-11-11 23:38:34 +03:00
orig_disk . get_source_path ( ) , new_disk . get_source_path ( ) )
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03:00
if self . _reflink :
2020-09-04 20:54:54 +03:00
vol_install = new_disk . get_vol_install ( )
2020-09-03 19:03:38 +03:00
vol_install . reflink = self . _reflink
2013-03-18 01:06:52 +04:00
2020-09-03 19:03:38 +03:00
for disk in self . _new_guest . devices . disk :
2013-03-18 01:06:52 +04:00
if disk . target == orig_disk . target :
xmldisk = disk
# Change the XML
2020-11-11 23:38:34 +03:00
xmldisk . set_source_path ( None )
2020-09-04 20:54:54 +03:00
xmldisk . type = new_disk . type
2013-07-13 18:09:00 +04:00
xmldisk . driver_name = orig_disk . driver_name
2013-03-18 01:06:52 +04:00
xmldisk . driver_type = orig_disk . driver_type
2020-11-11 23:38:34 +03:00
xmldisk . set_source_path ( new_disk . get_source_path ( ) )
2013-03-18 01:06:52 +04:00
2020-09-04 21:13:52 +03:00
self . _prepare_nvram ( )
2022-11-30 19:01:08 +03:00
self . _prepare_serial_files ( )
2017-03-06 11:43:10 +03:00
2013-03-18 01:06:52 +04:00
# Save altered clone xml
2020-09-05 15:31:43 +03:00
diff = xmlutil . diff ( self . _src_guest . get_xml ( ) ,
self . _new_guest . get_xml ( ) )
log . debug ( " Clone guest xml diff: \n %s " , diff )
2013-03-18 01:06:52 +04:00
2013-07-03 02:30:46 +04:00
def start_duplicate ( self , meter = None ) :
"""
Actually perform the duplication : cloning disks if needed and defining
the new clone xml .
"""
2019-06-17 04:12:39 +03:00
log . debug ( " Starting duplicate. " )
2019-06-08 00:32:51 +03:00
meter = progress . ensure_meter ( meter )
2013-07-03 02:30:46 +04:00
dom = None
try :
# Replace orig VM if required
2020-09-03 19:03:38 +03:00
if self . _replace :
_replace_vm ( self . conn , self . _new_guest . name )
2013-07-03 02:30:46 +04:00
# Define domain early to catch any xml errors before duping storage
2020-09-03 19:03:38 +03:00
dom = self . conn . defineXML ( self . _new_guest . get_xml ( ) )
2013-07-03 02:30:46 +04:00
2020-09-04 21:13:52 +03:00
diskinfos = self . get_diskinfos ( )
if self . _nvram_diskinfo :
diskinfos . append ( self . _nvram_diskinfo )
for diskinfo in diskinfos :
if not diskinfo . is_clone_requested ( ) :
continue
diskinfo . new_disk . build_storage ( meter )
2017-05-05 19:47:21 +03:00
except Exception as e :
2019-06-17 04:12:39 +03:00
log . debug ( " Duplicate failed: %s " , str ( e ) )
2013-07-03 02:30:46 +04:00
if dom :
dom . undefine ( )
raise
2019-06-17 04:12:39 +03:00
log . debug ( " Duplicating finished. " )