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