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