404 lines
14 KiB
Python
404 lines
14 KiB
Python
|
# Copyright (C) 2007, 2013-2014 Red Hat, Inc.
|
||
|
# Copyright (C) 2007 Daniel P. Berrange <berrange@redhat.com>
|
||
|
#
|
||
|
# This work is licensed under the GNU GPLv2 or later.
|
||
|
# See the COPYING file in the top-level directory.
|
||
|
|
||
|
from gi.repository import Gtk
|
||
|
from gi.repository import Pango
|
||
|
|
||
|
from virtinst import log
|
||
|
|
||
|
from .lib import uiutil
|
||
|
from .asyncjob import vmmAsyncJob
|
||
|
from .baseclass import vmmGObjectUI
|
||
|
from .createnet import vmmCreateNetwork
|
||
|
from .xmleditor import vmmXMLEditor
|
||
|
|
||
|
|
||
|
EDIT_NET_IDS = (
|
||
|
EDIT_NET_NAME,
|
||
|
EDIT_NET_AUTOSTART,
|
||
|
EDIT_NET_XML,
|
||
|
) = list(range(3))
|
||
|
|
||
|
|
||
|
ICON_RUNNING = "state_running"
|
||
|
ICON_SHUTOFF = "state_shutoff"
|
||
|
|
||
|
|
||
|
class vmmHostNets(vmmGObjectUI):
|
||
|
def __init__(self, conn, builder, topwin):
|
||
|
vmmGObjectUI.__init__(self, "hostnets.ui",
|
||
|
None, builder=builder, topwin=topwin)
|
||
|
self.conn = conn
|
||
|
|
||
|
self._addnet = None
|
||
|
self._xmleditor = None
|
||
|
|
||
|
self._active_edits = set()
|
||
|
self.top_box = self.widget("top-box")
|
||
|
|
||
|
self.builder.connect_signals({
|
||
|
"on_net_add_clicked": self._add_network_cb,
|
||
|
"on_net_delete_clicked": self._delete_network_cb,
|
||
|
"on_net_stop_clicked": self._stop_network_cb,
|
||
|
"on_net_start_clicked": self._start_network_cb,
|
||
|
"on_net_apply_clicked": (lambda *x: self._net_apply()),
|
||
|
"on_net_list_changed": self._net_selected_cb,
|
||
|
"on_net_autostart_toggled": (lambda *x:
|
||
|
self._enable_net_apply(EDIT_NET_AUTOSTART)),
|
||
|
"on_net_name_changed": (lambda *x:
|
||
|
self._enable_net_apply(EDIT_NET_NAME)),
|
||
|
})
|
||
|
|
||
|
self._init_ui()
|
||
|
self._populate_networks()
|
||
|
self.conn.connect("net-added", self._conn_nets_changed_cb)
|
||
|
self.conn.connect("net-removed", self._conn_nets_changed_cb)
|
||
|
self.conn.connect("state-changed", self._conn_state_changed_cb)
|
||
|
|
||
|
|
||
|
#######################
|
||
|
# Standard UI methods #
|
||
|
#######################
|
||
|
|
||
|
def _cleanup(self):
|
||
|
self.conn = None
|
||
|
|
||
|
if self._addnet:
|
||
|
self._addnet.cleanup()
|
||
|
self._addnet = None
|
||
|
|
||
|
self._xmleditor.cleanup()
|
||
|
self._xmleditor = None
|
||
|
|
||
|
def close(self, ignore1=None, ignore2=None):
|
||
|
if self._addnet:
|
||
|
self._addnet.close()
|
||
|
|
||
|
|
||
|
###########
|
||
|
# UI init #
|
||
|
###########
|
||
|
|
||
|
def _init_ui(self):
|
||
|
self.widget("network-pages").set_show_tabs(False)
|
||
|
|
||
|
# [ unique, label, icon name, icon size, is_active ]
|
||
|
netListModel = Gtk.ListStore(str, str, str, int, bool)
|
||
|
self.widget("net-list").set_model(netListModel)
|
||
|
|
||
|
sel = self.widget("net-list").get_selection()
|
||
|
sel.set_select_function((lambda *x: self._confirm_changes()), None)
|
||
|
|
||
|
netCol = Gtk.TreeViewColumn(_("Networks"))
|
||
|
netCol.set_spacing(6)
|
||
|
net_txt = Gtk.CellRendererText()
|
||
|
net_txt.set_property("ellipsize", Pango.EllipsizeMode.END)
|
||
|
net_img = Gtk.CellRendererPixbuf()
|
||
|
netCol.pack_start(net_img, False)
|
||
|
netCol.pack_start(net_txt, True)
|
||
|
netCol.add_attribute(net_txt, 'text', 1)
|
||
|
netCol.add_attribute(net_txt, 'sensitive', 4)
|
||
|
netCol.add_attribute(net_img, 'icon-name', 2)
|
||
|
netCol.add_attribute(net_img, 'stock-size', 3)
|
||
|
self.widget("net-list").append_column(netCol)
|
||
|
netListModel.set_sort_column_id(1, Gtk.SortType.ASCENDING)
|
||
|
|
||
|
self._xmleditor = vmmXMLEditor(self.builder, self.topwin,
|
||
|
self.widget("net-details-align"),
|
||
|
self.widget("net-details"))
|
||
|
self._xmleditor.connect("changed",
|
||
|
lambda s: self._enable_net_apply(EDIT_NET_XML))
|
||
|
self._xmleditor.connect("xml-requested",
|
||
|
self._xmleditor_xml_requested_cb)
|
||
|
self._xmleditor.connect("xml-reset",
|
||
|
self._xmleditor_xml_reset_cb)
|
||
|
|
||
|
|
||
|
##############
|
||
|
# Public API #
|
||
|
##############
|
||
|
|
||
|
def refresh_page(self):
|
||
|
self._populate_networks()
|
||
|
self.conn.schedule_priority_tick(pollnet=True)
|
||
|
|
||
|
|
||
|
#################
|
||
|
# UI populating #
|
||
|
#################
|
||
|
|
||
|
def _refresh_conn_state(self):
|
||
|
conn_active = self.conn.is_active()
|
||
|
self.widget("net-add").set_sensitive(conn_active and
|
||
|
self.conn.is_network_capable())
|
||
|
|
||
|
if conn_active and not self.conn.is_network_capable():
|
||
|
self._set_error_page(
|
||
|
_("Libvirt connection does not support virtual network "
|
||
|
"management."))
|
||
|
|
||
|
if conn_active:
|
||
|
uiutil.set_list_selection_by_number(self.widget("net-list"), 0)
|
||
|
return
|
||
|
|
||
|
self._set_error_page(_("Connection not active."))
|
||
|
self._populate_networks()
|
||
|
|
||
|
def _current_network(self):
|
||
|
connkey = uiutil.get_list_selection(self.widget("net-list"))
|
||
|
return connkey and self.conn.get_net(connkey)
|
||
|
|
||
|
def _set_error_page(self, msg):
|
||
|
self.widget("network-pages").set_current_page(1)
|
||
|
self.widget("network-error-label").set_text(msg)
|
||
|
|
||
|
def _refresh_current_network(self):
|
||
|
net = self._current_network()
|
||
|
if not net:
|
||
|
self._set_error_page(_("No virtual network selected."))
|
||
|
return
|
||
|
|
||
|
self.widget("network-pages").set_current_page(0)
|
||
|
|
||
|
try:
|
||
|
self._populate_net_state(net)
|
||
|
except Exception as e:
|
||
|
log.exception(e)
|
||
|
self._set_error_page(_("Error selecting network: %s") % e)
|
||
|
self._disable_net_apply()
|
||
|
|
||
|
def _populate_networks(self):
|
||
|
net_list = self.widget("net-list")
|
||
|
curnet = self._current_network()
|
||
|
|
||
|
model = net_list.get_model()
|
||
|
# Prevent events while the model is modified
|
||
|
net_list.set_model(None)
|
||
|
try:
|
||
|
net_list.get_selection().unselect_all()
|
||
|
model.clear()
|
||
|
for net in self.conn.list_nets():
|
||
|
net.disconnect_by_obj(self)
|
||
|
net.connect("state-changed", self._net_state_changed_cb)
|
||
|
model.append([net.get_connkey(), net.get_name(), "network-idle",
|
||
|
Gtk.IconSize.LARGE_TOOLBAR,
|
||
|
bool(net.is_active())])
|
||
|
finally:
|
||
|
net_list.set_model(model)
|
||
|
|
||
|
uiutil.set_list_selection(net_list,
|
||
|
curnet and curnet.get_connkey() or None)
|
||
|
|
||
|
def _populate_net_ipv4_state(self, net):
|
||
|
(netstr,
|
||
|
(dhcpstart, dhcpend),
|
||
|
(routeaddr, routevia)) = net.get_ipv4_network()
|
||
|
|
||
|
self.widget("net-ipv4-expander").set_visible(bool(netstr))
|
||
|
if not netstr:
|
||
|
return
|
||
|
|
||
|
forward = net.get_ipv4_forward_mode()
|
||
|
self.widget("net-ipv4-forwarding-icon").set_from_stock(
|
||
|
forward and Gtk.STOCK_CONNECT or Gtk.STOCK_DISCONNECT,
|
||
|
Gtk.IconSize.MENU)
|
||
|
self.widget("net-ipv4-forwarding").set_text(net.pretty_forward_mode())
|
||
|
|
||
|
dhcpstr = _("Disabled")
|
||
|
if dhcpstart:
|
||
|
dhcpstr = dhcpstart + " - " + dhcpend
|
||
|
self.widget("net-ipv4-dhcp-range").set_text(dhcpstr)
|
||
|
self.widget("net-ipv4-network").set_text(netstr)
|
||
|
|
||
|
uiutil.set_grid_row_visible(
|
||
|
self.widget("net-ipv4-route"), bool(routevia))
|
||
|
if routevia:
|
||
|
routevia = routeaddr + ", gateway=" + routevia
|
||
|
self.widget("net-ipv4-route").set_text(routevia or "")
|
||
|
|
||
|
def _populate_net_ipv6_state(self, net):
|
||
|
(netstr,
|
||
|
(dhcpstart, dhcpend),
|
||
|
(routeaddr, routevia)) = net.get_ipv6_network()
|
||
|
|
||
|
self.widget("net-ipv6-expander").set_visible(bool(netstr))
|
||
|
self.widget("net-ipv6-forwarding-icon").set_from_stock(
|
||
|
netstr and Gtk.STOCK_CONNECT or Gtk.STOCK_DISCONNECT,
|
||
|
Gtk.IconSize.MENU)
|
||
|
|
||
|
if netstr:
|
||
|
prettymode = _("Routed network")
|
||
|
elif net.get_ipv6_enabled():
|
||
|
prettymode = _("Isolated network, internal routing only")
|
||
|
else:
|
||
|
prettymode = _("Isolated network, routing disabled")
|
||
|
self.widget("net-ipv6-forwarding").set_text(prettymode)
|
||
|
|
||
|
dhcpstr = _("Disabled")
|
||
|
if dhcpstart:
|
||
|
dhcpstr = dhcpstart + " - " + dhcpend
|
||
|
self.widget("net-ipv6-dhcp-range").set_text(dhcpstr)
|
||
|
self.widget("net-ipv6-network").set_text(netstr or "")
|
||
|
|
||
|
uiutil.set_grid_row_visible(
|
||
|
self.widget("net-ipv6-route"), bool(routevia))
|
||
|
if routevia:
|
||
|
routevia = routeaddr + ", gateway=" + routevia
|
||
|
self.widget("net-ipv6-route").set_text(routevia or "")
|
||
|
|
||
|
def _populate_net_state(self, net):
|
||
|
active = net.is_active()
|
||
|
|
||
|
self.widget("net-details").set_sensitive(True)
|
||
|
self.widget("net-name").set_text(net.get_name())
|
||
|
self.widget("net-name").set_editable(not active)
|
||
|
self.widget("net-device").set_text(net.get_bridge_device() or "")
|
||
|
self.widget("net-name-domain").set_text(net.get_name_domain() or "")
|
||
|
uiutil.set_grid_row_visible(self.widget("net-name-domain"),
|
||
|
bool(net.get_name_domain()))
|
||
|
|
||
|
state = active and _("Active") or _("Inactive")
|
||
|
icon = (active and ICON_RUNNING or ICON_SHUTOFF)
|
||
|
self.widget("net-state").set_text(state)
|
||
|
self.widget("net-state-icon").set_from_icon_name(icon,
|
||
|
Gtk.IconSize.BUTTON)
|
||
|
|
||
|
self.widget("net-start").set_sensitive(not active)
|
||
|
self.widget("net-stop").set_sensitive(active)
|
||
|
self.widget("net-delete").set_sensitive(not active)
|
||
|
|
||
|
autostart = net.get_autostart()
|
||
|
self.widget("net-autostart").set_active(autostart)
|
||
|
self.widget("net-autostart").set_label(_("On Boot"))
|
||
|
|
||
|
self._populate_net_ipv4_state(net)
|
||
|
self._populate_net_ipv6_state(net)
|
||
|
|
||
|
self._xmleditor.set_xml_from_libvirtobject(net)
|
||
|
|
||
|
|
||
|
#############################
|
||
|
# Network lifecycle actions #
|
||
|
#############################
|
||
|
|
||
|
def _delete_network_cb(self, src):
|
||
|
net = self._current_network()
|
||
|
if net is None:
|
||
|
return
|
||
|
|
||
|
result = self.err.yes_no(_("Are you sure you want to permanently "
|
||
|
"delete the network %s?") % net.get_name())
|
||
|
if not result:
|
||
|
return
|
||
|
|
||
|
log.debug("Deleting network '%s'", net.get_name())
|
||
|
vmmAsyncJob.simple_async_noshow(net.delete, [], self,
|
||
|
_("Error deleting network '%s'") % net.get_name())
|
||
|
|
||
|
def _start_network_cb(self, src):
|
||
|
net = self._current_network()
|
||
|
if net is None:
|
||
|
return
|
||
|
|
||
|
log.debug("Starting network '%s'", net.get_name())
|
||
|
vmmAsyncJob.simple_async_noshow(net.start, [], self,
|
||
|
_("Error starting network '%s'") % net.get_name())
|
||
|
|
||
|
def _stop_network_cb(self, src):
|
||
|
net = self._current_network()
|
||
|
if net is None:
|
||
|
return
|
||
|
|
||
|
log.debug("Stopping network '%s'", net.get_name())
|
||
|
vmmAsyncJob.simple_async_noshow(net.stop, [], self,
|
||
|
_("Error stopping network '%s'") % net.get_name())
|
||
|
|
||
|
def _add_network_cb(self, src):
|
||
|
log.debug("Launching 'Add Network'")
|
||
|
try:
|
||
|
if self._addnet is None:
|
||
|
self._addnet = vmmCreateNetwork(self.conn)
|
||
|
self._addnet.show(self.topwin)
|
||
|
except Exception as e:
|
||
|
self.err.show_err(_("Error launching network wizard: %s") % str(e))
|
||
|
|
||
|
|
||
|
############################
|
||
|
# Net apply/config actions #
|
||
|
############################
|
||
|
|
||
|
def _net_apply(self):
|
||
|
net = self._current_network()
|
||
|
if net is None:
|
||
|
return
|
||
|
|
||
|
log.debug("Applying changes for network '%s'", net.get_name())
|
||
|
try:
|
||
|
if EDIT_NET_AUTOSTART in self._active_edits:
|
||
|
auto = self.widget("net-autostart").get_active()
|
||
|
net.set_autostart(auto)
|
||
|
if EDIT_NET_NAME in self._active_edits:
|
||
|
net.define_name(self.widget("net-name").get_text())
|
||
|
self.idle_add(self._populate_networks)
|
||
|
if EDIT_NET_XML in self._active_edits:
|
||
|
net.define_xml(self._xmleditor.get_xml())
|
||
|
|
||
|
except Exception as e:
|
||
|
self.err.show_err(_("Error changing network settings: %s") % str(e))
|
||
|
return
|
||
|
finally:
|
||
|
self._disable_net_apply()
|
||
|
|
||
|
def _disable_net_apply(self):
|
||
|
self._active_edits = set()
|
||
|
self.widget("net-apply").set_sensitive(False)
|
||
|
self._xmleditor.details_changed = False
|
||
|
|
||
|
def _enable_net_apply(self, edittype):
|
||
|
self.widget("net-apply").set_sensitive(True)
|
||
|
self._active_edits.add(edittype)
|
||
|
self._xmleditor.details_changed = True
|
||
|
|
||
|
def _confirm_changes(self):
|
||
|
if (self.is_visible() and
|
||
|
self._active_edits and
|
||
|
self.err.confirm_unapplied_changes()):
|
||
|
self._net_apply()
|
||
|
|
||
|
self._disable_net_apply()
|
||
|
return True
|
||
|
|
||
|
|
||
|
################
|
||
|
# UI listeners #
|
||
|
################
|
||
|
|
||
|
def _conn_state_changed_cb(self, conn):
|
||
|
self._refresh_conn_state()
|
||
|
|
||
|
def _conn_nets_changed_cb(self, src, connkey):
|
||
|
self._populate_networks()
|
||
|
|
||
|
def _net_state_changed_cb(self, net):
|
||
|
# Update net state inline in the tree model
|
||
|
for row in self.widget("net-list").get_model():
|
||
|
if row[0] == net.get_connkey():
|
||
|
row[4] = net.is_active()
|
||
|
|
||
|
# If refreshed network is the current net, refresh the UI
|
||
|
curnet = self._current_network()
|
||
|
if curnet and curnet.get_connkey() == net.get_connkey():
|
||
|
self._refresh_current_network()
|
||
|
|
||
|
def _net_selected_cb(self, selection):
|
||
|
self._refresh_current_network()
|
||
|
|
||
|
def _xmleditor_xml_requested_cb(self, src):
|
||
|
self._refresh_current_network()
|
||
|
|
||
|
def _xmleditor_xml_reset_cb(self, src):
|
||
|
self._refresh_current_network()
|