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