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