2014-09-11 19:13:31 +04:00
# Copyright (C) 2009, 2012-2014 Red Hat, Inc.
2009-03-09 23:17:09 +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-03-09 23:17:09 +03:00
2010-12-10 19:47:07 +03:00
import os
import stat
2009-03-09 23:17:09 +03:00
import traceback
import logging
2016-06-07 18:33:21 +03:00
from gi . repository import Gtk
from gi . repository import Gdk
from gi . repository import Pango
2009-03-09 23:17:09 +03:00
import virtinst
2019-06-08 01:21:24 +03:00
from virtinst import xmlutil
2009-03-09 23:17:09 +03:00
2014-09-13 00:10:45 +04:00
from . asyncjob import vmmAsyncJob
from . baseclass import vmmGObjectUI
from . import uiutil
2009-03-09 23:17:09 +03:00
STORAGE_ROW_CONFIRM = 0
STORAGE_ROW_CANT_DELETE = 1
STORAGE_ROW_PATH = 2
STORAGE_ROW_TARGET = 3
STORAGE_ROW_ICON_SHOW = 4
STORAGE_ROW_ICON = 5
STORAGE_ROW_ICON_SIZE = 6
STORAGE_ROW_TOOLTIP = 7
2013-04-13 22:34:52 +04:00
2010-12-09 01:26:19 +03:00
class vmmDeleteDialog ( vmmGObjectUI ) :
2018-03-15 00:48:17 +03:00
@classmethod
def show_instance ( cls , parentobj , vm ) :
try :
if not cls . _instance :
2018-03-17 23:08:20 +03:00
cls . _instance = vmmDeleteDialog ( )
2018-03-15 00:48:17 +03:00
cls . _instance . show ( parentobj . topwin , vm )
except Exception as e :
parentobj . err . show_err (
_ ( " Error launching delete dialog: %s " ) % str ( e ) )
2011-04-12 00:14:23 +04:00
def __init__ ( self ) :
2013-09-23 00:10:16 +04:00
vmmGObjectUI . __init__ ( self , " delete.ui " , " vmm-delete " )
2011-04-12 00:14:23 +04:00
self . vm = None
2009-03-09 23:17:09 +03:00
2013-02-16 22:31:46 +04:00
self . builder . connect_signals ( {
2017-08-05 09:39:32 +03:00
" on_vmm_delete_delete_event " : self . close ,
" on_delete_cancel_clicked " : self . close ,
" on_delete_ok_clicked " : self . finish ,
" on_delete_remove_storage_toggled " : self . toggle_remove_storage ,
2009-03-09 23:17:09 +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-03-09 23:17:09 +03:00
2013-09-27 18:14:53 +04:00
self . _init_state ( )
2009-03-09 23:17:09 +03:00
2013-09-27 18:14:53 +04:00
def _init_state ( self ) :
blue = Gdk . Color . parse ( " #0072A8 " ) [ 1 ]
self . widget ( " header " ) . modify_bg ( Gtk . StateType . NORMAL , blue )
2009-03-09 23:17:09 +03:00
2013-09-27 18:14:53 +04:00
prepare_storage_list ( self . widget ( " delete-storage-list " ) )
2009-03-09 23:17:09 +03:00
2018-03-15 00:48:17 +03:00
def show ( self , parent , vm ) :
2012-02-01 03:16:54 +04:00
logging . debug ( " Showing delete wizard " )
2018-03-15 14:43:56 +03:00
self . _set_vm ( vm )
2009-03-09 23:17:09 +03:00
self . reset_state ( )
2011-04-14 16:47:42 +04:00
self . topwin . set_transient_for ( parent )
2009-03-09 23:17:09 +03:00
self . topwin . present ( )
def close ( self , ignore1 = None , ignore2 = None ) :
2012-02-01 03:16:54 +04:00
logging . debug ( " Closing delete wizard " )
2009-03-09 23:17:09 +03:00
self . topwin . hide ( )
2018-03-15 14:43:56 +03:00
self . _set_vm ( None )
2009-03-09 23:17:09 +03:00
return 1
2011-07-24 05:16:54 +04:00
def _cleanup ( self ) :
2018-03-15 14:43:56 +03:00
pass
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
2009-03-09 23:17:09 +03:00
def reset_state ( self ) :
# Set VM name in title'
2013-09-27 18:14:53 +04:00
title_str = ( " <span size= ' large ' color= ' white ' > %s ' %s ' </span> " %
2019-06-08 01:21:24 +03:00
( _ ( " Delete " ) , xmlutil . xml_escape ( self . vm . get_name ( ) ) ) )
2013-09-27 18:14:53 +04:00
self . widget ( " header-label " ) . set_markup ( title_str )
2009-03-09 23:17:09 +03:00
2015-05-21 00:42:45 +03:00
self . topwin . resize ( 1 , 1 )
2011-07-14 21:13:13 +04:00
self . widget ( " delete-cancel " ) . grab_focus ( )
2009-12-15 00:48:35 +03:00
2013-06-18 07:29:20 +04:00
# Show warning message if VM is running
vm_active = self . vm . is_active ( )
2014-01-27 03:15:50 +04:00
uiutil . set_grid_row_visible (
2013-09-27 18:14:53 +04:00
self . widget ( " delete-warn-running-vm-box " ) , vm_active )
2013-06-18 07:29:20 +04:00
2014-02-17 17:11:49 +04:00
# Enable storage removal by default
2012-12-03 20:12:59 +04:00
self . widget ( " delete-remove-storage " ) . set_active ( True )
2011-07-14 21:13:13 +04:00
self . widget ( " delete-remove-storage " ) . toggled ( )
2009-03-09 23:17:09 +03:00
2011-07-14 21:13:13 +04:00
populate_storage_list ( self . widget ( " delete-storage-list " ) ,
2018-03-15 00:48:17 +03:00
self . vm , self . vm . conn )
2009-03-09 23:17:09 +03:00
2013-09-27 18:14:53 +04:00
def toggle_remove_storage ( self , src ) :
dodel = src . get_active ( )
2014-01-27 03:15:50 +04:00
uiutil . set_grid_row_visible (
2013-09-27 18:14:53 +04:00
self . widget ( " delete-storage-scroll " ) , dodel )
2009-03-09 23:17:09 +03:00
def get_paths_to_delete ( self ) :
2011-07-14 21:13:13 +04:00
del_list = self . widget ( " delete-storage-list " )
2009-03-09 23:17:09 +03:00
model = del_list . get_model ( )
paths = [ ]
2011-07-14 21:13:13 +04:00
if self . widget ( " delete-remove-storage " ) . get_active ( ) :
2009-03-09 23:17:09 +03:00
for row in model :
if ( not row [ STORAGE_ROW_CANT_DELETE ] and
row [ STORAGE_ROW_CONFIRM ] ) :
paths . append ( row [ STORAGE_ROW_PATH ] )
return paths
2013-09-07 04:59:01 +04:00
def _finish_cb ( self , error , details ) :
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 :
self . err . show_err ( error , details = details )
self . close ( )
2010-12-09 19:22:35 +03:00
def finish ( self , src_ignore ) :
2009-03-09 23:17:09 +03:00
devs = self . get_paths_to_delete ( )
2012-12-03 20:12:59 +04:00
if devs :
2013-09-27 18:14:53 +04:00
title = _ ( " Are you sure you want to delete the storage? " )
message = ( _ ( " The following paths will be deleted: \n \n %s " ) %
" \n " . join ( devs ) )
2014-01-27 02:42:24 +04:00
ret = self . err . chkbox_helper (
self . config . get_confirm_delstorage ,
self . config . set_confirm_delstorage ,
text1 = title , text2 = message )
2012-12-03 20:12:59 +04:00
if not ret :
return
2017-04-27 22:00:17 +03:00
self . set_finish_cursor ( )
2009-03-09 23:17:09 +03:00
title = _ ( " Deleting virtual machine ' %s ' " ) % self . vm . get_name ( )
text = title
if devs :
2009-07-15 01:06:50 +04:00
text = title + _ ( " and selected storage (this may take a while) " )
2009-03-09 23:17:09 +03:00
2018-03-15 14:43:56 +03:00
progWin = vmmAsyncJob ( self . _async_delete , [ self . vm , devs ] ,
2013-09-07 04:59:01 +04:00
self . _finish_cb , [ ] ,
2011-04-14 16:47:42 +04:00
title , text , self . topwin )
2013-09-07 04:59:01 +04:00
progWin . run ( )
2018-03-15 14:43:56 +03:00
self . _set_vm ( None )
2009-03-09 23:17:09 +03:00
2018-03-15 14:43:56 +03:00
def _async_delete ( self , asyncjob , vm , paths ) :
2009-03-09 23:17:09 +03:00
storage_errors = [ ]
2011-03-24 19:59:40 +03:00
details = " "
2018-03-15 14:43:56 +03:00
undefine = vm . is_persistent ( )
2009-03-09 23:17:09 +03:00
try :
2018-03-15 14:43:56 +03:00
if vm . is_active ( ) :
logging . debug ( " Forcing VM ' %s ' power off. " , vm . get_name ( ) )
vm . destroy ( )
2013-06-18 07:29:20 +04:00
2018-03-15 14:43:56 +03:00
conn = vm . conn . get_backend ( )
2012-02-10 19:24:43 +04:00
meter = asyncjob . get_meter ( )
2009-03-09 23:17:09 +03:00
for path in paths :
try :
2012-01-17 07:04:40 +04:00
logging . debug ( " Deleting path: %s " , path )
2010-12-10 19:47:07 +03:00
meter . start ( text = _ ( " Deleting path ' %s ' " ) % path )
2013-07-05 16:59:58 +04:00
self . _async_delete_path ( conn , path , meter )
2017-05-05 19:47:21 +03:00
except Exception as e :
2009-03-09 23:17:09 +03:00
storage_errors . append ( ( str ( e ) ,
" " . join ( traceback . format_exc ( ) ) ) )
meter . end ( 0 )
2017-11-24 19:26:59 +03:00
if undefine :
2018-03-15 14:43:56 +03:00
logging . debug ( " Removing VM ' %s ' " , vm . get_name ( ) )
vm . delete ( )
2009-03-09 23:17:09 +03:00
2017-05-05 19:47:21 +03:00
except Exception as e :
2009-04-03 22:15:15 +04:00
error = ( _ ( " Error deleting virtual machine ' %s ' : %s " ) %
2018-03-15 14:43:56 +03:00
( vm . get_name ( ) , str ( e ) ) )
2009-04-03 22:15:15 +04:00
details = " " . join ( traceback . format_exc ( ) )
2009-03-09 23:17:09 +03:00
storage_errstr = " "
for errinfo in storage_errors :
storage_errstr + = " %s \n %s \n " % ( errinfo [ 0 ] , errinfo [ 1 ] )
2011-08-29 22:01:40 +04:00
if not storage_errstr and not details :
2009-03-09 23:17:09 +03:00
return
# We had extra storage errors. If there was another error message,
# errors to it. Otherwise, build the main error around them.
2009-04-03 22:15:15 +04:00
if details :
details + = " \n \n "
details + = _ ( " Additionally, there were errors removing "
2009-03-09 23:17:09 +03:00
" certain storage devices: \n " )
2009-04-03 22:15:15 +04:00
details + = storage_errstr
2009-03-09 23:17:09 +03:00
else :
2009-04-03 22:15:15 +04:00
error = _ ( " Errors encountered while removing certain "
2009-03-09 23:17:09 +03:00
" storage devices. " )
2009-04-03 22:15:15 +04:00
details = storage_errstr
if error :
asyncjob . set_error ( error , details )
2018-03-15 14:43:56 +03:00
vm . conn . schedule_priority_tick ( pollvm = True )
2009-03-09 23:17:09 +03:00
def _async_delete_path ( self , conn , path , ignore ) :
vol = None
try :
vol = conn . storageVolLookupByPath ( path )
2017-07-24 11:26:48 +03:00
except Exception :
2012-01-17 07:04:40 +04:00
logging . debug ( " Path ' %s ' is not managed. Deleting locally " , path )
2009-03-09 23:17:09 +03:00
if vol :
vol . delete ( 0 )
else :
os . unlink ( path )
def populate_storage_list ( storage_list , vm , conn ) :
model = storage_list . get_model ( )
model . clear ( )
2016-06-17 20:06:35 +03:00
diskdata = [ ( d . target , d . path , d . read_only , d . shareable ,
d . device in [ " cdrom " , " floppy " ] ) for
2018-03-21 21:42:50 +03:00
d in vm . xmlobj . devices . disk ]
2014-01-15 02:26:29 +04:00
2016-06-17 20:06:35 +03:00
diskdata . append ( ( " kernel " , vm . get_xmlobj ( ) . os . kernel , True , False , True ) )
diskdata . append ( ( " initrd " , vm . get_xmlobj ( ) . os . initrd , True , False , True ) )
diskdata . append ( ( " dtb " , vm . get_xmlobj ( ) . os . dtb , True , False , True ) )
2009-03-09 23:17:09 +03:00
2016-06-17 20:06:35 +03:00
for target , path , ro , shared , is_media in diskdata :
2014-01-15 02:26:29 +04:00
if not path :
continue
2009-03-09 23:17:09 +03:00
# There are a few pieces here
# 1) Can we even delete the storage? If not, make the checkbox
# inconsistent. self.can_delete decides this for us, and if
# we can't delete, gives us a nice message to show the user
# for that row.
#
# 2) If we can delete, do we want to delete this storage by
# default? Reasons not to, are if the storage is marked
2014-02-26 05:36:16 +04:00
# readonly or shareable, or is in use by another VM.
2009-03-09 23:17:09 +03:00
default = False
definfo = None
vol = conn . get_vol_by_path ( path )
can_del , delinfo = can_delete ( conn , vol , path )
if can_del :
default , definfo = do_we_default ( conn , vm . get_name ( ) , vol ,
2016-06-17 20:06:35 +03:00
path , ro , shared , is_media )
2009-03-09 23:17:09 +03:00
info = None
if not can_del :
info = delinfo
elif not default :
info = definfo
2012-05-14 17:24:56 +04:00
icon = Gtk . STOCK_DIALOG_WARNING
icon_size = Gtk . IconSize . LARGE_TOOLBAR
2009-03-09 23:17:09 +03:00
row = [ default , not can_del , path , target ,
bool ( info ) , icon , icon_size , info ]
model . append ( row )
def prepare_storage_list ( storage_list ) :
# Checkbox, deleteable?, storage path, target (hda), icon stock,
# icon size, tooltip
2012-05-14 17:24:56 +04:00
model = Gtk . ListStore ( bool , bool , str , str , bool , str , int , str )
2009-03-09 23:17:09 +03:00
storage_list . set_model ( model )
2013-04-17 17:09:53 +04:00
storage_list . set_tooltip_column ( STORAGE_ROW_TOOLTIP )
2009-03-09 23:17:09 +03:00
2012-05-14 17:24:56 +04:00
confirmCol = Gtk . TreeViewColumn ( )
targetCol = Gtk . TreeViewColumn ( _ ( " Target " ) )
infoCol = Gtk . TreeViewColumn ( )
2015-05-21 00:42:45 +03:00
pathCol = Gtk . TreeViewColumn ( _ ( " Storage Path " ) )
pathCol . set_sizing ( Gtk . TreeViewColumnSizing . AUTOSIZE )
pathCol . set_expand ( True )
2009-03-09 23:17:09 +03:00
storage_list . append_column ( confirmCol )
storage_list . append_column ( pathCol )
storage_list . append_column ( targetCol )
storage_list . append_column ( infoCol )
2012-05-14 17:24:56 +04:00
chkbox = Gtk . CellRendererToggle ( )
2009-03-09 23:17:09 +03:00
chkbox . connect ( ' toggled ' , storage_item_toggled , storage_list )
confirmCol . pack_start ( chkbox , False )
confirmCol . add_attribute ( chkbox , ' active ' , STORAGE_ROW_CONFIRM )
confirmCol . add_attribute ( chkbox , ' inconsistent ' ,
STORAGE_ROW_CANT_DELETE )
confirmCol . set_sort_column_id ( STORAGE_ROW_CANT_DELETE )
2012-05-14 17:24:56 +04:00
path_txt = Gtk . CellRendererText ( )
2009-03-09 23:17:09 +03:00
pathCol . pack_start ( path_txt , True )
pathCol . add_attribute ( path_txt , ' text ' , STORAGE_ROW_PATH )
pathCol . set_sort_column_id ( STORAGE_ROW_PATH )
2015-05-21 00:42:45 +03:00
path_txt . set_property ( " width-chars " , 50 )
path_txt . set_property ( " ellipsize " , Pango . EllipsizeMode . MIDDLE )
2009-03-09 23:17:09 +03:00
2012-05-14 17:24:56 +04:00
target_txt = Gtk . CellRendererText ( )
2009-03-09 23:17:09 +03:00
targetCol . pack_start ( target_txt , False )
targetCol . add_attribute ( target_txt , ' text ' , STORAGE_ROW_TARGET )
targetCol . set_sort_column_id ( STORAGE_ROW_TARGET )
2012-05-14 17:24:56 +04:00
info_img = Gtk . CellRendererPixbuf ( )
2009-03-09 23:17:09 +03:00
infoCol . pack_start ( info_img , False )
infoCol . add_attribute ( info_img , ' visible ' , STORAGE_ROW_ICON_SHOW )
infoCol . add_attribute ( info_img , ' stock-id ' , STORAGE_ROW_ICON )
infoCol . add_attribute ( info_img , ' stock-size ' , STORAGE_ROW_ICON_SIZE )
infoCol . set_sort_column_id ( STORAGE_ROW_ICON )
2013-04-13 22:34:52 +04:00
2009-03-09 23:17:09 +03:00
def storage_item_toggled ( src , index , storage_list ) :
active = src . get_active ( )
model = storage_list . get_model ( )
model [ index ] [ STORAGE_ROW_CONFIRM ] = not active
2013-04-13 22:34:52 +04:00
2009-03-09 23:17:09 +03:00
def can_delete ( conn , vol , path ) :
""" Is the passed path even deleteable """
ret = True
msg = None
if vol :
# Managed storage
2017-03-06 11:28:52 +03:00
pool_type = vol . get_parent_pool ( ) . get_type ( )
if pool_type == virtinst . StoragePool . TYPE_ISCSI :
2009-03-09 23:17:09 +03:00
msg = _ ( " Cannot delete iscsi share. " )
2017-03-06 11:28:52 +03:00
elif pool_type == virtinst . StoragePool . TYPE_SCSI :
msg = _ ( " Cannot delete SCSI device. " )
2009-03-09 23:17:09 +03:00
else :
if conn . is_remote ( ) :
msg = _ ( " Cannot delete unmanaged remote storage. " )
elif not os . path . exists ( path ) :
msg = _ ( " Path does not exist. " )
elif not os . access ( os . path . dirname ( path ) , os . W_OK ) :
msg = _ ( " No write access to parent directory. " )
elif stat . S_ISBLK ( os . stat ( path ) [ stat . ST_MODE ] ) :
msg = _ ( " Cannot delete unmanaged block device. " )
if msg :
ret = False
return ( ret , msg )
2013-04-13 22:34:52 +04:00
2016-06-17 20:06:35 +03:00
def do_we_default ( conn , vm_name , vol , path , ro , shared , is_media ) :
2009-03-09 23:17:09 +03:00
""" Returns (do we delete by default?, info string if not) """
info = " "
def append_str ( str1 , str2 , delim = " \n " ) :
if not str2 :
return str1
if str1 :
str1 + = delim
str1 + = str2
return str1
if ro :
info = append_str ( info , _ ( " Storage is read-only. " ) )
elif not vol and not os . access ( path , os . W_OK ) :
info = append_str ( info , _ ( " No write access to path. " ) )
if shared :
info = append_str ( info , _ ( " Storage is marked as shareable. " ) )
2016-06-17 20:06:35 +03:00
if not info and is_media :
info = append_str ( info , _ ( " Storage is a media device. " ) )
2009-03-09 23:17:09 +03:00
try :
2018-03-20 19:18:35 +03:00
names = virtinst . DeviceDisk . path_in_use_by ( conn . get_backend ( ) , path )
2009-03-09 23:17:09 +03:00
if len ( names ) > 1 :
namestr = " "
names . remove ( vm_name )
for name in names :
namestr = append_str ( namestr , name , delim = " \n - " )
info = append_str ( info , _ ( " Storage is in use by the following "
" virtual machines: \n - %s " % namestr ) )
2017-05-05 19:47:21 +03:00
except Exception as e :
2012-01-17 07:04:40 +04:00
logging . exception ( " Failed checking disk conflict: %s " , str ( e ) )
2009-03-09 23:17:09 +03:00
return ( not info , info )