mirror of
https://github.com/virt-manager/virt-manager.git
synced 2025-02-25 18:55:27 -06:00
2936 lines
110 KiB
Python
2936 lines
110 KiB
Python
#
|
|
# Copyright (C) 2006-2008 Red Hat, Inc.
|
|
# Copyright (C) 2006 Daniel P. Berrange <berrange@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 gobject
|
|
import gtk
|
|
import libvirt
|
|
import logging
|
|
import traceback
|
|
import os
|
|
|
|
import virtManager.uihelpers as uihelpers
|
|
from virtManager.storagebrowse import vmmStorageBrowser
|
|
from virtManager.baseclass import vmmGObjectUI
|
|
from virtManager.addhardware import vmmAddHardware
|
|
from virtManager.choosecd import vmmChooseCD
|
|
from virtManager.console import vmmConsolePages
|
|
from virtManager.serialcon import vmmSerialConsole
|
|
from virtManager.graphwidgets import Sparkline
|
|
from virtManager import util as util
|
|
|
|
import virtinst
|
|
|
|
# Columns in hw list model
|
|
HW_LIST_COL_LABEL = 0
|
|
HW_LIST_COL_ICON_NAME = 1
|
|
HW_LIST_COL_ICON_SIZE = 2
|
|
HW_LIST_COL_TYPE = 3
|
|
HW_LIST_COL_DEVICE = 4
|
|
|
|
# Types for the hw list model: numbers specify what order they will be listed
|
|
HW_LIST_TYPE_GENERAL = 0
|
|
HW_LIST_TYPE_STATS = 1
|
|
HW_LIST_TYPE_CPU = 2
|
|
HW_LIST_TYPE_MEMORY = 3
|
|
HW_LIST_TYPE_BOOT = 4
|
|
HW_LIST_TYPE_DISK = 5
|
|
HW_LIST_TYPE_NIC = 6
|
|
HW_LIST_TYPE_INPUT = 7
|
|
HW_LIST_TYPE_GRAPHICS = 8
|
|
HW_LIST_TYPE_SOUND = 9
|
|
HW_LIST_TYPE_CHAR = 10
|
|
HW_LIST_TYPE_HOSTDEV = 11
|
|
HW_LIST_TYPE_VIDEO = 12
|
|
HW_LIST_TYPE_WATCHDOG = 13
|
|
HW_LIST_TYPE_CONTROLLER = 14
|
|
|
|
remove_pages = [HW_LIST_TYPE_NIC, HW_LIST_TYPE_INPUT,
|
|
HW_LIST_TYPE_GRAPHICS, HW_LIST_TYPE_SOUND, HW_LIST_TYPE_CHAR,
|
|
HW_LIST_TYPE_HOSTDEV, HW_LIST_TYPE_DISK, HW_LIST_TYPE_VIDEO,
|
|
HW_LIST_TYPE_WATCHDOG, HW_LIST_TYPE_CONTROLLER]
|
|
|
|
# Boot device columns
|
|
BOOT_DEV_TYPE = 0
|
|
BOOT_LABEL = 1
|
|
BOOT_ICON = 2
|
|
BOOT_ACTIVE = 3
|
|
|
|
# Main tab pages
|
|
PAGE_CONSOLE = 0
|
|
PAGE_DETAILS = 1
|
|
PAGE_DYNAMIC_OFFSET = 2
|
|
|
|
def prettyify_disk_bus(bus):
|
|
if bus in ["ide", "scsi", "usb"]:
|
|
return bus.upper()
|
|
|
|
if bus in ["xen"]:
|
|
return bus.capitalize()
|
|
|
|
if bus == "virtio":
|
|
return "VirtIO"
|
|
|
|
return bus
|
|
|
|
def prettyify_disk(devtype, bus, idx):
|
|
busstr = prettyify_disk_bus(bus) or ""
|
|
|
|
if devtype == "floppy":
|
|
devstr = "Floppy"
|
|
busstr = ""
|
|
elif devtype == "cdrom":
|
|
devstr = "CDROM"
|
|
else:
|
|
devstr = devtype.capitalize()
|
|
|
|
if busstr:
|
|
ret = "%s %s" % (busstr, devstr)
|
|
else:
|
|
ret = devstr
|
|
|
|
return "%s %s" % (ret, idx)
|
|
|
|
def safeint(val, fmt="%.3d"):
|
|
try:
|
|
int(val)
|
|
except:
|
|
return str(val)
|
|
return fmt % int(val)
|
|
|
|
def prettyify_bytes(val):
|
|
if val > (1024 * 1024 * 1024):
|
|
return "%2.2f GB" % (val / (1024.0 * 1024.0 * 1024.0))
|
|
else:
|
|
return "%2.2f MB" % (val / (1024.0 * 1024.0))
|
|
|
|
def build_hostdev_label(hostdev):
|
|
# String shown in the devices details section
|
|
srclabel = ""
|
|
# String shown in the VMs hardware list
|
|
hwlabel = ""
|
|
|
|
typ = hostdev.type
|
|
vendor = hostdev.vendor
|
|
product = hostdev.product
|
|
addrbus = hostdev.bus
|
|
addrdev = hostdev.device
|
|
addrslt = hostdev.slot
|
|
addrfun = hostdev.function
|
|
addrdom = hostdev.domain
|
|
|
|
def dehex(val):
|
|
if val.startswith("0x"):
|
|
val = val[2:]
|
|
return val
|
|
|
|
hwlabel = typ.upper()
|
|
srclabel = typ.upper()
|
|
|
|
if vendor and product:
|
|
# USB by vendor + product
|
|
devstr = " %s:%s" % (dehex(vendor), dehex(product))
|
|
srclabel += devstr
|
|
hwlabel += devstr
|
|
|
|
elif addrbus and addrdev:
|
|
# USB by bus + dev
|
|
srclabel += (" Bus %s Device %s" %
|
|
(safeint(addrbus), safeint(addrdev)))
|
|
hwlabel += " %s:%s" % (safeint(addrbus), safeint(addrdev))
|
|
|
|
elif addrbus and addrslt and addrfun and addrdom:
|
|
# PCI by bus:slot:function
|
|
devstr = (" %s:%s:%s.%s" %
|
|
(dehex(addrdom), dehex(addrbus),
|
|
dehex(addrslt), dehex(addrfun)))
|
|
srclabel += devstr
|
|
hwlabel += devstr
|
|
|
|
return srclabel, hwlabel
|
|
|
|
def lookup_nodedev(vmmconn, hostdev):
|
|
def intify(val, do_hex=False):
|
|
try:
|
|
if do_hex:
|
|
return int(val or '0x00', 16)
|
|
else:
|
|
return int(val)
|
|
except:
|
|
return -1
|
|
|
|
def attrVal(node, attr):
|
|
if not hasattr(node, attr):
|
|
return None
|
|
return getattr(node, attr)
|
|
|
|
devtype = hostdev.type
|
|
vendor_id = hostdev.vendor or -1
|
|
product_id = hostdev.product or -1
|
|
device = intify(hostdev.device, True)
|
|
bus = intify(hostdev.bus, True)
|
|
domain = intify(hostdev.domain, True)
|
|
func = intify(hostdev.function, True)
|
|
slot = intify(hostdev.slot, True)
|
|
found_dev = None
|
|
|
|
# For USB we want a device, not a bus
|
|
if devtype == 'usb':
|
|
devtype = 'usb_device'
|
|
|
|
devs = vmmconn.get_nodedevs(devtype, None)
|
|
for dev in devs:
|
|
# Try to get info from {product|vendor}_id
|
|
if (attrVal(dev, "product_id") == product_id and
|
|
attrVal(dev, "vendor_id") == vendor_id):
|
|
found_dev = dev
|
|
break
|
|
else:
|
|
# Try to get info from bus/addr
|
|
dev_id = intify(attrVal(dev, "device"))
|
|
bus_id = intify(attrVal(dev, "bus"))
|
|
dom_id = intify(attrVal(dev, "domain"))
|
|
func_id = intify(attrVal(dev, "function"))
|
|
slot_id = intify(attrVal(dev, "slot"))
|
|
|
|
if ((dev_id == device and bus_id == bus) or
|
|
(dom_id == domain and func_id == func and
|
|
bus_id == bus and slot_id == slot)):
|
|
found_dev = dev
|
|
break
|
|
|
|
return found_dev
|
|
|
|
class vmmDetails(vmmGObjectUI):
|
|
__gsignals__ = {
|
|
"action-show-console": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, (str, str)),
|
|
"action-save-domain": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, (str, str)),
|
|
"action-destroy-domain": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, (str, str)),
|
|
"action-suspend-domain": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, (str, str)),
|
|
"action-resume-domain": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, (str, str)),
|
|
"action-run-domain": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, (str, str)),
|
|
"action-shutdown-domain": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, (str, str)),
|
|
"action-reboot-domain": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, (str, str)),
|
|
"action-show-help": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, [str]),
|
|
"action-exit-app": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, []),
|
|
"action-view-manager": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, []),
|
|
"action-migrate-domain": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, (str, str)),
|
|
"action-clone-domain": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, (str, str)),
|
|
"details-closed": (gobject.SIGNAL_RUN_FIRST,
|
|
gobject.TYPE_NONE, ()),
|
|
}
|
|
|
|
|
|
def __init__(self, vm, engine, parent=None):
|
|
vmmGObjectUI.__init__(self, "vmm-details.glade", "vmm-details")
|
|
self.vm = vm
|
|
self.conn = self.vm.get_connection()
|
|
self.engine = engine
|
|
|
|
self.is_customize_dialog = False
|
|
if parent:
|
|
self.is_customize_dialog = True
|
|
# Details window is being abused as a 'configure before install'
|
|
# dialog, set things as appropriate
|
|
self.topwin.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
|
|
self.topwin.set_transient_for(parent)
|
|
|
|
self.window.get_widget("customize-toolbar").show()
|
|
self.window.get_widget("details-toolbar").hide()
|
|
self.window.get_widget("details-menubar").hide()
|
|
pages = self.window.get_widget("details-pages")
|
|
pages.set_current_page(PAGE_DETAILS)
|
|
|
|
|
|
self.serial_tabs = []
|
|
self.serial_mappings = {}
|
|
self.last_console_page = PAGE_CONSOLE
|
|
self.addhw = None
|
|
self.media_choosers = {"cdrom": None, "floppy": None}
|
|
self.storage_browser = None
|
|
|
|
self.ignorePause = False
|
|
self.ignoreDetails = False
|
|
self._cpu_copy_host = False
|
|
|
|
self.console = vmmConsolePages(self.vm, self.window)
|
|
|
|
# Set default window size
|
|
w, h = self.vm.get_details_window_size()
|
|
self.topwin.set_default_size(w or 800, h or 600)
|
|
|
|
self.addhwmenu = None
|
|
self.init_menus()
|
|
self.init_details()
|
|
|
|
self.serial_popup = None
|
|
self.serial_copy = None
|
|
self.serial_paste = None
|
|
self.serial_close = None
|
|
self.init_serial()
|
|
|
|
self.cpu_usage_graph = None
|
|
self.memory_usage_graph = None
|
|
self.disk_io_graph = None
|
|
self.network_traffic_graph = None
|
|
self.init_graphs()
|
|
|
|
self.window.signal_autoconnect({
|
|
"on_close_details_clicked": self.close,
|
|
"on_details_menu_close_activate": self.close,
|
|
"on_vmm_details_delete_event": self.close,
|
|
"on_vmm_details_configure_event": self.window_resized,
|
|
"on_details_menu_quit_activate": self.exit_app,
|
|
|
|
"on_control_vm_details_toggled": self.details_console_changed,
|
|
"on_control_vm_console_toggled": self.details_console_changed,
|
|
"on_control_run_clicked": self.control_vm_run,
|
|
"on_control_shutdown_clicked": self.control_vm_shutdown,
|
|
"on_control_pause_toggled": self.control_vm_pause,
|
|
"on_control_fullscreen_toggled": self.control_fullscreen,
|
|
|
|
"on_details_customize_finish_clicked": self.close,
|
|
|
|
"on_details_menu_run_activate": self.control_vm_run,
|
|
"on_details_menu_poweroff_activate": self.control_vm_shutdown,
|
|
"on_details_menu_reboot_activate": self.control_vm_reboot,
|
|
"on_details_menu_save_activate": self.control_vm_save,
|
|
"on_details_menu_destroy_activate": self.control_vm_destroy,
|
|
"on_details_menu_pause_activate": self.control_vm_pause,
|
|
"on_details_menu_clone_activate": self.control_vm_clone,
|
|
"on_details_menu_migrate_activate": self.control_vm_migrate,
|
|
"on_details_menu_screenshot_activate": self.control_vm_screenshot,
|
|
"on_details_menu_graphics_activate": self.control_vm_console,
|
|
"on_details_menu_view_toolbar_activate": self.toggle_toolbar,
|
|
"on_details_menu_view_manager_activate": self.view_manager,
|
|
"on_details_menu_view_details_toggled": self.details_console_changed,
|
|
"on_details_menu_view_console_toggled": self.details_console_changed,
|
|
|
|
"on_details_pages_switch_page": self.switch_page,
|
|
|
|
"on_overview_name_changed": self.config_enable_apply,
|
|
"on_overview_acpi_changed": self.config_acpi_changed,
|
|
"on_overview_apic_changed": self.config_apic_changed,
|
|
"on_overview_clock_changed": self.config_enable_apply,
|
|
"on_security_label_changed": self.security_label_changed,
|
|
"on_security_type_changed": self.security_type_changed,
|
|
|
|
"on_config_vcpus_changed": self.config_vcpus_changed,
|
|
"on_config_maxvcpus_changed": self.config_maxvcpus_changed,
|
|
"on_config_vcpupin_changed": self.config_vcpus_changed,
|
|
"on_config_vcpupin_generate_clicked": self.config_vcpupin_generate,
|
|
"on_cpu_model_changed": self.config_enable_apply,
|
|
"on_cpu_cores_changed": self.config_enable_apply,
|
|
"on_cpu_sockets_changed": self.config_enable_apply,
|
|
"on_cpu_threads_changed": self.config_enable_apply,
|
|
"on_cpu_copy_host_clicked": self.config_cpu_copy_host,
|
|
"on_cpu_topology_enable_toggled": self.config_cpu_topology_enable,
|
|
|
|
"on_config_memory_changed": self.config_memory_changed,
|
|
"on_config_maxmem_changed": self.config_maxmem_changed,
|
|
|
|
"on_config_boot_moveup_clicked" : (self.config_boot_move, True),
|
|
"on_config_boot_movedown_clicked" : (self.config_boot_move,
|
|
False),
|
|
"on_config_autostart_changed": self.config_enable_apply,
|
|
"on_boot_menu_changed": self.config_enable_apply,
|
|
"on_boot_kernel_changed": self.config_enable_apply,
|
|
"on_boot_kernel_initrd_changed": self.config_enable_apply,
|
|
"on_boot_kernel_args_changed": self.config_enable_apply,
|
|
"on_boot_kernel_browse_clicked": self.browse_kernel,
|
|
"on_boot_kernel_initrd_browse_clicked": self.browse_initrd,
|
|
|
|
"on_disk_readonly_changed": self.config_enable_apply,
|
|
"on_disk_shareable_changed": self.config_enable_apply,
|
|
"on_disk_cache_combo_changed": self.config_enable_apply,
|
|
"on_disk_bus_combo_changed": self.config_enable_apply,
|
|
"on_disk_format_changed": self.config_enable_apply,
|
|
|
|
"on_network_source_combo_changed": self.config_enable_apply,
|
|
"on_network_bridge_changed": self.config_enable_apply,
|
|
"on_network_model_combo_changed": self.config_enable_apply,
|
|
|
|
"on_vport_type_changed": self.config_enable_apply,
|
|
"on_vport_managerid_changed": self.config_enable_apply,
|
|
"on_vport_typeid_changed": self.config_enable_apply,
|
|
"on_vport_typeidversion_changed": self.config_enable_apply,
|
|
"on_vport_instanceid_changed": self.config_enable_apply,
|
|
|
|
"on_gfx_type_combo_changed": self.config_enable_apply,
|
|
"on_vnc_keymap_combo_changed": self.config_enable_apply,
|
|
"on_vnc_password_changed": self.config_enable_apply,
|
|
|
|
"on_sound_model_combo_changed": self.config_enable_apply,
|
|
|
|
"on_video_model_combo_changed": self.config_enable_apply,
|
|
|
|
"on_watchdog_model_combo_changed": self.config_enable_apply,
|
|
"on_watchdog_action_combo_changed": self.config_enable_apply,
|
|
|
|
"on_config_apply_clicked": self.config_apply,
|
|
|
|
"on_details_help_activate": self.show_help,
|
|
|
|
"on_config_cdrom_connect_clicked": self.toggle_storage_media,
|
|
"on_config_remove_clicked": self.remove_xml_dev,
|
|
"on_add_hardware_button_clicked": self.add_hardware,
|
|
|
|
"on_hw_list_button_press_event": self.popup_addhw_menu,
|
|
|
|
# Listeners stored in vmmConsolePages
|
|
"on_details_menu_view_fullscreen_activate": self.console.toggle_fullscreen,
|
|
"on_details_menu_view_size_to_vm_activate": self.console.size_to_vm,
|
|
"on_details_menu_view_scale_always_toggled": self.console.set_scale_type,
|
|
"on_details_menu_view_scale_fullscreen_toggled": self.console.set_scale_type,
|
|
"on_details_menu_view_scale_never_toggled": self.console.set_scale_type,
|
|
|
|
"on_details_menu_send_cad_activate": self.console.send_key,
|
|
"on_details_menu_send_cab_activate": self.console.send_key,
|
|
"on_details_menu_send_caf1_activate": self.console.send_key,
|
|
"on_details_menu_send_caf2_activate": self.console.send_key,
|
|
"on_details_menu_send_caf3_activate": self.console.send_key,
|
|
"on_details_menu_send_caf4_activate": self.console.send_key,
|
|
"on_details_menu_send_caf5_activate": self.console.send_key,
|
|
"on_details_menu_send_caf6_activate": self.console.send_key,
|
|
"on_details_menu_send_caf7_activate": self.console.send_key,
|
|
"on_details_menu_send_caf8_activate": self.console.send_key,
|
|
"on_details_menu_send_caf9_activate": self.console.send_key,
|
|
"on_details_menu_send_caf10_activate": self.console.send_key,
|
|
"on_details_menu_send_caf11_activate": self.console.send_key,
|
|
"on_details_menu_send_caf12_activate": self.console.send_key,
|
|
"on_details_menu_send_printscreen_activate": self.console.send_key,
|
|
|
|
"on_console_auth_password_activate": self.console.auth_login,
|
|
"on_console_auth_login_clicked": self.console.auth_login,
|
|
})
|
|
|
|
# Deliberately keep all this after signal connection
|
|
self.vm.connect("status-changed", self.refresh_vm_state)
|
|
self.vm.connect("config-changed", self.refresh_vm_state)
|
|
self.vm.connect("resources-sampled", self.refresh_resources)
|
|
self.window.get_widget("hw-list").get_selection().connect(
|
|
"changed",
|
|
self.hw_selected)
|
|
self.window.get_widget("config-boot-list").get_selection().connect(
|
|
"changed",
|
|
self.config_bootdev_selected)
|
|
|
|
finish_img = gtk.image_new_from_stock(gtk.STOCK_ADD,
|
|
gtk.ICON_SIZE_BUTTON)
|
|
self.window.get_widget("add-hardware-button").set_image(finish_img)
|
|
|
|
self.populate_hw_list()
|
|
self.repopulate_boot_list()
|
|
|
|
self.hw_selected()
|
|
self.refresh_vm_state()
|
|
|
|
|
|
def show(self):
|
|
vis = self.is_visible()
|
|
self.topwin.present()
|
|
if vis:
|
|
return
|
|
|
|
self.engine.increment_window_counter()
|
|
self.refresh_vm_state()
|
|
|
|
def close(self, ignore1=None, ignore2=None):
|
|
fs = self.window.get_widget("details-menu-view-fullscreen")
|
|
if fs.get_active():
|
|
fs.set_active(False)
|
|
|
|
if not self.is_visible():
|
|
return
|
|
|
|
self.topwin.hide()
|
|
if self.console.viewer and self.console.viewer.get_widget() and \
|
|
self.console.viewer.get_widget().flags() & gtk.VISIBLE:
|
|
try:
|
|
self.console.close_viewer()
|
|
except:
|
|
logging.error("Failure when disconnecting from desktop server")
|
|
self.engine.decrement_window_counter()
|
|
|
|
self.emit("details-closed")
|
|
return 1
|
|
|
|
def is_visible(self):
|
|
return bool(self.topwin.flags() & gtk.VISIBLE)
|
|
|
|
|
|
##########################
|
|
# Initialization helpers #
|
|
##########################
|
|
|
|
def init_menus(self):
|
|
# Shutdown button menu
|
|
uihelpers.build_shutdown_button_menu(
|
|
self.window.get_widget("control-shutdown"),
|
|
self.control_vm_shutdown,
|
|
self.control_vm_reboot,
|
|
self.control_vm_destroy,
|
|
self.control_vm_save)
|
|
|
|
icon_name = self.config.get_shutdown_icon_name()
|
|
for name in ["details-menu-shutdown",
|
|
"details-menu-reboot",
|
|
"details-menu-poweroff",
|
|
"details-menu-destroy"]:
|
|
image = gtk.image_new_from_icon_name(icon_name, gtk.ICON_SIZE_MENU)
|
|
self.window.get_widget(name).set_image(image)
|
|
|
|
# Add HW popup menu
|
|
self.addhwmenu = gtk.Menu()
|
|
addHW = gtk.ImageMenuItem(_("Add Hardware"))
|
|
addHWImg = gtk.Image()
|
|
addHWImg.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU)
|
|
addHW.set_image(addHWImg)
|
|
addHW.show()
|
|
addHW.connect("activate", self.add_hardware)
|
|
self.addhwmenu.add(addHW)
|
|
|
|
# Serial list menu
|
|
smenu = gtk.Menu()
|
|
smenu.connect("show", self.populate_serial_menu)
|
|
self.window.get_widget("details-menu-view-serial-list").set_submenu(smenu)
|
|
|
|
# Don't allowing changing network/disks for Dom0
|
|
dom0 = self.vm.is_management_domain()
|
|
self.window.get_widget("add-hardware-button").set_sensitive(not dom0)
|
|
|
|
self.window.get_widget("hw-panel").set_show_tabs(False)
|
|
self.window.get_widget("details-pages").set_show_tabs(False)
|
|
self.window.get_widget("console-pages").set_show_tabs(False)
|
|
self.window.get_widget("details-menu-view-toolbar").set_active(self.config.get_details_show_toolbar())
|
|
|
|
# XXX: Help docs useless/out of date
|
|
self.window.get_widget("help_menuitem").hide()
|
|
|
|
def init_serial(self):
|
|
self.serial_popup = gtk.Menu()
|
|
|
|
self.serial_copy = gtk.ImageMenuItem(gtk.STOCK_COPY)
|
|
self.serial_popup.add(self.serial_copy)
|
|
|
|
self.serial_paste = gtk.ImageMenuItem(gtk.STOCK_PASTE)
|
|
self.serial_popup.add(self.serial_paste)
|
|
|
|
self.serial_popup.add(gtk.SeparatorMenuItem())
|
|
|
|
self.serial_close = gtk.ImageMenuItem(_("Close tab"))
|
|
close_image = gtk.Image()
|
|
close_image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
|
|
self.serial_close.set_image(close_image)
|
|
self.serial_popup.add(self.serial_close)
|
|
|
|
def init_graphs(self):
|
|
graph_table = self.window.get_widget("graph-table")
|
|
|
|
self.cpu_usage_graph = Sparkline()
|
|
self.cpu_usage_graph.set_property("reversed", True)
|
|
graph_table.attach(self.cpu_usage_graph, 1, 2, 0, 1)
|
|
|
|
self.memory_usage_graph = Sparkline()
|
|
self.memory_usage_graph.set_property("reversed", True)
|
|
graph_table.attach(self.memory_usage_graph, 1, 2, 1, 2)
|
|
|
|
self.disk_io_graph = Sparkline()
|
|
self.disk_io_graph.set_property("reversed", True)
|
|
self.disk_io_graph.set_property("filled", False)
|
|
self.disk_io_graph.set_property("num_sets", 2)
|
|
self.disk_io_graph.set_property("rgb", map(lambda x: x / 255.0,
|
|
[0x82, 0x00, 0x3B, 0x29, 0x5C, 0x45]))
|
|
graph_table.attach(self.disk_io_graph, 1, 2, 2, 3)
|
|
|
|
self.network_traffic_graph = Sparkline()
|
|
self.network_traffic_graph.set_property("reversed", True)
|
|
self.network_traffic_graph.set_property("filled", False)
|
|
self.network_traffic_graph.set_property("num_sets", 2)
|
|
self.network_traffic_graph.set_property("rgb",
|
|
map(lambda x: x / 255.0,
|
|
[0x82, 0x00, 0x3B,
|
|
0x29, 0x5C, 0x45]))
|
|
graph_table.attach(self.network_traffic_graph, 1, 2, 3, 4)
|
|
|
|
graph_table.show_all()
|
|
|
|
def init_details(self):
|
|
# Hardware list
|
|
# [ label, icon name, icon size, hw type, hw data/class]
|
|
hw_list_model = gtk.ListStore(str, str, int, int,
|
|
gobject.TYPE_PYOBJECT)
|
|
self.window.get_widget("hw-list").set_model(hw_list_model)
|
|
|
|
hwCol = gtk.TreeViewColumn("Hardware")
|
|
hwCol.set_spacing(6)
|
|
hwCol.set_min_width(165)
|
|
hw_txt = gtk.CellRendererText()
|
|
hw_img = gtk.CellRendererPixbuf()
|
|
hwCol.pack_start(hw_img, False)
|
|
hwCol.pack_start(hw_txt, True)
|
|
hwCol.add_attribute(hw_txt, 'text', HW_LIST_COL_LABEL)
|
|
hwCol.add_attribute(hw_img, 'stock-size', HW_LIST_COL_ICON_SIZE)
|
|
hwCol.add_attribute(hw_img, 'icon-name', HW_LIST_COL_ICON_NAME)
|
|
self.window.get_widget("hw-list").append_column(hwCol)
|
|
|
|
# Description text view
|
|
desc = self.window.get_widget("overview-description")
|
|
buf = gtk.TextBuffer()
|
|
buf.connect("changed", self.config_enable_apply)
|
|
desc.set_buffer(buf)
|
|
|
|
# Clock combo
|
|
clock_combo = self.window.get_widget("overview-clock-combo")
|
|
clock_model = gtk.ListStore(str)
|
|
clock_combo.set_model(clock_model)
|
|
text = gtk.CellRendererText()
|
|
clock_combo.pack_start(text, True)
|
|
clock_combo.add_attribute(text, 'text', 0)
|
|
clock_model.set_sort_column_id(0, gtk.SORT_ASCENDING)
|
|
for offset in ["localtime", "utc"]:
|
|
clock_model.append([offset])
|
|
|
|
# Security info tooltips
|
|
util.tooltip_wrapper(self.window.get_widget("security-static-info"),
|
|
_("Static SELinux security type tells libvirt to always start the guest process with the specified label. The administrator is responsible for making sure the images are labeled correctly on disk."))
|
|
util.tooltip_wrapper(self.window.get_widget("security-dynamic-info"),
|
|
_("The dynamic SELinux security type tells libvirt to automatically pick a unique label for the guest process and guest image, ensuring total isolation of the guest. (Default)"))
|
|
|
|
# VCPU Pinning list
|
|
generate_cpuset = self.window.get_widget("config-vcpupin-generate")
|
|
generate_warn = self.window.get_widget("config-vcpupin-generate-err")
|
|
if not self.conn.get_capabilities().host.topology:
|
|
generate_cpuset.set_sensitive(False)
|
|
generate_warn.show()
|
|
util.tooltip_wrapper(generate_warn,
|
|
_("Libvirt did not detect NUMA capabilities."))
|
|
|
|
|
|
# [ VCPU #, Currently running on Phys CPU #, CPU Pinning list ]
|
|
vcpu_list = self.window.get_widget("config-vcpu-list")
|
|
vcpu_model = gtk.ListStore(str, str, str)
|
|
vcpu_list.set_model(vcpu_model)
|
|
|
|
vcpuCol = gtk.TreeViewColumn(_("VCPU"))
|
|
physCol = gtk.TreeViewColumn(_("On CPU"))
|
|
pinCol = gtk.TreeViewColumn(_("Pinning"))
|
|
|
|
vcpu_list.append_column(vcpuCol)
|
|
vcpu_list.append_column(physCol)
|
|
vcpu_list.append_column(pinCol)
|
|
|
|
vcpu_text = gtk.CellRendererText()
|
|
vcpuCol.pack_start(vcpu_text, True)
|
|
vcpuCol.add_attribute(vcpu_text, 'text', 0)
|
|
vcpuCol.set_sort_column_id(0)
|
|
|
|
phys_text = gtk.CellRendererText()
|
|
physCol.pack_start(phys_text, True)
|
|
physCol.add_attribute(phys_text, 'text', 1)
|
|
physCol.set_sort_column_id(1)
|
|
|
|
pin_text = gtk.CellRendererText()
|
|
pin_text.set_property("editable", True)
|
|
pin_text.connect("edited", self.config_vcpu_pin)
|
|
pinCol.pack_start(pin_text, True)
|
|
pinCol.add_attribute(pin_text, 'text', 2)
|
|
|
|
# Boot device list
|
|
boot_list = self.window.get_widget("config-boot-list")
|
|
# model = [ XML boot type, display name, icon name, enabled ]
|
|
boot_list_model = gtk.ListStore(str, str, str, bool)
|
|
boot_list.set_model(boot_list_model)
|
|
|
|
chkCol = gtk.TreeViewColumn()
|
|
txtCol = gtk.TreeViewColumn()
|
|
|
|
boot_list.append_column(chkCol)
|
|
boot_list.append_column(txtCol)
|
|
|
|
chk = gtk.CellRendererToggle()
|
|
chk.connect("toggled", self.config_boot_toggled)
|
|
chkCol.pack_start(chk, False)
|
|
chkCol.add_attribute(chk, 'active', BOOT_ACTIVE)
|
|
|
|
icon = gtk.CellRendererPixbuf()
|
|
txtCol.pack_start(icon, False)
|
|
txtCol.add_attribute(icon, 'icon-name', BOOT_ICON)
|
|
|
|
text = gtk.CellRendererText()
|
|
txtCol.pack_start(text, True)
|
|
txtCol.add_attribute(text, 'text', BOOT_LABEL)
|
|
txtCol.add_attribute(text, 'sensitive', BOOT_ACTIVE)
|
|
|
|
no_default = not self.is_customize_dialog
|
|
|
|
# CPU features
|
|
caps = self.vm.get_connection().get_capabilities()
|
|
cpu_values = None
|
|
cpu_names = []
|
|
all_features = []
|
|
|
|
try:
|
|
cpu_values = caps.get_cpu_values(self.vm.get_arch())
|
|
cpu_names = sorted(map(lambda c: c.model, cpu_values.cpus),
|
|
key=str.lower)
|
|
all_features = cpu_values.features
|
|
except:
|
|
logging.exception("Error populating CPU model list")
|
|
|
|
# [ feature name, enabled? ]
|
|
feat_list = self.window.get_widget("cpu-features")
|
|
feat_model = gtk.ListStore(str, bool)
|
|
feat_list.set_model(feat_model)
|
|
|
|
nameCol = gtk.TreeViewColumn()
|
|
chkCol = gtk.TreeViewColumn()
|
|
|
|
feat_list.append_column(nameCol)
|
|
feat_list.append_column(chkCol)
|
|
|
|
name_text = gtk.CellRendererText()
|
|
nameCol.pack_start(name_text, True)
|
|
nameCol.add_attribute(name_text, 'text', 0)
|
|
nameCol.set_sort_column_id(0)
|
|
|
|
feat_toggle = gtk.CellRendererToggle()
|
|
chkCol.pack_start(feat_toggle, True)
|
|
chkCol.add_attribute(feat_toggle, 'active', 1)
|
|
chkCol.set_sort_column_id(1)
|
|
|
|
def feature_changed(src, index, model):
|
|
model[index][1] = not src.get_active()
|
|
self.config_enable_apply()
|
|
|
|
feat_toggle.connect("toggled", feature_changed, feat_model)
|
|
for name in all_features:
|
|
feat_model.append([name, False])
|
|
|
|
# CPU model combo
|
|
cpu_model = self.window.get_widget("cpu-model")
|
|
|
|
model = gtk.ListStore(str, object)
|
|
cpu_model.set_model(model)
|
|
cpu_model.set_text_column(0)
|
|
model.set_sort_column_id(0, gtk.SORT_ASCENDING)
|
|
for name in cpu_names:
|
|
model.append([name, cpu_values.get_cpu(name)])
|
|
|
|
# Disk cache combo
|
|
disk_cache = self.window.get_widget("disk-cache-combo")
|
|
uihelpers.build_cache_combo(self.vm, disk_cache)
|
|
|
|
# Disk format combo
|
|
format_list = self.window.get_widget("disk-format")
|
|
uihelpers.build_storage_format_combo(self.vm, format_list)
|
|
|
|
# Disk bus combo
|
|
disk_bus = self.window.get_widget("disk-bus-combo")
|
|
uihelpers.build_disk_bus_combo(self.vm, disk_bus)
|
|
|
|
# Network source
|
|
net_source = self.window.get_widget("network-source-combo")
|
|
net_bridge = self.window.get_widget("network-bridge-box")
|
|
uihelpers.init_network_list(net_source, net_bridge)
|
|
|
|
# Network model
|
|
net_model = self.window.get_widget("network-model-combo")
|
|
uihelpers.build_netmodel_combo(self.vm, net_model)
|
|
|
|
# Graphics type
|
|
gfx_type = self.window.get_widget("gfx-type-combo")
|
|
model = gtk.ListStore(str, str)
|
|
gfx_type.set_model(model)
|
|
text = gtk.CellRendererText()
|
|
gfx_type.pack_start(text, True)
|
|
gfx_type.add_attribute(text, 'text', 1)
|
|
model.append([virtinst.VirtualGraphics.TYPE_VNC,
|
|
"VNC"])
|
|
model.append([virtinst.VirtualGraphics.TYPE_SPICE,
|
|
"Spice"])
|
|
gfx_type.set_active(-1)
|
|
|
|
# Graphics keymap
|
|
gfx_keymap = self.window.get_widget("gfx-keymap-combo")
|
|
uihelpers.build_vnc_keymap_combo(self.vm, gfx_keymap,
|
|
no_default=no_default)
|
|
|
|
# Sound model
|
|
sound_dev = self.window.get_widget("sound-model-combo")
|
|
uihelpers.build_sound_combo(self.vm, sound_dev, no_default=no_default)
|
|
|
|
# Video model combo
|
|
video_dev = self.window.get_widget("video-model-combo")
|
|
uihelpers.build_video_combo(self.vm, video_dev, no_default=no_default)
|
|
|
|
# Watchdog model combo
|
|
combo = self.window.get_widget("watchdog-model-combo")
|
|
uihelpers.build_watchdogmodel_combo(self.vm, combo,
|
|
no_default=no_default)
|
|
|
|
# Watchdog action combo
|
|
combo = self.window.get_widget("watchdog-action-combo")
|
|
uihelpers.build_watchdogaction_combo(self.vm, combo,
|
|
no_default=no_default)
|
|
|
|
# Helper function to handle the combo/label pattern used for
|
|
# video model, sound model, network model, etc.
|
|
def set_combo_label(self, prefix, value, model_idx=0, label="",
|
|
comparefunc=None):
|
|
label = label or value
|
|
model_label = self.window.get_widget(prefix + "-label")
|
|
model_combo = self.window.get_widget(prefix + "-combo")
|
|
|
|
idx = -1
|
|
if comparefunc:
|
|
model_in_list, idx = comparefunc(model_combo.get_model(), value)
|
|
else:
|
|
model_list = map(lambda x: x[model_idx], model_combo.get_model())
|
|
model_in_list = (value in model_list)
|
|
if model_in_list:
|
|
idx = model_list.index(value)
|
|
|
|
model_label.set_property("visible", not model_in_list)
|
|
model_combo.set_property("visible", model_in_list)
|
|
model_label.set_text(label or "")
|
|
|
|
if model_in_list:
|
|
model_combo.set_active(idx)
|
|
else:
|
|
model_combo.set_active(-1)
|
|
|
|
# Helper for accessing value of combo/label pattern
|
|
def get_combo_value(self, widgetname, model_idx=0):
|
|
combo = self.window.get_widget(widgetname)
|
|
if combo.get_active() < 0:
|
|
return None
|
|
return combo.get_model()[combo.get_active()][model_idx]
|
|
|
|
def get_combo_label_value(self, prefix, model_idx=0):
|
|
comboname = prefix + "-combo"
|
|
label = self.window.get_widget(prefix + "-label")
|
|
value = None
|
|
|
|
if label.get_property("visible"):
|
|
value = label.get_text()
|
|
else:
|
|
value = self.get_combo_value(comboname, model_idx)
|
|
|
|
return value
|
|
|
|
##########################
|
|
# Window state listeners #
|
|
##########################
|
|
|
|
def window_resized(self, ignore, event):
|
|
# Sometimes dimensions change when window isn't visible
|
|
if not self.is_visible():
|
|
return
|
|
|
|
self.vm.set_details_window_size(event.width, event.height)
|
|
|
|
def popup_addhw_menu(self, widget, event):
|
|
ignore = widget
|
|
if event.button != 3:
|
|
return
|
|
|
|
self.addhwmenu.popup(None, None, None, 0, event.time)
|
|
|
|
def populate_serial_menu(self, src):
|
|
for ent in src:
|
|
src.remove(ent)
|
|
|
|
devs = self.vm.get_serial_devs()
|
|
if len(devs) == 0:
|
|
item = gtk.MenuItem(_("No text console available"))
|
|
item.set_sensitive(False)
|
|
src.add(item)
|
|
|
|
on_serial = (self.last_console_page >= PAGE_DYNAMIC_OFFSET)
|
|
serial_page_dev = None
|
|
if on_serial:
|
|
serial_idx = self.last_console_page - PAGE_DYNAMIC_OFFSET
|
|
if len(self.serial_tabs) >= serial_idx:
|
|
serial_page_dev = self.serial_tabs[serial_idx]
|
|
on_graphics = (self.last_console_page == PAGE_CONSOLE)
|
|
|
|
group = None
|
|
usable_types = ["pty"]
|
|
for dev in devs:
|
|
sensitive = False
|
|
msg = ""
|
|
item = gtk.RadioMenuItem(group, dev[0])
|
|
if group == None:
|
|
group = item
|
|
|
|
if self.vm.get_connection().is_remote():
|
|
msg = _("Serial console not yet supported over remote "
|
|
"connection.")
|
|
elif not self.vm.is_active():
|
|
msg = _("Serial console not available for inactive guest.")
|
|
elif not dev[1] in usable_types:
|
|
msg = _("Console for device type '%s' not yet supported.") % \
|
|
dev[1]
|
|
elif dev[2] and not os.access(dev[2], os.R_OK | os.W_OK):
|
|
msg = _("Can not access console path '%s'.") % str(dev[2])
|
|
else:
|
|
sensitive = True
|
|
|
|
if not sensitive:
|
|
util.tooltip_wrapper(item, msg)
|
|
item.set_sensitive(sensitive)
|
|
|
|
if sensitive and on_serial and serial_page_dev == dev[0]:
|
|
# Tab is already open, make sure marked as such
|
|
item.set_active(True)
|
|
item.connect("toggled", self.control_serial_tab, dev[0], dev[3])
|
|
src.add(item)
|
|
|
|
src.add(gtk.SeparatorMenuItem())
|
|
|
|
devs = self.vm.get_graphics_devices()
|
|
if len(devs) == 0:
|
|
item = gtk.MenuItem(_("No graphical console available"))
|
|
item.set_sensitive(False)
|
|
src.add(item)
|
|
else:
|
|
dev = devs[0]
|
|
item = gtk.RadioMenuItem(group, _("Graphical Console %s") %
|
|
dev.pretty_type_simple(dev.type))
|
|
if group == None:
|
|
group = item
|
|
|
|
if on_graphics:
|
|
item.set_active(True)
|
|
item.connect("toggled", self.control_serial_tab,
|
|
dev.virtual_device_type, dev.type)
|
|
src.add(item)
|
|
|
|
src.show_all()
|
|
|
|
def control_fullscreen(self, src):
|
|
menu = self.window.get_widget("details-menu-view-fullscreen")
|
|
if src.get_active() != menu.get_active():
|
|
menu.set_active(src.get_active())
|
|
|
|
def toggle_toolbar(self, src):
|
|
active = src.get_active()
|
|
self.config.set_details_show_toolbar(active)
|
|
if active and not \
|
|
self.window.get_widget("details-menu-view-fullscreen").get_active():
|
|
self.window.get_widget("toolbar-box").show()
|
|
else:
|
|
self.window.get_widget("toolbar-box").hide()
|
|
|
|
def get_boot_selection(self):
|
|
widget = self.window.get_widget("config-boot-list")
|
|
selection = widget.get_selection()
|
|
model, treepath = selection.get_selected()
|
|
if treepath == None:
|
|
return None
|
|
return model[treepath]
|
|
|
|
def set_hw_selection(self, page):
|
|
hwlist = self.window.get_widget("hw-list")
|
|
selection = hwlist.get_selection()
|
|
selection.select_path(str(page))
|
|
|
|
def get_hw_selection(self, field):
|
|
vmlist = self.window.get_widget("hw-list")
|
|
selection = vmlist.get_selection()
|
|
active = selection.get_selected()
|
|
if active[1] == None:
|
|
return None
|
|
return active[0].get_value(active[1], field)
|
|
|
|
def force_get_hw_pagetype(self, page=None):
|
|
if page:
|
|
return page
|
|
|
|
page = self.get_hw_selection(HW_LIST_COL_TYPE)
|
|
if page is None:
|
|
page = HW_LIST_TYPE_GENERAL
|
|
self.window.get_widget("hw-list").get_selection().select_path(0)
|
|
|
|
return page
|
|
|
|
def hw_selected(self, ignore1=None, page=None, selected=True):
|
|
pagetype = self.force_get_hw_pagetype(page)
|
|
|
|
self.window.get_widget("config-remove").set_sensitive(True)
|
|
self.window.get_widget("hw-panel").set_sensitive(True)
|
|
self.window.get_widget("hw-panel").show()
|
|
|
|
try:
|
|
if pagetype == HW_LIST_TYPE_GENERAL:
|
|
self.refresh_overview_page()
|
|
elif pagetype == HW_LIST_TYPE_STATS:
|
|
self.refresh_stats_page()
|
|
elif pagetype == HW_LIST_TYPE_CPU:
|
|
self.refresh_config_cpu()
|
|
elif pagetype == HW_LIST_TYPE_MEMORY:
|
|
self.refresh_config_memory()
|
|
elif pagetype == HW_LIST_TYPE_BOOT:
|
|
self.refresh_boot_page()
|
|
elif pagetype == HW_LIST_TYPE_DISK:
|
|
self.refresh_disk_page()
|
|
elif pagetype == HW_LIST_TYPE_NIC:
|
|
self.refresh_network_page()
|
|
elif pagetype == HW_LIST_TYPE_INPUT:
|
|
self.refresh_input_page()
|
|
elif pagetype == HW_LIST_TYPE_GRAPHICS:
|
|
self.refresh_graphics_page()
|
|
elif pagetype == HW_LIST_TYPE_SOUND:
|
|
self.refresh_sound_page()
|
|
elif pagetype == HW_LIST_TYPE_CHAR:
|
|
self.refresh_char_page()
|
|
elif pagetype == HW_LIST_TYPE_HOSTDEV:
|
|
self.refresh_hostdev_page()
|
|
elif pagetype == HW_LIST_TYPE_VIDEO:
|
|
self.refresh_video_page()
|
|
elif pagetype == HW_LIST_TYPE_WATCHDOG:
|
|
self.refresh_watchdog_page()
|
|
elif pagetype == HW_LIST_TYPE_CONTROLLER:
|
|
self.refresh_controller_page()
|
|
else:
|
|
pagetype = -1
|
|
except Exception, e:
|
|
self.err.show_err(_("Error refreshing hardware page: %s") % str(e))
|
|
return
|
|
|
|
rem = pagetype in remove_pages
|
|
if selected:
|
|
self.window.get_widget("config-apply").set_sensitive(False)
|
|
self.window.get_widget("config-remove").set_property("visible", rem)
|
|
|
|
self.window.get_widget("hw-panel").set_current_page(pagetype)
|
|
|
|
def details_console_changed(self, src):
|
|
if self.ignoreDetails:
|
|
return
|
|
|
|
if not src.get_active():
|
|
return
|
|
|
|
is_details = False
|
|
if (src == self.window.get_widget("control-vm-details") or
|
|
src == self.window.get_widget("details-menu-view-details")):
|
|
is_details = True
|
|
|
|
pages = self.window.get_widget("details-pages")
|
|
if is_details:
|
|
pages.set_current_page(PAGE_DETAILS)
|
|
else:
|
|
pages.set_current_page(self.last_console_page)
|
|
|
|
def sync_details_console_view(self, is_details):
|
|
details = self.window.get_widget("control-vm-details")
|
|
details_menu = self.window.get_widget("details-menu-view-details")
|
|
console = self.window.get_widget("control-vm-console")
|
|
console_menu = self.window.get_widget("details-menu-view-console")
|
|
|
|
try:
|
|
self.ignoreDetails = True
|
|
|
|
details.set_active(is_details)
|
|
details_menu.set_active(is_details)
|
|
console.set_active(not is_details)
|
|
console_menu.set_active(not is_details)
|
|
finally:
|
|
self.ignoreDetails = False
|
|
|
|
def switch_page(self, ignore1=None, ignore2=None, newpage=None):
|
|
self.page_refresh(newpage)
|
|
|
|
self.sync_details_console_view(newpage == PAGE_DETAILS)
|
|
|
|
if newpage == PAGE_CONSOLE or newpage >= PAGE_DYNAMIC_OFFSET:
|
|
self.last_console_page = newpage
|
|
|
|
def change_run_text(self, can_restore):
|
|
if can_restore:
|
|
text = _("_Restore")
|
|
else:
|
|
text = _("_Run")
|
|
strip_text = text.replace("_", "")
|
|
|
|
self.window.get_widget("details-menu-run").get_child().set_label(text)
|
|
self.window.get_widget("control-run").set_label(strip_text)
|
|
|
|
def refresh_vm_state(self, ignore1=None, ignore2=None, ignore3=None):
|
|
vm = self.vm
|
|
status = self.vm.status()
|
|
|
|
self.toggle_toolbar(
|
|
self.window.get_widget("details-menu-view-toolbar"))
|
|
|
|
active = vm.is_active()
|
|
destroy = vm.is_destroyable()
|
|
run = vm.is_runable()
|
|
stop = vm.is_stoppable()
|
|
paused = vm.is_paused()
|
|
ro = vm.is_read_only()
|
|
|
|
if vm.managedsave_supported:
|
|
self.change_run_text(vm.hasSavedImage())
|
|
|
|
self.window.get_widget("details-menu-destroy").set_sensitive(destroy)
|
|
self.window.get_widget("control-run").set_sensitive(run)
|
|
self.window.get_widget("details-menu-run").set_sensitive(run)
|
|
|
|
self.window.get_widget("details-menu-migrate").set_sensitive(stop)
|
|
self.window.get_widget("control-shutdown").set_sensitive(stop)
|
|
self.window.get_widget("details-menu-shutdown").set_sensitive(stop)
|
|
self.window.get_widget("details-menu-save").set_sensitive(stop)
|
|
self.window.get_widget("control-pause").set_sensitive(stop)
|
|
self.window.get_widget("details-menu-pause").set_sensitive(stop)
|
|
|
|
# Set pause widget states
|
|
try:
|
|
self.ignorePause = True
|
|
self.window.get_widget("control-pause").set_active(paused)
|
|
self.window.get_widget("details-menu-pause").set_active(paused)
|
|
finally:
|
|
self.ignorePause = False
|
|
|
|
self.window.get_widget("overview-name").set_editable(not active)
|
|
|
|
self.window.get_widget("config-vcpus").set_sensitive(not ro)
|
|
self.window.get_widget("config-vcpupin").set_sensitive(not ro)
|
|
self.window.get_widget("config-memory").set_sensitive(not ro)
|
|
self.window.get_widget("config-maxmem").set_sensitive(not ro)
|
|
|
|
# Disable send key menu entries for offline VM
|
|
send_key = self.window.get_widget("details-menu-send-key")
|
|
for c in send_key.get_submenu().get_children():
|
|
c.set_sensitive(not (run or paused))
|
|
|
|
self.console.update_widget_states(vm, status)
|
|
|
|
self.window.get_widget("overview-status-text").set_text(
|
|
self.vm.run_status())
|
|
self.window.get_widget("overview-status-icon").set_from_pixbuf(
|
|
self.vm.run_status_icon())
|
|
|
|
details = self.window.get_widget("details-pages")
|
|
self.page_refresh(details.get_current_page())
|
|
|
|
# This is safe to refresh, and is dependent on domain state
|
|
self._refresh_runtime_pinning()
|
|
|
|
|
|
#############################
|
|
# External action listeners #
|
|
#############################
|
|
|
|
def show_help(self, src_ignore):
|
|
self.emit("action-show-help", "virt-manager-details-window")
|
|
|
|
def view_manager(self, src_ignore):
|
|
self.emit("action-view-manager")
|
|
|
|
def exit_app(self, src_ignore):
|
|
self.emit("action-exit-app")
|
|
|
|
def activate_console_page(self):
|
|
self.window.get_widget("details-pages").set_current_page(PAGE_CONSOLE)
|
|
|
|
def activate_performance_page(self):
|
|
self.window.get_widget("details-pages").set_current_page(PAGE_DETAILS)
|
|
self.set_hw_selection(HW_LIST_TYPE_STATS)
|
|
|
|
def activate_config_page(self):
|
|
self.window.get_widget("details-pages").set_current_page(PAGE_DETAILS)
|
|
|
|
def add_hardware(self, src_ignore):
|
|
try:
|
|
if self.addhw is None:
|
|
self.addhw = vmmAddHardware(self.vm)
|
|
|
|
self.addhw.show()
|
|
except Exception, e:
|
|
self.err.show_err((_("Error launching hardware dialog: %s") %
|
|
str(e)))
|
|
|
|
def remove_xml_dev(self, src_ignore):
|
|
info = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not info:
|
|
return
|
|
|
|
devtype = info.virtual_device_type
|
|
self.remove_device(devtype, info)
|
|
|
|
def control_vm_pause(self, src):
|
|
if self.ignorePause:
|
|
return
|
|
|
|
if src.get_active():
|
|
self.emit("action-suspend-domain",
|
|
self.vm.get_connection().get_uri(),
|
|
self.vm.get_uuid())
|
|
else:
|
|
self.emit("action-resume-domain",
|
|
self.vm.get_connection().get_uri(),
|
|
self.vm.get_uuid())
|
|
|
|
def control_vm_run(self, src_ignore):
|
|
self.emit("action-run-domain",
|
|
self.vm.get_connection().get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_shutdown(self, src_ignore):
|
|
self.emit("action-shutdown-domain",
|
|
self.vm.get_connection().get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_reboot(self, src_ignore):
|
|
self.emit("action-reboot-domain",
|
|
self.vm.get_connection().get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_console(self, src_ignore):
|
|
self.emit("action-show-console",
|
|
self.vm.get_connection().get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_save(self, src_ignore):
|
|
self.emit("action-save-domain",
|
|
self.vm.get_connection().get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_destroy(self, src_ignore):
|
|
self.emit("action-destroy-domain",
|
|
self.vm.get_connection().get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_clone(self, src_ignore):
|
|
self.emit("action-clone-domain",
|
|
self.vm.get_connection().get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_migrate(self, src_ignore):
|
|
self.emit("action-migrate-domain",
|
|
self.vm.get_connection().get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_screenshot(self, src_ignore):
|
|
# If someone feels kind they could extend this code to allow
|
|
# user to choose what image format they'd like to save in....
|
|
path = util.browse_local(
|
|
self.topwin,
|
|
_("Save Virtual Machine Screenshot"),
|
|
self.vm.get_connection(),
|
|
_type=("png", "PNG files"),
|
|
dialog_type=gtk.FILE_CHOOSER_ACTION_SAVE,
|
|
browse_reason=self.config.CONFIG_DIR_SCREENSHOT)
|
|
if not path:
|
|
return
|
|
|
|
filename = path
|
|
if not(filename.endswith(".png")):
|
|
filename += ".png"
|
|
image = self.console.viewer.get_pixbuf()
|
|
|
|
# Save along with a little metadata about us & the domain
|
|
image.save(filename, 'png',
|
|
{'tEXt::Hypervisor URI': self.vm.get_connection().get_uri(),
|
|
'tEXt::Domain Name': self.vm.get_name(),
|
|
'tEXt::Domain UUID': self.vm.get_uuid(),
|
|
'tEXt::Generator App': self.config.get_appname(),
|
|
'tEXt::Generator Version': self.config.get_appversion()})
|
|
|
|
msg = gtk.MessageDialog(self.topwin,
|
|
gtk.DIALOG_MODAL,
|
|
gtk.MESSAGE_INFO,
|
|
gtk.BUTTONS_OK,
|
|
(_("The screenshot has been saved to:\n%s") %
|
|
filename))
|
|
msg.set_title(_("Screenshot saved"))
|
|
msg.run()
|
|
msg.destroy()
|
|
|
|
|
|
# ------------------------------
|
|
# Serial Console pieces
|
|
# ------------------------------
|
|
|
|
def control_serial_tab(self, src_ignore, name, target_port):
|
|
is_graphics = (name == "graphics")
|
|
is_serial = not is_graphics
|
|
|
|
if is_graphics:
|
|
self.window.get_widget("details-pages").set_current_page(PAGE_CONSOLE)
|
|
elif is_serial:
|
|
self._show_serial_tab(name, target_port)
|
|
|
|
def show_serial_rcpopup(self, src, event):
|
|
if event.button != 3:
|
|
return
|
|
|
|
self.serial_popup.show_all()
|
|
self.serial_copy.connect("activate", self.serial_copy_text, src)
|
|
self.serial_paste.connect("activate", self.serial_paste_text, src)
|
|
self.serial_close.connect("activate", self.serial_close_tab,
|
|
self.window.get_widget("details-pages").get_current_page())
|
|
|
|
if src.get_has_selection():
|
|
self.serial_copy.set_sensitive(True)
|
|
else:
|
|
self.serial_copy.set_sensitive(False)
|
|
self.serial_popup.popup(None, None, None, 0, event.time)
|
|
|
|
def serial_close_tab(self, src_ignore, pagenum):
|
|
tab_idx = (pagenum - PAGE_DYNAMIC_OFFSET)
|
|
if (tab_idx < 0) or (tab_idx > len(self.serial_tabs) - 1):
|
|
return
|
|
return self._close_serial_tab(self.serial_tabs[tab_idx])
|
|
|
|
def serial_copy_text(self, src_ignore, terminal):
|
|
terminal.copy_clipboard()
|
|
|
|
def serial_paste_text(self, src_ignore, terminal):
|
|
terminal.paste_clipboard()
|
|
|
|
def _show_serial_tab(self, name, target_port):
|
|
if not self.serial_tabs.count(name):
|
|
serial = vmmSerialConsole(self.vm, target_port)
|
|
serial.terminal.connect("button-press-event",
|
|
self.show_serial_rcpopup)
|
|
|
|
title = gtk.Label(name)
|
|
child = serial.box
|
|
child.show_all()
|
|
self.window.get_widget("details-pages").append_page(child, title)
|
|
self.serial_tabs.append(name)
|
|
self.serial_mappings[name] = serial
|
|
|
|
page_idx = self.serial_tabs.index(name) + PAGE_DYNAMIC_OFFSET
|
|
self.window.get_widget("details-pages").set_current_page(page_idx)
|
|
|
|
def _close_serial_tab(self, name):
|
|
if not self.serial_tabs.count(name):
|
|
return
|
|
|
|
page_idx = self.serial_tabs.index(name) + PAGE_DYNAMIC_OFFSET
|
|
self.window.get_widget("details-pages").remove_page(page_idx)
|
|
|
|
self.serial_mappings[name].cleanup()
|
|
del(self.serial_mappings[name])
|
|
self.serial_tabs.remove(name)
|
|
|
|
|
|
############################
|
|
# Details/Hardware getters #
|
|
############################
|
|
|
|
def get_config_boot_devs(self):
|
|
boot_model = self.window.get_widget("config-boot-list").get_model()
|
|
devs = []
|
|
|
|
for row in boot_model:
|
|
if row[BOOT_ACTIVE]:
|
|
devs.append(row[BOOT_DEV_TYPE])
|
|
|
|
return devs
|
|
|
|
def get_config_cpu_model(self):
|
|
cpu_list = self.window.get_widget("cpu-model")
|
|
model = cpu_list.child.get_text()
|
|
|
|
for row in cpu_list.get_model():
|
|
if model == row[0]:
|
|
return model, row[1].vendor
|
|
|
|
return model, None
|
|
|
|
def get_config_cpu_features(self):
|
|
feature_list = self.window.get_widget("cpu-features")
|
|
ret = []
|
|
|
|
for row in feature_list.get_model():
|
|
if row[1]:
|
|
ret.append(row[0])
|
|
|
|
return ret
|
|
|
|
##############################
|
|
# Details/Hardware listeners #
|
|
##############################
|
|
def _spin_get_helper(self, wname):
|
|
widget = self.window.get_widget(wname)
|
|
adj = widget.get_adjustment()
|
|
txt = widget.get_text()
|
|
|
|
try:
|
|
ret = int(txt)
|
|
except:
|
|
ret = adj.value
|
|
return ret
|
|
|
|
def _browse_file(self, callback, is_media=False):
|
|
if is_media:
|
|
reason = self.config.CONFIG_DIR_MEDIA
|
|
else:
|
|
reason = self.config.CONFIG_DIR_IMAGE
|
|
|
|
if self.storage_browser == None:
|
|
self.storage_browser = vmmStorageBrowser(self.conn)
|
|
|
|
self.storage_browser.set_finish_cb(callback)
|
|
self.storage_browser.set_browse_reason(reason)
|
|
self.storage_browser.show(self.conn)
|
|
|
|
def browse_kernel(self, src_ignore):
|
|
def cb(ignore, path):
|
|
self.window.get_widget("boot-kernel").set_text(path)
|
|
self._browse_file(cb)
|
|
def browse_initrd(self, src_ignore):
|
|
def cb(ignore, path):
|
|
self.window.get_widget("boot-kernel-initrd").set_text(path)
|
|
self._browse_file(cb)
|
|
|
|
def config_enable_apply(self, ignore1=None, ignore2=None):
|
|
self.window.get_widget("config-apply").set_sensitive(True)
|
|
|
|
# Overview -> Machine settings
|
|
def config_acpi_changed(self, ignore):
|
|
widget = self.window.get_widget("overview-acpi")
|
|
incon = widget.get_inconsistent()
|
|
widget.set_inconsistent(False)
|
|
if incon:
|
|
widget.set_active(True)
|
|
self.config_enable_apply()
|
|
def config_apic_changed(self, ignore):
|
|
widget = self.window.get_widget("overview-apic")
|
|
incon = widget.get_inconsistent()
|
|
widget.set_inconsistent(False)
|
|
if incon:
|
|
widget.set_active(True)
|
|
self.config_enable_apply()
|
|
|
|
# Overview -> Security
|
|
def security_label_changed(self, label_ignore):
|
|
self.config_enable_apply()
|
|
|
|
def security_type_changed(self, button):
|
|
self.config_enable_apply()
|
|
self.window.get_widget("security-label").set_sensitive(
|
|
not button.get_active())
|
|
|
|
# Memory
|
|
def config_get_maxmem(self):
|
|
return self._spin_get_helper("config-maxmem")
|
|
def config_get_memory(self):
|
|
return self._spin_get_helper("config-memory")
|
|
|
|
def config_maxmem_changed(self, src_ignore):
|
|
self.config_enable_apply()
|
|
|
|
def config_memory_changed(self, src_ignore):
|
|
self.config_enable_apply()
|
|
|
|
maxadj = self.window.get_widget("config-maxmem").get_adjustment()
|
|
|
|
mem = self.config_get_memory()
|
|
if maxadj.value < mem:
|
|
maxadj.value = mem
|
|
maxadj.lower = mem
|
|
|
|
def generate_cpuset(self):
|
|
mem = int(self.vm.get_memory()) / 1024 / 1024
|
|
return virtinst.Guest.generate_cpuset(self.conn.vmm, mem)
|
|
|
|
# VCPUS
|
|
def config_get_vcpus(self):
|
|
return self._spin_get_helper("config-vcpus")
|
|
def config_get_maxvcpus(self):
|
|
return self._spin_get_helper("config-maxvcpus")
|
|
|
|
def config_vcpupin_generate(self, ignore):
|
|
try:
|
|
pinstr = self.generate_cpuset()
|
|
except Exception, e:
|
|
return self.err.val_err(
|
|
_("Error generating CPU configuration: %s") % str(e))
|
|
|
|
self.window.get_widget("config-vcpupin").set_text("")
|
|
self.window.get_widget("config-vcpupin").set_text(pinstr)
|
|
|
|
def config_vcpus_changed(self, ignore):
|
|
self.config_enable_apply()
|
|
|
|
conn = self.vm.get_connection()
|
|
host_active_count = conn.host_active_processor_count()
|
|
cur = self.config_get_vcpus()
|
|
|
|
# Warn about overcommit
|
|
warn = bool(cur > host_active_count)
|
|
self.window.get_widget("config-vcpus-warn-box").set_property(
|
|
"visible", warn)
|
|
|
|
maxadj = self.window.get_widget("config-maxvcpus").get_adjustment()
|
|
maxval = self.config_get_maxvcpus()
|
|
if maxval < cur:
|
|
maxadj.value = cur
|
|
maxadj.lower = cur
|
|
|
|
def config_maxvcpus_changed(self, ignore):
|
|
self.config_enable_apply()
|
|
|
|
def config_cpu_copy_host(self, src_ignore):
|
|
# Update UI with output copied from host
|
|
try:
|
|
CPU = virtinst.CPU(self.vm.get_connection().vmm)
|
|
CPU.copy_host_cpu()
|
|
|
|
self._refresh_cpu_config(CPU)
|
|
self._cpu_copy_host = True
|
|
except Exception, e:
|
|
self.err.show_err(_("Error copying host CPU: %s") % str(e))
|
|
return
|
|
|
|
def config_cpu_topology_enable(self, src):
|
|
do_enable = src.get_active()
|
|
self.window.get_widget("cpu-topology-table").set_sensitive(do_enable)
|
|
self.config_enable_apply()
|
|
|
|
# Boot device / Autostart
|
|
def config_bootdev_selected(self, ignore):
|
|
boot_row = self.get_boot_selection()
|
|
boot_selection = boot_row and boot_row[BOOT_DEV_TYPE]
|
|
boot_devs = self.get_config_boot_devs()
|
|
up_widget = self.window.get_widget("config-boot-moveup")
|
|
down_widget = self.window.get_widget("config-boot-movedown")
|
|
|
|
down_widget.set_sensitive(bool(boot_devs and
|
|
boot_selection and
|
|
boot_selection in boot_devs and
|
|
boot_selection != boot_devs[-1]))
|
|
up_widget.set_sensitive(bool(boot_devs and boot_selection and
|
|
boot_selection in boot_devs and
|
|
boot_selection != boot_devs[0]))
|
|
|
|
def config_boot_toggled(self, ignore, index):
|
|
boot_model = self.window.get_widget("config-boot-list").get_model()
|
|
boot_row = boot_model[index]
|
|
is_active = boot_row[BOOT_ACTIVE]
|
|
|
|
boot_row[BOOT_ACTIVE] = not is_active
|
|
|
|
self.repopulate_boot_list(self.get_config_boot_devs(),
|
|
boot_row[BOOT_DEV_TYPE])
|
|
self.config_enable_apply()
|
|
|
|
def config_boot_move(self, src_ignore, move_up):
|
|
boot_row = self.get_boot_selection()
|
|
if not boot_row:
|
|
return
|
|
|
|
boot_selection = boot_row[BOOT_DEV_TYPE]
|
|
boot_devs = self.get_config_boot_devs()
|
|
boot_idx = boot_devs.index(boot_selection)
|
|
if move_up:
|
|
new_idx = boot_idx - 1
|
|
else:
|
|
new_idx = boot_idx + 1
|
|
|
|
if new_idx < 0 or new_idx >= len(boot_devs):
|
|
# Somehow we got out of bounds
|
|
return
|
|
|
|
swap_dev = boot_devs[new_idx]
|
|
boot_devs[new_idx] = boot_selection
|
|
boot_devs[boot_idx] = swap_dev
|
|
|
|
self.repopulate_boot_list(boot_devs, boot_selection)
|
|
self.config_enable_apply()
|
|
|
|
# CDROM Eject/Connect
|
|
def toggle_storage_media(self, src_ignore):
|
|
disk = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not disk:
|
|
return
|
|
|
|
dev_id_info = disk
|
|
curpath = disk.path
|
|
devtype = disk.device
|
|
|
|
if curpath:
|
|
# Disconnect cdrom
|
|
self.change_storage_media(dev_id_info, None)
|
|
return
|
|
|
|
def change_cdrom_wrapper(src_ignore, dev_id_info, newpath):
|
|
return self.change_storage_media(dev_id_info, newpath)
|
|
|
|
# Launch 'Choose CD' dialog
|
|
if self.media_choosers[devtype] is None:
|
|
ret = vmmChooseCD(dev_id_info,
|
|
self.vm.get_connection(),
|
|
devtype)
|
|
|
|
ret.connect("cdrom-chosen", change_cdrom_wrapper)
|
|
self.media_choosers[devtype] = ret
|
|
|
|
dialog = self.media_choosers[devtype]
|
|
dialog.dev_id_info = dev_id_info
|
|
dialog.show()
|
|
|
|
##################################################
|
|
# Details/Hardware config changes (apply button) #
|
|
##################################################
|
|
|
|
def config_apply(self, ignore):
|
|
pagetype = self.get_hw_selection(HW_LIST_COL_TYPE)
|
|
devobj = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
key = devobj
|
|
ret = False
|
|
|
|
if pagetype is HW_LIST_TYPE_GENERAL:
|
|
ret = self.config_overview_apply()
|
|
elif pagetype is HW_LIST_TYPE_CPU:
|
|
ret = self.config_vcpus_apply()
|
|
elif pagetype is HW_LIST_TYPE_MEMORY:
|
|
ret = self.config_memory_apply()
|
|
elif pagetype is HW_LIST_TYPE_BOOT:
|
|
ret = self.config_boot_options_apply()
|
|
elif pagetype is HW_LIST_TYPE_DISK:
|
|
ret = self.config_disk_apply(key)
|
|
elif pagetype is HW_LIST_TYPE_NIC:
|
|
ret = self.config_network_apply(key)
|
|
elif pagetype is HW_LIST_TYPE_GRAPHICS:
|
|
ret = self.config_graphics_apply(key)
|
|
elif pagetype is HW_LIST_TYPE_SOUND:
|
|
ret = self.config_sound_apply(key)
|
|
elif pagetype is HW_LIST_TYPE_VIDEO:
|
|
ret = self.config_video_apply(key)
|
|
elif pagetype is HW_LIST_TYPE_WATCHDOG:
|
|
ret = self.config_watchdog_apply(key)
|
|
else:
|
|
ret = False
|
|
|
|
if ret is not False:
|
|
self.window.get_widget("config-apply").set_sensitive(False)
|
|
|
|
# Overview section
|
|
def config_overview_apply(self):
|
|
# Overview details
|
|
name = self.window.get_widget("overview-name").get_text()
|
|
|
|
# Machine details
|
|
enable_acpi = self.window.get_widget("overview-acpi").get_active()
|
|
if self.window.get_widget("overview-acpi").get_inconsistent():
|
|
enable_acpi = None
|
|
enable_apic = self.window.get_widget("overview-apic").get_active()
|
|
if self.window.get_widget("overview-apic").get_inconsistent():
|
|
enable_apic = None
|
|
clock_combo = self.window.get_widget("overview-clock-combo")
|
|
if clock_combo.get_property("visible"):
|
|
clock = clock_combo.get_model()[clock_combo.get_active()][0]
|
|
else:
|
|
clock = self.window.get_widget("overview-clock-label").get_text()
|
|
|
|
# Security
|
|
if self.window.get_widget("security-dynamic").get_active():
|
|
setype = "dynamic"
|
|
else:
|
|
setype = "static"
|
|
|
|
selabel = self.window.get_widget("security-label").get_text()
|
|
semodel = None
|
|
if self.window.get_widget("security-type-box").get_property(
|
|
"sensitive"):
|
|
semodel = self.window.get_widget("security-model").get_text()
|
|
|
|
# Description
|
|
desc_widget = self.window.get_widget("overview-description")
|
|
desc = desc_widget.get_buffer().get_property("text") or ""
|
|
|
|
return self._change_config_helper([self.vm.define_name,
|
|
self.vm.define_acpi,
|
|
self.vm.define_apic,
|
|
self.vm.define_clock,
|
|
self.vm.define_seclabel,
|
|
self.vm.define_description],
|
|
[(name,),
|
|
(enable_acpi,),
|
|
(enable_apic,),
|
|
(clock,),
|
|
(semodel, setype, selabel),
|
|
(desc,)])
|
|
|
|
# CPUs
|
|
def config_vcpus_apply(self):
|
|
vcpus = self.config_get_vcpus()
|
|
maxv = self.config_get_maxvcpus()
|
|
cpuset = self.window.get_widget("config-vcpupin").get_text()
|
|
|
|
do_top = self.window.get_widget("cpu-topology-enable").get_active()
|
|
sockets = self.window.get_widget("cpu-sockets").get_value()
|
|
cores = self.window.get_widget("cpu-cores").get_value()
|
|
threads = self.window.get_widget("cpu-threads").get_value()
|
|
model, vendor = self.get_config_cpu_model()
|
|
features = self.get_config_cpu_features()
|
|
|
|
logging.info("Setting vcpus for %s to %s, cpuset is %s" %
|
|
(self.vm.get_name(), str(vcpus), cpuset))
|
|
|
|
if not do_top:
|
|
sockets = None
|
|
cores = None
|
|
threads = None
|
|
|
|
define_funcs = [self.vm.define_vcpus,
|
|
self.vm.define_cpuset,
|
|
self.vm.define_cpu,
|
|
self.vm.define_cpu_topology]
|
|
define_args = [(vcpus, maxv),
|
|
(cpuset,),
|
|
(model, vendor, self._cpu_copy_host, features),
|
|
(sockets, cores, threads)]
|
|
|
|
ret = self._change_config_helper(define_funcs, define_args,
|
|
[self.vm.hotplug_vcpus,
|
|
self.config_vcpu_pin_cpuset],
|
|
[(vcpus,), (cpuset,)])
|
|
|
|
if ret:
|
|
self._cpu_copy_host = False
|
|
|
|
def config_vcpu_pin(self, src_ignore, path, new_text):
|
|
vcpu_list = self.window.get_widget("config-vcpu-list")
|
|
vcpu_model = vcpu_list.get_model()
|
|
row = vcpu_model[path]
|
|
conn = self.vm.get_connection()
|
|
|
|
try:
|
|
vcpu_num = int(row[0])
|
|
pinlist = virtinst.Guest.cpuset_str_to_tuple(conn.vmm, new_text)
|
|
except Exception, e:
|
|
self.err.val_err(_("Error building pin list: %s") % str(e))
|
|
return
|
|
|
|
try:
|
|
self.vm.pin_vcpu(vcpu_num, pinlist)
|
|
except Exception, e:
|
|
self.err.show_err(_("Error pinning vcpus: %s") % str(e))
|
|
return
|
|
|
|
self._refresh_runtime_pinning()
|
|
|
|
def config_vcpu_pin_cpuset(self, cpuset):
|
|
conn = self.vm.get_connection()
|
|
vcpu_list = self.window.get_widget("config-vcpu-list")
|
|
vcpu_model = vcpu_list.get_model()
|
|
|
|
if self.vm.vcpu_pinning() == cpuset:
|
|
return
|
|
|
|
pinlist = virtinst.Guest.cpuset_str_to_tuple(conn.vmm, cpuset)
|
|
for row in vcpu_model:
|
|
vcpu_num = row[0]
|
|
self.vm.pin_vcpu(int(vcpu_num), pinlist)
|
|
|
|
# Memory
|
|
def config_memory_apply(self):
|
|
curmem = None
|
|
maxmem = self.config_get_maxmem()
|
|
if self.window.get_widget("config-memory").get_property("sensitive"):
|
|
curmem = self.config_get_memory()
|
|
|
|
if curmem:
|
|
curmem = int(curmem) * 1024
|
|
if maxmem:
|
|
maxmem = int(maxmem) * 1024
|
|
|
|
return self._change_config_helper(self.vm.define_both_mem,
|
|
(curmem, maxmem),
|
|
self.vm.hotplug_both_mem,
|
|
(curmem, maxmem))
|
|
|
|
# Boot device / Autostart
|
|
def config_boot_options_apply(self):
|
|
auto = self.window.get_widget("config-autostart")
|
|
|
|
if auto.get_property("sensitive"):
|
|
try:
|
|
self.vm.set_autostart(auto.get_active())
|
|
except Exception, e:
|
|
self.err.show_err((_("Error changing autostart value: %s") %
|
|
str(e)))
|
|
return False
|
|
|
|
bootdevs = self.get_config_boot_devs()
|
|
bootmenu = self.window.get_widget("boot-menu").get_active()
|
|
|
|
kernel = self.window.get_widget("boot-kernel").get_text()
|
|
initrd = self.window.get_widget("boot-kernel-initrd").get_text()
|
|
args = self.window.get_widget("boot-kernel-args").get_text()
|
|
|
|
return self._change_config_helper([self.vm.set_boot_device,
|
|
self.vm.set_boot_menu,
|
|
self.vm.set_boot_kernel],
|
|
[(bootdevs,),
|
|
(bootmenu,),
|
|
(kernel, initrd, args)])
|
|
|
|
# CDROM
|
|
def change_storage_media(self, dev_id_info, newpath):
|
|
return self._change_config_helper(self.vm.define_storage_media,
|
|
(dev_id_info, newpath),
|
|
self.vm.hotplug_storage_media,
|
|
(dev_id_info, newpath))
|
|
|
|
# Disk options
|
|
def config_disk_apply(self, dev_id_info):
|
|
do_readonly = self.window.get_widget("disk-readonly").get_active()
|
|
do_shareable = self.window.get_widget("disk-shareable").get_active()
|
|
cache = self.get_combo_label_value("disk-cache")
|
|
fmt = self.window.get_widget("disk-format").child.get_text()
|
|
bus = self.get_combo_label_value("disk-bus")
|
|
|
|
return self._change_config_helper([self.vm.define_disk_readonly,
|
|
self.vm.define_disk_shareable,
|
|
self.vm.define_disk_cache,
|
|
self.vm.define_disk_driver_type,
|
|
self.vm.define_disk_bus],
|
|
[(dev_id_info, do_readonly),
|
|
(dev_id_info, do_shareable),
|
|
(dev_id_info, cache),
|
|
(dev_id_info, fmt),
|
|
(dev_id_info, bus)])
|
|
|
|
# Audio options
|
|
def config_sound_apply(self, dev_id_info):
|
|
model = self.get_combo_label_value("sound-model")
|
|
if model:
|
|
return self._change_config_helper(self.vm.define_sound_model,
|
|
(dev_id_info, model))
|
|
|
|
# Network options
|
|
def config_network_apply(self, dev_id_info):
|
|
net_list = self.window.get_widget("network-source-combo")
|
|
net_bridge = self.window.get_widget("network-bridge")
|
|
nettype, source = uihelpers.get_network_selection(net_list, net_bridge)
|
|
|
|
model = self.get_combo_label_value("network-model")
|
|
|
|
vport_type = self.window.get_widget("vport-type").get_text()
|
|
vport_managerid = self.window.get_widget("vport-managerid").get_text()
|
|
vport_typeid = self.window.get_widget("vport-typeid").get_text()
|
|
vport_idver = self.window.get_widget("vport-typeidversion").get_text()
|
|
vport_instid = self.window.get_widget("vport-instanceid").get_text()
|
|
|
|
return self._change_config_helper([self.vm.define_network_model,
|
|
self.vm.define_virtualport,
|
|
self.vm.define_network_source],
|
|
[(dev_id_info, model),
|
|
(dev_id_info, vport_type,
|
|
vport_managerid,
|
|
vport_typeid,
|
|
vport_idver,
|
|
vport_instid),
|
|
(dev_id_info, nettype, source)])
|
|
|
|
# Graphics options
|
|
def _do_change_spicevmc(self, gdev, newgtype):
|
|
has_multi_spice = (len(filter(
|
|
lambda dev: dev.type == dev.TYPE_SPICE,
|
|
self.vm.get_graphics_devices())) > 1)
|
|
has_spicevmc = bool(filter(
|
|
(lambda dev:
|
|
(dev.dev_type == dev.DEV_CHANNEL and
|
|
dev.char_type == dev.CHAR_SPICEVMC)),
|
|
self.vm.get_char_devices()))
|
|
fromspice = (gdev.type == "spice")
|
|
tospice = (newgtype == "spice")
|
|
|
|
if fromspice and tospice:
|
|
return False
|
|
if not fromspice and not tospice:
|
|
return False
|
|
|
|
if tospice and has_spicevmc:
|
|
return False
|
|
if fromspice and not has_spicevmc:
|
|
return False
|
|
|
|
if fromspice and has_multi_spice:
|
|
# Don't offer to remove if there are other spice displays
|
|
return False
|
|
|
|
msg = (_("You are switching graphics type to %(gtype)s, "
|
|
"would you like to %(action)s Spice agent channels?") %
|
|
{"gtype": newgtype,
|
|
"action": fromspice and "remove" or "add"})
|
|
return self.err.yes_no(msg)
|
|
|
|
def config_graphics_apply(self, dev_id_info):
|
|
gtype = self.get_combo_label_value("gfx-type")
|
|
passwd = self.window.get_widget("gfx-password").get_text() or None
|
|
keymap = self.get_combo_label_value("gfx-keymap")
|
|
|
|
change_spicevmc = self._do_change_spicevmc(dev_id_info, gtype)
|
|
|
|
return self._change_config_helper([self.vm.define_graphics_password,
|
|
self.vm.define_graphics_keymap,
|
|
self.vm.define_graphics_type],
|
|
[(dev_id_info, passwd),
|
|
(dev_id_info, keymap),
|
|
(dev_id_info, gtype,
|
|
change_spicevmc)],
|
|
[self.vm.hotplug_graphics_password],
|
|
[(dev_id_info, passwd)])
|
|
|
|
|
|
# Video options
|
|
def config_video_apply(self, dev_id_info):
|
|
model = self.get_combo_label_value("video-model")
|
|
if model:
|
|
return self._change_config_helper(self.vm.define_video_model,
|
|
(dev_id_info, model))
|
|
|
|
# Watchdog options
|
|
def config_watchdog_apply(self, dev_id_info):
|
|
model = self.get_combo_label_value("watchdog-model")
|
|
action = self.get_combo_label_value("watchdog-action")
|
|
if model or action:
|
|
return self._change_config_helper([self.vm.define_watchdog_model,
|
|
self.vm.define_watchdog_action],
|
|
[(dev_id_info, model),
|
|
(dev_id_info, action)])
|
|
|
|
# Device removal
|
|
def remove_device(self, dev_type, dev_id_info):
|
|
logging.debug("Removing device: %s %s" % (dev_type, dev_id_info))
|
|
do_prompt = self.config.get_confirm_removedev()
|
|
|
|
if do_prompt:
|
|
res = self.err.warn_chkbox(
|
|
text1=(_("Are you sure you want to remove this device?")),
|
|
chktext=_("Don't ask me again."),
|
|
buttons=gtk.BUTTONS_YES_NO)
|
|
|
|
response, skip_prompt = res
|
|
if not response:
|
|
return
|
|
self.config.set_confirm_removedev(not skip_prompt)
|
|
|
|
# Define the change
|
|
try:
|
|
self.vm.remove_device(dev_id_info)
|
|
except Exception, e:
|
|
self.err.show_err(_("Error Removing Device: %s" % str(e)))
|
|
return
|
|
|
|
# Try to hot remove
|
|
detach_err = False
|
|
try:
|
|
if self.vm.is_active():
|
|
self.vm.detach_device(dev_id_info)
|
|
except Exception, e:
|
|
logging.debug("Device could not be hotUNplugged: %s" % str(e))
|
|
detach_err = (str(e), "".join(traceback.format_exc()))
|
|
|
|
if not detach_err:
|
|
return
|
|
|
|
self.err.show_err(
|
|
_("Device could not be removed from the running machine"),
|
|
details=(detach_err[0] + "\n\n" + detach_err[1]),
|
|
text2=_("This change will take effect after the next VM reboot."),
|
|
buttons=gtk.BUTTONS_OK,
|
|
dialog_type=gtk.MESSAGE_INFO)
|
|
|
|
# Generic config change helpers
|
|
def _change_config_helper(self,
|
|
define_funcs, define_funcs_args,
|
|
hotplug_funcs=None, hotplug_funcs_args=None):
|
|
"""
|
|
Requires at least a 'define' function and arglist to be specified
|
|
(a function where we change the inactive guest config).
|
|
|
|
Arguments can be a single arg or a list or appropriate arg type (e.g.
|
|
a list of functions for define_funcs)
|
|
"""
|
|
def listify(val):
|
|
if not val:
|
|
return []
|
|
if type(val) is not list:
|
|
return [val]
|
|
return val
|
|
|
|
define_funcs = listify(define_funcs)
|
|
define_funcs_args = listify(define_funcs_args)
|
|
hotplug_funcs = listify(hotplug_funcs)
|
|
hotplug_funcs_args = listify(hotplug_funcs_args)
|
|
|
|
hotplug_err = []
|
|
active = self.vm.is_active()
|
|
|
|
# Hotplug change
|
|
func = None
|
|
if active and hotplug_funcs:
|
|
for idx in range(len(hotplug_funcs)):
|
|
func = hotplug_funcs[idx]
|
|
args = hotplug_funcs_args[idx]
|
|
try:
|
|
func(*args)
|
|
except Exception, e:
|
|
logging.debug("Hotplug failed: func=%s: %s" % (func,
|
|
str(e)))
|
|
hotplug_err.append((str(e),
|
|
"".join(traceback.format_exc())))
|
|
|
|
# Persistent config change
|
|
try:
|
|
for idx in range(len(define_funcs)):
|
|
func = define_funcs[idx]
|
|
args = define_funcs_args[idx]
|
|
func(*args)
|
|
if define_funcs:
|
|
self.vm.redefine_cached()
|
|
except Exception, e:
|
|
self.err.show_err((_("Error changing VM configuration: %s") %
|
|
str(e)))
|
|
# If we fail, make sure we flush the cache
|
|
self.vm.refresh_xml()
|
|
return False
|
|
|
|
|
|
if (hotplug_err or
|
|
(active and not len(hotplug_funcs) == len(define_funcs))):
|
|
if len(define_funcs) > 1:
|
|
msg = _("Some changes may require a guest reboot "
|
|
"to take effect.")
|
|
else:
|
|
msg = _("These changes will take effect after "
|
|
"the next guest reboot.")
|
|
|
|
dtype = hotplug_err and gtk.MESSAGE_WARNING or gtk.MESSAGE_INFO
|
|
hotplug_msg = ""
|
|
for err1, tb in hotplug_err:
|
|
hotplug_msg += (err1 + "\n\n" + tb + "\n")
|
|
|
|
self.err.show_err(msg,
|
|
details=hotplug_msg,
|
|
buttons=gtk.BUTTONS_OK,
|
|
dialog_type=dtype)
|
|
|
|
return True
|
|
|
|
########################
|
|
# Details page refresh #
|
|
########################
|
|
|
|
def refresh_resources(self, ignore):
|
|
details = self.window.get_widget("details-pages")
|
|
page = details.get_current_page()
|
|
|
|
# If the dialog is visible, we want to make sure the XML is always
|
|
# up to date
|
|
if self.is_visible():
|
|
self.vm.refresh_xml()
|
|
|
|
# Stats page needs to be refreshed every tick
|
|
if (page == PAGE_DETAILS and
|
|
self.get_hw_selection(HW_LIST_COL_TYPE) == HW_LIST_TYPE_STATS):
|
|
self.refresh_stats_page()
|
|
|
|
def page_refresh(self, page):
|
|
if page != PAGE_DETAILS:
|
|
return
|
|
|
|
# This function should only be called when the VM xml actually
|
|
# changes (not everytime it is refreshed). This saves us from blindly
|
|
# parsing the xml every tick
|
|
|
|
# Add / remove new devices
|
|
self.repopulate_hw_list()
|
|
|
|
pagetype = self.get_hw_selection(HW_LIST_COL_TYPE)
|
|
if pagetype is None:
|
|
return
|
|
|
|
if self.window.get_widget("config-apply").get_property("sensitive"):
|
|
# Apply button sensitive means user is making changes, don't
|
|
# erase them
|
|
return
|
|
|
|
self.hw_selected(page=pagetype)
|
|
|
|
def refresh_overview_page(self):
|
|
# Basic details
|
|
self.window.get_widget("overview-name").set_text(self.vm.get_name())
|
|
self.window.get_widget("overview-uuid").set_text(self.vm.get_uuid())
|
|
desc = self.vm.get_description() or ""
|
|
desc_widget = self.window.get_widget("overview-description")
|
|
desc_widget.get_buffer().set_text(desc)
|
|
|
|
# Hypervisor Details
|
|
self.window.get_widget("overview-hv").set_text(self.vm.get_pretty_hv_type())
|
|
arch = self.vm.get_arch() or _("Unknown")
|
|
emu = self.vm.get_emulator() or _("None")
|
|
self.window.get_widget("overview-arch").set_text(arch)
|
|
self.window.get_widget("overview-emulator").set_text(emu)
|
|
|
|
# Machine settings
|
|
acpi = self.vm.get_acpi()
|
|
apic = self.vm.get_apic()
|
|
clock = self.vm.get_clock()
|
|
|
|
# Hack in a way to represent 'default' acpi/apic for customize dialog
|
|
self.window.get_widget("overview-acpi").set_active(bool(acpi))
|
|
self.window.get_widget("overview-acpi").set_inconsistent(
|
|
acpi is None and self.is_customize_dialog)
|
|
self.window.get_widget("overview-apic").set_active(bool(apic))
|
|
self.window.get_widget("overview-apic").set_inconsistent(
|
|
apic is None and self.is_customize_dialog)
|
|
|
|
if not clock:
|
|
clock = _("Same as host")
|
|
self.set_combo_label("overview-clock", clock)
|
|
|
|
# Security details
|
|
semodel, ignore, vmlabel = self.vm.get_seclabel()
|
|
caps = self.vm.get_connection().get_capabilities()
|
|
|
|
if caps.host.secmodel and caps.host.secmodel.model:
|
|
semodel = caps.host.secmodel.model
|
|
|
|
self.window.get_widget("security-model").set_text(semodel or _("None"))
|
|
|
|
if not semodel or semodel == "apparmor":
|
|
self.window.get_widget("security-type-box").hide()
|
|
self.window.get_widget("security-type-label").hide()
|
|
else:
|
|
self.window.get_widget("security-type-box").set_sensitive(bool(semodel))
|
|
|
|
if self.vm.get_seclabel()[1] == "static":
|
|
self.window.get_widget("security-static").set_active(True)
|
|
else:
|
|
self.window.get_widget("security-dynamic").set_active(True)
|
|
|
|
self.window.get_widget("security-label").set_text(vmlabel)
|
|
|
|
def refresh_stats_page(self):
|
|
def _dsk_rx_tx_text(rx, tx, unit):
|
|
return ('<span color="#82003B">%(rx)d %(unit)s read</span>\n'
|
|
'<span color="#295C45">%(tx)d %(unit)s write</span>' %
|
|
{"rx": rx, "tx": tx, "unit": unit})
|
|
def _net_rx_tx_text(rx, tx, unit):
|
|
return ('<span color="#82003B">%(rx)d %(unit)s in</span>\n'
|
|
'<span color="#295C45">%(tx)d %(unit)s out</span>' %
|
|
{"rx": rx, "tx": tx, "unit": unit})
|
|
|
|
cpu_txt = _("Disabled")
|
|
mem_txt = _("Disabled")
|
|
dsk_txt = _("Disabled")
|
|
net_txt = _("Disabled")
|
|
|
|
cpu_txt = "%d %%" % self.vm.cpu_time_percentage()
|
|
|
|
vm_memory = self.vm.stats_memory()
|
|
host_memory = self.vm.get_connection().host_memory_size()
|
|
mem_txt = "%d MB of %d MB" % (int(round(vm_memory / 1024.0)),
|
|
int(round(host_memory / 1024.0)))
|
|
|
|
if self.config.get_stats_enable_disk_poll():
|
|
dsk_txt = _dsk_rx_tx_text(self.vm.disk_read_rate(),
|
|
self.vm.disk_write_rate(), "KB/s")
|
|
|
|
if self.config.get_stats_enable_net_poll():
|
|
net_txt = _net_rx_tx_text(self.vm.network_rx_rate(),
|
|
self.vm.network_tx_rate(), "KB/s")
|
|
|
|
self.window.get_widget("overview-cpu-usage-text").set_text(cpu_txt)
|
|
self.window.get_widget("overview-memory-usage-text").set_text(mem_txt)
|
|
self.window.get_widget("overview-network-traffic-text").set_markup(net_txt)
|
|
self.window.get_widget("overview-disk-usage-text").set_markup(dsk_txt)
|
|
|
|
self.cpu_usage_graph.set_property("data_array",
|
|
self.vm.cpu_time_vector())
|
|
self.memory_usage_graph.set_property("data_array",
|
|
self.vm.stats_memory_vector())
|
|
self.disk_io_graph.set_property("data_array",
|
|
self.vm.disk_io_vector())
|
|
self.network_traffic_graph.set_property("data_array",
|
|
self.vm.network_traffic_vector())
|
|
|
|
def _refresh_cpu_count(self):
|
|
conn = self.vm.get_connection()
|
|
host_active_count = conn.host_active_processor_count()
|
|
maxvcpus = self.vm.vcpu_max_count()
|
|
curvcpus = self.vm.vcpu_count()
|
|
|
|
curadj = self.window.get_widget("config-vcpus").get_adjustment()
|
|
maxadj = self.window.get_widget("config-maxvcpus").get_adjustment()
|
|
curadj.value = int(curvcpus)
|
|
maxadj.value = int(maxvcpus)
|
|
|
|
self.window.get_widget("state-host-cpus").set_text(
|
|
str(host_active_count))
|
|
|
|
# Warn about overcommit
|
|
warn = bool(self.config_get_vcpus() > host_active_count)
|
|
self.window.get_widget("config-vcpus-warn-box").set_property(
|
|
"visible", warn)
|
|
def _refresh_cpu_pinning(self):
|
|
# Populate VCPU pinning
|
|
vcpupin = self.vm.vcpu_pinning()
|
|
self.window.get_widget("config-vcpupin").set_text(vcpupin)
|
|
|
|
def _refresh_runtime_pinning(self):
|
|
conn = self.vm.get_connection()
|
|
host_active_count = conn.host_active_processor_count()
|
|
|
|
vcpu_list = self.window.get_widget("config-vcpu-list")
|
|
vcpu_model = vcpu_list.get_model()
|
|
vcpu_model.clear()
|
|
|
|
reason = ""
|
|
if not self.vm.is_active():
|
|
reason = _("VCPU info only available for running domain.")
|
|
elif not self.vm.getvcpus_supported:
|
|
reason = _("Virtual machine does not support runtime VPCU info.")
|
|
else:
|
|
try:
|
|
vcpu_info, vcpu_pinning = self.vm.vcpu_info()
|
|
except Exception, e:
|
|
reason = _("Error getting VCPU info: %s") % str(e)
|
|
|
|
vcpu_list.set_sensitive(not bool(reason))
|
|
util.tooltip_wrapper(vcpu_list, reason or None)
|
|
if reason:
|
|
return
|
|
|
|
def build_cpuset_str(pin_info):
|
|
pinstr = ""
|
|
for i in range(host_active_count):
|
|
if i < len(pin_info) and pin_info[i]:
|
|
pinstr += (",%s" % str(i))
|
|
|
|
return pinstr.strip(",")
|
|
|
|
for idx in range(len(vcpu_info)):
|
|
vcpu = str(vcpu_info[idx][0])
|
|
vcpucur = str(vcpu_info[idx][3])
|
|
vcpupin = build_cpuset_str(vcpu_pinning[idx])
|
|
|
|
vcpu_model.append([vcpu, vcpucur, vcpupin])
|
|
|
|
def _refresh_cpu_config(self, cpu):
|
|
feature_ui = self.window.get_widget("cpu-features")
|
|
model = cpu.model or ""
|
|
|
|
show_top = bool(cpu.sockets or cpu.cores or cpu.threads)
|
|
sockets = cpu.sockets or 1
|
|
cores = cpu.cores or 1
|
|
threads = cpu.threads or 1
|
|
|
|
self.window.get_widget("cpu-topology-enable").set_active(show_top)
|
|
self.window.get_widget("cpu-model").child.set_text(model)
|
|
self.window.get_widget("cpu-sockets").set_value(sockets)
|
|
self.window.get_widget("cpu-cores").set_value(cores)
|
|
self.window.get_widget("cpu-threads").set_value(threads)
|
|
|
|
def has_feature(name):
|
|
for f in cpu.features:
|
|
if f.name == name:
|
|
return True
|
|
return False
|
|
|
|
for row in feature_ui.get_model():
|
|
row[1] = has_feature(row[0])
|
|
|
|
def refresh_config_cpu(self):
|
|
self._cpu_copy_host = False
|
|
cpu = self.vm.get_cpu_config()
|
|
|
|
self._refresh_cpu_count()
|
|
self._refresh_cpu_pinning()
|
|
self._refresh_runtime_pinning()
|
|
self._refresh_cpu_config(cpu)
|
|
|
|
def refresh_config_memory(self):
|
|
host_mem_widget = self.window.get_widget("state-host-memory")
|
|
host_mem = self.vm.get_connection().host_memory_size() / 1024
|
|
vm_cur_mem = self.vm.get_memory() / 1024.0
|
|
vm_max_mem = self.vm.maximum_memory() / 1024.0
|
|
|
|
host_mem_widget.set_text("%d MB" % (int(round(host_mem))))
|
|
|
|
curmem = self.window.get_widget("config-memory").get_adjustment()
|
|
maxmem = self.window.get_widget("config-maxmem").get_adjustment()
|
|
curmem.value = int(round(vm_cur_mem))
|
|
maxmem.value = int(round(vm_max_mem))
|
|
|
|
if (not
|
|
self.window.get_widget("config-memory").get_property("sensitive")):
|
|
maxmem.lower = curmem.value
|
|
|
|
|
|
def refresh_disk_page(self):
|
|
disk = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not disk:
|
|
return
|
|
|
|
path = disk.path
|
|
devtype = disk.device
|
|
ro = disk.read_only
|
|
share = disk.shareable
|
|
bus = disk.bus
|
|
idx = disk.disk_bus_index
|
|
cache = disk.driver_cache
|
|
driver_type = disk.driver_type or ""
|
|
show_format = (not self.is_customize_dialog or
|
|
disk.path_exists(disk.conn, disk.path))
|
|
|
|
size = _("Unknown")
|
|
if not path:
|
|
size = "-"
|
|
else:
|
|
vol = self.conn.get_vol_by_path(path)
|
|
if vol:
|
|
size = vol.get_pretty_capacity()
|
|
elif not self.conn.is_remote():
|
|
ignore, val = virtinst.VirtualDisk.stat_local_path(path)
|
|
if val != 0:
|
|
size = prettyify_bytes(val)
|
|
|
|
is_cdrom = (devtype == virtinst.VirtualDisk.DEVICE_CDROM)
|
|
is_floppy = (devtype == virtinst.VirtualDisk.DEVICE_FLOPPY)
|
|
|
|
pretty_name = prettyify_disk(devtype, bus, idx)
|
|
|
|
self.window.get_widget("disk-source-path").set_text(path or "-")
|
|
self.window.get_widget("disk-target-type").set_text(pretty_name)
|
|
|
|
self.window.get_widget("disk-readonly").set_active(ro)
|
|
self.window.get_widget("disk-readonly").set_sensitive(not is_cdrom)
|
|
self.window.get_widget("disk-shareable").set_active(share)
|
|
self.window.get_widget("disk-size").set_text(size)
|
|
self.set_combo_label("disk-cache", cache)
|
|
|
|
self.window.get_widget("disk-format").set_sensitive(show_format)
|
|
self.window.get_widget("disk-format").child.set_text(driver_type)
|
|
|
|
no_default = not self.is_customize_dialog
|
|
|
|
self.populate_disk_bus_combo(devtype, no_default)
|
|
self.set_combo_label("disk-bus", bus)
|
|
|
|
button = self.window.get_widget("config-cdrom-connect")
|
|
if is_cdrom or is_floppy:
|
|
if not path:
|
|
# source device not connected
|
|
button.set_label(gtk.STOCK_CONNECT)
|
|
else:
|
|
button.set_label(gtk.STOCK_DISCONNECT)
|
|
button.show()
|
|
else:
|
|
button.hide()
|
|
|
|
def refresh_network_page(self):
|
|
net = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not net:
|
|
return
|
|
|
|
nettype = net.type
|
|
source = net.get_source()
|
|
model = net.model
|
|
|
|
netobj = None
|
|
if nettype == virtinst.VirtualNetworkInterface.TYPE_VIRTUAL:
|
|
name_dict = {}
|
|
for uuid in self.conn.list_net_uuids():
|
|
vnet = self.conn.get_net(uuid)
|
|
name = vnet.get_name()
|
|
name_dict[name] = vnet
|
|
|
|
if source and source in name_dict:
|
|
netobj = name_dict[source]
|
|
|
|
desc = uihelpers.pretty_network_desc(nettype, source, netobj)
|
|
|
|
self.window.get_widget("network-mac-address").set_text(net.macaddr)
|
|
uihelpers.populate_network_list(
|
|
self.window.get_widget("network-source-combo"),
|
|
self.conn)
|
|
self.window.get_widget("network-source-combo").set_active(-1)
|
|
|
|
self.window.get_widget("network-bridge").set_text("")
|
|
def compare_network(model, info):
|
|
for idx in range(len(model)):
|
|
row = model[idx]
|
|
if row[0] == info[0] and row[1] == info[1]:
|
|
return True, idx
|
|
|
|
if info[0] == virtinst.VirtualNetworkInterface.TYPE_BRIDGE:
|
|
idx = (len(model) - 1)
|
|
self.window.get_widget("network-bridge").set_text(str(info[1]))
|
|
return True, idx
|
|
|
|
return False, 0
|
|
|
|
self.set_combo_label("network-source",
|
|
(nettype, source), label=desc,
|
|
comparefunc=compare_network)
|
|
|
|
# Virtualport config
|
|
show_vport = (nettype == "direct")
|
|
vport = net.virtualport
|
|
self.window.get_widget("vport-expander").set_property("visible",
|
|
show_vport)
|
|
self.window.get_widget("vport-type").set_text(vport.type or "")
|
|
self.window.get_widget("vport-managerid").set_text(
|
|
vport.managerid or "")
|
|
self.window.get_widget("vport-typeid").set_text(
|
|
vport.typeid or "")
|
|
self.window.get_widget("vport-typeidversion").set_text(
|
|
vport.typeidversion or "")
|
|
self.window.get_widget("vport-instanceid").set_text(
|
|
vport.instanceid or "")
|
|
|
|
uihelpers.populate_netmodel_combo(self.vm,
|
|
self.window.get_widget("network-model-combo"))
|
|
self.set_combo_label("network-model", model)
|
|
|
|
def refresh_input_page(self):
|
|
inp = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not inp:
|
|
return
|
|
|
|
ident = "%s:%s" % (inp.type, inp.bus)
|
|
if ident == "tablet:usb":
|
|
dev = _("EvTouch USB Graphics Tablet")
|
|
elif ident == "mouse:usb":
|
|
dev = _("Generic USB Mouse")
|
|
elif ident == "mouse:xen":
|
|
dev = _("Xen Mouse")
|
|
elif ident == "mouse:ps2":
|
|
dev = _("PS/2 Mouse")
|
|
else:
|
|
dev = inp.bus + " " + inp.type
|
|
|
|
if inp.type == "tablet":
|
|
mode = _("Absolute Movement")
|
|
else:
|
|
mode = _("Relative Movement")
|
|
|
|
self.window.get_widget("input-dev-type").set_text(dev)
|
|
self.window.get_widget("input-dev-mode").set_text(mode)
|
|
|
|
# Can't remove primary Xen or PS/2 mice
|
|
if inp.type == "mouse" and inp.bus in ("xen", "ps2"):
|
|
self.window.get_widget("config-remove").set_sensitive(False)
|
|
else:
|
|
self.window.get_widget("config-remove").set_sensitive(True)
|
|
|
|
def refresh_graphics_page(self):
|
|
gfx = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not gfx:
|
|
return
|
|
|
|
title = self.window.get_widget("graphics-title")
|
|
table = self.window.get_widget("graphics-table")
|
|
table.foreach(lambda w, ignore: w.hide(), ())
|
|
|
|
def set_title(text):
|
|
title.set_markup("<b>%s</b>" % text)
|
|
|
|
def show_row(widget_name, suffix=""):
|
|
base = "gfx-%s" % widget_name
|
|
self.window.get_widget(base + "-title").show()
|
|
self.window.get_widget(base + suffix).show()
|
|
|
|
def show_text(widget_name, text):
|
|
show_row(widget_name)
|
|
self.window.get_widget("gfx-" + widget_name).set_text(text)
|
|
|
|
def port_to_string(port):
|
|
if port is None:
|
|
return "-"
|
|
return (port == -1 and _("Automatically allocated") or str(port))
|
|
|
|
gtype = gfx.type
|
|
is_vnc = (gtype == "vnc")
|
|
is_sdl = (gtype == "sdl")
|
|
is_spice = (gtype == "spice")
|
|
is_other = not (True in [is_vnc, is_sdl, is_spice])
|
|
|
|
set_title(_("%(graphicstype)s Server") %
|
|
{"graphicstype" : gfx.pretty_type_simple(gtype)})
|
|
|
|
settype = ""
|
|
if is_vnc or is_spice:
|
|
port = port_to_string(gfx.port)
|
|
address = (gfx.listen or "127.0.0.1")
|
|
keymap = (gfx.keymap or None)
|
|
passwd = gfx.passwd or ""
|
|
|
|
show_text("password", passwd)
|
|
show_text("port", port)
|
|
show_text("address", address)
|
|
|
|
show_row("keymap", "-box")
|
|
self.set_combo_label("gfx-keymap", keymap)
|
|
settype = gtype
|
|
|
|
if is_spice:
|
|
tlsport = port_to_string(gfx.tlsPort)
|
|
show_text("tlsport", tlsport)
|
|
|
|
if is_sdl:
|
|
set_title(_("Local SDL Window"))
|
|
|
|
display = gfx.display or _("Unknown")
|
|
xauth = gfx.xauth or _("Unknown")
|
|
|
|
show_text("display", display)
|
|
show_text("xauth", xauth)
|
|
|
|
if is_other:
|
|
settype = gfx.pretty_type_simple(gtype)
|
|
|
|
if settype:
|
|
show_row("type", "-box")
|
|
self.set_combo_label("gfx-type", gtype, label=settype)
|
|
|
|
def refresh_sound_page(self):
|
|
sound = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not sound:
|
|
return
|
|
|
|
self.set_combo_label("sound-model", sound.model)
|
|
|
|
def refresh_char_page(self):
|
|
chardev = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not chardev:
|
|
return
|
|
|
|
show_target_type = not (chardev.dev_type in
|
|
[chardev.DEV_SERIAL, chardev.DEV_PARALLEL])
|
|
|
|
def show_ui(param, val=None):
|
|
widgetname = "char-" + param.replace("_", "-")
|
|
labelname = widgetname + "-label"
|
|
doshow = chardev.supports_property(param)
|
|
|
|
# Exception: don't show target type for serial/parallel
|
|
if (param == "target_type" and not show_target_type):
|
|
doshow = False
|
|
|
|
if not val and doshow:
|
|
val = getattr(chardev, param)
|
|
|
|
self.window.get_widget(widgetname).set_property("visible", doshow)
|
|
self.window.get_widget(labelname).set_property("visible", doshow)
|
|
self.window.get_widget(widgetname).set_text(val or "-")
|
|
|
|
def build_host_str(base):
|
|
if (not chardev.supports_property(base + "_host") or
|
|
not chardev.supports_property(base + "_port")):
|
|
return ""
|
|
|
|
host = getattr(chardev, base + "_host") or ""
|
|
port = getattr(chardev, base + "_port") or ""
|
|
|
|
ret = str(host)
|
|
if port:
|
|
ret += ":%s" % str(port)
|
|
return ret
|
|
|
|
char_type = chardev.virtual_device_type.capitalize()
|
|
target_port = chardev.target_port
|
|
dev_type = chardev.char_type or "pty"
|
|
primary = hasattr(chardev, "virtmanager_console_dup")
|
|
|
|
typelabel = ""
|
|
if char_type == "serial":
|
|
typelabel = _("Serial Device")
|
|
elif char_type == "parallel":
|
|
typelabel = _("Parallel Device")
|
|
elif char_type == "console":
|
|
typelabel = _("Console Device")
|
|
elif char_type == "channel":
|
|
typelabel = _("Channel Device")
|
|
else:
|
|
typelabel = _("%s Device") % char_type.capitalize()
|
|
|
|
if target_port is not None and not show_target_type:
|
|
typelabel += " %s" % (int(target_port) + 1)
|
|
if primary:
|
|
typelabel += " (%s)" % _("Primary Console")
|
|
typelabel = "<b>%s</b>" % typelabel
|
|
|
|
self.window.get_widget("char-type").set_markup(typelabel)
|
|
self.window.get_widget("char-dev-type").set_text(dev_type)
|
|
|
|
# Device type specific properties, only show if apply to the cur dev
|
|
show_ui("source_host", build_host_str("source"))
|
|
show_ui("bind_host", build_host_str("bind"))
|
|
show_ui("source_path")
|
|
show_ui("target_type")
|
|
show_ui("target_name")
|
|
|
|
def refresh_hostdev_page(self):
|
|
hostdev = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not hostdev:
|
|
return
|
|
|
|
devtype = hostdev.type
|
|
pretty_name = None
|
|
nodedev = lookup_nodedev(self.vm.get_connection(), hostdev)
|
|
if nodedev:
|
|
pretty_name = nodedev.pretty_name()
|
|
|
|
if not pretty_name:
|
|
pretty_name = build_hostdev_label(hostdev)[0] or "-"
|
|
|
|
devlabel = "<b>Physical %s Device</b>" % devtype.upper()
|
|
self.window.get_widget("hostdev-title").set_markup(devlabel)
|
|
self.window.get_widget("hostdev-source").set_text(pretty_name)
|
|
|
|
def refresh_video_page(self):
|
|
vid = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not vid:
|
|
return
|
|
|
|
model = vid.model_type
|
|
ram = vid.vram
|
|
heads = vid.heads
|
|
try:
|
|
ramlabel = ram and "%d MB" % (int(ram) / 1024) or "-"
|
|
except:
|
|
ramlabel = "-"
|
|
|
|
self.window.get_widget("video-ram").set_text(ramlabel)
|
|
self.window.get_widget("video-heads").set_text(heads and heads or "-")
|
|
|
|
self.set_combo_label("video-model", model)
|
|
|
|
def refresh_watchdog_page(self):
|
|
watch = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not watch:
|
|
return
|
|
|
|
model = watch.model
|
|
action = watch.action
|
|
|
|
self.set_combo_label("watchdog-model", model)
|
|
self.set_combo_label("watchdog-action", action)
|
|
|
|
def refresh_controller_page(self):
|
|
dev = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not dev:
|
|
return
|
|
|
|
type_label = virtinst.VirtualController.pretty_type(dev.type)
|
|
self.window.get_widget("controller-type").set_text(type_label)
|
|
|
|
def refresh_boot_page(self):
|
|
# Refresh autostart
|
|
try:
|
|
# Older libvirt versions return None if not supported
|
|
autoval = self.vm.get_autostart()
|
|
except libvirt.libvirtError:
|
|
autoval = None
|
|
|
|
autostart_chk = self.window.get_widget("config-autostart")
|
|
enable_autostart = (autoval is not None)
|
|
autostart_chk.set_sensitive(enable_autostart)
|
|
autostart_chk.set_active(enable_autostart and autoval or False)
|
|
|
|
menu = self.vm.get_boot_menu() or False
|
|
self.window.get_widget("boot-menu").set_active(menu)
|
|
|
|
kernel, initrd, args = self.vm.get_boot_kernel_info()
|
|
expand = bool(kernel or initrd or args)
|
|
self.window.get_widget("boot-kernel").set_text(kernel or "")
|
|
self.window.get_widget("boot-kernel-initrd").set_text(initrd or "")
|
|
self.window.get_widget("boot-kernel-args").set_text(args or "")
|
|
if expand:
|
|
self.window.get_widget("boot-kernel-expander").set_expanded(True)
|
|
|
|
# Refresh Boot Device list
|
|
self.repopulate_boot_list()
|
|
|
|
|
|
############################
|
|
# Hardware list population #
|
|
############################
|
|
|
|
def populate_disk_bus_combo(self, devtype, no_default):
|
|
buslist = self.window.get_widget("disk-bus-combo")
|
|
busmodel = buslist.get_model()
|
|
busmodel.clear()
|
|
|
|
buses = []
|
|
if devtype == virtinst.VirtualDisk.DEVICE_FLOPPY:
|
|
buses.append(["fdc", "Floppy"])
|
|
elif devtype == virtinst.VirtualDisk.DEVICE_CDROM:
|
|
buses.append(["ide", "IDE"])
|
|
buses.append(["scsi", "SCSI"])
|
|
else:
|
|
if self.vm.is_hvm():
|
|
buses.append(["ide", "IDE"])
|
|
buses.append(["scsi", "SCSI"])
|
|
buses.append(["usb", "USB"])
|
|
if self.vm.get_hv_type() == "kvm":
|
|
buses.append(["virtio", "Virtio"])
|
|
if self.vm.get_connection().is_xen():
|
|
buses.append(["xen", "Xen"])
|
|
|
|
for row in buses:
|
|
busmodel.append(row)
|
|
if not no_default:
|
|
busmodel.append([None, "default"])
|
|
|
|
def populate_hw_list(self):
|
|
hw_list_model = self.window.get_widget("hw-list").get_model()
|
|
hw_list_model.clear()
|
|
|
|
def add_hw_list_option(title, page_id, data, icon_name):
|
|
hw_list_model.append([title, icon_name,
|
|
gtk.ICON_SIZE_LARGE_TOOLBAR,
|
|
page_id, data])
|
|
|
|
add_hw_list_option("Overview", HW_LIST_TYPE_GENERAL, [], "computer")
|
|
if not self.is_customize_dialog:
|
|
add_hw_list_option("Performance", HW_LIST_TYPE_STATS, [],
|
|
"utilities-system-monitor")
|
|
add_hw_list_option("Processor", HW_LIST_TYPE_CPU, [], "device_cpu")
|
|
add_hw_list_option("Memory", HW_LIST_TYPE_MEMORY, [], "device_mem")
|
|
add_hw_list_option("Boot Options", HW_LIST_TYPE_BOOT, [], "system-run")
|
|
|
|
self.repopulate_hw_list()
|
|
|
|
def repopulate_hw_list(self):
|
|
hw_list = self.window.get_widget("hw-list")
|
|
hw_list_model = hw_list.get_model()
|
|
|
|
currentDevices = []
|
|
|
|
def dev_cmp(origdev, newdev):
|
|
if not origdev:
|
|
return False
|
|
|
|
if origdev == newdev:
|
|
return True
|
|
|
|
if not origdev.get_xml_node_path():
|
|
return False
|
|
|
|
return origdev.get_xml_node_path() == newdev.get_xml_node_path()
|
|
|
|
def add_hw_list_option(idx, name, page_id, info, icon_name):
|
|
hw_list_model.insert(idx, [name, icon_name,
|
|
gtk.ICON_SIZE_LARGE_TOOLBAR,
|
|
page_id, info])
|
|
|
|
def update_hwlist(hwtype, info, name, icon_name):
|
|
"""
|
|
See if passed hw is already in list, and if so, update info.
|
|
If not in list, add it!
|
|
"""
|
|
currentDevices.append(info)
|
|
|
|
insertAt = 0
|
|
for row in hw_list_model:
|
|
rowdev = row[HW_LIST_COL_DEVICE]
|
|
if dev_cmp(rowdev, info):
|
|
# Update existing HW info
|
|
row[HW_LIST_COL_DEVICE] = info
|
|
row[HW_LIST_COL_LABEL] = name
|
|
row[HW_LIST_COL_ICON_NAME] = icon_name
|
|
return
|
|
|
|
if row[HW_LIST_COL_TYPE] <= hwtype:
|
|
insertAt += 1
|
|
|
|
# Add the new HW row
|
|
add_hw_list_option(insertAt, name, hwtype, info, icon_name)
|
|
|
|
# Populate list of disks
|
|
for disk in self.vm.get_disk_devices():
|
|
devtype = disk.device
|
|
bus = disk.bus
|
|
idx = disk.disk_bus_index
|
|
|
|
icon = "drive-harddisk"
|
|
if devtype == "cdrom":
|
|
icon = "media-optical"
|
|
elif devtype == "floppy":
|
|
icon = "media-floppy"
|
|
|
|
label = prettyify_disk(devtype, bus, idx)
|
|
|
|
update_hwlist(HW_LIST_TYPE_DISK, disk, label, icon)
|
|
|
|
# Populate list of NICs
|
|
for net in self.vm.get_network_devices():
|
|
mac = net.macaddr
|
|
|
|
update_hwlist(HW_LIST_TYPE_NIC, net,
|
|
"NIC %s" % mac[-9:], "network-idle")
|
|
|
|
# Populate list of input devices
|
|
for inp in self.vm.get_input_devices():
|
|
inptype = inp.type
|
|
|
|
icon = "input-mouse"
|
|
if inptype == "tablet":
|
|
label = _("Tablet")
|
|
icon = "input-tablet"
|
|
elif inptype == "mouse":
|
|
label = _("Mouse")
|
|
else:
|
|
label = _("Input")
|
|
|
|
update_hwlist(HW_LIST_TYPE_INPUT, inp, label, icon)
|
|
|
|
# Populate list of graphics devices
|
|
for gfx in self.vm.get_graphics_devices():
|
|
update_hwlist(HW_LIST_TYPE_GRAPHICS, gfx,
|
|
_("Display %s") % gfx.pretty_type_simple(gfx.type),
|
|
"video-display")
|
|
|
|
# Populate list of sound devices
|
|
for sound in self.vm.get_sound_devices():
|
|
update_hwlist(HW_LIST_TYPE_SOUND, sound,
|
|
_("Sound: %s" % sound.model), "audio-card")
|
|
|
|
# Populate list of char devices
|
|
for chardev in self.vm.get_char_devices():
|
|
devtype = chardev.virtual_device_type
|
|
port = chardev.target_port
|
|
|
|
label = devtype.capitalize()
|
|
if devtype not in ["console", "channel"]:
|
|
# Don't show port for console
|
|
label += " %s" % (int(port) + 1)
|
|
|
|
update_hwlist(HW_LIST_TYPE_CHAR, chardev, label,
|
|
"device_serial")
|
|
|
|
# Populate host devices
|
|
for hostdev in self.vm.get_hostdev_devices():
|
|
devtype = hostdev.type
|
|
label = build_hostdev_label(hostdev)[1]
|
|
|
|
if devtype == "usb":
|
|
icon = "device_usb"
|
|
else:
|
|
icon = "device_pci"
|
|
update_hwlist(HW_LIST_TYPE_HOSTDEV, hostdev, label, icon)
|
|
|
|
# Populate video devices
|
|
for vid in self.vm.get_video_devices():
|
|
update_hwlist(HW_LIST_TYPE_VIDEO, vid, _("Video"), "video-display")
|
|
|
|
for watch in self.vm.get_watchdog_devices():
|
|
update_hwlist(HW_LIST_TYPE_WATCHDOG, watch, _("Watchdog"),
|
|
"device_pci")
|
|
|
|
for cont in self.vm.get_controller_devices():
|
|
pretty_type = virtinst.VirtualController.pretty_type(cont.type)
|
|
update_hwlist(HW_LIST_TYPE_CONTROLLER, cont,
|
|
_("Controller %s") % pretty_type,
|
|
"device_pci")
|
|
|
|
devs = range(len(hw_list_model))
|
|
devs.reverse()
|
|
for i in devs:
|
|
_iter = hw_list_model.iter_nth_child(None, i)
|
|
olddev = hw_list_model[i][HW_LIST_COL_DEVICE]
|
|
|
|
# Existing device, don't remove it
|
|
if not olddev or olddev in currentDevices:
|
|
continue
|
|
|
|
hw_list_model.remove(_iter)
|
|
|
|
def repopulate_boot_list(self, bootdevs=None, dev_select=None):
|
|
boot_list = self.window.get_widget("config-boot-list")
|
|
boot_model = boot_list.get_model()
|
|
old_order = map(lambda x: x[BOOT_DEV_TYPE], boot_model)
|
|
boot_model.clear()
|
|
|
|
if bootdevs == None:
|
|
bootdevs = self.vm.get_boot_device()
|
|
|
|
boot_rows = {
|
|
"hd" : ["hd", "Hard Disk", "drive-harddisk", False],
|
|
"cdrom" : ["cdrom", "CDROM", "media-optical", False],
|
|
"network" : ["network", "Network (PXE)", "network-idle", False],
|
|
"fd" : ["fd", "Floppy", "media-floppy", False],
|
|
}
|
|
|
|
for dev in bootdevs:
|
|
foundrow = None
|
|
|
|
for key, row in boot_rows.items():
|
|
if key == dev:
|
|
foundrow = row
|
|
del(boot_rows[key])
|
|
break
|
|
|
|
if not foundrow:
|
|
# Some boot device listed that we don't know about.
|
|
foundrow = [dev, "Boot type '%s'" % dev,
|
|
"drive-harddisk", True]
|
|
|
|
foundrow[BOOT_ACTIVE] = True
|
|
boot_model.append(foundrow)
|
|
|
|
# Append all remaining boot_rows that aren't enabled
|
|
for dev in old_order:
|
|
if dev in boot_rows:
|
|
boot_model.append(boot_rows[dev])
|
|
del(boot_rows[dev])
|
|
|
|
for row in boot_rows.values():
|
|
boot_model.append(row)
|
|
|
|
boot_list.set_model(boot_model)
|
|
selection = boot_list.get_selection()
|
|
|
|
if dev_select:
|
|
idx = 0
|
|
for row in boot_model:
|
|
if row[BOOT_DEV_TYPE] == dev_select:
|
|
break
|
|
idx += 1
|
|
|
|
boot_list.get_selection().select_path(str(idx))
|
|
|
|
elif not selection.get_selected()[1]:
|
|
# Set a default selection
|
|
selection.select_path("0")
|
|
|
|
|
|
vmmGObjectUI.type_register(vmmDetails)
|