]>
Commit | Line | Data |
---|---|---|
064af421 BP |
1 | #!/usr/bin/python |
2 | # | |
42ab0203 | 3 | # Copyright (c) 2008,2009 Citrix Systems, Inc. All rights reserved. |
064af421 BP |
4 | # Copyright (c) 2009 Nicira Networks. |
5 | # | |
6 | """Usage: | |
7 | ||
8 | %(command-name)s --session <SESSION-REF> --pif <PIF-REF> [up|down|rewrite] | |
9 | %(command-name)s --force <BRIDGE> [up|down|rewrite <CONFIG>] | |
10 | %(command-name)s --force all down | |
11 | ||
12 | where, | |
13 | <CONFIG> = --device=<INTERFACE> --mode=dhcp | |
14 | <CONFIG> = --device=<INTERFACE> --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>] | |
15 | ||
16 | Options: | |
17 | --session A session reference to use to access the xapi DB | |
18 | --pif A PIF reference. | |
19 | --force-interface An interface name. Mutually exclusive with --session/--pif. | |
20 | ||
21 | Either both --session and --pif or just --pif-uuid. | |
22 | ||
23 | <ACTION> is either "up" or "down" or "rewrite" | |
24 | """ | |
25 | ||
26 | # | |
27 | # Undocumented parameters for test & dev: | |
28 | # | |
29 | # --output-directory=<DIR> Write configuration to <DIR>. Also disables actually | |
30 | # raising/lowering the interfaces | |
31 | # --pif-uuid A PIF UUID, use instead of --session/--pif. | |
32 | # | |
33 | # | |
34 | # | |
35 | # Notes: | |
36 | # 1. Every pif belongs to exactly one network | |
37 | # 2. Every network has zero or one pifs | |
38 | # 3. A network may have an associated bridge, allowing vifs to be attached | |
39 | # 4. A network may be bridgeless (there's no point having a bridge over a storage pif) | |
40 | ||
41 | # XXX: --force-interface=all down | |
42 | ||
43 | # XXX: --force-interface rewrite | |
44 | ||
45 | # XXX: Sometimes this leaves "orphaned" datapaths, e.g. a datapath whose | |
46 | # only port is the local port. Should delete those. | |
47 | ||
48 | # XXX: This can leave crud in ovs-vswitchd.conf in this scenario: | |
49 | # - Create bond in XenCenter. | |
50 | # - Create VLAN on bond in XenCenter. | |
51 | # - Attempt to delete bond in XenCenter (this will fail because there | |
52 | # is a VLAN on the bond, although the error may not be reported | |
53 | # until the next step) | |
54 | # - Delete VLAN in XenCenter. | |
55 | # - Delete bond in XenCenter. | |
56 | # At this point there will still be some configuration data for the bond | |
57 | # or the VLAN in ovs-vswitchd.conf. | |
58 | ||
59 | import XenAPI | |
60 | import os, sys, getopt, time, signal | |
61 | import syslog | |
62 | import traceback | |
63 | import time | |
64 | import re | |
65 | import pickle | |
2bb451b6 | 66 | import random |
064af421 BP |
67 | |
68 | output_directory = None | |
69 | ||
70 | db = None | |
71 | management_pif = None | |
72 | ||
0bb1f43d | 73 | dbcache_file = "/etc/ovs-vswitch.dbcache" |
064af421 BP |
74 | vswitch_config_dir = "/etc/openvswitch" |
75 | ||
76 | class Usage(Exception): | |
77 | def __init__(self, msg): | |
78 | Exception.__init__(self) | |
79 | self.msg = msg | |
80 | ||
81 | class Error(Exception): | |
82 | def __init__(self, msg): | |
83 | Exception.__init__(self) | |
84 | self.msg = msg | |
85 | ||
86 | class ConfigurationFile(object): | |
87 | """Write a file, tracking old and new versions. | |
88 | ||
89 | Supports writing a new version of a file and applying and | |
90 | reverting those changes. | |
91 | """ | |
92 | ||
93 | __STATE = {"OPEN":"OPEN", | |
94 | "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED", | |
95 | "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"} | |
96 | ||
97 | def __init__(self, fname, path="/etc/sysconfig/network-scripts"): | |
98 | ||
99 | self.__state = self.__STATE['OPEN'] | |
100 | self.__fname = fname | |
101 | self.__children = [] | |
102 | ||
103 | if debug_mode(): | |
104 | dirname = output_directory | |
105 | else: | |
106 | dirname = path | |
107 | ||
108 | self.__path = os.path.join(dirname, fname) | |
109 | self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old") | |
110 | self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new") | |
111 | self.__unlink = False | |
112 | ||
113 | self.__f = open(self.__newpath, "w") | |
114 | ||
115 | def attach_child(self, child): | |
116 | self.__children.append(child) | |
117 | ||
118 | def path(self): | |
119 | return self.__path | |
120 | ||
121 | def readlines(self): | |
122 | try: | |
123 | return open(self.path()).readlines() | |
124 | except: | |
125 | return "" | |
126 | ||
127 | def write(self, args): | |
128 | if self.__state != self.__STATE['OPEN']: | |
129 | raise Error("Attempt to write to file in state %s" % self.__state) | |
130 | self.__f.write(args) | |
131 | ||
132 | def unlink(self): | |
133 | if self.__state != self.__STATE['OPEN']: | |
134 | raise Error("Attempt to unlink file in state %s" % self.__state) | |
135 | self.__unlink = True | |
136 | self.__f.close() | |
137 | self.__state = self.__STATE['NOT-APPLIED'] | |
138 | ||
139 | def close(self): | |
140 | if self.__state != self.__STATE['OPEN']: | |
141 | raise Error("Attempt to close file in state %s" % self.__state) | |
142 | ||
143 | self.__f.close() | |
144 | self.__state = self.__STATE['NOT-APPLIED'] | |
145 | ||
146 | def changed(self): | |
147 | if self.__state != self.__STATE['NOT-APPLIED']: | |
148 | raise Error("Attempt to compare file in state %s" % self.__state) | |
149 | ||
150 | return True | |
151 | ||
152 | def apply(self): | |
153 | if self.__state != self.__STATE['NOT-APPLIED']: | |
154 | raise Error("Attempt to apply configuration from state %s" % self.__state) | |
155 | ||
156 | for child in self.__children: | |
157 | child.apply() | |
158 | ||
159 | log("Applying changes to %s configuration" % self.__fname) | |
160 | ||
161 | # Remove previous backup. | |
162 | if os.access(self.__oldpath, os.F_OK): | |
163 | os.unlink(self.__oldpath) | |
164 | ||
165 | # Save current configuration. | |
166 | if os.access(self.__path, os.F_OK): | |
167 | os.link(self.__path, self.__oldpath) | |
168 | os.unlink(self.__path) | |
169 | ||
170 | # Apply new configuration. | |
171 | assert(os.path.exists(self.__newpath)) | |
172 | if not self.__unlink: | |
173 | os.link(self.__newpath, self.__path) | |
174 | else: | |
175 | pass # implicit unlink of original file | |
176 | ||
177 | # Remove temporary file. | |
178 | os.unlink(self.__newpath) | |
179 | ||
180 | self.__state = self.__STATE['APPLIED'] | |
181 | ||
182 | def revert(self): | |
183 | if self.__state != self.__STATE['APPLIED']: | |
184 | raise Error("Attempt to revert configuration from state %s" % self.__state) | |
185 | ||
186 | for child in self.__children: | |
187 | child.revert() | |
188 | ||
189 | log("Reverting changes to %s configuration" % self.__fname) | |
190 | ||
191 | # Remove existing new configuration | |
192 | if os.access(self.__newpath, os.F_OK): | |
193 | os.unlink(self.__newpath) | |
194 | ||
195 | # Revert new configuration. | |
196 | if os.access(self.__path, os.F_OK): | |
197 | os.link(self.__path, self.__newpath) | |
198 | os.unlink(self.__path) | |
199 | ||
200 | # Revert to old configuration. | |
201 | if os.access(self.__oldpath, os.F_OK): | |
202 | os.link(self.__oldpath, self.__path) | |
203 | os.unlink(self.__oldpath) | |
204 | ||
205 | # Leave .*.xapi-new as an aid to debugging. | |
206 | ||
207 | self.__state = self.__STATE['REVERTED'] | |
208 | ||
209 | def commit(self): | |
210 | if self.__state != self.__STATE['APPLIED']: | |
211 | raise Error("Attempt to commit configuration from state %s" % self.__state) | |
212 | ||
213 | for child in self.__children: | |
214 | child.commit() | |
215 | ||
216 | log("Committing changes to %s configuration" % self.__fname) | |
217 | ||
218 | if os.access(self.__oldpath, os.F_OK): | |
219 | os.unlink(self.__oldpath) | |
220 | if os.access(self.__newpath, os.F_OK): | |
221 | os.unlink(self.__newpath) | |
222 | ||
223 | self.__state = self.__STATE['COMMITTED'] | |
224 | ||
225 | def debug_mode(): | |
226 | return output_directory is not None | |
227 | ||
228 | def log(s): | |
229 | if debug_mode(): | |
230 | print >>sys.stderr, s | |
231 | else: | |
232 | syslog.syslog(s) | |
233 | ||
234 | def check_allowed(pif): | |
235 | pifrec = db.get_pif_record(pif) | |
236 | try: | |
237 | f = open("/proc/ardence") | |
238 | macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines()) | |
239 | f.close() | |
240 | if len(macline) == 1: | |
241 | p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE) | |
242 | if p.match(macline[0]): | |
243 | log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec) | |
244 | return False | |
245 | except IOError: | |
246 | pass | |
247 | return True | |
248 | ||
249 | def interface_exists(i): | |
250 | return os.path.exists("/sys/class/net/" + i) | |
251 | ||
2bb451b6 BP |
252 | def get_netdev_mac(device): |
253 | try: | |
254 | return read_first_line_of_file("/sys/class/net/%s/address" % device) | |
255 | except: | |
256 | # Probably no such device. | |
257 | return None | |
258 | ||
259 | def get_netdev_tx_queue_len(device): | |
260 | try: | |
261 | return int(read_first_line_of_file("/sys/class/net/%s/tx_queue_len" | |
262 | % device)) | |
263 | except: | |
264 | # Probably no such device. | |
265 | return None | |
266 | ||
267 | def get_netdev_by_mac(mac): | |
268 | maybe = None | |
269 | for device in os.listdir("/sys/class/net"): | |
0ceda58c | 270 | dev_mac = get_netdev_mac(device) |
2bb451b6 BP |
271 | if dev_mac and mac.lower() == dev_mac.lower(): |
272 | if get_netdev_tx_queue_len(device): | |
273 | return device | |
274 | if not maybe: | |
275 | # Probably a datapath internal port. | |
276 | maybe = device | |
277 | return maybe | |
278 | ||
064af421 BP |
279 | class DatabaseCache(object): |
280 | def __init__(self, session_ref=None, cache_file=None): | |
281 | if session_ref and cache_file: | |
282 | raise Error("can't specify session reference and cache file") | |
283 | ||
284 | if cache_file == None: | |
285 | session = XenAPI.xapi_local() | |
286 | ||
287 | if not session_ref: | |
288 | log("No session ref given on command line, logging in.") | |
289 | session.xenapi.login_with_password("root", "") | |
290 | else: | |
291 | session._session = session_ref | |
292 | ||
293 | try: | |
294 | self.__vlans = session.xenapi.VLAN.get_all_records() | |
295 | self.__bonds = session.xenapi.Bond.get_all_records() | |
296 | self.__pifs = session.xenapi.PIF.get_all_records() | |
297 | self.__networks = session.xenapi.network.get_all_records() | |
298 | finally: | |
299 | if not session_ref: | |
300 | session.xenapi.session.logout() | |
301 | else: | |
302 | log("Loading xapi database cache from %s" % cache_file) | |
303 | f = open(cache_file, 'r') | |
304 | members = pickle.load(f) | |
305 | self.extras = pickle.load(f) | |
306 | f.close() | |
307 | ||
308 | self.__vlans = members['vlans'] | |
309 | self.__bonds = members['bonds'] | |
310 | self.__pifs = members['pifs'] | |
311 | self.__networks = members['networks'] | |
312 | ||
313 | def save(self, cache_file, extras): | |
314 | f = open(cache_file, 'w') | |
315 | pickle.dump({'vlans': self.__vlans, | |
316 | 'bonds': self.__bonds, | |
317 | 'pifs': self.__pifs, | |
318 | 'networks': self.__networks}, f) | |
319 | pickle.dump(extras, f) | |
320 | f.close() | |
321 | ||
322 | def get_pif_by_uuid(self, uuid): | |
323 | pifs = map(lambda (ref,rec): ref, | |
324 | filter(lambda (ref,rec): uuid == rec['uuid'], | |
325 | self.__pifs.items())) | |
326 | if len(pifs) == 0: | |
327 | raise Error("Unknown PIF \"%s\"" % uuid) | |
328 | elif len(pifs) > 1: | |
329 | raise Error("Non-unique PIF \"%s\"" % uuid) | |
330 | ||
331 | return pifs[0] | |
332 | ||
333 | def get_pifs_by_record(self, record): | |
334 | """record is partial pif record. | |
335 | Get the pif(s) whose record matches. | |
336 | """ | |
337 | def match(pifrec): | |
338 | for key in record: | |
339 | if record[key] != pifrec[key]: | |
340 | return False | |
341 | return True | |
342 | ||
343 | return map(lambda (ref,rec): ref, | |
344 | filter(lambda (ref,rec): match(rec), | |
345 | self.__pifs.items())) | |
346 | ||
347 | def get_pif_by_record(self, record): | |
348 | """record is partial pif record. | |
349 | Get the pif whose record matches. | |
350 | """ | |
351 | pifs = self.get_pifs_by_record(record) | |
352 | if len(pifs) == 0: | |
353 | raise Error("No matching PIF \"%s\"" % str(record)) | |
354 | elif len(pifs) > 1: | |
355 | raise Error("Multiple matching PIFs \"%s\"" % str(record)) | |
356 | ||
357 | return pifs[0] | |
358 | ||
359 | def get_pif_by_bridge(self, host, bridge): | |
360 | networks = map(lambda (ref,rec): ref, | |
361 | filter(lambda (ref,rec): rec['bridge'] == bridge, | |
362 | self.__networks.items())) | |
363 | if len(networks) == 0: | |
364 | raise Error("No matching network \"%s\"") | |
365 | ||
366 | answer = None | |
367 | for network in networks: | |
368 | nwrec = self.get_network_record(network) | |
369 | for pif in nwrec['PIFs']: | |
370 | pifrec = self.get_pif_record(pif) | |
371 | if pifrec['host'] != host: | |
372 | continue | |
373 | if answer: | |
374 | raise Error("Multiple PIFs on %s for network %s" % (host, bridge)) | |
375 | answer = pif | |
376 | if not answer: | |
377 | raise Error("No PIF on %s for network %s" % (host, bridge)) | |
378 | return answer | |
379 | ||
380 | def get_pif_record(self, pif): | |
381 | if self.__pifs.has_key(pif): | |
382 | return self.__pifs[pif] | |
383 | raise Error("Unknown PIF \"%s\"" % pif) | |
384 | def get_all_pifs(self): | |
385 | return self.__pifs | |
386 | def pif_exists(self, pif): | |
387 | return self.__pifs.has_key(pif) | |
388 | ||
389 | def get_management_pif(self, host): | |
390 | """ Returns the management pif on host | |
391 | """ | |
392 | all = self.get_all_pifs() | |
393 | for pif in all: | |
394 | pifrec = self.get_pif_record(pif) | |
395 | if pifrec['management'] and pifrec['host'] == host : | |
396 | return pif | |
397 | return None | |
398 | ||
399 | def get_network_record(self, network): | |
400 | if self.__networks.has_key(network): | |
401 | return self.__networks[network] | |
402 | raise Error("Unknown network \"%s\"" % network) | |
403 | def get_all_networks(self): | |
404 | return self.__networks | |
405 | ||
406 | def get_bond_record(self, bond): | |
407 | if self.__bonds.has_key(bond): | |
408 | return self.__bonds[bond] | |
409 | else: | |
410 | return None | |
411 | ||
412 | def get_vlan_record(self, vlan): | |
413 | if self.__vlans.has_key(vlan): | |
414 | return self.__vlans[vlan] | |
415 | else: | |
416 | return None | |
417 | ||
418 | def bridge_name(pif): | |
419 | """Return the bridge name associated with pif, or None if network is bridgeless""" | |
420 | pifrec = db.get_pif_record(pif) | |
421 | nwrec = db.get_network_record(pifrec['network']) | |
422 | ||
423 | if nwrec['bridge']: | |
424 | # TODO: sanity check that nwrec['bridgeless'] != 'true' | |
425 | return nwrec['bridge'] | |
426 | else: | |
427 | # TODO: sanity check that nwrec['bridgeless'] == 'true' | |
428 | return None | |
429 | ||
430 | def interface_name(pif): | |
431 | """Construct an interface name from the given PIF record.""" | |
432 | ||
433 | pifrec = db.get_pif_record(pif) | |
434 | ||
435 | if pifrec['VLAN'] == '-1': | |
436 | return pifrec['device'] | |
437 | else: | |
438 | return "%(device)s.%(VLAN)s" % pifrec | |
439 | ||
440 | def datapath_name(pif): | |
441 | """Return the OpenFlow datapath name associated with pif. | |
442 | For a non-VLAN PIF, the datapath name is the bridge name. | |
443 | For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave. | |
444 | (xapi will create a datapath named with the bridge name even though we won't | |
445 | use it.) | |
446 | """ | |
447 | ||
448 | pifrec = db.get_pif_record(pif) | |
449 | ||
450 | if pifrec['VLAN'] == '-1': | |
451 | return bridge_name(pif) | |
452 | else: | |
453 | return bridge_name(get_vlan_slave_of_pif(pif)) | |
454 | ||
455 | def ipdev_name(pif): | |
456 | """Return the the name of the network device that carries the | |
457 | IP configuration (if any) associated with pif. | |
458 | The ipdev name is the same as the bridge name. | |
459 | """ | |
460 | ||
461 | pifrec = db.get_pif_record(pif) | |
462 | return bridge_name(pif) | |
463 | ||
bc952bb3 | 464 | def get_physdev_pifs(pif): |
2bb451b6 BP |
465 | """Return the PIFs for the physical network device(s) associated with pif. |
466 | For a VLAN PIF, this is the VLAN slave's physical device PIF. | |
467 | For a bond master PIF, these are the bond slave PIFs. | |
468 | For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF. | |
064af421 BP |
469 | """ |
470 | ||
471 | pifrec = db.get_pif_record(pif) | |
472 | ||
473 | if pifrec['VLAN'] != '-1': | |
2bb451b6 | 474 | return [get_vlan_slave_of_pif(pif)] |
064af421 | 475 | elif len(pifrec['bond_master_of']) != 0: |
2bb451b6 | 476 | return get_bond_slaves_of_pif(pif) |
064af421 | 477 | else: |
2bb451b6 BP |
478 | return [pif] |
479 | ||
bc952bb3 | 480 | def get_physdev_names(pif): |
2bb451b6 BP |
481 | """Return the name(s) of the physical network device(s) associated with pif. |
482 | For a VLAN PIF, the physical devices are the VLAN slave's physical devices. | |
483 | For a bond master PIF, the physical devices are the bond slaves. | |
484 | For a non-VLAN, non-bond master PIF, the physical device is the PIF itself. | |
485 | """ | |
486 | ||
bc952bb3 | 487 | return [db.get_pif_record(phys)['device'] for phys in get_physdev_pifs(pif)] |
064af421 BP |
488 | |
489 | def log_pif_action(action, pif): | |
490 | pifrec = db.get_pif_record(pif) | |
491 | pifrec['action'] = action | |
492 | pifrec['interface-name'] = interface_name(pif) | |
493 | if action == "rewrite": | |
494 | pifrec['message'] = "Rewrite PIF %(uuid)s configuration" % pifrec | |
495 | else: | |
496 | pifrec['message'] = "Bring %(action)s PIF %(uuid)s" % pifrec | |
497 | log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % pifrec) | |
498 | ||
499 | def get_bond_masters_of_pif(pif): | |
500 | """Returns a list of PIFs which are bond masters of this PIF""" | |
501 | ||
502 | pifrec = db.get_pif_record(pif) | |
503 | ||
504 | bso = pifrec['bond_slave_of'] | |
505 | ||
506 | # bond-slave-of is currently a single reference but in principle a | |
507 | # PIF could be a member of several bonds which are not | |
508 | # concurrently attached. Be robust to this possibility. | |
509 | if not bso or bso == "OpaqueRef:NULL": | |
510 | bso = [] | |
511 | elif not type(bso) == list: | |
512 | bso = [bso] | |
513 | ||
514 | bondrecs = [db.get_bond_record(bond) for bond in bso] | |
515 | bondrecs = [rec for rec in bondrecs if rec] | |
516 | ||
517 | return [bond['master'] for bond in bondrecs] | |
518 | ||
519 | def get_bond_slaves_of_pif(pif): | |
520 | """Returns a list of PIFs which make up the given bonded pif.""" | |
521 | ||
522 | pifrec = db.get_pif_record(pif) | |
523 | host = pifrec['host'] | |
524 | ||
525 | bmo = pifrec['bond_master_of'] | |
526 | if len(bmo) > 1: | |
527 | raise Error("Bond-master-of contains too many elements") | |
528 | ||
529 | if len(bmo) == 0: | |
530 | return [] | |
531 | ||
532 | bondrec = db.get_bond_record(bmo[0]) | |
533 | if not bondrec: | |
534 | raise Error("No bond record for bond master PIF") | |
535 | ||
536 | return bondrec['slaves'] | |
537 | ||
538 | def get_vlan_slave_of_pif(pif): | |
539 | """Find the PIF which is the VLAN slave of pif. | |
540 | ||
541 | Returns the 'physical' PIF underneath the a VLAN PIF @pif.""" | |
542 | ||
543 | pifrec = db.get_pif_record(pif) | |
544 | ||
545 | vlan = pifrec['VLAN_master_of'] | |
546 | if not vlan or vlan == "OpaqueRef:NULL": | |
547 | raise Error("PIF is not a VLAN master") | |
548 | ||
549 | vlanrec = db.get_vlan_record(vlan) | |
550 | if not vlanrec: | |
551 | raise Error("No VLAN record found for PIF") | |
552 | ||
553 | return vlanrec['tagged_PIF'] | |
554 | ||
555 | def get_vlan_masters_of_pif(pif): | |
556 | """Returns a list of PIFs which are VLANs on top of the given pif.""" | |
557 | ||
558 | pifrec = db.get_pif_record(pif) | |
559 | vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']] | |
560 | return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])] | |
561 | ||
562 | def interface_deconfigure_commands(interface): | |
563 | # The use of [!0-9] keeps an interface of 'eth0' from matching | |
564 | # VLANs attached to eth0 (such as 'eth0.123'), which are distinct | |
565 | # interfaces. | |
566 | return ['--del-match=bridge.*.port=%s' % interface, | |
567 | '--del-match=bonding.%s.[!0-9]*' % interface, | |
568 | '--del-match=bonding.*.slave=%s' % interface, | |
569 | '--del-match=vlan.%s.[!0-9]*' % interface, | |
570 | '--del-match=port.%s.[!0-9]*' % interface, | |
571 | '--del-match=iface.%s.[!0-9]*' % interface] | |
572 | ||
573 | def run_command(command): | |
574 | log("Running command: " + ' '.join(command)) | |
575 | if os.spawnl(os.P_WAIT, command[0], *command) != 0: | |
576 | log("Command failed: " + ' '.join(command)) | |
577 | return False | |
578 | return True | |
579 | ||
2bb451b6 BP |
580 | def rename_netdev(old_name, new_name): |
581 | log("Changing the name of %s to %s" % (old_name, new_name)) | |
582 | run_command(['/sbin/ifconfig', old_name, 'down']) | |
583 | if not run_command(['/sbin/ip', 'link', 'set', old_name, | |
584 | 'name', new_name]): | |
585 | raise Error("Could not rename %s to %s" % (old_name, new_name)) | |
586 | ||
587 | # Check whether 'pif' exists and has the correct MAC. | |
588 | # If not, try to find a device with the correct MAC and rename it. | |
589 | # 'already_renamed' is used to avoid infinite recursion. | |
590 | def remap_pif(pif, already_renamed=[]): | |
591 | pifrec = db.get_pif_record(pif) | |
592 | device = pifrec['device'] | |
593 | mac = pifrec['MAC'] | |
594 | ||
595 | # Is there a network device named 'device' at all? | |
596 | device_exists = interface_exists(device) | |
597 | if device_exists: | |
598 | # Yes. Does it have MAC 'mac'? | |
599 | found_mac = get_netdev_mac(device) | |
600 | if found_mac and mac.lower() == found_mac.lower(): | |
601 | # Yes, everything checks out the way we want. Nothing to do. | |
602 | return | |
603 | else: | |
604 | log("No network device %s" % device) | |
605 | ||
606 | # What device has MAC 'mac'? | |
607 | cur_device = get_netdev_by_mac(mac) | |
608 | if not cur_device: | |
609 | log("No network device has MAC %s" % mac) | |
610 | return | |
611 | ||
612 | # First rename 'device', if it exists, to get it out of the way | |
613 | # for 'cur_device' to replace it. | |
614 | if device_exists: | |
615 | rename_netdev(device, "dev%d" % random.getrandbits(24)) | |
616 | ||
617 | # Rename 'cur_device' to 'device'. | |
618 | rename_netdev(cur_device, device) | |
619 | ||
449776d8 BP |
620 | def read_first_line_of_file(name): |
621 | file = None | |
622 | try: | |
623 | file = open(name, 'r') | |
624 | return file.readline().rstrip('\n') | |
625 | finally: | |
626 | if file != None: | |
627 | file.close() | |
628 | ||
064af421 BP |
629 | def down_netdev(interface, deconfigure=True): |
630 | if not interface_exists(interface): | |
631 | log("down_netdev: interface %s does not exist, ignoring" % interface) | |
632 | return | |
064af421 | 633 | if deconfigure: |
064af421 BP |
634 | # Kill dhclient. |
635 | pidfile_name = '/var/run/dhclient-%s.pid' % interface | |
064af421 | 636 | try: |
449776d8 | 637 | os.kill(int(read_first_line_of_file(pidfile_name)), signal.SIGTERM) |
064af421 BP |
638 | except: |
639 | pass | |
064af421 BP |
640 | |
641 | # Remove dhclient pidfile. | |
642 | try: | |
643 | os.remove(pidfile_name) | |
644 | except: | |
645 | pass | |
3cc42ebb BP |
646 | |
647 | run_command(["/sbin/ifconfig", interface, '0.0.0.0']) | |
648 | ||
649 | run_command(["/sbin/ifconfig", interface, 'down']) | |
064af421 BP |
650 | |
651 | def up_netdev(interface): | |
652 | run_command(["/sbin/ifconfig", interface, 'up']) | |
653 | ||
654 | def find_distinguished_pifs(pif): | |
655 | """Returns the PIFs on host that own DNS and the default route. | |
656 | The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set. | |
657 | The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set. | |
658 | ||
659 | Note: we prune out the bond master pif (if it exists). | |
660 | This is because when we are called to bring up an interface with a bond master, it is implicit that | |
661 | we should bring down that master.""" | |
662 | ||
663 | pifrec = db.get_pif_record(pif) | |
664 | host = pifrec['host'] | |
665 | ||
666 | pifs_on_host = [ __pif for __pif in db.get_all_pifs() if | |
667 | db.get_pif_record(__pif)['host'] == host and | |
668 | (not __pif in get_bond_masters_of_pif(pif)) ] | |
669 | ||
670 | peerdns_pif = None | |
671 | defaultroute_pif = None | |
672 | ||
673 | # loop through all the pifs on this host looking for one with | |
674 | # other-config:peerdns = true, and one with | |
675 | # other-config:default-route=true | |
676 | for __pif in pifs_on_host: | |
677 | __pifrec = db.get_pif_record(__pif) | |
678 | __oc = __pifrec['other_config'] | |
679 | if __oc.has_key('peerdns') and __oc['peerdns'] == 'true': | |
680 | if peerdns_pif == None: | |
681 | peerdns_pif = __pif | |
682 | else: | |
683 | log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \ | |
684 | (db.get_pif_record(peerdns_pif)['device'], __pifrec['device'])) | |
685 | if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true': | |
686 | if defaultroute_pif == None: | |
687 | defaultroute_pif = __pif | |
688 | else: | |
689 | log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \ | |
690 | (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device'])) | |
691 | ||
692 | # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute | |
693 | if peerdns_pif == None: | |
694 | peerdns_pif = management_pif | |
695 | if defaultroute_pif == None: | |
696 | defaultroute_pif = management_pif | |
697 | ||
698 | return peerdns_pif, defaultroute_pif | |
699 | ||
05e2ad78 BP |
700 | def run_ethtool(device, oc): |
701 | # Run "ethtool -s" if there are any settings. | |
064af421 BP |
702 | settings = [] |
703 | if oc.has_key('ethtool-speed'): | |
704 | val = oc['ethtool-speed'] | |
705 | if val in ["10", "100", "1000"]: | |
706 | settings += ['speed', val] | |
707 | else: | |
708 | log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val) | |
709 | if oc.has_key('ethtool-duplex'): | |
710 | val = oc['ethtool-duplex'] | |
711 | if val in ["10", "100", "1000"]: | |
712 | settings += ['duplex', 'val'] | |
713 | else: | |
714 | log("Invalid value for ethtool-duplex = %s. Must be half|full." % val) | |
715 | if oc.has_key('ethtool-autoneg'): | |
716 | val = oc['ethtool-autoneg'] | |
717 | if val in ["true", "on"]: | |
718 | settings += ['autoneg', 'on'] | |
719 | elif val in ["false", "off"]: | |
720 | settings += ['autoneg', 'off'] | |
721 | else: | |
722 | log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val) | |
05e2ad78 BP |
723 | if settings: |
724 | run_command(['/sbin/ethtool', '-s', device] + settings) | |
064af421 | 725 | |
05e2ad78 | 726 | # Run "ethtool -K" if there are any offload settings. |
064af421 BP |
727 | offload = [] |
728 | for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"): | |
729 | if oc.has_key("ethtool-" + opt): | |
730 | val = oc["ethtool-" + opt] | |
731 | if val in ["true", "on"]: | |
732 | offload += [opt, 'on'] | |
733 | elif val in ["false", "off"]: | |
734 | offload += [opt, 'off'] | |
735 | else: | |
736 | log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val)) | |
05e2ad78 BP |
737 | if offload: |
738 | run_command(['/sbin/ethtool', '-K', device] + offload) | |
064af421 | 739 | |
05e2ad78 BP |
740 | def mtu_setting(oc): |
741 | if oc.has_key('mtu'): | |
742 | try: | |
743 | int(oc['mtu']) # Check that the value is an integer | |
744 | return ['mtu', oc['mtu']] | |
745 | except ValueError, x: | |
746 | log("Invalid value for mtu = %s" % mtu) | |
747 | return [] | |
064af421 | 748 | |
88acec3b | 749 | def configure_local_port(pif): |
064af421 BP |
750 | pifrec = db.get_pif_record(pif) |
751 | datapath = datapath_name(pif) | |
752 | ipdev = ipdev_name(pif) | |
753 | ||
754 | host = pifrec['host'] | |
755 | nw = pifrec['network'] | |
756 | nwrec = db.get_network_record(nw) | |
757 | ||
c87d1024 BP |
758 | pif_oc = pifrec['other_config'] |
759 | nw_oc = nwrec['other_config'] | |
760 | ||
761 | # IP (except DHCP) and MTU. | |
064af421 BP |
762 | ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up'] |
763 | gateway = '' | |
764 | if pifrec['ip_configuration_mode'] == "DHCP": | |
765 | pass | |
766 | elif pifrec['ip_configuration_mode'] == "Static": | |
767 | ifconfig_argv += [pifrec['IP']] | |
768 | ifconfig_argv += ['netmask', pifrec['netmask']] | |
769 | gateway = pifrec['gateway'] | |
770 | elif pifrec['ip_configuration_mode'] == "None": | |
771 | # Nothing to do. | |
772 | pass | |
773 | else: | |
774 | raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode']) | |
c87d1024 | 775 | ifconfig_argv += mtu_setting(nw_oc) |
064af421 BP |
776 | run_command(ifconfig_argv) |
777 | ||
778 | (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif) | |
779 | ||
c87d1024 | 780 | # /etc/resolv.conf |
064af421 BP |
781 | if peerdns_pif == pif: |
782 | f = ConfigurationFile('resolv.conf', "/etc") | |
c87d1024 BP |
783 | if pif_oc.has_key('domain'): |
784 | f.write("search %s\n" % pif_oc['domain']) | |
064af421 BP |
785 | for dns in pifrec['DNS'].split(","): |
786 | f.write("nameserver %s\n" % dns) | |
787 | f.close() | |
788 | f.apply() | |
789 | f.commit() | |
790 | ||
c87d1024 | 791 | # Routing. |
064af421 BP |
792 | if defaultroute_pif == pif and gateway != '': |
793 | run_command(['/sbin/ip', 'route', 'replace', 'default', | |
794 | 'via', gateway, 'dev', ipdev]) | |
c87d1024 BP |
795 | if nw_oc.has_key('static-routes'): |
796 | for line in nw_oc['static-routes'].split(','): | |
064af421 BP |
797 | network, masklen, gateway = line.split('/') |
798 | run_command(['/sbin/ip', 'route', 'add', | |
8f749dac | 799 | '%s/%s' % (network, masklen), 'via', gateway, |
064af421 BP |
800 | 'dev', ipdev]) |
801 | ||
05e2ad78 | 802 | # Ethtool. |
c87d1024 | 803 | run_ethtool(ipdev, nw_oc) |
064af421 | 804 | |
c87d1024 | 805 | # DHCP. |
064af421 BP |
806 | if pifrec['ip_configuration_mode'] == "DHCP": |
807 | ||
808 | print "Determining IP information for %s..." % ipdev, | |
809 | argv = ['/sbin/dhclient', '-q', | |
810 | '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev, | |
811 | '-pf', '/var/run/dhclient-%s.pid' % ipdev, | |
812 | ipdev] | |
813 | if run_command(argv): | |
814 | print 'done.' | |
815 | else: | |
816 | print 'failed.' | |
817 | ||
88acec3b BP |
818 | def configure_physdev(pif): |
819 | pifrec = db.get_pif_record(pif) | |
820 | device = pifrec['device'] | |
821 | oc = pifrec['other_config'] | |
822 | ||
823 | run_command(['/sbin/ifconfig', device, 'up'] + mtu_setting(oc)) | |
824 | run_ethtool(device, oc) | |
825 | ||
064af421 BP |
826 | def modify_config(commands): |
827 | run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer', | |
828 | '-F', '/etc/ovs-vswitchd.conf'] | |
829 | + commands + ['-c']) | |
830 | run_command(['/sbin/service', 'vswitch', 'reload']) | |
831 | ||
832 | def is_bond_pif(pif): | |
833 | pifrec = db.get_pif_record(pif) | |
834 | return len(pifrec['bond_master_of']) != 0 | |
835 | ||
836 | def configure_bond(pif): | |
837 | pifrec = db.get_pif_record(pif) | |
838 | interface = interface_name(pif) | |
839 | ipdev = ipdev_name(pif) | |
840 | datapath = datapath_name(pif) | |
bc952bb3 | 841 | physdev_names = get_physdev_names(pif) |
064af421 BP |
842 | |
843 | argv = ['--del-match=bonding.%s.[!0-9]*' % interface] | |
844 | argv += ["--add=bonding.%s.slave=%s" % (interface, slave) | |
bc952bb3 | 845 | for slave in physdev_names] |
d2cd45db | 846 | argv += ['--add=bonding.%s.fake-iface=true' % interface] |
064af421 | 847 | |
58b7527e BP |
848 | if pifrec['MAC'] != "": |
849 | argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])] | |
850 | ||
064af421 BP |
851 | # Bonding options. |
852 | bond_options = { | |
853 | "mode": "balance-slb", | |
854 | "miimon": "100", | |
855 | "downdelay": "200", | |
856 | "updelay": "31000", | |
857 | "use_carrier": "1", | |
858 | } | |
859 | # override defaults with values from other-config whose keys | |
860 | # being with "bond-" | |
861 | oc = pifrec['other_config'] | |
862 | overrides = filter(lambda (key,val): | |
863 | key.startswith("bond-"), oc.items()) | |
864 | overrides = map(lambda (key,val): (key[5:], val), overrides) | |
865 | bond_options.update(overrides) | |
866 | for (name,val) in bond_options.items(): | |
867 | argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)] | |
868 | return argv | |
869 | ||
870 | def action_up(pif): | |
871 | pifrec = db.get_pif_record(pif) | |
872 | ||
873 | bridge = bridge_name(pif) | |
874 | interface = interface_name(pif) | |
875 | ipdev = ipdev_name(pif) | |
876 | datapath = datapath_name(pif) | |
bc952bb3 BP |
877 | physdev_pifs = get_physdev_pifs(pif) |
878 | physdev_names = get_physdev_names(pif) | |
064af421 BP |
879 | vlan_slave = None |
880 | if pifrec['VLAN'] != '-1': | |
881 | vlan_slave = get_vlan_slave_of_pif(pif) | |
882 | if vlan_slave and is_bond_pif(vlan_slave): | |
883 | bond_master = vlan_slave | |
884 | elif is_bond_pif(pif): | |
885 | bond_master = pif | |
886 | else: | |
887 | bond_master = None | |
8826590a BP |
888 | if bond_master: |
889 | bond_slaves = get_bond_slaves_of_pif(bond_master) | |
890 | else: | |
891 | bond_slaves = [] | |
064af421 BP |
892 | bond_masters = get_bond_masters_of_pif(pif) |
893 | ||
894 | # Support "rpm -e vswitch" gracefully by keeping Centos configuration | |
895 | # files up-to-date, even though we don't use them or need them. | |
896 | f = configure_pif(pif) | |
897 | mode = pifrec['ip_configuration_mode'] | |
898 | if bridge: | |
899 | log("Configuring %s using %s configuration" % (bridge, mode)) | |
900 | br = open_network_ifcfg(pif) | |
901 | configure_network(pif, br) | |
902 | br.close() | |
903 | f.attach_child(br) | |
904 | else: | |
905 | log("Configuring %s using %s configuration" % (interface, mode)) | |
906 | configure_network(pif, f) | |
907 | f.close() | |
908 | for master in bond_masters: | |
909 | master_bridge = bridge_name(master) | |
910 | removed = unconfigure_pif(master) | |
911 | f.attach_child(removed) | |
912 | if master_bridge: | |
913 | removed = open_network_ifcfg(master) | |
914 | log("Unlinking stale file %s" % removed.path()) | |
915 | removed.unlink() | |
916 | f.attach_child(removed) | |
917 | ||
918 | # /etc/xensource/scripts/vif needs to know where to add VIFs. | |
919 | if vlan_slave: | |
920 | if not os.path.exists(vswitch_config_dir): | |
921 | os.mkdir(vswitch_config_dir) | |
922 | br = ConfigurationFile("br-%s" % bridge, vswitch_config_dir) | |
923 | br.write("VLAN_SLAVE=%s\n" % datapath) | |
924 | br.write("VLAN_VID=%s\n" % pifrec['VLAN']) | |
925 | br.close() | |
926 | f.attach_child(br) | |
927 | ||
928 | # Update all configuration files (both ours and Centos's). | |
929 | f.apply() | |
930 | f.commit() | |
931 | ||
2bb451b6 BP |
932 | # Check the MAC address of each network device and remap if |
933 | # necessary to make names match our expectations. | |
bc952bb3 | 934 | for physdev_pif in physdev_pifs: |
2bb451b6 BP |
935 | remap_pif(physdev_pif) |
936 | ||
064af421 BP |
937 | # "ifconfig down" the network device and delete its IP address, etc. |
938 | down_netdev(ipdev) | |
bc952bb3 BP |
939 | for physdev_name in physdev_names: |
940 | down_netdev(physdev_name) | |
064af421 | 941 | |
7a3696ad BP |
942 | # If we are bringing up a bond, remove IP addresses from the |
943 | # slaves (because we are implicitly being asked to take them down). | |
944 | # | |
945 | # Conversely, if we are bringing up an interface that has bond | |
946 | # masters, remove IP addresses from the bond master (because we | |
947 | # are implicitly being asked to take it down). | |
948 | for bond_pif in bond_slaves + bond_masters: | |
949 | run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0']) | |
950 | ||
064af421 | 951 | # Remove all keys related to pif and any bond masters linked to PIF. |
bc952bb3 | 952 | del_ports = [ipdev] + physdev_names + bond_masters |
064af421 BP |
953 | if vlan_slave and bond_master: |
954 | del_ports += [interface_name(bond_master)] | |
955 | ||
956 | # What ports do we need to add to the datapath? | |
957 | # | |
958 | # We definitely need the ipdev, and ordinarily we want the | |
959 | # physical devices too, but for bonds we need the bond as bridge | |
960 | # port. | |
961 | add_ports = [ipdev, datapath] | |
962 | if not bond_master: | |
bc952bb3 | 963 | add_ports += physdev_names |
064af421 BP |
964 | else: |
965 | add_ports += [interface_name(bond_master)] | |
966 | ||
967 | # What ports do we need to delete? | |
968 | # | |
969 | # - All the ports that we add, to avoid duplication and to drop | |
970 | # them from another datapath in case they're misassigned. | |
971 | # | |
972 | # - The physical devices, since they will either be in add_ports | |
973 | # or added to the bonding device (see below). | |
974 | # | |
975 | # - The bond masters for pif. (Ordinarily pif shouldn't have any | |
976 | # bond masters. If it does then interface-reconfigure is | |
977 | # implicitly being asked to take them down.) | |
bc952bb3 | 978 | del_ports = add_ports + physdev_names + bond_masters |
064af421 BP |
979 | |
980 | # What networks does this datapath carry? | |
981 | # | |
982 | # - The network corresponding to the datapath's PIF. | |
983 | # | |
984 | # - The networks corresponding to any VLANs attached to the | |
985 | # datapath's PIF. | |
986 | network_uuids = [] | |
987 | for nwpif in db.get_pifs_by_record({'device': pifrec['device'], | |
988 | 'host': pifrec['host']}): | |
989 | net = db.get_pif_record(nwpif)['network'] | |
990 | network_uuids += [db.get_network_record(net)['uuid']] | |
991 | ||
8826590a BP |
992 | # Bring up bond slaves early, because ovs-vswitchd initially |
993 | # enables or disables bond slaves based on whether carrier is | |
994 | # detected when they are added, and a network device that is down | |
995 | # always reports "no carrier". | |
88acec3b | 996 | bond_slave_physdev_pifs = [] |
8826590a | 997 | for slave in bond_slaves: |
88acec3b BP |
998 | bond_slave_physdev_pifs += get_physdev_pifs(slave) |
999 | for slave_physdev_pif in set(bond_slave_physdev_pifs): | |
1000 | configure_physdev(slave_physdev_pif) | |
8826590a | 1001 | |
064af421 BP |
1002 | # Now modify the ovs-vswitchd config file. |
1003 | argv = [] | |
1004 | for port in set(del_ports): | |
1005 | argv += interface_deconfigure_commands(port) | |
1006 | for port in set(add_ports): | |
1007 | argv += ['--add=bridge.%s.port=%s' % (datapath, port)] | |
1008 | if vlan_slave: | |
1009 | argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])] | |
1010 | argv += ['--add=iface.%s.internal=true' % (ipdev)] | |
1011 | ||
1012 | # xapi creates a bridge by the name of the ipdev and requires | |
1013 | # that the IP address will be on it. We need to delete this | |
1014 | # bridge because we need that device to be a member of our | |
1015 | # datapath. | |
1016 | argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev] | |
1017 | ||
1018 | # xapi insists that its attempts to create the bridge succeed, | |
1019 | # so force that to happen. | |
1020 | argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)] | |
1021 | else: | |
1022 | try: | |
1023 | os.unlink("%s/br-%s" % (vswitch_config_dir, bridge)) | |
1024 | except OSError: | |
1025 | pass | |
1026 | argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath] | |
1027 | argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid) | |
1028 | for uuid in set(network_uuids)] | |
1029 | if bond_master: | |
1030 | argv += configure_bond(bond_master) | |
1031 | modify_config(argv) | |
1032 | ||
8826590a BP |
1033 | # Bring up VLAN slave, plus physical devices other than bond |
1034 | # slaves (which we brought up earlier). | |
064af421 BP |
1035 | if vlan_slave: |
1036 | up_netdev(ipdev_name(vlan_slave)) | |
88acec3b BP |
1037 | for physdev_pif in set(physdev_pifs) - set(bond_slave_physdev_pifs): |
1038 | configure_physdev(physdev_pif) | |
064af421 | 1039 | |
88acec3b BP |
1040 | # Configure network device for local port. |
1041 | configure_local_port(pif) | |
0d7e8aac | 1042 | |
064af421 BP |
1043 | # Update /etc/issue (which contains the IP address of the management interface) |
1044 | os.system("/sbin/update-issue") | |
7a3696ad BP |
1045 | |
1046 | if bond_slaves: | |
1047 | # There seems to be a race somewhere: without this sleep, using | |
1048 | # XenCenter to create a bond that becomes the management interface | |
1049 | # fails with "The underlying connection was closed: A connection that | |
1050 | # was expected to be kept alive was closed by the server." on every | |
1051 | # second or third try, even though /var/log/messages doesn't show | |
1052 | # anything unusual. | |
1053 | # | |
1054 | # The race is probably present even without vswitch, but bringing up a | |
1055 | # bond without vswitch involves a built-in pause of 10 seconds or more | |
1056 | # to wait for the bond to transition from learning to forwarding state. | |
1057 | time.sleep(5) | |
064af421 BP |
1058 | |
1059 | def action_down(pif): | |
1060 | rec = db.get_pif_record(pif) | |
1061 | interface = interface_name(pif) | |
1062 | bridge = bridge_name(pif) | |
1063 | ipdev = ipdev_name(pif) | |
1064 | ||
1065 | # Support "rpm -e vswitch" gracefully by keeping Centos configuration | |
1066 | # files up-to-date, even though we don't use them or need them. | |
1067 | f = unconfigure_pif(pif) | |
1068 | if bridge: | |
1069 | br = open_network_ifcfg(pif) | |
1070 | log("Unlinking stale file %s" % br.path()) | |
1071 | br.unlink() | |
1072 | f.attach_child(br) | |
1073 | try: | |
1074 | f.apply() | |
1075 | f.commit() | |
1076 | except Error, e: | |
1077 | log("action_down failed to apply changes: %s" % e.msg) | |
1078 | f.revert() | |
1079 | raise | |
1080 | ||
1081 | argv = [] | |
1082 | if rec['VLAN'] != '-1': | |
1083 | # Get rid of the VLAN device itself. | |
1084 | down_netdev(ipdev) | |
1085 | argv += interface_deconfigure_commands(ipdev) | |
1086 | ||
1087 | # If the VLAN's slave is attached, stop here. | |
1088 | slave = get_vlan_slave_of_pif(pif) | |
1089 | if db.get_pif_record(slave)['currently_attached']: | |
1090 | log("VLAN slave is currently attached") | |
1091 | modify_config(argv) | |
1092 | return | |
1093 | ||
1094 | # If the VLAN's slave has other VLANs that are attached, stop here. | |
1095 | masters = get_vlan_masters_of_pif(slave) | |
1096 | for m in masters: | |
1097 | if m != pif and db.get_pif_record(m)['currently_attached']: | |
1098 | log("VLAN slave has other master %s" % interface_naem(m)) | |
1099 | modify_config(argv) | |
1100 | return | |
1101 | ||
1102 | # Otherwise, take down the VLAN's slave too. | |
1103 | log("No more masters, bring down vlan slave %s" % interface_name(slave)) | |
1104 | pif = slave | |
1105 | else: | |
1106 | # Stop here if this PIF has attached VLAN masters. | |
1107 | vlan_masters = get_vlan_masters_of_pif(pif) | |
1108 | log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters])) | |
1109 | for m in vlan_masters: | |
1110 | if db.get_pif_record(m)['currently_attached']: | |
1111 | log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m))) | |
1112 | return | |
1113 | ||
1114 | # pif is now either a bond or a physical device which needs to be | |
1115 | # brought down. pif might have changed so re-check all its attributes. | |
1116 | rec = db.get_pif_record(pif) | |
1117 | interface = interface_name(pif) | |
1118 | bridge = bridge_name(pif) | |
1119 | ipdev = ipdev_name(pif) | |
1120 | ||
1121 | ||
1122 | bond_slaves = get_bond_slaves_of_pif(pif) | |
1123 | log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves])) | |
1124 | for slave in bond_slaves: | |
1125 | slave_interface = interface_name(slave) | |
1126 | log("bring down bond slave %s" % slave_interface) | |
1127 | argv += interface_deconfigure_commands(slave_interface) | |
1128 | down_netdev(slave_interface) | |
1129 | ||
1130 | argv += interface_deconfigure_commands(ipdev) | |
1131 | down_netdev(ipdev) | |
1132 | ||
1133 | argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)] | |
1134 | argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface] | |
1135 | modify_config(argv) | |
1136 | ||
1137 | def action_rewrite(pif): | |
1138 | # Support "rpm -e vswitch" gracefully by keeping Centos configuration | |
1139 | # files up-to-date, even though we don't use them or need them. | |
1140 | pifrec = db.get_pif_record(pif) | |
1141 | f = configure_pif(pif) | |
1142 | interface = interface_name(pif) | |
1143 | bridge = bridge_name(pif) | |
1144 | mode = pifrec['ip_configuration_mode'] | |
1145 | if bridge: | |
1146 | log("Configuring %s using %s configuration" % (bridge, mode)) | |
1147 | br = open_network_ifcfg(pif) | |
1148 | configure_network(pif, br) | |
1149 | br.close() | |
1150 | f.attach_child(br) | |
1151 | else: | |
1152 | log("Configuring %s using %s configuration" % (interface, mode)) | |
1153 | configure_network(pif, f) | |
1154 | f.close() | |
1155 | try: | |
1156 | f.apply() | |
1157 | f.commit() | |
1158 | except Error, e: | |
1159 | log("failed to apply changes: %s" % e.msg) | |
1160 | f.revert() | |
1161 | raise | |
1162 | ||
1163 | # We have no code of our own to run here. | |
1164 | pass | |
1165 | ||
1166 | def main(argv=None): | |
1167 | global output_directory, management_pif | |
1168 | ||
1169 | session = None | |
1170 | pif_uuid = None | |
1171 | pif = None | |
1172 | ||
1173 | force_interface = None | |
1174 | force_management = False | |
1175 | ||
1176 | if argv is None: | |
1177 | argv = sys.argv | |
1178 | ||
1179 | try: | |
1180 | try: | |
1181 | shortops = "h" | |
1182 | longops = [ "output-directory=", | |
1183 | "pif=", "pif-uuid=", | |
1184 | "session=", | |
1185 | "force=", | |
1186 | "force-interface=", | |
1187 | "management", | |
1188 | "test-mode", | |
1189 | "device=", "mode=", "ip=", "netmask=", "gateway=", | |
1190 | "help" ] | |
1191 | arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops) | |
1192 | except getopt.GetoptError, msg: | |
1193 | raise Usage(msg) | |
1194 | ||
1195 | force_rewrite_config = {} | |
1196 | ||
1197 | for o,a in arglist: | |
1198 | if o == "--output-directory": | |
1199 | output_directory = a | |
1200 | elif o == "--pif": | |
1201 | pif = a | |
1202 | elif o == "--pif-uuid": | |
1203 | pif_uuid = a | |
1204 | elif o == "--session": | |
1205 | session = a | |
1206 | elif o == "--force-interface" or o == "--force": | |
1207 | force_interface = a | |
1208 | elif o == "--management": | |
1209 | force_management = True | |
1210 | elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]: | |
1211 | force_rewrite_config[o[2:]] = a | |
1212 | elif o == "-h" or o == "--help": | |
1213 | print __doc__ % {'command-name': os.path.basename(argv[0])} | |
1214 | return 0 | |
1215 | ||
1216 | if not debug_mode(): | |
1217 | syslog.openlog(os.path.basename(argv[0])) | |
1218 | log("Called as " + str.join(" ", argv)) | |
1219 | if len(args) < 1: | |
1220 | raise Usage("Required option <action> not present") | |
1221 | if len(args) > 1: | |
1222 | raise Usage("Too many arguments") | |
1223 | ||
1224 | action = args[0] | |
1225 | # backwards compatibility | |
1226 | if action == "rewrite-configuration": action = "rewrite" | |
1227 | ||
1228 | if output_directory and ( session or pif ): | |
1229 | raise Usage("--session/--pif cannot be used with --output-directory") | |
1230 | if ( session or pif ) and pif_uuid: | |
1231 | raise Usage("--session/--pif and --pif-uuid are mutually exclusive.") | |
1232 | if ( session and not pif ) or ( not session and pif ): | |
1233 | raise Usage("--session and --pif must be used together.") | |
1234 | if force_interface and ( session or pif or pif_uuid ): | |
1235 | raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid") | |
1236 | if len(force_rewrite_config) and not (force_interface and action == "rewrite"): | |
1237 | raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway") | |
1238 | ||
1239 | global db | |
1240 | if force_interface: | |
1241 | log("Force interface %s %s" % (force_interface, action)) | |
1242 | ||
1243 | if action == "rewrite": | |
1244 | action_force_rewrite(force_interface, force_rewrite_config) | |
1245 | else: | |
1246 | db = DatabaseCache(cache_file=dbcache_file) | |
1247 | host = db.extras['host'] | |
1248 | pif = db.get_pif_by_bridge(host, force_interface) | |
1249 | management_pif = db.get_management_pif(host) | |
1250 | ||
1251 | if action == "up": | |
1252 | action_up(pif) | |
1253 | elif action == "down": | |
1254 | action_down(pif) | |
1255 | else: | |
1256 | raise Usage("Unknown action %s" % action) | |
1257 | else: | |
1258 | db = DatabaseCache(session_ref=session) | |
1259 | ||
1260 | if pif_uuid: | |
1261 | pif = db.get_pif_by_uuid(pif_uuid) | |
1262 | ||
1263 | if not pif: | |
1264 | raise Usage("No PIF given") | |
1265 | ||
1266 | if force_management: | |
1267 | # pif is going to be the management pif | |
1268 | management_pif = pif | |
1269 | else: | |
1270 | # pif is not going to be the management pif. | |
1271 | # Search DB cache for pif on same host with management=true | |
1272 | pifrec = db.get_pif_record(pif) | |
1273 | host = pifrec['host'] | |
1274 | management_pif = db.get_management_pif(host) | |
1275 | ||
1276 | log_pif_action(action, pif) | |
1277 | ||
1278 | if not check_allowed(pif): | |
1279 | return 0 | |
1280 | ||
1281 | if action == "up": | |
1282 | action_up(pif) | |
1283 | elif action == "down": | |
1284 | action_down(pif) | |
1285 | elif action == "rewrite": | |
1286 | action_rewrite(pif) | |
1287 | else: | |
1288 | raise Usage("Unknown action %s" % action) | |
1289 | ||
1290 | # Save cache. | |
1291 | pifrec = db.get_pif_record(pif) | |
1292 | db.save(dbcache_file, {'host': pifrec['host']}) | |
1293 | ||
1294 | except Usage, err: | |
1295 | print >>sys.stderr, err.msg | |
1296 | print >>sys.stderr, "For help use --help." | |
1297 | return 2 | |
1298 | except Error, err: | |
1299 | log(err.msg) | |
1300 | return 1 | |
1301 | ||
1302 | return 0 | |
1303 | \f | |
1304 | # The following code allows interface-reconfigure to keep Centos | |
1305 | # network configuration files up-to-date, even though the vswitch | |
1306 | # never uses them. In turn, that means that "rpm -e vswitch" does not | |
1307 | # have to update any configuration files. | |
1308 | ||
1309 | def configure_ethtool(oc, f): | |
1310 | # Options for "ethtool -s" | |
1311 | settings = None | |
1312 | setting_opts = ["autoneg", "speed", "duplex"] | |
1313 | # Options for "ethtool -K" | |
1314 | offload = None | |
1315 | offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"] | |
1316 | ||
1317 | for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]: | |
1318 | val = oc["ethtool-" + opt] | |
1319 | ||
1320 | if opt in ["speed"]: | |
1321 | if val in ["10", "100", "1000"]: | |
1322 | val = "speed " + val | |
1323 | else: | |
1324 | log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val) | |
1325 | val = None | |
1326 | elif opt in ["duplex"]: | |
1327 | if val in ["half", "full"]: | |
1328 | val = "duplex " + val | |
1329 | else: | |
1330 | log("Invalid value for ethtool-duplex = %s. Must be half|full." % val) | |
1331 | val = None | |
1332 | elif opt in ["autoneg"] + offload_opts: | |
1333 | if val in ["true", "on"]: | |
1334 | val = opt + " on" | |
1335 | elif val in ["false", "off"]: | |
1336 | val = opt + " off" | |
1337 | else: | |
1338 | log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val)) | |
1339 | val = None | |
1340 | ||
1341 | if opt in setting_opts: | |
1342 | if val and settings: | |
1343 | settings = settings + " " + val | |
1344 | else: | |
1345 | settings = val | |
1346 | elif opt in offload_opts: | |
1347 | if val and offload: | |
1348 | offload = offload + " " + val | |
1349 | else: | |
1350 | offload = val | |
1351 | ||
1352 | if settings: | |
1353 | f.write("ETHTOOL_OPTS=\"%s\"\n" % settings) | |
1354 | if offload: | |
1355 | f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload) | |
1356 | ||
1357 | def configure_mtu(oc, f): | |
1358 | if not oc.has_key('mtu'): | |
1359 | return | |
1360 | ||
1361 | try: | |
1362 | mtu = int(oc['mtu']) | |
1363 | f.write("MTU=%d\n" % mtu) | |
1364 | except ValueError, x: | |
1365 | log("Invalid value for mtu = %s" % mtu) | |
1366 | ||
1367 | def configure_static_routes(interface, oc, f): | |
1368 | """Open a route-<interface> file for static routes. | |
1369 | ||
1370 | Opens the static routes configuration file for interface and writes one | |
1371 | line for each route specified in the network's other config "static-routes" value. | |
1372 | E.g. if | |
1373 | interface ( RO): xenbr1 | |
1374 | other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;... | |
1375 | ||
1376 | Then route-xenbr1 should be | |
1377 | 172.16.0.0/15 via 192.168.0.3 dev xenbr1 | |
1378 | 172.18.0.0/16 via 192.168.0.4 dev xenbr1 | |
1379 | """ | |
1380 | fname = "route-%s" % interface | |
1381 | if oc.has_key('static-routes'): | |
1382 | # The key is present - extract comma seperates entries | |
1383 | lines = oc['static-routes'].split(',') | |
1384 | else: | |
1385 | # The key is not present, i.e. there are no static routes | |
1386 | lines = [] | |
1387 | ||
1388 | child = ConfigurationFile(fname) | |
1389 | child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \ | |
1390 | (os.path.basename(child.path()), os.path.basename(sys.argv[0]))) | |
1391 | ||
1392 | try: | |
1393 | for l in lines: | |
1394 | network, masklen, gateway = l.split('/') | |
1395 | child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface)) | |
1396 | ||
1397 | f.attach_child(child) | |
1398 | child.close() | |
1399 | ||
1400 | except ValueError, e: | |
1401 | log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e)) | |
1402 | ||
1403 | def __open_ifcfg(interface): | |
1404 | """Open a network interface configuration file. | |
1405 | ||
1406 | Opens the configuration file for interface, writes a header and | |
1407 | common options and returns the file object. | |
1408 | """ | |
1409 | fname = "ifcfg-%s" % interface | |
1410 | f = ConfigurationFile(fname) | |
1411 | ||
1412 | f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \ | |
1413 | (os.path.basename(f.path()), os.path.basename(sys.argv[0]))) | |
1414 | f.write("XEMANAGED=yes\n") | |
1415 | f.write("DEVICE=%s\n" % interface) | |
1416 | f.write("ONBOOT=no\n") | |
1417 | ||
1418 | return f | |
1419 | ||
1420 | def open_network_ifcfg(pif): | |
1421 | bridge = bridge_name(pif) | |
1422 | interface = interface_name(pif) | |
1423 | if bridge: | |
1424 | return __open_ifcfg(bridge) | |
1425 | else: | |
1426 | return __open_ifcfg(interface) | |
1427 | ||
1428 | ||
1429 | def open_pif_ifcfg(pif): | |
1430 | pifrec = db.get_pif_record(pif) | |
1431 | ||
1432 | log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC'])) | |
1433 | ||
1434 | f = __open_ifcfg(interface_name(pif)) | |
1435 | ||
1436 | if pifrec.has_key('other_config'): | |
1437 | configure_ethtool(pifrec['other_config'], f) | |
1438 | configure_mtu(pifrec['other_config'], f) | |
1439 | ||
1440 | return f | |
1441 | ||
1442 | def configure_network(pif, f): | |
1443 | """Write the configuration file for a network. | |
1444 | ||
1445 | Writes configuration derived from the network object into the relevant | |
1446 | ifcfg file. The configuration file is passed in, but if the network is | |
1447 | bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>. | |
1448 | ||
1449 | This routine may also write ifcfg files of the networks corresponding to other PIFs | |
1450 | in order to maintain consistency. | |
1451 | ||
1452 | params: | |
1453 | pif: Opaque_ref of pif | |
1454 | f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration | |
1455 | """ | |
1456 | ||
1457 | pifrec = db.get_pif_record(pif) | |
1458 | host = pifrec['host'] | |
1459 | nw = pifrec['network'] | |
1460 | nwrec = db.get_network_record(nw) | |
1461 | oc = None | |
1462 | bridge = bridge_name(pif) | |
1463 | interface = interface_name(pif) | |
1464 | if bridge: | |
1465 | device = bridge | |
1466 | else: | |
1467 | device = interface | |
1468 | ||
1469 | if nwrec.has_key('other_config'): | |
1470 | configure_ethtool(nwrec['other_config'], f) | |
1471 | configure_mtu(nwrec['other_config'], f) | |
1472 | configure_static_routes(device, nwrec['other_config'], f) | |
1473 | ||
1474 | ||
1475 | if pifrec.has_key('other_config'): | |
1476 | oc = pifrec['other_config'] | |
1477 | ||
1478 | if device == bridge: | |
1479 | f.write("TYPE=Bridge\n") | |
1480 | f.write("DELAY=0\n") | |
1481 | f.write("STP=off\n") | |
1482 | f.write("PIFDEV=%s\n" % interface_name(pif)) | |
1483 | ||
1484 | if pifrec['ip_configuration_mode'] == "DHCP": | |
1485 | f.write("BOOTPROTO=dhcp\n") | |
1486 | f.write("PERSISTENT_DHCLIENT=yes\n") | |
1487 | elif pifrec['ip_configuration_mode'] == "Static": | |
1488 | f.write("BOOTPROTO=none\n") | |
1489 | f.write("NETMASK=%(netmask)s\n" % pifrec) | |
1490 | f.write("IPADDR=%(IP)s\n" % pifrec) | |
1491 | f.write("GATEWAY=%(gateway)s\n" % pifrec) | |
1492 | elif pifrec['ip_configuration_mode'] == "None": | |
1493 | f.write("BOOTPROTO=none\n") | |
1494 | else: | |
1495 | raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode']) | |
1496 | ||
1497 | if pifrec.has_key('DNS') and pifrec['DNS'] != "": | |
1498 | ServerList = pifrec['DNS'].split(",") | |
1499 | for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i])) | |
1500 | if oc and oc.has_key('domain'): | |
1501 | f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' ')) | |
1502 | ||
1503 | # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network. | |
1504 | # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set. | |
1505 | # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set. | |
1506 | ||
1507 | # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV | |
1508 | # | |
1509 | # Note: we prune out the bond master pif (if it exists). | |
1510 | # This is because when we are called to bring up an interface with a bond master, it is implicit that | |
1511 | # we should bring down that master. | |
1512 | pifs_on_host = [ __pif for __pif in db.get_all_pifs() if | |
1513 | db.get_pif_record(__pif)['host'] == host and | |
1514 | (not __pif in get_bond_masters_of_pif(pif)) ] | |
1515 | other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ] | |
1516 | ||
1517 | peerdns_pif = None | |
1518 | defaultroute_pif = None | |
1519 | ||
1520 | # loop through all the pifs on this host looking for one with | |
1521 | # other-config:peerdns = true, and one with | |
1522 | # other-config:default-route=true | |
1523 | for __pif in pifs_on_host: | |
1524 | __pifrec = db.get_pif_record(__pif) | |
1525 | __oc = __pifrec['other_config'] | |
1526 | if __oc.has_key('peerdns') and __oc['peerdns'] == 'true': | |
1527 | if peerdns_pif == None: | |
1528 | peerdns_pif = __pif | |
1529 | else: | |
1530 | log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \ | |
1531 | (db.get_pif_record(peerdns_pif)['device'], __pifrec['device'])) | |
1532 | if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true': | |
1533 | if defaultroute_pif == None: | |
1534 | defaultroute_pif = __pif | |
1535 | else: | |
1536 | log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \ | |
1537 | (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device'])) | |
1538 | ||
1539 | # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute | |
1540 | if peerdns_pif == None: | |
1541 | peerdns_pif = management_pif | |
1542 | if defaultroute_pif == None: | |
1543 | defaultroute_pif = management_pif | |
1544 | ||
1545 | # Update all the other network's ifcfg files and ensure consistency | |
1546 | for __pif in other_pifs_on_host: | |
1547 | __f = open_network_ifcfg(__pif) | |
1548 | peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no') | |
1549 | lines = __f.readlines() | |
1550 | ||
1551 | if not peerdns_line_wanted in lines: | |
1552 | # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting | |
1553 | for line in lines: | |
1554 | if not line.lstrip().startswith('PEERDNS'): | |
1555 | __f.write(line) | |
1556 | log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path())) | |
1557 | __f.write(peerdns_line_wanted) | |
1558 | __f.close() | |
1559 | f.attach_child(__f) | |
1560 | ||
1561 | else: | |
1562 | # There is no need to change this ifcfg file. So don't attach_child. | |
1563 | pass | |
1564 | ||
1565 | # ... and for this pif too | |
1566 | f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no')) | |
1567 | ||
1568 | # Update gatewaydev | |
1569 | fnetwork = ConfigurationFile("network", "/etc/sysconfig") | |
1570 | for line in fnetwork.readlines(): | |
1571 | if line.lstrip().startswith('GATEWAY') : | |
1572 | continue | |
1573 | fnetwork.write(line) | |
1574 | if defaultroute_pif: | |
1575 | gatewaydev = bridge_name(defaultroute_pif) | |
1576 | if not gatewaydev: | |
1577 | gatewaydev = interface_name(defaultroute_pif) | |
1578 | fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev) | |
1579 | fnetwork.close() | |
1580 | f.attach_child(fnetwork) | |
1581 | ||
1582 | return | |
1583 | ||
1584 | ||
1585 | def configure_physical_interface(pif): | |
1586 | """Write the configuration for a physical interface. | |
1587 | ||
1588 | Writes the configuration file for the physical interface described by | |
1589 | the pif object. | |
1590 | ||
1591 | Returns the open file handle for the interface configuration file. | |
1592 | """ | |
1593 | ||
1594 | pifrec = db.get_pif_record(pif) | |
1595 | ||
1596 | f = open_pif_ifcfg(pif) | |
1597 | ||
1598 | f.write("TYPE=Ethernet\n") | |
1599 | f.write("HWADDR=%(MAC)s\n" % pifrec) | |
1600 | ||
1601 | return f | |
1602 | ||
1603 | def configure_bond_interface(pif): | |
1604 | """Write the configuration for a bond interface. | |
1605 | ||
1606 | Writes the configuration file for the bond interface described by | |
1607 | the pif object. Handles writing the configuration for the slave | |
1608 | interfaces. | |
1609 | ||
1610 | Returns the open file handle for the bond interface configuration | |
1611 | file. | |
1612 | """ | |
1613 | ||
1614 | pifrec = db.get_pif_record(pif) | |
1615 | oc = pifrec['other_config'] | |
1616 | f = open_pif_ifcfg(pif) | |
1617 | ||
1618 | if pifrec['MAC'] != "": | |
1619 | f.write("MACADDR=%s\n" % pifrec['MAC']) | |
1620 | ||
1621 | for slave in get_bond_slaves_of_pif(pif): | |
1622 | s = configure_physical_interface(slave) | |
1623 | s.write("MASTER=%(device)s\n" % pifrec) | |
1624 | s.write("SLAVE=yes\n") | |
1625 | s.close() | |
1626 | f.attach_child(s) | |
1627 | ||
1628 | # The bond option defaults | |
1629 | bond_options = { | |
1630 | "mode": "balance-slb", | |
1631 | "miimon": "100", | |
1632 | "downdelay": "200", | |
1633 | "updelay": "31000", | |
1634 | "use_carrier": "1", | |
1635 | } | |
1636 | ||
1637 | # override defaults with values from other-config whose keys being with "bond-" | |
1638 | overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items()) | |
1639 | overrides = map(lambda (key,val): (key[5:], val), overrides) | |
1640 | bond_options.update(overrides) | |
1641 | ||
1642 | # write the bond options to ifcfg-bondX | |
1643 | f.write('BONDING_OPTS="') | |
1644 | for (name,val) in bond_options.items(): | |
1645 | f.write("%s=%s " % (name,val)) | |
1646 | f.write('"\n') | |
1647 | return f | |
1648 | ||
1649 | def configure_vlan_interface(pif): | |
1650 | """Write the configuration for a VLAN interface. | |
1651 | ||
1652 | Writes the configuration file for the VLAN interface described by | |
1653 | the pif object. Handles writing the configuration for the master | |
1654 | interface if necessary. | |
1655 | ||
1656 | Returns the open file handle for the VLAN interface configuration | |
1657 | file. | |
1658 | """ | |
1659 | ||
1660 | slave = configure_pif(get_vlan_slave_of_pif(pif)) | |
1661 | slave.close() | |
1662 | ||
1663 | f = open_pif_ifcfg(pif) | |
1664 | f.write("VLAN=yes\n") | |
1665 | f.attach_child(slave) | |
1666 | ||
1667 | return f | |
1668 | ||
1669 | def configure_pif(pif): | |
1670 | """Write the configuration for a PIF object. | |
1671 | ||
1672 | Writes the configuration file the PIF and all dependent | |
1673 | interfaces (bond slaves and VLAN masters etc). | |
1674 | ||
1675 | Returns the open file handle for the interface configuration file. | |
1676 | """ | |
1677 | ||
1678 | pifrec = db.get_pif_record(pif) | |
1679 | ||
1680 | if pifrec['VLAN'] != '-1': | |
1681 | f = configure_vlan_interface(pif) | |
1682 | elif len(pifrec['bond_master_of']) != 0: | |
1683 | f = configure_bond_interface(pif) | |
1684 | else: | |
1685 | f = configure_physical_interface(pif) | |
1686 | ||
1687 | bridge = bridge_name(pif) | |
1688 | if bridge: | |
1689 | f.write("BRIDGE=%s\n" % bridge) | |
1690 | ||
1691 | return f | |
1692 | ||
1693 | def unconfigure_pif(pif): | |
1694 | """Clear up the files created by configure_pif""" | |
1695 | f = open_pif_ifcfg(pif) | |
1696 | log("Unlinking stale file %s" % f.path()) | |
1697 | f.unlink() | |
1698 | return f | |
1699 | \f | |
1700 | if __name__ == "__main__": | |
1701 | rc = 1 | |
1702 | try: | |
1703 | rc = main() | |
1704 | except: | |
1705 | ex = sys.exc_info() | |
1706 | err = traceback.format_exception(*ex) | |
1707 | for exline in err: | |
1708 | log(exline) | |
1709 | ||
1710 | if not debug_mode(): | |
1711 | syslog.closelog() | |
1712 | ||
1713 | sys.exit(rc) |