3766 lines
134 KiB
Python
3766 lines
134 KiB
Python
#
|
|
# Copyright (C) 2006-2008, 2013 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 logging
|
|
import traceback
|
|
|
|
# pylint: disable=E0611
|
|
from gi.repository import GObject
|
|
from gi.repository import Gtk
|
|
from gi.repository import Gdk
|
|
# pylint: enable=E0611
|
|
|
|
import libvirt
|
|
|
|
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
|
|
|
|
|
|
# Parameters that can be editted in the details window
|
|
EDIT_TOTAL = 38
|
|
(EDIT_NAME,
|
|
EDIT_ACPI,
|
|
EDIT_APIC,
|
|
EDIT_CLOCK,
|
|
EDIT_MACHTYPE,
|
|
EDIT_SECURITY,
|
|
EDIT_DESC,
|
|
|
|
EDIT_VCPUS,
|
|
EDIT_CPUSET,
|
|
EDIT_CPU,
|
|
EDIT_TOPOLOGY,
|
|
|
|
EDIT_MEM,
|
|
|
|
EDIT_AUTOSTART,
|
|
EDIT_BOOTORDER,
|
|
EDIT_BOOTMENU,
|
|
EDIT_KERNEL,
|
|
EDIT_INIT,
|
|
|
|
EDIT_DISK_RO,
|
|
EDIT_DISK_SHARE,
|
|
EDIT_DISK_CACHE,
|
|
EDIT_DISK_IO,
|
|
EDIT_DISK_BUS,
|
|
EDIT_DISK_SERIAL,
|
|
EDIT_DISK_FORMAT,
|
|
EDIT_DISK_IOTUNE,
|
|
|
|
EDIT_SOUND_MODEL,
|
|
|
|
EDIT_SMARTCARD_MODE,
|
|
|
|
EDIT_NET_MODEL,
|
|
EDIT_NET_VPORT,
|
|
EDIT_NET_SOURCE,
|
|
|
|
EDIT_GFX_PASSWD,
|
|
EDIT_GFX_TYPE,
|
|
EDIT_GFX_KEYMAP,
|
|
|
|
EDIT_VIDEO_MODEL,
|
|
|
|
EDIT_WATCHDOG_MODEL,
|
|
EDIT_WATCHDOG_ACTION,
|
|
|
|
EDIT_CONTROLLER_MODEL,
|
|
|
|
EDIT_TPM_TYPE,
|
|
) = range(EDIT_TOTAL)
|
|
|
|
|
|
# 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
|
|
HW_LIST_TYPE_FILESYSTEM = 15
|
|
HW_LIST_TYPE_SMARTCARD = 16
|
|
HW_LIST_TYPE_REDIRDEV = 17
|
|
HW_LIST_TYPE_TPM = 18
|
|
|
|
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,
|
|
HW_LIST_TYPE_FILESYSTEM, HW_LIST_TYPE_SMARTCARD,
|
|
HW_LIST_TYPE_REDIRDEV, HW_LIST_TYPE_TPM]
|
|
|
|
# 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", "sata", "scsi", "usb"]:
|
|
return bus.upper()
|
|
|
|
if bus in ["xen"]:
|
|
return bus.capitalize()
|
|
|
|
if bus == "virtio":
|
|
return "VirtIO"
|
|
|
|
if bus == "spapr-vscsi":
|
|
return "vSCSI"
|
|
|
|
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_redir_label(redirdev):
|
|
# String shown in the devices details section
|
|
addrlabel = ""
|
|
# String shown in the VMs hardware list
|
|
hwlabel = ""
|
|
|
|
if redirdev.type == 'spicevmc':
|
|
addrlabel = None
|
|
elif redirdev.type == 'tcp':
|
|
addrlabel += _("%s:%s") % (redirdev.host, redirdev.service)
|
|
else:
|
|
raise RuntimeError("unhandled redirection kind: %s" % redirdev.type)
|
|
|
|
hwlabel = _("Redirected %s") % redirdev.bus.upper()
|
|
|
|
return addrlabel, hwlabel
|
|
|
|
|
|
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
|
|
found_dev = None
|
|
|
|
vendor_id = product_id = bus = device = \
|
|
domain = slot = func = None
|
|
|
|
# For USB we want a device, not a bus
|
|
if devtype == 'usb':
|
|
devtype = 'usb_device'
|
|
vendor_id = hostdev.vendor or -1
|
|
product_id = hostdev.product or -1
|
|
bus = intify(hostdev.bus)
|
|
device = intify(hostdev.device)
|
|
|
|
elif devtype == 'pci':
|
|
domain = intify(hostdev.domain, True)
|
|
bus = intify(hostdev.bus, True)
|
|
slot = intify(hostdev.slot, True)
|
|
func = intify(hostdev.function, True)
|
|
|
|
devs = vmmconn.get_nodedevs(devtype, None)
|
|
for dev in devs:
|
|
# Try to match with product_id|vendor_id|bus|device
|
|
if (attrVal(dev, "product_id") == product_id and
|
|
attrVal(dev, "vendor_id") == vendor_id and
|
|
attrVal(dev, "bus") == bus and
|
|
attrVal(dev, "device") == device):
|
|
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-save-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
|
|
"action-destroy-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
|
|
"action-suspend-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
|
|
"action-resume-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
|
|
"action-run-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
|
|
"action-shutdown-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
|
|
"action-reset-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
|
|
"action-reboot-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
|
|
"action-exit-app": (GObject.SignalFlags.RUN_FIRST, None, []),
|
|
"action-view-manager": (GObject.SignalFlags.RUN_FIRST, None, []),
|
|
"action-migrate-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
|
|
"action-delete-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
|
|
"action-clone-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
|
|
"details-closed": (GObject.SignalFlags.RUN_FIRST, None, []),
|
|
"details-opened": (GObject.SignalFlags.RUN_FIRST, None, []),
|
|
"customize-finished": (GObject.SignalFlags.RUN_FIRST, None, []),
|
|
}
|
|
|
|
def __init__(self, vm, parent=None):
|
|
vmmGObjectUI.__init__(self, "vmm-details.ui", "vmm-details")
|
|
self.vm = vm
|
|
self.conn = self.vm.conn
|
|
|
|
self.is_customize_dialog = False
|
|
if parent:
|
|
# Details window is being abused as a 'configure before install'
|
|
# dialog, set things as appropriate
|
|
self.is_customize_dialog = True
|
|
self.topwin.set_type_hint(Gdk.WindowTypeHint.DIALOG)
|
|
self.topwin.set_transient_for(parent)
|
|
|
|
self.widget("toolbar-box").show()
|
|
self.widget("customize-toolbar").show()
|
|
self.widget("details-toolbar").hide()
|
|
self.widget("details-menubar").hide()
|
|
pages = self.widget("details-pages")
|
|
pages.set_current_page(PAGE_DETAILS)
|
|
|
|
|
|
self.active_edits = []
|
|
|
|
self.serial_tabs = []
|
|
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.builder, self.topwin)
|
|
|
|
# Set default window size
|
|
w, h = self.vm.get_details_window_size()
|
|
self.topwin.set_default_size(w or 800, h or 600)
|
|
|
|
self.oldhwkey = None
|
|
self.addhwmenu = None
|
|
self.keycombo_menu = None
|
|
self.init_menus()
|
|
self.init_details()
|
|
|
|
self.cpu_usage_graph = None
|
|
self.memory_usage_graph = None
|
|
self.disk_io_graph = None
|
|
self.network_traffic_graph = None
|
|
self.init_graphs()
|
|
|
|
self.builder.connect_signals({
|
|
"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.customize_finish,
|
|
"on_details_cancel_customize_clicked": self.close,
|
|
|
|
"on_details_menu_virtual_manager_activate": self.control_vm_menu,
|
|
"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_reset_activate": self.control_vm_reset,
|
|
"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_delete_activate": self.control_vm_delete,
|
|
"on_details_menu_screenshot_activate": self.control_vm_screenshot,
|
|
"on_details_menu_usb_redirection": self.control_vm_usb_redirection,
|
|
"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": lambda *x: self.enable_apply(x, EDIT_NAME),
|
|
"on_overview_acpi_changed": self.config_acpi_changed,
|
|
"on_overview_apic_changed": self.config_apic_changed,
|
|
"on_overview_clock_changed": lambda *x: self.enable_apply(x, EDIT_CLOCK),
|
|
"on_machine_type_changed": lambda *x: self.enable_apply(x, EDIT_MACHTYPE),
|
|
"on_security_label_changed": lambda *x: self.enable_apply(x, EDIT_SECURITY),
|
|
"on_security_relabel_changed": lambda *x: self.enable_apply(x, EDIT_SECURITY),
|
|
"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": lambda *x: self.enable_apply(x, EDIT_CPUSET),
|
|
"on_config_vcpupin_generate_clicked": self.config_vcpupin_generate,
|
|
"on_cpu_model_changed": lambda *x: self.enable_apply(x, EDIT_CPU),
|
|
"on_cpu_cores_changed": lambda *x: self.enable_apply(x, EDIT_TOPOLOGY),
|
|
"on_cpu_sockets_changed": lambda *x: self.enable_apply(x, EDIT_TOPOLOGY),
|
|
"on_cpu_threads_changed": lambda *x: self.enable_apply(x, EDIT_TOPOLOGY),
|
|
"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" : lambda *x: self.config_boot_move(x, True),
|
|
"on_config_boot_movedown_clicked" : lambda *x: self.config_boot_move(x, False),
|
|
"on_config_autostart_changed": lambda *x: self.enable_apply(x, x, EDIT_AUTOSTART),
|
|
"on_boot_menu_changed": lambda *x: self.enable_apply(x, EDIT_BOOTMENU),
|
|
"on_boot_kernel_changed": lambda *x: self.enable_apply(x, EDIT_KERNEL),
|
|
"on_boot_kernel_initrd_changed": lambda *x: self.enable_apply(x, EDIT_KERNEL),
|
|
"on_boot_kernel_args_changed": lambda *x: self.enable_apply(x, EDIT_KERNEL),
|
|
"on_boot_kernel_browse_clicked": self.browse_kernel,
|
|
"on_boot_kernel_initrd_browse_clicked": self.browse_initrd,
|
|
"on_boot_init_path_changed": lambda *x: self.enable_apply(x, EDIT_INIT),
|
|
|
|
"on_disk_readonly_changed": lambda *x: self.enable_apply(x, EDIT_DISK_RO),
|
|
"on_disk_shareable_changed": lambda *x: self.enable_apply(x, EDIT_DISK_SHARE),
|
|
"on_disk_cache_combo_changed": lambda *x: self.enable_apply(x, EDIT_DISK_CACHE),
|
|
"on_disk_io_combo_changed": lambda *x: self.enable_apply(x, EDIT_DISK_IO),
|
|
"on_disk_bus_combo_changed": lambda *x: self.enable_apply(x, EDIT_DISK_BUS),
|
|
"on_disk_format_changed": lambda *x: self.enable_apply(x, EDIT_DISK_FORMAT),
|
|
"on_disk_serial_changed": lambda *x: self.enable_apply(x, EDIT_DISK_SERIAL),
|
|
"on_disk_iotune_changed": self.iotune_changed,
|
|
|
|
"on_network_source_combo_changed": lambda *x: self.enable_apply(x, EDIT_NET_SOURCE),
|
|
"on_network_bridge_changed": lambda *x: self.enable_apply(x, EDIT_NET_SOURCE),
|
|
"on_network-source-mode-combo_changed": lambda *x: self.enable_apply(x, EDIT_NET_SOURCE),
|
|
"on_network_model_combo_changed": lambda *x: self.enable_apply(x, EDIT_NET_MODEL),
|
|
|
|
"on_vport_type_changed": lambda *x: self.enable_apply(x, EDIT_NET_VPORT),
|
|
"on_vport_managerid_changed": lambda *x: self.enable_apply(x,
|
|
EDIT_NET_VPORT),
|
|
"on_vport_typeid_changed": lambda *x: self.enable_apply(x,
|
|
EDIT_NET_VPORT),
|
|
"on_vport_typeidversion_changed": lambda *x: self.enable_apply(x,
|
|
EDIT_NET_VPORT),
|
|
"on_vport_instanceid_changed": lambda *x: self.enable_apply(x,
|
|
EDIT_NET_VPORT),
|
|
|
|
"on_gfx_type_combo_changed": lambda *x: self.enable_apply(x, EDIT_GFX_TYPE),
|
|
"on_vnc_keymap_combo_changed": lambda *x: self.enable_apply(x,
|
|
EDIT_GFX_KEYMAP),
|
|
"on_vnc_password_changed": lambda *x: self.enable_apply(x, EDIT_GFX_PASSWD),
|
|
|
|
"on_sound_model_combo_changed": lambda *x: self.enable_apply(x,
|
|
EDIT_SOUND_MODEL),
|
|
|
|
"on_video_model_combo_changed": lambda *x: self.enable_apply(x,
|
|
EDIT_VIDEO_MODEL),
|
|
|
|
"on_watchdog_model_combo_changed": lambda *x: self.enable_apply(x,
|
|
EDIT_WATCHDOG_MODEL),
|
|
"on_watchdog_action_combo_changed": lambda *x: self.enable_apply(x,
|
|
EDIT_WATCHDOG_ACTION),
|
|
|
|
"on_smartcard_mode_combo_changed": lambda *x: self.enable_apply(x,
|
|
EDIT_SMARTCARD_MODE),
|
|
|
|
"on_tpm_type_combo_changed": (self.enable_apply,
|
|
EDIT_TPM_TYPE),
|
|
|
|
"on_config_apply_clicked": self.config_apply,
|
|
"on_config_cancel_clicked": self.config_cancel,
|
|
|
|
"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_console_pages_switch_page": self.console.page_changed,
|
|
"on_console_auth_password_activate": self.console.auth_login,
|
|
"on_console_auth_login_clicked": self.console.auth_login,
|
|
"on_controller_model_combo_changed": lambda *x: self.enable_apply(x,
|
|
EDIT_CONTROLLER_MODEL),
|
|
})
|
|
|
|
# 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.widget("hw-list").get_selection().connect("changed",
|
|
self.hw_changed)
|
|
self.widget("config-boot-list").get_selection().connect(
|
|
"changed",
|
|
self.config_bootdev_selected)
|
|
|
|
finish_img = Gtk.Image.new_from_stock(Gtk.STOCK_ADD,
|
|
Gtk.IconSize.BUTTON)
|
|
self.widget("add-hardware-button").set_image(finish_img)
|
|
|
|
self.populate_hw_list()
|
|
self.repopulate_boot_list()
|
|
|
|
self.hw_selected()
|
|
self.refresh_vm_state()
|
|
|
|
def _cleanup(self):
|
|
self.oldhwkey = None
|
|
|
|
if self.addhw:
|
|
self.addhw.cleanup()
|
|
self.addhw = None
|
|
|
|
if self.storage_browser:
|
|
self.storage_browser.cleanup()
|
|
self.storage_browser = None
|
|
|
|
for key in self.media_choosers:
|
|
if self.media_choosers[key]:
|
|
self.media_choosers[key].cleanup()
|
|
self.media_choosers = {}
|
|
|
|
for serial in self.serial_tabs:
|
|
self._close_serial_tab(serial)
|
|
|
|
self.console.cleanup()
|
|
self.console = None
|
|
|
|
self.vm = None
|
|
self.conn = None
|
|
self.addhwmenu = None
|
|
|
|
def show(self):
|
|
logging.debug("Showing VM details: %s", self.vm)
|
|
vis = self.is_visible()
|
|
self.topwin.present()
|
|
if vis:
|
|
return
|
|
|
|
self.emit("details-opened")
|
|
self.refresh_vm_state()
|
|
|
|
def customize_finish(self, src):
|
|
ignore = src
|
|
if self.has_unapplied_changes(self.get_hw_row()):
|
|
return
|
|
|
|
return self._close(customize_finish=True)
|
|
|
|
def close(self, ignore1=None, ignore2=None):
|
|
logging.debug("Closing VM details: %s", self.vm)
|
|
return self._close()
|
|
|
|
def _close(self, customize_finish=False):
|
|
fs = self.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.display and
|
|
self.console.viewer.display.get_visible()):
|
|
try:
|
|
self.console.close_viewer()
|
|
except:
|
|
logging.error("Failure when disconnecting from desktop server")
|
|
|
|
if customize_finish:
|
|
self.emit("customize-finished")
|
|
else:
|
|
self.emit("details-closed")
|
|
return 1
|
|
|
|
def is_visible(self):
|
|
return bool(self.topwin.get_visible())
|
|
|
|
|
|
##########################
|
|
# Initialization helpers #
|
|
##########################
|
|
|
|
def init_menus(self):
|
|
# Virtual Machine menu
|
|
self.widget("details-menu-usb-redirection").set_tooltip_text(
|
|
_("Redirect USB device attached on host to virtual machine with SPICE graphics. USB Redirection device is required for Virtual Machine to support this functionality. Auto-redirection is enabled by default"))
|
|
uihelpers.build_shutdown_button_menu(self.widget("control-shutdown"),
|
|
self.control_vm_shutdown,
|
|
self.control_vm_reboot,
|
|
self.control_vm_reset,
|
|
self.control_vm_destroy,
|
|
self.control_vm_save)
|
|
|
|
for name in ["details-menu-shutdown",
|
|
"details-menu-reboot",
|
|
"details-menu-reset",
|
|
"details-menu-poweroff",
|
|
"details-menu-destroy"]:
|
|
image = Gtk.Image.new_from_icon_name("system-shutdown",
|
|
Gtk.IconSize.MENU)
|
|
self.widget(name).set_image(image)
|
|
|
|
# Add HW popup menu
|
|
self.addhwmenu = Gtk.Menu()
|
|
|
|
addHW = Gtk.ImageMenuItem(_("_Add Hardware"))
|
|
addHW.set_use_underline(True)
|
|
addHWImg = Gtk.Image()
|
|
addHWImg.set_from_stock(Gtk.STOCK_ADD, Gtk.IconSize.MENU)
|
|
addHW.set_image(addHWImg)
|
|
addHW.show()
|
|
addHW.connect("activate", self.add_hardware)
|
|
|
|
rmHW = Gtk.ImageMenuItem(_("_Remove Hardware"))
|
|
rmHW.set_use_underline(True)
|
|
rmHWImg = Gtk.Image()
|
|
rmHWImg.set_from_stock(Gtk.STOCK_REMOVE, Gtk.IconSize.MENU)
|
|
rmHW.set_image(rmHWImg)
|
|
rmHW.show()
|
|
rmHW.connect("activate", self.remove_xml_dev)
|
|
|
|
self.addhwmenu.add(addHW)
|
|
self.addhwmenu.add(rmHW)
|
|
|
|
# Serial list menu
|
|
smenu = Gtk.Menu()
|
|
smenu.connect("show", self.populate_serial_menu)
|
|
self.widget("details-menu-view-serial-list").set_submenu(smenu)
|
|
|
|
# Don't allowing changing network/disks for Dom0
|
|
dom0 = self.vm.is_management_domain()
|
|
self.widget("add-hardware-button").set_sensitive(not dom0)
|
|
|
|
self.widget("hw-panel").set_show_tabs(False)
|
|
self.widget("details-pages").set_show_tabs(False)
|
|
self.widget("console-pages").set_show_tabs(False)
|
|
self.widget("details-menu-view-toolbar").set_active(
|
|
self.config.get_details_show_toolbar())
|
|
|
|
# Keycombo menu (ctrl+alt+del etc.)
|
|
self.keycombo_menu = uihelpers.build_keycombo_menu(
|
|
self.console.send_key)
|
|
self.widget("details-menu-send-key").set_submenu(self.keycombo_menu)
|
|
|
|
|
|
def init_graphs(self):
|
|
graph_table = self.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", [x / 255.0 for x in
|
|
[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", [x / 255.0 for x in
|
|
[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, object)
|
|
self.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.widget("hw-list").append_column(hwCol)
|
|
|
|
# Description text view
|
|
desc = self.widget("overview-description")
|
|
buf = Gtk.TextBuffer()
|
|
buf.connect("changed", self.enable_apply, EDIT_DESC)
|
|
desc.set_buffer(buf)
|
|
|
|
# List of applications.
|
|
apps_list = self.widget("inspection-apps")
|
|
apps_model = Gtk.ListStore(str, str, str)
|
|
apps_list.set_model(apps_model)
|
|
|
|
name_col = Gtk.TreeViewColumn(_("Name"))
|
|
version_col = Gtk.TreeViewColumn(_("Version"))
|
|
summary_col = Gtk.TreeViewColumn()
|
|
|
|
apps_list.append_column(name_col)
|
|
apps_list.append_column(version_col)
|
|
apps_list.append_column(summary_col)
|
|
|
|
name_text = Gtk.CellRendererText()
|
|
name_col.pack_start(name_text, True)
|
|
name_col.add_attribute(name_text, 'text', 0)
|
|
name_col.set_sort_column_id(0)
|
|
|
|
version_text = Gtk.CellRendererText()
|
|
version_col.pack_start(version_text, True)
|
|
version_col.add_attribute(version_text, 'text', 1)
|
|
version_col.set_sort_column_id(1)
|
|
|
|
summary_text = Gtk.CellRendererText()
|
|
summary_col.pack_start(summary_text, True)
|
|
summary_col.add_attribute(summary_text, 'text', 2)
|
|
summary_col.set_sort_column_id(2)
|
|
|
|
# Clock combo
|
|
clock_combo = self.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.SortType.ASCENDING)
|
|
for offset in ["localtime", "utc"]:
|
|
clock_model.append([offset])
|
|
|
|
arch = self.vm.get_arch()
|
|
caps = self.vm.conn.caps
|
|
machines = []
|
|
|
|
if len(caps.guests) > 0:
|
|
for guest in caps.guests:
|
|
if len(guest.domains) > 0:
|
|
for domain in guest.domains:
|
|
machines = list(set(machines + domain.machines))
|
|
|
|
if arch in ["i686", "x86_64"]:
|
|
self.widget("label81").hide()
|
|
self.widget("hbox30").hide()
|
|
else:
|
|
machtype_combo = self.widget("machine-type-combo")
|
|
machtype_model = Gtk.ListStore(str)
|
|
machtype_combo.set_model(machtype_model)
|
|
text = Gtk.CellRendererText()
|
|
machtype_combo.pack_start(text, True)
|
|
machtype_combo.add_attribute(text, 'text', 0)
|
|
machtype_model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
|
|
|
|
if len(machines) > 0:
|
|
for machine in machines:
|
|
machtype_model.append([machine])
|
|
|
|
# Security info tooltips
|
|
self.widget("security-static-info").set_tooltip_text(
|
|
_("Static SELinux security type tells libvirt to always start the guest process with the specified label. Unless 'relabel' is set, the administrator is responsible for making sure the images are labeled correctly on disk."))
|
|
self.widget("security-dynamic-info").set_tooltip_text(
|
|
_("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.widget("config-vcpupin-generate")
|
|
generate_warn = self.widget("config-vcpupin-generate-err")
|
|
if not self.conn.caps.host.topology:
|
|
generate_cpuset.set_sensitive(False)
|
|
generate_warn.show()
|
|
generate_warn.set_tooltip_text(_("Libvirt did not detect NUMA capabilities."))
|
|
|
|
|
|
# [ VCPU #, Currently running on Phys CPU #, CPU Pinning list ]
|
|
vcpu_list = self.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.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.conn.caps
|
|
cpu_values = None
|
|
cpu_names = []
|
|
all_features = []
|
|
|
|
try:
|
|
cpu_values = caps.get_cpu_values(self.vm.get_arch())
|
|
cpu_names = sorted([c.model for c in cpu_values.cpus],
|
|
key=str.lower)
|
|
all_features = cpu_values.features
|
|
except:
|
|
logging.exception("Error populating CPU model list")
|
|
|
|
# [ feature name, mode]
|
|
feat_list = self.widget("cpu-features")
|
|
feat_model = Gtk.ListStore(str, str)
|
|
feat_list.set_model(feat_model)
|
|
|
|
nameCol = Gtk.TreeViewColumn()
|
|
polCol = Gtk.TreeViewColumn()
|
|
polCol.set_min_width(80)
|
|
|
|
feat_list.append_column(nameCol)
|
|
feat_list.append_column(polCol)
|
|
|
|
# Feature name col
|
|
name_text = Gtk.CellRendererText()
|
|
nameCol.pack_start(name_text, True)
|
|
nameCol.add_attribute(name_text, 'text', 0)
|
|
nameCol.set_sort_column_id(0)
|
|
|
|
# Feature policy col
|
|
feat_combo = Gtk.CellRendererCombo()
|
|
m = Gtk.ListStore(str)
|
|
for p in virtinst.CPUFeature.POLICIES:
|
|
m.append([p])
|
|
m.append(["default"])
|
|
feat_combo.set_property("model", m)
|
|
feat_combo.set_property("text-column", 0)
|
|
feat_combo.set_property("editable", True)
|
|
polCol.pack_start(feat_combo, False)
|
|
polCol.add_attribute(feat_combo, 'text', 1)
|
|
polCol.set_sort_column_id(1)
|
|
|
|
def feature_changed(src, index, treeiter, model):
|
|
model[index][1] = src.get_property("model")[treeiter][0]
|
|
self.enable_apply(EDIT_CPU)
|
|
|
|
feat_combo.connect("changed", feature_changed, feat_model)
|
|
for name in all_features:
|
|
feat_model.append([name, "default"])
|
|
|
|
# CPU model combo
|
|
cpu_model = self.widget("cpu-model")
|
|
|
|
model = Gtk.ListStore(str, object)
|
|
cpu_model.set_model(model)
|
|
cpu_model.set_entry_text_column(0)
|
|
model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
|
|
for name in cpu_names:
|
|
model.append([name, cpu_values.get_cpu(name)])
|
|
|
|
# Disk cache combo
|
|
disk_cache = self.widget("disk-cache-combo")
|
|
uihelpers.build_cache_combo(self.vm, disk_cache)
|
|
|
|
# Disk io combo
|
|
disk_io = self.widget("disk-io-combo")
|
|
uihelpers.build_io_combo(self.vm, disk_io)
|
|
|
|
# Disk format combo
|
|
format_list = self.widget("disk-format")
|
|
uihelpers.build_storage_format_combo(self.vm, format_list)
|
|
|
|
# Disk bus combo
|
|
disk_bus = self.widget("disk-bus-combo")
|
|
uihelpers.build_disk_bus_combo(self.vm, disk_bus)
|
|
|
|
# Disk iotune expander
|
|
if not (self.conn.is_qemu() or self.conn.is_test_conn()):
|
|
self.widget("iotune-expander").set_property("visible", False)
|
|
|
|
# Network source
|
|
net_source = self.widget("network-source-combo")
|
|
net_bridge = self.widget("network-bridge-box")
|
|
source_mode_box = self.widget("network-source-mode-box")
|
|
source_mode_label = self.widget("network-source-mode")
|
|
vport_expander = self.widget("vport-expander")
|
|
uihelpers.init_network_list(net_source, net_bridge, source_mode_box,
|
|
source_mode_label, vport_expander)
|
|
|
|
# source mode
|
|
source_mode = self.widget("network-source-mode-combo")
|
|
uihelpers.build_source_mode_combo(self.vm, source_mode)
|
|
|
|
# Network model
|
|
net_model = self.widget("network-model-combo")
|
|
uihelpers.build_netmodel_combo(self.vm, net_model)
|
|
|
|
# Graphics type
|
|
gfx_type = self.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.widget("gfx-keymap-combo")
|
|
uihelpers.build_vnc_keymap_combo(self.vm, gfx_keymap,
|
|
no_default=no_default)
|
|
|
|
# Sound model
|
|
sound_dev = self.widget("sound-model-combo")
|
|
uihelpers.build_sound_combo(self.vm, sound_dev, no_default=no_default)
|
|
|
|
# Video model combo
|
|
video_dev = self.widget("video-model-combo")
|
|
uihelpers.build_video_combo(self.vm, video_dev, no_default=no_default)
|
|
|
|
# Watchdog model combo
|
|
combo = self.widget("watchdog-model-combo")
|
|
uihelpers.build_watchdogmodel_combo(self.vm, combo,
|
|
no_default=no_default)
|
|
|
|
# Watchdog action combo
|
|
combo = self.widget("watchdog-action-combo")
|
|
uihelpers.build_watchdogaction_combo(self.vm, combo,
|
|
no_default=no_default)
|
|
|
|
# Smartcard mode
|
|
sc_mode = self.widget("smartcard-mode-combo")
|
|
uihelpers.build_smartcard_mode_combo(self.vm, sc_mode)
|
|
|
|
# Redirection type
|
|
combo = self.widget("redir-type-combo")
|
|
uihelpers.build_redir_type_combo(self.vm, combo)
|
|
|
|
# Controller model
|
|
combo = self.widget("controller-model-combo")
|
|
model = Gtk.ListStore(str, str)
|
|
combo.set_model(model)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 1)
|
|
combo.set_active(-1)
|
|
|
|
|
|
# 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.widget(prefix + "-label")
|
|
model_combo = self.widget(prefix + "-combo")
|
|
|
|
idx = -1
|
|
if comparefunc:
|
|
model_in_list, idx = comparefunc(model_combo.get_model(), value)
|
|
else:
|
|
model_list = [x[model_idx] for x in 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.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.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, None, 0, event.time)
|
|
|
|
def build_serial_list(self):
|
|
ret = []
|
|
|
|
def add_row(text, err, sensitive, do_radio, cb, serialidx):
|
|
ret.append([text, err, sensitive, do_radio, cb, serialidx])
|
|
|
|
devs = self.vm.get_serial_devs()
|
|
if len(devs) == 0:
|
|
add_row(_("No text console available"),
|
|
None, False, False, None, None)
|
|
|
|
def build_desc(dev):
|
|
if dev.virtual_device_type == "console":
|
|
return "Text Console %d" % (dev.vmmindex + 1)
|
|
return "Serial %d" % (dev.vmmindex + 1)
|
|
|
|
for dev in devs:
|
|
desc = build_desc(dev)
|
|
idx = dev.vmmindex
|
|
|
|
err = vmmSerialConsole.can_connect(self.vm, dev)
|
|
sensitive = not bool(err)
|
|
|
|
def cb(src):
|
|
return self.control_serial_tab(src, desc, idx)
|
|
|
|
add_row(desc, err, sensitive, True, cb, idx)
|
|
|
|
return ret
|
|
|
|
def current_serial_dev(self):
|
|
showing_serial = (self.last_console_page >= PAGE_DYNAMIC_OFFSET)
|
|
if not showing_serial:
|
|
return
|
|
|
|
serial_idx = self.last_console_page - PAGE_DYNAMIC_OFFSET
|
|
if len(self.serial_tabs) < serial_idx:
|
|
return
|
|
|
|
return self.serial_tabs[serial_idx]
|
|
|
|
def populate_serial_menu(self, src):
|
|
for ent in src:
|
|
src.remove(ent)
|
|
|
|
serial_page_dev = self.current_serial_dev()
|
|
showing_graphics = (self.last_console_page == PAGE_CONSOLE)
|
|
|
|
# Populate serial devices
|
|
group = None
|
|
itemlist = self.build_serial_list()
|
|
for msg, err, sensitive, do_radio, cb, ignore in itemlist:
|
|
if do_radio:
|
|
item = Gtk.RadioMenuItem(group)
|
|
item.set_label(msg)
|
|
if group is None:
|
|
group = item
|
|
else:
|
|
item = Gtk.MenuItem(msg)
|
|
|
|
item.set_sensitive(sensitive)
|
|
|
|
if err and not sensitive:
|
|
item.set_tooltip_text(err)
|
|
|
|
if cb:
|
|
item.connect("toggled", cb)
|
|
|
|
# Tab is already open, make sure marked as such
|
|
if (sensitive and
|
|
serial_page_dev and
|
|
serial_page_dev.name == msg):
|
|
item.set_active(True)
|
|
|
|
src.add(item)
|
|
|
|
src.add(Gtk.SeparatorMenuItem())
|
|
|
|
# Populate graphical devices
|
|
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)
|
|
item.set_label(_("Graphical Console %s") %
|
|
dev.pretty_type_simple(dev.type))
|
|
if group is None:
|
|
group = item
|
|
|
|
if showing_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.widget("details-menu-view-fullscreen")
|
|
if src.get_active() != menu.get_active():
|
|
menu.set_active(src.get_active())
|
|
|
|
def toggle_toolbar(self, src):
|
|
if self.is_customize_dialog:
|
|
return
|
|
|
|
active = src.get_active()
|
|
self.config.set_details_show_toolbar(active)
|
|
|
|
if (active and not
|
|
self.widget("details-menu-view-fullscreen").get_active()):
|
|
self.widget("toolbar-box").show()
|
|
else:
|
|
self.widget("toolbar-box").hide()
|
|
|
|
def get_selected_row(self, widget):
|
|
selection = widget.get_selection()
|
|
model, treepath = selection.get_selected()
|
|
if treepath is None:
|
|
return None
|
|
return model[treepath]
|
|
|
|
def get_boot_selection(self):
|
|
return self.get_selected_row(self.widget("config-boot-list"))
|
|
|
|
def set_hw_selection(self, page, disable_apply=True):
|
|
if disable_apply:
|
|
self.disable_apply()
|
|
|
|
hwlist = self.widget("hw-list")
|
|
selection = hwlist.get_selection()
|
|
selection.select_path(str(page))
|
|
|
|
def get_hw_row(self):
|
|
return self.get_selected_row(self.widget("hw-list"))
|
|
|
|
def get_hw_selection(self, field):
|
|
row = self.get_hw_row()
|
|
if not row:
|
|
return None
|
|
return row[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.set_hw_selection(0)
|
|
|
|
return page
|
|
|
|
def has_unapplied_changes(self, row):
|
|
if not row:
|
|
return False
|
|
|
|
if not self.widget("config-apply").get_sensitive():
|
|
return False
|
|
|
|
if not util.chkbox_helper(self,
|
|
self.config.get_confirm_unapplied,
|
|
self.config.set_confirm_unapplied,
|
|
text1=(_("There are unapplied changes. Would you like to apply "
|
|
"them now?")),
|
|
chktext=_("Don't warn me again."),
|
|
alwaysrecord=True,
|
|
default=False):
|
|
return False
|
|
|
|
return not self.config_apply(row=row)
|
|
|
|
def hw_changed(self, ignore):
|
|
newrow = self.get_hw_row()
|
|
model = self.widget("hw-list").get_model()
|
|
|
|
if newrow[HW_LIST_COL_DEVICE] == self.oldhwkey:
|
|
return
|
|
|
|
oldhwrow = None
|
|
for row in model:
|
|
if row[HW_LIST_COL_DEVICE] == self.oldhwkey:
|
|
oldhwrow = row
|
|
break
|
|
|
|
if self.has_unapplied_changes(oldhwrow):
|
|
# Unapplied changes, and syncing them failed
|
|
pageidx = 0
|
|
for idx in range(len(model)):
|
|
if model[idx][HW_LIST_COL_DEVICE] == self.oldhwkey:
|
|
pageidx = idx
|
|
break
|
|
self.set_hw_selection(pageidx, disable_apply=False)
|
|
else:
|
|
self.oldhwkey = newrow[HW_LIST_COL_DEVICE]
|
|
self.hw_selected()
|
|
|
|
def hw_selected(self, page=None):
|
|
pagetype = self.force_get_hw_pagetype(page)
|
|
|
|
self.widget("config-remove").set_sensitive(True)
|
|
self.widget("hw-panel").set_sensitive(True)
|
|
self.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()
|
|
elif pagetype == HW_LIST_TYPE_FILESYSTEM:
|
|
self.refresh_filesystem_page()
|
|
elif pagetype == HW_LIST_TYPE_SMARTCARD:
|
|
self.refresh_smartcard_page()
|
|
elif pagetype == HW_LIST_TYPE_REDIRDEV:
|
|
self.refresh_redir_page()
|
|
elif pagetype == HW_LIST_TYPE_TPM:
|
|
self.refresh_tpm_page()
|
|
else:
|
|
pagetype = -1
|
|
except Exception, e:
|
|
self.err.show_err(_("Error refreshing hardware page: %s") % str(e))
|
|
return
|
|
|
|
rem = pagetype in remove_pages
|
|
self.disable_apply()
|
|
self.widget("config-remove").set_property("visible", rem)
|
|
|
|
self.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.widget("control-vm-details") or
|
|
src == self.widget("details-menu-view-details")):
|
|
is_details = True
|
|
|
|
pages = self.widget("details-pages")
|
|
if pages.get_current_page() == PAGE_DETAILS:
|
|
if self.has_unapplied_changes(self.get_hw_row()):
|
|
self.sync_details_console_view(True)
|
|
return
|
|
self.disable_apply()
|
|
|
|
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.widget("control-vm-details")
|
|
details_menu = self.widget("details-menu-view-details")
|
|
console = self.widget("control-vm-console")
|
|
console_menu = self.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)
|
|
self.console.set_allow_fullscreen()
|
|
|
|
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.widget("details-menu-run").get_child().set_label(text)
|
|
self.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.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.widget("details-menu-destroy").set_sensitive(destroy)
|
|
self.widget("control-run").set_sensitive(run)
|
|
self.widget("details-menu-run").set_sensitive(run)
|
|
|
|
self.widget("details-menu-migrate").set_sensitive(stop)
|
|
self.widget("control-shutdown").set_sensitive(stop)
|
|
self.widget("details-menu-shutdown").set_sensitive(stop)
|
|
self.widget("details-menu-save").set_sensitive(stop)
|
|
self.widget("control-pause").set_sensitive(stop)
|
|
self.widget("details-menu-pause").set_sensitive(stop)
|
|
|
|
self.set_pause_state(paused)
|
|
|
|
self.widget("overview-name").set_editable(not active)
|
|
|
|
self.widget("config-vcpus").set_sensitive(not ro)
|
|
self.widget("config-vcpupin").set_sensitive(not ro)
|
|
self.widget("config-memory").set_sensitive(not ro)
|
|
self.widget("config-maxmem").set_sensitive(not ro)
|
|
|
|
# Disable send key menu entries for offline VM
|
|
self.console.send_key_button.set_sensitive(not (run or paused))
|
|
send_key = self.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)
|
|
if not run:
|
|
self.activate_default_console_page()
|
|
|
|
self.widget("overview-status-text").set_text(
|
|
self.vm.run_status())
|
|
self.widget("overview-status-icon").set_from_icon_name(
|
|
self.vm.run_status_icon_name(), Gtk.IconSize.MENU)
|
|
|
|
details = self.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 view_manager(self, src_ignore):
|
|
self.emit("action-view-manager")
|
|
|
|
def exit_app(self, src_ignore):
|
|
self.emit("action-exit-app")
|
|
|
|
def activate_default_console_page(self):
|
|
if self.vm.get_graphics_devices() or not self.vm.get_serial_devs():
|
|
return
|
|
|
|
# Only show serial page if we are already on console view
|
|
pages = self.widget("details-pages")
|
|
if pages.get_current_page() != PAGE_CONSOLE:
|
|
return
|
|
|
|
# Show serial console
|
|
devs = self.build_serial_list()
|
|
for name, ignore, sensitive, ignore, cb, serialidx in devs:
|
|
if not sensitive or not cb:
|
|
continue
|
|
|
|
self._show_serial_tab(name, serialidx)
|
|
break
|
|
|
|
def activate_default_page(self):
|
|
pages = self.widget("details-pages")
|
|
pages.set_current_page(PAGE_CONSOLE)
|
|
self.activate_default_console_page()
|
|
|
|
def activate_console_page(self):
|
|
pages = self.widget("details-pages")
|
|
pages.set_current_page(PAGE_CONSOLE)
|
|
|
|
def activate_performance_page(self):
|
|
self.widget("details-pages").set_current_page(PAGE_DETAILS)
|
|
self.set_hw_selection(HW_LIST_TYPE_STATS)
|
|
|
|
def activate_config_page(self):
|
|
self.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(self.topwin)
|
|
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 set_pause_state(self, paused):
|
|
# Set pause widget states
|
|
try:
|
|
self.ignorePause = True
|
|
self.widget("control-pause").set_active(paused)
|
|
self.widget("details-menu-pause").set_active(paused)
|
|
finally:
|
|
self.ignorePause = False
|
|
|
|
def control_vm_pause(self, src):
|
|
if self.ignorePause:
|
|
return
|
|
|
|
# Let state handler listener change things if nec.
|
|
self.set_pause_state(not src.get_active())
|
|
|
|
if not self.vm.is_paused():
|
|
self.emit("action-suspend-domain",
|
|
self.vm.conn.get_uri(),
|
|
self.vm.get_uuid())
|
|
else:
|
|
self.emit("action-resume-domain",
|
|
self.vm.conn.get_uri(),
|
|
self.vm.get_uuid())
|
|
|
|
def control_vm_menu(self, src_ignore):
|
|
if self.console.viewer.has_usb_redirection() and \
|
|
self.vm.has_spicevmc_type_redirdev():
|
|
widget = self.widget("details-menu-usb-redirection")
|
|
if not widget.get_sensitive():
|
|
widget.set_sensitive(True)
|
|
|
|
def control_vm_run(self, src_ignore):
|
|
self.emit("action-run-domain",
|
|
self.vm.conn.get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_shutdown(self, src_ignore):
|
|
self.emit("action-shutdown-domain",
|
|
self.vm.conn.get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_reboot(self, src_ignore):
|
|
self.emit("action-reboot-domain",
|
|
self.vm.conn.get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_save(self, src_ignore):
|
|
self.emit("action-save-domain",
|
|
self.vm.conn.get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_reset(self, src_ignore):
|
|
self.emit("action-reset-domain",
|
|
self.vm.conn.get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_destroy(self, src_ignore):
|
|
self.emit("action-destroy-domain",
|
|
self.vm.conn.get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_clone(self, src_ignore):
|
|
self.emit("action-clone-domain",
|
|
self.vm.conn.get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_migrate(self, src_ignore):
|
|
self.emit("action-migrate-domain",
|
|
self.vm.conn.get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_delete(self, src_ignore):
|
|
self.emit("action-delete-domain",
|
|
self.vm.conn.get_uri(), self.vm.get_uuid())
|
|
|
|
def control_vm_screenshot(self, src):
|
|
ignore = src
|
|
try:
|
|
return self._take_screenshot()
|
|
except Exception, e:
|
|
self.err.show_err(_("Error taking screenshot: %s") % str(e))
|
|
|
|
def control_vm_usb_redirection(self, src):
|
|
ignore = src
|
|
spice_usbdev_dialog = self.err
|
|
|
|
spice_usbdev_widget = self.console.viewer.get_usb_widget()
|
|
if not spice_usbdev_widget:
|
|
self.err.show_err(_("Error initializing spice USB device widget"))
|
|
return
|
|
|
|
spice_usbdev_widget.show()
|
|
spice_usbdev_dialog.show_info(_("Select USB devices for redirection"),
|
|
widget=spice_usbdev_widget)
|
|
|
|
def _take_screenshot(self):
|
|
image = self.console.viewer.get_pixbuf()
|
|
|
|
metadata = {
|
|
'tEXt::Hypervisor URI': self.vm.conn.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(),
|
|
}
|
|
|
|
ret = image.save_to_bufferv('png', metadata.keys(), metadata.values())
|
|
# On Fedora 19, ret is (bool, str)
|
|
# Someday the bindings might be fixed to just return the str, try
|
|
# and future proof it a bit
|
|
if type(ret) is tuple and len(ret) >= 2:
|
|
ret = ret[1]
|
|
|
|
import datetime
|
|
now = str(datetime.datetime.now()).split(".")[0].replace(" ", "_")
|
|
default = "Screenshot_%s_%s.png" % (self.vm.get_name(), now)
|
|
|
|
path = util.browse_local(
|
|
self.topwin,
|
|
_("Save Virtual Machine Screenshot"),
|
|
self.vm.conn,
|
|
_type=("png", "PNG files"),
|
|
dialog_type=Gtk.FileChooserAction.SAVE,
|
|
browse_reason=self.config.CONFIG_DIR_SCREENSHOT,
|
|
default_name=default)
|
|
if not path:
|
|
logging.debug("No screenshot path given, skipping save.")
|
|
return
|
|
|
|
filename = path
|
|
if not filename.endswith(".png"):
|
|
filename += ".png"
|
|
file(filename, "wb").write(ret)
|
|
|
|
|
|
#########################
|
|
# Serial Console pieces #
|
|
#########################
|
|
|
|
def control_serial_tab(self, src_ignore, name, target_port):
|
|
pages = self.widget("details-pages")
|
|
is_graphics = (name == "graphics")
|
|
is_serial = not is_graphics
|
|
|
|
if is_graphics:
|
|
pages.set_current_page(PAGE_CONSOLE)
|
|
elif is_serial:
|
|
self._show_serial_tab(name, target_port)
|
|
|
|
def _show_serial_tab(self, name, target_port):
|
|
serial = None
|
|
for s in self.serial_tabs:
|
|
if s.name == name:
|
|
serial = s
|
|
break
|
|
|
|
if not serial:
|
|
serial = vmmSerialConsole(self.vm, target_port, name)
|
|
|
|
title = Gtk.Label(label=name)
|
|
self.widget("details-pages").append_page(serial.box, title)
|
|
self.serial_tabs.append(serial)
|
|
serial.open_console()
|
|
|
|
page_idx = self.serial_tabs.index(serial) + PAGE_DYNAMIC_OFFSET
|
|
self.widget("details-pages").set_current_page(page_idx)
|
|
|
|
def _close_serial_tab(self, serial):
|
|
if not serial in self.serial_tabs:
|
|
return
|
|
|
|
page_idx = self.serial_tabs.index(serial) + PAGE_DYNAMIC_OFFSET
|
|
self.widget("details-pages").remove_page(page_idx)
|
|
|
|
serial.cleanup()
|
|
self.serial_tabs.remove(serial)
|
|
|
|
|
|
############################
|
|
# Details/Hardware getters #
|
|
############################
|
|
|
|
def get_config_boot_devs(self):
|
|
boot_model = self.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.widget("cpu-model")
|
|
model = cpu_list.get_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.widget("cpu-features")
|
|
ret = []
|
|
|
|
for row in feature_list.get_model():
|
|
if row[1] in ["off", "model"]:
|
|
continue
|
|
ret.append(row)
|
|
|
|
return ret
|
|
|
|
##############################
|
|
# Details/Hardware listeners #
|
|
##############################
|
|
|
|
def _browse_file(self, callback, is_media=False):
|
|
if is_media:
|
|
reason = self.config.CONFIG_DIR_ISO_MEDIA
|
|
else:
|
|
reason = self.config.CONFIG_DIR_IMAGE
|
|
|
|
if self.storage_browser is 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.topwin, self.conn)
|
|
|
|
def browse_kernel(self, src_ignore):
|
|
def cb(ignore, path):
|
|
self.widget("boot-kernel").set_text(path)
|
|
self._browse_file(cb)
|
|
def browse_initrd(self, src_ignore):
|
|
def cb(ignore, path):
|
|
self.widget("boot-kernel-initrd").set_text(path)
|
|
self._browse_file(cb)
|
|
|
|
def disable_apply(self):
|
|
self.active_edits = []
|
|
self.widget("config-apply").set_sensitive(False)
|
|
self.widget("config-cancel").set_sensitive(False)
|
|
|
|
def enable_apply(self, *arglist):
|
|
edittype = arglist[-1]
|
|
self.widget("config-apply").set_sensitive(True)
|
|
self.widget("config-cancel").set_sensitive(True)
|
|
if edittype not in self.active_edits:
|
|
self.active_edits.append(edittype)
|
|
|
|
# Overview -> Machine settings
|
|
def config_acpi_changed(self, ignore):
|
|
widget = self.widget("overview-acpi")
|
|
incon = widget.get_inconsistent()
|
|
widget.set_inconsistent(False)
|
|
if incon:
|
|
widget.set_active(True)
|
|
self.enable_apply(EDIT_ACPI)
|
|
def config_apic_changed(self, ignore):
|
|
widget = self.widget("overview-apic")
|
|
incon = widget.get_inconsistent()
|
|
widget.set_inconsistent(False)
|
|
if incon:
|
|
widget.set_active(True)
|
|
self.enable_apply(EDIT_APIC)
|
|
|
|
# Overview -> Security
|
|
def security_type_changed(self, button):
|
|
self.enable_apply(EDIT_SECURITY)
|
|
self.widget("security-label").set_sensitive(not button.get_active())
|
|
self.widget("security-relabel").set_sensitive(not button.get_active())
|
|
|
|
# Memory
|
|
def config_get_maxmem(self):
|
|
return uihelpers.spin_get_helper(self.widget("config-maxmem"))
|
|
def config_get_memory(self):
|
|
return uihelpers.spin_get_helper(self.widget("config-memory"))
|
|
|
|
def config_maxmem_changed(self, src_ignore):
|
|
self.enable_apply(EDIT_MEM)
|
|
|
|
def config_memory_changed(self, src_ignore):
|
|
self.enable_apply(EDIT_MEM)
|
|
|
|
maxadj = self.widget("config-maxmem")
|
|
|
|
mem = self.config_get_memory()
|
|
if maxadj.get_value() < mem:
|
|
maxadj.set_value(mem)
|
|
|
|
ignore, upper = maxadj.get_range()
|
|
maxadj.set_range(mem, upper)
|
|
|
|
def generate_cpuset(self):
|
|
mem = int(self.vm.get_memory()) / 1024 / 1024
|
|
return virtinst.Guest.generate_cpuset(self.conn.get_backend(), mem)
|
|
|
|
# VCPUS
|
|
def config_get_vcpus(self):
|
|
return uihelpers.spin_get_helper(self.widget("config-vcpus"))
|
|
def config_get_maxvcpus(self):
|
|
return uihelpers.spin_get_helper(self.widget("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"), e)
|
|
|
|
self.widget("config-vcpupin").set_text("")
|
|
self.widget("config-vcpupin").set_text(pinstr)
|
|
|
|
def config_vcpus_changed(self, ignore):
|
|
self.enable_apply(EDIT_VCPUS)
|
|
|
|
conn = self.vm.conn
|
|
host_active_count = conn.host_active_processor_count()
|
|
cur = self.config_get_vcpus()
|
|
|
|
# Warn about overcommit
|
|
warn = bool(cur > host_active_count)
|
|
self.widget("config-vcpus-warn-box").set_property("visible", warn)
|
|
|
|
maxadj = self.widget("config-maxvcpus")
|
|
maxval = self.config_get_maxvcpus()
|
|
if maxval < cur:
|
|
maxadj.set_value(cur)
|
|
ignore, upper = maxadj.get_range()
|
|
maxadj.set_range(cur, upper)
|
|
|
|
def config_maxvcpus_changed(self, ignore):
|
|
self.enable_apply(EDIT_VCPUS)
|
|
|
|
def config_cpu_copy_host(self, src_ignore):
|
|
# Update UI with output copied from host
|
|
try:
|
|
CPU = virtinst.CPU(self.vm.conn.get_backend())
|
|
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.widget("cpu-topology-table").set_sensitive(do_enable)
|
|
self.enable_apply(EDIT_TOPOLOGY)
|
|
|
|
# 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.widget("config-boot-moveup")
|
|
down_widget = self.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.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.enable_apply(EDIT_BOOTORDER)
|
|
|
|
def config_boot_move(self, src, move_up):
|
|
ignore = src
|
|
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.enable_apply(EDIT_BOOTORDER)
|
|
|
|
# IO Tuning
|
|
def iotune_changed(self, ignore):
|
|
iotune_rbs = int(self.get_text("disk-iotune-rbs") or 0)
|
|
iotune_ris = int(self.get_text("disk-iotune-ris") or 0)
|
|
iotune_tbs = int(self.get_text("disk-iotune-tbs") or 0)
|
|
iotune_tis = int(self.get_text("disk-iotune-tis") or 0)
|
|
iotune_wbs = int(self.get_text("disk-iotune-wbs") or 0)
|
|
iotune_wis = int(self.get_text("disk-iotune-wis") or 0)
|
|
|
|
# libvirt doesn't support having read/write settings along side total
|
|
# settings, so disable the widgets accordingly.
|
|
|
|
have_rw_bytes = (iotune_rbs > 0 or
|
|
iotune_wbs > 0)
|
|
have_t_bytes = (not have_rw_bytes and iotune_tbs > 0)
|
|
|
|
self.widget("disk-iotune-rbs").set_sensitive(have_rw_bytes or not
|
|
have_t_bytes)
|
|
self.widget("disk-iotune-wbs").set_sensitive(have_rw_bytes or not
|
|
have_t_bytes)
|
|
self.widget("disk-iotune-tbs").set_sensitive(have_t_bytes or not
|
|
have_rw_bytes)
|
|
|
|
if have_rw_bytes:
|
|
self.widget("disk-iotune-tbs").set_value(0)
|
|
elif have_t_bytes:
|
|
self.widget("disk-iotune-rbs").set_value(0)
|
|
self.widget("disk-iotune-wbs").set_value(0)
|
|
|
|
have_rw_iops = (iotune_ris > 0 or iotune_wis > 0)
|
|
have_t_iops = (not have_rw_iops and iotune_tis > 0)
|
|
|
|
self.widget("disk-iotune-ris").set_sensitive(have_rw_iops or not
|
|
have_t_iops)
|
|
self.widget("disk-iotune-wis").set_sensitive(have_rw_iops or not
|
|
have_t_iops)
|
|
self.widget("disk-iotune-tis").set_sensitive(have_t_iops or not
|
|
have_rw_iops)
|
|
|
|
if have_rw_iops:
|
|
self.widget("disk-iotune-tis").set_value(0)
|
|
elif have_t_iops:
|
|
self.widget("disk-iotune-ris").set_value(0)
|
|
self.widget("disk-iotune-wis").set_value(0)
|
|
|
|
self.enable_apply(EDIT_DISK_IOTUNE)
|
|
|
|
|
|
# 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
|
|
|
|
try:
|
|
if curpath:
|
|
# Disconnect cdrom
|
|
self.change_storage_media(dev_id_info, None)
|
|
return
|
|
except Exception, e:
|
|
self.err.show_err((_("Error disconnecting media: %s") % e))
|
|
return
|
|
|
|
try:
|
|
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(self.vm, dev_id_info)
|
|
|
|
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(self.topwin)
|
|
except Exception, e:
|
|
self.err.show_err((_("Error launching media dialog: %s") % e))
|
|
return
|
|
|
|
##################################################
|
|
# Details/Hardware config changes (apply button) #
|
|
##################################################
|
|
|
|
def config_cancel(self, ignore=None):
|
|
# Remove current changes and deactive 'apply' button
|
|
self.hw_selected()
|
|
|
|
def config_apply(self, ignore=None, row=None):
|
|
pagetype = None
|
|
devobj = None
|
|
|
|
if not row:
|
|
row = self.get_hw_row()
|
|
if row:
|
|
pagetype = row[HW_LIST_COL_TYPE]
|
|
devobj = row[HW_LIST_COL_DEVICE]
|
|
|
|
key = devobj
|
|
ret = False
|
|
|
|
try:
|
|
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)
|
|
elif pagetype is HW_LIST_TYPE_SMARTCARD:
|
|
ret = self.config_smartcard_apply(key)
|
|
elif pagetype is HW_LIST_TYPE_CONTROLLER:
|
|
ret = self.config_controller_apply(key)
|
|
elif pagetype is HW_LIST_TYPE_TPM:
|
|
ret = self.config_tpm_apply(key)
|
|
else:
|
|
ret = False
|
|
except Exception, e:
|
|
return self.err.show_err(_("Error apply changes: %s") % e)
|
|
|
|
if ret is not False:
|
|
self.disable_apply()
|
|
return True
|
|
|
|
def get_text(self, widgetname, strip=True):
|
|
ret = self.widget(widgetname).get_text()
|
|
if strip:
|
|
ret = ret.strip()
|
|
return ret
|
|
|
|
def editted(self, pagetype):
|
|
if pagetype not in range(EDIT_TOTAL):
|
|
raise RuntimeError("crap! %s" % pagetype)
|
|
return pagetype in self.active_edits
|
|
|
|
def make_apply_data(self):
|
|
definefuncs = []
|
|
defineargs = []
|
|
hotplugfuncs = []
|
|
hotplugargs = []
|
|
|
|
def add_define(func, *args):
|
|
definefuncs.append(func)
|
|
defineargs.append(args)
|
|
def add_hotplug(func, *args):
|
|
hotplugfuncs.append(func)
|
|
hotplugargs.append(args)
|
|
|
|
return (definefuncs, defineargs, add_define,
|
|
hotplugfuncs, hotplugargs, add_hotplug)
|
|
|
|
# Overview section
|
|
def config_overview_apply(self):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
ignore = add_hotplug
|
|
|
|
if self.editted(EDIT_NAME):
|
|
name = self.widget("overview-name").get_text()
|
|
add_define(self.vm.define_name, name)
|
|
|
|
if self.editted(EDIT_ACPI):
|
|
enable_acpi = self.widget("overview-acpi").get_active()
|
|
if self.widget("overview-acpi").get_inconsistent():
|
|
enable_acpi = None
|
|
add_define(self.vm.define_acpi, enable_acpi)
|
|
|
|
if self.editted(EDIT_APIC):
|
|
enable_apic = self.widget("overview-apic").get_active()
|
|
if self.widget("overview-apic").get_inconsistent():
|
|
enable_apic = None
|
|
add_define(self.vm.define_apic, enable_apic)
|
|
|
|
if self.editted(EDIT_CLOCK):
|
|
clock = self.get_combo_label_value("overview-clock")
|
|
add_define(self.vm.define_clock, clock)
|
|
|
|
if self.editted(EDIT_MACHTYPE):
|
|
machtype = self.get_combo_label_value("machine-type")
|
|
add_define(self.vm.define_machtype, machtype)
|
|
|
|
if self.editted(EDIT_SECURITY):
|
|
semodel = None
|
|
setype = "static"
|
|
selabel = self.get_text("security-label")
|
|
relabel = self.widget("security-relabel").get_active()
|
|
|
|
if self.widget("security-dynamic").get_active():
|
|
setype = "dynamic"
|
|
relabel = True
|
|
if self.widget("security-type-box").get_sensitive():
|
|
semodel = self.get_text("security-model")
|
|
|
|
add_define(self.vm.define_seclabel, semodel, setype, selabel, relabel)
|
|
|
|
if self.editted(EDIT_DESC):
|
|
desc_widget = self.widget("overview-description")
|
|
desc = desc_widget.get_buffer().get_property("text") or ""
|
|
add_define(self.vm.define_description, desc)
|
|
add_hotplug(self.vm.hotplug_description, desc)
|
|
|
|
return self._change_config_helper(df, da, hf, ha)
|
|
|
|
# CPUs
|
|
def config_vcpus_apply(self):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
|
|
if self.editted(EDIT_VCPUS):
|
|
vcpus = self.config_get_vcpus()
|
|
maxv = self.config_get_maxvcpus()
|
|
add_define(self.vm.define_vcpus, vcpus, maxv)
|
|
add_hotplug(self.vm.hotplug_vcpus, vcpus)
|
|
|
|
if self.editted(EDIT_CPUSET):
|
|
cpuset = self.get_text("config-vcpupin")
|
|
print cpuset
|
|
add_define(self.vm.define_cpuset, cpuset)
|
|
add_hotplug(self.config_vcpu_pin_cpuset, cpuset)
|
|
|
|
if self.editted(EDIT_CPU):
|
|
model, vendor = self.get_config_cpu_model()
|
|
features = self.get_config_cpu_features()
|
|
add_define(self.vm.define_cpu,
|
|
model, vendor, self._cpu_copy_host, features)
|
|
|
|
if self.editted(EDIT_TOPOLOGY):
|
|
do_top = self.widget("cpu-topology-enable").get_active()
|
|
sockets = self.widget("cpu-sockets").get_value()
|
|
cores = self.widget("cpu-cores").get_value()
|
|
threads = self.widget("cpu-threads").get_value()
|
|
if not do_top:
|
|
sockets = None
|
|
cores = None
|
|
threads = None
|
|
|
|
add_define(self.vm.define_cpu_topology, sockets, cores, threads)
|
|
|
|
ret = self._change_config_helper(df, da, hf, ha)
|
|
if ret:
|
|
self._cpu_copy_host = False
|
|
return ret
|
|
|
|
def config_vcpu_pin(self, src_ignore, path, new_text):
|
|
vcpu_list = self.widget("config-vcpu-list")
|
|
vcpu_model = vcpu_list.get_model()
|
|
row = vcpu_model[path]
|
|
conn = self.vm.conn
|
|
|
|
try:
|
|
new_text = new_text.strip()
|
|
vcpu_num = int(row[0])
|
|
pinlist = virtinst.Guest.cpuset_str_to_tuple(
|
|
conn.get_backend(), new_text)
|
|
except Exception, e:
|
|
self.err.val_err(_("Error building pin list"), e)
|
|
return
|
|
|
|
try:
|
|
self.vm.pin_vcpu(vcpu_num, pinlist)
|
|
except Exception, e:
|
|
self.err.show_err(_("Error pinning vcpus"), e)
|
|
return
|
|
|
|
self._refresh_runtime_pinning()
|
|
|
|
def config_vcpu_pin_cpuset(self, cpuset):
|
|
conn = self.vm.conn
|
|
vcpu_list = self.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.get_backend(), cpuset)
|
|
for row in vcpu_model:
|
|
vcpu_num = row[0]
|
|
self.vm.pin_vcpu(int(vcpu_num), pinlist)
|
|
|
|
# Memory
|
|
def config_memory_apply(self):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
|
|
if self.editted(EDIT_MEM):
|
|
curmem = None
|
|
maxmem = self.config_get_maxmem()
|
|
if self.widget("config-memory").get_sensitive():
|
|
curmem = self.config_get_memory()
|
|
|
|
if curmem:
|
|
curmem = int(curmem) * 1024
|
|
if maxmem:
|
|
maxmem = int(maxmem) * 1024
|
|
|
|
add_define(self.vm.define_both_mem, curmem, maxmem)
|
|
add_hotplug(self.vm.hotplug_both_mem, curmem, maxmem)
|
|
|
|
return self._change_config_helper(df, da, hf, ha)
|
|
|
|
# Boot device / Autostart
|
|
def config_boot_options_apply(self):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
ignore = add_hotplug
|
|
|
|
if self.editted(EDIT_AUTOSTART):
|
|
auto = self.widget("config-autostart")
|
|
try:
|
|
self.vm.set_autostart(auto.get_active())
|
|
except Exception, e:
|
|
self.err.show_err(
|
|
(_("Error changing autostart value: %s") % str(e)))
|
|
return False
|
|
|
|
if self.editted(EDIT_BOOTORDER):
|
|
bootdevs = self.get_config_boot_devs()
|
|
add_define(self.vm.set_boot_device, bootdevs)
|
|
|
|
if self.editted(EDIT_BOOTMENU):
|
|
bootmenu = self.widget("boot-menu").get_active()
|
|
add_define(self.vm.set_boot_menu, bootmenu)
|
|
|
|
if self.editted(EDIT_KERNEL):
|
|
kernel = self.get_text("boot-kernel")
|
|
initrd = self.get_text("boot-kernel-initrd")
|
|
args = self.get_text("boot-kernel-args")
|
|
|
|
if initrd and not kernel:
|
|
return self.err.val_err(
|
|
_("Cannot set initrd without specifying a kernel path"))
|
|
if args and not kernel:
|
|
return self.err.val_err(
|
|
_("Cannot set kernel arguments without specifying a kernel path"))
|
|
|
|
add_define(self.vm.set_boot_kernel, kernel, initrd, args)
|
|
|
|
if self.editted(EDIT_INIT):
|
|
init = self.get_text("boot-init-path")
|
|
if not init:
|
|
return self.err.val_err(_("An init path must be specified"))
|
|
add_define(self.vm.set_boot_init, init)
|
|
|
|
return self._change_config_helper(df, da, hf, ha)
|
|
|
|
# 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):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
ignore = add_hotplug
|
|
|
|
if self.editted(EDIT_DISK_RO):
|
|
do_readonly = self.widget("disk-readonly").get_active()
|
|
add_define(self.vm.define_disk_readonly, dev_id_info, do_readonly)
|
|
|
|
if self.editted(EDIT_DISK_SHARE):
|
|
do_shareable = self.widget("disk-shareable").get_active()
|
|
add_define(self.vm.define_disk_shareable,
|
|
dev_id_info, do_shareable)
|
|
|
|
if self.editted(EDIT_DISK_CACHE):
|
|
cache = self.get_combo_label_value("disk-cache")
|
|
add_define(self.vm.define_disk_cache, dev_id_info, cache)
|
|
|
|
if self.editted(EDIT_DISK_IO):
|
|
io = self.get_combo_label_value("disk-io")
|
|
add_define(self.vm.define_disk_io, dev_id_info, io)
|
|
|
|
if self.editted(EDIT_DISK_FORMAT):
|
|
fmt = self.widget("disk-format").get_child().get_text().strip()
|
|
add_define(self.vm.define_disk_driver_type, dev_id_info, fmt)
|
|
|
|
if self.editted(EDIT_DISK_SERIAL):
|
|
serial = self.get_text("disk-serial")
|
|
add_define(self.vm.define_disk_serial, dev_id_info, serial)
|
|
|
|
if self.editted(EDIT_DISK_IOTUNE):
|
|
iotune_rbs = int(self.widget("disk-iotune-rbs").get_value() * 1024)
|
|
iotune_ris = int(self.widget("disk-iotune-ris").get_value())
|
|
iotune_tbs = int(self.widget("disk-iotune-tbs").get_value() * 1024)
|
|
iotune_tis = int(self.widget("disk-iotune-tis").get_value())
|
|
iotune_wbs = int(self.widget("disk-iotune-wbs").get_value() * 1024)
|
|
iotune_wis = int(self.widget("disk-iotune-wis").get_value())
|
|
|
|
add_define(self.vm.define_disk_iotune_rbs, dev_id_info, iotune_rbs)
|
|
add_define(self.vm.define_disk_iotune_ris, dev_id_info, iotune_ris)
|
|
add_define(self.vm.define_disk_iotune_tbs, dev_id_info, iotune_tbs)
|
|
add_define(self.vm.define_disk_iotune_tis, dev_id_info, iotune_tis)
|
|
add_define(self.vm.define_disk_iotune_wbs, dev_id_info, iotune_wbs)
|
|
add_define(self.vm.define_disk_iotune_wis, dev_id_info, iotune_wis)
|
|
|
|
# Do this last since it can change uniqueness info of the dev
|
|
if self.editted(EDIT_DISK_BUS):
|
|
bus = self.get_combo_label_value("disk-bus")
|
|
addr = None
|
|
if bus == "spapr-vscsi":
|
|
bus = "scsi"
|
|
addr = "spapr-vio"
|
|
add_define(self.vm.define_disk_bus, dev_id_info, bus, addr)
|
|
|
|
return self._change_config_helper(df, da, hf, ha)
|
|
|
|
# Audio options
|
|
def config_sound_apply(self, dev_id_info):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
ignore = add_hotplug
|
|
|
|
if self.editted(EDIT_SOUND_MODEL):
|
|
model = self.get_combo_label_value("sound-model")
|
|
if model:
|
|
add_define(self.vm.define_sound_model, dev_id_info, model)
|
|
|
|
return self._change_config_helper(df, da, hf, ha)
|
|
|
|
# Smartcard options
|
|
def config_smartcard_apply(self, dev_id_info):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
ignore = add_hotplug
|
|
|
|
if self.editted(EDIT_SMARTCARD_MODE):
|
|
model = self.get_combo_label_value("smartcard-mode")
|
|
if model:
|
|
add_define(self.vm.define_smartcard_mode, dev_id_info, model)
|
|
|
|
return self._change_config_helper(df, da, hf, ha)
|
|
|
|
# TPM options
|
|
def config_tpm_apply(self, dev_id_info):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
ignore = add_hotplug
|
|
|
|
if self.editted(EDIT_TPM_TYPE):
|
|
typ = self.get_combo_label_value("tpm-type")
|
|
if typ:
|
|
add_define(self.vm.define_tpm_type, dev_id_info, typ)
|
|
|
|
return self._change_config_helper(df, da, hf, ha)
|
|
|
|
# Network options
|
|
def config_network_apply(self, dev_id_info):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
ignore = add_hotplug
|
|
|
|
if self.editted(EDIT_NET_MODEL):
|
|
model = self.get_combo_label_value("network-model")
|
|
addr = None
|
|
if model == "spapr-vlan":
|
|
addr = "spapr-vio"
|
|
add_define(self.vm.define_network_model, dev_id_info, model, addr)
|
|
|
|
if self.editted(EDIT_NET_SOURCE):
|
|
mode = None
|
|
net_list = self.widget("network-source-combo")
|
|
net_bridge = self.widget("network-bridge")
|
|
nettype, source = uihelpers.get_network_selection(net_list,
|
|
net_bridge)
|
|
if nettype == "direct":
|
|
mode = self.get_combo_label_value("network-source-mode")
|
|
|
|
add_define(self.vm.define_network_source, dev_id_info,
|
|
nettype, source, mode)
|
|
|
|
if self.editted(EDIT_NET_VPORT):
|
|
vport_type = self.get_text("vport-type")
|
|
vport_managerid = self.get_text("vport-managerid")
|
|
vport_typeid = self.get_text("vport-typeid")
|
|
vport_idver = self.get_text("vport-typeidversion")
|
|
vport_instid = self.get_text("vport-instanceid")
|
|
|
|
add_define(self.vm.define_virtualport, dev_id_info,
|
|
vport_type, vport_managerid, vport_typeid,
|
|
vport_idver, vport_instid)
|
|
|
|
return self._change_config_helper(df, da, hf, ha)
|
|
|
|
# Graphics options
|
|
def _do_change_spicevmc(self, gdev, newgtype):
|
|
has_multi_spice = (len([d for d in self.vm.get_graphics_devices() if
|
|
d.type == d.TYPE_SPICE]) > 1)
|
|
has_spicevmc = bool([d for d in self.vm.get_char_devices() if
|
|
(d.dev_type == d.DEV_CHANNEL and
|
|
d.char_type == d.CHAR_SPICEVMC)])
|
|
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):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
|
|
if self.editted(EDIT_GFX_PASSWD):
|
|
passwd = self.get_text("gfx-password", strip=False) or None
|
|
add_define(self.vm.define_graphics_password, dev_id_info, passwd)
|
|
add_hotplug(self.vm.hotplug_graphics_password, dev_id_info,
|
|
passwd)
|
|
|
|
if self.editted(EDIT_GFX_KEYMAP):
|
|
keymap = self.get_combo_label_value("gfx-keymap")
|
|
add_define(self.vm.define_graphics_keymap, dev_id_info, keymap)
|
|
|
|
# Do this last since it can change graphics unique ID
|
|
if self.editted(EDIT_GFX_TYPE):
|
|
gtype = self.get_combo_label_value("gfx-type")
|
|
change_spicevmc = self._do_change_spicevmc(dev_id_info, gtype)
|
|
add_define(self.vm.define_graphics_type, dev_id_info,
|
|
gtype, change_spicevmc)
|
|
|
|
return self._change_config_helper(df, da, hf, ha)
|
|
|
|
# Video options
|
|
def config_video_apply(self, dev_id_info):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
ignore = add_hotplug
|
|
|
|
if self.editted(EDIT_VIDEO_MODEL):
|
|
model = self.get_combo_label_value("video-model")
|
|
if model:
|
|
add_define(self.vm.define_video_model, dev_id_info, model)
|
|
|
|
return self._change_config_helper(df, da, hf, ha)
|
|
|
|
# Controller options
|
|
def config_controller_apply(self, dev_id_info):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
ignore = add_hotplug
|
|
|
|
if self.editted(EDIT_CONTROLLER_MODEL):
|
|
model = self.get_combo_label_value("controller-model")
|
|
if model:
|
|
add_define(self.vm.define_controller_model, dev_id_info, model)
|
|
|
|
return self._change_config_helper(df, da, hf, ha)
|
|
|
|
# Watchdog options
|
|
def config_watchdog_apply(self, dev_id_info):
|
|
df, da, add_define, hf, ha, add_hotplug = self.make_apply_data()
|
|
ignore = add_hotplug
|
|
|
|
if self.editted(EDIT_WATCHDOG_MODEL):
|
|
model = self.get_combo_label_value("watchdog-model")
|
|
add_define(self.vm.define_watchdog_model, dev_id_info, model)
|
|
|
|
if self.editted(EDIT_WATCHDOG_ACTION):
|
|
action = self.get_combo_label_value("watchdog-action")
|
|
add_define(self.vm.define_watchdog_action, dev_id_info, action)
|
|
|
|
return self._change_config_helper(df, da, hf, ha)
|
|
|
|
# Device removal
|
|
def remove_device(self, dev_type, dev_id_info):
|
|
logging.debug("Removing device: %s %s", dev_type, dev_id_info)
|
|
|
|
if not util.chkbox_helper(self, self.config.get_confirm_removedev,
|
|
self.config.set_confirm_removedev,
|
|
text1=(_("Are you sure you want to remove this device?"))):
|
|
return
|
|
|
|
# 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:
|
|
self.disable_apply()
|
|
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 guest "
|
|
"shutdown."),
|
|
buttons=Gtk.ButtonsType.OK,
|
|
dialog_type=Gtk.MessageType.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)
|
|
"""
|
|
define_funcs = virtinst.util.listify(define_funcs)
|
|
define_funcs_args = virtinst.util.listify(define_funcs_args)
|
|
hotplug_funcs = virtinst.util.listify(hotplug_funcs)
|
|
hotplug_funcs_args = virtinst.util.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 shutdown "
|
|
"to take effect.")
|
|
else:
|
|
msg = _("These changes will take effect after "
|
|
"the next guest shutdown.")
|
|
|
|
dtype = hotplug_err and Gtk.MessageType.WARNING or Gtk.MessageType.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.ButtonsType.OK,
|
|
dialog_type=dtype)
|
|
|
|
return True
|
|
|
|
########################
|
|
# Details page refresh #
|
|
########################
|
|
|
|
def refresh_resources(self, ignore):
|
|
details = self.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.widget("config-apply").get_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.widget("overview-name").set_text(self.vm.get_name())
|
|
self.widget("overview-uuid").set_text(self.vm.get_uuid())
|
|
desc = self.vm.get_description() or ""
|
|
desc_widget = self.widget("overview-description")
|
|
desc_widget.get_buffer().set_text(desc)
|
|
|
|
# Hypervisor Details
|
|
self.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.widget("overview-arch").set_text(arch)
|
|
self.widget("overview-emulator").set_text(emu)
|
|
|
|
# Operating System (ie. inspection data)
|
|
hostname = self.vm.inspection.hostname
|
|
if not hostname:
|
|
hostname = _("unknown")
|
|
self.widget("inspection-hostname").set_text(hostname)
|
|
product_name = self.vm.inspection.product_name
|
|
if not product_name:
|
|
product_name = _("unknown")
|
|
self.widget("inspection-product-name").set_text(product_name)
|
|
|
|
# Applications (also inspection data)
|
|
apps = self.vm.inspection.applications or []
|
|
|
|
apps_list = self.widget("inspection-apps")
|
|
apps_model = apps_list.get_model()
|
|
apps_model.clear()
|
|
for app in apps:
|
|
name = ""
|
|
if app["app_name"]:
|
|
name = app["app_name"]
|
|
if app["app_display_name"]:
|
|
name = app["app_display_name"]
|
|
version = ""
|
|
if app["app_version"]:
|
|
version = app["app_version"]
|
|
if app["app_release"]:
|
|
version += "-" + app["app_release"]
|
|
summary = ""
|
|
if app["app_summary"]:
|
|
summary = app["app_summary"]
|
|
|
|
apps_model.append([name, version, summary])
|
|
|
|
# Machine settings
|
|
acpi = self.vm.get_acpi()
|
|
apic = self.vm.get_apic()
|
|
clock = self.vm.get_clock()
|
|
machtype = self.vm.get_machtype()
|
|
|
|
# Hack in a way to represent 'default' acpi/apic for customize dialog
|
|
self.widget("overview-acpi").set_active(bool(acpi))
|
|
self.widget("overview-acpi").set_inconsistent(
|
|
acpi is None and self.is_customize_dialog)
|
|
self.widget("overview-apic").set_active(bool(apic))
|
|
self.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)
|
|
|
|
if not arch in ["i686", "x86_64"]:
|
|
if machtype is not None:
|
|
self.set_combo_label("machine-type", machtype)
|
|
|
|
# Security details
|
|
semodel, sectype, vmlabel, relabel = self.vm.get_seclabel()
|
|
caps = self.vm.conn.caps
|
|
|
|
if caps.host.secmodel and caps.host.secmodel.model:
|
|
semodel = caps.host.secmodel.model
|
|
|
|
self.widget("security-model").set_text(semodel or _("None"))
|
|
|
|
if not semodel or semodel == "apparmor":
|
|
self.widget("security-type-box").hide()
|
|
self.widget("security-type-label").hide()
|
|
else:
|
|
self.widget("security-type-box").set_sensitive(bool(semodel))
|
|
|
|
if sectype == "static":
|
|
self.widget("security-static").set_active(True)
|
|
self.widget("security-relabel").set_sensitive(True)
|
|
# As "no" is default for relabel with 'static' label and
|
|
# 'dynamic' must have relabel='yes', this will work properly
|
|
# for both False (relabel='no') and None (relabel not
|
|
# specified)
|
|
self.widget("security-relabel").set_active(relabel)
|
|
else:
|
|
self.widget("security-dynamic").set_active(True)
|
|
# Dynamic label type must use resource labeling
|
|
self.widget("security-relabel").set_active(True)
|
|
self.widget("security-relabel").set_sensitive(False)
|
|
self.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.guest_cpu_time_percentage()
|
|
|
|
cur_vm_memory = self.vm.stats_memory()
|
|
vm_memory = self.vm.maximum_memory()
|
|
mem_txt = "%s of %s" % (util.pretty_mem(cur_vm_memory),
|
|
util.pretty_mem(vm_memory))
|
|
|
|
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.widget("overview-cpu-usage-text").set_text(cpu_txt)
|
|
self.widget("overview-memory-usage-text").set_text(mem_txt)
|
|
self.widget("overview-network-traffic-text").set_markup(net_txt)
|
|
self.widget("overview-disk-usage-text").set_markup(dsk_txt)
|
|
|
|
self.cpu_usage_graph.set_property("data_array",
|
|
self.vm.guest_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.conn
|
|
host_active_count = conn.host_active_processor_count()
|
|
maxvcpus = self.vm.vcpu_max_count()
|
|
curvcpus = self.vm.vcpu_count()
|
|
|
|
curadj = self.widget("config-vcpus")
|
|
maxadj = self.widget("config-maxvcpus")
|
|
curadj.set_value(int(curvcpus))
|
|
maxadj.set_value(int(maxvcpus))
|
|
|
|
self.widget("state-host-cpus").set_text(str(host_active_count))
|
|
|
|
# Warn about overcommit
|
|
warn = bool(self.config_get_vcpus() > host_active_count)
|
|
self.widget("config-vcpus-warn-box").set_property("visible", warn)
|
|
def _refresh_cpu_pinning(self):
|
|
# Populate VCPU pinning
|
|
vcpupin = self.vm.vcpu_pinning()
|
|
self.widget("config-vcpupin").set_text(vcpupin)
|
|
|
|
def _refresh_runtime_pinning(self):
|
|
conn = self.vm.conn
|
|
host_active_count = conn.host_active_processor_count()
|
|
|
|
vcpu_list = self.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.")
|
|
else:
|
|
try:
|
|
vcpu_info, vcpu_pinning = self.vm.vcpu_info()
|
|
except Exception, e:
|
|
reason = _("Error getting VCPU info: %s") % str(e)
|
|
|
|
if not self.vm.getvcpus_supported:
|
|
reason = _("Virtual machine does not support runtime "
|
|
"VPCU info.")
|
|
|
|
vcpu_list.set_sensitive(not bool(reason))
|
|
vcpu_list.set_tooltip_text(reason or "")
|
|
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.widget("cpu-features")
|
|
model = cpu.model or ""
|
|
caps = self.vm.conn.caps
|
|
|
|
capscpu = None
|
|
try:
|
|
arch = self.vm.get_arch()
|
|
if arch:
|
|
cpu_values = caps.get_cpu_values(arch)
|
|
for c in cpu_values.cpus:
|
|
if model and c.model == model:
|
|
capscpu = c
|
|
break
|
|
except:
|
|
pass
|
|
|
|
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.widget("cpu-topology-enable").set_active(show_top)
|
|
self.widget("cpu-model").get_child().set_text(model)
|
|
self.widget("cpu-sockets").set_value(sockets)
|
|
self.widget("cpu-cores").set_value(cores)
|
|
self.widget("cpu-threads").set_value(threads)
|
|
|
|
def get_feature_policy(name):
|
|
for f in cpu.features:
|
|
if f.name == name:
|
|
return f.policy
|
|
|
|
if capscpu:
|
|
for f in capscpu.features:
|
|
if f == name:
|
|
return "model"
|
|
return "off"
|
|
|
|
for row in feature_ui.get_model():
|
|
row[1] = get_feature_policy(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.widget("state-host-memory")
|
|
host_mem = self.vm.conn.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.widget("config-memory")
|
|
maxmem = self.widget("config-maxmem")
|
|
curmem.set_value(int(round(vm_cur_mem)))
|
|
maxmem.set_value(int(round(vm_max_mem)))
|
|
|
|
if not self.widget("config-memory").get_sensitive():
|
|
ignore, upper = maxmem.get_range()
|
|
maxmem.set_range(curmem.get_value(), upper)
|
|
|
|
|
|
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
|
|
addr = disk.address.type
|
|
idx = disk.disk_bus_index
|
|
cache = disk.driver_cache
|
|
io = disk.driver_io
|
|
driver_type = disk.driver_type or ""
|
|
serial = disk.serial
|
|
|
|
iotune_rbs = (disk.iotune_rbs or 0) / 1024
|
|
iotune_ris = (disk.iotune_ris or 0)
|
|
iotune_tbs = (disk.iotune_tbs or 0) / 1024
|
|
iotune_tis = (disk.iotune_tis or 0)
|
|
iotune_wbs = (disk.iotune_wbs or 0) / 1024
|
|
iotune_wis = (disk.iotune_wis or 0)
|
|
|
|
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)
|
|
|
|
if addr == "spapr-vio":
|
|
bus = "spapr-vscsi"
|
|
|
|
pretty_name = prettyify_disk(devtype, bus, idx)
|
|
|
|
self.widget("disk-source-path").set_text(path or "-")
|
|
self.widget("disk-target-type").set_text(pretty_name)
|
|
|
|
self.widget("disk-readonly").set_active(ro)
|
|
self.widget("disk-readonly").set_sensitive(not is_cdrom)
|
|
self.widget("disk-shareable").set_active(share)
|
|
self.widget("disk-size").set_text(size)
|
|
self.set_combo_label("disk-cache", cache)
|
|
self.set_combo_label("disk-io", io)
|
|
|
|
self.widget("disk-format").set_sensitive(show_format)
|
|
self.widget("disk-format").get_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)
|
|
self.widget("disk-serial").set_text(serial or "")
|
|
|
|
self.widget("disk-iotune-rbs").set_value(iotune_rbs)
|
|
self.widget("disk-iotune-ris").set_value(iotune_ris)
|
|
self.widget("disk-iotune-tbs").set_value(iotune_tbs)
|
|
self.widget("disk-iotune-tis").set_value(iotune_tis)
|
|
self.widget("disk-iotune-wbs").set_value(iotune_wbs)
|
|
self.widget("disk-iotune-wis").set_value(iotune_wis)
|
|
|
|
button = self.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()
|
|
source_mode = net.source_mode
|
|
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.widget("network-mac-address").set_text(net.macaddr)
|
|
uihelpers.populate_network_list(
|
|
self.widget("network-source-combo"),
|
|
self.conn)
|
|
self.widget("network-source-combo").set_active(-1)
|
|
|
|
self.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.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)
|
|
|
|
# source mode
|
|
uihelpers.populate_source_mode_combo(self.vm,
|
|
self.widget("network-source-mode-combo"))
|
|
self.set_combo_label("network-source-mode", source_mode)
|
|
|
|
# Virtualport config
|
|
show_vport = (nettype == "direct")
|
|
vport = net.virtualport
|
|
self.widget("vport-expander").set_property("visible", show_vport)
|
|
self.widget("vport-type").set_text(vport.type or "")
|
|
self.widget("vport-managerid").set_text(vport.managerid or "")
|
|
self.widget("vport-typeid").set_text(vport.typeid or "")
|
|
self.widget("vport-typeidversion").set_text(vport.typeidversion or "")
|
|
self.widget("vport-instanceid").set_text(vport.instanceid or "")
|
|
|
|
uihelpers.populate_netmodel_combo(self.vm,
|
|
self.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.widget("input-dev-type").set_text(dev)
|
|
self.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.widget("config-remove").set_sensitive(False)
|
|
else:
|
|
self.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.widget("graphics-title")
|
|
table = self.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.widget(base + "-title").show()
|
|
self.widget(base + suffix).show()
|
|
|
|
def show_text(widget_name, text):
|
|
show_row(widget_name)
|
|
self.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_smartcard_page(self):
|
|
sc = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not sc:
|
|
return
|
|
|
|
self.set_combo_label("smartcard-mode", sc.mode)
|
|
|
|
def refresh_redir_page(self):
|
|
rd = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not rd:
|
|
return
|
|
|
|
address = build_redir_label(rd)[0] or "-"
|
|
|
|
devlabel = "<b>Redirected %s Device</b>" % rd.bus.upper()
|
|
self.widget("redir-title").set_markup(devlabel)
|
|
self.widget("redir-address").set_text(address)
|
|
|
|
self.widget("redir-type-label").set_text(rd.type)
|
|
self.widget("redir-type-combo").hide()
|
|
|
|
def refresh_tpm_page(self):
|
|
tpmdev = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not tpmdev:
|
|
return
|
|
|
|
def show_ui(param, val=None):
|
|
widgetname = "tpm-" + param.replace("_", "-")
|
|
labelname = widgetname + "-label"
|
|
doshow = tpmdev.supports_property(param)
|
|
|
|
if not val and doshow:
|
|
val = getattr(tpmdev, param)
|
|
|
|
self.widget(widgetname).set_property("visible", doshow)
|
|
self.widget(labelname).set_property("visible", doshow)
|
|
self.widget(widgetname).set_text(val or "-")
|
|
|
|
dev_type = tpmdev.type
|
|
|
|
self.widget("tpm-dev-type").set_text(dev_type)
|
|
|
|
# Device type specific properties, only show if apply to the cur dev
|
|
show_ui("device_path")
|
|
|
|
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, ro=True)
|
|
|
|
# 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.widget(widgetname).set_property("visible", doshow)
|
|
self.widget(labelname).set_property("visible", doshow)
|
|
self.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.widget("char-type").set_markup(typelabel)
|
|
self.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.conn, 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.widget("hostdev-title").set_markup(devlabel)
|
|
self.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
|
|
|
|
no_default = not self.is_customize_dialog
|
|
uihelpers.populate_video_combo(self.vm,
|
|
self.widget("video-model-combo"),
|
|
no_default=no_default)
|
|
|
|
model = vid.model_type
|
|
ram = vid.vram
|
|
heads = vid.heads
|
|
try:
|
|
ramlabel = ram and "%d MB" % (int(ram) / 1024) or "-"
|
|
except:
|
|
ramlabel = "-"
|
|
|
|
self.widget("video-ram").set_text(ramlabel)
|
|
self.widget("video-heads").set_text(heads and heads or "-")
|
|
|
|
self.set_combo_label("video-model", model,
|
|
label=vid.pretty_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)
|
|
model_label = dev.model
|
|
if not model_label:
|
|
model_label = _("Default")
|
|
|
|
self.widget("controller-type").set_text(type_label)
|
|
|
|
combo = self.widget("controller-model-combo")
|
|
model = combo.get_model()
|
|
model.clear()
|
|
if dev.type == virtinst.VirtualController.CONTROLLER_TYPE_USB:
|
|
model.append(["Default", "Default"])
|
|
model.append(["ich9-ehci1", "USB 2"])
|
|
self.widget("config-remove").set_sensitive(False)
|
|
else:
|
|
self.widget("config-remove").set_sensitive(True)
|
|
|
|
self.set_combo_label("controller-model", model_label)
|
|
|
|
def refresh_filesystem_page(self):
|
|
dev = self.get_hw_selection(HW_LIST_COL_DEVICE)
|
|
if not dev:
|
|
return
|
|
|
|
self.widget("fs-type").set_text(dev.type)
|
|
|
|
# mode can be irrelevant depending on the fs driver type
|
|
# selected.
|
|
if dev.mode:
|
|
self.show_pair("fs-mode", True)
|
|
self.widget("fs-mode").set_text(dev.mode)
|
|
else:
|
|
self.show_pair("fs-mode", False)
|
|
|
|
self.widget("fs-driver").set_text(dev.driver or _("Default"))
|
|
|
|
self.widget("fs-wrpolicy").set_text(dev.wrpolicy or _("Default"))
|
|
|
|
self.widget("fs-source").set_text(dev.source)
|
|
self.widget("fs-target").set_text(dev.target)
|
|
if dev.readonly:
|
|
self.widget("fs-readonly").set_text("Yes")
|
|
else:
|
|
self.widget("fs-readonly").set_text("No")
|
|
|
|
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
|
|
autostart_chk = self.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)
|
|
|
|
show_kernel = not self.vm.is_container()
|
|
show_init = self.vm.is_container()
|
|
show_boot = (not self.vm.is_container() and not self.vm.is_xenpv())
|
|
|
|
self.widget("boot-order-align").set_property("visible", show_boot)
|
|
self.widget("boot-kernel-align").set_property("visible", show_kernel)
|
|
self.widget("boot-init-align").set_property("visible", show_init)
|
|
|
|
# Kernel/initrd boot
|
|
kernel, initrd, args = self.vm.get_boot_kernel_info()
|
|
expand = bool(kernel or initrd or args)
|
|
self.widget("boot-kernel").set_text(kernel or "")
|
|
self.widget("boot-kernel-initrd").set_text(initrd or "")
|
|
self.widget("boot-kernel-args").set_text(args or "")
|
|
if expand:
|
|
self.widget("boot-kernel-expander").set_expanded(True)
|
|
|
|
# <init> populate
|
|
init = self.vm.get_init()
|
|
self.widget("boot-init-path").set_text(init or "")
|
|
|
|
# Boot menu populate
|
|
menu = self.vm.get_boot_menu() or False
|
|
self.widget("boot-menu").set_active(menu)
|
|
self.repopulate_boot_list()
|
|
|
|
|
|
############################
|
|
# Hardware list population #
|
|
############################
|
|
|
|
def populate_disk_bus_combo(self, devtype, no_default):
|
|
buslist = self.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"])
|
|
if self.vm.rhel6_defaults():
|
|
buses.append(["scsi", "SCSI"])
|
|
else:
|
|
if self.vm.is_hvm():
|
|
buses.append(["ide", "IDE"])
|
|
if self.vm.rhel6_defaults():
|
|
buses.append(["scsi", "SCSI"])
|
|
buses.append(["usb", "USB"])
|
|
if self.vm.get_hv_type() in ["kvm", "test"]:
|
|
buses.append(["sata", "SATA"])
|
|
buses.append(["virtio", "Virtio"])
|
|
if (self.vm.get_hv_type() == "kvm" and
|
|
self.vm.get_machtype() == "pseries"):
|
|
buses.append(["spapr-vscsi", "sPAPR-vSCSI"])
|
|
if self.vm.conn.is_xen() or self.vm.get_hv_type() == "test":
|
|
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.widget("hw-list").get_model()
|
|
hw_list_model.clear()
|
|
|
|
def add_hw_list_option(title, page_id, icon_name):
|
|
hw_list_model.append([title, icon_name,
|
|
Gtk.IconSize.LARGE_TOOLBAR,
|
|
page_id, title])
|
|
|
|
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.widget("hw-list")
|
|
hw_list_model = hw_list.get_model()
|
|
|
|
currentDevices = []
|
|
|
|
def dev_cmp(origdev, newdev):
|
|
if isinstance(origdev, str):
|
|
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.IconSize.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"
|
|
|
|
if disk.address.type == "spapr-vio":
|
|
bus = "spapr-vscsi"
|
|
|
|
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 redir devices
|
|
for redirdev in self.vm.get_redirdev_devices():
|
|
bus = redirdev.bus
|
|
label = build_redir_label(redirdev)[1]
|
|
|
|
if bus == "usb":
|
|
icon = "device_usb"
|
|
else:
|
|
icon = "device_pci"
|
|
update_hwlist(HW_LIST_TYPE_REDIRDEV, redirdev, label, icon)
|
|
|
|
# Populate video devices
|
|
for vid in self.vm.get_video_devices():
|
|
update_hwlist(HW_LIST_TYPE_VIDEO, vid,
|
|
_("Video %s") % vid.pretty_model(vid.model_type),
|
|
"video-display")
|
|
|
|
# Populate watchdog devices
|
|
for watch in self.vm.get_watchdog_devices():
|
|
update_hwlist(HW_LIST_TYPE_WATCHDOG, watch, _("Watchdog"),
|
|
"device_pci")
|
|
|
|
# Populate controller devices
|
|
for cont in self.vm.get_controller_devices():
|
|
# skip USB2 ICH9 companion controllers
|
|
if cont.model in ["ich9-uhci1", "ich9-uhci2", "ich9-uhci3"]:
|
|
continue
|
|
|
|
pretty_type = virtinst.VirtualController.pretty_type(cont.type)
|
|
update_hwlist(HW_LIST_TYPE_CONTROLLER, cont,
|
|
_("Controller %s") % pretty_type,
|
|
"device_pci")
|
|
|
|
# Populate filesystem devices
|
|
for fs in self.vm.get_filesystem_devices():
|
|
target = fs.target[:8]
|
|
update_hwlist(HW_LIST_TYPE_FILESYSTEM, fs,
|
|
_("Filesystem %s") % target,
|
|
Gtk.STOCK_DIRECTORY)
|
|
|
|
# Populate list of smartcard devices
|
|
for sc in self.vm.get_smartcard_devices():
|
|
update_hwlist(HW_LIST_TYPE_SMARTCARD, sc,
|
|
_("Smartcard"), "device_serial")
|
|
|
|
# Populate list of TPM devices
|
|
for tpm in self.vm.get_tpm_devices():
|
|
update_hwlist(HW_LIST_TYPE_TPM, tpm,
|
|
_("TPM"), "device_cpu")
|
|
|
|
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 type(olddev) is str or olddev in currentDevices:
|
|
continue
|
|
|
|
hw_list_model.remove(_iter)
|
|
|
|
def repopulate_boot_list(self, bootdevs=None, dev_select=None):
|
|
boot_list = self.widget("config-boot-list")
|
|
boot_model = boot_list.get_model()
|
|
old_order = [x[BOOT_DEV_TYPE] for x in boot_model]
|
|
boot_model.clear()
|
|
|
|
if bootdevs is 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")
|
|
|
|
def show_pair(self, basename, show):
|
|
combo = self.widget(basename)
|
|
label = self.widget(basename + "-title")
|
|
|
|
combo.set_property("visible", show)
|
|
label.set_property("visible", show)
|