mirror of
https://github.com/virt-manager/virt-manager.git
synced 2024-12-23 17:34:21 +03:00
932e6e00bb
And clean up the API usage to avoid these types of issues in the future.
489 lines
17 KiB
Python
489 lines
17 KiB
Python
#
|
|
# Copyright (C) 2014 Red Hat, Inc.
|
|
#
|
|
# 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
|
|
|
|
from gi.repository import Gtk
|
|
from gi.repository import GObject
|
|
|
|
import virtinst
|
|
from virtManager import uiutil
|
|
from virtManager.baseclass import vmmGObjectUI
|
|
|
|
|
|
class vmmNetworkList(vmmGObjectUI):
|
|
__gsignals__ = {
|
|
"changed": (GObject.SignalFlags.RUN_FIRST, None, []),
|
|
"changed-vport": (GObject.SignalFlags.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_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):
|
|
try:
|
|
self.conn.disconnect_by_func(self._repopulate_network_list)
|
|
self.conn.disconnect_by_func(self._repopulate_network_list)
|
|
self.conn.disconnect_by_func(self._repopulate_network_list)
|
|
self.conn.disconnect_by_func(self._repopulate_network_list)
|
|
except:
|
|
pass
|
|
|
|
self.conn = None
|
|
|
|
|
|
##########################
|
|
# 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.set_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)
|
|
|
|
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.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 _build_source_row(self, nettype, name,
|
|
label, is_sensitive, is_running, manual_bridge=False, key=None):
|
|
return [nettype, name, label,
|
|
is_sensitive, is_running, manual_bridge,
|
|
key]
|
|
|
|
def _find_virtual_networks(self):
|
|
vnet_dict = {}
|
|
vnet_bridges = []
|
|
hasNet = False
|
|
netIdxLabel = None
|
|
|
|
for uuid in self.conn.list_net_uuids():
|
|
net = self.conn.get_net(uuid)
|
|
nettype = virtinst.VirtualNetworkInterface.TYPE_VIRTUAL
|
|
|
|
label = self._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] = self._build_source_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] = self._build_source_row(
|
|
None, None, label, False, False)
|
|
|
|
return vnet_dict, vnet_bridges, netIdxLabel
|
|
|
|
def _find_physical_devices(self, vnet_bridges):
|
|
vnet_taps = []
|
|
for vm in self.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)
|
|
|
|
bridge_dict = {}
|
|
iface_dict = {}
|
|
hasShared = False
|
|
brIdxLabel = None
|
|
skip_ifaces = ["lo"]
|
|
|
|
for name in self.conn.list_net_device_paths():
|
|
br = self.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]) or
|
|
(br.get_name() in skip_ifaces)):
|
|
# 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)" % self._pretty_network_desc(nettype,
|
|
bridge_name)
|
|
else:
|
|
bridge_name = name
|
|
brlabel = _("(Empty bridge)")
|
|
else:
|
|
if self.conn.check_support(
|
|
self.conn.SUPPORT_CONN_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 = self._build_source_row(
|
|
nettype, bridge_name, label, sensitive, True,
|
|
key=br.get_name())
|
|
|
|
if sensitive:
|
|
bridge_dict[label] = row
|
|
else:
|
|
iface_dict[label] = row
|
|
|
|
return bridge_dict, iface_dict, brIdxLabel
|
|
|
|
def _populate_network_list(self):
|
|
net_list = self.widget("net-source")
|
|
model = net_list.get_model()
|
|
model.clear()
|
|
|
|
# For qemu:///session
|
|
if self.conn.is_qemu_session():
|
|
nettype = virtinst.VirtualNetworkInterface.TYPE_USER
|
|
r = self._build_source_row(
|
|
nettype, None, self._pretty_network_desc(nettype), True, True)
|
|
model.append(r)
|
|
net_list.set_active(0)
|
|
return
|
|
|
|
(vnet_dict, vnet_bridges, netIdxLabel) = self._find_virtual_networks()
|
|
(bridge_dict, iface_dict, brIdxLabel) = self._find_physical_devices(
|
|
vnet_bridges)
|
|
|
|
for indict in [bridge_dict, vnet_dict, iface_dict]:
|
|
keylist = indict.keys()
|
|
keylist.sort()
|
|
rowlist = [indict[k] for k in keylist]
|
|
for row in rowlist:
|
|
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 = brIdxLabel or netIdxLabel
|
|
|
|
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 in range(len(model)) if
|
|
model[idx][2] == label][0]
|
|
|
|
# After all is said and done, add a manual bridge option
|
|
manual_row = self._build_source_row(
|
|
None, None, _("Specify shared device name"),
|
|
True, False, manual_bridge=True)
|
|
model.append(manual_row)
|
|
|
|
net_list.set_active(default)
|
|
|
|
|
|
###############
|
|
# Public APIs #
|
|
###############
|
|
|
|
def get_network_row(self):
|
|
return uiutil.get_list_selection(self.widget("net-source"), None)
|
|
|
|
def get_network_selection(self):
|
|
bridge_entry = self.widget("net-bridge-name")
|
|
row = self.get_network_row()
|
|
if not row:
|
|
return 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.VirtualNetworkInterface.TYPE_BRIDGE
|
|
net_src = bridge_entry.get_text()
|
|
|
|
mode = None
|
|
if self.widget("net-source-mode").is_visible():
|
|
mode = uiutil.get_list_selection(self.widget("net-source-mode"), 0)
|
|
|
|
return net_type, net_src, mode
|
|
|
|
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 validate_network(self, macaddr, model=None):
|
|
nettype, devname, mode = self.get_network_selection()
|
|
if nettype is None:
|
|
return None
|
|
|
|
net = None
|
|
|
|
# Make sure VirtualNetwork is running
|
|
netobj = None
|
|
if nettype == virtinst.VirtualNetworkInterface.TYPE_VIRTUAL:
|
|
for net in self.conn.nets.values():
|
|
if net.get_name() == devname:
|
|
netobj = net
|
|
break
|
|
|
|
if netobj and not netobj.is_active():
|
|
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 False
|
|
|
|
# Try to start the network
|
|
try:
|
|
netobj.start()
|
|
netobj.tick()
|
|
logging.info("Started network '%s'", devname)
|
|
except Exception, e:
|
|
return self.err.show_err(_("Could not start virtual network "
|
|
"'%s': %s") % (devname, str(e)))
|
|
|
|
# Create network device
|
|
try:
|
|
net = virtinst.VirtualNetworkInterface(self.conn.get_backend())
|
|
net.type = nettype
|
|
net.source = devname
|
|
net.macaddr = macaddr
|
|
net.model = model
|
|
net.source_mode = mode
|
|
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
|
|
except Exception, e:
|
|
return self.err.val_err(_("Error with network parameters."), e)
|
|
|
|
# Make sure there is no mac address collision
|
|
isfatal, errmsg = net.is_conflict_net(net.conn, net.macaddr)
|
|
if isfatal:
|
|
return self.err.val_err(_("Mac address collision."), errmsg)
|
|
elif errmsg is not None:
|
|
retv = self.err.yes_no(_("Mac address collision."),
|
|
_("%s Are you sure you want to use this address?") % errmsg)
|
|
if not retv:
|
|
return False
|
|
|
|
return net
|
|
|
|
def reset_state(self):
|
|
self._populate_network_list()
|
|
|
|
net_warn = self.widget("net-source-warn")
|
|
net_err = self.conn.netdev_error
|
|
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("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
|
|
source_mode = net.source_mode
|
|
is_direct = (net.type == "direct")
|
|
|
|
uiutil.set_combo_entry(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")
|
|
|
|
|
|
#############
|
|
# Listeners #
|
|
#############
|
|
|
|
def _emit_changed(self, *args, **kwargs):
|
|
ignore = args
|
|
ignore = kwargs
|
|
self.emit("changed")
|
|
|
|
def _emit_vport_changed(self, *args, **kwargs):
|
|
ignore = args
|
|
ignore = kwargs
|
|
self.emit("changed-vport")
|
|
|
|
def _repopulate_network_list(self, *args, **kwargs):
|
|
ignore = args
|
|
ignore = kwargs
|
|
|
|
netlist = self.widget("net-source")
|
|
label = uiutil.get_list_selection(netlist, 2)
|
|
self._populate_network_list()
|
|
|
|
for row in netlist.get_model():
|
|
if label and row[2] == label:
|
|
netlist.set_active_iter(row.iter)
|
|
return
|
|
|
|
def _on_net_source_changed(self, src):
|
|
ignore = src
|
|
self._emit_changed()
|
|
row = self.get_network_row()
|
|
if not row:
|
|
return
|
|
|
|
is_direct = (row[0] == virtinst.VirtualNetworkInterface.TYPE_DIRECT)
|
|
|
|
self.widget("vport-expander").set_visible(is_direct)
|
|
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)
|