mirror of
https://github.com/virt-manager/virt-manager.git
synced 2024-12-25 23:21:45 +03:00
a8858cd366
Some ARM bits we are about to add would substantially complicate the current setup, so move all the logic into guest.py where we can be more flexible. I think this is closer to what libosinfo will give us as well.
1353 lines
40 KiB
Python
1353 lines
40 KiB
Python
#
|
|
# Copyright (C) 2009, 2013 Red Hat, Inc.
|
|
# Copyright (C) 2009 Cole Robinson <crobinso@redhat.com>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
# MA 02110-1301 USA.
|
|
#
|
|
|
|
import logging
|
|
import os
|
|
import statvfs
|
|
|
|
# pylint: disable=E0611
|
|
from gi.repository import Gtk
|
|
# pylint: enable=E0611
|
|
|
|
import libvirt
|
|
|
|
import virtinst
|
|
from virtinst import util
|
|
from virtManager import config
|
|
|
|
OPTICAL_DEV_PATH = 0
|
|
OPTICAL_LABEL = 1
|
|
OPTICAL_IS_MEDIA_PRESENT = 2
|
|
OPTICAL_DEV_KEY = 3
|
|
OPTICAL_MEDIA_KEY = 4
|
|
OPTICAL_IS_VALID = 5
|
|
|
|
|
|
############################################################
|
|
# Helpers for shared storage UI between create/addhardware #
|
|
############################################################
|
|
|
|
def set_sparse_tooltip(widget):
|
|
sparse_str = _("Fully allocating storage may take longer now, "
|
|
"but the OS install phase will be quicker. \n\n"
|
|
"Skipping allocation can also cause space issues on "
|
|
"the host machine, if the maximum image size exceeds "
|
|
"available storage space. \n\n"
|
|
"Tip: Storage format qcow2 and qed "
|
|
"do not support full allocation.")
|
|
widget.set_tooltip_text(sparse_str)
|
|
|
|
|
|
def host_disk_space(conn):
|
|
pool = get_default_pool(conn)
|
|
path = get_default_dir(conn)
|
|
|
|
avail = 0
|
|
if pool and pool.is_active():
|
|
# FIXME: make sure not inactive?
|
|
# FIXME: use a conn specific function after we send pool-added
|
|
pool.refresh()
|
|
avail = int(util.xpath(pool.get_xml(), "/pool/available"))
|
|
|
|
elif not conn.is_remote() and os.path.exists(path):
|
|
vfs = os.statvfs(os.path.dirname(path))
|
|
avail = vfs[statvfs.F_FRSIZE] * vfs[statvfs.F_BAVAIL]
|
|
|
|
return float(avail / 1024.0 / 1024.0 / 1024.0)
|
|
|
|
|
|
def update_host_space(conn, widget):
|
|
try:
|
|
max_storage = host_disk_space(conn)
|
|
except:
|
|
logging.exception("Error determining host disk space")
|
|
return
|
|
|
|
def pretty_storage(size):
|
|
return "%.1f Gb" % float(size)
|
|
|
|
hd_label = ("%s available in the default location" %
|
|
pretty_storage(max_storage))
|
|
hd_label = ("<span color='#484848'>%s</span>" % hd_label)
|
|
widget.set_markup(hd_label)
|
|
|
|
|
|
def check_default_pool_active(err, conn):
|
|
default_pool = get_default_pool(conn)
|
|
if default_pool and not default_pool.is_active():
|
|
res = err.yes_no(_("Default pool is not active."),
|
|
_("Storage pool '%s' is not active. "
|
|
"Would you like to start the pool "
|
|
"now?") % default_pool.get_name())
|
|
if not res:
|
|
return False
|
|
|
|
# Try to start the pool
|
|
try:
|
|
default_pool.start()
|
|
logging.info("Started pool '%s'", default_pool.get_name())
|
|
except Exception, e:
|
|
return err.show_err(_("Could not start storage_pool "
|
|
"'%s': %s") %
|
|
(default_pool.get_name(), str(e)))
|
|
return True
|
|
|
|
|
|
#####################################################
|
|
# Hardware model list building (for details, addhw) #
|
|
#####################################################
|
|
|
|
def build_video_combo(vm, video_dev, no_default=None):
|
|
video_dev_model = Gtk.ListStore(str, str)
|
|
video_dev.set_model(video_dev_model)
|
|
text = Gtk.CellRendererText()
|
|
video_dev.pack_start(text, True)
|
|
video_dev.add_attribute(text, 'text', 1)
|
|
video_dev_model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
|
|
|
|
populate_video_combo(vm, video_dev, no_default)
|
|
|
|
|
|
def populate_video_combo(vm, video_dev, no_default=None):
|
|
video_dev_model = video_dev.get_model()
|
|
has_spice = bool([g for g in vm.get_graphics_devices()
|
|
if g.type == g.TYPE_SPICE])
|
|
has_qxl = bool([v for v in vm.get_video_devices()
|
|
if v.model == "qxl"])
|
|
|
|
video_dev_model.clear()
|
|
tmpdev = virtinst.VirtualVideoDevice(vm.conn.get_backend())
|
|
for m in tmpdev.MODELS:
|
|
if not vm.rhel6_defaults():
|
|
if m == "qxl" and not has_spice and not has_qxl:
|
|
# Only list QXL video option when VM has SPICE video
|
|
continue
|
|
|
|
if m == tmpdev.MODEL_DEFAULT and no_default:
|
|
continue
|
|
video_dev_model.append([m, tmpdev.pretty_model(m)])
|
|
|
|
if len(video_dev_model) > 0:
|
|
video_dev.set_active(0)
|
|
|
|
|
|
def build_sound_combo(vm, combo, no_default=False):
|
|
dev_model = Gtk.ListStore(str)
|
|
combo.set_model(dev_model)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 0)
|
|
dev_model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
|
|
|
|
disable_rhel = not vm.rhel6_defaults()
|
|
rhel_soundmodels = ["ich6", "ac97"]
|
|
|
|
for m in virtinst.VirtualAudio.MODELS:
|
|
if m == virtinst.VirtualAudio.MODEL_DEFAULT and no_default:
|
|
continue
|
|
|
|
if (disable_rhel and m not in rhel_soundmodels):
|
|
continue
|
|
|
|
dev_model.append([m])
|
|
if len(dev_model) > 0:
|
|
combo.set_active(0)
|
|
|
|
|
|
def build_watchdogmodel_combo(vm, combo, no_default=False):
|
|
ignore = vm
|
|
dev_model = Gtk.ListStore(str)
|
|
combo.set_model(dev_model)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 0)
|
|
dev_model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
|
|
|
|
for m in virtinst.VirtualWatchdog.MODELS:
|
|
if m == virtinst.VirtualAudio.MODEL_DEFAULT and no_default:
|
|
continue
|
|
dev_model.append([m])
|
|
if len(dev_model) > 0:
|
|
combo.set_active(0)
|
|
|
|
|
|
def build_watchdogaction_combo(vm, combo, no_default=False):
|
|
ignore = vm
|
|
dev_model = Gtk.ListStore(str, str)
|
|
combo.set_model(dev_model)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 1)
|
|
dev_model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
|
|
|
|
for m in virtinst.VirtualWatchdog.ACTIONS:
|
|
if m == virtinst.VirtualWatchdog.ACTION_DEFAULT and no_default:
|
|
continue
|
|
dev_model.append([m, virtinst.VirtualWatchdog.get_action_desc(m)])
|
|
if len(dev_model) > 0:
|
|
combo.set_active(0)
|
|
|
|
|
|
def build_source_mode_combo(vm, combo):
|
|
source_mode = Gtk.ListStore(str, str)
|
|
combo.set_model(source_mode)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 1)
|
|
|
|
populate_source_mode_combo(vm, combo)
|
|
combo.set_active(0)
|
|
|
|
|
|
def populate_source_mode_combo(vm, combo):
|
|
ignore = vm
|
|
model = combo.get_model()
|
|
model.clear()
|
|
|
|
# [xml value, label]
|
|
model.append([None, "Default"])
|
|
model.append(["vepa", "VEPA"])
|
|
model.append(["bridge", "Bridge"])
|
|
model.append(["private", "Private"])
|
|
model.append(["passthrough", "Passthrough"])
|
|
|
|
|
|
def build_smartcard_mode_combo(vm, combo):
|
|
dev_model = Gtk.ListStore(str, str)
|
|
combo.set_model(dev_model)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 1)
|
|
dev_model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
|
|
|
|
populate_smartcard_mode_combo(vm, combo)
|
|
|
|
idx = -1
|
|
for rowid in range(len(combo.get_model())):
|
|
idx = 0
|
|
row = combo.get_model()[rowid]
|
|
if row[0] == virtinst.VirtualSmartCardDevice.MODE_DEFAULT:
|
|
idx = rowid
|
|
break
|
|
combo.set_active(idx)
|
|
|
|
|
|
def populate_smartcard_mode_combo(vm, combo):
|
|
ignore = vm
|
|
model = combo.get_model()
|
|
model.clear()
|
|
|
|
# [xml value, label]
|
|
model.append(["passthrough", "Passthrough"])
|
|
model.append(["host", "Host"])
|
|
|
|
|
|
def build_redir_type_combo(vm, combo):
|
|
source_mode = Gtk.ListStore(str, str, bool)
|
|
combo.set_model(source_mode)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 1)
|
|
|
|
populate_redir_type_combo(vm, combo)
|
|
combo.set_active(0)
|
|
|
|
|
|
def populate_redir_type_combo(vm, combo):
|
|
ignore = vm
|
|
model = combo.get_model()
|
|
model.clear()
|
|
|
|
# [xml value, label, conn details]
|
|
model.append(["spicevmc", "Spice channel", False])
|
|
model.append(["tcp", "TCP", True])
|
|
|
|
|
|
def build_tpm_type_combo(vm, combo):
|
|
dev_model = Gtk.ListStore(str, str)
|
|
combo.set_model(dev_model)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 1)
|
|
dev_model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
|
|
|
|
populate_tpm_type_combo(vm, combo)
|
|
|
|
idx = -1
|
|
for rowid in range(len(combo.get_model())):
|
|
idx = 0
|
|
row = combo.get_model()[rowid]
|
|
if row[0] == virtinst.VirtualTPMDevice.TYPE_DEFAULT:
|
|
idx = rowid
|
|
break
|
|
combo.set_active(idx)
|
|
|
|
|
|
def populate_tpm_type_combo(vm, combo):
|
|
ignore = vm
|
|
types = combo.get_model()
|
|
types.clear()
|
|
|
|
# [xml value, label]
|
|
for t in virtinst.VirtualTPMDevice.TYPES:
|
|
types.append([t, virtinst.VirtualTPMDevice.get_pretty_type(t)])
|
|
|
|
|
|
def build_netmodel_combo(vm, combo):
|
|
dev_model = Gtk.ListStore(str, str)
|
|
combo.set_model(dev_model)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 1)
|
|
dev_model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
|
|
|
|
populate_netmodel_combo(vm, combo)
|
|
combo.set_active(0)
|
|
|
|
|
|
def populate_netmodel_combo(vm, combo):
|
|
model = combo.get_model()
|
|
model.clear()
|
|
|
|
# [xml value, label]
|
|
model.append([None, _("Hypervisor default")])
|
|
if vm.is_hvm():
|
|
mod_list = ["rtl8139", "ne2k_pci", "pcnet", "e1000"]
|
|
if vm.get_hv_type() in ["kvm", "qemu", "test"]:
|
|
mod_list.append("virtio")
|
|
if (vm.get_hv_type() == "kvm" and
|
|
vm.get_machtype() == "pseries"):
|
|
mod_list.append("spapr-vlan")
|
|
if vm.get_hv_type() in ["xen", "test"]:
|
|
mod_list.append("netfront")
|
|
mod_list.sort()
|
|
|
|
for m in mod_list:
|
|
model.append([m, m])
|
|
|
|
|
|
def build_cache_combo(vm, combo, no_default=False):
|
|
ignore = vm
|
|
dev_model = Gtk.ListStore(str, str)
|
|
combo.set_model(dev_model)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 1)
|
|
dev_model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
|
|
|
|
combo.set_active(-1)
|
|
for m in virtinst.VirtualDisk.cache_types:
|
|
dev_model.append([m, m])
|
|
|
|
if not no_default:
|
|
dev_model.append([None, "default"])
|
|
combo.set_active(0)
|
|
|
|
|
|
def build_io_combo(vm, combo, no_default=False):
|
|
ignore = vm
|
|
dev_model = Gtk.ListStore(str, str)
|
|
combo.set_model(dev_model)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 1)
|
|
dev_model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
|
|
|
|
combo.set_active(-1)
|
|
for m in virtinst.VirtualDisk.io_modes:
|
|
dev_model.append([m, m])
|
|
|
|
if not no_default:
|
|
dev_model.append([None, "default"])
|
|
combo.set_active(0)
|
|
|
|
|
|
def build_disk_bus_combo(vm, combo, no_default=False):
|
|
ignore = vm
|
|
dev_model = Gtk.ListStore(str, str)
|
|
combo.set_model(dev_model)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 1)
|
|
dev_model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
|
|
|
|
if not no_default:
|
|
dev_model.append([None, "default"])
|
|
combo.set_active(-1)
|
|
|
|
|
|
def build_vnc_keymap_combo(vm, combo, no_default=False):
|
|
ignore = vm
|
|
|
|
model = Gtk.ListStore(str, str)
|
|
combo.set_model(model)
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', 1)
|
|
|
|
if not no_default:
|
|
model.append([None, "default"])
|
|
else:
|
|
model.append([None, "Auto"])
|
|
|
|
model.append([virtinst.VirtualGraphics.KEYMAP_LOCAL,
|
|
"Copy local keymap"])
|
|
for k in virtinst.VirtualGraphics.valid_keymaps():
|
|
model.append([k, k])
|
|
|
|
combo.set_active(-1)
|
|
|
|
|
|
#####################################
|
|
# Storage format list/combo helpers #
|
|
#####################################
|
|
|
|
def build_storage_format_combo(vm, combo):
|
|
dev_model = Gtk.ListStore(str)
|
|
combo.set_model(dev_model)
|
|
combo.set_entry_text_column(0)
|
|
|
|
formats = ["raw", "qcow2", "qed"]
|
|
if vm.rhel6_defaults():
|
|
formats.append("vmdk")
|
|
formats.append("vdi")
|
|
|
|
for m in formats:
|
|
dev_model.append([m])
|
|
|
|
combo.set_active(0)
|
|
|
|
|
|
#######################################################################
|
|
# Widgets for listing network device options (in create, addhardware) #
|
|
#######################################################################
|
|
|
|
def pretty_network_desc(nettype, source=None, netobj=None):
|
|
if nettype == virtinst.VirtualNetworkInterface.TYPE_USER:
|
|
return _("Usermode networking")
|
|
|
|
extra = None
|
|
if nettype == virtinst.VirtualNetworkInterface.TYPE_BRIDGE:
|
|
ret = _("Bridge")
|
|
elif nettype == virtinst.VirtualNetworkInterface.TYPE_VIRTUAL:
|
|
ret = _("Virtual network")
|
|
if netobj:
|
|
extra = ": %s" % netobj.pretty_forward_mode()
|
|
else:
|
|
ret = nettype.capitalize()
|
|
|
|
if source:
|
|
ret += " '%s'" % source
|
|
if extra:
|
|
ret += " %s" % extra
|
|
|
|
return ret
|
|
|
|
|
|
def init_network_list(net_list, bridge_box,
|
|
source_mode_box=None, source_mode_label=None,
|
|
vport_expander=None):
|
|
# [ network type, source name, label, sensitive?, net is active,
|
|
# manual bridge, net instance]
|
|
net_model = Gtk.ListStore(str, str, str, bool, bool, bool, object)
|
|
net_list.set_model(net_model)
|
|
|
|
net_list.connect("changed", net_list_changed, bridge_box, source_mode_box,
|
|
source_mode_label, vport_expander)
|
|
|
|
text = Gtk.CellRendererText()
|
|
net_list.pack_start(text, True)
|
|
net_list.add_attribute(text, 'text', 2)
|
|
net_list.add_attribute(text, 'sensitive', 3)
|
|
|
|
|
|
def net_list_changed(net_list, bridge_box,
|
|
source_mode_box, source_mode_label, vport_expander):
|
|
active = net_list.get_active()
|
|
if active < 0:
|
|
return
|
|
|
|
if not bridge_box:
|
|
return
|
|
|
|
row = net_list.get_model()[active]
|
|
|
|
if source_mode_box is not None:
|
|
show_source_mode = (row[0] ==
|
|
virtinst.VirtualNetworkInterface.TYPE_DIRECT)
|
|
source_mode_box.set_property("visible", show_source_mode)
|
|
source_mode_label.set_property("visible", show_source_mode)
|
|
vport_expander.set_property("visible", show_source_mode)
|
|
|
|
show_bridge = row[5]
|
|
|
|
bridge_box.set_property("visible", show_bridge)
|
|
|
|
|
|
def get_network_selection(net_list, bridge_entry):
|
|
idx = net_list.get_active()
|
|
if idx == -1:
|
|
return None, None
|
|
|
|
row = net_list.get_model()[net_list.get_active()]
|
|
net_type = row[0]
|
|
net_src = row[1]
|
|
net_check_bridge = row[5]
|
|
|
|
if net_check_bridge and bridge_entry:
|
|
net_type = virtinst.VirtualNetworkInterface.TYPE_BRIDGE
|
|
net_src = bridge_entry.get_text()
|
|
|
|
return net_type, net_src
|
|
|
|
|
|
def populate_network_list(net_list, conn, show_direct_interfaces=True):
|
|
model = net_list.get_model()
|
|
model.clear()
|
|
|
|
vnet_bridges = []
|
|
vnet_dict = {}
|
|
bridge_dict = {}
|
|
iface_dict = {}
|
|
|
|
def build_row(nettype, name, label, is_sensitive, is_running,
|
|
manual_bridge=False, key=None):
|
|
return [nettype, name, label,
|
|
is_sensitive, is_running, manual_bridge,
|
|
key]
|
|
|
|
def set_active(idx):
|
|
net_list.set_active(idx)
|
|
|
|
def add_dict(indict, model):
|
|
keylist = indict.keys()
|
|
keylist.sort()
|
|
rowlist = [indict[k] for k in keylist]
|
|
for row in rowlist:
|
|
model.append(row)
|
|
|
|
# For qemu:///session
|
|
if conn.is_qemu_session():
|
|
nettype = virtinst.VirtualNetworkInterface.TYPE_USER
|
|
r = build_row(nettype, None, pretty_network_desc(nettype), True, True)
|
|
model.append(r)
|
|
set_active(0)
|
|
return
|
|
|
|
hasNet = False
|
|
netIdxLabel = None
|
|
# Virtual Networks
|
|
for uuid in conn.list_net_uuids():
|
|
net = conn.get_net(uuid)
|
|
nettype = virtinst.VirtualNetworkInterface.TYPE_VIRTUAL
|
|
|
|
label = pretty_network_desc(nettype, net.get_name(), net)
|
|
if not net.is_active():
|
|
label += " (%s)" % _("Inactive")
|
|
|
|
hasNet = True
|
|
# FIXME: Should we use 'default' even if it's inactive?
|
|
# FIXME: This preference should be configurable
|
|
if net.get_name() == "default":
|
|
netIdxLabel = label
|
|
|
|
vnet_dict[label] = build_row(nettype, net.get_name(), label, True,
|
|
net.is_active(), key=net.get_uuid())
|
|
|
|
# Build a list of vnet bridges, so we know not to list them
|
|
# in the physical interface list
|
|
vnet_bridge = net.get_bridge_device()
|
|
if vnet_bridge:
|
|
vnet_bridges.append(vnet_bridge)
|
|
|
|
if not hasNet:
|
|
label = _("No virtual networks available")
|
|
vnet_dict[label] = build_row(None, None, label, False, False)
|
|
|
|
vnet_taps = []
|
|
for vm in conn.vms.values():
|
|
for nic in vm.get_network_devices(refresh_if_nec=False):
|
|
if nic.target_dev and nic.target_dev not in vnet_taps:
|
|
vnet_taps.append(nic.target_dev)
|
|
|
|
# Physical devices
|
|
hasShared = False
|
|
brIdxLabel = None
|
|
for name in conn.list_net_device_paths():
|
|
br = conn.get_net_device(name)
|
|
bridge_name = br.get_bridge()
|
|
nettype = virtinst.VirtualNetworkInterface.TYPE_BRIDGE
|
|
|
|
if ((bridge_name in vnet_bridges) or
|
|
(br.get_name() in vnet_bridges) or
|
|
(br.get_name() in vnet_taps) or
|
|
(br.get_name() in [v + "-nic" for v in vnet_bridges])):
|
|
# Don't list this, as it is basically duplicating virtual net info
|
|
continue
|
|
|
|
if br.is_shared():
|
|
sensitive = True
|
|
if br.get_bridge():
|
|
hasShared = True
|
|
brlabel = "(%s)" % pretty_network_desc(nettype, bridge_name)
|
|
else:
|
|
bridge_name = name
|
|
brlabel = _("(Empty bridge)")
|
|
else:
|
|
if (show_direct_interfaces and
|
|
conn.check_conn_support(
|
|
conn.SUPPORT_CONN_HV_DIRECT_INTERFACE)):
|
|
sensitive = True
|
|
nettype = virtinst.VirtualNetworkInterface.TYPE_DIRECT
|
|
bridge_name = name
|
|
brlabel = ": %s" % _("macvtap")
|
|
else:
|
|
sensitive = False
|
|
brlabel = "(%s)" % _("Not bridged")
|
|
|
|
label = _("Host device %s %s") % (br.get_name(), brlabel)
|
|
if hasShared and not brIdxLabel:
|
|
brIdxLabel = label
|
|
|
|
row = build_row(nettype, bridge_name, label, sensitive, True,
|
|
key=br.get_name())
|
|
|
|
if sensitive:
|
|
bridge_dict[label] = row
|
|
else:
|
|
iface_dict[label] = row
|
|
|
|
add_dict(bridge_dict, model)
|
|
add_dict(vnet_dict, model)
|
|
add_dict(iface_dict, model)
|
|
|
|
# If there is a bridge device, default to that
|
|
# If not, use 'default' network
|
|
# If not present, use first list entry
|
|
# If list empty, use no network devices
|
|
return_warn = False
|
|
label = brIdxLabel or netIdxLabel
|
|
|
|
for idx in range(len(model)):
|
|
row = model[idx]
|
|
is_inactive = not row[4]
|
|
if label:
|
|
if row[2] == label:
|
|
default = idx
|
|
return_warn = is_inactive
|
|
break
|
|
else:
|
|
if row[3] is True:
|
|
default = idx
|
|
return_warn = is_inactive
|
|
break
|
|
else:
|
|
return_warn = True
|
|
row = build_row(None, None, _("No networking"), True, False)
|
|
model.insert(0, row)
|
|
default = 0
|
|
|
|
# After all is said and done, add a manual bridge option
|
|
manual_row = build_row(None, None, _("Specify shared device name"),
|
|
True, False, manual_bridge=True)
|
|
model.append(manual_row)
|
|
|
|
set_active(default)
|
|
return return_warn
|
|
|
|
|
|
def validate_network(err, conn, nettype, devname, macaddr, model=None):
|
|
net = None
|
|
|
|
if nettype is None:
|
|
return None
|
|
|
|
# Make sure VirtualNetwork is running
|
|
netobj = None
|
|
if nettype == virtinst.VirtualNetworkInterface.TYPE_VIRTUAL:
|
|
for net in conn.nets.values():
|
|
if net.get_name() == devname:
|
|
netobj = net
|
|
break
|
|
|
|
if netobj and not netobj.is_active():
|
|
res = err.yes_no(_("Virtual Network is not active."),
|
|
_("Virtual Network '%s' is not active. "
|
|
"Would you like to start the network "
|
|
"now?") % devname)
|
|
if not res:
|
|
return False
|
|
|
|
# Try to start the network
|
|
try:
|
|
netobj.start()
|
|
netobj.tick()
|
|
logging.info("Started network '%s'", devname)
|
|
except Exception, e:
|
|
return err.show_err(_("Could not start virtual network "
|
|
"'%s': %s") % (devname, str(e)))
|
|
|
|
# Create network device
|
|
try:
|
|
bridge = None
|
|
netname = None
|
|
if nettype == virtinst.VirtualNetworkInterface.TYPE_VIRTUAL:
|
|
netname = devname
|
|
elif nettype == virtinst.VirtualNetworkInterface.TYPE_BRIDGE:
|
|
bridge = devname
|
|
elif nettype == virtinst.VirtualNetworkInterface.TYPE_DIRECT:
|
|
bridge = devname
|
|
elif nettype == virtinst.VirtualNetworkInterface.TYPE_USER:
|
|
pass
|
|
|
|
net = virtinst.VirtualNetworkInterface(conn.get_backend())
|
|
net.type = nettype
|
|
net.bridge = bridge
|
|
net.network = netname
|
|
net.macaddr = macaddr
|
|
net.model = model
|
|
if net.model == "spapr-vlan":
|
|
net.address.set_addrstr("spapr-vio")
|
|
|
|
|
|
except Exception, e:
|
|
return err.val_err(_("Error with network parameters."), e)
|
|
|
|
# Make sure there is no mac address collision
|
|
isfatal, errmsg = net.is_conflict_net(conn.get_backend(), net.macaddr)
|
|
if isfatal:
|
|
return err.val_err(_("Mac address collision."), errmsg)
|
|
elif errmsg is not None:
|
|
retv = err.yes_no(_("Mac address collision."),
|
|
_("%s Are you sure you want to use this "
|
|
"address?") % errmsg)
|
|
if not retv:
|
|
return False
|
|
|
|
return net
|
|
|
|
|
|
############################################
|
|
# Populate media widget (choosecd, create) #
|
|
############################################
|
|
|
|
def init_mediadev_combo(widget):
|
|
# [Device path, pretty label, has_media?, device key, media key,
|
|
# vmmMediaDevice, is valid device]
|
|
model = Gtk.ListStore(str, str, bool, str, str, bool)
|
|
widget.set_model(model)
|
|
model.clear()
|
|
|
|
text = Gtk.CellRendererText()
|
|
widget.pack_start(text, True)
|
|
widget.add_attribute(text, 'text', OPTICAL_LABEL)
|
|
widget.add_attribute(text, 'sensitive', OPTICAL_IS_VALID)
|
|
|
|
|
|
def populate_mediadev_combo(conn, widget, devtype):
|
|
sigs = []
|
|
|
|
model = widget.get_model()
|
|
model.clear()
|
|
set_mediadev_default(model)
|
|
|
|
sigs.append(conn.connect("mediadev-added", mediadev_added, widget, devtype))
|
|
sigs.append(conn.connect("mediadev-removed", mediadev_removed, widget))
|
|
|
|
widget.set_active(-1)
|
|
mediadev_set_default_selection(widget)
|
|
|
|
return sigs
|
|
|
|
|
|
def set_mediadev_default(model):
|
|
if len(model) == 0:
|
|
model.append([None, _("No device present"), False, None, None, False])
|
|
|
|
|
|
def set_row_from_object(row, obj):
|
|
row[OPTICAL_DEV_PATH] = obj.get_path()
|
|
row[OPTICAL_LABEL] = obj.pretty_label()
|
|
row[OPTICAL_IS_MEDIA_PRESENT] = obj.has_media()
|
|
row[OPTICAL_DEV_KEY] = obj.get_key()
|
|
row[OPTICAL_MEDIA_KEY] = obj.get_media_key()
|
|
row[OPTICAL_IS_VALID] = True
|
|
|
|
|
|
def mediadev_removed(ignore_helper, key, widget):
|
|
model = widget.get_model()
|
|
active = widget.get_active()
|
|
idx = 0
|
|
|
|
for row in model:
|
|
if row[OPTICAL_DEV_KEY] == key:
|
|
# Whole device removed
|
|
del(model[idx])
|
|
|
|
if idx > active and active != -1:
|
|
widget.set_active(active - 1)
|
|
elif idx == active:
|
|
widget.set_active(-1)
|
|
|
|
idx += 1
|
|
|
|
set_mediadev_default(model)
|
|
mediadev_set_default_selection(widget)
|
|
|
|
|
|
def mediadev_added(ignore_helper, newobj, widget, devtype):
|
|
model = widget.get_model()
|
|
|
|
if newobj.get_media_type() != devtype:
|
|
return
|
|
if model is None:
|
|
return
|
|
|
|
if len(model) == 1 and model[0][OPTICAL_IS_VALID] is False:
|
|
# Only entry is the 'No device' entry
|
|
model.clear()
|
|
|
|
newobj.connect("media-added", mediadev_media_changed, widget)
|
|
newobj.connect("media-removed", mediadev_media_changed, widget)
|
|
|
|
# Brand new device
|
|
row = [None, None, None, None, None, None]
|
|
set_row_from_object(row, newobj)
|
|
model.append(row)
|
|
|
|
mediadev_set_default_selection(widget)
|
|
|
|
|
|
def mediadev_media_changed(newobj, widget):
|
|
model = widget.get_model()
|
|
active = widget.get_active()
|
|
idx = 0
|
|
|
|
# Search for the row with matching device node and
|
|
# fill in info about inserted media. If model has no current
|
|
# selection, select the new media.
|
|
for row in model:
|
|
if row[OPTICAL_DEV_PATH] == newobj.get_path():
|
|
set_row_from_object(row, newobj)
|
|
has_media = row[OPTICAL_IS_MEDIA_PRESENT]
|
|
|
|
if has_media and active == -1:
|
|
widget.set_active(idx)
|
|
elif not has_media and active == idx:
|
|
widget.set_active(-1)
|
|
|
|
idx = idx + 1
|
|
|
|
mediadev_set_default_selection(widget)
|
|
|
|
|
|
def mediadev_set_default_selection(widget):
|
|
# Set the first active cdrom device as selected, otherwise none
|
|
model = widget.get_model()
|
|
idx = 0
|
|
active = widget.get_active()
|
|
|
|
if active != -1:
|
|
# already a selection, don't change it
|
|
return
|
|
|
|
for row in model:
|
|
if row[OPTICAL_IS_MEDIA_PRESENT] is True:
|
|
widget.set_active(idx)
|
|
return
|
|
idx += 1
|
|
|
|
widget.set_active(-1)
|
|
|
|
|
|
####################################################################
|
|
# Build toolbar shutdown button menu (manager and details toolbar) #
|
|
####################################################################
|
|
|
|
def build_shutdown_button_menu(widget, shutdown_cb, reboot_cb, reset_cb,
|
|
destroy_cb, save_cb):
|
|
widget.set_icon_name("system-shutdown")
|
|
menu = Gtk.Menu()
|
|
widget.set_menu(menu)
|
|
|
|
rebootimg = Gtk.Image.new_from_icon_name("system-shutdown",
|
|
Gtk.IconSize.MENU)
|
|
shutdownimg = Gtk.Image.new_from_icon_name("system-shutdown",
|
|
Gtk.IconSize.MENU)
|
|
destroyimg = Gtk.Image.new_from_icon_name("system-shutdown",
|
|
Gtk.IconSize.MENU)
|
|
resetimg = Gtk.Image.new_from_icon_name("system-shutdown",
|
|
Gtk.IconSize.MENU)
|
|
saveimg = Gtk.Image.new_from_icon_name(Gtk.STOCK_SAVE, Gtk.IconSize.MENU)
|
|
|
|
reboot = Gtk.ImageMenuItem.new_with_mnemonic(_("_Reboot"))
|
|
reboot.set_image(rebootimg)
|
|
reboot.show()
|
|
reboot.connect("activate", reboot_cb)
|
|
menu.add(reboot)
|
|
|
|
shutdown = Gtk.ImageMenuItem.new_with_mnemonic(_("_Shut Down"))
|
|
shutdown.set_image(shutdownimg)
|
|
shutdown.show()
|
|
shutdown.connect("activate", shutdown_cb)
|
|
menu.add(shutdown)
|
|
|
|
reset = Gtk.ImageMenuItem.new_with_mnemonic(_("_Force Reset"))
|
|
reset.set_image(resetimg)
|
|
reset.show()
|
|
reset.connect("activate", reset_cb)
|
|
menu.add(reset)
|
|
|
|
destroy = Gtk.ImageMenuItem.new_with_mnemonic(_("_Force Off"))
|
|
destroy.set_image(destroyimg)
|
|
destroy.show()
|
|
destroy.connect("activate", destroy_cb)
|
|
menu.add(destroy)
|
|
|
|
sep = Gtk.SeparatorMenuItem()
|
|
sep.show()
|
|
menu.add(sep)
|
|
|
|
save = Gtk.ImageMenuItem.new_with_mnemonic(_("Sa_ve"))
|
|
save.set_image(saveimg)
|
|
save.show()
|
|
save.connect("activate", save_cb)
|
|
menu.add(save)
|
|
|
|
|
|
#####################################
|
|
# Path permissions checker for qemu #
|
|
#####################################
|
|
|
|
def check_path_search_for_qemu(err, conn, path):
|
|
if conn.is_remote() or not conn.is_qemu_system():
|
|
return
|
|
|
|
user = config.running_config.default_qemu_user
|
|
|
|
skip_paths = config.running_config.get_perms_fix_ignore()
|
|
broken_paths = virtinst.VirtualDisk.check_path_search_for_user(
|
|
conn.get_backend(),
|
|
path, user)
|
|
for p in broken_paths:
|
|
if p in skip_paths:
|
|
broken_paths.remove(p)
|
|
|
|
if not broken_paths:
|
|
return
|
|
|
|
logging.debug("No search access for dirs: %s", broken_paths)
|
|
resp, chkres = err.warn_chkbox(
|
|
_("The emulator may not have search permissions "
|
|
"for the path '%s'.") % path,
|
|
_("Do you want to correct this now?"),
|
|
_("Don't ask about these directories again."),
|
|
buttons=Gtk.ButtonsType.YES_NO)
|
|
|
|
if chkres:
|
|
config.running_config.add_perms_fix_ignore(broken_paths)
|
|
if not resp:
|
|
return
|
|
|
|
logging.debug("Attempting to correct permission issues.")
|
|
errors = virtinst.VirtualDisk.fix_path_search_for_user(conn.get_backend(),
|
|
path, user)
|
|
if not errors:
|
|
return
|
|
|
|
errmsg = _("Errors were encountered changing permissions for the "
|
|
"following directories:")
|
|
details = ""
|
|
for path, error in errors.items():
|
|
if path not in broken_paths:
|
|
continue
|
|
details += "%s : %s\n" % (path, error)
|
|
|
|
logging.debug("Permission errors:\n%s", details)
|
|
|
|
ignore, chkres = err.err_chkbox(errmsg, details,
|
|
_("Don't ask about these directories again."))
|
|
|
|
if chkres:
|
|
config.running_config.add_perms_fix_ignore(errors.keys())
|
|
|
|
|
|
######################################
|
|
# Interface startmode widget builder #
|
|
######################################
|
|
|
|
def build_startmode_combo(start_list):
|
|
start_model = Gtk.ListStore(str)
|
|
start_list.set_model(start_model)
|
|
text = Gtk.CellRendererText()
|
|
start_list.pack_start(text, True)
|
|
start_list.add_attribute(text, 'text', 0)
|
|
start_model.append(["none"])
|
|
start_model.append(["onboot"])
|
|
start_model.append(["hotplug"])
|
|
|
|
|
|
#########################
|
|
# Console keycombo menu #
|
|
#########################
|
|
|
|
def build_keycombo_menu(cb):
|
|
menu = Gtk.Menu()
|
|
|
|
def make_item(name, combo):
|
|
item = Gtk.MenuItem.new_with_mnemonic(name)
|
|
item.connect("activate", cb, combo)
|
|
|
|
menu.add(item)
|
|
|
|
make_item("Ctrl+Alt+_Backspace", ["Control_L", "Alt_L", "BackSpace"])
|
|
make_item("Ctrl+Alt+_Delete", ["Control_L", "Alt_L", "Delete"])
|
|
menu.add(Gtk.SeparatorMenuItem())
|
|
|
|
for i in range(1, 13):
|
|
make_item("Ctrl+Alt+F_%d" % i, ["Control_L", "Alt_L", "F%d" % i])
|
|
menu.add(Gtk.SeparatorMenuItem())
|
|
|
|
make_item("_Printscreen", ["Print"])
|
|
|
|
menu.show_all()
|
|
return menu
|
|
|
|
|
|
#############
|
|
# Misc bits #
|
|
#############
|
|
|
|
def spin_get_helper(widget):
|
|
adj = widget.get_adjustment()
|
|
txt = widget.get_text()
|
|
|
|
try:
|
|
ret = int(txt)
|
|
except:
|
|
ret = adj.get_value()
|
|
return ret
|
|
|
|
|
|
|
|
def get_default_pool_path(conn):
|
|
if conn.is_session_uri():
|
|
return os.path.expanduser("~/VirtualMachines")
|
|
return "/var/lib/libvirt/images"
|
|
|
|
|
|
def get_default_pool_name(conn):
|
|
ignore = conn
|
|
return "default"
|
|
|
|
|
|
def build_default_pool(vmmconn):
|
|
"""
|
|
Helper to build the 'default' storage pool
|
|
"""
|
|
conn = vmmconn.get_backend()
|
|
|
|
path = get_default_pool_path(vmmconn)
|
|
name = get_default_pool_name(vmmconn)
|
|
pool = None
|
|
try:
|
|
pool = conn.storagePoolLookupByName(name)
|
|
except libvirt.libvirtError:
|
|
pass
|
|
|
|
if pool:
|
|
return
|
|
|
|
try:
|
|
logging.debug("Attempting to build default pool with target '%s'",
|
|
path)
|
|
defpool = virtinst.Storage.DirectoryPool(conn=conn,
|
|
name=name,
|
|
target_path=path)
|
|
newpool = defpool.install(build=True, create=True)
|
|
newpool.setAutostart(True)
|
|
except Exception, e:
|
|
raise RuntimeError(_("Couldn't create default storage pool '%s': %s") %
|
|
(path, str(e)))
|
|
|
|
|
|
def get_ideal_path_info(conn, name):
|
|
path = get_default_dir(conn)
|
|
suffix = ".img"
|
|
return (path, name, suffix)
|
|
|
|
|
|
def get_ideal_path(conn, name):
|
|
target, name, suffix = get_ideal_path_info(conn, name)
|
|
return os.path.join(target, name) + suffix
|
|
|
|
|
|
def get_default_pool(conn):
|
|
pool = None
|
|
default_name = get_default_pool_name(conn)
|
|
for uuid in conn.list_pool_uuids():
|
|
p = conn.get_pool(uuid)
|
|
if p.get_name() == default_name:
|
|
pool = p
|
|
|
|
return pool
|
|
|
|
|
|
def get_default_dir(conn):
|
|
pool = get_default_pool(conn)
|
|
|
|
if pool:
|
|
return pool.get_target_path()
|
|
else:
|
|
return config.running_config.get_default_image_dir(conn)
|
|
|
|
|
|
def get_default_path(conn, name, collidelist=None):
|
|
collidelist = collidelist or []
|
|
pool = get_default_pool(conn)
|
|
|
|
default_dir = get_default_dir(conn)
|
|
|
|
def path_exists(p):
|
|
return os.path.exists(p) or p in collidelist
|
|
|
|
if not pool:
|
|
# Use old generating method
|
|
origf = os.path.join(default_dir, name + ".img")
|
|
f = origf
|
|
|
|
n = 1
|
|
while path_exists(f) and n < 100:
|
|
f = os.path.join(default_dir, name +
|
|
"-" + str(n) + ".img")
|
|
n += 1
|
|
|
|
if path_exists(f):
|
|
f = origf
|
|
|
|
path = f
|
|
else:
|
|
target, ignore, suffix = get_ideal_path_info(conn, name)
|
|
|
|
# Sanitize collidelist to work with the collision checker
|
|
newcollidelist = []
|
|
for c in collidelist:
|
|
if c and os.path.dirname(c) == pool.get_target_path():
|
|
newcollidelist.append(os.path.basename(c))
|
|
|
|
path = virtinst.Storage.StorageVolume.find_free_name(name,
|
|
pool_object=pool.get_backend(), suffix=suffix,
|
|
collidelist=newcollidelist)
|
|
|
|
path = os.path.join(target, path)
|
|
|
|
return path
|
|
|
|
|
|
def browse_local(parent, dialog_name, conn, start_folder=None,
|
|
_type=None, dialog_type=None,
|
|
confirm_func=None, browse_reason=None,
|
|
choose_button=None, default_name=None):
|
|
"""
|
|
Helper function for launching a filechooser
|
|
|
|
@param parent: Parent window for the filechooser
|
|
@param dialog_name: String to use in the title bar of the filechooser.
|
|
@param conn: vmmConnection used by calling class
|
|
@param start_folder: Folder the filechooser is viewing at startup
|
|
@param _type: File extension to filter by (e.g. "iso", "png")
|
|
@param dialog_type: Maps to FileChooserDialog 'action'
|
|
@param confirm_func: Optional callback function if file is chosen.
|
|
@param browse_reason: The vmmConfig.CONFIG_DIR* reason we are browsing.
|
|
If set, this will override the 'folder' parameter with the gconf
|
|
value, and store the user chosen path.
|
|
|
|
"""
|
|
# Initial setup
|
|
overwrite_confirm = False
|
|
|
|
if dialog_type is None:
|
|
dialog_type = Gtk.FileChooserAction.OPEN
|
|
if dialog_type == Gtk.FileChooserAction.SAVE:
|
|
if choose_button is None:
|
|
choose_button = Gtk.STOCK_SAVE
|
|
overwrite_confirm = True
|
|
|
|
if choose_button is None:
|
|
choose_button = Gtk.STOCK_OPEN
|
|
|
|
fcdialog = Gtk.FileChooserDialog(title=dialog_name,
|
|
parent=parent,
|
|
action=dialog_type,
|
|
buttons=(Gtk.STOCK_CANCEL,
|
|
Gtk.ResponseType.CANCEL,
|
|
choose_button,
|
|
Gtk.ResponseType.ACCEPT))
|
|
fcdialog.set_default_response(Gtk.ResponseType.ACCEPT)
|
|
|
|
if default_name:
|
|
fcdialog.set_current_name(default_name)
|
|
|
|
# If confirm is set, warn about a file overwrite
|
|
if confirm_func:
|
|
overwrite_confirm = True
|
|
fcdialog.connect("confirm-overwrite", confirm_func)
|
|
fcdialog.set_do_overwrite_confirmation(overwrite_confirm)
|
|
|
|
# Set file match pattern (ex. *.png)
|
|
if _type is not None:
|
|
pattern = _type
|
|
name = None
|
|
if type(_type) is tuple:
|
|
pattern = _type[0]
|
|
name = _type[1]
|
|
|
|
f = Gtk.FileFilter()
|
|
f.add_pattern("*." + pattern)
|
|
if name:
|
|
f.set_name(name)
|
|
fcdialog.set_filter(f)
|
|
|
|
# Set initial dialog folder
|
|
if browse_reason:
|
|
start_folder = config.running_config.get_default_directory(conn,
|
|
browse_reason)
|
|
|
|
if start_folder is not None:
|
|
if os.access(start_folder, os.R_OK):
|
|
fcdialog.set_current_folder(start_folder)
|
|
|
|
# Run the dialog and parse the response
|
|
ret = None
|
|
if fcdialog.run() == Gtk.ResponseType.ACCEPT:
|
|
ret = fcdialog.get_filename()
|
|
fcdialog.destroy()
|
|
|
|
# Store the chosen directory in gconf if necessary
|
|
if ret and browse_reason and not ret.startswith("/dev"):
|
|
config.running_config.set_default_directory(os.path.dirname(ret),
|
|
browse_reason)
|
|
return ret
|
|
|
|
|
|
def pretty_hv(gtype, domtype):
|
|
"""
|
|
Convert XML <domain type='foo'> and <os><type>bar</type>
|
|
into a more human relevant string.
|
|
"""
|
|
|
|
gtype = gtype.lower()
|
|
domtype = domtype.lower()
|
|
|
|
label = domtype
|
|
if domtype == "kvm":
|
|
if gtype == "xen":
|
|
label = "xenner"
|
|
elif domtype == "xen":
|
|
if gtype == "xen":
|
|
label = "xen (paravirt)"
|
|
elif gtype == "hvm":
|
|
label = "xen (fullvirt)"
|
|
elif domtype == "test":
|
|
if gtype == "xen":
|
|
label = "test (xen)"
|
|
elif gtype == "hvm":
|
|
label = "test (hvm)"
|
|
|
|
return label
|
|
|
|
|
|
def iface_in_use_by(conn, name):
|
|
use_str = ""
|
|
for i in conn.list_interface_names():
|
|
iface = conn.get_interface(i)
|
|
if name in iface.get_slave_names():
|
|
if use_str:
|
|
use_str += ", "
|
|
use_str += iface.get_name()
|
|
|
|
return use_str
|
|
|
|
|
|
def chkbox_helper(src, getcb, setcb, text1, text2=None,
|
|
alwaysrecord=False,
|
|
default=True,
|
|
chktext=_("Don't ask me again")):
|
|
"""
|
|
Helper to prompt user about proceeding with an operation
|
|
Returns True if the 'yes' or 'ok' button was selected, False otherwise
|
|
|
|
@alwaysrecord: Don't require user to select 'yes' to record chkbox value
|
|
@default: What value to return if getcb tells us not to prompt
|
|
"""
|
|
do_prompt = getcb()
|
|
if not do_prompt:
|
|
return default
|
|
|
|
res = src.err.warn_chkbox(text1=text1, text2=text2,
|
|
chktext=chktext,
|
|
buttons=Gtk.ButtonsType.YES_NO)
|
|
response, skip_prompt = res
|
|
if alwaysrecord or response:
|
|
setcb(not skip_prompt)
|
|
|
|
return response
|
|
|
|
|
|
def get_list_selection(widget):
|
|
selection = widget.get_selection()
|
|
active = selection.get_selected()
|
|
|
|
treestore, treeiter = active
|
|
if treeiter is not None:
|
|
return treestore[treeiter]
|
|
return None
|
|
|
|
|
|
def set_list_selection(widget, rownum):
|
|
path = str(rownum)
|
|
selection = widget.get_selection()
|
|
|
|
selection.unselect_all()
|
|
widget.set_cursor(path)
|
|
selection.select_path(path)
|
|
|
|
|
|
def default_uri(always_system=False):
|
|
if os.path.exists('/var/lib/xend'):
|
|
if (os.path.exists('/dev/xen/evtchn') or
|
|
os.path.exists("/proc/xen")):
|
|
return 'xen:///'
|
|
|
|
if (os.path.exists("/usr/bin/qemu") or
|
|
os.path.exists("/usr/bin/qemu-kvm") or
|
|
os.path.exists("/usr/bin/kvm") or
|
|
os.path.exists("/usr/libexec/qemu-kvm")):
|
|
if always_system or os.geteuid() == 0:
|
|
return "qemu:///system"
|
|
else:
|
|
return "qemu:///session"
|
|
return None
|
|
|
|
|
|
def exception_is_libvirt_error(e, error):
|
|
return (hasattr(libvirt, error) and
|
|
e.get_error_code() == getattr(libvirt, error))
|