source: trunk/debathena/debathena/getcluster/getcluster.py @ 25869

Revision 25869, 7.4 KB checked in by jdreed, 11 years ago (diff)
In getcluster: * Rewrite in Python and remove autogoo (Trac: #1037) * Actually quote shell output (Trac: #1037) * Add decision debugging with DEBUG_GETCLUSTER=yes * Convert to Debian native package * Bump compat level and Standards-Version * Depend on python-hesiod * Build-Depend on python-hesiod (for 'make check') * Convert to arch-indep package
  • Property svn:executable set to *
Line 
1#!/usr/bin/python
2#
3# A rewrite of getcluster(1) in Python
4#
5
6FALLBACKFILE = "/etc/cluster.fallback"
7LOCALFILE = "/etc/cluster.local"
8CLUSTERFILE = "/etc/cluster"
9HESTYPE = "cluster"
10GENERIC = "public-linux"
11UPDATE_INTERVAL = (3600 * 4)
12
13import hesiod
14import re
15import sys
16import errno
17import socket
18import os
19import time
20import random
21import struct
22from optparse import OptionParser
23
24cluster_re = re.compile('^\S+ \S+( \S+){0,2}$')
25debugMode = os.getenv('DEBUG_GETCLUSTER', 'no') == 'yes'
26
27def debug(msg, *args):
28    if debugMode:
29        print >>sys.stderr, "D:", msg % (args)
30
31def perror(msg, *args):
32    print >>sys.stderr, ("%s: " + msg) % ((sys.argv[0],) + args)
33
34def merge(list1, list2):
35    rv = [] + list1
36    for i in list2:
37        if i.split(' ', 1)[0] not in [j.split(' ', 1)[0] for j in list1]:
38            rv.append(i)
39    return rv
40
41def cleanup(l):
42    return filter(lambda x: cluster_re.match(x), map(lambda x: x.rstrip(), l))
43
44def vercmp(v1, v2):
45    v1l = map(int, map(lambda x: 0 if x is None else x, re.search('^(\d+)(?:\.(\d+)){0,1}',v1).groups()))
46    v2l = map(int, map(lambda x: 0 if x is None else x, re.search('^(\d+)(?:\.(\d+)){0,1}',v2).groups()))
47    return cmp(v1l, v2l)
48
49
50def output_var(var, val, bourne=False, plaintext=False):
51    val = val.replace("'", "'\\''")
52    var = var.upper()
53    if bourne:
54        print "%s='%s' ; export %s" % (var, val, var)
55    elif plaintext:
56        print "%s %s" % (var, val)
57    else:
58        print "setenv %s '%s' ;" % (var, val)
59
60def main():
61    parser = OptionParser(usage='%prog [options] version', add_help_option=False)
62    parser.add_option("-b", action="store_true", dest="bourne", default=False)
63    parser.add_option("-p", action="store_true", dest="plain", default=False)
64    parser.add_option("-d", action="store_true", dest="debug", default=False)
65    parser.add_option("-f", action="store_true", dest="deprecated", default=False)
66    parser.add_option("-l", action="store_true", dest="deprecated", default=False)
67    parser.add_option("-h", action="store", type="string", dest="hostname")
68
69    (options, args) = parser.parse_args()
70    if len(args) == 2:
71        perror("Ignoring deprecated hostname syntax.")
72        args.pop(0)
73    if len(args) != 1:
74        parser.print_usage()
75        return 1
76   
77    if options.deprecated:
78        perror("-f and -l are deprecated and will be ignored.")
79    if options.bourne and options.plain:
80        parser.error("-p and -b are mutually exclusive.")
81
82    ws_version=args[0]
83    fallback=[]
84    local=[]
85    data=[]
86    if (options.hostname == None):
87        # Get the hostname/cluster name from /etc/cluster if present
88        try:
89            with open(CLUSTERFILE, 'r') as f:
90                options.hostname = f.readline().strip()
91        except IOError as e:
92            # Fallback to nodename
93            options.hostname = os.uname()[1]
94
95        if not options.hostname:
96            perror("Cannot determine hostname or %s was empty", CLUSTERFILE)
97            return 1
98
99        try:
100            with open(FALLBACKFILE, 'r') as f:
101                fallback = cleanup(f.readlines())
102        except IOError as e:
103            pass
104
105        try:
106            with open(LOCALFILE, 'r') as f:
107                local = cleanup(f.readlines())
108        except IOError as e:
109            pass
110
111    if options.debug:
112        data = cleanup(sys.stdin.readlines())
113    else:
114        hes = None
115        try:
116            hes = hesiod.Lookup(options.hostname, HESTYPE)
117        except IOError as e:
118            if e.errno != errno.ENOENT:
119                perror("hesiod_resolve: %s", str(e))
120                return 1
121        if hes == None:
122            try:
123                hes = hesiod.Lookup(GENERIC, HESTYPE)
124            except IOError as e:
125                if e.errno != errno.ENOENT:
126                    perror("hesiod_resolve: %s", str(e))
127                    return 1
128        if hes == None:
129            perror("Could not find any Hesiod cluster data.")
130            return 1
131        data = cleanup(hes.results)
132
133    if len(data) == 0 and len(fallback) == 0 and len(local) == 0:
134        if (options.debug):
135            perror("No valid cluster data specified on stdin.")
136        else:
137            perror("No cluster information available for %s", options.hostname)
138    # Sort everything into a hash, with varnames as keys and lists of lists as values
139    cdata = {}
140    for i in map(lambda x: x.split(' '), merge(merge(local, data), fallback)):
141        var = i.pop(0)
142        if var not in cdata:
143            cdata[var] = []
144        cdata[var].append(i)
145
146    now = int(time.time())
147    update_time = -1
148    try:
149        update_time = int(os.getenv('UPDATE_TIME', ''))
150    except ValueError:
151        pass
152    autoupdate = os.getenv('AUTOUPDATE', 'false') == 'true'
153    # This is secretly a 32-bit integer regardless of platform
154    ip = -1 & 0xffffffff
155    try:
156        ip = socket.inet_aton(os.getenv('ADDR', ''))
157    except socket.error:
158        pass
159
160    new_prod = '0.0'
161    new_testing = '0.0'
162    output_time = False
163    vars = {}
164    for var in cdata:
165        maxverforval = '0.0'
166        for opt in cdata[var]:
167            val = opt.pop(0)
168            vers = opt.pop(0) if len(opt) else None
169            flags = opt.pop(0) if len(opt) else ''
170            debug("Examining: %s %s %s %s", var, val, vers, flags)
171            if vers == None:
172                # No version data.  Save it as the default
173                # if we don't already have one
174                if var not in vars:
175                    debug("-> Saving %s as default", val)
176                    vars[var] = val
177            elif vercmp(vers, ws_version) < 0:
178                # Too old.  Discard
179                debug("-> Discarding %s (%s < %s)", val, vers, ws_version)
180            elif ((autoupdate and 't' not in flags) or (vercmp(vers, ws_version) == 0)):
181                if vercmp(vers, ws_version) > 0:
182                    debug("-> Found newer value (%s > %s)", vers, ws_version)
183                    output_time = True
184                    if (update_time == -1) or (now < update_time):
185                        debug("-> Defering new value.")
186                        continue
187                if vercmp(vers, maxverforval) >= 0:
188                    # We found a better option
189                    debug("-> Storing %s (%s > previous max %s)", val, vers, maxverforval)
190                    maxverforval = vers
191                    vars[var] = val
192            else:
193                debug("-> Discarding, but checking new versions")
194                if 't' in flags and vercmp(vers, new_testing) > 0:
195                    debug("-> Storing new testing version %s", vers)
196                    new_testing = vers
197                if 't' not in flags and vercmp(vers, new_prod) > 0:
198                    debug("-> Storing new production version %s", vers)
199                    new_prod = vers
200
201    for v in vars:
202        output_var(v, vars[v], options.bourne, options.plain)
203
204    if vercmp(new_testing, ws_version) > 0:
205        output_var('NEW_TESTING_RELEASE', new_testing, options.bourne, options.plain)
206
207    if vercmp(new_prod, ws_version) > 0:
208        output_var('NEW_PRODUCTION_RELEASE', new_prod, options.bourne, options.plain)
209
210    if output_time:
211        if update_time == -1:
212            random.seed(socket.ntohl(ip))
213            # Python doesn't have RAND_MAX?
214            update_time = now + random.randint(0,65535) % UPDATE_INTERVAL
215        output_var('UPDATE_TIME', "%lu" % (update_time), options.bourne, options.plain)
216   
217if __name__ == '__main__':
218    sys.exit(main())
Note: See TracBrowser for help on using the repository browser.