408 lines
13 KiB
Python
408 lines
13 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.
|
|
|
|
from gi.repository import Gtk
|
|
|
|
import virtinst
|
|
from virtinst import log
|
|
|
|
from ..lib import uiutil
|
|
from ..baseclass import vmmGObjectUI
|
|
|
|
|
|
NET_ROW_LABEL = 0
|
|
NET_ROW_SENSITIVE = 1
|
|
NET_ROW_DATA = 2
|
|
|
|
|
|
class _NetRowData:
|
|
@staticmethod
|
|
def build_row(*args, **kwargs):
|
|
rowdata = _NetRowData(*args, **kwargs)
|
|
return [rowdata.label, rowdata.is_sensitive, rowdata]
|
|
|
|
def __init__(self, nettype, source, label, is_sensitive, manual=False):
|
|
self.nettype = nettype
|
|
self.source = source
|
|
self.label = label
|
|
self.is_sensitive = is_sensitive
|
|
self.manual = manual
|
|
|
|
|
|
def _build_manual_row(nettype, label):
|
|
return _NetRowData.build_row(nettype, None, label, True, manual=True)
|
|
|
|
|
|
def _pretty_network_desc(nettype, source=None, netobj=None):
|
|
if nettype == virtinst.DeviceInterface.TYPE_USER:
|
|
return _("Usermode networking")
|
|
|
|
extra = None
|
|
if 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
|
|
|
|
|
|
class vmmNetworkList(vmmGObjectUI):
|
|
__gsignals__ = {
|
|
"changed": (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_portgroup_changed": self._emit_changed,
|
|
"on_net_bridge_name_changed": self._emit_changed,
|
|
})
|
|
|
|
self._init_ui()
|
|
self.top_label = self.widget("net-source-label")
|
|
self.top_box = self.widget("net-source-box")
|
|
|
|
def _cleanup(self):
|
|
self.conn.disconnect_by_obj(self)
|
|
self.conn = None
|
|
|
|
self.top_label.destroy()
|
|
self.top_box.destroy()
|
|
|
|
|
|
##########################
|
|
# Initialization methods #
|
|
##########################
|
|
|
|
def _init_ui(self):
|
|
fields = []
|
|
fields.insert(NET_ROW_LABEL, str)
|
|
fields.insert(NET_ROW_SENSITIVE, bool)
|
|
fields.insert(NET_ROW_DATA, object)
|
|
|
|
model = Gtk.ListStore(*fields)
|
|
combo = self.widget("net-source")
|
|
combo.set_model(model)
|
|
|
|
text = Gtk.CellRendererText()
|
|
combo.pack_start(text, True)
|
|
combo.add_attribute(text, 'text', NET_ROW_LABEL)
|
|
combo.add_attribute(text, 'sensitive', NET_ROW_SENSITIVE)
|
|
|
|
combo = self.widget("net-portgroup")
|
|
model = Gtk.ListStore(str)
|
|
combo.set_model(model)
|
|
uiutil.init_combo_text_column(combo, 0)
|
|
|
|
self.conn.connect("net-added", self._repopulate_network_list)
|
|
self.conn.connect("net-removed", self._repopulate_network_list)
|
|
|
|
def _find_virtual_networks(self):
|
|
rows = []
|
|
|
|
for net in self.conn.list_nets():
|
|
nettype = virtinst.DeviceInterface.TYPE_VIRTUAL
|
|
|
|
label = _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)"
|
|
|
|
row = _NetRowData.build_row(nettype, net.get_name(), label, True)
|
|
rows.append(row)
|
|
|
|
return rows
|
|
|
|
def _populate_network_model(self, model):
|
|
model.clear()
|
|
|
|
def _add_manual_bridge_row():
|
|
_nettype = virtinst.DeviceInterface.TYPE_BRIDGE
|
|
_label = _("Bridge device...")
|
|
model.append(_build_manual_row(_nettype, _label))
|
|
return len(model) - 1
|
|
|
|
def _add_manual_macvtap_row():
|
|
_label = _("Macvtap device...")
|
|
_nettype = virtinst.DeviceInterface.TYPE_DIRECT
|
|
model.append(_build_manual_row(_nettype, _label))
|
|
|
|
vnets = self._find_virtual_networks()
|
|
default_bridge = virtinst.DeviceInterface.default_bridge(
|
|
self.conn.get_backend())
|
|
|
|
add_usermode = False
|
|
if self.conn.is_qemu_unprivileged():
|
|
log.debug("Using unprivileged qemu, adding usermode net")
|
|
vnets = []
|
|
default_bridge = None
|
|
add_usermode = True
|
|
|
|
if add_usermode:
|
|
nettype = virtinst.DeviceInterface.TYPE_USER
|
|
label = _pretty_network_desc(nettype)
|
|
model.append(_NetRowData.build_row(nettype, None, label, True))
|
|
|
|
defaultnetidx = None
|
|
for row in sorted(vnets, key=lambda r: r[NET_ROW_LABEL]):
|
|
model.append(row)
|
|
if row[NET_ROW_DATA].source == "default":
|
|
defaultnetidx = len(model) - 1
|
|
|
|
bridgeidx = _add_manual_bridge_row()
|
|
_add_manual_macvtap_row()
|
|
|
|
# If there is a bridge device, default to that
|
|
if default_bridge:
|
|
self.widget("net-manual-source").set_text(default_bridge)
|
|
return bridgeidx
|
|
|
|
# If not, use 'default' network
|
|
if defaultnetidx is not None:
|
|
return defaultnetidx
|
|
|
|
# If not present, use first list entry
|
|
if bridgeidx == 0:
|
|
# This means we are defaulting to something that
|
|
# requires manual intervention. Raise the warning
|
|
self.widget("net-default-warn-box").show()
|
|
return 0
|
|
|
|
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:
|
|
netobj = self.conn.get_net_by_name(devname)
|
|
|
|
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 # pragma: no cover
|
|
|
|
# Try to start the network
|
|
try:
|
|
netobj.start()
|
|
log.debug("Started network '%s'", devname)
|
|
except Exception as e: # pragma: no cover
|
|
return self.err.show_err(
|
|
_("Could not start virtual network '%(device)s': %(error)s") % {
|
|
"device": devname,
|
|
"error": str(e),
|
|
})
|
|
|
|
def _find_rowiter_for_dev(self, net):
|
|
"""
|
|
Find the row in our current model that matches the passed in
|
|
net device (like populating the details UI for an existing VM).
|
|
If we don't find a match, we fake it a bit
|
|
"""
|
|
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"
|
|
|
|
combo = self.widget("net-source")
|
|
def _find_row(_nettype, _source, _manual):
|
|
for row in combo.get_model():
|
|
rowdata = row[NET_ROW_DATA]
|
|
if _nettype and rowdata.nettype != _nettype:
|
|
continue
|
|
if _source and rowdata.source != _source:
|
|
continue
|
|
if _manual and rowdata.manual != _manual:
|
|
continue # pragma: no cover
|
|
return row.iter
|
|
|
|
# Find the matching row in the net list
|
|
rowiter = _find_row(nettype, source, None)
|
|
if rowiter:
|
|
return rowiter
|
|
|
|
# If this is a bridge or macvtap device, show the
|
|
# manual source mode
|
|
if nettype in [virtinst.DeviceInterface.TYPE_BRIDGE,
|
|
virtinst.DeviceInterface.TYPE_DIRECT]:
|
|
rowiter = _find_row(nettype, None, True)
|
|
self.widget("net-manual-source").set_text(source or "")
|
|
if rowiter:
|
|
return rowiter
|
|
|
|
# This is some network type we don't know about. Generate
|
|
# a label for it and stuff it in the list
|
|
desc = _pretty_network_desc(nettype, source)
|
|
combo.get_model().insert(0,
|
|
_NetRowData.build_row(nettype, source, desc, True))
|
|
return combo.get_model()[0].iter
|
|
|
|
|
|
###############
|
|
# Public APIs #
|
|
###############
|
|
|
|
def _get_network_row_data(self):
|
|
return uiutil.get_list_selection(
|
|
self.widget("net-source"), column=NET_ROW_DATA)
|
|
|
|
def get_network_selection(self):
|
|
rowdata = self._get_network_row_data()
|
|
net_type = rowdata.nettype
|
|
net_src = rowdata.source
|
|
net_check_manual = rowdata.manual
|
|
|
|
if net_check_manual:
|
|
net_src = self.widget("net-manual-source").get_text() or None
|
|
|
|
mode = None
|
|
is_direct = (net_type == virtinst.DeviceInterface.TYPE_DIRECT)
|
|
if is_direct:
|
|
# This is generally the safest and most featureful default
|
|
mode = "bridge"
|
|
|
|
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
|
|
|
|
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
|
|
|
|
return net
|
|
|
|
def validate_device(self, net):
|
|
self._check_network_is_running(net)
|
|
virtinst.DeviceInterface.check_mac_in_use(net.conn, net.macaddr)
|
|
net.validate()
|
|
|
|
def reset_state(self):
|
|
self.widget("net-default-warn-box").set_visible(False)
|
|
self.widget("net-manual-source").set_text("")
|
|
self.widget("net-portgroup").get_child().set_text("")
|
|
self._repopulate_network_list()
|
|
|
|
def set_dev(self, net):
|
|
self.reset_state()
|
|
rowiter = self._find_rowiter_for_dev(net)
|
|
|
|
combo = self.widget("net-source")
|
|
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 _repopulate_network_list(self, *args, **kwargs):
|
|
ignore1 = args
|
|
ignore2 = kwargs
|
|
|
|
netlist = self.widget("net-source")
|
|
current_label = uiutil.get_list_selection(
|
|
netlist, column=NET_ROW_LABEL)
|
|
|
|
model = netlist.get_model()
|
|
if not model:
|
|
return # pragma: no cover
|
|
|
|
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[NET_ROW_LABEL] == current_label:
|
|
netlist.set_active_iter(row.iter)
|
|
return
|
|
|
|
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])
|
|
if p.default:
|
|
default = p.name
|
|
|
|
uiutil.set_list_selection(combo, default)
|
|
|
|
def _on_net_source_changed(self, src):
|
|
ignore = src
|
|
self._emit_changed()
|
|
rowdata = self._get_network_row_data()
|
|
if not rowdata:
|
|
return # pragma: no cover
|
|
|
|
nettype = rowdata.nettype
|
|
is_direct = (nettype == virtinst.DeviceInterface.TYPE_DIRECT)
|
|
is_virtual = (nettype == virtinst.DeviceInterface.TYPE_VIRTUAL)
|
|
|
|
uiutil.set_grid_row_visible(
|
|
self.widget("net-macvtap-warn-box"), is_direct)
|
|
|
|
show_bridge = rowdata.manual
|
|
uiutil.set_grid_row_visible(
|
|
self.widget("net-manual-source"), show_bridge)
|
|
|
|
portgroups = []
|
|
if is_virtual:
|
|
net = self.conn.get_net_by_name(rowdata.source)
|
|
portgroups = net.get_xmlobj().portgroups
|
|
|
|
uiutil.set_grid_row_visible(
|
|
self.widget("net-portgroup"), bool(portgroups))
|
|
self._populate_portgroups(portgroups)
|