/[qa]/trunk/mia/status.py
ViewVC logotype

Contents of /trunk/mia/status.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1481 - (show annotations) (download) (as text)
Thu Dec 28 15:00:14 2006 UTC (6 years, 4 months ago) by myon
File MIME type: text/x-python
File size: 9633 byte(s)
move carnivore out of data/
1 # The module which forms the heart of the MIA scripts
2 # Copyright (C) 2001, 2002, 2003, 2004 Martin Michlmayr <tbm@cyrius.com>
3 # Copyright (C) 2006 Jeroen van Wolffelaar <jeroen@wolffelaar.nl>
4 # Copyright (C) 2006 Christoph Berg <myon@debian.org>
5 # $Id$
6
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
21
22 # This is a collection of software to track down developers who are
23 # Missing In Action (MIA).
24
25 import os, re, string, sys, time, pwd
26 sys.path.append("/org/qa.debian.org/carnivore")
27 import apt_pkg, carnivore
28
29 now = time.time()
30
31 config = "/org/qa.debian.org/mia/mia.conf"
32
33
34 re_summary_line = re.compile(r"(\d+)( [a-z]+ [^ \t]+)*:\s*(.*)")
35
36 apt_pkg.init()
37 Cnf = apt_pkg.newConfiguration()
38 apt_pkg.ReadConfigFileISC(Cnf, config)
39 info = { }
40
41 def find_status(path=Cnf["Dir::Database"]):
42 files = []
43
44 try:
45 for file in os.listdir(path):
46 root, ext = os.path.splitext(file)
47 if ext == ".summary":
48 files.append(root)
49 except OSError:
50 print "Cannot find directory of database: %s" % path
51 sys.exit(1)
52
53 return files
54
55
56 def read_history(id):
57 global info
58 id = id.replace('@', '=')
59
60 if info.has_key(id):
61 return info[id]
62
63 try:
64 summary = open(Cnf["Dir::Database"] + "/" + id + ".summary", "r")
65 except IOError:
66 return None
67
68 res = {}
69
70 for line in summary.readlines():
71 result = re_summary_line.search(line)
72
73 if result is None:
74 print >> sys.stderr, "Error parsing file %s: %s" % (file, line)
75 sys.exit(1)
76
77 text = result.group(3)
78 # TODO: return sensibly
79 if result.group(2):
80 extra = result.group(2).strip().split()
81 while extra:
82 if extra[0] == "nomail":
83 if text.find(";") == -1:
84 text += ";"
85 extra.pop(0)
86 text += " {command-line supplied by %s}" % extra.pop(0)
87 elif extra[0] == "from":
88 if text.find(";") == -1:
89 text += ";"
90 extra.pop(0)
91 text += " {from %s}" % extra.pop(0)
92 else:
93 print >> sys.stderr, "Error parsing extra info in %s: %s" % (file, line)
94 sys.exit(1)
95
96 t = int(result.group(1))
97 while res.has_key(t): # gross hack to make keys unique
98 t += 1
99 res[t] = text
100
101 summary.close()
102
103 info[id] = res
104 return res
105
106 regular_statusses = {
107 'none': 0,
108 'ok': 0,
109 'busy': 1,
110 'inactive': 2,
111 'unresponsive': 3,
112 'retiring': 4,
113 'mia': 6,
114 'needs-wat': 7,
115 'retired': 10,
116 'role': 99
117 }
118 suspend_statusses = {
119 'ok': -1,
120 'npa': 365*24*3600,
121 'willfix': 4*7*24*3600
122 }
123 prod_types = ['nice', 'prod', 'last-warning', 'wat']
124
125 def parse_status(info):
126 status = 'none'
127 status_time = -1
128 suspend_status = None
129 suspend_until = -1
130 prod = 'none'
131 prod_time = -1
132 times = info.keys()
133 times.sort()
134 for time in times:
135 value = info[time].split(';')[0]
136 for v in value.split(','):
137 v = v.strip().lower()
138 foo = v.split(None, 1)
139 word = foo[0]
140 rest = None
141 if len(foo) > 1:
142 rest = foo[1]
143 if v in regular_statusses.keys() and v != 'none':
144 if regular_statusses[v] == 0 \
145 or regular_statusses[v] > regular_statusses[status]:
146 status = v
147 status_time = time
148 elif word in suspend_statusses.keys():
149 suspend_status = word
150 suspend_until = time + parse_for(suspend_statusses[word], rest)
151 elif word in prod_types:
152 prod = word
153 prod_time = time
154 # suspend current status by default for 3 weeks in case of
155 # outgoing mail
156 suspend_status = 'mailed'
157 suspend_until = time + parse_for(3*7*24*3600, rest)
158 elif word in ['in', 'out']:
159 # suspend current status by default for 3 weeks in case of
160 # mail contact
161 susp = time + parse_for(3*7*24*3600, rest)
162 if susp > suspend_until:
163 suspend_status = 'mailed'
164 suspend_until = susp
165 elif v == "-":
166 pass
167 else:
168 # too much stuff yet # sys.stderr.write("Warning: parse error in %s\n" % v)
169 if not info[time].endswith(" {parse error}"):
170 if info[time].find(";") == -1:
171 info[time] += ";"
172 info[time] += " {parse error}"
173 if status == 'none':
174 status = 'busy'
175 status_time = time
176
177 suspend = ""
178 res = {'status': status, 'prod': prod}
179 if status != 'none':
180 res['status_time'] = status_time
181 if prod != 'none':
182 res['prod_time'] = prod_time
183 if regular_statusses[status] and suspend_status:
184 if suspend_until > now:
185 res['suspend'] = suspend_status
186 res['suspend_until'] = suspend_until
187 suspend = " (but %s for another %s)" % (suspend_status,
188 time_passed(suspend_until, -1))
189 else:
190 suspend = " (was %s until %s ago)" % (suspend_status,
191 time_passed(suspend_until))
192
193 prod_for = ""
194 if prod_time >= 0: prod_for = " for %s" % time_passed(prod_time)
195 res['human'] = "Status is %s for %s%s; Prod-level is %s%s" % \
196 (status, time_passed(status_time), suspend, prod, prod_for)
197 if not info:
198 res['human'] = "Not in database"
199
200 return res
201
202 time_spans = {
203 'd': 24*3600,
204 'w': 7*24*3600,
205 'm': 30*24*3600,
206 'y': 365*24*3600
207 }
208
209 def parse_for(default, arg):
210 if arg == None:
211 return default
212 re_time = re.compile(r"(\d+)([dwmy])")
213 res = 0
214 for t in re_time.finditer(arg):
215 res += int(t.group(1)) * time_spans[t.group(2)]
216 return res
217
218 def time_passed(t, multiplier=1):
219 return "%sd" % int(multiplier*(now - t)/24/3600)
220
221 def write_status(file, id, summary, withMail=1, sender=None):
222 try:
223 status = open(Cnf["Dir::Database"] + "/" + file + ".summary", "a")
224 except IOError:
225 print "Cannot open summary file to write!"
226 sys.exit(1)
227
228 nomail = ""
229 if not withMail:
230 nomail = " nomail %s" % pwd.getpwuid(os.getuid())[0]
231 sender_str = ""
232 re_sender = re.compile(r"<(.+@.+)>")
233 if sender:
234 result = re_sender.search(sender)
235 if result:
236 sender_str = " from %s" % result.group(1)
237
238 string = str(int(id)) + nomail + sender_str + ": " + summary + "\n"
239 status.write(string)
240 status.close()
241
242 if info.has_key(file):
243 del(info[file])
244
245
246 def parse_history(history):
247 lines = [ ]
248 all_contacts = history.keys()
249 all_contacts.sort()
250 for when in all_contacts:
251 lines.append(" %s: %s" % (get_time(when, "%Y-%m-%d"), history[when]))
252 return lines
253
254 def get_time(when, format):
255 return time.strftime(format, time.gmtime(when))
256
257 def searchCarnivore(query, regex=False):
258 return carnivore.search(query)
259
260 def get(carnivoreId):
261 return miaEntry(carnivore.get(carnivoreId))
262
263 class miaEntry:
264 def __init__(self, carnivore):
265 self.ce = carnivore
266 self.id = carnivore.id
267
268 def __repr__(self):
269 return "miaEntry("+(self.ce.ldap + self.ce.email)[0]+")"
270
271 def getHistory(self):
272 mia_db = None
273 role = False
274 for id in self.ce.ldap + self.ce.email:
275 mia_status = read_history(id)
276 if id.find("@lists.") >= 0:
277 role = True
278 if mia_status and mia_db:
279 sys.stderr.write("Maintainer has duplicate entries in MIA db: %s and %s\n"
280 % (self.mia_path_id, id))
281 sys.exit(1)
282 if mia_status:
283 mia_db = mia_status
284 self.mia_path_id = id
285 if role and mia_db:
286 sys.stderr.write("Looks like role but has MIA entry: %s \n"
287 % self.mia_path_id)
288 sys.exit(1)
289 if role:
290 return {0: 'role; Detected mailinglist address'}
291 if not mia_db:
292 return {}
293 return mia_db
294
295 def getStatus(self):
296 return parse_status(self.getHistory())
297
298 def statusIsAtLeast(self, status):
299 return regular_statusses[self.getStatus()['status']] \
300 >= regular_statusses[status]
301
302 def getPrettyprintedText(self):
303 text = self.ce.getPrettyprintedText()
304
305 history = self.getHistory()
306 text += "X-MIA: "+parse_status(history)['human']+"\n"
307 text += "\n".join(parse_history(history))
308
309 return text
310
311 def prettyPrint(self):
312 print self.getPrettyprintedText() + "\n"
313
314 # vim: ts=4:expandtab:shiftwidth=4:

Properties

Name Value
svn:eol-style native
svn:executable *
svn:keywords Author Date Id Revision

  ViewVC Help
Powered by ViewVC 1.1.5