2014-01-29 01:53:11 +04:00
#
# Copyright (C) 2014 Red Hat, Inc.
#
# 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
import os
import statvfs
2014-09-12 20:08:44 +04:00
import time
2014-01-29 01:53:11 +04:00
from gi . repository import GObject
from gi . repository import Gtk
import virtinst
2014-09-13 00:10:45 +04:00
from . import uiutil
from . baseclass import vmmGObjectUI
2014-01-29 01:53:11 +04:00
class vmmAddStorage ( vmmGObjectUI ) :
__gsignals__ = {
" browse-clicked " : ( GObject . SignalFlags . RUN_FIRST , None , [ object ] ) ,
" storage-toggled " : ( GObject . SignalFlags . RUN_FIRST , None , [ object ] )
}
def __init__ ( self , conn , builder , topwin ) :
vmmGObjectUI . __init__ ( self , " addstorage.ui " , None ,
builder = builder , topwin = topwin )
self . conn = conn
self . storage_browser = None
self . builder . connect_signals ( {
" on_config_storage_browse_clicked " : self . _browse_storage ,
" on_config_storage_select_toggled " : self . _toggle_storage_select ,
} )
self . top_box = self . widget ( " config-storage-box " )
def _cleanup ( self ) :
self . conn = None
##########################
# Initialization methods #
##########################
def _get_default_dir ( self ) :
pool = self . conn . get_default_pool ( )
if pool :
return pool . get_target_path ( )
return self . config . get_default_image_dir ( self . conn )
def _get_ideal_path_info ( self , name ) :
path = self . _get_default_dir ( )
2014-02-05 02:30:24 +04:00
fmt = self . conn . get_default_storage_format ( )
suffix = virtinst . StorageVolume . get_file_extension_for_format ( fmt )
return ( path , name , suffix or " .img " )
2014-01-29 01:53:11 +04:00
def _get_ideal_path ( self , name ) :
target , name , suffix = self . _get_ideal_path_info ( name )
return os . path . join ( target , name ) + suffix
def _host_disk_space ( self ) :
pool = self . conn . get_default_pool ( )
path = self . _get_default_dir ( )
avail = 0
if pool and pool . is_active ( ) :
2014-09-12 20:08:44 +04:00
# Rate limit this, since it can be spammed at dialog startup time
if ( ( time . time ( ) - pool . get_last_refresh_time ( ) ) > 10 ) :
pool . refresh ( )
2014-01-29 01:53:11 +04:00
avail = int ( pool . get_available ( ) )
elif not self . conn . is_remote ( ) and os . path . exists ( path ) :
vfs = os . statvfs ( os . path . dirname ( path ) )
avail = vfs [ statvfs . F_FRSIZE ] * vfs [ statvfs . F_BAVAIL ]
return float ( avail / 1024.0 / 1024.0 / 1024.0 )
def _update_host_space ( self ) :
widget = self . widget ( " phys-hd-label " )
try :
max_storage = self . _host_disk_space ( )
except :
logging . exception ( " Error determining host disk space " )
widget . set_markup ( " " )
return
def pretty_storage ( size ) :
2014-06-16 07:56:02 +04:00
return " %.1f GiB " % float ( size )
2014-01-29 01:53:11 +04:00
hd_label = ( " %s available in the default location " %
pretty_storage ( max_storage ) )
hd_label = ( " <span color= ' #484848 ' > %s </span> " % hd_label )
widget . set_markup ( hd_label )
def _check_default_pool_active ( self ) :
default_pool = self . conn . get_default_pool ( )
if default_pool and not default_pool . is_active ( ) :
res = self . err . yes_no ( _ ( " Default pool is not active. " ) ,
_ ( " Storage pool ' %s ' is not active. "
" Would you like to start the pool "
" now? " ) % default_pool . get_name ( ) )
if not res :
return False
# Try to start the pool
try :
default_pool . start ( )
logging . info ( " Started pool ' %s ' " , default_pool . get_name ( ) )
except Exception , e :
return self . err . show_err ( _ ( " Could not start storage_pool "
" ' %s ' : %s " ) %
( default_pool . get_name ( ) , str ( e ) ) )
return True
##############
# Public API #
##############
@staticmethod
2014-02-11 03:08:59 +04:00
def check_path_search ( src , conn , path ) :
2014-01-29 01:53:11 +04:00
skip_paths = src . config . get_perms_fix_ignore ( )
2014-02-11 03:08:59 +04:00
user , broken_paths = virtinst . VirtualDisk . check_path_search (
conn . get_backend ( ) , path )
for p in broken_paths [ : ] :
2014-01-29 01:53:11 +04:00
if p in skip_paths :
broken_paths . remove ( p )
if not broken_paths :
return
logging . debug ( " No search access for dirs: %s " , broken_paths )
resp , chkres = src . err . warn_chkbox (
_ ( " The emulator may not have search permissions "
" for the path ' %s ' . " ) % path ,
_ ( " Do you want to correct this now? " ) ,
_ ( " Don ' t ask about these directories again. " ) ,
buttons = Gtk . ButtonsType . YES_NO )
if chkres :
src . config . add_perms_fix_ignore ( broken_paths )
if not resp :
return
logging . debug ( " Attempting to correct permission issues. " )
2014-02-11 03:08:59 +04:00
errors = virtinst . VirtualDisk . fix_path_search_for_user (
conn . get_backend ( ) , path , user )
2014-01-29 01:53:11 +04:00
if not errors :
return
errmsg = _ ( " Errors were encountered changing permissions for the "
" following directories: " )
details = " "
for path , error in errors . items ( ) :
if path not in broken_paths :
continue
details + = " %s : %s \n " % ( path , error )
logging . debug ( " Permission errors: \n %s " , details )
ignore , chkres = src . err . err_chkbox ( errmsg , details ,
_ ( " Don ' t ask about these directories again. " ) )
if chkres :
src . config . add_perms_fix_ignore ( errors . keys ( ) )
def reset_state ( self ) :
self . _update_host_space ( )
self . widget ( " config-storage-create " ) . set_active ( True )
self . widget ( " config-storage-size " ) . set_value ( 8 )
self . widget ( " config-storage-entry " ) . set_text ( " " )
self . widget ( " config-storage-nosparse " ) . set_active ( True )
2014-03-13 01:22:48 +04:00
self . widget ( " config-storage-create-box " ) . set_sensitive ( True )
2014-01-29 01:53:11 +04:00
fmt = self . conn . get_default_storage_format ( )
can_alloc = fmt in [ " raw " ]
self . widget ( " config-storage-nosparse " ) . set_active ( can_alloc )
self . widget ( " config-storage-nosparse " ) . set_sensitive ( can_alloc )
self . widget ( " config-storage-nosparse " ) . set_tooltip_text (
not can_alloc and
( _ ( " Disk format ' %s ' does not support full allocation. " ) % fmt ) or
" " )
storage_tooltip = None
can_storage = ( not self . conn . is_remote ( ) or
self . conn . is_storage_capable ( ) )
use_storage = self . widget ( " config-storage-select " )
storage_area = self . widget ( " config-storage-box " )
storage_area . set_sensitive ( can_storage )
if not can_storage :
storage_tooltip = _ ( " Connection does not support storage "
" management. " )
use_storage . set_sensitive ( True )
storage_area . set_tooltip_text ( storage_tooltip or " " )
def get_default_path ( self , name , collidelist = None ) :
collidelist = collidelist or [ ]
pool = self . conn . get_default_pool ( )
default_dir = self . _get_default_dir ( )
def path_exists ( p ) :
return os . path . exists ( p ) or p in collidelist
if not pool :
# Use old generating method
origf = os . path . join ( default_dir , name + " .img " )
f = origf
n = 1
while path_exists ( f ) and n < 100 :
f = os . path . join ( default_dir , name +
" - " + str ( n ) + " .img " )
n + = 1
if path_exists ( f ) :
f = origf
path = f
else :
target , ignore , suffix = self . _get_ideal_path_info ( name )
# Sanitize collidelist to work with the collision checker
newcollidelist = [ ]
for c in collidelist :
if c and os . path . dirname ( c ) == pool . get_target_path ( ) :
newcollidelist . append ( os . path . basename ( c ) )
path = virtinst . StorageVolume . find_free_name (
pool . get_backend ( ) , name ,
suffix = suffix , collidelist = newcollidelist )
path = os . path . join ( target , path )
return path
def is_default_storage ( self ) :
2014-12-03 21:08:18 +03:00
return self . widget ( " config-storage-create " ) . get_active ( )
2014-01-29 01:53:11 +04:00
2014-02-05 02:39:44 +04:00
def _check_ideal_path ( self , path , vmname , collidelist ) :
2014-01-29 01:53:11 +04:00
# See if the ideal disk path (/default/pool/vmname.img)
# exists, and if unused, prompt the use for using it
conn = self . conn . get_backend ( )
ideal = self . _get_ideal_path ( vmname )
if ideal in collidelist :
return path
do_exist = False
ret = True
try :
do_exist = virtinst . VirtualDisk . path_exists ( conn , ideal )
ret = virtinst . VirtualDisk . path_in_use_by ( conn , ideal )
except :
logging . exception ( " Error checking default path usage " )
if not do_exist or ret :
return path
do_use = self . err . yes_no (
_ ( " The following storage already exists, but is not \n "
" in use by any virtual machine: \n \n %s \n \n "
" Would you like to reuse this storage? " ) % ideal )
if do_use :
return ideal
return path
def validate_storage ( self , vmname ,
path = None , size = None , sparse = None ,
device = " disk " , fmt = None , collidelist = None ) :
2014-02-05 02:39:44 +04:00
collidelist = collidelist or [ ]
2014-01-29 01:53:11 +04:00
use_storage = self . widget ( " config-storage-box " ) . is_sensitive ( )
is_default = self . is_default_storage ( )
conn = self . conn . get_backend ( )
# Validate storage
if not use_storage :
return True
# Make sure default pool is running
if is_default :
ret = self . _check_default_pool_active ( )
if not ret :
return False
readonly = False
if device == virtinst . VirtualDisk . DEVICE_CDROM :
readonly = True
try :
if size is None and sparse is None :
size = uiutil . spin_get_helper (
self . widget ( " config-storage-size " ) )
sparse = (
not self . widget ( " config-storage-nosparse " ) . get_active ( ) )
if path is None :
if is_default :
2014-02-05 02:39:44 +04:00
path = self . get_default_path ( vmname , collidelist )
2014-01-29 01:53:11 +04:00
else :
2014-04-09 19:33:00 +04:00
path = self . widget ( " config-storage-entry " ) . get_text ( ) . strip ( )
2014-01-29 01:53:11 +04:00
if is_default :
path = self . _check_ideal_path ( path , vmname , collidelist )
2014-03-13 00:54:14 +04:00
if not path and device in [ " disk " , " lun " ] :
2014-01-29 01:53:11 +04:00
return self . err . val_err ( _ ( " A storage path must be specified. " ) )
disk = virtinst . VirtualDisk ( conn )
2014-03-13 00:54:14 +04:00
disk . path = path or None
2014-01-29 01:53:11 +04:00
disk . read_only = readonly
disk . device = device
disk . set_create_storage ( size = size , sparse = sparse ,
fmt = fmt or None )
if not fmt :
fmt = self . conn . get_default_storage_format ( )
if ( self . is_default_storage ( ) and
disk . get_vol_install ( ) and
fmt in disk . get_vol_install ( ) . list_formats ( ) ) :
logging . debug ( " Setting disk format from prefs: %s " , fmt )
disk . get_vol_install ( ) . format = fmt
disk . validate ( )
except Exception , e :
return self . err . val_err ( _ ( " Storage parameter error. " ) , e )
return disk
def validate_disk_object ( self , disk ) :
isfatal , errmsg = disk . is_size_conflict ( )
if not isfatal and errmsg :
# Fatal errors are reported when setting 'size'
res = self . err . ok_cancel ( _ ( " Not Enough Free Space " ) , errmsg )
if not res :
return False
# Disk collision
names = disk . is_conflict_disk ( )
if names :
res = self . err . yes_no (
_ ( ' Disk " %s " is already in use by other guests %s ' ) %
( disk . path , names ) ,
_ ( " Do you really want to use the disk? " ) )
if not res :
return False
2014-02-11 03:08:59 +04:00
self . check_path_search ( self , self . conn , disk . path )
2014-01-29 01:53:11 +04:00
#############
# Listeners #
#############
def _browse_storage ( self , ignore ) :
self . emit ( " browse-clicked " , self . widget ( " config-storage-entry " ) )
def _toggle_storage_select ( self , src ) :
act = src . get_active ( )
self . widget ( " config-storage-browse-box " ) . set_sensitive ( act )
self . emit ( " storage-toggled " , src )