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