1# Copyright (C) 2014 Red Hat, Inc.
2#
3# This work is licensed under the GNU GPLv2 or later.
4# See the COPYING file in the top-level directory.
5
6from gi.repository import Gtk
7
8import virtinst
9from virtinst import log
10
11from ..lib import uiutil
12from ..baseclass import vmmGObjectUI
13
14
15NET_ROW_TYPE = 0
16NET_ROW_SOURCE = 1
17NET_ROW_LABEL = 2
18NET_ROW_SENSITIVE = 3
19NET_ROW_MANUAL = 4
20
21
22def _build_row(nettype, source_name,
23        label, is_sensitive, manual=False):
24    row = []
25    row.insert(NET_ROW_TYPE, nettype)
26    row.insert(NET_ROW_SOURCE, source_name)
27    row.insert(NET_ROW_LABEL, label)
28    row.insert(NET_ROW_SENSITIVE, is_sensitive)
29    row.insert(NET_ROW_MANUAL, manual)
30    return row
31
32
33def _build_manual_row(nettype, label):
34    return _build_row(nettype, None, label, True, manual=True)
35
36
37def _pretty_network_desc(nettype, source=None, netobj=None):
38    if nettype == virtinst.DeviceInterface.TYPE_USER:
39        return _("Usermode networking")
40
41    extra = None
42    if nettype == virtinst.DeviceInterface.TYPE_VIRTUAL:
43        ret = _("Virtual network")
44        if netobj:
45            extra = ": %s" % netobj.pretty_forward_mode()
46    else:
47        ret = nettype.capitalize()
48
49    if source:
50        ret += " '%s'" % source
51    if extra:
52        ret += " %s" % extra
53
54    return ret
55
56
57class vmmNetworkList(vmmGObjectUI):
58    __gsignals__ = {
59        "changed": (vmmGObjectUI.RUN_FIRST, None, []),
60    }
61
62    def __init__(self, conn, builder, topwin):
63        vmmGObjectUI.__init__(self, "netlist.ui",
64                              None, builder=builder, topwin=topwin)
65        self.conn = conn
66
67        self.builder.connect_signals({
68            "on_net_source_changed": self._on_net_source_changed,
69            "on_net_bridge_name_changed": self._emit_changed,
70        })
71
72        self._init_ui()
73        self.top_label = self.widget("net-source-label")
74        self.top_box = self.widget("net-source-box")
75
76    def _cleanup(self):
77        self.conn.disconnect_by_obj(self)
78        self.conn = None
79
80        self.top_label.destroy()
81        self.top_box.destroy()
82
83
84    ##########################
85    # Initialization methods #
86    ##########################
87
88    def _init_ui(self):
89        fields = []
90        fields.insert(NET_ROW_TYPE, str)
91        fields.insert(NET_ROW_SOURCE, str)
92        fields.insert(NET_ROW_LABEL, str)
93        fields.insert(NET_ROW_SENSITIVE, bool)
94        fields.insert(NET_ROW_MANUAL, bool)
95
96        model = Gtk.ListStore(*fields)
97        combo = self.widget("net-source")
98        combo.set_model(model)
99
100        text = Gtk.CellRendererText()
101        combo.pack_start(text, True)
102        combo.add_attribute(text, 'text', NET_ROW_LABEL)
103        combo.add_attribute(text, 'sensitive', NET_ROW_SENSITIVE)
104
105        self.conn.connect("net-added", self._repopulate_network_list)
106        self.conn.connect("net-removed", self._repopulate_network_list)
107
108    def _find_virtual_networks(self):
109        rows = []
110
111        for net in self.conn.list_nets():
112            nettype = virtinst.DeviceInterface.TYPE_VIRTUAL
113
114            label = _pretty_network_desc(nettype, net.get_name(), net)
115            if not net.is_active():
116                label += " (%s)" % _("Inactive")
117
118            if net.get_xmlobj().virtualport_type == "openvswitch":
119                label += " (OpenVSwitch)"
120
121            row = _build_row(nettype, net.get_name(), label, True)
122            rows.append(row)
123
124        return rows
125
126    def _populate_network_model(self, model):
127        model.clear()
128
129        def _add_manual_bridge_row():
130            _nettype = virtinst.DeviceInterface.TYPE_BRIDGE
131            _label = _("Bridge device...")
132            model.append(_build_manual_row(_nettype, _label))
133            return len(model) - 1
134
135        def _add_manual_macvtap_row():
136            _label = _("Macvtap device...")
137            _nettype = virtinst.DeviceInterface.TYPE_DIRECT
138            model.append(_build_manual_row(_nettype, _label))
139
140        vnets = self._find_virtual_networks()
141        default_bridge = virtinst.DeviceInterface.default_bridge(
142                self.conn.get_backend())
143
144        add_usermode = False
145        if self.conn.is_qemu_unprivileged():
146            log.debug("Using unprivileged qemu, adding usermode net")
147            vnets = []
148            default_bridge = None
149            add_usermode = True
150
151        if add_usermode:
152            nettype = virtinst.DeviceInterface.TYPE_USER
153            label = _pretty_network_desc(nettype)
154            model.append(_build_row(nettype, None, label, True))
155
156        defaultnetidx = None
157        for row in sorted(vnets, key=lambda r: r[NET_ROW_LABEL]):
158            model.append(row)
159            if row[NET_ROW_SOURCE] == "default":
160                defaultnetidx = len(model) - 1
161
162        bridgeidx = _add_manual_bridge_row()
163        _add_manual_macvtap_row()
164
165        # If there is a bridge device, default to that
166        if default_bridge:
167            self.widget("net-manual-source").set_text(default_bridge)
168            return bridgeidx
169
170        # If not, use 'default' network
171        if defaultnetidx is not None:
172            return defaultnetidx
173
174        # If not present, use first list entry
175        if bridgeidx == 0:
176            # This means we are defaulting to something that
177            # requires manual intervention. Raise the warning
178            self.widget("net-default-warn-box").show()
179        return 0
180
181    def _check_network_is_running(self, net):
182        # Make sure VirtualNetwork is running
183        if not net.type == virtinst.DeviceInterface.TYPE_VIRTUAL:
184            return
185        devname = net.source
186
187        netobj = None
188        if net.type == virtinst.DeviceInterface.TYPE_VIRTUAL:
189            netobj = self.conn.get_net_by_name(devname)
190
191        if not netobj or netobj.is_active():
192            return
193
194        res = self.err.yes_no(_("Virtual Network is not active."),
195            _("Virtual Network '%s' is not active. "
196              "Would you like to start the network "
197              "now?") % devname)
198        if not res:
199            return  # pragma: no cover
200
201        # Try to start the network
202        try:
203            netobj.start()
204            log.debug("Started network '%s'", devname)
205        except Exception as e:  # pragma: no cover
206            return self.err.show_err(
207                _("Could not start virtual network '%(device)s': %(error)s") % {
208                    "device": devname,
209                    "error": str(e),
210                })
211
212    def _find_rowiter_for_dev(self, net):
213        """
214        Find the row in our current model that matches the passed in
215        net device (like populating the details UI for an existing VM).
216        If we don't find a match, we fake it a bit
217        """
218        nettype = net.type
219        source = net.source
220        if net.network:
221            # If using type=network with a forward mode=bridge network,
222            # on domain startup the runtime XML will be changed to
223            # type=bridge and both source/@bridge and source/@network will
224            # be filled in. For our purposes, treat this as a type=network
225            source = net.network
226            nettype = "network"
227
228        combo = self.widget("net-source")
229        def _find_row(_nettype, _source, _manual):
230            for row in combo.get_model():
231                if _nettype and row[NET_ROW_TYPE] != _nettype:
232                    continue
233                if _source and row[NET_ROW_SOURCE] != _source:
234                    continue
235                if _manual and row[NET_ROW_MANUAL] != _manual:
236                    continue  # pragma: no cover
237                return row.iter
238
239        # Find the matching row in the net list
240        rowiter = _find_row(nettype, source, None)
241        if rowiter:
242            return rowiter
243
244        # If this is a bridge or macvtap device, show the
245        # manual source mode
246        if nettype in [virtinst.DeviceInterface.TYPE_BRIDGE,
247                       virtinst.DeviceInterface.TYPE_DIRECT]:
248            rowiter = _find_row(nettype, None, True)
249            self.widget("net-manual-source").set_text(source or "")
250            if rowiter:
251                return rowiter
252
253        # This is some network type we don't know about. Generate
254        # a label for it and stuff it in the list
255        desc = _pretty_network_desc(nettype, source)
256        combo.get_model().insert(0,
257            _build_row(nettype, source, desc, True))
258        return combo.get_model()[0].iter
259
260
261    ###############
262    # Public APIs #
263    ###############
264
265    def _get_network_row(self):
266        return uiutil.get_list_selected_row(self.widget("net-source"))
267
268    def get_network_selection(self):
269        row = self._get_network_row()
270        net_type = row[NET_ROW_TYPE]
271        net_src = row[NET_ROW_SOURCE]
272        net_check_manual = row[NET_ROW_MANUAL]
273
274        if net_check_manual:
275            net_src = self.widget("net-manual-source").get_text() or None
276
277        mode = None
278        is_direct = (net_type == virtinst.DeviceInterface.TYPE_DIRECT)
279        if is_direct:
280            # This is generally the safest and most featureful default
281            mode = "bridge"
282
283        return net_type, net_src, mode
284
285    def build_device(self, macaddr, model=None):
286        nettype, devname, mode = self.get_network_selection()
287
288        net = virtinst.DeviceInterface(self.conn.get_backend())
289        net.type = nettype
290        net.source = devname
291        net.macaddr = macaddr
292        net.model = model
293        net.source_mode = mode
294
295        return net
296
297    def validate_device(self, net):
298        self._check_network_is_running(net)
299        virtinst.DeviceInterface.check_mac_in_use(net.conn, net.macaddr)
300        net.validate()
301
302    def reset_state(self):
303        self.widget("net-default-warn-box").set_visible(False)
304        self.widget("net-manual-source").set_text("")
305        self._repopulate_network_list()
306
307    def set_dev(self, net):
308        self.reset_state()
309        rowiter = self._find_rowiter_for_dev(net)
310
311        combo = self.widget("net-source")
312        combo.set_active_iter(rowiter)
313        combo.emit("changed")
314
315
316    #############
317    # Listeners #
318    #############
319
320    def _emit_changed(self, *args, **kwargs):
321        ignore1 = args
322        ignore2 = kwargs
323        self.emit("changed")
324
325    def _repopulate_network_list(self, *args, **kwargs):
326        ignore1 = args
327        ignore2 = kwargs
328
329        netlist = self.widget("net-source")
330        current_label = uiutil.get_list_selection(netlist, column=2)
331
332        model = netlist.get_model()
333        if not model:
334            return  # pragma: no cover
335
336        try:
337            if model:
338                netlist.set_model(None)
339                default_idx = self._populate_network_model(model)
340        finally:
341            netlist.set_model(model)
342
343        for row in netlist.get_model():
344            if current_label and row[2] == current_label:
345                netlist.set_active_iter(row.iter)
346                return
347
348        netlist.set_active(default_idx)
349
350
351    def _on_net_source_changed(self, src):
352        ignore = src
353        self._emit_changed()
354        row = self._get_network_row()
355        if not row:
356            return  # pragma: no cover
357
358        nettype = row[NET_ROW_TYPE]
359        is_direct = (nettype == virtinst.DeviceInterface.TYPE_DIRECT)
360        uiutil.set_grid_row_visible(
361            self.widget("net-macvtap-warn-box"), is_direct)
362
363        show_bridge = row[NET_ROW_MANUAL]
364        uiutil.set_grid_row_visible(
365            self.widget("net-manual-source"), show_bridge)
366