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