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