]>
Commit | Line | Data |
---|---|---|
b3080599 IC |
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 | ||
64ddb6fe | 18 | sysfs_bonding_masters = root_prefix() + "/sys/class/net/bonding_masters" |
b3080599 IC |
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 | ||
64ddb6fe | 26 | f = ConfigurationFile("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), interface)) |
b3080599 IC |
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(): | |
64ddb6fe | 72 | lines = open(root_prefix() + "/proc/modules").read().split("\n") |
b3080599 IC |
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 | ||
b3080599 IC |
138 | # |
139 | # Bring Interface up/down. | |
140 | # | |
141 | ||
142 | def bring_down_interface(pif, destroy=False): | |
143 | """Bring down the interface associated with PIF. | |
144 | ||
145 | Brings down the given interface as well as any physical interfaces | |
146 | which are bond slaves of this one. This is because they will be | |
147 | required when the bond is brought up.""" | |
148 | ||
149 | def destroy_bridge(pif): | |
150 | """Bring down the bridge associated with a PIF.""" | |
151 | #if not pif_is_bridged(pif): | |
152 | # return | |
153 | bridge = pif_bridge_name(pif) | |
154 | if not netdev_exists(bridge): | |
155 | log("destroy_bridge: bridge %s does not exist, ignoring" % bridge) | |
156 | return | |
157 | log("Destroy bridge %s" % bridge) | |
158 | netdev_down(bridge) | |
159 | run_command(["/usr/sbin/brctl", "delbr", bridge]) | |
160 | ||
161 | def destroy_vlan(pif): | |
162 | vlan = pif_netdev_name(pif) | |
163 | if not netdev_exists(vlan): | |
164 | log("vconfig del: vlan %s does not exist, ignoring" % vlan) | |
165 | return | |
166 | log("Destroy vlan device %s" % vlan) | |
167 | run_command(["/sbin/vconfig", "rem", vlan]) | |
168 | ||
169 | if pif_is_vlan(pif): | |
170 | interface = pif_netdev_name(pif) | |
171 | log("bring_down_interface: %s is a VLAN" % interface) | |
172 | netdev_down(interface) | |
173 | ||
174 | if destroy: | |
175 | destroy_vlan(pif) | |
176 | destroy_bridge(pif) | |
177 | else: | |
178 | return | |
179 | ||
180 | slave = pif_get_vlan_slave(pif) | |
181 | if db().get_pif_record(slave)['currently_attached']: | |
182 | log("bring_down_interface: vlan slave is currently attached") | |
183 | return | |
184 | ||
185 | masters = pif_get_vlan_masters(slave) | |
186 | masters = [m for m in masters if m != pif and db().get_pif_record(m)['currently_attached']] | |
187 | if len(masters) > 0: | |
188 | log("bring_down_interface: vlan slave has other masters") | |
189 | return | |
190 | ||
191 | log("bring_down_interface: no more masters, bring down vlan slave %s" % pif_netdev_name(slave)) | |
192 | pif = slave | |
193 | else: | |
194 | vlan_masters = pif_get_vlan_masters(pif) | |
195 | log("vlan masters of %s - %s" % (db().get_pif_record(pif)['device'], [pif_netdev_name(m) for m in vlan_masters])) | |
196 | if len([m for m in vlan_masters if db().get_pif_record(m)['currently_attached']]) > 0: | |
197 | log("Leaving %s up due to currently attached VLAN masters" % pif_netdev_name(pif)) | |
198 | return | |
199 | ||
200 | # pif is now either a bond or a physical device which needs to be brought down | |
201 | ||
202 | # Need to bring down bond slaves first since the bond device | |
203 | # must be up to enslave/unenslave. | |
204 | bond_slaves = pif_get_bond_slaves_sorted(pif) | |
205 | log("bond slaves of %s - %s" % (db().get_pif_record(pif)['device'], [pif_netdev_name(s) for s in bond_slaves])) | |
206 | for slave in bond_slaves: | |
207 | slave_interface = pif_netdev_name(slave) | |
208 | if db().get_pif_record(slave)['currently_attached']: | |
209 | log("leave bond slave %s up (currently attached)" % slave_interface) | |
210 | continue | |
211 | log("bring down bond slave %s" % slave_interface) | |
212 | netdev_down(slave_interface) | |
213 | # Also destroy the bridge associated with the slave, since | |
214 | # it will carry the MAC address and possibly an IP address | |
215 | # leading to confusion. | |
216 | destroy_bridge(slave) | |
217 | ||
218 | interface = pif_netdev_name(pif) | |
219 | log("Bring interface %s down" % interface) | |
220 | netdev_down(interface) | |
221 | ||
222 | if destroy: | |
223 | destroy_bond_device(pif) | |
224 | destroy_bridge(pif) | |
225 | ||
226 | def interface_is_up(pif): | |
227 | try: | |
228 | interface = pif_netdev_name(pif) | |
64ddb6fe | 229 | state = open("%s/sys/class/net/%s/operstate" % (root_prefix(), interface)).read().strip() |
b3080599 IC |
230 | return state == "up" |
231 | except: | |
232 | return False # interface prolly doesn't exist | |
233 | ||
234 | def bring_up_interface(pif): | |
235 | """Bring up the interface associated with a PIF. | |
236 | ||
237 | Also bring up the interfaces listed in additional. | |
238 | """ | |
239 | ||
240 | # VLAN on bond seems to need bond brought up explicitly, but VLAN | |
241 | # on normal device does not. Might as well always bring it up. | |
242 | if pif_is_vlan(pif): | |
243 | slave = pif_get_vlan_slave(pif) | |
244 | if not interface_is_up(slave): | |
245 | bring_up_interface(slave) | |
246 | ||
247 | interface = pif_netdev_name(pif) | |
248 | ||
249 | create_bond_device(pif) | |
250 | ||
251 | log("Bring interface %s up" % interface) | |
252 | netdev_up(interface) | |
253 | ||
254 | ||
255 | # | |
256 | # Datapath topology configuration. | |
257 | # | |
258 | ||
259 | def _configure_physical_interface(pif): | |
260 | """Write the configuration for a physical interface. | |
261 | ||
262 | Writes the configuration file for the physical interface described by | |
263 | the pif object. | |
264 | ||
265 | Returns the open file handle for the interface configuration file. | |
266 | """ | |
267 | ||
268 | pifrec = db().get_pif_record(pif) | |
269 | ||
9a2b1175 IC |
270 | log("Configuring physical interface %s" % pifrec['device']) |
271 | ||
b3080599 IC |
272 | f = open_pif_ifcfg(pif) |
273 | ||
274 | f.write("TYPE=Ethernet\n") | |
275 | f.write("HWADDR=%(MAC)s\n" % pifrec) | |
276 | ||
404c1692 AE |
277 | settings,offload = ethtool_settings(pifrec['other_config'], |
278 | PIF_OTHERCONFIG_DEFAULTS) | |
b3080599 IC |
279 | if len(settings): |
280 | f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings)) | |
281 | if len(offload): | |
282 | f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload)) | |
283 | ||
9a2b1175 | 284 | mtu = mtu_setting(pifrec['network'], "PIF", pifrec['other_config']) |
b3080599 IC |
285 | if mtu: |
286 | f.write("MTU=%s\n" % mtu) | |
287 | ||
288 | return f | |
289 | ||
290 | def pif_get_bond_slaves_sorted(pif): | |
291 | pifrec = db().get_pif_record(pif) | |
292 | ||
293 | # build a list of slave's pifs | |
294 | slave_pifs = pif_get_bond_slaves(pif) | |
295 | ||
296 | # Ensure any currently attached slaves are listed in the opposite order to the order in | |
297 | # which they were attached. The first slave attached must be the last detached since | |
298 | # the bond is using its MAC address. | |
299 | try: | |
64ddb6fe | 300 | attached_slaves = open("%s/sys/class/net/%s/bonding/slaves" % (root_prefix(), pifrec['device'])).readline().split() |
b3080599 IC |
301 | for slave in attached_slaves: |
302 | pifs = [p for p in db().get_pifs_by_device(slave) if not pif_is_vlan(p)] | |
303 | slave_pif = pifs[0] | |
304 | slave_pifs.remove(slave_pif) | |
305 | slave_pifs.insert(0, slave_pif) | |
306 | except IOError: | |
307 | pass | |
308 | ||
309 | return slave_pifs | |
310 | ||
311 | def _configure_bond_interface(pif): | |
312 | """Write the configuration for a bond interface. | |
313 | ||
314 | Writes the configuration file for the bond interface described by | |
315 | the pif object. Handles writing the configuration for the slave | |
316 | interfaces. | |
317 | ||
318 | Returns the open file handle for the bond interface configuration | |
319 | file. | |
320 | """ | |
321 | ||
322 | pifrec = db().get_pif_record(pif) | |
323 | ||
324 | f = open_pif_ifcfg(pif) | |
325 | ||
326 | if pifrec['MAC'] != "": | |
327 | f.write("MACADDR=%s\n" % pifrec['MAC']) | |
328 | ||
329 | for slave in pif_get_bond_slaves(pif): | |
330 | s = _configure_physical_interface(slave) | |
331 | s.write("MASTER=%(device)s\n" % pifrec) | |
332 | s.write("SLAVE=yes\n") | |
333 | s.close() | |
334 | f.attach_child(s) | |
335 | ||
336 | settings,offload = ethtool_settings(pifrec['other_config']) | |
337 | if len(settings): | |
338 | f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings)) | |
339 | if len(offload): | |
340 | f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload)) | |
341 | ||
404c1692 | 342 | mtu = mtu_setting(pifrec['network'], "Bond-PIF", pifrec['other_config']) |
b3080599 IC |
343 | if mtu: |
344 | f.write("MTU=%s\n" % mtu) | |
345 | ||
346 | # The bond option defaults | |
347 | bond_options = { | |
348 | "mode": "balance-slb", | |
349 | "miimon": "100", | |
350 | "downdelay": "200", | |
351 | "updelay": "31000", | |
352 | "use_carrier": "1", | |
2776e408 | 353 | "hashing-algorithm": "src_mac", |
b3080599 IC |
354 | } |
355 | ||
356 | # override defaults with values from other-config whose keys being with "bond-" | |
357 | oc = pifrec['other_config'] | |
358 | overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items()) | |
359 | overrides = map(lambda (key,val): (key[5:], val), overrides) | |
360 | bond_options.update(overrides) | |
361 | ||
362 | # write the bond options to ifcfg-bondX | |
363 | f.write('BONDING_OPTS="') | |
364 | for (name,val) in bond_options.items(): | |
365 | f.write("%s=%s " % (name,val)) | |
366 | f.write('"\n') | |
367 | return f | |
368 | ||
369 | def _configure_vlan_interface(pif): | |
370 | """Write the configuration for a VLAN interface. | |
371 | ||
372 | Writes the configuration file for the VLAN interface described by | |
373 | the pif object. Handles writing the configuration for the master | |
374 | interface if necessary. | |
375 | ||
376 | Returns the open file handle for the VLAN interface configuration | |
377 | file. | |
378 | """ | |
379 | ||
380 | slave = _configure_pif(pif_get_vlan_slave(pif)) | |
381 | ||
382 | pifrec = db().get_pif_record(pif) | |
383 | ||
384 | f = open_pif_ifcfg(pif) | |
385 | f.write("VLAN=yes\n") | |
386 | ||
387 | settings,offload = ethtool_settings(pifrec['other_config']) | |
388 | if len(settings): | |
389 | f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings)) | |
390 | if len(offload): | |
391 | f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload)) | |
392 | ||
404c1692 | 393 | mtu = mtu_setting(pifrec['network'], "VLAN-PIF", pifrec['other_config']) |
b3080599 IC |
394 | if mtu: |
395 | f.write("MTU=%s\n" % mtu) | |
396 | ||
397 | f.attach_child(slave) | |
398 | ||
399 | return f | |
400 | ||
401 | def _configure_pif(pif): | |
402 | """Write the configuration for a PIF object. | |
403 | ||
404 | Writes the configuration file the PIF and all dependent | |
405 | interfaces (bond slaves and VLAN masters etc). | |
406 | ||
407 | Returns the open file handle for the interface configuration file. | |
408 | """ | |
409 | ||
410 | if pif_is_vlan(pif): | |
411 | f = _configure_vlan_interface(pif) | |
412 | elif pif_is_bond(pif): | |
413 | f = _configure_bond_interface(pif) | |
414 | else: | |
415 | f = _configure_physical_interface(pif) | |
416 | ||
417 | f.write("BRIDGE=%s\n" % pif_bridge_name(pif)) | |
418 | f.close() | |
419 | ||
420 | return f | |
421 | ||
422 | # | |
423 | # | |
424 | # | |
425 | ||
426 | class DatapathBridge(Datapath): | |
427 | def __init__(self, pif): | |
92e906e4 IC |
428 | if pif_is_tunnel(pif): |
429 | raise Error("Tunnel PIFs are not supported in Bridge mode") | |
430 | ||
b3080599 IC |
431 | Datapath.__init__(self, pif) |
432 | log("Configured for Bridge datapath") | |
433 | ||
434 | def configure_ipdev(self, cfg): | |
435 | if pif_is_bridged(self._pif): | |
436 | cfg.write("TYPE=Bridge\n") | |
437 | cfg.write("DELAY=0\n") | |
438 | cfg.write("STP=off\n") | |
439 | cfg.write("PIFDEV=%s\n" % pif_netdev_name(self._pif)) | |
440 | else: | |
441 | cfg.write("TYPE=Ethernet\n") | |
442 | ||
443 | def preconfigure(self, parent): | |
444 | pf = _configure_pif(self._pif) | |
445 | parent.attach_child(pf) | |
446 | ||
447 | def bring_down_existing(self): | |
448 | # Bring down any VLAN masters so that we can reconfigure the slave. | |
449 | for master in pif_get_vlan_masters(self._pif): | |
450 | name = pif_netdev_name(master) | |
451 | log("action_up: bring down vlan master %s" % (name)) | |
452 | netdev_down(name) | |
453 | ||
454 | # interface-reconfigure is never explicitly called to down a bond master. | |
455 | # However, when we are called to up a slave it is implicit that we are destroying the master. | |
456 | bond_masters = pif_get_bond_masters(self._pif) | |
457 | for master in bond_masters: | |
458 | log("action_up: bring down bond master %s" % (pif_netdev_name(master))) | |
459 | # bring down master | |
460 | bring_down_interface(master, destroy=True) | |
461 | ||
462 | # No masters left - now its safe to reconfigure the slave. | |
463 | bring_down_interface(self._pif) | |
464 | ||
465 | def configure(self): | |
466 | bring_up_interface(self._pif) | |
467 | ||
468 | def post(self): | |
469 | # Bring back any currently-attached VLAN masters | |
470 | for master in [v for v in pif_get_vlan_masters(self._pif) if db().get_pif_record(v)['currently_attached']]: | |
471 | name = pif_netdev_name(master) | |
472 | log("action_up: bring up %s" % (name)) | |
473 | netdev_up(name) | |
474 | ||
475 | def bring_down(self): | |
476 | bring_down_interface(self._pif, destroy=True) |