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