6c9e3fa8c494d8cb628350ff93ea38a5a7109d48
[sliver-openvswitch.git] / xenserver / opt_xensource_libexec_InterfaceReconfigureVswitch.py
1 # Copyright (c) 2008,2009 Citrix Systems, Inc.
2 # Copyright (c) 2009,2010,2011 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 import os
16 import re
17
18 #
19 # Bare Network Devices -- network devices without IP configuration
20 #
21
22 def netdev_down(netdev):
23     """Bring down a bare network device"""
24     if not netdev_exists(netdev):
25         log("netdev: down: device %s does not exist, ignoring" % netdev)
26         return
27     run_command(["/sbin/ifconfig", netdev, 'down'])
28
29 def netdev_up(netdev, mtu=None):
30     """Bring up a bare network device"""
31     if not netdev_exists(netdev):
32         raise Error("netdev: up: device %s does not exist" % netdev)
33
34     if mtu:
35         mtu = ["mtu", mtu]
36     else:
37         mtu = []
38
39     run_command(["/sbin/ifconfig", netdev, 'up'] + mtu)
40
41 #
42 # PIF miscellanea
43 #
44
45 def pif_currently_in_use(pif):
46     """Determine if a PIF is currently in use.
47
48     A PIF is determined to be currently in use if
49     - PIF.currently-attached is true
50     - Any bond master is currently attached
51     - Any VLAN master is currently attached
52     """
53     rec = db().get_pif_record(pif)
54     if rec['currently_attached']:
55         log("configure_datapath: %s is currently attached" % (pif_netdev_name(pif)))
56         return True
57     for b in pif_get_bond_masters(pif):
58         if pif_currently_in_use(b):
59             log("configure_datapath: %s is in use by BOND master %s" % (pif_netdev_name(pif),pif_netdev_name(b)))
60             return True
61     for v in pif_get_vlan_masters(pif):
62         if pif_currently_in_use(v):
63             log("configure_datapath: %s is in use by VLAN master %s" % (pif_netdev_name(pif),pif_netdev_name(v)))
64             return True
65     return False
66
67 #
68 # Datapath Configuration
69 #
70
71 def pif_datapath(pif):
72     """Return the datapath PIF associated with PIF.
73 A non-VLAN PIF is its own datapath PIF, except that a bridgeless PIF has
74 no datapath PIF at all.
75 A VLAN PIF's datapath PIF is its VLAN slave's datapath PIF.
76 """
77     if pif_is_vlan(pif):
78         return pif_datapath(pif_get_vlan_slave(pif))
79
80     pifrec = db().get_pif_record(pif)
81     nwrec = db().get_network_record(pifrec['network'])
82     if not nwrec['bridge']:
83         return None
84     else:
85         return pif
86
87 def datapath_get_physical_pifs(pif):
88     """Return the PIFs for the physical network device(s) associated with a datapath PIF.
89 For a bond master PIF, these are the bond slave PIFs.
90 For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
91
92 A VLAN PIF cannot be a datapath PIF.
93 """
94     if pif_is_tunnel(pif):
95         return []
96     elif pif_is_vlan(pif):
97         # Seems like overkill...
98         raise Error("get-physical-pifs should not get passed a VLAN")
99     elif pif_is_bond(pif):
100         return pif_get_bond_slaves(pif)
101     else:
102         return [pif]
103
104 def datapath_deconfigure_physical(netdev):
105     return ['--', '--with-iface', '--if-exists', 'del-port', netdev]
106
107 def vsctl_escape(s):
108     if s.isalnum():
109         return s
110
111     def escape(match):
112         c = match.group(0)
113         if c == '\0':
114             raise Error("strings may not contain null bytes")
115         elif c == '\\':
116             return r'\\'
117         elif c == '\n':
118             return r'\n'
119         elif c == '\r':
120             return r'\r'
121         elif c == '\t':
122             return r'\t'
123         elif c == '\b':
124             return r'\b'
125         elif c == '\a':
126             return r'\a'
127         else:
128             return r'\x%02x' % ord(c)
129     return '"' + re.sub(r'["\\\000-\037]', escape, s) + '"'
130
131 def datapath_configure_tunnel(pif):
132     pass
133
134 def datapath_configure_bond(pif,slaves):
135     bridge = pif_bridge_name(pif)
136     pifrec = db().get_pif_record(pif)
137     interface = pif_netdev_name(pif)
138
139     argv = ['--', '--fake-iface', 'add-bond', bridge, interface]
140     for slave in slaves:
141         argv += [pif_netdev_name(slave)]
142
143     # Bonding options.
144     bond_options = {
145         "mode":   "balance-slb",
146         "miimon": "100",
147         "downdelay": "200",
148         "updelay": "31000",
149         "use_carrier": "1",
150         "hashing-algorithm": "src_mac",
151         }
152     # override defaults with values from other-config whose keys
153     # being with "bond-"
154     oc = pifrec['other_config']
155     overrides = filter(lambda (key,val):
156                            key.startswith("bond-"), oc.items())
157     overrides = map(lambda (key,val): (key[5:], val), overrides)
158     bond_options.update(overrides)
159     mode = None
160     halgo = None
161
162     argv += ['--', 'set', 'Port', interface]
163     if pifrec['MAC'] != "":
164         argv += ['MAC=%s' % vsctl_escape(pifrec['MAC'])]
165     for (name,val) in bond_options.items():
166         if name in ['updelay', 'downdelay']:
167             # updelay and downdelay have dedicated schema columns.
168             # The value must be a nonnegative integer.
169             try:
170                 value = int(val)
171                 if value < 0:
172                     raise ValueError
173
174                 argv += ['bond_%s=%d' % (name, value)]
175             except ValueError:
176                 log("bridge %s has invalid %s '%s'" % (bridge, name, value))
177         elif name in ['miimon', 'use_carrier']:
178             try:
179                 value = int(val)
180                 if value < 0:
181                     raise ValueError
182
183                 if name == 'use_carrier':
184                     if value:
185                         value = "carrier"
186                     else:
187                         value = "miimon"
188                     argv += ["other-config:bond-detect-mode=%s" % value]
189                 else:
190                     argv += ["other-config:bond-miimon-interval=%d" % value]
191             except ValueError:
192                 log("bridge %s has invalid %s '%s'" % (bridge, name, value))
193         elif name == "mode":
194             mode = val
195         elif name == "hashing-algorithm":
196             halgo = val
197         else:
198             # Pass other bond options into other_config.
199             argv += ["other-config:%s=%s" % (vsctl_escape("bond-%s" % name),
200                                              vsctl_escape(val))]
201
202     if mode == 'lacp':
203         argv += ['lacp=active']
204
205         if halgo == 'src_mac':
206             argv += ['bond_mode=balance-slb']
207         elif halgo == "tcpudp_ports":
208             argv += ['bond_mode=balance-tcp']
209         else:
210             log("bridge %s has invalid bond-hashing-algorithm '%s'" % (bridge, halgo))
211             argv += ['bond_mode=balance-slb']
212     elif mode in ['balance-slb', 'active-backup']:
213         argv += ['lacp=off', 'bond_mode=%s' % mode]
214     else:
215         log("bridge %s has invalid bond-mode '%s'" % (bridge, mode))
216         argv += ['lacp=off', 'bond_mode=balance-slb']
217
218     return argv
219
220 def datapath_deconfigure_bond(netdev):
221     return ['--', '--with-iface', '--if-exists', 'del-port', netdev]
222
223 def datapath_deconfigure_ipdev(interface):
224     return ['--', '--with-iface', '--if-exists', 'del-port', interface]
225
226 def datapath_modify_config(commands):
227     #log("modifying configuration:")
228     #for c in commands:
229     #    log("  %s" % c)
230             
231     rc = run_command(['/usr/bin/ovs-vsctl'] + ['--timeout=20']
232                      + [c for c in commands if not c.startswith('#')])
233     if not rc:       
234         raise Error("Failed to modify vswitch configuration")
235     return True
236
237 #
238 # Toplevel Datapath Configuration.
239 #
240
241 def configure_datapath(pif):
242     """Bring up the configuration for 'pif', which must not be a VLAN PIF, by:
243     - Tearing down other PIFs that use the same physical devices as 'pif'.
244     - Ensuring that 'pif' itself is set up.
245     - *Not* tearing down any PIFs that are stacked on top of 'pif' (i.e. VLANs
246       on top of 'pif'.
247
248     Returns a tuple containing
249     - A list containing the necessary vsctl command line arguments
250     - A list of additional devices which should be brought up after
251       the configuration is applied.
252     """
253
254     vsctl_argv = []
255     extra_up_ports = []
256
257     assert not pif_is_vlan(pif)
258     bridge = pif_bridge_name(pif)
259
260     physical_devices = datapath_get_physical_pifs(pif)
261
262     vsctl_argv += ['## configuring datapath %s' % bridge]
263
264     # Determine additional devices to deconfigure.
265     #
266     # Given all physical devices which are part of this PIF we need to
267     # consider:
268     # - any additional bond which a physical device is part of.
269     # - any additional physical devices which are part of an additional bond.
270     #
271     # Any of these which are not currently in use should be brought
272     # down and deconfigured.
273     extra_down_bonds = []
274     extra_down_ports = []
275     for p in physical_devices:
276         for bond in pif_get_bond_masters(p):
277             if bond == pif:
278                 log("configure_datapath: leaving bond %s up" % pif_netdev_name(bond))
279                 continue
280             if bond in extra_down_bonds:
281                 continue
282             if db().get_pif_record(bond)['currently_attached']:
283                 log("configure_datapath: implicitly tearing down currently-attached bond %s" % pif_netdev_name(bond))
284
285             extra_down_bonds += [bond]
286
287             for s in pif_get_bond_slaves(bond):
288                 if s in physical_devices:
289                     continue
290                 if s in extra_down_ports:
291                     continue
292                 if pif_currently_in_use(s):
293                     continue
294                 extra_down_ports += [s]
295
296     log("configure_datapath: bridge      - %s" % bridge)
297     log("configure_datapath: physical    - %s" % [pif_netdev_name(p) for p in physical_devices])
298     log("configure_datapath: extra ports - %s" % [pif_netdev_name(p) for p in extra_down_ports])
299     log("configure_datapath: extra bonds - %s" % [pif_netdev_name(p) for p in extra_down_bonds])
300
301     # Need to fully deconfigure any bridge which any of the:
302     # - physical devices
303     # - bond devices
304     # - sibling devices
305     # refers to
306     for brpif in physical_devices + extra_down_ports + extra_down_bonds:
307         if brpif == pif:
308             continue
309         b = pif_bridge_name(brpif)
310         #ifdown(b)
311         # XXX
312         netdev_down(b)
313         vsctl_argv += ['# remove bridge %s' % b]
314         vsctl_argv += ['--', '--if-exists', 'del-br', b]
315
316     for n in extra_down_ports:
317         dev = pif_netdev_name(n)
318         vsctl_argv += ['# deconfigure sibling physical device %s' % dev]
319         vsctl_argv += datapath_deconfigure_physical(dev)
320         netdev_down(dev)
321
322     for n in extra_down_bonds:
323         dev = pif_netdev_name(n)
324         vsctl_argv += ['# deconfigure bond device %s' % dev]
325         vsctl_argv += datapath_deconfigure_bond(dev)
326         netdev_down(dev)
327
328     for p in physical_devices:
329         dev = pif_netdev_name(p)
330         vsctl_argv += ['# deconfigure physical port %s' % dev]
331         vsctl_argv += datapath_deconfigure_physical(dev)
332
333     vsctl_argv += ['--', '--may-exist', 'add-br', bridge]
334
335     if len(physical_devices) > 1:
336         vsctl_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
337         vsctl_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
338         vsctl_argv += ['# configure bond %s' % pif_netdev_name(pif)]
339         vsctl_argv += datapath_configure_bond(pif, physical_devices)
340         extra_up_ports += [pif_netdev_name(pif)]
341     elif len(physical_devices) == 1:
342         iface = pif_netdev_name(physical_devices[0])
343         vsctl_argv += ['# add physical device %s' % iface]
344         vsctl_argv += ['--', '--may-exist', 'add-port', bridge, iface]
345     elif pif_is_tunnel(pif):
346         datapath_configure_tunnel(pif)
347
348     vsctl_argv += ['# configure Bridge MAC']
349     vsctl_argv += ['--', 'set', 'Bridge', bridge,
350                    'other-config:hwaddr=%s' % vsctl_escape(db().get_pif_record(pif)['MAC'])]
351
352     pool = db().get_pool_record()
353     network = db().get_network_by_bridge(bridge)
354     network_rec = None
355     fail_mode = None
356     valid_fail_modes = ['standalone', 'secure']
357
358     if network:
359         network_rec = db().get_network_record(network)
360         fail_mode = network_rec['other_config'].get('vswitch-controller-fail-mode')
361
362     if (fail_mode not in valid_fail_modes) and pool:
363         fail_mode = pool['other_config'].get('vswitch-controller-fail-mode')
364
365     if fail_mode not in valid_fail_modes:
366         fail_mode = 'standalone'
367
368     vsctl_argv += ['--', 'set', 'Bridge', bridge, 'fail_mode=%s' % fail_mode]
369
370     if network_rec:
371         dib = network_rec['other_config'].get('vswitch-disable-in-band')
372         if not dib:
373             vsctl_argv += ['--', 'remove', 'Bridge', bridge, 'other_config', 'disable-in-band']
374         elif dib in ['true', 'false']:
375             vsctl_argv += ['--', 'set', 'Bridge', bridge, 'other_config:disable-in-band=' + dib]
376         else:
377             log('"' + dib + '"' "isn't a valid setting for other_config:disable-in-band on " + bridge)
378
379     vsctl_argv += set_br_external_ids(pif)
380     vsctl_argv += ['## done configuring datapath %s' % bridge]
381
382     return vsctl_argv,extra_up_ports
383
384 def deconfigure_bridge(pif):
385     vsctl_argv = []
386
387     bridge = pif_bridge_name(pif)
388
389     log("deconfigure_bridge: bridge           - %s" % bridge)
390
391     vsctl_argv += ['# deconfigure bridge %s' % bridge]
392     vsctl_argv += ['--', '--if-exists', 'del-br', bridge]
393
394     return vsctl_argv
395
396 def set_br_external_ids(pif):
397     pifrec = db().get_pif_record(pif)
398     dp = pif_datapath(pif)
399     dprec = db().get_pif_record(dp)
400
401     xs_network_uuids = []
402     for nwpif in db().get_pifs_by_device(pifrec['device']):
403         rec = db().get_pif_record(nwpif)
404
405         # When state is read from dbcache PIF.currently_attached
406         # is always assumed to be false... Err on the side of
407         # listing even detached networks for the time being.
408         #if nwpif != pif and not rec['currently_attached']:
409         #    log("Network PIF %s not currently attached (%s)" % (rec['uuid'],pifrec['uuid']))
410         #    continue
411         nwrec = db().get_network_record(rec['network'])
412
413         uuid = nwrec['uuid']
414         if pif_is_vlan(nwpif):
415             xs_network_uuids.append(uuid)
416         else:
417             xs_network_uuids.insert(0, uuid)
418
419     vsctl_argv = []
420     vsctl_argv += ['# configure xs-network-uuids']
421     vsctl_argv += ['--', 'br-set-external-id', pif_bridge_name(pif),
422             'xs-network-uuids', ';'.join(xs_network_uuids)]
423
424     return vsctl_argv
425
426 #
427 #
428 #
429
430 class DatapathVswitch(Datapath):
431     def __init__(self, pif):
432         Datapath.__init__(self, pif)
433         self._dp = pif_datapath(pif)
434         self._ipdev = pif_ipdev_name(pif)
435
436         if pif_is_vlan(pif) and not self._dp:
437             raise Error("Unbridged VLAN devices not implemented yet")
438         
439         log("Configured for Vswitch datapath")
440
441     @classmethod
442     def rewrite(cls):
443         if not os.path.exists("/var/run/openvswitch/db.sock"):
444             # ovsdb-server is not running, so we can't update the database.
445             # Probably we are being called as part of system shutdown.  Just
446             # skip the update, since the external-ids will be updated on the
447             # next boot anyhow.
448             return
449
450         vsctl_argv = []
451         for pif in db().get_all_pifs():
452             pifrec = db().get_pif_record(pif)
453             if not pif_is_vlan(pif) and pifrec['currently_attached']:
454                 vsctl_argv += set_br_external_ids(pif)
455
456         if vsctl_argv != []:
457             datapath_modify_config(vsctl_argv)
458
459     def configure_ipdev(self, cfg):
460         cfg.write("TYPE=Ethernet\n")
461
462     def preconfigure(self, parent):
463         vsctl_argv = []
464         extra_ports = []
465
466         pifrec = db().get_pif_record(self._pif)
467         dprec = db().get_pif_record(self._dp)
468
469         ipdev = self._ipdev
470         c,e = configure_datapath(self._dp)
471         bridge = pif_bridge_name(self._pif)
472         vsctl_argv += c
473         extra_ports += e
474
475         dpname = pif_bridge_name(self._dp)
476         
477         if pif_is_vlan(self._pif):
478             # In some cases XAPI may misguidedly leave an instance of
479             # 'bridge' which should be deleted.
480             vsctl_argv += ['--', '--if-exists', 'del-br', bridge]
481
482             # configure_datapath() set up the underlying datapath bridge.
483             # Stack a VLAN bridge on top of it.
484             vsctl_argv += ['--', '--may-exist', 'add-br',
485                            bridge, dpname, pifrec['VLAN']]
486
487             vsctl_argv += set_br_external_ids(self._pif)
488
489         if ipdev != bridge:
490             vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
491             vsctl_argv += datapath_deconfigure_ipdev(ipdev)
492             vsctl_argv += ["# reconfigure ipdev %s" % ipdev]
493             vsctl_argv += ['--', 'add-port', bridge, ipdev]
494
495         if ipdev != dpname:
496             vsctl_argv += ['# configure Interface MAC']
497             vsctl_argv += ['--', 'set', 'Interface', pif_ipdev_name(self._pif),
498                            'MAC=%s' % vsctl_escape(dprec['MAC'])]
499
500         self._vsctl_argv = vsctl_argv
501         self._extra_ports = extra_ports
502
503     def bring_down_existing(self):
504         # interface-reconfigure is never explicitly called to down a
505         # bond master.  However, when we are called to up a slave it
506         # is implicit that we are destroying the master.  Conversely,
507         # when we are called to up a bond is is implicit that we are
508         # taking down the slaves.
509         #
510         # This is (only) important in the case where the device being
511         # implicitly taken down uses DHCP.  We need to kill the
512         # dhclient process, otherwise performing the inverse operation
513         # later later will fail because ifup will refuse to start a
514         # duplicate dhclient.
515         bond_masters = pif_get_bond_masters(self._pif)
516         for master in bond_masters:
517             log("action_up: bring down bond master %s" % (pif_netdev_name(master)))
518             run_command(["/sbin/ifdown", pif_bridge_name(master)])
519
520         bond_slaves = pif_get_bond_slaves(self._pif)
521         for slave in bond_slaves:
522             log("action_up: bring down bond slave %s" % (pif_netdev_name(slave)))
523             run_command(["/sbin/ifdown", pif_bridge_name(slave)])
524
525     def configure(self):
526         # Bring up physical devices. ovs-vswitchd initially enables or
527         # disables bond slaves based on whether carrier is detected
528         # when they are added, and a network device that is down
529         # always reports "no carrier".
530         physical_devices = datapath_get_physical_pifs(self._dp)
531         
532         for p in physical_devices:
533             prec = db().get_pif_record(p)
534             oc = prec['other_config']
535
536             dev = pif_netdev_name(p)
537
538             mtu = mtu_setting(prec['network'], "PIF", oc)
539
540             netdev_up(dev, mtu)
541
542             settings, offload = ethtool_settings(oc, PIF_OTHERCONFIG_DEFAULTS)
543             if len(settings):
544                 run_command(['/sbin/ethtool', '-s', dev] + settings)
545             if len(offload):
546                 run_command(['/sbin/ethtool', '-K', dev] + offload)
547
548         datapath_modify_config(self._vsctl_argv)
549
550     def post(self):
551         for p in self._extra_ports:
552             log("action_up: bring up %s" % p)
553             netdev_up(p)
554
555     def bring_down(self):
556         vsctl_argv = []
557
558         dp = self._dp
559         ipdev = self._ipdev
560         
561         bridge = pif_bridge_name(dp)
562
563         log("deconfigure ipdev %s on %s" % (ipdev,bridge))
564         vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
565         vsctl_argv += datapath_deconfigure_ipdev(ipdev)
566
567         if pif_is_vlan(self._pif):
568             # Delete the VLAN bridge.
569             vsctl_argv += deconfigure_bridge(self._pif)
570
571             # If the VLAN's slave is attached, leave datapath setup.
572             slave = pif_get_vlan_slave(self._pif)
573             if db().get_pif_record(slave)['currently_attached']:
574                 log("action_down: vlan slave is currently attached")
575                 dp = None
576
577             # If the VLAN's slave has other VLANs that are attached, leave datapath setup.
578             for master in pif_get_vlan_masters(slave):
579                 if master != self._pif and db().get_pif_record(master)['currently_attached']:
580                     log("action_down: vlan slave has other master: %s" % pif_netdev_name(master))
581                     dp = None
582
583             # Otherwise, take down the datapath too (fall through)
584             if dp:
585                 log("action_down: no more masters, bring down slave %s" % bridge)
586         else:
587             # Stop here if this PIF has attached VLAN masters.
588             masters = [db().get_pif_record(m)['VLAN'] for m in pif_get_vlan_masters(self._pif) if db().get_pif_record(m)['currently_attached']]
589             if len(masters) > 0:
590                 log("Leaving datapath %s up due to currently attached VLAN masters %s" % (bridge, masters))
591                 dp = None
592
593         if dp:
594             vsctl_argv += deconfigure_bridge(dp)
595
596             physical_devices = [pif_netdev_name(p) for p in datapath_get_physical_pifs(dp)]
597
598             log("action_down: bring down physical devices - %s" % physical_devices)
599         
600             for p in physical_devices:
601                 netdev_down(p)
602
603         datapath_modify_config(vsctl_argv)