1602 lines
56 KiB
Python
1602 lines
56 KiB
Python
# Copyright (C) 2006-2007, 2012-2015 Red Hat, Inc.
|
|
# Copyright (C) 2006 Hugh O. Brock <hbrock@redhat.com>
|
|
#
|
|
# This work is licensed under the GNU GPLv2 or later.
|
|
# See the COPYING file in the top-level directory.
|
|
|
|
import traceback
|
|
|
|
from gi.repository import Gtk
|
|
|
|
from virtinst import (DeviceChannel, DeviceConsole,
|
|
DeviceController, DeviceDisk, DeviceHostdev,
|
|
DeviceInput, DeviceInterface, DevicePanic, DeviceParallel,
|
|
DeviceRedirdev, DeviceRng, DeviceSerial, DeviceSmartcard,
|
|
DeviceSound, DeviceVideo, DeviceVsock, DeviceWatchdog)
|
|
from virtinst import log
|
|
|
|
from .lib import uiutil
|
|
from .asyncjob import vmmAsyncJob
|
|
from .baseclass import vmmGObjectUI
|
|
from .device.addstorage import vmmAddStorage
|
|
from .device.fsdetails import vmmFSDetails
|
|
from .device.gfxdetails import vmmGraphicsDetails
|
|
from .device.netlist import vmmNetworkList
|
|
from .device.tpmdetails import vmmTPMDetails
|
|
from .device.vsockdetails import vmmVsockDetails
|
|
from .storagebrowse import vmmStorageBrowser
|
|
from .xmleditor import vmmXMLEditor
|
|
|
|
|
|
(PAGE_DISK,
|
|
PAGE_CONTROLLER,
|
|
PAGE_NETWORK,
|
|
PAGE_INPUT,
|
|
PAGE_GRAPHICS,
|
|
PAGE_SOUND,
|
|
PAGE_HOSTDEV,
|
|
PAGE_CHAR,
|
|
PAGE_VIDEO,
|
|
PAGE_WATCHDOG,
|
|
PAGE_FILESYSTEM,
|
|
PAGE_SMARTCARD,
|
|
PAGE_USBREDIR,
|
|
PAGE_TPM,
|
|
PAGE_RNG,
|
|
PAGE_PANIC,
|
|
PAGE_VSOCK) = range(17)
|
|
|
|
|
|
class vmmAddHardware(vmmGObjectUI):
|
|
def __init__(self, vm):
|
|
vmmGObjectUI.__init__(self, "addhardware.ui", "vmm-add-hardware")
|
|
|
|
self.vm = vm
|
|
self.conn = vm.conn
|
|
|
|
self._storagebrowser = None
|
|
|
|
self._remove_usb_controller = None
|
|
self._selected_model = None
|
|
|
|
self._gfxdetails = vmmGraphicsDetails(
|
|
self.vm, self.builder, self.topwin)
|
|
self.widget("graphics-align").add(self._gfxdetails.top_box)
|
|
|
|
self._fsdetails = vmmFSDetails(self.vm, self.builder, self.topwin)
|
|
self.widget("fs-box").add(self._fsdetails.top_box)
|
|
|
|
self._netlist = vmmNetworkList(self.conn, self.builder, self.topwin)
|
|
self.widget("network-source-label-align").add(self._netlist.top_label)
|
|
self.widget("network-source-ui-align").add(self._netlist.top_box)
|
|
|
|
self.addstorage = vmmAddStorage(self.conn, self.builder, self.topwin)
|
|
self.widget("storage-align").add(self.addstorage.top_box)
|
|
self.widget("storage-advanced-align").add(
|
|
self.addstorage.advanced_top_box)
|
|
self.addstorage.connect("browse-clicked", self._browse_storage_cb)
|
|
|
|
self._vsockdetails = vmmVsockDetails(self.vm, self.builder, self.topwin)
|
|
self.widget("vsock-align").add(self._vsockdetails.top_box)
|
|
|
|
self._tpmdetails = vmmTPMDetails(self.vm, self.builder, self.topwin)
|
|
self.widget("tpm-align").add(self._tpmdetails.top_box)
|
|
|
|
self._xmleditor = vmmXMLEditor(self.builder, self.topwin,
|
|
self.widget("create-pages-align"),
|
|
self.widget("create-pages"))
|
|
self._xmleditor.connect("xml-requested",
|
|
self._xmleditor_xml_requested_cb)
|
|
|
|
self.builder.connect_signals({
|
|
"on_create_cancel_clicked": self.close,
|
|
"on_vmm_create_delete_event": self.close,
|
|
"on_create_finish_clicked": self._finish,
|
|
"on_hw_list_changed": self._hw_selected_cb,
|
|
|
|
"on_storage_devtype_changed": self._change_storage_devtype,
|
|
"on_storage_bustype_changed": self._storage_bus_changed_cb,
|
|
|
|
"on_mac_address_clicked": self._change_macaddr_use,
|
|
|
|
"on_char_device_type_changed": self._change_char_device_type,
|
|
"on_char_target_name_changed": self._change_char_target_name,
|
|
"on_char_auto_socket_toggled": self._change_char_auto_socket,
|
|
|
|
"on_usbredir_type_changed": self._change_usbredir_type,
|
|
|
|
"on_controller_type_changed": self._change_controller_type,
|
|
})
|
|
self.bind_escape_key_close()
|
|
|
|
self._set_initial_state()
|
|
|
|
def show(self, parent):
|
|
log.debug("Showing addhw")
|
|
self._reset_state()
|
|
self.topwin.set_transient_for(parent)
|
|
self.topwin.present()
|
|
self.conn.schedule_priority_tick(pollnet=True,
|
|
pollpool=True,
|
|
pollnodedev=True)
|
|
|
|
def close(self, ignore1=None, ignore2=None):
|
|
if self.is_visible():
|
|
log.debug("Closing addhw")
|
|
self.topwin.hide()
|
|
if self._storagebrowser:
|
|
self._storagebrowser.close()
|
|
|
|
return 1
|
|
|
|
def _cleanup(self):
|
|
self.vm = None
|
|
self.conn = None
|
|
|
|
if self._storagebrowser:
|
|
self._storagebrowser.cleanup()
|
|
self._storagebrowser = None
|
|
|
|
self._gfxdetails.cleanup()
|
|
self._gfxdetails = None
|
|
self._fsdetails.cleanup()
|
|
self._fsdetails = None
|
|
self._netlist.cleanup()
|
|
self._netlist = None
|
|
self.addstorage.cleanup()
|
|
self.addstorage = None
|
|
self._vsockdetails.cleanup()
|
|
self._vsockdetails = None
|
|
self._tpmdetails.cleanup()
|
|
self._tpmdetails = None
|
|
self._xmleditor.cleanup()
|
|
self._xmleditor = None
|
|
|
|
|
|
##########################
|
|
# Initialization methods #
|
|
##########################
|
|
|
|
def _set_initial_state(self):
|
|
self.widget("create-pages").set_show_tabs(False)
|
|
self.widget("top-pages").set_show_tabs(False)
|
|
|
|
hw_col = Gtk.TreeViewColumn(_("Hardware"))
|
|
hw_col.set_spacing(6)
|
|
hw_col.set_min_width(165)
|
|
|
|
icon = Gtk.CellRendererPixbuf()
|
|
icon.set_property("stock-size", Gtk.IconSize.BUTTON)
|
|
text = Gtk.CellRendererText()
|
|
text.set_property("xpad", 6)
|
|
|
|
hw_col.pack_start(icon, False)
|
|
hw_col.pack_start(text, True)
|
|
hw_col.add_attribute(icon, 'icon-name', 1)
|
|
hw_col.add_attribute(text, 'text', 0)
|
|
hw_col.add_attribute(text, 'sensitive', 3)
|
|
self.widget("hw-list").append_column(hw_col)
|
|
|
|
# Individual HW page UI
|
|
self.build_disk_bus_combo(self.vm, self.widget("storage-bustype"))
|
|
self._build_disk_device_combo()
|
|
self.build_network_model_combo(self.vm, self.widget("net-model"))
|
|
self._build_input_combo()
|
|
self.build_sound_combo(self.vm, self.widget("sound-model"))
|
|
self._build_hostdev_treeview()
|
|
self.build_video_combo(self.vm, self.widget("video-model"))
|
|
uiutil.build_simple_combo(self.widget("char-device-type"), [])
|
|
self._build_char_target_type_combo()
|
|
self._build_char_target_name_combo()
|
|
self.build_watchdogmodel_combo(self.vm, self.widget("watchdog-model"))
|
|
self.build_watchdogaction_combo(self.vm, self.widget("watchdog-action"))
|
|
self.build_smartcard_mode_combo(self.vm, self.widget("smartcard-mode"))
|
|
self._build_redir_type_combo()
|
|
self._build_panic_model_combo()
|
|
uiutil.build_simple_combo(self.widget("controller-model"), [])
|
|
self._build_controller_type_combo()
|
|
|
|
|
|
# Available HW options
|
|
is_local = not self.conn.is_remote()
|
|
have_storage = (is_local or self.conn.support.conn_storage())
|
|
storage_tooltip = None
|
|
if not have_storage: # pragma: no cover
|
|
storage_tooltip = _("Connection does not support storage"
|
|
" management.")
|
|
|
|
# Name, icon name, page number, is sensitive, tooltip, icon size,
|
|
# device type (serial/parallel)...
|
|
model = Gtk.ListStore(str, str, int, bool, str, str)
|
|
self.widget("hw-list").set_model(model)
|
|
|
|
def add_hw_option(name, icon, page, sensitive, errortxt, devtype=None):
|
|
model.append([name, icon, page, sensitive, errortxt, devtype])
|
|
|
|
add_hw_option(_("Storage"), "drive-harddisk", PAGE_DISK, have_storage,
|
|
have_storage and storage_tooltip or None)
|
|
add_hw_option(_("Controller"), "device_pci", PAGE_CONTROLLER, True, None)
|
|
add_hw_option(_("Network"), "network-idle", PAGE_NETWORK, True, None)
|
|
add_hw_option(_("Input"), "input-mouse", PAGE_INPUT, self.vm.is_hvm(),
|
|
_("Not supported for this guest type."))
|
|
add_hw_option(_("Graphics"), "video-display", PAGE_GRAPHICS,
|
|
True, None)
|
|
add_hw_option(_("Sound"), "audio-card", PAGE_SOUND,
|
|
self.vm.is_hvm(),
|
|
_("Not supported for this guest type."))
|
|
add_hw_option(_("Serial"), "device_serial", PAGE_CHAR,
|
|
self.vm.is_hvm(),
|
|
_("Not supported for this guest type."),
|
|
"serial")
|
|
add_hw_option(_("Parallel"), "device_serial", PAGE_CHAR,
|
|
self.vm.is_hvm(),
|
|
_("Not supported for this guest type."),
|
|
"parallel")
|
|
add_hw_option(_("Console"), "device_serial", PAGE_CHAR,
|
|
True, None, "console")
|
|
add_hw_option(_("Channel"), "device_serial", PAGE_CHAR,
|
|
self.vm.is_hvm(),
|
|
_("Not supported for this guest type."),
|
|
"channel")
|
|
add_hw_option(_("USB Host Device"), "device_usb", PAGE_HOSTDEV,
|
|
self.conn.support.conn_nodedev(),
|
|
_("Connection does not support host device enumeration"),
|
|
"usb")
|
|
|
|
nodedev_enabled = self.conn.support.conn_nodedev()
|
|
nodedev_errstr = _("Connection does not support "
|
|
"host device enumeration")
|
|
if self.vm.is_container():
|
|
nodedev_enabled = False
|
|
nodedev_errstr = _("Not supported for containers")
|
|
add_hw_option(_("PCI Host Device"), "device_pci", PAGE_HOSTDEV,
|
|
nodedev_enabled, nodedev_errstr, "pci")
|
|
|
|
add_hw_option(_("MDEV Host Device"), "device_pci", PAGE_HOSTDEV,
|
|
self.conn.support.conn_nodedev(),
|
|
_("Connection does not support host device enumeration"),
|
|
"mdev")
|
|
add_hw_option(_("Video"), "video-display", PAGE_VIDEO, True,
|
|
_("Libvirt version does not support video devices."))
|
|
add_hw_option(_("Watchdog"), "device_pci", PAGE_WATCHDOG,
|
|
self.vm.is_hvm(),
|
|
_("Not supported for this guest type."))
|
|
add_hw_option(_("Filesystem"), "folder", PAGE_FILESYSTEM, True, None)
|
|
add_hw_option(_("Smartcard"), "device_serial", PAGE_SMARTCARD,
|
|
True, None)
|
|
add_hw_option(_("USB Redirection"), "device_usb", PAGE_USBREDIR,
|
|
True, None)
|
|
add_hw_option(_("TPM"), "device_cpu", PAGE_TPM,
|
|
True, None)
|
|
add_hw_option(_("RNG"), "system-run", PAGE_RNG, True, None)
|
|
add_hw_option(_("Panic Notifier"), "system-run", PAGE_PANIC,
|
|
bool(DevicePanic.get_models(self.vm.get_xmlobj())),
|
|
_("Not supported for this hypervisor/libvirt/arch combination."))
|
|
add_hw_option(_("VirtIO VSOCK"), "network-idle", PAGE_VSOCK,
|
|
self.vm.is_hvm(),
|
|
_("Not supported for this hypervisor/libvirt/arch combination."))
|
|
|
|
|
|
def _reset_state(self):
|
|
# Hide all notebook pages, otherwise the wizard window is as large
|
|
# as the largest page
|
|
for page in range(self.widget("create-pages").get_n_pages()):
|
|
widget = self.widget("create-pages").get_nth_page(page)
|
|
widget.hide()
|
|
|
|
self._set_hw_selection(0)
|
|
|
|
# Storage params
|
|
self.widget("storage-devtype").set_active(0)
|
|
self.widget("storage-devtype").emit("changed")
|
|
self.addstorage.reset_state()
|
|
|
|
|
|
# Network init
|
|
newmac = DeviceInterface.generate_mac(self.conn.get_backend())
|
|
self.widget("mac-address").set_active(bool(newmac))
|
|
self.widget("create-mac-address").set_text(newmac)
|
|
self._change_macaddr_use()
|
|
|
|
self._netlist.reset_state()
|
|
|
|
netmodel = self.widget("net-model")
|
|
self.populate_network_model_combo(self.vm, netmodel)
|
|
|
|
|
|
# Char parameters
|
|
self.widget("char-path").set_text("")
|
|
self.widget("char-channel").set_text("")
|
|
self.widget("char-auto-socket").set_active(True)
|
|
self.widget("char-vdagent-clipboard").set_active(True)
|
|
|
|
|
|
# RNG params
|
|
default_rng = "/dev/random"
|
|
if self.conn.support.conn_rng_urandom():
|
|
default_rng = "/dev/urandom"
|
|
self.widget("rng-device").set_text(default_rng)
|
|
|
|
|
|
# Remaining devices
|
|
self._fsdetails.reset_state()
|
|
self._gfxdetails.reset_state()
|
|
self._vsockdetails.reset_state()
|
|
self._tpmdetails.reset_state()
|
|
|
|
@staticmethod
|
|
def change_config_helper(define_func, define_args, vm, err,
|
|
devobj=None, hotplug_args=None):
|
|
"""
|
|
UI helper that handles the logic and reports errors for the
|
|
requested VM define and hotplug changes
|
|
|
|
Used here and in details.py
|
|
"""
|
|
hotplug_args = hotplug_args or {}
|
|
|
|
# Persistent config change
|
|
try:
|
|
if devobj:
|
|
# Device XML editing
|
|
define_func(devobj=devobj, do_hotplug=False, **define_args)
|
|
else:
|
|
# Guest XML editing
|
|
define_func(**define_args)
|
|
except Exception as e:
|
|
err.show_err((_("Error changing VM configuration: %s") %
|
|
str(e)))
|
|
return False
|
|
|
|
if not vm.is_active():
|
|
return True
|
|
|
|
# Hotplug change
|
|
hotplug_err = None
|
|
did_hotplug = False
|
|
try:
|
|
if devobj:
|
|
define_func(devobj=devobj, do_hotplug=True, **define_args)
|
|
did_hotplug = True
|
|
elif hotplug_args:
|
|
did_hotplug = True
|
|
vm.hotplug(**hotplug_args)
|
|
except Exception as e:
|
|
did_hotplug = True
|
|
log.debug("Hotplug failed: %s", str(e))
|
|
hotplug_err = ((str(e), "".join(traceback.format_exc())))
|
|
|
|
if did_hotplug and not hotplug_err:
|
|
return True
|
|
|
|
msg = _("These changes will take effect after "
|
|
"the next guest shutdown.")
|
|
dtype = (hotplug_err and
|
|
Gtk.MessageType.WARNING or Gtk.MessageType.INFO)
|
|
hotplug_msg = ""
|
|
if hotplug_err:
|
|
hotplug_msg += (hotplug_err[0] + "\n\n" +
|
|
hotplug_err[1] + "\n")
|
|
|
|
err.show_err(msg,
|
|
details=hotplug_msg,
|
|
buttons=Gtk.ButtonsType.OK,
|
|
dialog_type=dtype)
|
|
|
|
return True
|
|
|
|
|
|
#####################
|
|
# Pretty UI helpers #
|
|
#####################
|
|
|
|
@staticmethod
|
|
def char_recommended_types(char_class):
|
|
if char_class.XML_NAME == "console":
|
|
return [DeviceSerial.TYPE_PTY]
|
|
|
|
ret = [DeviceSerial.TYPE_PTY,
|
|
DeviceSerial.TYPE_FILE,
|
|
DeviceSerial.TYPE_UNIX]
|
|
if char_class.XML_NAME == "channel":
|
|
ret = [DeviceSerial.TYPE_SPICEVMC,
|
|
DeviceSerial.TYPE_SPICEPORT,
|
|
DeviceSerial.TYPE_QEMUVDAGENT] + ret
|
|
return ret
|
|
|
|
@staticmethod
|
|
def char_pretty_channel_name(val):
|
|
labels = {
|
|
DeviceChannel.CHANNEL_NAME_SPICE: "spice",
|
|
DeviceChannel.CHANNEL_NAME_QEMUGA: "qemu-ga",
|
|
DeviceChannel.CHANNEL_NAME_LIBGUESTFS: "libguestfs",
|
|
DeviceChannel.CHANNEL_NAME_SPICE_WEBDAV: "spice-webdav",
|
|
}
|
|
return labels.get(val, None)
|
|
|
|
@staticmethod
|
|
def char_pretty_type(val):
|
|
"""
|
|
Return a human readable description of the passed char type
|
|
"""
|
|
labels = {
|
|
DeviceSerial.TYPE_PTY: _("Pseudo TTY"),
|
|
DeviceSerial.TYPE_FILE: _("Output to a file"),
|
|
DeviceSerial.TYPE_TCP: _("TCP net console"),
|
|
DeviceSerial.TYPE_UDP: _("UDP net console"),
|
|
DeviceSerial.TYPE_UNIX: _("UNIX socket"),
|
|
DeviceSerial.TYPE_SPICEVMC: _("Spice agent"),
|
|
DeviceSerial.TYPE_SPICEPORT: _("Spice port"),
|
|
DeviceSerial.TYPE_QEMUVDAGENT: _("QEMU vdagent"),
|
|
}
|
|
return labels.get(val, val)
|
|
|
|
@staticmethod
|
|
def controller_recommended_types():
|
|
return [DeviceController.TYPE_SCSI,
|
|
DeviceController.TYPE_USB,
|
|
DeviceController.TYPE_VIRTIOSERIAL,
|
|
DeviceController.TYPE_CCID]
|
|
|
|
@staticmethod
|
|
def controller_pretty_type(val):
|
|
labels = {
|
|
DeviceController.TYPE_IDE: _("IDE"),
|
|
DeviceController.TYPE_FDC: _("Floppy"),
|
|
DeviceController.TYPE_SCSI: _("SCSI"),
|
|
DeviceController.TYPE_SATA: _("SATA"),
|
|
DeviceController.TYPE_VIRTIOSERIAL: _("VirtIO Serial"),
|
|
DeviceController.TYPE_USB: _("USB"),
|
|
DeviceController.TYPE_PCI: _("PCI"),
|
|
DeviceController.TYPE_CCID: _("CCID"),
|
|
DeviceController.TYPE_XENBUS: _("xenbus"),
|
|
}
|
|
return labels.get(val, val)
|
|
|
|
@staticmethod
|
|
def controller_pretty_desc(dev):
|
|
if dev.type == DeviceController.TYPE_SCSI:
|
|
if dev.model == "virtio-scsi":
|
|
return _("VirtIO SCSI")
|
|
if dev.type == DeviceController.TYPE_PCI:
|
|
if dev.model == "pcie-root":
|
|
return _("PCIe")
|
|
return vmmAddHardware.controller_pretty_type(dev.type)
|
|
|
|
@staticmethod
|
|
def disk_old_recommended_buses(guest):
|
|
ret = []
|
|
if guest.os.is_hvm() or guest.conn.is_test():
|
|
if not guest.os.is_q35():
|
|
ret.append("ide")
|
|
ret.append("sata")
|
|
ret.append("fdc")
|
|
ret.append("scsi")
|
|
ret.append("usb")
|
|
|
|
if guest.type in ["qemu", "kvm", "test"]:
|
|
ret.append("sd")
|
|
ret.append("virtio")
|
|
|
|
if guest.conn.is_xen() or guest.conn.is_test():
|
|
ret.append("xen")
|
|
|
|
return ret
|
|
|
|
@staticmethod
|
|
def disk_recommended_buses(guest, domcaps, devtype):
|
|
# try to get supported disk bus types from domain capabilities
|
|
if "bus" in domcaps.devices.disk.enum_names():
|
|
buses = domcaps.devices.disk.get_enum("bus").get_values()
|
|
else:
|
|
buses = vmmAddHardware.disk_old_recommended_buses(guest)
|
|
|
|
bus_map = {
|
|
"disk": ["ide", "sata", "scsi", "sd", "usb", "virtio", "xen"],
|
|
"floppy": ["fdc"],
|
|
"cdrom": ["ide", "sata", "scsi", "usb"],
|
|
"lun": ["scsi"],
|
|
}
|
|
return [bus for bus in buses if bus in bus_map.get(devtype, [])]
|
|
|
|
@staticmethod
|
|
def disk_pretty_bus(bus):
|
|
bus_mappings = {
|
|
"ide": _("IDE"),
|
|
"sata": _("SATA"),
|
|
"scsi": _("SCSI"),
|
|
"sd": _("SD"),
|
|
"usb": _("USB"),
|
|
"virtio": _("VirtIO"),
|
|
"xen": _("Xen"),
|
|
}
|
|
return bus_mappings.get(bus, bus)
|
|
|
|
@staticmethod
|
|
def panic_pretty_model(val):
|
|
labels = {
|
|
DevicePanic.MODEL_ISA: _("ISA"),
|
|
DevicePanic.MODEL_PSERIES: _("pSeries"),
|
|
DevicePanic.MODEL_HYPERV: _("Hyper-V"),
|
|
DevicePanic.MODEL_S390: _("s390"),
|
|
}
|
|
return labels.get(val, val)
|
|
|
|
@staticmethod
|
|
def rng_pretty_type(val):
|
|
labels = {
|
|
DeviceRng.TYPE_RANDOM: _("Random"),
|
|
DeviceRng.TYPE_EGD: _("Entropy Gathering Daemon"),
|
|
DeviceRng.TYPE_BUILTIN: _("Builtin RNG"),
|
|
}
|
|
return labels.get(val, val)
|
|
|
|
@staticmethod
|
|
def sound_recommended_models(_guest):
|
|
return ["ich6", "ich9", "ac97"]
|
|
|
|
@staticmethod
|
|
def sound_pretty_model(model):
|
|
ret = model.upper()
|
|
if model in ["ich6", "ich9"]:
|
|
ret = "HDA (%s)" % model.upper()
|
|
return ret
|
|
|
|
@staticmethod
|
|
def watchdog_pretty_action(val):
|
|
labels = {
|
|
DeviceWatchdog.ACTION_RESET: _("Forcefully reset the guest"),
|
|
DeviceWatchdog.ACTION_SHUTDOWN: _("Gracefully shutdown the guest"),
|
|
DeviceWatchdog.ACTION_POWEROFF: _("Forcefully power off the guest"),
|
|
DeviceWatchdog.ACTION_PAUSE: _("Pause the guest"),
|
|
DeviceWatchdog.ACTION_NONE: _("No action"),
|
|
DeviceWatchdog.ACTION_DUMP: _("Dump guest memory core"),
|
|
}
|
|
return labels.get(val, val)
|
|
|
|
@staticmethod
|
|
def input_pretty_name(typ, bus):
|
|
if typ == DeviceInput.TYPE_TABLET and bus == DeviceInput.BUS_USB:
|
|
return _("EvTouch USB Graphics Tablet")
|
|
|
|
typ_labels = {
|
|
DeviceInput.TYPE_KEYBOARD: _("Keyboard"),
|
|
DeviceInput.TYPE_MOUSE: _("Mouse"),
|
|
DeviceInput.TYPE_TABLET: _("Tablet"),
|
|
}
|
|
|
|
bus_labels = {
|
|
DeviceInput.BUS_PS2: _("PS/2"),
|
|
DeviceInput.BUS_USB: _("USB"),
|
|
DeviceInput.BUS_VIRTIO: _("VirtIO"),
|
|
DeviceInput.BUS_XEN: _("Xen"),
|
|
}
|
|
|
|
bus_label = bus_labels.get(bus, bus)
|
|
typ_label = typ_labels.get(typ, typ)
|
|
# translators: Examples: 'USB Mouse', 'PS/2 Keyboard'
|
|
ret = _("%(input_bus)s %(input_type)s") % {
|
|
"input_bus": bus_label, "input_type": typ_label}
|
|
return ret
|
|
|
|
@staticmethod
|
|
def interface_recommended_models(guest):
|
|
if not guest.os.is_hvm():
|
|
return []
|
|
|
|
ret = []
|
|
if guest.type in ["kvm", "qemu", "vz", "test"]:
|
|
ret.append("virtio")
|
|
if guest.os.is_x86():
|
|
if guest.os.is_q35():
|
|
ret.append("e1000e")
|
|
else:
|
|
ret.append("rtl8139")
|
|
ret.append("e1000")
|
|
if guest.type in ["xen", "test"]:
|
|
ret.append("netfront")
|
|
|
|
ret.sort()
|
|
return ret
|
|
|
|
@staticmethod
|
|
def redirdev_pretty_type(typ):
|
|
if typ == "tcp":
|
|
return "TCP"
|
|
if typ == "spicevmc":
|
|
return "SpiceVMC"
|
|
return typ and typ.capitalize()
|
|
|
|
@staticmethod
|
|
def video_recommended_models(guest):
|
|
if guest.conn.is_xen():
|
|
return ["xen", "vga"]
|
|
if guest.conn.is_qemu() or guest.conn.is_test():
|
|
return ["vga", "bochs", "qxl", "virtio", "ramfb", "none"]
|
|
return []
|
|
|
|
@staticmethod
|
|
def video_pretty_model(model):
|
|
if model in ["qxl", "vmvga", "vga"]:
|
|
return model.upper()
|
|
return model.capitalize()
|
|
|
|
@staticmethod
|
|
def hostdev_pretty_name(hostdev):
|
|
def dehex(val):
|
|
if val.startswith("0x"):
|
|
val = val[2:]
|
|
return val
|
|
|
|
def safeint(val, fmt="%.3d"):
|
|
try:
|
|
int(val)
|
|
except Exception: # pragma: no cover
|
|
return str(val)
|
|
return fmt % int(val)
|
|
|
|
label = hostdev.type.upper()
|
|
|
|
if hostdev.vendor and hostdev.product:
|
|
label += " %s:%s" % (dehex(hostdev.vendor), dehex(hostdev.product))
|
|
|
|
elif hostdev.bus and hostdev.device:
|
|
label += " %s:%s" % (safeint(hostdev.bus), safeint(hostdev.device))
|
|
|
|
elif (hostdev.bus and hostdev.slot and
|
|
hostdev.function and hostdev.domain):
|
|
label += (" %s:%s:%s.%s" %
|
|
(dehex(hostdev.domain), dehex(hostdev.bus),
|
|
dehex(hostdev.slot), dehex(hostdev.function)))
|
|
|
|
elif hostdev.uuid:
|
|
label += " %s" % (str(hostdev.uuid))
|
|
|
|
return label
|
|
|
|
|
|
#########################
|
|
# UI init/reset helpers #
|
|
#########################
|
|
|
|
def _build_disk_device_combo(self):
|
|
target_list = self.widget("storage-devtype")
|
|
# [device, icon, label]
|
|
target_model = Gtk.ListStore(str, str, str)
|
|
target_list.set_model(target_model)
|
|
icon = Gtk.CellRendererPixbuf()
|
|
icon.set_property("stock-size", Gtk.IconSize.BUTTON)
|
|
target_list.pack_start(icon, False)
|
|
target_list.add_attribute(icon, 'icon-name', 1)
|
|
text = Gtk.CellRendererText()
|
|
text.set_property("xpad", 6)
|
|
target_list.pack_start(text, True)
|
|
target_list.add_attribute(text, 'text', 2)
|
|
target_model.append([DeviceDisk.DEVICE_DISK,
|
|
"drive-harddisk", _("Disk device")])
|
|
target_model.append([DeviceDisk.DEVICE_CDROM,
|
|
"media-optical", _("CDROM device")])
|
|
target_model.append([DeviceDisk.DEVICE_FLOPPY,
|
|
"media-floppy", _("Floppy device")])
|
|
if self.conn.is_qemu() or self.conn.is_test():
|
|
target_model.append([DeviceDisk.DEVICE_LUN,
|
|
"drive-harddisk", _("LUN Passthrough")])
|
|
target_list.set_active(0)
|
|
|
|
@staticmethod
|
|
def build_disk_bus_combo(_vm, combo):
|
|
uiutil.build_simple_combo(combo, [])
|
|
|
|
@staticmethod
|
|
def populate_disk_bus_combo(vm, devtype, model):
|
|
domcaps = vm.get_domain_capabilities()
|
|
buses = vmmAddHardware.disk_recommended_buses(vm.xmlobj, domcaps, devtype)
|
|
|
|
model.clear()
|
|
for bus in buses:
|
|
model.append([bus, vmmAddHardware.disk_pretty_bus(bus)])
|
|
|
|
|
|
@staticmethod
|
|
def populate_network_model_combo(vm, combo):
|
|
model = combo.get_model()
|
|
model.clear()
|
|
|
|
# [xml value, label]
|
|
model.append([None, _("Hypervisor default")])
|
|
for netmodel in vmmAddHardware.interface_recommended_models(vm.xmlobj):
|
|
model.append([netmodel, netmodel])
|
|
|
|
uiutil.set_list_selection(
|
|
combo, DeviceInterface.default_model(vm.xmlobj))
|
|
|
|
@staticmethod
|
|
def build_network_model_combo(vm, combo):
|
|
uiutil.build_simple_combo(combo, [])
|
|
vmmAddHardware.populate_network_model_combo(vm, combo)
|
|
|
|
|
|
def _build_input_combo(self):
|
|
devices = [
|
|
(DeviceInput.TYPE_TABLET, DeviceInput.BUS_USB),
|
|
(DeviceInput.TYPE_MOUSE, DeviceInput.BUS_USB),
|
|
(DeviceInput.TYPE_KEYBOARD, DeviceInput.BUS_USB),
|
|
(DeviceInput.TYPE_KEYBOARD, DeviceInput.BUS_VIRTIO),
|
|
(DeviceInput.TYPE_TABLET, DeviceInput.BUS_VIRTIO),
|
|
]
|
|
|
|
cvals = [((t, b), vmmAddHardware.input_pretty_name(t, b))
|
|
for t, b in devices]
|
|
uiutil.build_simple_combo(self.widget("input-type"), cvals)
|
|
|
|
|
|
@staticmethod
|
|
def build_sound_combo(vm, combo):
|
|
values = []
|
|
for m in vmmAddHardware.sound_recommended_models(vm.xmlobj):
|
|
values.append([m, vmmAddHardware.sound_pretty_model(m)])
|
|
|
|
default = DeviceSound.default_model(vm.xmlobj)
|
|
uiutil.build_simple_combo(combo, values, default_value=default)
|
|
|
|
|
|
def _build_hostdev_treeview(self):
|
|
host_dev = self.widget("host-device")
|
|
# [ xmlobj, label, sensitive, tooltip]
|
|
host_dev_model = Gtk.ListStore(object, str, bool, str)
|
|
host_dev.set_model(host_dev_model)
|
|
host_col = Gtk.TreeViewColumn()
|
|
text = Gtk.CellRendererText()
|
|
host_col.pack_start(text, True)
|
|
host_col.add_attribute(text, 'text', 1)
|
|
host_col.add_attribute(text, 'sensitive', 2)
|
|
host_dev.set_tooltip_column(3)
|
|
host_dev_model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
|
|
host_dev.append_column(host_col)
|
|
|
|
|
|
def _hostdev_row_selected_cb(self, selection):
|
|
model, treeiter = selection.get_selected()
|
|
sensitive = treeiter and model[treeiter][2] or False
|
|
self.widget("create-finish").set_sensitive(sensitive)
|
|
|
|
|
|
def _populate_hostdev_model(self, devtype):
|
|
devlist = self.widget("host-device")
|
|
model = devlist.get_model()
|
|
model.clear()
|
|
|
|
devs = self.conn.filter_nodedevs(devtype)
|
|
netdevs = self.conn.filter_nodedevs("net")
|
|
for dev in devs:
|
|
if dev.xmlobj.is_usb_linux_root_hub():
|
|
continue
|
|
if dev.xmlobj.is_pci_bridge():
|
|
continue
|
|
prettyname = dev.pretty_name()
|
|
|
|
if devtype == "pci":
|
|
for subdev in netdevs:
|
|
if dev.xmlobj.name == subdev.xmlobj.parent:
|
|
prettyname += " (%s)" % subdev.pretty_name()
|
|
|
|
# parent device names are appended with mdev names in
|
|
# libvirt 7.8.0
|
|
if devtype == "mdev" and len(prettyname) <= 41:
|
|
for parentdev in self.conn.list_nodedevs():
|
|
if dev.xmlobj.parent == parentdev.xmlobj.name:
|
|
prettyname = "%s %s" % (
|
|
parentdev.pretty_name(), prettyname)
|
|
|
|
tooltip = None
|
|
sensitive = dev.is_active()
|
|
if not sensitive:
|
|
tooltip = _("%s is not active in the host system.\n"
|
|
"Please start the mdev in the host system before "
|
|
"adding it to the guest.") % prettyname
|
|
model.append([dev.xmlobj, prettyname, sensitive, tooltip])
|
|
|
|
if len(model) == 0:
|
|
model.append([None, _("No Devices Available"), False, None])
|
|
|
|
uiutil.set_list_selection_by_number(devlist, 0)
|
|
|
|
devlist.get_selection().connect(
|
|
"changed", self._hostdev_row_selected_cb)
|
|
devlist.get_selection().emit("changed")
|
|
|
|
|
|
@staticmethod
|
|
def build_video_combo(vm, combo):
|
|
values = []
|
|
for m in vmmAddHardware.video_recommended_models(vm.xmlobj):
|
|
values.append([m, vmmAddHardware.video_pretty_model(m)])
|
|
if not values:
|
|
values.append([None, _("Hypervisor default")])
|
|
default = DeviceVideo.default_model(vm.xmlobj)
|
|
uiutil.build_simple_combo(combo, values, default_value=default)
|
|
|
|
|
|
def _build_char_target_type_combo(self):
|
|
values = []
|
|
if self.conn.is_qemu():
|
|
values.append(["virtio", "VirtIO"])
|
|
else:
|
|
values.append([None, _("Hypervisor default")])
|
|
uiutil.build_simple_combo(self.widget("char-target-type"), values)
|
|
|
|
def _build_char_target_name_combo(self):
|
|
values = []
|
|
for n in DeviceChannel.CHANNEL_NAMES:
|
|
values.append([n, n])
|
|
uiutil.build_simple_combo(self.widget("char-target-name"), values)
|
|
|
|
def _populate_char_device_type_combo(self):
|
|
char_class = self._get_char_class()
|
|
model = self.widget("char-device-type").get_model()
|
|
model.clear()
|
|
|
|
for t in vmmAddHardware.char_recommended_types(char_class):
|
|
model.append([t, vmmAddHardware.char_pretty_type(t) + " (%s)" % t])
|
|
uiutil.set_list_selection(self.widget("char-device-type"), "pty")
|
|
|
|
|
|
@staticmethod
|
|
def build_watchdogmodel_combo(_vm, combo):
|
|
values = []
|
|
for m in DeviceWatchdog.MODELS:
|
|
values.append([m, m.upper()])
|
|
uiutil.build_simple_combo(combo, values, default_value=DeviceWatchdog.MODEL_I6300)
|
|
|
|
@staticmethod
|
|
def build_watchdogaction_combo(_vm, combo):
|
|
values = []
|
|
for m in DeviceWatchdog.ACTIONS:
|
|
values.append([m, vmmAddHardware.watchdog_pretty_action(m)])
|
|
uiutil.build_simple_combo(combo, values, default_value=DeviceWatchdog.ACTION_RESET)
|
|
|
|
|
|
@staticmethod
|
|
def build_smartcard_mode_combo(_vm, combo):
|
|
values = [
|
|
["passthrough", _("Passthrough")],
|
|
["host", _("Host")],
|
|
]
|
|
uiutil.build_simple_combo(combo, values)
|
|
|
|
|
|
def _build_redir_type_combo(self):
|
|
values = [["spicevmc", _("Spice channel")]]
|
|
uiutil.build_simple_combo(self.widget("usbredir-list"), values)
|
|
|
|
|
|
def _build_panic_model_combo(self):
|
|
values = []
|
|
for m in DevicePanic.get_models(self.vm.get_xmlobj()):
|
|
values.append([m, vmmAddHardware.panic_pretty_model(m)])
|
|
|
|
default = DevicePanic.get_default_model(self.vm.get_xmlobj())
|
|
uiutil.build_simple_combo(self.widget("panic-model"), values, default_value=default)
|
|
|
|
|
|
def _build_controller_type_combo(self):
|
|
values = []
|
|
for t in vmmAddHardware.controller_recommended_types():
|
|
values.append([t, vmmAddHardware.controller_pretty_type(t)])
|
|
|
|
uiutil.build_simple_combo(self.widget("controller-type"), values,
|
|
default_value=DeviceController.TYPE_SCSI)
|
|
|
|
@staticmethod
|
|
def populate_controller_model_combo(combo, controller_type):
|
|
model = combo.get_model()
|
|
model.clear()
|
|
|
|
rows = []
|
|
if controller_type == DeviceController.TYPE_USB:
|
|
rows.append(["usb3", _("USB 3")])
|
|
rows.append(["ich9-ehci1", _("USB 2")])
|
|
elif controller_type == DeviceController.TYPE_SCSI:
|
|
rows.append(["virtio-scsi", _("VirtIO SCSI")])
|
|
rows.append([None, _("Hypervisor default")])
|
|
|
|
for row in rows:
|
|
model.append(row)
|
|
uiutil.set_list_selection(combo, rows[0][0])
|
|
|
|
|
|
|
|
#########################
|
|
# Internal misc helpers #
|
|
#########################
|
|
|
|
def _get_char_class(self):
|
|
row = self._get_hw_selection()
|
|
label = "serial"
|
|
|
|
if row:
|
|
label = row[5]
|
|
|
|
if label == "parallel":
|
|
return DeviceParallel
|
|
elif label == "channel":
|
|
return DeviceChannel
|
|
elif label == "console":
|
|
return DeviceConsole
|
|
return DeviceSerial
|
|
|
|
def _set_hw_selection(self, page):
|
|
uiutil.set_list_selection_by_number(self.widget("hw-list"), page)
|
|
|
|
def _get_hw_selection(self):
|
|
return uiutil.get_list_selected_row(self.widget("hw-list"))
|
|
|
|
def _set_error_page(self, msg=None):
|
|
self.widget("top-pages").set_current_page(1)
|
|
self.widget("error-label").set_text(msg or "Hardware selection error.")
|
|
self.widget("create-finish").set_sensitive(False)
|
|
|
|
|
|
################
|
|
# UI listeners #
|
|
################
|
|
|
|
def _hw_selected_cb(self, src):
|
|
self.widget("create-finish").set_sensitive(True)
|
|
self._xmleditor.reset_state()
|
|
|
|
row = self._get_hw_selection()
|
|
if not row or not row[3]:
|
|
self._set_error_page(row and row[4] or None)
|
|
return
|
|
|
|
page = row[2]
|
|
|
|
if page == PAGE_CHAR:
|
|
# Need to do this here, since we share the char page between
|
|
# multiple different HW options
|
|
self._populate_char_device_type_combo()
|
|
self.widget("char-device-type").emit("changed")
|
|
self.widget("char-target-name").emit("changed")
|
|
|
|
if page == PAGE_HOSTDEV:
|
|
# Need to do this here, since we share the hostdev page
|
|
# between different HW options
|
|
row = self._get_hw_selection()
|
|
devtype = "usb_device"
|
|
if row and row[5] == "pci":
|
|
devtype = "pci"
|
|
if row and row[5] == "mdev":
|
|
devtype = "mdev"
|
|
self._populate_hostdev_model(devtype)
|
|
|
|
if page == PAGE_CONTROLLER:
|
|
# We need to trigger this as it can desensitive 'finish'
|
|
self.widget("controller-type").emit("changed")
|
|
|
|
self._set_page_title(page)
|
|
self.widget("create-pages").get_nth_page(page).show()
|
|
self.widget("create-pages").set_current_page(page)
|
|
self.widget("top-pages").set_current_page(0)
|
|
|
|
def _dev_to_title(self, page):
|
|
if page == PAGE_DISK:
|
|
return _("Storage")
|
|
if page == PAGE_CONTROLLER:
|
|
return _("Controller")
|
|
if page == PAGE_NETWORK:
|
|
return _("Network")
|
|
if page == PAGE_INPUT:
|
|
return _("Input")
|
|
if page == PAGE_GRAPHICS:
|
|
return _("Graphics")
|
|
if page == PAGE_SOUND:
|
|
return _("Sound")
|
|
if page == PAGE_VIDEO:
|
|
return _("Video Device")
|
|
if page == PAGE_WATCHDOG:
|
|
return _("Watchdog Device")
|
|
if page == PAGE_FILESYSTEM:
|
|
return _("Filesystem Passthrough")
|
|
if page == PAGE_SMARTCARD:
|
|
return _("Smartcard")
|
|
if page == PAGE_USBREDIR:
|
|
return _("USB Redirection")
|
|
if page == PAGE_TPM:
|
|
return _("TPM")
|
|
if page == PAGE_RNG:
|
|
return _("Random Number Generator")
|
|
if page == PAGE_PANIC:
|
|
return _("Panic Notifier")
|
|
if page == PAGE_VSOCK:
|
|
return _("VM Sockets")
|
|
|
|
if page == PAGE_CHAR:
|
|
devclass = self._get_char_class()(self.conn.get_backend())
|
|
return _("%s Device") % devclass.DEVICE_TYPE.capitalize()
|
|
if page == PAGE_HOSTDEV:
|
|
row = self._get_hw_selection()
|
|
if row and row[5] == "pci":
|
|
return _("PCI Device")
|
|
if row and row[5] == "mdev":
|
|
return _("MDEV Device")
|
|
return _("USB Device")
|
|
|
|
raise RuntimeError("Unknown page %s" % page) # pragma: no cover
|
|
|
|
def _set_page_title(self, page):
|
|
title = self._dev_to_title(page)
|
|
self.widget("page-title-label").set_markup(title)
|
|
|
|
def _xmleditor_xml_requested_cb(self, src):
|
|
dev = self._build_device(check_xmleditor=False)
|
|
self._xmleditor.set_xml(dev and dev.get_xml() or "")
|
|
|
|
|
|
#########################
|
|
# Device page listeners #
|
|
#########################
|
|
|
|
def _refresh_disk_bus(self, devtype):
|
|
widget = self.widget("storage-bustype")
|
|
model = widget.get_model()
|
|
self.populate_disk_bus_combo(self.vm, devtype, model)
|
|
|
|
# By default, select bus of the first disk assigned to the VM
|
|
default_bus = None
|
|
for i in self.vm.xmlobj.devices.disk:
|
|
if i.device == devtype:
|
|
default_bus = i.bus
|
|
break
|
|
|
|
if default_bus:
|
|
uiutil.set_list_selection(widget, default_bus)
|
|
elif len(model) > 0:
|
|
widget.set_active(0)
|
|
|
|
def _change_storage_devtype(self, ignore):
|
|
devtype = uiutil.get_list_selection(
|
|
self.widget("storage-devtype"))
|
|
self._refresh_disk_bus(devtype)
|
|
|
|
allow_create = devtype not in ["cdrom", "floppy"]
|
|
self.addstorage.widget("storage-create-box").set_sensitive(
|
|
allow_create)
|
|
if not allow_create:
|
|
self.addstorage.widget("storage-select").set_active(True)
|
|
|
|
def _storage_bus_changed_cb(self, src):
|
|
bus = uiutil.get_list_selection(
|
|
self.widget("storage-bustype"))
|
|
self.addstorage.set_disk_bus(bus)
|
|
|
|
def _change_macaddr_use(self, ignore=None):
|
|
if self.widget("mac-address").get_active():
|
|
self.widget("create-mac-address").set_sensitive(True)
|
|
else:
|
|
self.widget("create-mac-address").set_sensitive(False)
|
|
|
|
def _change_char_auto_socket(self, src):
|
|
if not src.get_visible():
|
|
return
|
|
|
|
doshow = not src.get_active()
|
|
uiutil.set_grid_row_visible(self.widget("char-path-label"), doshow)
|
|
|
|
def _change_char_target_name(self, src):
|
|
if not src.get_visible():
|
|
return
|
|
|
|
text = src.get_child().get_text()
|
|
settype = None
|
|
if text == DeviceChannel.CHANNEL_NAME_SPICE:
|
|
settype = "spicevmc"
|
|
elif text == DeviceChannel.CHANNEL_NAME_SPICE_WEBDAV:
|
|
settype = "spiceport"
|
|
self.widget("char-channel").set_text(text)
|
|
elif (text == DeviceChannel.CHANNEL_NAME_QEMUGA or
|
|
text == DeviceChannel.CHANNEL_NAME_LIBGUESTFS):
|
|
settype = "unix"
|
|
if settype:
|
|
uiutil.set_list_selection(
|
|
self.widget("char-device-type"), settype)
|
|
|
|
def _change_char_device_type(self, src):
|
|
devtype = uiutil.get_list_selection(src)
|
|
if devtype is None:
|
|
return
|
|
|
|
char_class = self._get_char_class()
|
|
dev = char_class(self.conn.get_backend())
|
|
dev.type = devtype
|
|
|
|
ischan = dev.DEVICE_TYPE == "channel"
|
|
iscon = dev.DEVICE_TYPE == "console"
|
|
show_auto = devtype == "unix" and ischan
|
|
|
|
supports_path = [dev.TYPE_FILE, dev.TYPE_UNIX,
|
|
dev.TYPE_DEV, dev.TYPE_PIPE]
|
|
supports_channel = [dev.TYPE_SPICEPORT]
|
|
supports_clipboard = [dev.TYPE_QEMUVDAGENT]
|
|
|
|
uiutil.set_grid_row_visible(self.widget("char-path-label"),
|
|
devtype in supports_path)
|
|
uiutil.set_grid_row_visible(self.widget("char-channel-label"),
|
|
devtype in supports_channel)
|
|
uiutil.set_grid_row_visible(self.widget("char-vdagent-clipboard-label"),
|
|
devtype in supports_clipboard)
|
|
|
|
uiutil.set_grid_row_visible(
|
|
self.widget("char-target-name-label"), ischan)
|
|
uiutil.set_grid_row_visible(
|
|
self.widget("char-target-type-label"), iscon)
|
|
uiutil.set_grid_row_visible(
|
|
self.widget("char-auto-socket-label"), show_auto)
|
|
self.widget("char-auto-socket").emit("toggled")
|
|
|
|
def _change_usbredir_type(self, src):
|
|
pass
|
|
|
|
def _change_controller_type(self, src):
|
|
ignore = src
|
|
combo = self.widget("controller-model")
|
|
|
|
def show_tooltip(model_tooltip, show):
|
|
vmname = self.vm.get_name()
|
|
tooltip = (_("%s already has a USB controller attached.\n"
|
|
"Adding more than one USB controller is not supported.\n"
|
|
"You can change the USB controller type in the VM details screen.")
|
|
% vmname)
|
|
model_tooltip.set_visible(show)
|
|
model_tooltip.set_tooltip_text(tooltip)
|
|
|
|
controller_type = uiutil.get_list_selection(
|
|
self.widget("controller-type"))
|
|
combo.set_sensitive(True)
|
|
model_tooltip = self.widget("controller-tooltip")
|
|
show_tooltip(model_tooltip, False)
|
|
|
|
controllers = self.vm.xmlobj.devices.controller
|
|
if controller_type == DeviceController.TYPE_USB:
|
|
usb_controllers = [x for x in controllers if
|
|
(x.type == DeviceController.TYPE_USB)]
|
|
if (len(usb_controllers) == 0):
|
|
self.widget("create-finish").set_sensitive(True)
|
|
elif (len(usb_controllers) == 1 and
|
|
usb_controllers[0].model == "none"):
|
|
self._remove_usb_controller = usb_controllers[0]
|
|
self.widget("create-finish").set_sensitive(True)
|
|
else:
|
|
show_tooltip(model_tooltip, True)
|
|
self.widget("create-finish").set_sensitive(False)
|
|
else:
|
|
self.widget("create-finish").set_sensitive(True)
|
|
|
|
self.populate_controller_model_combo(combo, controller_type)
|
|
uiutil.set_grid_row_visible(combo, len(combo.get_model()) > 1)
|
|
|
|
|
|
######################
|
|
# Add device methods #
|
|
######################
|
|
|
|
def _setup_device(self, asyncjob, dev):
|
|
if dev.DEVICE_TYPE != "disk":
|
|
return
|
|
|
|
poolname = None
|
|
if (dev.wants_storage_creation() and
|
|
dev.get_parent_pool()):
|
|
poolname = dev.get_parent_pool().name()
|
|
|
|
log.debug("Running build_storage() for device=%s", dev)
|
|
dev.build_storage(meter=asyncjob.get_meter())
|
|
log.debug("build_storage() complete")
|
|
|
|
if poolname:
|
|
try:
|
|
pool = self.conn.get_pool_by_name(poolname)
|
|
self.idle_add(pool.refresh)
|
|
except Exception: # pragma: no cover
|
|
log.debug("Error looking up pool=%s for refresh after "
|
|
"storage creation.", poolname, exc_info=True)
|
|
|
|
|
|
def _add_device(self, dev):
|
|
xml = dev.get_xml()
|
|
log.debug("Adding device:\n%s", xml)
|
|
|
|
if self._remove_usb_controller:
|
|
kwargs = {}
|
|
kwargs["model"] = self._selected_model
|
|
|
|
self.change_config_helper(self.vm.define_controller,
|
|
kwargs, self.vm, self.err,
|
|
devobj=self._remove_usb_controller)
|
|
|
|
self._remove_usb_controller = None
|
|
self._selected_model = None
|
|
|
|
return
|
|
|
|
controller = getattr(dev, "vmm_controller", None)
|
|
if controller is not None:
|
|
log.debug("Adding controller:\n%s",
|
|
controller.get_xml())
|
|
# Hotplug device
|
|
attach_err = False
|
|
try:
|
|
if controller is not None:
|
|
self.vm.attach_device(controller)
|
|
self.vm.attach_device(dev)
|
|
except Exception as e:
|
|
log.debug("Device could not be hotplugged: %s", str(e))
|
|
attach_err = (str(e), "".join(traceback.format_exc()))
|
|
|
|
if attach_err:
|
|
res = self.err.show_err(
|
|
_("Are you sure you want to add this device?"),
|
|
details=(attach_err[0] + "\n\n" + attach_err[1]),
|
|
text2=(
|
|
_("This device could not be attached to the running machine. "
|
|
"Would you like to make the device available after the "
|
|
"next guest shutdown?")),
|
|
dialog_type=Gtk.MessageType.WARNING,
|
|
buttons=Gtk.ButtonsType.YES_NO,
|
|
modal=True)
|
|
|
|
if not res:
|
|
return False
|
|
|
|
# Alter persistent config
|
|
if controller is not None:
|
|
self.vm.add_device(controller)
|
|
self.vm.add_device(dev)
|
|
|
|
return False
|
|
|
|
def _finish_cb(self, error, details, dev):
|
|
failure = True
|
|
if not error:
|
|
try:
|
|
failure = self._add_device(dev)
|
|
except Exception as e:
|
|
failure = True
|
|
error = _("Unable to add device: %s") % str(e)
|
|
details = "".join(traceback.format_exc())
|
|
|
|
if error is not None:
|
|
self.err.show_err(error, details=details)
|
|
|
|
self.reset_finish_cursor()
|
|
|
|
if not failure:
|
|
self.close()
|
|
|
|
def _finish(self, ignore=None):
|
|
dev = self._build_device(check_xmleditor=True)
|
|
if not dev:
|
|
return
|
|
|
|
try:
|
|
if self._validate_device(dev) is False:
|
|
return
|
|
except Exception as e:
|
|
self.err.show_err(
|
|
_("Error validating device parameters: %s") % str(e))
|
|
return
|
|
|
|
self.set_finish_cursor()
|
|
progWin = vmmAsyncJob(self._setup_device, [dev],
|
|
self._finish_cb, [dev],
|
|
_("Creating device"),
|
|
_("Depending on the device, this may take "
|
|
"a few minutes to complete."),
|
|
self.topwin)
|
|
progWin.run()
|
|
|
|
|
|
###########################
|
|
# Device build/validation #
|
|
###########################
|
|
|
|
def _validate_hostdev_collision(self, dev):
|
|
names = []
|
|
nodedev = getattr(dev, "vmm_nodedev", None)
|
|
if not nodedev:
|
|
return # pragma: no cover
|
|
|
|
for vm in self.conn.list_vms():
|
|
for hostdev in vm.xmlobj.devices.hostdev:
|
|
if nodedev.compare_to_hostdev(hostdev):
|
|
names.append(vm.get_name())
|
|
if names:
|
|
res = self.err.yes_no(
|
|
_('The device is already in use by other guests %s') %
|
|
(names),
|
|
_("Do you really want to use the device?"))
|
|
if not res:
|
|
return False
|
|
|
|
def _validate_device(self, dev):
|
|
if dev.DEVICE_TYPE == "disk":
|
|
if self.addstorage.validate_device(dev) is False:
|
|
return False
|
|
|
|
if dev.DEVICE_TYPE == "interface":
|
|
self._netlist.validate_device(dev)
|
|
|
|
if dev.DEVICE_TYPE == "hostdev":
|
|
if self._validate_hostdev_collision(dev) is False:
|
|
return False
|
|
|
|
dev.validate()
|
|
|
|
def _build_xmleditor_device(self, srcdev):
|
|
xml = self._xmleditor.get_xml()
|
|
log.debug("Using XML from xmleditor:\n%s", xml)
|
|
devclass = srcdev.__class__
|
|
dev = devclass(srcdev.conn, parsexml=xml)
|
|
|
|
if srcdev.DEVICE_TYPE == "disk":
|
|
if (srcdev.get_source_path() == dev.get_source_path() and
|
|
srcdev.get_vol_install()):
|
|
dev.set_vol_install(srcdev.get_vol_install())
|
|
elif dev.get_source_path():
|
|
# Needed to convince disk.validate() to validate a passed path
|
|
dev.set_backend_for_existing_path()
|
|
|
|
return dev
|
|
|
|
def _build_device(self, check_xmleditor):
|
|
page_num = self.widget("create-pages").get_current_page()
|
|
try:
|
|
dev = self._build_device_page(page_num)
|
|
|
|
if check_xmleditor and self._xmleditor.is_xml_selected():
|
|
dev = self._build_xmleditor_device(dev)
|
|
|
|
return dev
|
|
except Exception as e:
|
|
self.err.show_err(
|
|
_("Error building device XML: %s") % str(e))
|
|
return
|
|
|
|
def _build_device_page(self, page_num):
|
|
# pylint: disable=assignment-from-no-return
|
|
if page_num == PAGE_DISK:
|
|
dev = self._build_storage()
|
|
elif page_num == PAGE_CONTROLLER:
|
|
dev = self._build_controller()
|
|
elif page_num == PAGE_NETWORK:
|
|
dev = self._build_network()
|
|
elif page_num == PAGE_INPUT:
|
|
dev = self._build_input()
|
|
elif page_num == PAGE_GRAPHICS:
|
|
dev = self._build_graphics()
|
|
elif page_num == PAGE_SOUND:
|
|
dev = self._build_sound()
|
|
elif page_num == PAGE_HOSTDEV:
|
|
dev = self._build_hostdev()
|
|
elif page_num == PAGE_CHAR:
|
|
dev = self._build_char()
|
|
elif page_num == PAGE_VIDEO:
|
|
dev = self._build_video()
|
|
elif page_num == PAGE_WATCHDOG:
|
|
dev = self._build_watchdog()
|
|
elif page_num == PAGE_FILESYSTEM:
|
|
dev = self._build_filesystem()
|
|
elif page_num == PAGE_SMARTCARD:
|
|
dev = self._build_smartcard()
|
|
elif page_num == PAGE_USBREDIR:
|
|
dev = self._build_usbredir()
|
|
elif page_num == PAGE_TPM:
|
|
dev = self._build_tpm()
|
|
elif page_num == PAGE_RNG:
|
|
dev = self._build_rng()
|
|
elif page_num == PAGE_PANIC:
|
|
dev = self._build_panic()
|
|
elif page_num == PAGE_VSOCK:
|
|
dev = self._build_vsock()
|
|
|
|
dev.set_defaults(self.vm.get_xmlobj())
|
|
return dev
|
|
|
|
def _set_disk_controller(self, disk):
|
|
# Add a SCSI controller with model virtio-scsi if needed
|
|
disk.vmm_controller = None
|
|
if not self.vm.xmlobj.can_default_virtioscsi():
|
|
return
|
|
|
|
controller = DeviceController(self.conn.get_backend())
|
|
controller.type = "scsi"
|
|
controller.model = "virtio-scsi"
|
|
controller.index = 0
|
|
disk.vmm_controller = controller
|
|
|
|
def _build_storage(self):
|
|
bus = uiutil.get_list_selection(
|
|
self.widget("storage-bustype"))
|
|
device = uiutil.get_list_selection(
|
|
self.widget("storage-devtype"))
|
|
|
|
disk = self.addstorage.build_device(self.vm.get_name(),
|
|
collideguest=self.vm.xmlobj, device=device)
|
|
|
|
used = []
|
|
disk.bus = bus
|
|
|
|
# Generate target
|
|
disks = (self.vm.xmlobj.devices.disk +
|
|
self.vm.get_xmlobj(inactive=True).devices.disk)
|
|
for d in disks:
|
|
if d.target not in used:
|
|
used.append(d.target)
|
|
|
|
self._set_disk_controller(disk)
|
|
disk.generate_target(used)
|
|
return disk
|
|
|
|
def _build_network(self):
|
|
model = uiutil.get_list_selection(self.widget("net-model"))
|
|
mac = None
|
|
if self.widget("mac-address").get_active():
|
|
mac = self.widget("create-mac-address").get_text()
|
|
|
|
dev = self._netlist.build_device(mac, model)
|
|
return dev
|
|
|
|
def _build_input(self):
|
|
typ, bus = uiutil.get_list_selection(self.widget("input-type"))
|
|
dev = DeviceInput(self.conn.get_backend())
|
|
dev.type = typ
|
|
dev.bus = bus
|
|
return dev
|
|
|
|
def _build_graphics(self):
|
|
return self._gfxdetails.build_device()
|
|
|
|
def _build_sound(self):
|
|
smodel = uiutil.get_list_selection(self.widget("sound-model"))
|
|
dev = DeviceSound(self.conn.get_backend())
|
|
dev.model = smodel
|
|
return dev
|
|
|
|
def _build_hostdev(self):
|
|
nodedev = uiutil.get_list_selection(self.widget("host-device"))
|
|
dev = DeviceHostdev(self.conn.get_backend())
|
|
dev.set_from_nodedev(nodedev)
|
|
setattr(dev, "vmm_nodedev", nodedev)
|
|
return dev
|
|
|
|
def _build_char(self):
|
|
char_class = self._get_char_class()
|
|
devtype = uiutil.get_list_selection(self.widget("char-device-type"))
|
|
|
|
typebox = self.widget("char-target-type")
|
|
source_path = self.widget("char-path").get_text()
|
|
source_channel = self.widget("char-channel").get_text()
|
|
target_name = self.widget("char-target-name").get_child().get_text()
|
|
target_type = uiutil.get_list_selection(typebox)
|
|
clipboard = self.widget("char-vdagent-clipboard").get_active()
|
|
|
|
if not self.widget("char-path").get_visible():
|
|
source_path = None
|
|
if not self.widget("char-channel").get_visible():
|
|
source_channel = None
|
|
if not self.widget("char-target-name").get_visible():
|
|
target_name = None
|
|
if not typebox.get_visible():
|
|
target_type = None
|
|
|
|
dev = char_class(self.conn.get_backend())
|
|
dev.type = devtype
|
|
dev.source.path = source_path
|
|
dev.source.channel = source_channel
|
|
dev.source.clipboard_copypaste = clipboard
|
|
dev.target_name = target_name
|
|
dev.target_type = target_type
|
|
return dev
|
|
|
|
def _build_video(self):
|
|
model = uiutil.get_list_selection(self.widget("video-model"))
|
|
dev = DeviceVideo(self.conn.get_backend())
|
|
dev.model = model
|
|
return dev
|
|
|
|
def _build_watchdog(self):
|
|
model = uiutil.get_list_selection(self.widget("watchdog-model"))
|
|
action = uiutil.get_list_selection(self.widget("watchdog-action"))
|
|
dev = DeviceWatchdog(self.conn.get_backend())
|
|
dev.model = model
|
|
dev.action = action
|
|
return dev
|
|
|
|
def _build_filesystem(self):
|
|
return self._fsdetails.build_device()
|
|
|
|
def _build_smartcard(self):
|
|
mode = uiutil.get_list_selection(self.widget("smartcard-mode"))
|
|
dev = DeviceSmartcard(self.conn.get_backend())
|
|
dev.mode = mode
|
|
return dev
|
|
|
|
def _build_usbredir(self):
|
|
stype = uiutil.get_list_selection(self.widget("usbredir-list"))
|
|
dev = DeviceRedirdev(self.conn.get_backend())
|
|
dev.type = stype
|
|
return dev
|
|
|
|
def _build_tpm(self):
|
|
return self._tpmdetails.build_device()
|
|
|
|
def _build_panic(self):
|
|
model = uiutil.get_list_selection(self.widget("panic-model"))
|
|
dev = DevicePanic(self.conn.get_backend())
|
|
dev.model = model
|
|
return dev
|
|
|
|
def _build_vsock(self):
|
|
auto_cid, cid = self._vsockdetails.get_values()
|
|
dev = DeviceVsock(self.conn.get_backend())
|
|
dev.auto_cid = auto_cid
|
|
dev.cid = cid
|
|
return dev
|
|
|
|
def _build_controller(self):
|
|
controller_type = uiutil.get_list_selection(
|
|
self.widget("controller-type"))
|
|
model = uiutil.get_list_selection(self.widget("controller-model"))
|
|
|
|
self._selected_model = model
|
|
if model == "usb3":
|
|
dev = DeviceController.get_usb3_controller(
|
|
self.conn.get_backend(), self.vm.xmlobj)
|
|
model = None
|
|
else:
|
|
dev = DeviceController(self.conn.get_backend())
|
|
|
|
controllers = self.vm.xmlobj.devices.controller
|
|
controller_num = [x for x in controllers if
|
|
(x.type == controller_type)]
|
|
if len(controller_num) > 0:
|
|
index_new = max([x.index for x in controller_num]) + 1
|
|
dev.index = index_new
|
|
|
|
dev.type = controller_type
|
|
|
|
if model and model != "none":
|
|
dev.model = model
|
|
return dev
|
|
|
|
def _build_rng(self):
|
|
device = self.widget("rng-device").get_text()
|
|
dev = DeviceRng(self.conn.get_backend())
|
|
dev.backend_model = DeviceRng.TYPE_RANDOM
|
|
dev.device = device
|
|
return dev
|
|
|
|
|
|
####################
|
|
# Unsorted helpers #
|
|
####################
|
|
|
|
def _browse_storage_cb(self, ignore, widget):
|
|
self._browse_file(widget)
|
|
|
|
def _browse_file(self, textent, isdir=False):
|
|
def set_storage_cb(src, path):
|
|
if path:
|
|
textent.set_text(path)
|
|
|
|
reason = (isdir and
|
|
self.config.CONFIG_DIR_FS or
|
|
self.config.CONFIG_DIR_IMAGE)
|
|
if self._storagebrowser is None:
|
|
self._storagebrowser = vmmStorageBrowser(self.conn)
|
|
|
|
self._storagebrowser.set_finish_cb(set_storage_cb)
|
|
self._storagebrowser.set_browse_reason(reason)
|
|
|
|
self._storagebrowser.show(self.topwin)
|