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