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
import libvirt
2019-06-08 01:16:53 +03:00
from . import generatename
2019-06-08 00:32:51 +03:00
from . import progress
2019-06-08 01:21:24 +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
2014-09-12 23:59:22 +04:00
from . storage import StorageVolume
2018-03-20 19:27:37 +03:00
from . devices import DeviceChannel
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-07-12 00:31:40 +03:00
raise RuntimeError (
2020-07-14 10:41:49 +03:00
_ ( " Could not remove old vm ' %(vm)s ' : %(error)s " ) % {
" vm " : name ,
" error " : str ( e ) ,
} )
2020-01-27 20:13:23 +03:00
2013-07-03 02:30:46 +04:00
class Cloner ( object ) :
2013-03-18 01:06:52 +04:00
# Reasons why we don't default to cloning.
CLONE_POLICY_NO_READONLY = 1
CLONE_POLICY_NO_SHAREABLE = 2
CLONE_POLICY_NO_EMPTYMEDIA = 3
2013-07-03 02:30:46 +04:00
def __init__ ( self , conn ) :
self . conn = conn
2013-03-18 01:06:52 +04:00
# original guest name or uuid
2013-07-03 02:30:46 +04:00
self . _original_guest = None
self . original_dom = None
self . _original_disks = [ ]
self . _original_xml = None
self . _guest = None
2013-03-18 01:06:52 +04:00
# clone guest
2013-07-03 02:30:46 +04:00
self . _clone_name = None
self . _clone_disks = [ ]
self . _clone_macs = [ ]
self . _clone_uuid = None
self . _clone_sparse = True
self . _clone_xml = None
2017-03-06 11:43:10 +03:00
self . clone_nvram = None
self . _nvram_disk = None
2013-07-03 02:30:46 +04:00
self . _force_target = [ ]
self . _skip_target = [ ]
self . _preserve = True
self . _clone_running = False
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
# Default clone policy for back compat: don't clone readonly,
# shareable, or empty disks
2019-06-15 23:15:01 +03:00
self . _clone_policy = [ ]
self . clone_policy = [ self . CLONE_POLICY_NO_READONLY ,
self . CLONE_POLICY_NO_SHAREABLE ,
self . CLONE_POLICY_NO_EMPTYMEDIA ]
2013-03-18 01:06:52 +04:00
# Generate a random UUID at the start
2019-06-07 01:22:33 +03:00
self . clone_uuid = Guest . generate_uuid ( conn )
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
##############
# Properties #
##############
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# Original guest name
2013-03-18 01:06:52 +04:00
def get_original_guest ( self ) :
return self . _original_guest
def set_original_guest ( self , original_guest ) :
if self . _lookup_vm ( original_guest ) :
self . _original_guest = original_guest
2018-02-23 04:44:09 +03:00
original_guest = property ( get_original_guest , set_original_guest )
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# XML of the original guest
2013-03-18 01:06:52 +04:00
def set_original_xml ( self , val ) :
self . _original_xml = val
2013-09-23 01:34:53 +04:00
self . _original_guest = Guest ( self . conn ,
parsexml = self . _original_xml ) . name
2013-03-18 01:06:52 +04:00
def get_original_xml ( self ) :
return self . _original_xml
2018-02-23 04:44:09 +03:00
original_xml = property ( get_original_xml , set_original_xml )
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# Name to use for the new guest clone
2013-03-18 01:06:52 +04:00
def get_clone_name ( self ) :
return self . _clone_name
def set_clone_name ( self , name ) :
try :
2013-08-09 22:16:59 +04:00
Guest . validate_name ( self . conn , name ,
2014-01-18 03:44:26 +04:00
check_collision = not self . replace ,
validate = False )
2017-05-05 19:47:21 +03:00
except ValueError as e :
2013-03-18 01:06:52 +04:00
raise ValueError ( _ ( " Invalid name for new guest: %s " ) % e )
self . _clone_name = name
2018-02-23 04:44:09 +03:00
clone_name = property ( get_clone_name , set_clone_name )
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# UUID to use for the new guest clone
2013-03-18 01:06:52 +04:00
def set_clone_uuid ( self , uuid ) :
self . _clone_uuid = uuid
def get_clone_uuid ( self ) :
return self . _clone_uuid
2018-02-23 04:44:09 +03:00
clone_uuid = property ( get_clone_uuid , set_clone_uuid )
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# Paths to use for the new disk locations
2013-07-03 02:30:46 +04:00
def set_clone_paths ( self , paths ) :
2013-03-18 01:06:52 +04:00
disklist = [ ]
2019-06-08 01:21:24 +03:00
for path in xmlutil . listify ( paths ) :
2013-03-18 01:06:52 +04:00
try :
2018-03-20 19:18:35 +03:00
device = DeviceDisk . DEVICE_DISK
2013-03-18 01:06:52 +04:00
if not path :
2018-03-20 19:18:35 +03:00
device = DeviceDisk . DEVICE_CDROM
2013-03-18 01:06:52 +04:00
2018-03-20 19:18:35 +03:00
disk = DeviceDisk ( self . conn )
2013-07-13 18:09:00 +04:00
disk . path = path
disk . device = device
2014-12-06 05:09:26 +03:00
if ( not self . preserve_dest_disks and
disk . wants_storage_creation ( ) ) :
2018-03-20 19:18:35 +03:00
vol_install = DeviceDisk . build_vol_install (
2014-12-06 05:09:26 +03:00
self . conn , os . path . basename ( disk . path ) ,
disk . get_parent_pool ( ) , .000001 , False )
disk . set_vol_install ( vol_install )
2013-07-13 18:09:00 +04:00
disk . validate ( )
2013-03-18 01:06:52 +04:00
disklist . append ( disk )
2017-05-05 19:47:21 +03:00
except Exception as e :
2019-06-17 04:12:39 +03:00
log . debug ( " Error setting clone path. " , exc_info = True )
2020-07-12 00:31:40 +03:00
raise ValueError (
2020-07-15 15:48:57 +03:00
_ ( " Could not use path ' %(path)s ' for cloning: %(error)s " ) % {
" path " : path ,
" error " : str ( e ) ,
} )
2013-03-18 01:06:52 +04:00
2013-07-03 02:30:46 +04:00
self . _clone_disks = disklist
def get_clone_paths ( self ) :
return [ d . path for d in self . clone_disks ]
2018-02-23 04:44:09 +03:00
clone_paths = property ( get_clone_paths , set_clone_paths )
2013-03-18 01:06:52 +04:00
2018-03-20 19:18:35 +03:00
# DeviceDisk instances for the new disk paths
2018-02-23 04:44:09 +03:00
@property
def clone_disks ( self ) :
2013-07-03 02:30:46 +04:00
return self . _clone_disks
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# MAC address for the new guest clone
2013-07-03 02:30:46 +04:00
def set_clone_macs ( self , mac ) :
2020-07-06 01:39:15 +03:00
self . _clone_macs = xmlutil . listify ( mac )
2013-07-03 02:30:46 +04:00
def get_clone_macs ( self ) :
return self . _clone_macs
2018-02-23 04:44:09 +03:00
clone_macs = property ( get_clone_macs , set_clone_macs )
2013-07-03 02:30:46 +04:00
2018-03-20 19:18:35 +03:00
# DeviceDisk instances of the original disks being cloned
2018-02-23 04:44:09 +03:00
@property
def original_disks ( self ) :
2013-07-03 02:30:46 +04:00
return self . _original_disks
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# Generated XML for the guest clone
2013-03-18 01:06:52 +04:00
def get_clone_xml ( self ) :
return self . _clone_xml
def set_clone_xml ( self , clone_xml ) :
self . _clone_xml = clone_xml
2018-02-23 04:44:09 +03:00
clone_xml = property ( get_clone_xml , set_clone_xml )
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# Whether to attempt sparse allocation during cloning
2013-03-18 01:06:52 +04:00
def get_clone_sparse ( self ) :
return self . _clone_sparse
def set_clone_sparse ( self , flg ) :
self . _clone_sparse = flg
2018-02-23 04:44:09 +03:00
clone_sparse = property ( get_clone_sparse , set_clone_sparse )
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# If true, preserve ALL original disk devices
2013-03-18 01:06:52 +04:00
def get_preserve ( self ) :
return self . _preserve
def set_preserve ( self , flg ) :
self . _preserve = flg
2018-02-23 04:44:09 +03:00
preserve = property ( get_preserve , set_preserve )
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# If true, preserve ALL disk devices for the NEW guest.
# This means no storage cloning.
# This is a convenience access for not Cloner.preserve
@property
def preserve_dest_disks ( self ) :
2013-03-18 01:06:52 +04:00
return not self . preserve
2018-02-23 04:44:09 +03:00
# List of disk targets that we force cloning despite
# Cloner's recommendation
2013-03-18 01:06:52 +04:00
def set_force_target ( self , dev ) :
2017-10-11 14:35:41 +03:00
if isinstance ( dev , list ) :
2013-03-18 01:06:52 +04:00
self . _force_target = dev [ : ]
else :
self . _force_target . append ( dev )
def get_force_target ( self ) :
return self . _force_target
2018-02-23 04:44:09 +03:00
force_target = property ( get_force_target , set_force_target )
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# List of disk targets that we skip cloning despite Cloner's
# recommendation. This takes precedence over force_target.")
2013-03-18 01:06:52 +04:00
def set_skip_target ( self , dev ) :
2017-10-11 14:35:41 +03:00
if isinstance ( dev , list ) :
2013-03-18 01:06:52 +04:00
self . _skip_target = dev [ : ]
else :
self . _skip_target . append ( dev )
def get_skip_target ( self ) :
return self . _skip_target
2018-02-23 04:44:09 +03:00
skip_target = property ( get_skip_target , set_skip_target )
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# List of policy rules for determining which vm disks to clone.
# See CLONE_POLICY_*
2013-03-18 01:06:52 +04:00
def set_clone_policy ( self , policy_list ) :
self . _clone_policy = policy_list
def get_clone_policy ( self ) :
return self . _clone_policy
2018-02-23 04:44:09 +03:00
clone_policy = property ( get_clone_policy , set_clone_policy )
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# Allow cloning a running VM. If enabled, domain state is not
# checked before cloning.
2013-03-18 01:06:52 +04:00
def get_clone_running ( self ) :
return self . _clone_running
def set_clone_running ( self , val ) :
self . _clone_running = bool ( val )
2018-02-23 04:44:09 +03:00
clone_running = property ( get_clone_running , set_clone_running )
2013-03-18 01:06:52 +04:00
2018-02-23 04:44:09 +03:00
# If enabled, don't check for clone name collision, simply undefine
# any conflicting guest.
2013-03-18 01:06:52 +04:00
def _get_replace ( self ) :
2013-08-09 22:16:59 +04:00
return self . _replace
2013-03-18 01:06:52 +04:00
def _set_replace ( self , val ) :
2013-08-09 22:16:59 +04:00
self . _replace = bool ( val )
2018-02-23 04:44:09 +03:00
replace = property ( _get_replace , _set_replace )
# If true, use COW lightweight copy
2015-02-07 05:18:05 +03:00
def _get_reflink ( self ) :
return self . _reflink
def _set_reflink ( self , reflink ) :
self . _reflink = reflink
2018-02-23 04:44:09 +03:00
reflink = property ( _get_reflink , _set_reflink )
2015-02-07 05:18:05 +03:00
2018-02-23 04:44:09 +03:00
######################
# Functional methods #
######################
2013-03-18 01:06:52 +04:00
def setup_original ( self ) :
"""
Validate and setup all parameters needed for the original ( cloned ) VM
"""
2019-06-17 04:12:39 +03:00
log . debug ( " Validating original guest parameters " )
2013-03-18 01:06:52 +04:00
2013-04-13 22:34:52 +04:00
if self . original_guest is None and self . original_xml is None :
2020-07-13 11:25:39 +03:00
raise RuntimeError ( _ ( " Original guest name or XML is required. " ) )
2013-03-18 01:06:52 +04:00
2013-04-13 22:34:52 +04:00
if self . original_guest is not None and not self . original_xml :
2013-07-03 02:30:46 +04:00
self . original_dom = self . _lookup_vm ( self . original_guest )
2017-09-15 19:34:58 +03:00
flags = libvirt . VIR_DOMAIN_XML_SECURE
self . original_xml = self . original_dom . XMLDesc ( flags )
2013-03-18 01:06:52 +04:00
2019-06-17 04:12:39 +03:00
log . debug ( " Original XML: \n %s " , self . original_xml )
2013-03-18 01:06:52 +04:00
2013-07-13 18:09:00 +04:00
self . _guest = Guest ( self . conn , parsexml = self . original_xml )
2013-09-24 05:27:54 +04:00
self . _guest . id = None
2013-03-18 01:06:52 +04:00
# Pull clonable storage info from the original xml
2013-07-03 02:30:46 +04:00
self . _original_disks = self . _get_original_disks_info ( )
2013-03-18 01:06:52 +04:00
2019-06-17 04:12:39 +03:00
log . debug ( " Original paths: %s " ,
2013-07-03 02:30:46 +04:00
[ d . path for d in self . original_disks ] )
2019-06-17 04:12:39 +03:00
log . debug ( " Original sizes: %s " ,
2013-07-13 18:09:00 +04:00
[ d . get_size ( ) for d in self . original_disks ] )
2013-03-18 01:06:52 +04:00
2020-01-30 00:14:55 +03:00
if not self . clone_running and self . original_dom :
2013-07-03 02:30:46 +04:00
status = self . original_dom . info ( ) [ 0 ]
2020-01-30 00:14:55 +03:00
if status not in [ libvirt . VIR_DOMAIN_SHUTOFF ] :
raise RuntimeError ( _ ( " Domain to clone must be shutoff. " ) )
2013-03-18 01:06:52 +04:00
2013-04-24 18:47:56 +04:00
def _setup_disk_clone_destination ( self , orig_disk , clone_disk ) :
"""
Helper that validates the new path location
"""
if self . preserve_dest_disks :
return
2013-07-13 18:09:00 +04:00
if clone_disk . get_vol_object ( ) :
2013-04-24 18:47:56 +04: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.
2013-07-06 04:36:28 +04:00
if ( self . conn . is_remote ( ) or
2013-04-24 18:47:56 +04:00
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 )
# Setup proper cloning inputs for the new virtual disks
2013-07-13 18:09:00 +04:00
if ( orig_disk . get_vol_object ( ) and
clone_disk . get_vol_install ( ) ) :
clone_vol_install = clone_disk . get_vol_install ( )
2013-04-24 18:47:56 +04:00
# 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
2013-07-13 18:09:00 +04:00
if ( clone_vol_install . pool . name ( ) ==
2017-07-18 23:21:41 +03:00
orig_disk . get_parent_pool ( ) . name ( ) ) :
2013-09-20 04:18:12 +04:00
vol_install = StorageVolume ( self . conn )
vol_install . input_vol = orig_disk . get_vol_object ( )
vol_install . sync_input_vol ( )
vol_install . name = clone_vol_install . name
2013-04-24 18:47:56 +04:00
else :
2013-09-20 04:18:12 +04:00
# Cross pool cloning
2015-03-31 18:59:06 +03:00
# Sync only the format of the image.
2013-07-13 18:09:00 +04:00
clone_vol_install . input_vol = orig_disk . get_vol_object ( )
vol_install = clone_vol_install
2015-03-31 18:59:06 +03:00
vol_install . input_vol = orig_disk . get_vol_object ( )
vol_install . sync_input_vol ( only_format = True )
2013-07-13 18:09:00 +04:00
2019-05-13 16:56:06 +03:00
if not self . clone_sparse :
2019-03-04 21:20:51 +03:00
vol_install . allocation = vol_install . capacity
2015-02-07 05:18:05 +03:00
vol_install . reflink = self . reflink
2014-12-06 05:09:26 +03:00
clone_disk . set_vol_install ( vol_install )
elif orig_disk . path :
clone_disk . set_local_disk_to_clone ( orig_disk , self . clone_sparse )
2013-07-13 18:09:00 +04:00
clone_disk . validate ( )
2013-04-24 18:47:56 +04:00
2013-03-18 01:06:52 +04:00
2017-03-06 11:43:10 +03:00
def _prepare_nvram ( self ) :
if self . clone_nvram is None :
nvram_dir = os . path . dirname ( self . _guest . os . nvram )
self . clone_nvram = os . path . join ( nvram_dir ,
" %s _VARS.fd " % self . _clone_name )
2019-03-28 16:27:04 +03:00
old_nvram = DeviceDisk ( self . conn )
old_nvram . path = self . _guest . os . nvram
2018-03-20 19:18:35 +03:00
nvram = DeviceDisk ( self . conn )
2017-03-06 11:43:10 +03:00
nvram . path = self . clone_nvram
2019-03-28 16:27:04 +03:00
if ( not self . preserve_dest_disks and
nvram . wants_storage_creation ( ) and
old_nvram . get_vol_object ( ) ) :
2017-03-06 11:43:10 +03:00
2018-03-20 19:18:35 +03:00
nvram_install = DeviceDisk . build_vol_install (
2017-03-06 11:43:10 +03:00
self . conn , os . path . basename ( nvram . path ) ,
nvram . get_parent_pool ( ) , nvram . get_size ( ) , False )
nvram_install . input_vol = old_nvram . get_vol_object ( )
nvram_install . sync_input_vol ( only_format = True )
nvram_install . reflink = self . reflink
nvram . set_vol_install ( nvram_install )
2019-03-28 16:27:04 +03:00
nvram . validate ( )
self . _nvram_disk = nvram
2017-03-06 11:43:10 +03:00
self . _guest . os . nvram = nvram . path
2013-03-18 01:06:52 +04:00
def setup_clone ( self ) :
"""
Validate and set up all parameters needed for the new ( clone ) VM
"""
2019-06-17 04:12:39 +03:00
log . debug ( " Validating clone parameters. " )
2013-03-18 01:06:52 +04:00
self . _clone_xml = self . original_xml
2013-07-03 02:30:46 +04:00
if len ( self . clone_disks ) < len ( self . original_disks ) :
2013-03-18 01:06:52 +04:00
raise ValueError ( _ ( " More disks to clone than new paths specified. "
" ( %(passed)d specified, %(need)d needed " ) %
2017-08-05 09:39:32 +03:00
{ " passed " : len ( self . clone_disks ) ,
" need " : len ( self . original_disks ) } )
2013-03-18 01:06:52 +04:00
2019-06-17 04:12:39 +03:00
log . debug ( " Clone paths: %s " , [ d . path for d in self . clone_disks ] )
2013-03-18 01:06:52 +04:00
self . _guest . name = self . _clone_name
self . _guest . uuid = self . _clone_uuid
2020-01-29 23:31:21 +03:00
self . _guest . title = None
2013-07-03 02:30:46 +04:00
self . _clone_macs . reverse ( )
2018-03-21 00:23:34 +03:00
for dev in self . _guest . devices . graphics :
2013-03-18 01:06:52 +04:00
if dev . port and dev . port != - 1 :
2019-06-17 04:12:39 +03:00
log . warning ( _ ( " Setting the graphics device port to autoport, "
2013-03-18 01:06:52 +04:00
" in order to avoid conflicting. " ) )
dev . port = - 1
2014-07-21 15:54:38 +04:00
clone_macs = self . _clone_macs [ : ]
2018-03-21 00:23:34 +03:00
for iface in self . _guest . devices . interface :
2013-03-18 01:06:52 +04:00
iface . target_dev = None
2014-07-21 15:54:38 +04:00
if clone_macs :
mac = clone_macs . pop ( )
2013-03-18 01:06:52 +04:00
else :
2018-03-20 19:18:35 +03:00
mac = DeviceInterface . generate_mac ( self . conn )
2013-03-18 01:06:52 +04:00
iface . macaddr = mac
# Changing storage XML
2017-10-11 14:36:03 +03:00
for i , orig_disk in enumerate ( self . _original_disks ) :
2013-07-03 02:30:46 +04:00
clone_disk = self . _clone_disks [ i ]
2013-03-18 01:06:52 +04:00
2018-03-21 00:23:34 +03:00
for disk in self . _guest . devices . disk :
2013-03-18 01:06:52 +04:00
if disk . target == orig_disk . target :
xmldisk = disk
2013-04-24 18:47:56 +04:00
self . _setup_disk_clone_destination ( orig_disk , clone_disk )
2013-03-18 01:06:52 +04:00
# Change the XML
xmldisk . path = None
xmldisk . type = clone_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
2013-07-13 18:09:00 +04:00
xmldisk . path = clone_disk . path
2013-03-18 01:06:52 +04:00
2015-11-02 15:20:15 +03:00
# For guest agent channel, remove a path to generate a new one with
# new guest name
2018-03-21 00:23:34 +03:00
for channel in self . _guest . devices . channel :
2018-06-28 17:18:43 +03:00
if ( channel . type == DeviceChannel . TYPE_UNIX and
2019-05-13 21:23:27 +03:00
channel . target_name and channel . source . path and
channel . target_name in channel . source . path ) :
channel . source . path = None
2015-11-02 15:20:15 +03:00
2017-03-06 11:43:10 +03:00
if self . _guest . os . nvram :
self . _prepare_nvram ( )
2013-03-18 01:06:52 +04:00
# Save altered clone xml
2018-08-31 23:52:02 +03:00
self . _clone_xml = self . _guest . get_xml ( )
2019-06-17 04:12:39 +03:00
log . debug ( " Clone guest xml is \n %s " , self . _clone_xml )
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-01-27 20:13:23 +03:00
if self . replace :
_replace_vm ( self . conn , self . clone_name )
2013-07-03 02:30:46 +04:00
# Define domain early to catch any xml errors before duping storage
dom = self . conn . defineXML ( self . clone_xml )
if self . preserve :
for dst_dev in self . clone_disks :
2018-09-03 23:44:38 +03:00
dst_dev . build_storage ( meter )
2017-03-06 11:43:10 +03:00
if self . _nvram_disk :
2018-09-03 23:44:38 +03:00
self . _nvram_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. " )
2013-07-03 02:30:46 +04:00
def generate_clone_disk_path ( self , origpath , newname = None ) :
origname = self . original_guest
newname = newname or self . clone_name
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.
2018-02-14 03:04:08 +03:00
if " . " in origpath and len ( origpath . rsplit ( " . " , 1 ) [ 1 ] ) < = 7 :
2013-07-03 02:30:46 +04:00
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 )
2019-06-11 17:05:15 +03:00
def cb ( p ) :
return DeviceDisk . path_definitely_exists ( self . conn , p )
return generatename . generate_name ( clonebase , cb , suffix = suffix )
2013-07-03 02:30:46 +04:00
2019-06-15 23:15:01 +03:00
def generate_clone_name ( self , basename = None ) :
2013-07-03 02:30:46 +04:00
# If the orig name is "foo-clone", we don't want the clone to be
# "foo-clone-clone", we want "foo-clone1"
2019-06-15 23:15:01 +03:00
if not basename :
basename = self . original_guest
2013-07-03 02:30:46 +04:00
match = re . search ( " -clone[1-9]*$ " , basename )
2013-10-05 18:22:27 +04:00
start_num = 1
2019-06-15 23:15:01 +03:00
force_num = False
2013-07-03 02:30:46 +04:00
if match :
num_match = re . search ( " [1-9]+$ " , match . group ( ) )
if num_match :
2019-06-15 23:15:01 +03:00
start_num = int ( str ( num_match . group ( ) ) ) + 1
force_num = True
2013-07-03 02:30:46 +04:00
basename = basename . replace ( match . group ( ) , " " )
2019-06-11 17:05:15 +03:00
def cb ( n ) :
return generatename . check_libvirt_collision (
self . conn . lookupByName , n )
2013-07-03 02:30:46 +04:00
basename = basename + " -clone "
2019-06-11 17:05:15 +03:00
return generatename . generate_name ( basename , cb ,
2019-06-15 23:15:01 +03:00
sep = " " , start_num = start_num , force_num = force_num )
2013-07-03 02:30:46 +04:00
############################
# Private helper functions #
############################
2013-03-18 01:06:52 +04:00
# Parse disk paths that need to be cloned from the original guest's xml
2018-03-20 19:18:35 +03:00
# Return a list of DeviceDisk instances pointing to the original
2013-03-18 01:06:52 +04:00
# storage
2013-07-03 02:30:46 +04:00
def _get_original_disks_info ( self ) :
2013-03-18 01:06:52 +04:00
clonelist = [ ]
retdisks = [ ]
2018-03-21 00:23:34 +03:00
for disk in self . _guest . devices . disk :
2013-03-18 01:06:52 +04:00
if self . _do_we_clone_device ( disk ) :
clonelist . append ( disk )
continue
# Set up virtual disk to encapsulate all relevant path info
for disk in clonelist :
validate = not self . preserve_dest_disks
try :
2018-03-20 19:18:35 +03:00
device = DeviceDisk . DEVICE_DISK
2013-03-18 01:06:52 +04:00
if not disk . path :
2018-03-20 19:18:35 +03:00
# Tell DeviceDisk we are a cdrom to allow empty media
device = DeviceDisk . DEVICE_CDROM
2013-03-18 01:06:52 +04:00
2018-03-20 19:18:35 +03:00
newd = DeviceDisk ( self . conn )
2013-07-13 18:09:00 +04:00
newd . path = disk . path
newd . device = device
2013-07-14 23:53:23 +04:00
newd . driver_name = disk . driver_name
2013-07-13 18:09:00 +04:00
newd . driver_type = disk . driver_type
newd . target = disk . target
if validate :
2014-12-06 05:09:26 +03:00
if newd . wants_storage_creation ( ) :
2016-02-05 18:18:16 +03:00
raise ValueError ( _ ( " Disk path ' %s ' does not exist. " ) %
2013-07-13 18:09:00 +04:00
newd . path )
2017-05-05 19:47:21 +03:00
except Exception as e :
2019-06-17 04:12:39 +03:00
log . debug ( " Exception creating clone disk objects " ,
2014-02-09 01:36:45 +04:00
exc_info = True )
2013-03-18 01:06:52 +04:00
raise ValueError ( _ ( " Could not determine original disk "
" information: %s " % str ( e ) ) )
2013-07-13 18:09:00 +04:00
retdisks . append ( newd )
2013-03-18 01:06:52 +04:00
return retdisks
# Pull disk #i from the original guest xml, return it's source path
# if it should be cloned
# Cloning policy based on 'clone_policy', 'force_target' and 'skip_target'
def _do_we_clone_device ( self , disk ) :
if disk . target in self . skip_target :
return False
if disk . target in self . force_target :
return True
# No media path
if ( not disk . path and
self . CLONE_POLICY_NO_EMPTYMEDIA in self . clone_policy ) :
return False
# Readonly disks
if ( disk . read_only and
self . CLONE_POLICY_NO_READONLY in self . clone_policy ) :
return False
# Shareable disks
if ( disk . shareable and
self . CLONE_POLICY_NO_SHAREABLE in self . clone_policy ) :
return False
return True
# Simple wrapper for checking a vm exists and returning the domain
def _lookup_vm ( self , name ) :
try :
2013-07-03 02:30:46 +04:00
return self . conn . lookupByName ( name )
2013-03-18 01:06:52 +04:00
except libvirt . libvirtError :
raise ValueError ( _ ( " Domain ' %s ' was not found. " ) % str ( name ) )