virt-manager/src/virtManager/delete.py

373 lines
12 KiB
Python
Raw Normal View History

2009-03-09 23:17:09 +03:00
#
# Copyright (C) 2009 Red Hat, Inc.
# 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 gtk
2009-03-09 23:17:09 +03:00
import os, stat
import traceback
import logging
import virtinst
2009-04-03 22:15:22 +04:00
from virtManager import util
2010-12-09 01:26:19 +03:00
from virtManager.baseclass import vmmGObjectUI
2009-03-09 23:17:09 +03:00
from virtManager.asyncjob import vmmAsyncJob
from virtManager.createmeter import vmmCreateMeter
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
2010-12-09 01:26:19 +03:00
class vmmDeleteDialog(vmmGObjectUI):
2009-03-09 23:17:09 +03:00
__gsignals__ = {
}
def __init__(self, vm):
2010-12-09 01:26:19 +03:00
vmmGObjectUI.__init__(self, "vmm-delete.glade", "vmm-delete")
2009-03-09 23:17:09 +03:00
self.vm = vm
self.conn = vm.connection
self.window.signal_autoconnect({
"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,
})
util.bind_escape_key_close(self)
2009-03-09 23:17:09 +03:00
image = gtk.image_new_from_icon_name("vm_delete_wizard",
gtk.ICON_SIZE_DIALOG)
image.show()
self.window.get_widget("icon-box").pack_end(image, False)
2009-03-09 23:17:09 +03:00
prepare_storage_list(self.window.get_widget("delete-storage-list"))
def toggle_remove_storage(self, src):
dodel = src.get_active()
self.window.get_widget("delete-storage-list").set_sensitive(dodel)
def show(self):
self.reset_state()
self.topwin.present()
def close(self, ignore1=None, ignore2=None):
self.topwin.hide()
return 1
def reset_state(self):
# Set VM name in title'
title_str = ("<span size='x-large'>%s '%s'</span>" %
(_("Delete"), self.vm.get_name()))
self.window.get_widget("delete-main-label").set_markup(title_str)
2009-12-15 00:48:35 +03:00
self.window.get_widget("delete-cancel").grab_focus()
2009-03-09 23:17:09 +03:00
# Disable storage removal by default
self.window.get_widget("delete-remove-storage").set_active(False)
self.window.get_widget("delete-remove-storage").toggled()
populate_storage_list(self.window.get_widget("delete-storage-list"),
self.vm, self.conn)
def set_vm(self, vm):
self.vm = vm
self.conn = vm.connection
self.reset_state()
def get_config_format(self):
format_combo = self.window.get_widget("vol-format")
model = format_combo.get_model()
if format_combo.get_active_iter() != None:
model = format_combo.get_model()
return model.get_value(format_combo.get_active_iter(), 0)
return None
def get_paths_to_delete(self):
del_list = self.window.get_widget("delete-storage-list")
model = del_list.get_model()
paths = []
if self.window.get_widget("delete-remove-storage").get_active():
for row in model:
if (not row[STORAGE_ROW_CANT_DELETE] and
row[STORAGE_ROW_CONFIRM]):
paths.append(row[STORAGE_ROW_PATH])
return paths
def finish(self, src_ignore):
2009-03-09 23:17:09 +03:00
devs = self.get_paths_to_delete()
self.topwin.set_sensitive(False)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
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
progWin = vmmAsyncJob(self._async_delete, [devs],
2009-03-09 23:17:09 +03:00
title=title, text=text)
2010-12-10 17:57:42 +03:00
error, details = progWin.run()
2009-03-09 23:17:09 +03:00
self.topwin.set_sensitive(True)
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
self.close()
if error is not None:
self.err.show_err(error, details)
2009-03-09 23:17:09 +03:00
self.conn.tick(noStatsUpdate=True)
2010-12-10 17:57:42 +03:00
def _async_delete(self, asyncjob, paths):
2009-03-09 23:17:09 +03:00
newconn = None
storage_errors = []
try:
# Open a seperate connection to install on since this is async
logging.debug("Threading off connection to delete vol.")
2010-12-09 23:45:05 +03:00
newconn = util.dup_conn(self.conn).vmm
2009-03-09 23:17:09 +03:00
meter = vmmCreateMeter(asyncjob)
for path in paths:
try:
logging.debug("Deleting path: %s" % path)
meter.start(text = _("Deleting path '%s'") % path)
self._async_delete_path(newconn, path, meter)
except Exception, e:
storage_errors.append((str(e),
"".join(traceback.format_exc())))
meter.end(0)
logging.debug("Removing VM '%s'" % self.vm.get_name())
self.vm.delete()
except Exception, e:
error = (_("Error deleting virtual machine '%s': %s") %
(self.vm.get_name(), str(e)))
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])
if not storage_errstr:
return
# We had extra storage errors. If there was another error message,
# errors to it. Otherwise, build the main error around them.
if details:
details += "\n\n"
details += _("Additionally, there were errors removing"
2009-03-09 23:17:09 +03:00
" certain storage devices: \n")
details += storage_errstr
2009-03-09 23:17:09 +03:00
else:
error = _("Errors encountered while removing certain "
2009-03-09 23:17:09 +03:00
"storage devices.")
details = storage_errstr
if error:
asyncjob.set_error(error, details)
2009-03-09 23:17:09 +03:00
def _async_delete_path(self, conn, path, ignore):
vol = None
try:
vol = conn.storageVolLookupByPath(path)
except:
logging.debug("Path '%s' is not managed. Deleting locally." % path)
if vol:
vol.delete(0)
else:
os.unlink(path)
def populate_storage_list(storage_list, vm, conn):
model = storage_list.get_model()
model.clear()
for disk in vm.get_disk_devices():
vol = None
target = disk.target
path = disk.path
ro = disk.read_only
shared = disk.shareable
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
# readonly or sharable, or is in use by another VM.
if not path:
2009-03-09 23:17:09 +03:00
continue
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,
path, ro, shared)
info = None
if not can_del:
info = delinfo
elif not default:
info = definfo
icon = gtk.STOCK_DIALOG_WARNING
icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR
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
model = gtk.ListStore(bool, bool, str, str, bool, str, int, str)
storage_list.set_model(model)
try:
storage_list.set_tooltip_column(STORAGE_ROW_TOOLTIP)
except:
# FIXME: use tooltip wrapper for this
pass
confirmCol = gtk.TreeViewColumn()
pathCol = gtk.TreeViewColumn(_("Storage Path"))
targetCol = gtk.TreeViewColumn(_("Target"))
infoCol = gtk.TreeViewColumn()
storage_list.append_column(confirmCol)
storage_list.append_column(pathCol)
storage_list.append_column(targetCol)
storage_list.append_column(infoCol)
chkbox = gtk.CellRendererToggle()
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)
path_txt = gtk.CellRendererText()
pathCol.pack_start(path_txt, True)
pathCol.add_attribute(path_txt, 'text', STORAGE_ROW_PATH)
pathCol.set_sort_column_id(STORAGE_ROW_PATH)
target_txt = gtk.CellRendererText()
targetCol.pack_start(target_txt, False)
targetCol.add_attribute(target_txt, 'text', STORAGE_ROW_TARGET)
targetCol.set_sort_column_id(STORAGE_ROW_TARGET)
info_img = gtk.CellRendererPixbuf()
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)
def storage_item_toggled(src, index, storage_list):
active = src.get_active()
model = storage_list.get_model()
model[index][STORAGE_ROW_CONFIRM] = not active
def can_delete(conn, vol, path):
"""Is the passed path even deleteable"""
ret = True
msg = None
if vol:
# Managed storage
if (vol.get_pool().get_type() ==
virtinst.Storage.StoragePool.TYPE_ISCSI):
msg = _("Cannot delete iscsi share.")
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)
def do_we_default(conn, vm_name, vol, path, ro, shared):
""" 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."))
try:
names = virtinst.VirtualDisk.path_in_use_by(conn.vmm, 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))
except Exception, e:
logging.exception("Failed checking disk conflict: %s" % str(e))
return (not info, info)
2010-12-09 01:26:19 +03:00
vmmGObjectUI.type_register(vmmDeleteDialog)