2009-11-15 23:17:03 +03:00
#
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>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
#
import logging
2015-04-16 18:21:34 +03:00
import traceback
2009-11-15 23:17:03 +03:00
2012-05-14 17:24:56 +04:00
from gi . repository import Gdk
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
2013-08-09 17:23:01 +04:00
from virtinst import util
2009-11-15 23:17:03 +03:00
2014-09-13 00:10:45 +04:00
from . import uiutil
from . baseclass import vmmGObjectUI
from . asyncjob import vmmAsyncJob
from . 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 ) :
2015-04-16 18:21:34 +03:00
def __init__ ( self , engine ) :
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
self . conn = None
self . _conns = { }
2009-11-18 21:51:36 +03:00
2013-02-16 22:31:46 +04:00
self . builder . connect_signals ( {
2015-04-16 18:21:34 +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 ( )
2009-11-15 23:17:03 +03:00
2015-04-16 18:21:34 +03:00
self . _init_state ( engine )
2009-11-18 21:51:36 +03:00
2015-04-16 18:21:34 +03:00
def _cleanup ( self ) :
self . vm = None
self . conn = None
self . _conns = None
##############
# Public API #
##############
def show ( self , parent , vm ) :
2012-02-01 03:16:54 +04:00
logging . debug ( " Showing migrate wizard " )
2015-04-16 18:21:34 +03:00
self . vm = vm
self . conn = vm . conn
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 ) :
2012-02-01 03:16:54 +04:00
logging . debug ( " Closing migrate wizard " )
2009-11-15 23:17:03 +03:00
self . topwin . hide ( )
return 1
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
2015-04-16 18:21:34 +03:00
def _init_state ( self , engine ) :
2013-09-27 18:26:43 +04:00
blue = Gdk . color_parse ( " #0072A8 " )
self . widget ( " header " ) . modify_bg ( Gtk . StateType . NORMAL , blue )
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 ) :
row1 = model [ iter1 ]
row2 = model [ iter2 ]
if row1 [ COL_URI ] is None :
return - 1
if row2 [ COL_URI ] is None :
return 1
return cmp ( row1 [ COL_LABEL ] , row2 [ COL_LABEL ] )
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
# Hook up signals to get connection listing
2015-04-16 18:21:34 +03:00
engine . connect ( " conn-added " , self . _conn_added_cb )
engine . connect ( " conn-removed " , self . _conn_removed_cb )
self . widget ( " migrate-dest " ) . emit ( " changed " )
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 ) :
2009-11-15 23:17:03 +03:00
title_str = ( " <span size= ' large ' color= ' white ' > %s ' %s ' </span> " %
2011-08-29 20:11:43 +04:00
( _ ( " Migrate " ) , util . 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 ) :
2014-04-03 18:53:54 +04:00
row = uiutil . get_list_selection ( src , None )
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 = " "
if can_migrate and uri in self . _conns :
destconn = self . _conns [ uri ]
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 ) :
return uiutil . get_list_selection ( self . widget ( " migrate-mode " ) , 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
2015-04-16 18:21:34 +03:00
def _conn_added_cb ( self , engine , conn ) :
ignore = engine
self . _conns [ conn . get_uri ( ) ] = conn
2009-11-18 21:51:36 +03:00
2015-04-16 18:21:34 +03:00
def _conn_removed_cb ( self , engine , uri ) :
ignore = engine
del ( self . _conns [ uri ] )
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 = [ ]
for conn in self . _conns . values ( ) :
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 ( ) :
uri = " xenmigr:// %s " % address
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 ) :
self . topwin . set_sensitive ( True )
self . topwin . get_window ( ) . set_cursor (
Gdk . Cursor . new ( Gdk . CursorType . TOP_LEFT_ARROW ) )
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 :
self . conn . schedule_priority_tick ( pollvm = True )
destconn . schedule_priority_tick ( pollvm = True )
self . close ( )
2015-04-16 18:21:34 +03:00
def _finish ( self ) :
2009-11-15 23:17:03 +03:00
try :
2015-04-16 18:21:34 +03:00
row = uiutil . get_list_selection ( self . widget ( " migrate-dest " ) , None )
destlabel = row [ COL_LABEL ]
destconn = self . _conns . get ( row [ COL_URI ] )
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 ( )
2009-11-15 23:17:03 +03:00
except Exception , e :
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
self . topwin . set_sensitive ( False )
2013-09-07 04:59:01 +04:00
self . topwin . get_window ( ) . set_cursor (
Gdk . Cursor . new ( Gdk . CursorType . WATCH ) )
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 ] ,
_ ( " Migrating VM ' %s ' " % self . vm . get_name ( ) ) ,
2015-04-16 18:21:34 +03:00
( _ ( " Migrating VM ' %s ' to %s . This may take a while. " ) %
( self . vm . get_name ( ) , 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 ) :
2010-12-08 22:14:38 +03:00
logging . debug ( " Cancelling migrate job " )
2010-12-08 20:52:33 +03:00
if not vm :
return
try :
vm . abort_job ( )
except Exception , e :
2010-12-08 22:14:38 +03:00
logging . exception ( " Error cancelling migrate job " )
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 ( ) )
logging . debug ( " Migrating vm= %s from %s to %s " , vm . get_name ( ) ,
srcconn . get_uri ( ) , dstconn . get_uri ( ) )
2015-04-22 00:32:43 +03:00
vm . migrate ( dstconn , migrate_uri , tunnel , unsafe , temporary ,
meter = meter )