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