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