xenserver: Hoist identical bridge and vswitch functions into common code.
[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, parent=None, vlan=None):
159     """Bring up the datapath configuration for PIF.
160
161     Should be careful not to glitch existing users of the datapath, e.g. other VLANs etc.
162
163     Should take care of tearing down other PIFs which encompass common physical devices.
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     if parent and datapath:
248         vsctl_argv += ['--', '--may-exist', 'add-br', bridge, parent, vlan]
249     else:
250         vsctl_argv += ['--', '--may-exist', 'add-br', bridge]
251
252     if len(physical_devices) > 1:
253         vsctl_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
254         vsctl_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
255         vsctl_argv += ['# configure bond %s' % pif_netdev_name(pif)]
256         vsctl_argv += datapath_configure_bond(pif, physical_devices)
257         extra_up_ports += [pif_netdev_name(pif)]
258     else:
259         iface = pif_netdev_name(physical_devices[0])
260         vsctl_argv += ['# add physical device %s' % iface]
261         vsctl_argv += ['--', '--may-exist', 'add-port', bridge, iface]
262
263     return vsctl_argv,extra_up_ports
264
265 def deconfigure_datapath(pif):
266     vsctl_argv = []
267
268     bridge = pif_bridge_name(pif)
269
270     physical_devices = datapath_get_physical_pifs(pif)
271
272     log("deconfigure_datapath: bridge           - %s" % bridge)
273     log("deconfigure_datapath: physical devices - %s" % [pif_netdev_name(p) for p in physical_devices])
274
275     for p in physical_devices:
276         dev = pif_netdev_name(p)
277         vsctl_argv += ['# deconfigure physical port %s' % dev]
278         vsctl_argv += datapath_deconfigure_physical(dev)
279         netdev_down(dev)
280
281     if len(physical_devices) > 1:
282         vsctl_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
283         vsctl_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
284
285     vsctl_argv += ['# deconfigure bridge %s' % bridge]
286     vsctl_argv += ['--', '--if-exists', 'del-br', bridge]
287
288     return vsctl_argv
289
290 #
291 #
292 #
293
294 class DatapathVswitch(Datapath):
295     def __init__(self, pif):
296         Datapath.__init__(self, pif)
297         self._dp = pif_datapath(pif)
298         self._ipdev = pif_ipdev_name(pif)
299
300         if pif_is_vlan(pif) and not self._dp:
301             raise Error("Unbridged VLAN devices not implemented yet")
302         
303         log("Configured for Vswitch datapath")
304
305     def configure_ipdev(self, cfg):
306         cfg.write("TYPE=Ethernet\n")
307
308     def preconfigure(self, parent):
309         vsctl_argv = []
310         extra_ports = []
311
312         pifrec = db().get_pif_record(self._pif)
313         dprec = db().get_pif_record(self._dp)
314
315         ipdev = self._ipdev
316         bridge = pif_bridge_name(self._dp)
317         if pif_is_vlan(self._pif):
318             datapath = pif_datapath(self._pif)
319             c,e = configure_datapath(self._dp, datapath, pifrec['VLAN'])
320         else:
321             c,e = configure_datapath(self._dp)
322         vsctl_argv += c
323         extra_ports += e
324
325         xs_network_uuids = []
326         for nwpif in db().get_pifs_by_device(db().get_pif_record(self._pif)['device']):
327             rec = db().get_pif_record(nwpif)
328
329             # When state is read from dbcache PIF.currently_attached
330             # is always assumed to be false... Err on the side of
331             # listing even detached networks for the time being.
332             #if nwpif != pif and not rec['currently_attached']:
333             #    log("Network PIF %s not currently attached (%s)" % (rec['uuid'],pifrec['uuid']))
334             #    continue
335             nwrec = db().get_network_record(rec['network'])
336             xs_network_uuids += [nwrec['uuid']]
337
338         vsctl_argv += ['# configure xs-network-uuids']
339         vsctl_argv += ['--', 'br-set-external-id', bridge,
340                 'xs-network-uuids', ';'.join(xs_network_uuids)]
341
342         if ipdev != bridge:
343             vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
344             vsctl_argv += datapath_deconfigure_ipdev(ipdev)
345             vsctl_argv += ["# reconfigure ipdev %s" % ipdev]
346             vsctl_argv += ['--', 'add-port', bridge, ipdev]
347
348         # XXX Needs support in ovs-vsctl
349         #if bridge == ipdev:
350         #    vsctl_argv += ['--add=bridge.%s.mac=%s' % (bridge, dprec['MAC'])]
351         #else:
352         #    vsctl_argv += ['--add=iface.%s.mac=%s' % (ipdev, dprec['MAC'])]
353
354         self._vsctl_argv = vsctl_argv
355         self._extra_ports = extra_ports
356
357     def bring_down_existing(self):
358         pass
359
360     def configure(self):
361         # Bring up physical devices. ovs-vswitchd initially enables or
362         # disables bond slaves based on whether carrier is detected
363         # when they are added, and a network device that is down
364         # always reports "no carrier".
365         physical_devices = datapath_get_physical_pifs(self._dp)
366         
367         for p in physical_devices:
368             oc = db().get_pif_record(p)['other_config']
369
370             dev = pif_netdev_name(p)
371
372             mtu = mtu_setting(oc)
373
374             netdev_up(dev, mtu)
375
376             settings, offload = ethtool_settings(oc)
377             if len(settings):
378                 run_command(['/sbin/ethtool', '-s', dev] + settings)
379             if len(offload):
380                 run_command(['/sbin/ethtool', '-K', dev] + offload)
381
382         datapath_modify_config(self._vsctl_argv)
383
384     def post(self):
385         for p in self._extra_ports:
386             log("action_up: bring up %s" % p)
387             netdev_up(p)
388
389     def bring_down(self):
390         vsctl_argv = []
391
392         dp = self._dp
393         ipdev = self._ipdev
394         
395         bridge = pif_bridge_name(dp)
396
397         #nw = db().get_pif_record(self._pif)['network']
398         #nwrec = db().get_network_record(nw)
399         #vsctl_argv += ['# deconfigure xs-network-uuids']
400         #vsctl_argv += ['--del-entry=bridge.%s.xs-network-uuids=%s' % (bridge,nwrec['uuid'])]
401
402         log("deconfigure ipdev %s on %s" % (ipdev,bridge))
403         vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
404         vsctl_argv += datapath_deconfigure_ipdev(ipdev)
405
406         if pif_is_vlan(self._pif):
407             # If the VLAN's slave is attached, leave datapath setup.
408             slave = pif_get_vlan_slave(self._pif)
409             if db().get_pif_record(slave)['currently_attached']:
410                 log("action_down: vlan slave is currently attached")
411                 dp = None
412
413             # If the VLAN's slave has other VLANs that are attached, leave datapath setup.
414             for master in pif_get_vlan_masters(slave):
415                 if master != self._pif and db().get_pif_record(master)['currently_attached']:
416                     log("action_down: vlan slave has other master: %s" % pif_netdev_name(master))
417                     dp = None
418
419             # Otherwise, take down the datapath too (fall through)
420             if dp:
421                 log("action_down: no more masters, bring down slave %s" % bridge)
422         else:
423             # Stop here if this PIF has attached VLAN masters.
424             masters = [db().get_pif_record(m)['VLAN'] for m in pif_get_vlan_masters(self._pif) if db().get_pif_record(m)['currently_attached']]
425             if len(masters) > 0:
426                 log("Leaving datapath %s up due to currently attached VLAN masters %s" % (bridge, masters))
427                 dp = None
428
429         if dp:
430             vsctl_argv += deconfigure_datapath(dp)
431             datapath_modify_config(vsctl_argv)