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