Merge "master" into "next".
[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 # 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 += ['--', '--may-exist', 'add-br', bridge, parent, vlan]
269     else:
270         vsctl_argv += ['--', '--may-exist', '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 += ['--', '--may-exist', '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         dprec = db().get_pif_record(self._dp)
334
335         ipdev = self._ipdev
336         bridge = pif_bridge_name(self._dp)
337         if pif_is_vlan(self._pif):
338             datapath = pif_datapath(self._pif)
339             c,e = configure_datapath(self._dp, datapath, pifrec['VLAN'])
340         else:
341             c,e = configure_datapath(self._dp)
342         vsctl_argv += c
343         extra_ports += e
344
345         xs_network_uuids = []
346         for nwpif in db().get_pifs_by_device(db().get_pif_record(self._pif)['device']):
347             rec = db().get_pif_record(nwpif)
348
349             # When state is read from dbcache PIF.currently_attached
350             # is always assumed to be false... Err on the side of
351             # listing even detached networks for the time being.
352             #if nwpif != pif and not rec['currently_attached']:
353             #    log("Network PIF %s not currently attached (%s)" % (rec['uuid'],pifrec['uuid']))
354             #    continue
355             nwrec = db().get_network_record(rec['network'])
356             xs_network_uuids += [nwrec['uuid']]
357
358         vsctl_argv += ['# configure xs-network-uuids']
359         vsctl_argv += ['--', 'br-set-external-id', bridge,
360                 'xs-network-uuids', ';'.join(xs_network_uuids)]
361
362         if ipdev != bridge:
363             vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
364             vsctl_argv += datapath_deconfigure_ipdev(ipdev)
365             vsctl_argv += ["# reconfigure ipdev %s" % ipdev]
366             vsctl_argv += ['--', 'add-port', bridge, ipdev]
367
368         # XXX Needs support in ovs-vsctl
369         #if bridge == ipdev:
370         #    vsctl_argv += ['--add=bridge.%s.mac=%s' % (bridge, dprec['MAC'])]
371         #else:
372         #    vsctl_argv += ['--add=iface.%s.mac=%s' % (ipdev, dprec['MAC'])]
373
374         self._vsctl_argv = vsctl_argv
375         self._extra_ports = extra_ports
376
377     def bring_down_existing(self):
378         pass
379
380     def configure(self):
381         # Bring up physical devices. ovs-vswitchd initially enables or
382         # disables bond slaves based on whether carrier is detected
383         # when they are added, and a network device that is down
384         # always reports "no carrier".
385         physical_devices = datapath_get_physical_pifs(self._dp)
386         
387         for p in physical_devices:
388             oc = db().get_pif_record(p)['other_config']
389
390             dev = pif_netdev_name(p)
391
392             mtu = mtu_setting(oc)
393
394             netdev_up(dev, mtu)
395
396             settings, offload = ethtool_settings(oc)
397             if len(settings):
398                 run_command(['/sbin/ethtool', '-s', dev] + settings)
399             if len(offload):
400                 run_command(['/sbin/ethtool', '-K', dev] + offload)
401
402         datapath_modify_config(self._vsctl_argv)
403
404     def post(self):
405         for p in self._extra_ports:
406             log("action_up: bring up %s" % p)
407             netdev_up(p)
408
409     def bring_down(self):
410         vsctl_argv = []
411
412         dp = self._dp
413         ipdev = self._ipdev
414         
415         bridge = pif_bridge_name(dp)
416
417         #nw = db().get_pif_record(self._pif)['network']
418         #nwrec = db().get_network_record(nw)
419         #vsctl_argv += ['# deconfigure xs-network-uuids']
420         #vsctl_argv += ['--del-entry=bridge.%s.xs-network-uuids=%s' % (bridge,nwrec['uuid'])]
421
422         log("deconfigure ipdev %s on %s" % (ipdev,bridge))
423         vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
424         vsctl_argv += datapath_deconfigure_ipdev(ipdev)
425
426         if pif_is_vlan(self._pif):
427             # If the VLAN's slave is attached, leave datapath setup.
428             slave = pif_get_vlan_slave(self._pif)
429             if db().get_pif_record(slave)['currently_attached']:
430                 log("action_down: vlan slave is currently attached")
431                 dp = None
432
433             # If the VLAN's slave has other VLANs that are attached, leave datapath setup.
434             for master in pif_get_vlan_masters(slave):
435                 if master != self._pif and db().get_pif_record(master)['currently_attached']:
436                     log("action_down: vlan slave has other master: %s" % pif_netdev_name(master))
437                     dp = None
438
439             # Otherwise, take down the datapath too (fall through)
440             if dp:
441                 log("action_down: no more masters, bring down slave %s" % bridge)
442         else:
443             # Stop here if this PIF has attached VLAN masters.
444             masters = [db().get_pif_record(m)['VLAN'] for m in pif_get_vlan_masters(self._pif) if db().get_pif_record(m)['currently_attached']]
445             if len(masters) > 0:
446                 log("Leaving datapath %s up due to currently attached VLAN masters %s" % (bridge, masters))
447                 dp = None
448
449         if dp:
450             vsctl_argv += deconfigure_datapath(dp)
451             datapath_modify_config(vsctl_argv)