virt-manager/virtManager/device/netlist.py

546 lines
18 KiB
Python

# Copyright (C) 2014 Red Hat, Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import collections
from gi.repository import Gtk
import virtinst
from virtinst import log
from ..lib import uiutil
from ..baseclass import vmmGObjectUI
NetDev = collections.namedtuple('Netdev', ['name', 'is_bridge', 'slave_names'])
class vmmNetworkList(vmmGObjectUI):
__gsignals__ = {
"changed": (vmmGObjectUI.RUN_FIRST, None, []),
"changed-vport": (vmmGObjectUI.RUN_FIRST, None, [])
}
def __init__(self, conn, builder, topwin):
vmmGObjectUI.__init__(self, "netlist.ui",
None, builder=builder, topwin=topwin)
self.conn = conn
self.builder.connect_signals({
"on_net_source_changed": self._on_net_source_changed,
"on_net_source_mode_changed": self._emit_changed,
"on_net_portgroup_changed": self._emit_changed,
"on_net_bridge_name_changed": self._emit_changed,
"on_vport_type_changed": self._emit_vport_changed,
"on_vport_managerid_changed": self._emit_vport_changed,
"on_vport_typeid_changed": self._emit_vport_changed,
"on_vport_typeidversion_changed": self._emit_vport_changed,
"on_vport_instanceid_changed": self._emit_vport_changed,
})
self._init_ui()
self.top_label = self.widget("net-source-label")
self.top_box = self.widget("net-source-box")
self.top_vport = self.widget("vport-expander")
def _cleanup(self):
self.conn.disconnect_by_obj(self)
self.conn = None
self.top_label.destroy()
self.top_box.destroy()
self.top_vport.destroy()
##########################
# Initialization methods #
##########################
def _init_ui(self):
# [ network type, source name, label, sensitive?, net is active,
# manual bridge, net instance]
model = Gtk.ListStore(str, str, str, bool, bool, bool, object)
combo = self.widget("net-source")
combo.set_model(model)
text = Gtk.CellRendererText()
combo.pack_start(text, True)
combo.add_attribute(text, 'text', 2)
combo.add_attribute(text, 'sensitive', 3)
combo = self.widget("net-source-mode")
# [xml value, label]
model = Gtk.ListStore(str, str)
combo.set_model(model)
uiutil.init_combo_text_column(combo, 1)
model.append(["bridge", _("Bridge")])
model.append(["vepa", "VEPA"])
model.append(["private", _("Private")])
model.append(["passthrough", _("Passthrough")])
combo.set_active(0)
combo = self.widget("net-portgroup")
# [xml value, label]
model = Gtk.ListStore(str, str)
combo.set_model(model)
uiutil.init_combo_text_column(combo, 1)
self.conn.connect("net-added", self._repopulate_network_list)
self.conn.connect("net-removed", self._repopulate_network_list)
self.conn.connect("interface-added", self._repopulate_network_list)
self.conn.connect("interface-removed", self._repopulate_network_list)
def _pretty_network_desc(self, nettype, source=None, netobj=None):
if nettype == virtinst.DeviceInterface.TYPE_USER:
return _("Usermode networking")
extra = None
if nettype == virtinst.DeviceInterface.TYPE_BRIDGE:
ret = _("Bridge")
elif nettype == virtinst.DeviceInterface.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 _build_source_row(self, nettype, source_name,
label, is_sensitive, is_running, manual_bridge=False, key=None):
return [nettype, source_name, label,
is_sensitive, is_running, manual_bridge,
key]
def _find_virtual_networks(self):
rows = []
vnet_bridges = []
default_label = None
for net in self.conn.list_nets():
nettype = virtinst.DeviceInterface.TYPE_VIRTUAL
label = self._pretty_network_desc(nettype, net.get_name(), net)
if not net.is_active():
label += " (%s)" % _("Inactive")
if net.get_xmlobj().virtualport_type == "openvswitch":
label += " (OpenVSwitch)"
if net.get_name() == "default":
default_label = label
rows.append(self._build_source_row(
nettype, net.get_name(), label, True,
net.is_active(), key=net.get_connkey()))
# 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 rows:
label = _("No virtual networks available")
rows.append(self._build_source_row(
None, None, label, False, False))
return rows, vnet_bridges, default_label
def _find_physical_devices(self, vnet_bridges):
rows = []
can_default = False
default_label = None
skip_ifaces = ["lo"]
vnet_taps = []
for vm in self.conn.list_vms():
for nic in vm.get_interface_devices_norefresh():
if nic.target_dev and nic.target_dev not in vnet_taps:
vnet_taps.append(nic.target_dev)
netdevs = {}
for iface in self.conn.list_interfaces():
name = iface.get_name()
netdevs[name] = NetDev(name, iface.is_bridge(),
iface.get_interface_names())
for nodedev in self.conn.filter_nodedevs("net"):
if nodedev.xmlobj.interface not in netdevs:
netdev = NetDev(nodedev.xmlobj.interface, False, [])
netdevs[nodedev.xmlobj.interface] = netdev
# For every bridge used by a virtual network, and any slaves of
# those devices, don't list them.
for vnet_bridge in vnet_bridges:
slave_names = netdevs.pop(vnet_bridge,
NetDev(None, None, [])).slave_names
for slave in slave_names:
netdevs.pop(slave, None)
for name, is_bridge, slave_names in list(netdevs.values()):
if ((name in vnet_taps) or
(name in [v + "-nic" for v in vnet_bridges]) or
(name in skip_ifaces)):
# Don't list this, as it is basically duplicating
# virtual net info
continue
sensitive = True
source_name = name
label = _("Host device %s") % (name)
if is_bridge:
nettype = virtinst.DeviceInterface.TYPE_BRIDGE
if slave_names:
extra = (_("Host device %s") % slave_names[0])
can_default = True
else:
extra = _("Empty bridge")
label = _("Bridge %s: %s") % (name, extra)
elif self.conn.is_qemu() or self.conn.is_test():
nettype = virtinst.DeviceInterface.TYPE_DIRECT
label += (": %s" % _("macvtap"))
else:
nettype = None
sensitive = False
source_name = None
label += (": %s" % _("Not bridged"))
if can_default and not default_label:
default_label = label
rows.append(self._build_source_row(
nettype, source_name, label, sensitive, True,
key=name))
return rows, default_label
def _populate_network_model(self, model):
model.clear()
def _add_manual_bridge_row():
manual_row = self._build_source_row(
None, None, _("Specify shared device name"),
True, False, manual_bridge=True)
model.append(manual_row)
if self.conn.is_qemu_session():
nettype = virtinst.DeviceInterface.TYPE_USER
r = self._build_source_row(
nettype, None, self._pretty_network_desc(nettype), True, True)
model.append(r)
_add_manual_bridge_row()
return
(vnets, vnet_bridges, default_net) = self._find_virtual_networks()
(iface_rows, default_bridge) = self._find_physical_devices(
vnet_bridges)
# Sorting is:
# 1) Bridges
# 2) Virtual networks
# 3) direct/macvtap
# 4) Disabled list entries
# Each category sorted alphabetically
bridges = [row for row in iface_rows if row[0] == "bridge"]
direct = [row for row in iface_rows if row[0] == "direct"]
disabled = [row for row in iface_rows if row[0] is None]
for rows in [bridges, vnets, direct, disabled]:
rows.sort(key=lambda r: r[2])
for row in rows:
model.append(row)
# 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
label = default_bridge or default_net
default = 0
if not len(model):
row = self._build_source_row(
None, None, _("No networking"), True, False)
model.insert(0, row)
default = 0
elif label:
default = [idx for idx, model_label in enumerate(model) if
model_label[2] == label][0]
_add_manual_bridge_row()
return default
def _check_network_is_running(self, net):
# Make sure VirtualNetwork is running
if not net.type == virtinst.DeviceInterface.TYPE_VIRTUAL:
return
devname = net.source
netobj = None
if net.type == virtinst.DeviceInterface.TYPE_VIRTUAL:
for n in self.conn.list_nets():
if n.get_name() == devname:
netobj = n
break
if not netobj or netobj.is_active():
return
res = self.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
# Try to start the network
try:
netobj.start()
log.debug("Started network '%s'", devname)
except Exception as e:
return self.err.show_err(_("Could not start virtual network "
"'%s': %s") % (devname, str(e)))
###############
# Public APIs #
###############
def get_network_row(self):
return uiutil.get_list_selected_row(self.widget("net-source"))
def get_network_selection(self):
bridge_entry = self.widget("net-bridge-name")
row = self.get_network_row()
if not row:
return None, None, None, None
net_type = row[0]
net_src = row[1]
net_check_bridge = row[5]
if net_check_bridge and bridge_entry:
net_type = virtinst.DeviceInterface.TYPE_BRIDGE
net_src = bridge_entry.get_text() or None
mode = None
if self.widget("net-source-mode").is_visible():
mode = uiutil.get_list_selection(self.widget("net-source-mode"))
portgroup = None
if self.widget("net-portgroup").is_visible():
portgroup = uiutil.get_list_selection(self.widget("net-portgroup"))
return net_type, net_src, mode, portgroup or None
def get_vport(self):
vport_type = self.widget("vport-type").get_text()
vport_managerid = self.widget("vport-managerid").get_text()
vport_typeid = self.widget("vport-typeid").get_text()
vport_idver = self.widget("vport-typeidversion").get_text()
vport_instid = self.widget("vport-instanceid").get_text()
return (vport_type, vport_managerid, vport_typeid,
vport_idver, vport_instid)
def build_device(self, macaddr, model=None):
nettype, devname, mode, portgroup = self.get_network_selection()
net = virtinst.DeviceInterface(self.conn.get_backend())
net.type = nettype
net.source = devname
net.macaddr = macaddr
net.model = model
net.source_mode = mode
net.portgroup = portgroup
if net.model == "spapr-vlan":
net.address.set_addrstr("spapr-vio")
if net.type == "direct":
(vport_type, vport_managerid, vport_typeid,
vport_idver, vport_instid) = self.get_vport()
net.virtualport.type = vport_type or None
net.virtualport.managerid = vport_managerid or None
net.virtualport.typeid = vport_typeid or None
net.virtualport.typeidversion = vport_idver or None
net.virtualport.instanceid = vport_instid or None
return net
def validate_device(self, net):
self._check_network_is_running(net)
net.validate()
def reset_state(self):
self._repopulate_network_list()
net_err = None
if (not self.conn.is_nodedev_capable() or
not self.conn.is_interface_capable()):
net_err = _("Libvirt version does not support "
"physical interface listing.")
net_warn = self.widget("net-source-warn")
net_warn.set_visible(bool(net_err))
net_warn.set_tooltip_text(net_err or "")
self.widget("net-bridge-name").set_text("")
self.widget("net-source-mode").set_active(0)
self.widget("net-portgroup").get_child().set_text("")
self.widget("vport-type").set_text("")
self.widget("vport-managerid").set_text("")
self.widget("vport-typeid").set_text("")
self.widget("vport-typeidversion").set_text("")
self.widget("vport-instanceid").set_text("")
def set_dev(self, net):
self.reset_state()
nettype = net.type
source = net.source
if net.network:
# If using type=network with a forward mode=bridge network,
# on domain startup the runtime XML will be changed to
# type=bridge and both source/@bridge and source/@network will
# be filled in. For our purposes, treat this as a type=network
source = net.network
nettype = "network"
source_mode = net.source_mode
is_direct = (net.type == "direct")
uiutil.set_list_selection(self.widget("net-source-mode"), source_mode)
# Virtualport config
self.widget("vport-expander").set_visible(is_direct)
vport = net.virtualport
self.widget("vport-type").set_text(vport.type or "")
self.widget("vport-managerid").set_text(str(vport.managerid or ""))
self.widget("vport-typeid").set_text(str(vport.typeid or ""))
self.widget("vport-typeidversion").set_text(
str(vport.typeidversion or ""))
self.widget("vport-instanceid").set_text(vport.instanceid or "")
# Find the matching row in the net list
combo = self.widget("net-source")
rowiter = None
for row in combo.get_model():
if row[0] == nettype and row[1] == source:
rowiter = row.iter
break
if not rowiter:
if nettype == "bridge":
rowiter = combo.get_model()[-1].iter
self.widget("net-bridge-name").set_text(source)
if not rowiter:
desc = self._pretty_network_desc(nettype, source)
combo.get_model().insert(0,
self._build_source_row(nettype, source, desc, True, True))
rowiter = combo.get_model()[0].iter
combo.set_active_iter(rowiter)
combo.emit("changed")
if net.portgroup:
uiutil.set_list_selection(self.widget("net-portgroup"), net.portgroup)
#############
# Listeners #
#############
def _emit_changed(self, *args, **kwargs):
ignore1 = args
ignore2 = kwargs
self.emit("changed")
def _emit_vport_changed(self, *args, **kwargs):
ignore1 = args
ignore2 = kwargs
self.emit("changed-vport")
def _repopulate_network_list(self, *args, **kwargs):
ignore1 = args
ignore2 = kwargs
netlist = self.widget("net-source")
current_label = uiutil.get_list_selection(netlist, column=2)
model = netlist.get_model()
if not model:
return
try:
if model:
netlist.set_model(None)
default_idx = self._populate_network_model(model)
finally:
netlist.set_model(model)
for row in netlist.get_model():
if current_label and row[2] == current_label:
netlist.set_active_iter(row.iter)
return
if default_idx is None:
default_idx = 0
netlist.set_active(default_idx)
def _populate_portgroups(self, portgroups):
combo = self.widget("net-portgroup")
model = combo.get_model()
model.clear()
default = None
for p in portgroups:
model.append([p.name, p.name])
if p.default:
default = p.name
uiutil.set_list_selection(combo, default)
def _on_net_source_changed(self, src):
ignore = src
self._emit_changed()
row = self.get_network_row()
if not row:
return
is_openvswitch = row[2].endswith("(OpenVSwitch)")
is_direct = (row[0] == virtinst.DeviceInterface.TYPE_DIRECT)
self.widget("vport-expander").set_visible(is_direct or is_openvswitch)
uiutil.set_grid_row_visible(self.widget("net-source-mode"), is_direct)
uiutil.set_grid_row_visible(
self.widget("net-macvtap-warn-box"), is_direct)
if is_direct and self.widget("net-source-mode").get_active() == -1:
self.widget("net-source-mode").set_active(0)
show_bridge = row[5]
uiutil.set_grid_row_visible(
self.widget("net-bridge-name"), show_bridge)
portgroups = []
connkey = row[6]
if connkey and row[0] == virtinst.DeviceInterface.TYPE_VIRTUAL:
portgroups = self.conn.get_net(connkey).get_xmlobj().portgroups
uiutil.set_grid_row_visible(
self.widget("net-portgroup"), bool(portgroups))
self._populate_portgroups(portgroups)