2013-04-01 23:46:30 +04:00
# Copyright (C) 2009, 2013 Red Hat, Inc.
2009-11-15 23:17:03 +03: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-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
import traceback
2009-11-15 23:17:03 +03:00
2013-02-16 23:03:30 +04:00
from gi . repository import Gtk
2015-04-11 19:57:32 +03:00
from gi . repository import Pango
2011-04-18 20:39:53 +04:00
2019-06-17 04:12:39 +03:00
from virtinst import log
2019-06-08 01:21:24 +03:00
from virtinst import xmlutil
2009-11-15 23:17:03 +03:00
2019-06-17 05:19:17 +03:00
from . lib import uiutil
2014-09-13 00:10:45 +04:00
from . asyncjob import vmmAsyncJob
2018-03-14 20:13:22 +03:00
from . baseclass import vmmGObjectUI
from . connmanager import vmmConnectionManager
2019-06-17 04:40:46 +03:00
from . object . domain import vmmDomain
2009-11-15 23:17:03 +03:00
2013-04-13 22:34:52 +04:00
2015-04-16 18:21:34 +03:00
NUM_COLS = 3
( COL_LABEL ,
COL_URI ,
COL_CAN_MIGRATE ) = range ( NUM_COLS )
2010-12-09 01:26:19 +03:00
class vmmMigrateDialog ( vmmGObjectUI ) :
2018-03-15 00:50:22 +03:00
@classmethod
def show_instance ( cls , parentobj , vm ) :
try :
if not cls . _instance :
2018-03-17 23:08:20 +03:00
cls . _instance = vmmMigrateDialog ( )
2018-03-15 00:50:22 +03:00
cls . _instance . show ( parentobj . topwin , vm )
except Exception as e :
parentobj . err . show_err (
_ ( " Error launching migrate dialog: %s " ) % str ( e ) )
2018-03-14 20:13:22 +03:00
def __init__ ( self ) :
2013-09-23 00:10:16 +04:00
vmmGObjectUI . __init__ ( self , " migrate.ui " , " vmm-migrate " )
2015-04-16 18:21:34 +03:00
self . vm = None
2009-11-18 21:51:36 +03:00
2013-02-16 22:31:46 +04:00
self . builder . connect_signals ( {
2017-08-05 09:39:32 +03:00
" on_vmm_migrate_delete_event " : self . _delete_event ,
" on_migrate_cancel_clicked " : self . _cancel_clicked ,
" on_migrate_finish_clicked " : self . _finish_clicked ,
" on_migrate_dest_changed " : self . _destconn_changed ,
" on_migrate_set_address_toggled " : self . _set_address_toggled ,
" on_migrate_set_port_toggled " : self . _set_port_toggled ,
" on_migrate_mode_changed " : self . _mode_changed ,
2009-11-15 23:17:03 +03: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-11-15 23:17:03 +03:00
2018-03-14 20:13:22 +03:00
self . _init_state ( )
2009-11-18 21:51:36 +03:00
2015-04-16 18:21:34 +03:00
def _cleanup ( self ) :
self . vm = None
2018-03-15 00:56:28 +03:00
@property
def _connobjs ( self ) :
return vmmConnectionManager . get_instance ( ) . conns
2015-04-16 18:21:34 +03:00
2018-03-15 14:43:56 +03:00
@property
def conn ( self ) :
return self . vm and self . vm . conn or None
2015-04-16 18:21:34 +03:00
##############
# Public API #
##############
def show ( self , parent , vm ) :
2019-06-17 04:12:39 +03:00
log . debug ( " Showing migrate wizard " )
2018-03-18 02:42:19 +03:00
self . _set_vm ( vm )
2015-04-16 18:21:34 +03:00
self . _reset_state ( )
2011-04-14 16:47:42 +04:00
self . topwin . set_transient_for ( parent )
2009-11-15 23:17:03 +03:00
self . topwin . present ( )
def close ( self , ignore1 = None , ignore2 = None ) :
2019-06-17 04:12:39 +03:00
log . debug ( " Closing migrate wizard " )
2009-11-15 23:17:03 +03:00
self . topwin . hide ( )
2018-03-18 02:42:19 +03:00
self . _set_vm ( None )
2009-11-15 23:17:03 +03:00
return 1
2018-03-18 02:42:19 +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-04-13 17:27:02 +04:00
2015-04-16 18:21:34 +03:00
################
# Init helpers #
################
2011-04-13 17:27:02 +04:00
2018-03-14 20:13:22 +03:00
def _init_state ( self ) :
2015-04-16 18:21:34 +03:00
# Connection combo
cols = [ None ] * NUM_COLS
cols [ COL_LABEL ] = str
cols [ COL_URI ] = str
cols [ COL_CAN_MIGRATE ] = bool
model = Gtk . ListStore ( * cols )
combo = self . widget ( " migrate-dest " )
combo . set_model ( model )
text = uiutil . init_combo_text_column ( combo , COL_LABEL )
2015-04-11 19:57:32 +03:00
text . set_property ( " ellipsize " , Pango . EllipsizeMode . MIDDLE )
text . set_property ( " width-chars " , 30 )
2015-04-16 18:21:34 +03:00
combo . add_attribute ( text , ' sensitive ' , COL_CAN_MIGRATE )
model . set_sort_column_id ( COL_LABEL , Gtk . SortType . ASCENDING )
def _sorter ( model , iter1 , iter2 , ignore ) :
2018-01-28 00:21:59 +03:00
def _cmp ( a , b ) :
return ( ( a > b ) - ( a < b ) )
2015-04-16 18:21:34 +03:00
row1 = model [ iter1 ]
row2 = model [ iter2 ]
if row1 [ COL_URI ] is None :
return - 1
if row2 [ COL_URI ] is None :
return 1
2018-01-28 00:21:59 +03:00
return _cmp ( row1 [ COL_LABEL ] , row2 [ COL_LABEL ] )
2015-04-16 18:21:34 +03:00
model . set_sort_func ( COL_LABEL , _sorter )
# Mode combo
combo = self . widget ( " migrate-mode " )
# label, is_tunnel
model = Gtk . ListStore ( str , bool )
model . append ( [ _ ( " Direct " ) , False ] )
model . append ( [ _ ( " Tunnelled " ) , True ] )
combo . set_model ( model )
uiutil . init_combo_text_column ( combo , 0 )
2009-11-18 21:51:36 +03:00
2018-03-15 00:56:28 +03:00
self . widget ( " migrate-dest " ) . emit ( " changed " )
2015-04-16 18:21:34 +03:00
self . widget ( " migrate-mode " ) . set_tooltip_text (
self . widget ( " migrate-mode-label " ) . get_tooltip_text ( ) )
self . widget ( " migrate-unsafe " ) . set_tooltip_text (
self . widget ( " migrate-unsafe-label " ) . get_tooltip_text ( ) )
2015-04-22 00:32:43 +03:00
self . widget ( " migrate-temporary " ) . set_tooltip_text (
self . widget ( " migrate-temporary-label " ) . get_tooltip_text ( ) )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
def _reset_state ( self ) :
2020-02-02 01:16:45 +03:00
title_str = ( " <span size= ' large ' > %s ' %s ' </span> " %
2019-06-08 01:21:24 +03:00
( _ ( " Migrate " ) , xmlutil . xml_escape ( self . vm . get_name ( ) ) ) )
2013-09-27 18:26:43 +04:00
self . widget ( " header-label " ) . set_markup ( title_str )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
self . widget ( " migrate-advanced-expander " ) . set_expanded ( False )
2011-07-14 21:13:13 +04:00
self . widget ( " migrate-cancel " ) . grab_focus ( )
2009-12-15 00:48:35 +03:00
2015-04-16 18:21:34 +03:00
self . widget ( " config-box " ) . set_visible ( True )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
hostname = self . conn . libvirt_gethostname ( )
srctext = " %s ( %s ) " % ( hostname , self . conn . get_pretty_desc ( ) )
self . widget ( " migrate-label-name " ) . set_text ( self . vm . get_name_or_title ( ) )
self . widget ( " migrate-label-src " ) . set_text ( srctext )
self . widget ( " migrate-label-src " ) . set_tooltip_text ( self . conn . get_uri ( ) )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
self . widget ( " migrate-set-address " ) . set_active ( True )
self . widget ( " migrate-set-address " ) . emit ( " toggled " )
self . widget ( " migrate-set-port " ) . set_active ( True )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
self . widget ( " migrate-mode " ) . set_active ( 0 )
2013-12-11 01:33:12 +04:00
self . widget ( " migrate-unsafe " ) . set_active ( False )
2015-04-22 00:32:43 +03:00
self . widget ( " migrate-temporary " ) . set_active ( False )
2009-11-15 23:17:03 +03:00
if self . conn . is_xen ( ) :
# Default xen port is 8002
2011-07-14 21:13:13 +04:00
self . widget ( " migrate-port " ) . set_value ( 8002 )
2009-11-15 23:17:03 +03:00
else :
# QEMU migrate port range is 49152+64
2011-07-14 21:13:13 +04:00
self . widget ( " migrate-port " ) . set_value ( 49152 )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
self . _populate_destconn ( )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
#############
# Listeners #
#############
2013-12-11 01:33:12 +04:00
2015-04-16 18:21:34 +03:00
def _delete_event ( self , ignore1 , ignore2 ) :
self . close ( )
return 1
2013-12-11 01:33:12 +04:00
2015-04-16 18:21:34 +03:00
def _cancel_clicked ( self , src ) :
ignore = src
self . close ( )
2009-11-18 21:51:36 +03:00
2015-04-16 18:21:34 +03:00
def _finish_clicked ( self , src ) :
ignore = src
self . _finish ( )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
def _destconn_changed ( self , src ) :
2015-05-20 00:17:53 +03:00
row = uiutil . get_list_selected_row ( src )
2015-04-16 18:21:34 +03:00
if not row :
return
can_migrate = row and row [ COL_CAN_MIGRATE ] or False
uri = row [ COL_URI ]
2013-01-13 01:13:53 +04:00
tooltip = " "
2015-04-16 18:21:34 +03:00
if not can_migrate :
2009-11-18 21:51:36 +03:00
tooltip = _ ( " A valid destination connection must be selected. " )
2015-04-16 18:21:34 +03:00
self . widget ( " config-box " ) . set_visible ( can_migrate )
self . widget ( " migrate-finish " ) . set_sensitive ( can_migrate )
2012-05-14 17:24:56 +04:00
self . widget ( " migrate-finish " ) . set_tooltip_text ( tooltip )
2009-11-18 21:51:36 +03:00
2015-04-16 18:21:34 +03:00
address = " "
address_warning = " "
tunnel_warning = " "
tunnel_uri = " "
2018-03-15 00:56:28 +03:00
if can_migrate and uri in self . _connobjs :
destconn = self . _connobjs [ uri ]
2015-04-16 18:21:34 +03:00
tunnel_uri = destconn . get_uri ( )
if not destconn . is_remote ( ) :
tunnel_warning = _ ( " A remotely accessible libvirt URI "
" is required for tunneled migration, but the "
" selected connection is a local URI. Libvirt will "
" reject this unless you add a transport. " )
tunnel_warning = ( " <span size= ' small ' > %s </span> " %
tunnel_warning )
address = destconn . libvirt_gethostname ( )
if self . _is_localhost ( address ) :
address_warning = _ ( " The destination ' s hostname is "
" ' localhost ' , which will be rejected by libvirt. "
" You must configure the destination to have a valid "
" publicly accessible hostname. " )
address_warning = ( " <span size= ' small ' > %s </span> " %
address_warning )
self . widget ( " migrate-address " ) . set_text ( address )
uiutil . set_grid_row_visible (
self . widget ( " migrate-address-warning-box " ) , bool ( address_warning ) )
self . widget ( " migrate-address-warning-label " ) . set_markup ( address_warning )
self . widget ( " migrate-tunnel-uri " ) . set_text ( tunnel_uri )
uiutil . set_grid_row_visible (
self . widget ( " migrate-tunnel-warning-box " ) , bool ( tunnel_warning ) )
self . widget ( " migrate-tunnel-warning-label " ) . set_markup ( tunnel_warning )
def _set_address_toggled ( self , src ) :
2009-11-15 23:17:03 +03:00
enable = src . get_active ( )
2015-04-16 18:21:34 +03:00
self . widget ( " migrate-address " ) . set_visible ( enable )
self . widget ( " migrate-address-label " ) . set_visible ( not enable )
2011-07-14 21:13:13 +04:00
port_enable = self . widget ( " migrate-set-port " ) . get_active ( )
2015-04-16 18:21:34 +03:00
self . widget ( " migrate-set-port " ) . set_active ( enable and port_enable )
self . widget ( " migrate-set-port " ) . emit ( " toggled " )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
def _set_port_toggled ( self , src ) :
2009-11-15 23:17:03 +03:00
enable = src . get_active ( )
2015-04-16 18:21:34 +03:00
self . widget ( " migrate-port " ) . set_visible ( enable )
self . widget ( " migrate-port-label " ) . set_visible ( not enable )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
def _is_tunnel_selected ( self ) :
2015-05-20 00:17:53 +03:00
return uiutil . get_list_selection ( self . widget ( " migrate-mode " ) , column = 1 )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
def _mode_changed ( self , src ) :
ignore = src
is_tunnel = self . _is_tunnel_selected ( )
self . widget ( " migrate-direct-box " ) . set_visible ( not is_tunnel )
self . widget ( " migrate-tunnel-box " ) . set_visible ( is_tunnel )
2009-11-15 23:17:03 +03:00
2009-11-18 21:51:36 +03:00
2015-04-16 18:21:34 +03:00
###########################
# destconn combo handling #
###########################
2009-11-18 21:51:36 +03:00
2015-04-16 18:21:34 +03:00
def _is_localhost ( self , addr ) :
return not addr or addr . startswith ( " localhost " )
2009-11-18 21:51:36 +03:00
2015-04-16 18:21:34 +03:00
def _build_dest_row ( self , destconn ) :
2009-11-18 21:51:36 +03:00
driver = self . conn . get_driver ( )
origuri = self . conn . get_uri ( )
can_migrate = False
2014-07-05 02:20:08 +04:00
desc = destconn . get_pretty_desc ( )
2009-11-18 21:51:36 +03:00
reason = " "
desturi = destconn . get_uri ( )
if destconn . get_driver ( ) != driver :
2015-04-16 18:21:34 +03:00
reason = _ ( " Hypervisors do not match " )
2014-09-12 02:16:21 +04:00
elif destconn . is_disconnected ( ) :
2015-04-16 18:21:34 +03:00
reason = _ ( " Disconnected " )
2009-11-18 21:51:36 +03:00
elif destconn . get_uri ( ) == origuri :
2015-04-16 18:21:34 +03:00
reason = _ ( " Same connection " )
2014-09-12 02:16:21 +04:00
elif destconn . is_active ( ) :
2009-11-18 21:51:36 +03:00
can_migrate = True
2015-04-16 18:21:34 +03:00
if reason :
desc = " %s ( %s ) " % ( desc , reason )
return [ desc , desturi , can_migrate ]
def _populate_destconn ( self ) :
combo = self . widget ( " migrate-dest " )
model = combo . get_model ( )
model . clear ( )
rows = [ ]
2018-03-15 00:56:28 +03:00
for conn in list ( self . _connobjs . values ( ) ) :
2015-04-16 18:21:34 +03:00
rows . append ( self . _build_dest_row ( conn ) )
if not any ( [ row [ COL_CAN_MIGRATE ] for row in rows ] ) :
rows . insert ( 0 ,
[ _ ( " No usable connections available. " ) , None , False ] )
for row in rows :
model . append ( row )
2009-11-18 21:51:36 +03:00
2015-04-16 18:21:34 +03:00
combo . set_active ( 0 )
for idx , row in enumerate ( model ) :
if row [ COL_CAN_MIGRATE ] :
combo . set_active ( idx )
break
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
####################
# migrate handling #
####################
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
def _build_regular_migrate_uri ( self ) :
address = None
if self . widget ( " migrate-address " ) . get_visible ( ) :
address = self . widget ( " migrate-address " ) . get_text ( )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
port = None
if self . widget ( " migrate-port " ) . get_visible ( ) :
port = int ( self . widget ( " migrate-port " ) . get_value ( ) )
if not address :
return
if self . conn . is_xen ( ) :
2016-09-07 07:55:42 +03:00
uri = " %s " % address
2015-04-16 18:21:34 +03:00
else :
uri = " tcp: %s " % address
if port :
uri + = " : %s " % port
return uri
2009-11-15 23:17:03 +03:00
2013-09-07 04:59:01 +04:00
def _finish_cb ( self , error , details , destconn ) :
2017-04-27 22:00:17 +03:00
self . reset_finish_cursor ( )
2013-09-07 04:59:01 +04:00
if error :
error = _ ( " Unable to migrate guest: %s " ) % error
2015-04-16 18:21:34 +03:00
self . err . show_err ( error , details = details )
2013-09-07 04:59:01 +04:00
else :
destconn . schedule_priority_tick ( pollvm = True )
2018-03-15 00:56:28 +03:00
self . conn . schedule_priority_tick ( pollvm = True )
2013-09-07 04:59:01 +04:00
self . close ( )
2015-04-16 18:21:34 +03:00
def _finish ( self ) :
2009-11-15 23:17:03 +03:00
try :
2015-05-20 00:17:53 +03:00
row = uiutil . get_list_selected_row ( self . widget ( " migrate-dest " ) )
2015-04-16 18:21:34 +03:00
destlabel = row [ COL_LABEL ]
2018-03-15 00:56:28 +03:00
destconn = self . _connobjs . get ( row [ COL_URI ] )
2015-04-16 18:21:34 +03:00
tunnel = self . _is_tunnel_selected ( )
unsafe = self . widget ( " migrate-unsafe " ) . get_active ( )
2015-04-22 00:32:43 +03:00
temporary = self . widget ( " migrate-temporary " ) . get_active ( )
2015-04-16 18:21:34 +03:00
if tunnel :
uri = self . widget ( " migrate-tunnel-uri " ) . get_text ( )
else :
uri = self . _build_regular_migrate_uri ( )
2017-05-05 19:47:21 +03:00
except Exception as e :
2009-11-15 23:17:03 +03:00
details = " " . join ( traceback . format_exc ( ) )
self . err . show_err ( ( _ ( " Uncaught error validating input: %s " ) %
2011-04-06 19:22:03 +04:00
str ( e ) ) ,
details = details )
2009-11-15 23:17:03 +03:00
return
2017-04-27 22:00:17 +03:00
self . set_finish_cursor ( )
2009-11-15 23:17:03 +03:00
2013-06-14 01:05:33 +04:00
cancel_cb = None
2010-12-08 20:52:33 +03:00
if self . vm . getjobinfo_supported :
2015-04-16 18:21:34 +03:00
cancel_cb = ( self . _cancel_migration , self . vm )
if uri :
destlabel + = " " + uri
2010-12-08 20:52:33 +03:00
2013-09-07 04:59:01 +04:00
progWin = vmmAsyncJob (
self . _async_migrate ,
2015-04-22 00:32:43 +03:00
[ self . vm , destconn , uri , tunnel , unsafe , temporary ] ,
2013-09-07 04:59:01 +04:00
self . _finish_cb , [ destconn ] ,
2015-06-02 15:21:58 +03:00
_ ( " Migrating VM ' %s ' " ) % self . vm . get_name ( ) ,
2020-07-12 00:31:40 +03:00
( _ ( " Migrating VM ' %(name)s ' to %(host)s . This may take a while. " ) %
{ " name " : self . vm . get_name ( ) , " host " : destlabel } ) ,
2013-09-07 04:59:01 +04:00
self . topwin , cancel_cb = cancel_cb )
progWin . run ( )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
def _cancel_migration ( self , asyncjob , vm ) :
2019-06-17 04:12:39 +03:00
log . debug ( " Cancelling migrate job " )
2010-12-08 20:52:33 +03:00
if not vm :
return
try :
vm . abort_job ( )
2017-05-05 19:47:21 +03:00
except Exception as e :
2019-06-17 04:12:39 +03:00
log . exception ( " Error cancelling migrate job " )
2010-12-08 22:14:38 +03:00
asyncjob . show_warning ( _ ( " Error cancelling migrate job: %s " ) % e )
2010-12-08 20:52:33 +03:00
return
asyncjob . job_canceled = True
return
2010-12-10 17:57:42 +03:00
def _async_migrate ( self , asyncjob ,
2015-04-22 00:32:43 +03:00
origvm , origdconn , migrate_uri , tunnel , unsafe , temporary ) :
2012-02-10 19:24:43 +04:00
meter = asyncjob . get_meter ( )
2010-12-10 22:59:24 +03:00
2013-07-05 16:59:58 +04:00
srcconn = origvm . conn
dstconn = origdconn
2010-12-10 22:59:24 +03:00
2013-07-05 16:59:58 +04:00
vminst = srcconn . get_backend ( ) . lookupByName ( origvm . get_name ( ) )
2010-12-10 22:59:24 +03:00
vm = vmmDomain ( srcconn , vminst , vminst . UUID ( ) )
2019-06-17 04:12:39 +03:00
log . debug ( " Migrating vm= %s from %s to %s " , vm . get_name ( ) ,
2010-12-10 22:59:24 +03:00
srcconn . get_uri ( ) , dstconn . get_uri ( ) )
2015-04-22 00:32:43 +03:00
vm . migrate ( dstconn , migrate_uri , tunnel , unsafe , temporary ,
meter = meter )