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