2013-10-28 00:59:46 +04:00
# Copyright (C) 2009, 2013 Red Hat, Inc.
2009-07-26 23:54:14 +04:00
# Copyright (C) 2009 Cole Robinson <crobinso@redhat.com>
#
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.
2009-07-26 23:54:14 +04:00
import os
2012-05-14 17:24:56 +04:00
from gi . repository import Gtk
from gi . repository import Gdk
2009-07-26 23:54:14 +04:00
2016-12-13 20:36:49 +03:00
import virtinst
from virtinst import Cloner
2018-03-20 19:18:35 +03:00
from virtinst import DeviceInterface
2019-06-17 04:12:39 +03:00
from virtinst import log
2016-12-13 20:36:49 +03:00
2019-06-17 05:19:17 +03:00
from . lib import uiutil
2014-09-13 00:10:45 +04:00
from . baseclass import vmmGObjectUI
from . asyncjob import vmmAsyncJob
from . storagebrowse import vmmStorageBrowser
2009-07-26 23:54:14 +04:00
STORAGE_COMBO_CLONE = 0
STORAGE_COMBO_SHARE = 1
STORAGE_COMBO_SEP = 2
STORAGE_COMBO_DETAILS = 3
STORAGE_INFO_ORIG_PATH = 0
STORAGE_INFO_NEW_PATH = 1
STORAGE_INFO_TARGET = 2
STORAGE_INFO_SIZE = 3
STORAGE_INFO_DEVTYPE = 4
STORAGE_INFO_DO_CLONE = 5
STORAGE_INFO_CAN_CLONE = 6
STORAGE_INFO_CAN_SHARE = 7
STORAGE_INFO_DO_DEFAULT = 8
STORAGE_INFO_DEFINFO = 9
STORAGE_INFO_FAILINFO = 10
STORAGE_INFO_COMBO = 11
2010-12-12 21:39:45 +03:00
STORAGE_INFO_MANUAL_PATH = 12
2009-07-26 23:54:14 +04:00
NETWORK_INFO_LABEL = 0
NETWORK_INFO_ORIG_MAC = 1
NETWORK_INFO_NEW_MAC = 2
2013-04-13 22:34:52 +04:00
2011-04-18 20:39:53 +04:00
def can_we_clone ( conn , vol , path ) :
""" Is the passed path even clone-able """
ret = True
msg = None
if not path :
msg = _ ( " No storage to clone. " )
2018-09-01 04:06:18 +03:00
elif not vol :
2011-04-18 20:39:53 +04:00
is_dev = path . startswith ( " /dev " )
if conn . is_remote ( ) :
msg = _ ( " Cannot clone unmanaged remote storage. " )
elif not os . access ( path , os . R_OK ) :
if is_dev :
msg = _ ( " Block devices to clone must be libvirt \n "
" managed storage volumes. " )
else :
msg = _ ( " No write access to parent directory. " )
elif not os . path . exists ( path ) :
msg = _ ( " Path does not exist. " )
2019-02-28 13:47:40 +03:00
else :
pool = vol . get_parent_pool ( )
if not pool . supports_volume_creation ( clone = True ) :
msg = _ ( " Cannot clone %s storage pool. " ) % pool . get_type ( )
2011-04-18 20:39:53 +04:00
if msg :
ret = False
return ( ret , msg )
2013-04-13 22:34:52 +04:00
2011-04-18 20:39:53 +04:00
def do_we_default ( conn , vol , path , ro , shared , devtype ) :
""" Returns (do we clone by default?, info string if not) """
ignore = conn
info = " "
2019-03-05 12:16:06 +03:00
can_default = True
2011-04-18 20:39:53 +04:00
def append_str ( str1 , str2 , delim = " , " ) :
if not str2 :
return str1
if str1 :
str1 + = delim
str1 + = str2
return str1
2018-03-20 19:18:35 +03:00
if ( devtype == virtinst . DeviceDisk . DEVICE_CDROM or
devtype == virtinst . DeviceDisk . DEVICE_FLOPPY ) :
2011-04-18 20:39:53 +04:00
info = append_str ( info , _ ( " Removable " ) )
if ro :
info = append_str ( info , _ ( " Read Only " ) )
elif not vol and path and not os . access ( path , os . W_OK ) :
info = append_str ( info , _ ( " No write access " ) )
2017-03-07 00:30:07 +03:00
if vol :
pool_type = vol . get_parent_pool ( ) . get_type ( )
2019-02-28 13:47:40 +03:00
if pool_type == virtinst . StoragePool . TYPE_DISK :
2017-03-07 00:30:07 +03:00
info = append_str ( info , _ ( " Disk device " ) )
2019-03-05 12:16:06 +03:00
can_default = False
2017-03-06 11:28:51 +03:00
2011-04-18 20:39:53 +04:00
if shared :
info = append_str ( info , _ ( " Shareable " ) )
2019-03-05 12:16:06 +03:00
return ( not info , info , can_default )
2009-07-26 23:54:14 +04:00
2013-04-13 22:34:52 +04:00
2011-04-18 20:39:53 +04:00
class vmmCloneVM ( vmmGObjectUI ) :
2018-03-15 01:17:03 +03:00
@classmethod
def show_instance ( cls , parentobj , vm ) :
try :
# Maintain one dialog per connection
uri = vm . conn . get_uri ( )
2018-03-18 02:42:19 +03:00
if cls . _instances is None :
cls . _instances = { }
2018-03-15 01:17:03 +03:00
if uri not in cls . _instances :
2018-03-17 23:08:20 +03:00
cls . _instances [ uri ] = vmmCloneVM ( )
2018-03-15 01:17:03 +03:00
cls . _instances [ uri ] . show ( parentobj . topwin , vm )
except Exception as e :
parentobj . err . show_err (
_ ( " Error launching clone dialog: %s " ) % str ( e ) )
2009-07-26 23:54:14 +04:00
2018-03-15 01:17:03 +03:00
def __init__ ( self ) :
vmmGObjectUI . __init__ ( self , " clone.ui " , " vmm-clone " )
self . vm = None
2011-04-13 17:20:59 +04:00
self . clone_design = None
self . storage_list = { }
self . target_list = [ ]
self . net_list = { }
self . mac_list = [ ]
self . storage_browser = None
2012-02-02 02:26:46 +04:00
self . change_mac = self . widget ( " vmm-change-mac " )
self . change_mac . set_transient_for ( self . topwin )
self . change_storage = self . widget ( " vmm-change-storage " )
self . change_storage . set_transient_for ( self . topwin )
2013-02-16 22:31:46 +04:00
self . builder . connect_signals ( {
2017-08-05 09:39:32 +03:00
" on_clone_delete_event " : self . close ,
" on_clone_cancel_clicked " : self . close ,
" on_clone_ok_clicked " : self . finish ,
2012-02-02 02:26:46 +04:00
# Change mac dialog
2009-07-26 23:54:14 +04:00
" on_vmm_change_mac_delete_event " : self . change_mac_close ,
2017-08-05 09:39:32 +03:00
" on_change_mac_cancel_clicked " : self . change_mac_close ,
" on_change_mac_ok_clicked " : self . change_mac_finish ,
2009-07-26 23:54:14 +04:00
2012-02-02 02:26:46 +04:00
# Change storage dialog
2009-07-26 23:54:14 +04:00
" on_vmm_change_storage_delete_event " : self . change_storage_close ,
2017-08-05 09:39:32 +03:00
" on_change_storage_cancel_clicked " : self . change_storage_close ,
" on_change_storage_ok_clicked " : self . change_storage_finish ,
" on_change_storage_doclone_toggled " : self . change_storage_doclone_toggled ,
2009-07-26 23:54:14 +04:00
2017-08-05 09:39:32 +03:00
" on_change_storage_browse_clicked " : self . change_storage_browse ,
2009-07-26 23:54:14 +04:00
} )
2011-04-18 19:25:28 +04:00
self . bind_escape_key_close ( )
2018-03-15 14:43:56 +03:00
self . _cleanup_on_app_close ( )
2009-07-26 23:54:14 +04:00
2018-03-15 01:17:03 +03:00
self . _init_ui ( )
@property
def conn ( self ) :
if self . vm :
return self . vm . conn
return None
2009-07-26 23:54:14 +04:00
2018-03-15 01:17:03 +03:00
def show ( self , parent , vm ) :
2019-06-17 04:12:39 +03:00
log . debug ( " Showing clone wizard " )
2018-03-15 14:43:56 +03:00
self . _set_vm ( vm )
2009-07-26 23:54:14 +04:00
self . reset_state ( )
2011-04-14 16:47:42 +04:00
self . topwin . set_transient_for ( parent )
2011-04-14 21:07:52 +04:00
self . topwin . resize ( 1 , 1 )
2009-07-26 23:54:14 +04:00
self . topwin . present ( )
def close ( self , ignore1 = None , ignore2 = None ) :
2019-06-17 04:12:39 +03:00
log . debug ( " Closing clone wizard " )
2009-07-26 23:54:14 +04:00
self . change_mac_close ( )
self . change_storage_close ( )
2010-12-09 22:46:15 +03:00
self . topwin . hide ( )
2011-04-13 17:27:02 +04:00
2018-03-15 14:43:56 +03:00
self . _set_vm ( None )
2011-04-13 17:27:02 +04:00
self . clone_design = None
self . storage_list = { }
self . target_list = [ ]
self . net_list = { }
self . mac_list = [ ]
2009-07-26 23:54:14 +04:00
return 1
2018-03-15 14:43:56 +03:00
def _vm_removed ( self , _conn , connkey ) :
if self . vm . get_connkey ( ) == connkey :
self . close ( )
def _set_vm ( self , newvm ) :
oldvm = self . vm
if oldvm :
oldvm . conn . disconnect_by_obj ( self )
if newvm :
newvm . conn . connect ( " vm-removed " , self . _vm_removed )
self . vm = newvm
2011-07-24 05:16:54 +04:00
def _cleanup ( self ) :
2011-04-13 17:27:02 +04:00
self . change_mac . destroy ( )
self . change_mac = None
self . change_storage . destroy ( )
self . change_storage = None
if self . storage_browser :
self . storage_browser . cleanup ( )
self . storage_browser = None
2009-07-26 23:54:14 +04:00
def change_mac_close ( self , ignore1 = None , ignore2 = None ) :
self . change_mac . hide ( )
return 1
def change_storage_close ( self , ignore1 = None , ignore2 = None ) :
self . change_storage . hide ( )
return 1
# First time setup
2018-03-15 01:17:03 +03:00
def _init_ui ( self ) :
2012-05-14 17:24:56 +04:00
blue = Gdk . Color . parse ( " #0072A8 " ) [ 1 ]
2013-09-27 18:26:43 +04:00
self . widget ( " header " ) . modify_bg ( Gtk . StateType . NORMAL , blue )
2009-07-26 23:54:14 +04:00
2013-01-13 01:13:53 +04:00
context = self . topwin . get_style_context ( )
defcolor = context . get_background_color ( Gtk . StateType . NORMAL )
2013-09-27 18:26:43 +04:00
self . widget ( " storage-viewport " ) . override_background_color (
Gtk . StateType . NORMAL ,
2013-03-11 12:21:47 +04:00
defcolor )
2013-01-13 01:13:53 +04:00
2009-07-26 23:54:14 +04:00
# Populate state
def reset_state ( self ) :
2011-07-14 21:13:13 +04:00
self . widget ( " clone-cancel " ) . grab_focus ( )
2009-12-15 00:48:35 +03:00
2009-07-26 23:54:14 +04:00
# Populate default clone values
self . setup_clone_info ( )
cd = self . clone_design
2011-07-14 21:13:13 +04:00
self . widget ( " clone-orig-name " ) . set_text ( cd . original_guest )
self . widget ( " clone-new-name " ) . set_text ( cd . clone_name )
2009-07-26 23:54:14 +04:00
2014-02-02 03:59:10 +04:00
uiutil . set_grid_row_visible (
self . widget ( " clone-dest-host " ) , self . conn . is_remote ( ) )
2015-04-19 00:52:25 +03:00
self . widget ( " clone-dest-host " ) . set_text ( self . conn . get_pretty_desc ( ) )
2014-02-02 03:59:10 +04:00
2009-07-26 23:54:14 +04:00
# We need to determine which disks fail (and why).
self . storage_list , self . target_list = self . check_all_storage ( )
self . populate_storage_lists ( )
self . populate_network_list ( )
return
def setup_clone_info ( self ) :
self . clone_design = self . build_new_clone_design ( )
def build_new_clone_design ( self , new_name = None ) :
2013-07-05 16:59:58 +04:00
design = Cloner ( self . conn . get_backend ( ) )
2018-03-15 01:17:03 +03:00
design . original_guest = self . vm . get_name ( )
2009-07-26 23:54:14 +04:00
if not new_name :
2013-07-03 02:30:46 +04:00
new_name = design . generate_clone_name ( )
design . clone_name = new_name
2009-07-26 23:54:14 +04:00
# Erase any clone_policy from the original design, so that we
# get the entire device list.
2013-07-03 02:30:46 +04:00
design . clone_policy = [ ]
return design
2009-07-26 23:54:14 +04:00
def populate_network_list ( self ) :
2011-07-14 21:13:13 +04:00
net_box = self . widget ( " clone-network-box " )
2009-07-26 23:54:14 +04:00
for c in net_box . get_children ( ) :
net_box . remove ( c )
2011-04-14 21:12:24 +04:00
c . destroy ( )
2009-07-26 23:54:14 +04:00
self . net_list = { }
self . mac_list = [ ]
def build_net_row ( labelstr , origmac , newmac ) :
2012-05-14 17:24:56 +04:00
label = Gtk . Label ( label = labelstr + " ( %s ) " % origmac )
2009-07-26 23:54:14 +04:00
label . set_alignment ( 0 , .5 )
2012-05-14 17:24:56 +04:00
button = Gtk . Button ( _ ( " Details... " ) )
2009-07-26 23:54:14 +04:00
button . connect ( " clicked " , self . net_change_mac , origmac )
2012-05-14 17:24:56 +04:00
hbox = Gtk . HBox ( )
2009-07-26 23:54:14 +04:00
hbox . set_spacing ( 12 )
2012-05-14 17:24:56 +04:00
hbox . pack_start ( label , True , True , 0 )
hbox . pack_end ( button , False , False , False )
2009-07-26 23:54:14 +04:00
hbox . show_all ( )
2013-01-13 01:13:53 +04:00
net_box . pack_start ( hbox , False , False , False )
2009-07-26 23:54:14 +04:00
net_row = [ ]
net_row . insert ( NETWORK_INFO_LABEL , labelstr )
net_row . insert ( NETWORK_INFO_ORIG_MAC , origmac )
net_row . insert ( NETWORK_INFO_NEW_MAC , newmac )
self . net_list [ origmac ] = net_row
self . mac_list . append ( origmac )
2018-03-21 21:42:50 +03:00
for net in self . vm . xmlobj . devices . interface :
2010-09-03 23:19:54 +04:00
mac = net . macaddr
2013-09-24 18:00:01 +04:00
net_dev = net . source
2010-09-03 23:19:54 +04:00
net_type = net . type
2009-07-26 23:54:14 +04:00
# Generate a new MAC
2018-03-20 19:18:35 +03:00
newmac = DeviceInterface . generate_mac (
2013-07-15 22:14:05 +04:00
self . conn . get_backend ( ) )
2009-07-26 23:54:14 +04:00
# [ interface type, device name, origmac, newmac, label ]
2018-03-20 19:18:35 +03:00
if net_type == DeviceInterface . TYPE_USER :
2009-07-26 23:54:14 +04:00
label = _ ( " Usermode " )
2018-03-20 19:18:35 +03:00
elif net_type == DeviceInterface . TYPE_VIRTUAL :
2014-06-03 01:17:47 +04:00
net = None
2018-03-15 01:17:03 +03:00
for netobj in self . vm . conn . list_nets ( ) :
2014-06-03 01:17:47 +04:00
if netobj . get_name ( ) == net_dev :
net = netobj
break
2009-07-26 23:54:14 +04:00
if net :
label = " "
2009-11-16 15:14:49 +03:00
desc = net . pretty_forward_mode ( )
2009-07-26 23:54:14 +04:00
label + = " %s " % desc
else :
label = ( _ ( " Virtual Network " ) +
( net_dev and " %s " % net_dev or " " ) )
else :
# 'bridge' or anything else
label = ( net_type . capitalize ( ) +
2010-09-03 23:19:54 +04:00
( net_dev and ( " %s " % net_dev ) or " " ) )
2009-07-26 23:54:14 +04:00
build_net_row ( label , mac , newmac )
2017-10-11 14:35:46 +03:00
no_net = ( not list ( self . net_list . keys ( ) ) )
2013-09-02 04:18:14 +04:00
self . widget ( " clone-network-box " ) . set_visible ( not no_net )
self . widget ( " clone-no-net " ) . set_visible ( no_net )
2009-07-26 23:54:14 +04:00
def check_all_storage ( self ) :
"""
Determine which storage is cloneable , and which isn ' t
"""
2018-03-21 21:42:50 +03:00
diskinfos = self . vm . xmlobj . devices . disk
2009-07-26 23:54:14 +04:00
cd = self . clone_design
storage_list = { }
# We need to determine which disks fail (and why).
2013-04-12 00:32:00 +04:00
all_targets = [ d . target for d in diskinfos ]
2009-07-26 23:54:14 +04:00
for disk in diskinfos :
2010-09-03 22:10:04 +04:00
force_target = disk . target
path = disk . path
ro = disk . read_only
shared = disk . shareable
devtype = disk . device
2009-07-26 23:54:14 +04:00
size = None
clone_path = None
failinfo = " "
definfo = " "
storage_row = [ ]
2009-11-16 15:03:01 +03:00
storage_row . insert ( STORAGE_INFO_ORIG_PATH , path or " - " )
2009-07-26 23:54:14 +04:00
storage_row . insert ( STORAGE_INFO_NEW_PATH , clone_path )
storage_row . insert ( STORAGE_INFO_TARGET , force_target )
storage_row . insert ( STORAGE_INFO_SIZE , size )
storage_row . insert ( STORAGE_INFO_DEVTYPE , devtype )
storage_row . insert ( STORAGE_INFO_DO_CLONE , False )
storage_row . insert ( STORAGE_INFO_CAN_CLONE , False )
storage_row . insert ( STORAGE_INFO_CAN_SHARE , False )
storage_row . insert ( STORAGE_INFO_DO_DEFAULT , False )
storage_row . insert ( STORAGE_INFO_DEFINFO , definfo )
storage_row . insert ( STORAGE_INFO_FAILINFO , failinfo )
storage_row . insert ( STORAGE_INFO_COMBO , None )
2010-12-12 21:39:45 +03:00
storage_row . insert ( STORAGE_INFO_MANUAL_PATH , False )
2009-07-26 23:54:14 +04:00
skip_targets = all_targets [ : ]
skip_targets . remove ( force_target )
vol = self . conn . get_vol_by_path ( path )
2019-03-05 12:16:06 +03:00
default , definfo , can_default = do_we_default ( self . conn , vol , path ,
ro , shared , devtype )
2009-07-26 23:54:14 +04:00
def storage_add ( failinfo = None ) :
2014-10-28 02:20:20 +03:00
# pylint: disable=cell-var-from-loop
2009-07-26 23:54:14 +04:00
storage_row [ STORAGE_INFO_DEFINFO ] = definfo
storage_row [ STORAGE_INFO_DO_DEFAULT ] = default
storage_row [ STORAGE_INFO_CAN_SHARE ] = bool ( definfo )
if failinfo :
storage_row [ STORAGE_INFO_FAILINFO ] = failinfo
storage_row [ STORAGE_INFO_DO_CLONE ] = False
storage_list [ force_target ] = storage_row
# If origdisk is empty, deliberately make it fail
if not path :
storage_add ( _ ( " Nothing to clone. " ) )
continue
try :
cd . skip_target = skip_targets
cd . setup_original ( )
2017-05-05 19:47:21 +03:00
except Exception as e :
2019-06-17 04:12:39 +03:00
log . exception ( " Disk target ' %s ' caused clone error " ,
2009-07-26 23:54:14 +04:00
force_target )
storage_add ( str ( e ) )
continue
can_clone , cloneinfo = can_we_clone ( self . conn , vol , path )
if not can_clone :
storage_add ( cloneinfo )
continue
2017-03-06 11:28:50 +03:00
storage_row [ STORAGE_INFO_CAN_CLONE ] = True
# If we cannot create default clone_path don't even try to do that
2019-03-05 12:16:06 +03:00
if not can_default :
2017-03-06 11:28:50 +03:00
storage_add ( )
continue
2009-07-26 23:54:14 +04:00
try :
# Generate disk path, make sure that works
2010-12-12 21:39:45 +03:00
clone_path = self . generate_clone_path_name ( path )
2009-07-26 23:54:14 +04:00
2019-06-17 04:12:39 +03:00
log . debug ( " Original path: %s \n Generated clone path: %s " ,
2012-01-17 07:04:40 +04:00
path , clone_path )
2009-07-26 23:54:14 +04:00
2013-07-03 02:30:46 +04:00
cd . clone_paths = clone_path
2013-09-29 19:31:03 +04:00
size = cd . original_disks [ 0 ] . get_size ( )
2017-05-05 19:47:21 +03:00
except Exception as e :
2019-06-17 04:12:39 +03:00
log . exception ( " Error setting generated path ' %s ' " ,
2009-07-26 23:54:14 +04:00
clone_path )
storage_add ( str ( e ) )
storage_row [ STORAGE_INFO_NEW_PATH ] = clone_path
storage_row [ STORAGE_INFO_SIZE ] = self . pretty_storage ( size )
storage_add ( )
return storage_list , all_targets
2010-12-12 21:39:45 +03:00
def generate_clone_path_name ( self , origpath , newname = None ) :
cd = self . clone_design
if not newname :
newname = cd . clone_name
2013-07-03 02:30:46 +04:00
clone_path = cd . generate_clone_disk_path ( origpath ,
newname = newname )
2010-12-12 21:39:45 +03:00
return clone_path
def set_paths_from_clone_name ( self ) :
cd = self . clone_design
2011-07-14 21:13:13 +04:00
newname = self . widget ( " clone-new-name " ) . get_text ( )
2010-12-12 21:39:45 +03:00
if not newname :
return
if cd . clone_name == newname :
return
2017-10-11 14:35:46 +03:00
for row in list ( self . storage_list . values ( ) ) :
2010-12-12 21:39:45 +03:00
origpath = row [ STORAGE_INFO_ORIG_PATH ]
if row [ STORAGE_INFO_MANUAL_PATH ] :
continue
if not row [ STORAGE_INFO_DO_CLONE ] :
return
try :
newpath = self . generate_clone_path_name ( origpath , newname )
row [ STORAGE_INFO_NEW_PATH ] = newpath
2017-05-05 19:47:21 +03:00
except Exception as e :
2019-06-17 04:12:39 +03:00
log . debug ( " Generating new path from clone name failed: %s " ,
2015-07-10 15:07:02 +03:00
str ( e ) )
2010-12-12 21:39:45 +03:00
2009-07-26 23:54:14 +04:00
def build_storage_entry ( self , disk , storage_box ) :
origpath = disk [ STORAGE_INFO_ORIG_PATH ]
devtype = disk [ STORAGE_INFO_DEVTYPE ]
size = disk [ STORAGE_INFO_SIZE ]
can_clone = disk [ STORAGE_INFO_CAN_CLONE ]
2009-12-02 05:56:12 +03:00
do_clone = disk [ STORAGE_INFO_DO_CLONE ]
2009-07-26 23:54:14 +04:00
can_share = disk [ STORAGE_INFO_CAN_SHARE ]
is_default = disk [ STORAGE_INFO_DO_DEFAULT ]
definfo = disk [ STORAGE_INFO_DEFINFO ]
failinfo = disk [ STORAGE_INFO_FAILINFO ]
target = disk [ STORAGE_INFO_TARGET ]
2018-03-15 01:17:03 +03:00
orig_name = self . vm . get_name ( )
2009-07-26 23:54:14 +04:00
disk_label = os . path . basename ( origpath )
info_label = None
if not can_clone :
2012-05-14 17:24:56 +04:00
info_label = Gtk . Label ( )
2009-07-26 23:54:14 +04:00
info_label . set_alignment ( 0 , .5 )
info_label . set_markup ( " <span size= ' small ' > %s </span> " % failinfo )
2014-09-20 06:08:22 +04:00
info_label . set_line_wrap ( True )
2009-07-26 23:54:14 +04:00
if not is_default :
disk_label + = ( definfo and " ( %s ) " % definfo or " " )
# Build icon
2012-05-14 17:24:56 +04:00
icon = Gtk . Image ( )
2018-03-20 19:18:35 +03:00
if devtype == virtinst . DeviceDisk . DEVICE_FLOPPY :
2009-07-26 23:54:14 +04:00
iconname = " media-floppy "
2018-03-20 19:18:35 +03:00
elif devtype == virtinst . DeviceDisk . DEVICE_CDROM :
2009-07-26 23:54:14 +04:00
iconname = " media-optical "
else :
iconname = " drive-harddisk "
2012-05-14 17:24:56 +04:00
icon . set_from_icon_name ( iconname , Gtk . IconSize . MENU )
disk_name_label = Gtk . Label ( label = disk_label )
2009-07-26 23:54:14 +04:00
disk_name_label . set_alignment ( 0 , .5 )
2012-05-14 17:24:56 +04:00
disk_name_box = Gtk . HBox ( spacing = 9 )
2013-01-13 01:13:53 +04:00
disk_name_box . pack_start ( icon , False , False , 0 )
disk_name_box . pack_start ( disk_name_label , True , True , 0 )
2009-07-26 23:54:14 +04:00
def sep_func ( model , it , combo ) :
2010-12-09 19:22:35 +03:00
ignore = combo
2009-07-26 23:54:14 +04:00
return model [ it ] [ 2 ]
# [String, sensitive, is sep]
2012-05-14 17:24:56 +04:00
model = Gtk . ListStore ( str , bool , bool )
2013-01-13 01:13:53 +04:00
option_combo = Gtk . ComboBox ( )
option_combo . set_model ( model )
2012-05-14 17:24:56 +04:00
text = Gtk . CellRendererText ( )
2013-01-13 01:13:53 +04:00
option_combo . pack_start ( text , True )
2009-07-26 23:54:14 +04:00
option_combo . add_attribute ( text , " text " , 0 )
option_combo . add_attribute ( text , " sensitive " , 1 )
option_combo . set_row_separator_func ( sep_func , option_combo )
option_combo . connect ( " changed " , self . storage_combo_changed , target )
2012-05-14 17:24:56 +04:00
vbox = Gtk . VBox ( spacing = 1 )
2009-07-26 23:54:14 +04:00
if can_clone or can_share :
model . insert ( STORAGE_COMBO_CLONE ,
[ ( _ ( " Clone this disk " ) +
( size and " ( %s ) " % size or " " ) ) ,
can_clone , False ] )
model . insert ( STORAGE_COMBO_SHARE ,
2009-07-29 05:22:40 +04:00
[ _ ( " Share disk with %s " ) % orig_name , can_share ,
False ] )
2009-07-26 23:54:14 +04:00
model . insert ( STORAGE_COMBO_SEP , [ " " , False , True ] )
model . insert ( STORAGE_COMBO_DETAILS ,
[ _ ( " Details... " ) , True , False ] )
2009-12-02 05:56:12 +03:00
if ( can_clone and is_default ) or do_clone :
2009-07-26 23:54:14 +04:00
option_combo . set_active ( STORAGE_COMBO_CLONE )
else :
option_combo . set_active ( STORAGE_COMBO_SHARE )
else :
model . insert ( STORAGE_COMBO_CLONE ,
[ _ ( " Storage cannot be shared or cloned. " ) ,
False , False ] )
option_combo . set_active ( STORAGE_COMBO_CLONE )
2013-01-13 01:13:53 +04:00
vbox . pack_start ( disk_name_box , False , False , 0 )
vbox . pack_start ( option_combo , False , False , 0 )
2009-07-26 23:54:14 +04:00
if info_label :
2013-01-13 01:13:53 +04:00
vbox . pack_start ( info_label , False , False , 0 )
storage_box . pack_start ( vbox , False , False , 0 )
2009-07-26 23:54:14 +04:00
disk [ STORAGE_INFO_COMBO ] = option_combo
def populate_storage_lists ( self ) :
2011-07-14 21:13:13 +04:00
storage_box = self . widget ( " clone-storage-box " )
2009-07-26 23:54:14 +04:00
for c in storage_box . get_children ( ) :
storage_box . remove ( c )
2011-04-14 21:12:24 +04:00
c . destroy ( )
2009-07-26 23:54:14 +04:00
for target in self . target_list :
disk = self . storage_list [ target ]
self . build_storage_entry ( disk , storage_box )
num_c = min ( len ( self . target_list ) , 3 )
if num_c :
2011-07-14 21:13:13 +04:00
scroll = self . widget ( " clone-storage-scroll " )
2010-12-10 19:47:07 +03:00
scroll . set_size_request ( - 1 , 80 * num_c )
2009-07-26 23:54:14 +04:00
storage_box . show_all ( )
no_storage = not bool ( len ( self . target_list ) )
2013-09-02 04:18:14 +04:00
self . widget ( " clone-storage-box " ) . set_visible ( not no_storage )
self . widget ( " clone-no-storage-pass " ) . set_visible ( no_storage )
2009-07-26 23:54:14 +04:00
skip_targets = [ ]
new_disks = [ ]
for target in self . target_list :
do_clone = self . storage_list [ target ] [ STORAGE_INFO_DO_CLONE ]
new_path = self . storage_list [ target ] [ STORAGE_INFO_NEW_PATH ]
if do_clone :
new_disks . append ( new_path )
else :
skip_targets . append ( target )
self . clone_design . skip_target = skip_targets
2017-03-06 23:54:51 +03:00
try :
self . clone_design . clone_paths = new_disks
2017-05-05 19:47:21 +03:00
except Exception as e :
2017-03-06 23:54:51 +03:00
# Just log the error and go on. The UI will fail later if needed
2019-06-17 04:12:39 +03:00
log . debug ( " Error setting clone_paths: %s " , str ( e ) )
2009-07-26 23:54:14 +04:00
# If any storage cannot be cloned or shared, don't allow cloning
clone = True
tooltip = " "
2017-10-11 14:35:46 +03:00
for row in list ( self . storage_list . values ( ) ) :
2009-07-26 23:54:14 +04:00
can_clone = row [ STORAGE_INFO_CAN_CLONE ]
can_share = row [ STORAGE_INFO_CAN_SHARE ]
if not ( can_clone or can_share ) :
clone = False
tooltip = _ ( " One or more disks cannot be cloned or shared. " )
break
2011-07-14 21:13:13 +04:00
ok_button = self . widget ( " clone-ok " )
2009-07-26 23:54:14 +04:00
ok_button . set_sensitive ( clone )
2012-05-14 17:24:56 +04:00
ok_button . set_tooltip_text ( tooltip )
2009-07-26 23:54:14 +04:00
def net_change_mac ( self , ignore , origmac ) :
2010-12-10 19:47:07 +03:00
row = self . net_list [ origmac ]
2009-07-26 23:54:14 +04:00
orig_mac = row [ NETWORK_INFO_ORIG_MAC ]
2010-12-10 19:47:07 +03:00
new_mac = row [ NETWORK_INFO_NEW_MAC ]
2009-07-26 23:54:14 +04:00
typ = row [ NETWORK_INFO_LABEL ]
2012-02-02 02:26:46 +04:00
self . widget ( " change-mac-orig " ) . set_text ( orig_mac )
self . widget ( " change-mac-type " ) . set_text ( typ )
self . widget ( " change-mac-new " ) . set_text ( new_mac )
2009-07-26 23:54:14 +04:00
self . change_mac . show_all ( )
def storage_combo_changed ( self , src , target ) :
idx = src . get_active ( )
row = self . storage_list [ target ]
if idx == STORAGE_COMBO_CLONE :
row [ STORAGE_INFO_DO_CLONE ] = True
return
elif idx == STORAGE_COMBO_SHARE :
row [ STORAGE_INFO_DO_CLONE ] = False
return
elif idx != STORAGE_COMBO_DETAILS :
return
do_clone = row [ STORAGE_INFO_DO_CLONE ]
if do_clone :
src . set_active ( STORAGE_COMBO_CLONE )
else :
src . set_active ( STORAGE_COMBO_SHARE )
# Show storage
2018-09-29 21:27:34 +03:00
self . storage_change_path ( row )
2009-07-26 23:54:14 +04:00
def change_storage_doclone_toggled ( self , src ) :
do_clone = src . get_active ( )
2012-02-02 02:26:46 +04:00
self . widget ( " change-storage-new " ) . set_sensitive ( do_clone )
self . widget ( " change-storage-browse " ) . set_sensitive ( do_clone )
2009-07-26 23:54:14 +04:00
def storage_change_path ( self , row ) :
2010-12-12 21:39:45 +03:00
# If storage paths are dependent on manually entered clone name,
# make sure they are up to date
self . set_paths_from_clone_name ( )
2009-07-26 23:54:14 +04:00
orig = row [ STORAGE_INFO_ORIG_PATH ]
new = row [ STORAGE_INFO_NEW_PATH ]
tgt = row [ STORAGE_INFO_TARGET ]
size = row [ STORAGE_INFO_SIZE ]
can_clone = row [ STORAGE_INFO_CAN_CLONE ]
can_share = row [ STORAGE_INFO_CAN_SHARE ]
do_clone = row [ STORAGE_INFO_DO_CLONE ]
2012-02-02 02:26:46 +04:00
self . widget ( " change-storage-doclone " ) . set_active ( True )
self . widget ( " change-storage-doclone " ) . toggled ( )
self . widget ( " change-storage-orig " ) . set_text ( orig )
self . widget ( " change-storage-target " ) . set_text ( tgt )
self . widget ( " change-storage-size " ) . set_text ( size or " - " )
self . widget ( " change-storage-doclone " ) . set_active ( do_clone )
2009-07-26 23:54:14 +04:00
if can_clone :
2012-02-02 02:26:46 +04:00
self . widget ( " change-storage-new " ) . set_text ( new or " " )
2009-07-26 23:54:14 +04:00
else :
2012-02-02 02:26:46 +04:00
self . widget ( " change-storage-new " ) . set_text ( " " )
self . widget ( " change-storage-doclone " ) . set_sensitive ( can_clone and
can_share )
2009-07-26 23:54:14 +04:00
2012-02-02 02:26:46 +04:00
self . widget ( " vmm-change-storage " ) . show_all ( )
2009-07-26 23:54:14 +04:00
def change_mac_finish ( self , ignore ) :
2012-02-02 02:26:46 +04:00
orig = self . widget ( " change-mac-orig " ) . get_text ( )
new = self . widget ( " change-mac-new " ) . get_text ( )
2009-07-26 23:54:14 +04:00
row = self . net_list [ orig ]
try :
2018-09-03 23:54:23 +03:00
DeviceInterface . is_conflict_net ( self . conn . get_backend ( ) , new )
2009-07-26 23:54:14 +04:00
row [ NETWORK_INFO_NEW_MAC ] = new
2017-05-05 19:47:21 +03:00
except Exception as e :
2011-04-06 19:22:03 +04:00
self . err . show_err ( _ ( " Error changing MAC address: %s " ) % str ( e ) )
2009-07-26 23:54:14 +04:00
return
self . change_mac_close ( )
def change_storage_finish ( self , ignore ) :
2012-02-02 02:26:46 +04:00
target = self . widget ( " change-storage-target " ) . get_text ( )
2009-07-26 23:54:14 +04:00
row = self . storage_list [ target ]
# Sync 'do clone' checkbox, and main dialog combo
combo = row [ STORAGE_INFO_COMBO ]
2012-02-02 02:26:46 +04:00
do_clone = self . widget ( " change-storage-doclone " ) . get_active ( )
2009-12-02 05:56:12 +03:00
if do_clone :
2009-07-26 23:54:14 +04:00
combo . set_active ( STORAGE_COMBO_CLONE )
else :
combo . set_active ( STORAGE_COMBO_SHARE )
2009-12-02 05:56:12 +03:00
row [ STORAGE_INFO_DO_CLONE ] = do_clone
2009-07-26 23:54:14 +04:00
if not do_clone :
self . change_storage_close ( )
return
2012-02-02 02:26:46 +04:00
new_path = self . widget ( " change-storage-new " ) . get_text ( )
2009-07-26 23:54:14 +04:00
2018-03-20 19:18:35 +03:00
if virtinst . DeviceDisk . path_definitely_exists ( self . clone_design . conn ,
2014-12-05 04:00:34 +03:00
new_path ) :
2009-07-26 23:54:14 +04:00
res = self . err . yes_no ( _ ( " Cloning will overwrite the existing "
" file " ) ,
_ ( " Using an existing image will overwrite "
" the path during the clone process. Are "
" you sure you want to use this path? " ) )
if not res :
return
try :
2013-07-03 02:30:46 +04:00
self . clone_design . clone_paths = new_path
2009-07-26 23:54:14 +04:00
row [ STORAGE_INFO_NEW_PATH ] = new_path
2010-12-12 21:39:45 +03:00
row [ STORAGE_INFO_MANUAL_PATH ] = True
2017-03-06 11:28:49 +03:00
self . populate_storage_lists ( )
2017-05-05 19:47:21 +03:00
except Exception as e :
2011-04-06 19:22:03 +04:00
self . err . show_err ( _ ( " Error changing storage path: %s " ) % str ( e ) )
2009-07-26 23:54:14 +04:00
return
self . change_storage_close ( )
def pretty_storage ( self , size ) :
if not size :
return " "
2014-06-16 07:56:02 +04:00
return " %.1f GiB " % float ( size )
2009-07-26 23:54:14 +04:00
# Listeners
def validate ( self ) :
2010-12-12 21:39:45 +03:00
self . set_paths_from_clone_name ( )
2011-07-14 21:13:13 +04:00
name = self . widget ( " clone-new-name " ) . get_text ( )
2009-07-26 23:54:14 +04:00
# Make another clone_design
cd = self . build_new_clone_design ( name )
# Set MAC addresses
2013-07-03 02:30:46 +04:00
clonemacs = [ ]
2009-07-26 23:54:14 +04:00
for mac in self . mac_list :
row = self . net_list [ mac ]
2013-07-03 02:30:46 +04:00
clonemacs . append ( row [ NETWORK_INFO_NEW_MAC ] )
cd . clone_macs = clonemacs
2009-07-26 23:54:14 +04:00
skip_targets = [ ]
new_paths = [ ]
warn_str = " "
for target in self . target_list :
path = self . storage_list [ target ] [ STORAGE_INFO_ORIG_PATH ]
new_path = self . storage_list [ target ] [ STORAGE_INFO_NEW_PATH ]
do_clone = self . storage_list [ target ] [ STORAGE_INFO_DO_CLONE ]
do_default = self . storage_list [ target ] [ STORAGE_INFO_DO_DEFAULT ]
if do_clone :
new_paths . append ( new_path )
else :
skip_targets . append ( target )
if not path or path == ' - ' :
continue
if not do_default :
continue
warn_str + = " %s : %s \n " % ( target , path )
cd . skip_target = skip_targets
cd . setup_original ( )
2013-07-03 02:30:46 +04:00
cd . clone_paths = new_paths
2009-07-26 23:54:14 +04:00
if warn_str :
res = self . err . ok_cancel (
_ ( " Skipping disks may cause data to be overwritten. " ) ,
_ ( " The following disk devices will not be cloned: \n \n %s \n "
" Running the new guest could overwrite data in these "
" disk images. " )
2017-08-17 14:01:28 +03:00
% warn_str )
2009-07-26 23:54:14 +04:00
if not res :
return False
cd . setup_clone ( )
self . clone_design = cd
return True
2018-03-15 01:17:03 +03:00
def _finish_cb ( self , error , details , conn ) :
2017-04-27 22:00:17 +03:00
self . reset_finish_cursor ( )
2013-09-07 04:59:01 +04:00
if error is not None :
error = ( _ ( " Error creating virtual machine clone ' %s ' : %s " ) %
( self . clone_design . clone_name , error ) )
self . err . show_err ( error , details = details )
2015-04-15 01:42:56 +03:00
return
2018-03-15 01:17:03 +03:00
conn . schedule_priority_tick ( pollvm = True )
2015-04-15 01:42:56 +03:00
self . close ( )
2013-09-07 04:59:01 +04:00
2010-12-09 19:22:35 +03:00
def finish ( self , src_ignore ) :
2009-07-26 23:54:14 +04:00
try :
if not self . validate ( ) :
return
2017-05-05 19:47:21 +03:00
except Exception as e :
2011-04-06 19:22:03 +04:00
self . err . show_err ( _ ( " Uncaught error validating input: %s " ) % str ( e ) )
2009-07-26 23:54:14 +04:00
return
2017-04-27 22:00:17 +03:00
self . set_finish_cursor ( )
2009-07-26 23:54:14 +04:00
title = ( _ ( " Creating virtual machine clone ' %s ' " ) %
self . clone_design . clone_name )
text = title
2013-07-03 02:30:46 +04:00
if self . clone_design . clone_disks :
2009-07-26 23:54:14 +04:00
text = title + _ ( " and selected storage (this may take a while) " )
2018-03-15 01:17:03 +03:00
progWin = vmmAsyncJob ( self . _async_clone , [ ] ,
self . _finish_cb , [ self . conn ] ,
2013-09-07 04:59:01 +04:00
title , text , self . topwin )
progWin . run ( )
2009-07-26 23:54:14 +04:00
def _async_clone ( self , asyncjob ) :
2019-06-17 00:32:41 +03:00
meter = asyncjob . get_meter ( )
2015-04-15 01:42:56 +03:00
2019-06-17 00:32:41 +03:00
refresh_pools = [ ]
for disk in self . clone_design . clone_disks :
if not disk . wants_storage_creation ( ) :
continue
2015-04-15 01:42:56 +03:00
2019-06-17 00:32:41 +03:00
pool = disk . get_parent_pool ( )
if not pool :
continue
2015-04-15 01:42:56 +03:00
2019-06-17 00:32:41 +03:00
poolname = pool . name ( )
if poolname not in refresh_pools :
refresh_pools . append ( poolname )
2015-04-15 01:42:56 +03:00
2019-06-17 00:32:41 +03:00
self . clone_design . start_duplicate ( meter )
2015-04-15 01:42:56 +03:00
2019-06-17 00:32:41 +03:00
for poolname in refresh_pools :
try :
pool = self . conn . get_pool ( poolname )
self . idle_add ( pool . refresh )
except Exception :
2019-06-17 04:12:39 +03:00
log . debug ( " Error looking up pool= %s for refresh after "
2019-06-17 02:23:27 +03:00
" VM clone. " , poolname , exc_info = True )
2009-07-26 23:54:14 +04:00
def change_storage_browse ( self , ignore ) :
2010-12-09 19:22:35 +03:00
def callback ( src_ignore , txt ) :
2012-02-02 02:26:46 +04:00
self . widget ( " change-storage-new " ) . set_text ( txt )
2009-07-26 23:54:14 +04:00
2015-04-09 01:29:48 +03:00
if self . storage_browser and self . storage_browser . conn != self . conn :
self . storage_browser . cleanup ( )
self . storage_browser = None
2012-11-08 17:15:02 +04:00
if self . storage_browser is None :
2010-12-09 01:26:19 +03:00
self . storage_browser = vmmStorageBrowser ( self . conn )
2014-12-10 02:37:00 +03:00
self . storage_browser . set_finish_cb ( callback )
2009-07-26 23:54:14 +04:00
2015-04-09 01:29:48 +03:00
self . storage_browser . show ( self . topwin )