xenserver: Update interface-reconfigure and vif integration scripts.
[openvswitch] / xenserver / opt_xensource_libexec_InterfaceReconfigureBridge.py
1 # Copyright (c) 2008,2009 Citrix Systems, Inc.
2 #
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU Lesser General Public License as published
5 # by the Free Software Foundation; version 2.1 only. with the special
6 # exception on linking described in file LICENSE.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU Lesser General Public License for more details.
12 #
13 from InterfaceReconfigure import *
14
15 import sys
16 import time
17
18 sysfs_bonding_masters = "/sys/class/net/bonding_masters"
19
20 def open_pif_ifcfg(pif):
21     pifrec = db().get_pif_record(pif)
22
23     interface = pif_netdev_name(pif)
24     log("Configuring %s (%s)" % (interface, pifrec['MAC']))
25
26     f = ConfigurationFile("/etc/sysconfig/network-scripts/ifcfg-%s" % interface)
27
28     f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
29             (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
30     f.write("XEMANAGED=yes\n")
31     f.write("DEVICE=%s\n" % interface)
32     f.write("ONBOOT=no\n")
33
34     return f
35
36 #
37 # Bare Network Devices -- network devices without IP configuration
38 #
39
40 def netdev_down(netdev):
41     """Bring down a bare network device"""
42     if not netdev_exists(netdev):
43         log("netdev: down: device %s does not exist, ignoring" % netdev)
44         return
45     run_command(["/sbin/ifdown", netdev])
46
47 def netdev_up(netdev, mtu=None):
48     """Bring up a bare network device"""
49     #if not netdev_exists(netdev):
50     #    raise Error("netdev: up: device %s does not exist" % netdev)
51
52     run_command(["/sbin/ifup", netdev])
53
54 #
55 # Bonding driver
56 #
57
58 def load_bonding_driver():
59     log("Loading bonding driver")
60     run_command(["/sbin/modprobe", "bonding"])
61     try:
62         # bond_device_exists() uses the contents of sysfs_bonding_masters to work out which devices
63         # have already been created.  Unfortunately the driver creates "bond0" automatically at
64         # modprobe init.  Get rid of this now or our accounting will go wrong.
65         f = open(sysfs_bonding_masters, "w")
66         f.write("-bond0")
67         f.close()
68     except IOError, e:
69         log("Failed to load bonding driver: %s" % e)
70
71 def bonding_driver_loaded():
72     lines = open("/proc/modules").read().split("\n")
73     modules = [line.split(" ")[0] for line in lines]
74     return "bonding" in modules
75
76 def bond_device_exists(name):
77     f = open(sysfs_bonding_masters, "r")
78     bonds = f.readline().split()
79     f.close()
80     return name in bonds
81
82 def __create_bond_device(name):
83
84     if not bonding_driver_loaded():
85         load_bonding_driver()
86
87     if bond_device_exists(name):
88         log("bond master %s already exists, not creating" % name)
89     else:
90         log("Creating bond master %s" % name)
91         try:
92             f = open(sysfs_bonding_masters, "w")
93             f.write("+" + name)
94             f.close()
95         except IOError, e:
96             log("Failed to create %s: %s" % (name, e))
97
98 def create_bond_device(pif):
99     """Ensures that a bond master device exists in the kernel."""
100
101     if not pif_is_bond(pif):
102         return
103
104     __create_bond_device(pif_netdev_name(pif))
105
106 def __destroy_bond_device(name):
107     if bond_device_exists(name):
108         retries = 10 # 10 * 0.5 seconds
109         while retries > 0:
110             retries = retries - 1
111             log("Destroying bond master %s (%d attempts remain)" % (name,retries))
112             try:
113                 f = open(sysfs_bonding_masters, "w")
114                 f.write("-" + name)
115                 f.close()
116                 retries = 0
117             except IOError, e:
118                 time.sleep(0.5)
119     else:
120         log("bond master %s does not exist, not destroying" % name)
121
122 def destroy_bond_device(pif):
123     """No, Mr. Bond, I expect you to die."""
124
125     pifrec = db().get_pif_record(pif)
126
127     if not pif_is_bond(pif):
128         return
129
130     # If the bonding module isn't loaded then do nothing.
131     if not os.access(sysfs_bonding_masters, os.F_OK):
132         return
133
134     name = pif_netdev_name(pif)
135
136     __destroy_bond_device(name)
137
138 #
139 # Bridges
140 #
141
142 def pif_is_bridged(pif):
143     pifrec = db().get_pif_record(pif)
144     nwrec = db().get_network_record(pifrec['network'])
145
146     if nwrec['bridge']:
147         # TODO: sanity check that nwrec['bridgeless'] != 'true'
148         return True
149     else:
150         # TODO: sanity check that nwrec['bridgeless'] == 'true'
151         return False
152
153 def pif_bridge_name(pif):
154     """Return the bridge name of a pif.
155
156     PIF must be a bridged PIF."""
157     pifrec = db().get_pif_record(pif)
158
159     nwrec = db().get_network_record(pifrec['network'])
160
161     if nwrec['bridge']:
162         return nwrec['bridge']
163     else:
164         raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
165
166 #
167 # Bring Interface up/down.
168 #
169
170 def bring_down_interface(pif, destroy=False):
171     """Bring down the interface associated with PIF.
172
173     Brings down the given interface as well as any physical interfaces
174     which are bond slaves of this one. This is because they will be
175     required when the bond is brought up."""
176
177     def destroy_bridge(pif):
178         """Bring down the bridge associated with a PIF."""
179         #if not pif_is_bridged(pif):
180         #    return
181         bridge = pif_bridge_name(pif)
182         if not netdev_exists(bridge):
183             log("destroy_bridge: bridge %s does not exist, ignoring" % bridge)
184             return
185         log("Destroy bridge %s" % bridge)
186         netdev_down(bridge)
187         run_command(["/usr/sbin/brctl", "delbr", bridge])
188
189     def destroy_vlan(pif):
190         vlan = pif_netdev_name(pif)
191         if not netdev_exists(vlan):
192             log("vconfig del: vlan %s does not exist, ignoring" % vlan)
193             return
194         log("Destroy vlan device %s" % vlan)
195         run_command(["/sbin/vconfig", "rem", vlan])
196
197     if pif_is_vlan(pif):
198         interface = pif_netdev_name(pif)
199         log("bring_down_interface: %s is a VLAN" % interface)
200         netdev_down(interface)
201
202         if destroy:
203             destroy_vlan(pif)
204             destroy_bridge(pif)
205         else:
206             return
207
208         slave = pif_get_vlan_slave(pif)
209         if db().get_pif_record(slave)['currently_attached']:
210             log("bring_down_interface: vlan slave is currently attached")
211             return
212
213         masters = pif_get_vlan_masters(slave)
214         masters = [m for m in masters if m != pif and db().get_pif_record(m)['currently_attached']]
215         if len(masters) > 0:
216             log("bring_down_interface: vlan slave has other masters")
217             return
218
219         log("bring_down_interface: no more masters, bring down vlan slave %s" % pif_netdev_name(slave))
220         pif = slave
221     else:
222         vlan_masters = pif_get_vlan_masters(pif)
223         log("vlan masters of %s - %s" % (db().get_pif_record(pif)['device'], [pif_netdev_name(m) for m in vlan_masters]))
224         if len([m for m in vlan_masters if db().get_pif_record(m)['currently_attached']]) > 0:
225             log("Leaving %s up due to currently attached VLAN masters" % pif_netdev_name(pif))
226             return
227
228     # pif is now either a bond or a physical device which needs to be brought down
229
230     # Need to bring down bond slaves first since the bond device
231     # must be up to enslave/unenslave.
232     bond_slaves = pif_get_bond_slaves_sorted(pif)
233     log("bond slaves of %s - %s" % (db().get_pif_record(pif)['device'], [pif_netdev_name(s) for s in bond_slaves]))
234     for slave in bond_slaves:
235         slave_interface = pif_netdev_name(slave)
236         if db().get_pif_record(slave)['currently_attached']:
237             log("leave bond slave %s up (currently attached)" % slave_interface)
238             continue
239         log("bring down bond slave %s" % slave_interface)
240         netdev_down(slave_interface)
241         # Also destroy the bridge associated with the slave, since
242         # it will carry the MAC address and possibly an IP address
243         # leading to confusion.
244         destroy_bridge(slave)
245
246     interface = pif_netdev_name(pif)
247     log("Bring interface %s down" % interface)
248     netdev_down(interface)
249
250     if destroy:
251         destroy_bond_device(pif)
252         destroy_bridge(pif)
253
254 def interface_is_up(pif):
255     try:
256         interface = pif_netdev_name(pif)
257         state = open("/sys/class/net/%s/operstate" % interface).read().strip()
258         return state == "up"
259     except:
260         return False # interface prolly doesn't exist
261
262 def bring_up_interface(pif):
263     """Bring up the interface associated with a PIF.
264
265     Also bring up the interfaces listed in additional.
266     """
267
268     # VLAN on bond seems to need bond brought up explicitly, but VLAN
269     # on normal device does not. Might as well always bring it up.
270     if pif_is_vlan(pif):
271         slave = pif_get_vlan_slave(pif)
272         if not interface_is_up(slave):
273             bring_up_interface(slave)
274
275     interface = pif_netdev_name(pif)
276
277     create_bond_device(pif)
278
279     log("Bring interface %s up" % interface)
280     netdev_up(interface)
281
282
283 #
284 # Datapath topology configuration.
285 #
286
287 def _configure_physical_interface(pif):
288     """Write the configuration for a physical interface.
289
290     Writes the configuration file for the physical interface described by
291     the pif object.
292
293     Returns the open file handle for the interface configuration file.
294     """
295
296     pifrec = db().get_pif_record(pif)
297
298     f = open_pif_ifcfg(pif)
299
300     f.write("TYPE=Ethernet\n")
301     f.write("HWADDR=%(MAC)s\n" % pifrec)
302
303     settings,offload = ethtool_settings(pifrec['other_config'])
304     if len(settings):
305         f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
306     if len(offload):
307         f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
308
309     mtu = mtu_setting(pifrec['other_config'])
310     if mtu:
311         f.write("MTU=%s\n" % mtu)
312
313     return f
314
315 def pif_get_bond_slaves_sorted(pif):
316     pifrec = db().get_pif_record(pif)
317
318     # build a list of slave's pifs
319     slave_pifs = pif_get_bond_slaves(pif)
320
321     # Ensure any currently attached slaves are listed in the opposite order to the order in
322     # which they were attached.  The first slave attached must be the last detached since
323     # the bond is using its MAC address.
324     try:
325         attached_slaves = open("/sys/class/net/%s/bonding/slaves" % pifrec['device']).readline().split()
326         for slave in attached_slaves:
327             pifs = [p for p in db().get_pifs_by_device(slave) if not pif_is_vlan(p)]
328             slave_pif = pifs[0]
329             slave_pifs.remove(slave_pif)
330             slave_pifs.insert(0, slave_pif)
331     except IOError:
332         pass
333
334     return slave_pifs
335
336 def _configure_bond_interface(pif):
337     """Write the configuration for a bond interface.
338
339     Writes the configuration file for the bond interface described by
340     the pif object. Handles writing the configuration for the slave
341     interfaces.
342
343     Returns the open file handle for the bond interface configuration
344     file.
345     """
346
347     pifrec = db().get_pif_record(pif)
348
349     f = open_pif_ifcfg(pif)
350
351     if pifrec['MAC'] != "":
352         f.write("MACADDR=%s\n" % pifrec['MAC'])
353
354     for slave in pif_get_bond_slaves(pif):
355         s = _configure_physical_interface(slave)
356         s.write("MASTER=%(device)s\n" % pifrec)
357         s.write("SLAVE=yes\n")
358         s.close()
359         f.attach_child(s)
360
361     settings,offload = ethtool_settings(pifrec['other_config'])
362     if len(settings):
363         f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
364     if len(offload):
365         f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
366
367     mtu = mtu_setting(pifrec['other_config'])
368     if mtu:
369         f.write("MTU=%s\n" % mtu)
370
371     # The bond option defaults
372     bond_options = {
373         "mode":   "balance-slb",
374         "miimon": "100",
375         "downdelay": "200",
376         "updelay": "31000",
377         "use_carrier": "1",
378         }
379
380     # override defaults with values from other-config whose keys being with "bond-"
381     oc = pifrec['other_config']
382     overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
383     overrides = map(lambda (key,val): (key[5:], val), overrides)
384     bond_options.update(overrides)
385
386     # write the bond options to ifcfg-bondX
387     f.write('BONDING_OPTS="')
388     for (name,val) in bond_options.items():
389         f.write("%s=%s " % (name,val))
390     f.write('"\n')
391     return f
392
393 def _configure_vlan_interface(pif):
394     """Write the configuration for a VLAN interface.
395
396     Writes the configuration file for the VLAN interface described by
397     the pif object. Handles writing the configuration for the master
398     interface if necessary.
399
400     Returns the open file handle for the VLAN interface configuration
401     file.
402     """
403
404     slave = _configure_pif(pif_get_vlan_slave(pif))
405
406     pifrec = db().get_pif_record(pif)
407
408     f = open_pif_ifcfg(pif)
409     f.write("VLAN=yes\n")
410
411     settings,offload = ethtool_settings(pifrec['other_config'])
412     if len(settings):
413         f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
414     if len(offload):
415         f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
416
417     mtu = mtu_setting(pifrec['other_config'])
418     if mtu:
419         f.write("MTU=%s\n" % mtu)
420
421     f.attach_child(slave)
422
423     return f
424
425 def _configure_pif(pif):
426     """Write the configuration for a PIF object.
427
428     Writes the configuration file the PIF and all dependent
429     interfaces (bond slaves and VLAN masters etc).
430
431     Returns the open file handle for the interface configuration file.
432     """
433
434     if pif_is_vlan(pif):
435         f = _configure_vlan_interface(pif)
436     elif pif_is_bond(pif):
437         f = _configure_bond_interface(pif)
438     else:
439         f = _configure_physical_interface(pif)
440
441     f.write("BRIDGE=%s\n" % pif_bridge_name(pif))
442     f.close()
443
444     return f
445
446 #
447 #
448 #
449
450 class DatapathBridge(Datapath):
451     def __init__(self, pif):
452         Datapath.__init__(self, pif)
453         log("Configured for Bridge datapath")
454
455     def configure_ipdev(self, cfg):
456         if pif_is_bridged(self._pif):
457             cfg.write("TYPE=Bridge\n")
458             cfg.write("DELAY=0\n")
459             cfg.write("STP=off\n")
460             cfg.write("PIFDEV=%s\n" % pif_netdev_name(self._pif))
461         else:
462             cfg.write("TYPE=Ethernet\n")
463         
464     def preconfigure(self, parent):
465         pf = _configure_pif(self._pif)
466         parent.attach_child(pf)
467
468     def bring_down_existing(self):
469         # Bring down any VLAN masters so that we can reconfigure the slave.
470         for master in pif_get_vlan_masters(self._pif):
471             name = pif_netdev_name(master)
472             log("action_up: bring down vlan master %s" % (name))
473             netdev_down(name)
474
475         # interface-reconfigure is never explicitly called to down a bond master.
476         # However, when we are called to up a slave it is implicit that we are destroying the master.
477         bond_masters = pif_get_bond_masters(self._pif)
478         for master in bond_masters:
479             log("action_up: bring down bond master %s" % (pif_netdev_name(master)))
480             # bring down master
481             bring_down_interface(master, destroy=True)
482
483         # No masters left - now its safe to reconfigure the slave.
484         bring_down_interface(self._pif)
485         
486     def configure(self):
487         bring_up_interface(self._pif)
488
489     def post(self):
490         # Bring back any currently-attached VLAN masters
491         for master in [v for v in pif_get_vlan_masters(self._pif) if db().get_pif_record(v)['currently_attached']]:
492             name = pif_netdev_name(master)
493             log("action_up: bring up %s" % (name))
494             netdev_up(name)
495
496     def bring_down(self):
497         bring_down_interface(self._pif, destroy=True)