xenserver: Fix bringing up VLAN PIFs.
[openvswitch] / xenserver / opt_xensource_libexec_InterfaceReconfigureVswitch.py
1 # Copyright (c) 2008,2009 Citrix Systems, Inc.
2 # Copyright (c) 2009,2010 Nicira Networks.
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU Lesser General Public License as published
6 # by the Free Software Foundation; version 2.1 only. with the special
7 # exception on linking described in file LICENSE.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU Lesser General Public License for more details.
13 #
14 from InterfaceReconfigure import *
15
16 #
17 # Bare Network Devices -- network devices without IP configuration
18 #
19
20 def netdev_down(netdev):
21     """Bring down a bare network device"""
22     if not netdev_exists(netdev):
23         log("netdev: down: device %s does not exist, ignoring" % netdev)
24         return
25     run_command(["/sbin/ifconfig", netdev, 'down'])
26
27 def netdev_up(netdev, mtu=None):
28     """Bring up a bare network device"""
29     if not netdev_exists(netdev):
30         raise Error("netdev: up: device %s does not exist" % netdev)
31
32     if mtu:
33         mtu = ["mtu", mtu]
34     else:
35         mtu = []
36
37     run_command(["/sbin/ifconfig", netdev, 'up'] + mtu)
38
39 #
40 # PIF miscellanea
41 #
42
43 def pif_currently_in_use(pif):
44     """Determine if a PIF is currently in use.
45
46     A PIF is determined to be currently in use if
47     - PIF.currently-attached is true
48     - Any bond master is currently attached
49     - Any VLAN master is currently attached
50     """
51     rec = db().get_pif_record(pif)
52     if rec['currently_attached']:
53         log("configure_datapath: %s is currently attached" % (pif_netdev_name(pif)))
54         return True
55     for b in pif_get_bond_masters(pif):
56         if pif_currently_in_use(b):
57             log("configure_datapath: %s is in use by BOND master %s" % (pif_netdev_name(pif),pif_netdev_name(b)))
58             return True
59     for v in pif_get_vlan_masters(pif):
60         if pif_currently_in_use(v):
61             log("configure_datapath: %s is in use by VLAN master %s" % (pif_netdev_name(pif),pif_netdev_name(v)))
62             return True
63     return False
64
65 #
66 # Datapath Configuration
67 #
68
69 def pif_datapath(pif):
70     """Return the datapath PIF associated with PIF.
71 A non-VLAN PIF is its own datapath PIF, except that a bridgeless PIF has
72 no datapath PIF at all.
73 A VLAN PIF's datapath PIF is its VLAN slave's datapath PIF.
74 """
75     if pif_is_vlan(pif):
76         return pif_datapath(pif_get_vlan_slave(pif))
77
78     pifrec = db().get_pif_record(pif)
79     nwrec = db().get_network_record(pifrec['network'])
80     if not nwrec['bridge']:
81         return None
82     else:
83         return pif
84
85 def datapath_get_physical_pifs(pif):
86     """Return the PIFs for the physical network device(s) associated with a datapath PIF.
87 For a bond master PIF, these are the bond slave PIFs.
88 For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
89
90 A VLAN PIF cannot be a datapath PIF.
91 """
92     if pif_is_vlan(pif):
93         # Seems like overkill...
94         raise Error("get-physical-pifs should not get passed a VLAN")
95     elif pif_is_bond(pif):
96         return pif_get_bond_slaves(pif)
97     else:
98         return [pif]
99
100 def datapath_deconfigure_physical(netdev):
101     return ['--', '--with-iface', '--if-exists', 'del-port', netdev]
102
103 def datapath_configure_bond(pif,slaves):
104     bridge = pif_bridge_name(pif)
105     pifrec = db().get_pif_record(pif)
106     interface = pif_netdev_name(pif)
107
108     argv = ['--', '--fake-iface', 'add-bond', bridge, interface]
109     for slave in slaves:
110         argv += [pif_netdev_name(slave)]
111
112     # XXX need ovs-vsctl support
113     #if pifrec['MAC'] != "":
114     #    argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
115
116     # Bonding options.
117     bond_options = {
118         "mode":   "balance-slb",
119         "miimon": "100",
120         "downdelay": "200",
121         "updelay": "31000",
122         "use_carrier": "1",
123         }
124     # override defaults with values from other-config whose keys
125     # being with "bond-"
126     oc = pifrec['other_config']
127     overrides = filter(lambda (key,val):
128                            key.startswith("bond-"), oc.items())
129     overrides = map(lambda (key,val): (key[5:], val), overrides)
130     bond_options.update(overrides)
131     for (name,val) in bond_options.items():
132         # XXX need ovs-vsctl support for bond options
133         #argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
134         pass
135     return argv
136
137 def datapath_deconfigure_bond(netdev):
138     return ['--', '--with-iface', '--if-exists', 'del-port', netdev]
139
140 def datapath_deconfigure_ipdev(interface):
141     return ['--', '--with-iface', '--if-exists', 'del-port', interface]
142
143 def datapath_modify_config(commands):
144     #log("modifying configuration:")
145     #for c in commands:
146     #    log("  %s" % c)
147             
148     rc = run_command(['/usr/bin/ovs-vsctl'] + ['--timeout=20']
149                      + [c for c in commands if not c.startswith('#')])
150     if not rc:       
151         raise Error("Failed to modify vswitch configuration")
152     return True
153
154 #
155 # Toplevel Datapath Configuration.
156 #
157
158 def configure_datapath(pif):
159     """Bring up the configuration for 'pif', which must not be a VLAN PIF, by:
160     - Tearing down other PIFs that use the same physical devices as 'pif'.
161     - Ensuring that 'pif' itself is set up.
162     - *Not* tearing down any PIFs that are stacked on top of 'pif' (i.e. VLANs
163       on top of 'pif'.
164
165     Returns a tuple containing
166     - A list containing the necessary vsctl command line arguments
167     - A list of additional devices which should be brought up after
168       the configuration is applied.
169     """
170
171     vsctl_argv = []
172     extra_up_ports = []
173
174     bridge = pif_bridge_name(pif)
175
176     physical_devices = datapath_get_physical_pifs(pif)
177
178     # Determine additional devices to deconfigure.
179     #
180     # Given all physical devices which are part of this PIF we need to
181     # consider:
182     # - any additional bond which a physical device is part of.
183     # - any additional physical devices which are part of an additional bond.
184     #
185     # Any of these which are not currently in use should be brought
186     # down and deconfigured.
187     extra_down_bonds = []
188     extra_down_ports = []
189     for p in physical_devices:
190         for bond in pif_get_bond_masters(p):
191             if bond == pif:
192                 log("configure_datapath: leaving bond %s up" % pif_netdev_name(bond))
193                 continue
194             if bond in extra_down_bonds:
195                 continue
196             if db().get_pif_record(bond)['currently_attached']:
197                 log("configure_datapath: implicitly tearing down currently-attached bond %s" % pif_netdev_name(bond))
198
199             extra_down_bonds += [bond]
200
201             for s in pif_get_bond_slaves(bond):
202                 if s in physical_devices:
203                     continue
204                 if s in extra_down_ports:
205                     continue
206                 if pif_currently_in_use(s):
207                     continue
208                 extra_down_ports += [s]
209
210     log("configure_datapath: bridge      - %s" % bridge)
211     log("configure_datapath: physical    - %s" % [pif_netdev_name(p) for p in physical_devices])
212     log("configure_datapath: extra ports - %s" % [pif_netdev_name(p) for p in extra_down_ports])
213     log("configure_datapath: extra bonds - %s" % [pif_netdev_name(p) for p in extra_down_bonds])
214
215     # Need to fully deconfigure any bridge which any of the:
216     # - physical devices
217     # - bond devices
218     # - sibling devices
219     # refers to
220     for brpif in physical_devices + extra_down_ports + extra_down_bonds:
221         if brpif == pif:
222             continue
223         b = pif_bridge_name(brpif)
224         #ifdown(b)
225         # XXX
226         netdev_down(b)
227         vsctl_argv += ['# remove bridge %s' % b]
228         vsctl_argv += ['--', '--if-exists', 'del-br', b]
229
230     for n in extra_down_ports:
231         dev = pif_netdev_name(n)
232         vsctl_argv += ['# deconfigure sibling physical device %s' % dev]
233         vsctl_argv += datapath_deconfigure_physical(dev)
234         netdev_down(dev)
235
236     for n in extra_down_bonds:
237         dev = pif_netdev_name(n)
238         vsctl_argv += ['# deconfigure bond device %s' % dev]
239         vsctl_argv += datapath_deconfigure_bond(dev)
240         netdev_down(dev)
241
242     for p in physical_devices:
243         dev = pif_netdev_name(p)
244         vsctl_argv += ['# deconfigure physical port %s' % dev]
245         vsctl_argv += datapath_deconfigure_physical(dev)
246
247     vsctl_argv += ['--', '--may-exist', 'add-br', bridge]
248
249     if len(physical_devices) > 1:
250         vsctl_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
251         vsctl_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
252         vsctl_argv += ['# configure bond %s' % pif_netdev_name(pif)]
253         vsctl_argv += datapath_configure_bond(pif, physical_devices)
254         extra_up_ports += [pif_netdev_name(pif)]
255     else:
256         iface = pif_netdev_name(physical_devices[0])
257         vsctl_argv += ['# add physical device %s' % iface]
258         vsctl_argv += ['--', '--may-exist', 'add-port', bridge, iface]
259
260     return vsctl_argv,extra_up_ports
261
262 def deconfigure_datapath(pif):
263     vsctl_argv = []
264
265     bridge = pif_bridge_name(pif)
266
267     physical_devices = datapath_get_physical_pifs(pif)
268
269     log("deconfigure_datapath: bridge           - %s" % bridge)
270     log("deconfigure_datapath: physical devices - %s" % [pif_netdev_name(p) for p in physical_devices])
271
272     for p in physical_devices:
273         dev = pif_netdev_name(p)
274         vsctl_argv += ['# deconfigure physical port %s' % dev]
275         vsctl_argv += datapath_deconfigure_physical(dev)
276         netdev_down(dev)
277
278     if len(physical_devices) > 1:
279         vsctl_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
280         vsctl_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
281
282     vsctl_argv += ['# deconfigure bridge %s' % bridge]
283     vsctl_argv += ['--', '--if-exists', 'del-br', bridge]
284
285     return vsctl_argv
286
287 #
288 #
289 #
290
291 class DatapathVswitch(Datapath):
292     def __init__(self, pif):
293         Datapath.__init__(self, pif)
294         self._dp = pif_datapath(pif)
295         self._ipdev = pif_ipdev_name(pif)
296
297         if pif_is_vlan(pif) and not self._dp:
298             raise Error("Unbridged VLAN devices not implemented yet")
299         
300         log("Configured for Vswitch datapath")
301
302     def configure_ipdev(self, cfg):
303         cfg.write("TYPE=Ethernet\n")
304
305     def preconfigure(self, parent):
306         vsctl_argv = []
307         extra_ports = []
308
309         pifrec = db().get_pif_record(self._pif)
310         dprec = db().get_pif_record(self._dp)
311
312         ipdev = self._ipdev
313         c,e = configure_datapath(self._dp)
314         bridge = pif_bridge_name(self._pif)
315         vsctl_argv += c
316         extra_ports += e
317
318         if pif_is_vlan(self._pif):
319             # XXX this is only needed on XS5.5, because XAPI misguidedly
320             # creates the fake bridge (via bridge ioctl) before it calls us.
321             vsctl_argv += ['--', '--if-exists', 'del-br', bridge]
322
323             # configure_datapath() set up the underlying datapath bridge.
324             # Stack a VLAN bridge on top of it.
325             vsctl_argv += ['--', '--may-exist', 'add-br',
326                            bridge, pif_bridge_name(self._dp), pifrec['VLAN']]
327         xs_network_uuids = []
328         for nwpif in db().get_pifs_by_device(db().get_pif_record(self._pif)['device']):
329             rec = db().get_pif_record(nwpif)
330
331             # When state is read from dbcache PIF.currently_attached
332             # is always assumed to be false... Err on the side of
333             # listing even detached networks for the time being.
334             #if nwpif != pif and not rec['currently_attached']:
335             #    log("Network PIF %s not currently attached (%s)" % (rec['uuid'],pifrec['uuid']))
336             #    continue
337             nwrec = db().get_network_record(rec['network'])
338             xs_network_uuids += [nwrec['uuid']]
339
340         vsctl_argv += ['# configure xs-network-uuids']
341         vsctl_argv += ['--', 'br-set-external-id', bridge,
342                 'xs-network-uuids', ';'.join(xs_network_uuids)]
343
344         if ipdev != bridge:
345             vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
346             vsctl_argv += datapath_deconfigure_ipdev(ipdev)
347             vsctl_argv += ["# reconfigure ipdev %s" % ipdev]
348             vsctl_argv += ['--', 'add-port', bridge, ipdev]
349
350         # XXX Needs support in ovs-vsctl
351         #if bridge == ipdev:
352         #    vsctl_argv += ['--add=bridge.%s.mac=%s' % (bridge, dprec['MAC'])]
353         #else:
354         #    vsctl_argv += ['--add=iface.%s.mac=%s' % (ipdev, dprec['MAC'])]
355
356         self._vsctl_argv = vsctl_argv
357         self._extra_ports = extra_ports
358
359     def bring_down_existing(self):
360         pass
361
362     def configure(self):
363         # Bring up physical devices. ovs-vswitchd initially enables or
364         # disables bond slaves based on whether carrier is detected
365         # when they are added, and a network device that is down
366         # always reports "no carrier".
367         physical_devices = datapath_get_physical_pifs(self._dp)
368         
369         for p in physical_devices:
370             oc = db().get_pif_record(p)['other_config']
371
372             dev = pif_netdev_name(p)
373
374             mtu = mtu_setting(oc)
375
376             netdev_up(dev, mtu)
377
378             settings, offload = ethtool_settings(oc)
379             if len(settings):
380                 run_command(['/sbin/ethtool', '-s', dev] + settings)
381             if len(offload):
382                 run_command(['/sbin/ethtool', '-K', dev] + offload)
383
384         datapath_modify_config(self._vsctl_argv)
385
386     def post(self):
387         for p in self._extra_ports:
388             log("action_up: bring up %s" % p)
389             netdev_up(p)
390
391     def bring_down(self):
392         vsctl_argv = []
393
394         dp = self._dp
395         ipdev = self._ipdev
396         
397         bridge = pif_bridge_name(dp)
398
399         #nw = db().get_pif_record(self._pif)['network']
400         #nwrec = db().get_network_record(nw)
401         #vsctl_argv += ['# deconfigure xs-network-uuids']
402         #vsctl_argv += ['--del-entry=bridge.%s.xs-network-uuids=%s' % (bridge,nwrec['uuid'])]
403
404         log("deconfigure ipdev %s on %s" % (ipdev,bridge))
405         vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
406         vsctl_argv += datapath_deconfigure_ipdev(ipdev)
407
408         if pif_is_vlan(self._pif):
409             # If the VLAN's slave is attached, leave datapath setup.
410             slave = pif_get_vlan_slave(self._pif)
411             if db().get_pif_record(slave)['currently_attached']:
412                 log("action_down: vlan slave is currently attached")
413                 dp = None
414
415             # If the VLAN's slave has other VLANs that are attached, leave datapath setup.
416             for master in pif_get_vlan_masters(slave):
417                 if master != self._pif and db().get_pif_record(master)['currently_attached']:
418                     log("action_down: vlan slave has other master: %s" % pif_netdev_name(master))
419                     dp = None
420
421             # Otherwise, take down the datapath too (fall through)
422             if dp:
423                 log("action_down: no more masters, bring down slave %s" % bridge)
424         else:
425             # Stop here if this PIF has attached VLAN masters.
426             masters = [db().get_pif_record(m)['VLAN'] for m in pif_get_vlan_masters(self._pif) if db().get_pif_record(m)['currently_attached']]
427             if len(masters) > 0:
428                 log("Leaving datapath %s up due to currently attached VLAN masters %s" % (bridge, masters))
429                 dp = None
430
431         if dp:
432             vsctl_argv += deconfigure_datapath(dp)
433             datapath_modify_config(vsctl_argv)