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