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