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