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