]> git.proxmox.com Git - ovs.git/blob - xenserver/opt_xensource_libexec_interface-reconfigure
vswitchd: Support creating fake bond device interfaces.
[ovs.git] / xenserver / opt_xensource_libexec_interface-reconfigure
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2008,2009 Citrix Systems, Inc. All rights reserved.
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
66 import random
67
68 output_directory = None
69
70 db = None
71 management_pif = None
72
73 dbcache_file = "/etc/ovs-vswitch.dbcache"
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
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"):
270 dev_mac = get_netdev_mac(device)
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
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
464 def physdev_pifs(pif):
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.
469 """
470
471 pifrec = db.get_pif_record(pif)
472
473 if pifrec['VLAN'] != '-1':
474 return [get_vlan_slave_of_pif(pif)]
475 elif len(pifrec['bond_master_of']) != 0:
476 return get_bond_slaves_of_pif(pif)
477 else:
478 return [pif]
479
480 def physdev_names(pif):
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
487 return [db.get_pif_record(phys)['device'] for phys in physdev_pifs(pif)]
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
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
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
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
633 if deconfigure:
634 # Kill dhclient.
635 pidfile_name = '/var/run/dhclient-%s.pid' % interface
636 try:
637 os.kill(int(read_first_line_of_file(pidfile_name)), signal.SIGTERM)
638 except:
639 pass
640
641 # Remove dhclient pidfile.
642 try:
643 os.remove(pidfile_name)
644 except:
645 pass
646
647 run_command(["/sbin/ifconfig", interface, '0.0.0.0'])
648
649 run_command(["/sbin/ifconfig", interface, 'down'])
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
700 def ethtool_settings(oc):
701 # Options for "ethtool -s"
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)
723
724 # Options for "ethtool -K"
725 offload = []
726 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
727 if oc.has_key("ethtool-" + opt):
728 val = oc["ethtool-" + opt]
729 if val in ["true", "on"]:
730 offload += [opt, 'on']
731 elif val in ["false", "off"]:
732 offload += [opt, 'off']
733 else:
734 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
735
736 return settings, offload
737
738 def configure_netdev(pif):
739 pifrec = db.get_pif_record(pif)
740 datapath = datapath_name(pif)
741 ipdev = ipdev_name(pif)
742
743 host = pifrec['host']
744 nw = pifrec['network']
745 nwrec = db.get_network_record(nw)
746
747 ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up']
748 gateway = ''
749 if pifrec['ip_configuration_mode'] == "DHCP":
750 pass
751 elif pifrec['ip_configuration_mode'] == "Static":
752 ifconfig_argv += [pifrec['IP']]
753 ifconfig_argv += ['netmask', pifrec['netmask']]
754 gateway = pifrec['gateway']
755 elif pifrec['ip_configuration_mode'] == "None":
756 # Nothing to do.
757 pass
758 else:
759 raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
760
761 oc = pifrec['other_config']
762 if oc.has_key('mtu'):
763 try:
764 int(oc['mtu']) # Check that the value is an integer
765 ifconfig_argv += ['mtu', oc['mtu']]
766 except ValueError, x:
767 log("Invalid value for mtu = %s" % mtu)
768
769 run_command(ifconfig_argv)
770
771 (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
772
773 if peerdns_pif == pif:
774 f = ConfigurationFile('resolv.conf', "/etc")
775 if oc.has_key('domain'):
776 f.write("search %s\n" % oc['domain'])
777 for dns in pifrec['DNS'].split(","):
778 f.write("nameserver %s\n" % dns)
779 f.close()
780 f.apply()
781 f.commit()
782
783 if defaultroute_pif == pif and gateway != '':
784 run_command(['/sbin/ip', 'route', 'replace', 'default',
785 'via', gateway, 'dev', ipdev])
786
787 if oc.has_key('static-routes'):
788 for line in oc['static-routes'].split(','):
789 network, masklen, gateway = line.split('/')
790 run_command(['/sbin/ip', 'route', 'add',
791 '%s/%s' % (netmask, masklen), 'via', gateway,
792 'dev', ipdev])
793
794 settings, offload = ethtool_settings(oc)
795 if settings:
796 run_command(['/sbin/ethtool', '-s', ipdev] + settings)
797 if offload:
798 run_command(['/sbin/ethtool', '-K', ipdev] + offload)
799
800 if pifrec['ip_configuration_mode'] == "DHCP":
801 print
802 print "Determining IP information for %s..." % ipdev,
803 argv = ['/sbin/dhclient', '-q',
804 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev,
805 '-pf', '/var/run/dhclient-%s.pid' % ipdev,
806 ipdev]
807 if run_command(argv):
808 print 'done.'
809 else:
810 print 'failed.'
811
812 def modify_config(commands):
813 run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
814 '-F', '/etc/ovs-vswitchd.conf']
815 + commands + ['-c'])
816 run_command(['/sbin/service', 'vswitch', 'reload'])
817
818 def is_bond_pif(pif):
819 pifrec = db.get_pif_record(pif)
820 return len(pifrec['bond_master_of']) != 0
821
822 def configure_bond(pif):
823 pifrec = db.get_pif_record(pif)
824 interface = interface_name(pif)
825 ipdev = ipdev_name(pif)
826 datapath = datapath_name(pif)
827 physdevs = physdev_names(pif)
828
829 argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
830 argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
831 for slave in physdevs]
832 argv += ['--add=bonding.%s.fake-iface=true']
833
834 if pifrec['MAC'] != "":
835 argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
836
837 # Bonding options.
838 bond_options = {
839 "mode": "balance-slb",
840 "miimon": "100",
841 "downdelay": "200",
842 "updelay": "31000",
843 "use_carrier": "1",
844 }
845 # override defaults with values from other-config whose keys
846 # being with "bond-"
847 oc = pifrec['other_config']
848 overrides = filter(lambda (key,val):
849 key.startswith("bond-"), oc.items())
850 overrides = map(lambda (key,val): (key[5:], val), overrides)
851 bond_options.update(overrides)
852 for (name,val) in bond_options.items():
853 argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
854 return argv
855
856 def action_up(pif):
857 pifrec = db.get_pif_record(pif)
858
859 bridge = bridge_name(pif)
860 interface = interface_name(pif)
861 ipdev = ipdev_name(pif)
862 datapath = datapath_name(pif)
863 physdevs = physdev_names(pif)
864 vlan_slave = None
865 if pifrec['VLAN'] != '-1':
866 vlan_slave = get_vlan_slave_of_pif(pif)
867 if vlan_slave and is_bond_pif(vlan_slave):
868 bond_master = vlan_slave
869 elif is_bond_pif(pif):
870 bond_master = pif
871 else:
872 bond_master = None
873 if bond_master:
874 bond_slaves = get_bond_slaves_of_pif(bond_master)
875 else:
876 bond_slaves = []
877 bond_masters = get_bond_masters_of_pif(pif)
878
879 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
880 # files up-to-date, even though we don't use them or need them.
881 f = configure_pif(pif)
882 mode = pifrec['ip_configuration_mode']
883 if bridge:
884 log("Configuring %s using %s configuration" % (bridge, mode))
885 br = open_network_ifcfg(pif)
886 configure_network(pif, br)
887 br.close()
888 f.attach_child(br)
889 else:
890 log("Configuring %s using %s configuration" % (interface, mode))
891 configure_network(pif, f)
892 f.close()
893 for master in bond_masters:
894 master_bridge = bridge_name(master)
895 removed = unconfigure_pif(master)
896 f.attach_child(removed)
897 if master_bridge:
898 removed = open_network_ifcfg(master)
899 log("Unlinking stale file %s" % removed.path())
900 removed.unlink()
901 f.attach_child(removed)
902
903 # /etc/xensource/scripts/vif needs to know where to add VIFs.
904 if vlan_slave:
905 if not os.path.exists(vswitch_config_dir):
906 os.mkdir(vswitch_config_dir)
907 br = ConfigurationFile("br-%s" % bridge, vswitch_config_dir)
908 br.write("VLAN_SLAVE=%s\n" % datapath)
909 br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
910 br.close()
911 f.attach_child(br)
912
913 # Update all configuration files (both ours and Centos's).
914 f.apply()
915 f.commit()
916
917 # Check the MAC address of each network device and remap if
918 # necessary to make names match our expectations.
919 for physdev_pif in physdev_pifs(pif):
920 remap_pif(physdev_pif)
921
922 # "ifconfig down" the network device and delete its IP address, etc.
923 down_netdev(ipdev)
924 for physdev in physdevs:
925 down_netdev(physdev)
926
927 # If we are bringing up a bond, remove IP addresses from the
928 # slaves (because we are implicitly being asked to take them down).
929 #
930 # Conversely, if we are bringing up an interface that has bond
931 # masters, remove IP addresses from the bond master (because we
932 # are implicitly being asked to take it down).
933 for bond_pif in bond_slaves + bond_masters:
934 run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0'])
935
936 # Remove all keys related to pif and any bond masters linked to PIF.
937 del_ports = [ipdev] + physdevs + bond_masters
938 if vlan_slave and bond_master:
939 del_ports += [interface_name(bond_master)]
940
941 # What ports do we need to add to the datapath?
942 #
943 # We definitely need the ipdev, and ordinarily we want the
944 # physical devices too, but for bonds we need the bond as bridge
945 # port.
946 add_ports = [ipdev, datapath]
947 if not bond_master:
948 add_ports += physdevs
949 else:
950 add_ports += [interface_name(bond_master)]
951
952 # What ports do we need to delete?
953 #
954 # - All the ports that we add, to avoid duplication and to drop
955 # them from another datapath in case they're misassigned.
956 #
957 # - The physical devices, since they will either be in add_ports
958 # or added to the bonding device (see below).
959 #
960 # - The bond masters for pif. (Ordinarily pif shouldn't have any
961 # bond masters. If it does then interface-reconfigure is
962 # implicitly being asked to take them down.)
963 del_ports = add_ports + physdevs + bond_masters
964
965 # What networks does this datapath carry?
966 #
967 # - The network corresponding to the datapath's PIF.
968 #
969 # - The networks corresponding to any VLANs attached to the
970 # datapath's PIF.
971 network_uuids = []
972 for nwpif in db.get_pifs_by_record({'device': pifrec['device'],
973 'host': pifrec['host']}):
974 net = db.get_pif_record(nwpif)['network']
975 network_uuids += [db.get_network_record(net)['uuid']]
976
977 # Bring up bond slaves early, because ovs-vswitchd initially
978 # enables or disables bond slaves based on whether carrier is
979 # detected when they are added, and a network device that is down
980 # always reports "no carrier".
981 bond_slave_physdevs = []
982 for slave in bond_slaves:
983 bond_slave_physdevs += physdev_names(slave)
984 for slave_physdev in bond_slave_physdevs:
985 up_netdev(slave_physdev)
986
987 # Now modify the ovs-vswitchd config file.
988 argv = []
989 for port in set(del_ports):
990 argv += interface_deconfigure_commands(port)
991 for port in set(add_ports):
992 argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
993 if vlan_slave:
994 argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
995 argv += ['--add=iface.%s.internal=true' % (ipdev)]
996
997 # xapi creates a bridge by the name of the ipdev and requires
998 # that the IP address will be on it. We need to delete this
999 # bridge because we need that device to be a member of our
1000 # datapath.
1001 argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
1002
1003 # xapi insists that its attempts to create the bridge succeed,
1004 # so force that to happen.
1005 argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
1006 else:
1007 try:
1008 os.unlink("%s/br-%s" % (vswitch_config_dir, bridge))
1009 except OSError:
1010 pass
1011 argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
1012 argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
1013 for uuid in set(network_uuids)]
1014 if bond_master:
1015 argv += configure_bond(bond_master)
1016 modify_config(argv)
1017
1018 # Configure network devices.
1019 configure_netdev(pif)
1020
1021 # Bring up VLAN slave, plus physical devices other than bond
1022 # slaves (which we brought up earlier).
1023 if vlan_slave:
1024 up_netdev(ipdev_name(vlan_slave))
1025 for physdev in set(physdevs) - set(bond_slave_physdevs):
1026 up_netdev(physdev)
1027
1028 # Update /etc/issue (which contains the IP address of the management interface)
1029 os.system("/sbin/update-issue")
1030
1031 if bond_slaves:
1032 # There seems to be a race somewhere: without this sleep, using
1033 # XenCenter to create a bond that becomes the management interface
1034 # fails with "The underlying connection was closed: A connection that
1035 # was expected to be kept alive was closed by the server." on every
1036 # second or third try, even though /var/log/messages doesn't show
1037 # anything unusual.
1038 #
1039 # The race is probably present even without vswitch, but bringing up a
1040 # bond without vswitch involves a built-in pause of 10 seconds or more
1041 # to wait for the bond to transition from learning to forwarding state.
1042 time.sleep(5)
1043
1044 def action_down(pif):
1045 rec = db.get_pif_record(pif)
1046 interface = interface_name(pif)
1047 bridge = bridge_name(pif)
1048 ipdev = ipdev_name(pif)
1049
1050 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1051 # files up-to-date, even though we don't use them or need them.
1052 f = unconfigure_pif(pif)
1053 if bridge:
1054 br = open_network_ifcfg(pif)
1055 log("Unlinking stale file %s" % br.path())
1056 br.unlink()
1057 f.attach_child(br)
1058 try:
1059 f.apply()
1060 f.commit()
1061 except Error, e:
1062 log("action_down failed to apply changes: %s" % e.msg)
1063 f.revert()
1064 raise
1065
1066 argv = []
1067 if rec['VLAN'] != '-1':
1068 # Get rid of the VLAN device itself.
1069 down_netdev(ipdev)
1070 argv += interface_deconfigure_commands(ipdev)
1071
1072 # If the VLAN's slave is attached, stop here.
1073 slave = get_vlan_slave_of_pif(pif)
1074 if db.get_pif_record(slave)['currently_attached']:
1075 log("VLAN slave is currently attached")
1076 modify_config(argv)
1077 return
1078
1079 # If the VLAN's slave has other VLANs that are attached, stop here.
1080 masters = get_vlan_masters_of_pif(slave)
1081 for m in masters:
1082 if m != pif and db.get_pif_record(m)['currently_attached']:
1083 log("VLAN slave has other master %s" % interface_naem(m))
1084 modify_config(argv)
1085 return
1086
1087 # Otherwise, take down the VLAN's slave too.
1088 log("No more masters, bring down vlan slave %s" % interface_name(slave))
1089 pif = slave
1090 else:
1091 # Stop here if this PIF has attached VLAN masters.
1092 vlan_masters = get_vlan_masters_of_pif(pif)
1093 log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
1094 for m in vlan_masters:
1095 if db.get_pif_record(m)['currently_attached']:
1096 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
1097 return
1098
1099 # pif is now either a bond or a physical device which needs to be
1100 # brought down. pif might have changed so re-check all its attributes.
1101 rec = db.get_pif_record(pif)
1102 interface = interface_name(pif)
1103 bridge = bridge_name(pif)
1104 ipdev = ipdev_name(pif)
1105
1106
1107 bond_slaves = get_bond_slaves_of_pif(pif)
1108 log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
1109 for slave in bond_slaves:
1110 slave_interface = interface_name(slave)
1111 log("bring down bond slave %s" % slave_interface)
1112 argv += interface_deconfigure_commands(slave_interface)
1113 down_netdev(slave_interface)
1114
1115 argv += interface_deconfigure_commands(ipdev)
1116 down_netdev(ipdev)
1117
1118 argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1119 argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1120 modify_config(argv)
1121
1122 def action_rewrite(pif):
1123 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1124 # files up-to-date, even though we don't use them or need them.
1125 pifrec = db.get_pif_record(pif)
1126 f = configure_pif(pif)
1127 interface = interface_name(pif)
1128 bridge = bridge_name(pif)
1129 mode = pifrec['ip_configuration_mode']
1130 if bridge:
1131 log("Configuring %s using %s configuration" % (bridge, mode))
1132 br = open_network_ifcfg(pif)
1133 configure_network(pif, br)
1134 br.close()
1135 f.attach_child(br)
1136 else:
1137 log("Configuring %s using %s configuration" % (interface, mode))
1138 configure_network(pif, f)
1139 f.close()
1140 try:
1141 f.apply()
1142 f.commit()
1143 except Error, e:
1144 log("failed to apply changes: %s" % e.msg)
1145 f.revert()
1146 raise
1147
1148 # We have no code of our own to run here.
1149 pass
1150
1151 def main(argv=None):
1152 global output_directory, management_pif
1153
1154 session = None
1155 pif_uuid = None
1156 pif = None
1157
1158 force_interface = None
1159 force_management = False
1160
1161 if argv is None:
1162 argv = sys.argv
1163
1164 try:
1165 try:
1166 shortops = "h"
1167 longops = [ "output-directory=",
1168 "pif=", "pif-uuid=",
1169 "session=",
1170 "force=",
1171 "force-interface=",
1172 "management",
1173 "test-mode",
1174 "device=", "mode=", "ip=", "netmask=", "gateway=",
1175 "help" ]
1176 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1177 except getopt.GetoptError, msg:
1178 raise Usage(msg)
1179
1180 force_rewrite_config = {}
1181
1182 for o,a in arglist:
1183 if o == "--output-directory":
1184 output_directory = a
1185 elif o == "--pif":
1186 pif = a
1187 elif o == "--pif-uuid":
1188 pif_uuid = a
1189 elif o == "--session":
1190 session = a
1191 elif o == "--force-interface" or o == "--force":
1192 force_interface = a
1193 elif o == "--management":
1194 force_management = True
1195 elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1196 force_rewrite_config[o[2:]] = a
1197 elif o == "-h" or o == "--help":
1198 print __doc__ % {'command-name': os.path.basename(argv[0])}
1199 return 0
1200
1201 if not debug_mode():
1202 syslog.openlog(os.path.basename(argv[0]))
1203 log("Called as " + str.join(" ", argv))
1204 if len(args) < 1:
1205 raise Usage("Required option <action> not present")
1206 if len(args) > 1:
1207 raise Usage("Too many arguments")
1208
1209 action = args[0]
1210 # backwards compatibility
1211 if action == "rewrite-configuration": action = "rewrite"
1212
1213 if output_directory and ( session or pif ):
1214 raise Usage("--session/--pif cannot be used with --output-directory")
1215 if ( session or pif ) and pif_uuid:
1216 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1217 if ( session and not pif ) or ( not session and pif ):
1218 raise Usage("--session and --pif must be used together.")
1219 if force_interface and ( session or pif or pif_uuid ):
1220 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1221 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1222 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1223
1224 global db
1225 if force_interface:
1226 log("Force interface %s %s" % (force_interface, action))
1227
1228 if action == "rewrite":
1229 action_force_rewrite(force_interface, force_rewrite_config)
1230 else:
1231 db = DatabaseCache(cache_file=dbcache_file)
1232 host = db.extras['host']
1233 pif = db.get_pif_by_bridge(host, force_interface)
1234 management_pif = db.get_management_pif(host)
1235
1236 if action == "up":
1237 action_up(pif)
1238 elif action == "down":
1239 action_down(pif)
1240 else:
1241 raise Usage("Unknown action %s" % action)
1242 else:
1243 db = DatabaseCache(session_ref=session)
1244
1245 if pif_uuid:
1246 pif = db.get_pif_by_uuid(pif_uuid)
1247
1248 if not pif:
1249 raise Usage("No PIF given")
1250
1251 if force_management:
1252 # pif is going to be the management pif
1253 management_pif = pif
1254 else:
1255 # pif is not going to be the management pif.
1256 # Search DB cache for pif on same host with management=true
1257 pifrec = db.get_pif_record(pif)
1258 host = pifrec['host']
1259 management_pif = db.get_management_pif(host)
1260
1261 log_pif_action(action, pif)
1262
1263 if not check_allowed(pif):
1264 return 0
1265
1266 if action == "up":
1267 action_up(pif)
1268 elif action == "down":
1269 action_down(pif)
1270 elif action == "rewrite":
1271 action_rewrite(pif)
1272 else:
1273 raise Usage("Unknown action %s" % action)
1274
1275 # Save cache.
1276 pifrec = db.get_pif_record(pif)
1277 db.save(dbcache_file, {'host': pifrec['host']})
1278
1279 except Usage, err:
1280 print >>sys.stderr, err.msg
1281 print >>sys.stderr, "For help use --help."
1282 return 2
1283 except Error, err:
1284 log(err.msg)
1285 return 1
1286
1287 return 0
1288 \f
1289 # The following code allows interface-reconfigure to keep Centos
1290 # network configuration files up-to-date, even though the vswitch
1291 # never uses them. In turn, that means that "rpm -e vswitch" does not
1292 # have to update any configuration files.
1293
1294 def configure_ethtool(oc, f):
1295 # Options for "ethtool -s"
1296 settings = None
1297 setting_opts = ["autoneg", "speed", "duplex"]
1298 # Options for "ethtool -K"
1299 offload = None
1300 offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1301
1302 for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1303 val = oc["ethtool-" + opt]
1304
1305 if opt in ["speed"]:
1306 if val in ["10", "100", "1000"]:
1307 val = "speed " + val
1308 else:
1309 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1310 val = None
1311 elif opt in ["duplex"]:
1312 if val in ["half", "full"]:
1313 val = "duplex " + val
1314 else:
1315 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1316 val = None
1317 elif opt in ["autoneg"] + offload_opts:
1318 if val in ["true", "on"]:
1319 val = opt + " on"
1320 elif val in ["false", "off"]:
1321 val = opt + " off"
1322 else:
1323 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1324 val = None
1325
1326 if opt in setting_opts:
1327 if val and settings:
1328 settings = settings + " " + val
1329 else:
1330 settings = val
1331 elif opt in offload_opts:
1332 if val and offload:
1333 offload = offload + " " + val
1334 else:
1335 offload = val
1336
1337 if settings:
1338 f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1339 if offload:
1340 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1341
1342 def configure_mtu(oc, f):
1343 if not oc.has_key('mtu'):
1344 return
1345
1346 try:
1347 mtu = int(oc['mtu'])
1348 f.write("MTU=%d\n" % mtu)
1349 except ValueError, x:
1350 log("Invalid value for mtu = %s" % mtu)
1351
1352 def configure_static_routes(interface, oc, f):
1353 """Open a route-<interface> file for static routes.
1354
1355 Opens the static routes configuration file for interface and writes one
1356 line for each route specified in the network's other config "static-routes" value.
1357 E.g. if
1358 interface ( RO): xenbr1
1359 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1360
1361 Then route-xenbr1 should be
1362 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1363 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1364 """
1365 fname = "route-%s" % interface
1366 if oc.has_key('static-routes'):
1367 # The key is present - extract comma seperates entries
1368 lines = oc['static-routes'].split(',')
1369 else:
1370 # The key is not present, i.e. there are no static routes
1371 lines = []
1372
1373 child = ConfigurationFile(fname)
1374 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1375 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1376
1377 try:
1378 for l in lines:
1379 network, masklen, gateway = l.split('/')
1380 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1381
1382 f.attach_child(child)
1383 child.close()
1384
1385 except ValueError, e:
1386 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1387
1388 def __open_ifcfg(interface):
1389 """Open a network interface configuration file.
1390
1391 Opens the configuration file for interface, writes a header and
1392 common options and returns the file object.
1393 """
1394 fname = "ifcfg-%s" % interface
1395 f = ConfigurationFile(fname)
1396
1397 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1398 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1399 f.write("XEMANAGED=yes\n")
1400 f.write("DEVICE=%s\n" % interface)
1401 f.write("ONBOOT=no\n")
1402
1403 return f
1404
1405 def open_network_ifcfg(pif):
1406 bridge = bridge_name(pif)
1407 interface = interface_name(pif)
1408 if bridge:
1409 return __open_ifcfg(bridge)
1410 else:
1411 return __open_ifcfg(interface)
1412
1413
1414 def open_pif_ifcfg(pif):
1415 pifrec = db.get_pif_record(pif)
1416
1417 log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1418
1419 f = __open_ifcfg(interface_name(pif))
1420
1421 if pifrec.has_key('other_config'):
1422 configure_ethtool(pifrec['other_config'], f)
1423 configure_mtu(pifrec['other_config'], f)
1424
1425 return f
1426
1427 def configure_network(pif, f):
1428 """Write the configuration file for a network.
1429
1430 Writes configuration derived from the network object into the relevant
1431 ifcfg file. The configuration file is passed in, but if the network is
1432 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1433
1434 This routine may also write ifcfg files of the networks corresponding to other PIFs
1435 in order to maintain consistency.
1436
1437 params:
1438 pif: Opaque_ref of pif
1439 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1440 """
1441
1442 pifrec = db.get_pif_record(pif)
1443 host = pifrec['host']
1444 nw = pifrec['network']
1445 nwrec = db.get_network_record(nw)
1446 oc = None
1447 bridge = bridge_name(pif)
1448 interface = interface_name(pif)
1449 if bridge:
1450 device = bridge
1451 else:
1452 device = interface
1453
1454 if nwrec.has_key('other_config'):
1455 configure_ethtool(nwrec['other_config'], f)
1456 configure_mtu(nwrec['other_config'], f)
1457 configure_static_routes(device, nwrec['other_config'], f)
1458
1459
1460 if pifrec.has_key('other_config'):
1461 oc = pifrec['other_config']
1462
1463 if device == bridge:
1464 f.write("TYPE=Bridge\n")
1465 f.write("DELAY=0\n")
1466 f.write("STP=off\n")
1467 f.write("PIFDEV=%s\n" % interface_name(pif))
1468
1469 if pifrec['ip_configuration_mode'] == "DHCP":
1470 f.write("BOOTPROTO=dhcp\n")
1471 f.write("PERSISTENT_DHCLIENT=yes\n")
1472 elif pifrec['ip_configuration_mode'] == "Static":
1473 f.write("BOOTPROTO=none\n")
1474 f.write("NETMASK=%(netmask)s\n" % pifrec)
1475 f.write("IPADDR=%(IP)s\n" % pifrec)
1476 f.write("GATEWAY=%(gateway)s\n" % pifrec)
1477 elif pifrec['ip_configuration_mode'] == "None":
1478 f.write("BOOTPROTO=none\n")
1479 else:
1480 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1481
1482 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1483 ServerList = pifrec['DNS'].split(",")
1484 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1485 if oc and oc.has_key('domain'):
1486 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1487
1488 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1489 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1490 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1491
1492 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1493 #
1494 # Note: we prune out the bond master pif (if it exists).
1495 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1496 # we should bring down that master.
1497 pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1498 db.get_pif_record(__pif)['host'] == host and
1499 (not __pif in get_bond_masters_of_pif(pif)) ]
1500 other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1501
1502 peerdns_pif = None
1503 defaultroute_pif = None
1504
1505 # loop through all the pifs on this host looking for one with
1506 # other-config:peerdns = true, and one with
1507 # other-config:default-route=true
1508 for __pif in pifs_on_host:
1509 __pifrec = db.get_pif_record(__pif)
1510 __oc = __pifrec['other_config']
1511 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1512 if peerdns_pif == None:
1513 peerdns_pif = __pif
1514 else:
1515 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1516 (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1517 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1518 if defaultroute_pif == None:
1519 defaultroute_pif = __pif
1520 else:
1521 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1522 (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1523
1524 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1525 if peerdns_pif == None:
1526 peerdns_pif = management_pif
1527 if defaultroute_pif == None:
1528 defaultroute_pif = management_pif
1529
1530 # Update all the other network's ifcfg files and ensure consistency
1531 for __pif in other_pifs_on_host:
1532 __f = open_network_ifcfg(__pif)
1533 peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1534 lines = __f.readlines()
1535
1536 if not peerdns_line_wanted in lines:
1537 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1538 for line in lines:
1539 if not line.lstrip().startswith('PEERDNS'):
1540 __f.write(line)
1541 log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1542 __f.write(peerdns_line_wanted)
1543 __f.close()
1544 f.attach_child(__f)
1545
1546 else:
1547 # There is no need to change this ifcfg file. So don't attach_child.
1548 pass
1549
1550 # ... and for this pif too
1551 f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1552
1553 # Update gatewaydev
1554 fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1555 for line in fnetwork.readlines():
1556 if line.lstrip().startswith('GATEWAY') :
1557 continue
1558 fnetwork.write(line)
1559 if defaultroute_pif:
1560 gatewaydev = bridge_name(defaultroute_pif)
1561 if not gatewaydev:
1562 gatewaydev = interface_name(defaultroute_pif)
1563 fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1564 fnetwork.close()
1565 f.attach_child(fnetwork)
1566
1567 return
1568
1569
1570 def configure_physical_interface(pif):
1571 """Write the configuration for a physical interface.
1572
1573 Writes the configuration file for the physical interface described by
1574 the pif object.
1575
1576 Returns the open file handle for the interface configuration file.
1577 """
1578
1579 pifrec = db.get_pif_record(pif)
1580
1581 f = open_pif_ifcfg(pif)
1582
1583 f.write("TYPE=Ethernet\n")
1584 f.write("HWADDR=%(MAC)s\n" % pifrec)
1585
1586 return f
1587
1588 def configure_bond_interface(pif):
1589 """Write the configuration for a bond interface.
1590
1591 Writes the configuration file for the bond interface described by
1592 the pif object. Handles writing the configuration for the slave
1593 interfaces.
1594
1595 Returns the open file handle for the bond interface configuration
1596 file.
1597 """
1598
1599 pifrec = db.get_pif_record(pif)
1600 oc = pifrec['other_config']
1601 f = open_pif_ifcfg(pif)
1602
1603 if pifrec['MAC'] != "":
1604 f.write("MACADDR=%s\n" % pifrec['MAC'])
1605
1606 for slave in get_bond_slaves_of_pif(pif):
1607 s = configure_physical_interface(slave)
1608 s.write("MASTER=%(device)s\n" % pifrec)
1609 s.write("SLAVE=yes\n")
1610 s.close()
1611 f.attach_child(s)
1612
1613 # The bond option defaults
1614 bond_options = {
1615 "mode": "balance-slb",
1616 "miimon": "100",
1617 "downdelay": "200",
1618 "updelay": "31000",
1619 "use_carrier": "1",
1620 }
1621
1622 # override defaults with values from other-config whose keys being with "bond-"
1623 overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1624 overrides = map(lambda (key,val): (key[5:], val), overrides)
1625 bond_options.update(overrides)
1626
1627 # write the bond options to ifcfg-bondX
1628 f.write('BONDING_OPTS="')
1629 for (name,val) in bond_options.items():
1630 f.write("%s=%s " % (name,val))
1631 f.write('"\n')
1632 return f
1633
1634 def configure_vlan_interface(pif):
1635 """Write the configuration for a VLAN interface.
1636
1637 Writes the configuration file for the VLAN interface described by
1638 the pif object. Handles writing the configuration for the master
1639 interface if necessary.
1640
1641 Returns the open file handle for the VLAN interface configuration
1642 file.
1643 """
1644
1645 slave = configure_pif(get_vlan_slave_of_pif(pif))
1646 slave.close()
1647
1648 f = open_pif_ifcfg(pif)
1649 f.write("VLAN=yes\n")
1650 f.attach_child(slave)
1651
1652 return f
1653
1654 def configure_pif(pif):
1655 """Write the configuration for a PIF object.
1656
1657 Writes the configuration file the PIF and all dependent
1658 interfaces (bond slaves and VLAN masters etc).
1659
1660 Returns the open file handle for the interface configuration file.
1661 """
1662
1663 pifrec = db.get_pif_record(pif)
1664
1665 if pifrec['VLAN'] != '-1':
1666 f = configure_vlan_interface(pif)
1667 elif len(pifrec['bond_master_of']) != 0:
1668 f = configure_bond_interface(pif)
1669 else:
1670 f = configure_physical_interface(pif)
1671
1672 bridge = bridge_name(pif)
1673 if bridge:
1674 f.write("BRIDGE=%s\n" % bridge)
1675
1676 return f
1677
1678 def unconfigure_pif(pif):
1679 """Clear up the files created by configure_pif"""
1680 f = open_pif_ifcfg(pif)
1681 log("Unlinking stale file %s" % f.path())
1682 f.unlink()
1683 return f
1684 \f
1685 if __name__ == "__main__":
1686 rc = 1
1687 try:
1688 rc = main()
1689 except:
1690 ex = sys.exc_info()
1691 err = traceback.format_exception(*ex)
1692 for exline in err:
1693 log(exline)
1694
1695 if not debug_mode():
1696 syslog.closelog()
1697
1698 sys.exit(rc)