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

Contents of /trunk/mia/status.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2361 - (show annotations) (download) (as text)
Tue Mar 9 10:41:38 2010 UTC (3 years, 2 months ago) by jhr
File MIME type: text/x-python
File size: 9846 byte(s)
suspend only 2 weeks for nice, in, out
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 'removed': 10,
117 'role': 99
118 }
119 suspend_statusses = {
120 'ok': -1,
121 'npa': 365*24*3600,
122 'willfix': 4*7*24*3600
123 }
124 prod_types = ['nice', 'prod', 'last-warning', 'wat']
125
126 def parse_status(info):
127 status = 'none'
128 status_time = -1
129 suspend_status = None
130 suspend_until = -1
131 prod = 'none'
132 prod_time = -1
133 times = info.keys()
134 times.sort()
135 for time in times:
136 value = info[time].split(';')[0]
137 for v in value.split(','):
138 v = v.strip().lower()
139 foo = v.split(None, 1)
140 word = foo[0]
141 rest = None
142 if len(foo) > 1:
143 rest = foo[1]
144 if v in regular_statusses.keys() and v != 'none':
145 if regular_statusses[v] == 0 \
146 or regular_statusses[v] > regular_statusses[status]:
147 status = v
148 status_time = time
149 elif word in suspend_statusses.keys():
150 suspend_status = word
151 suspend_until = time + parse_for(suspend_statusses[word], rest)
152 elif word in prod_types:
153 prod = word
154 prod_time = time
155 # suspend current status by default for 3 weeks in case of
156 # outgoing mail, special case: 2 weeks for nice since nice is
157 # to be sent twice anyways
158 suspend_status = 'mailed'
159 if prod == 'nice':
160 suspend_until = time + parse_for(2*7*24*3600, rest)
161 else:
162 suspend_until = time + parse_for(3*7*24*3600, rest)
163 elif word in ['in', 'out']:
164 # suspend current status by default for 2 weeks in case of
165 # mail contact
166 susp = time + parse_for(2*7*24*3600, rest)
167 if susp > suspend_until:
168 suspend_status = 'mailed'
169 suspend_until = susp
170 elif v == "-":
171 pass
172 else:
173 # too much stuff yet # sys.stderr.write("Warning: parse error in %s\n" % v)
174 if not info[time].endswith(" {parse error}"):
175 if info[time].find(";") == -1:
176 info[time] += ";"
177 info[time] += " {parse error}"
178 if status == 'none':
179 status = 'busy'
180 status_time = time
181
182 suspend = ""
183 res = {'status': status, 'prod': prod}
184 if status != 'none':
185 res['status_time'] = status_time
186 if prod != 'none':
187 res['prod_time'] = prod_time
188 if regular_statusses[status] and suspend_status:
189 if suspend_until > now:
190 res['suspend'] = suspend_status
191 res['suspend_until'] = suspend_until
192 suspend = " (but %s for another %s)" % (suspend_status,
193 time_passed(suspend_until, -1))
194 else:
195 suspend = " (was %s until %s ago)" % (suspend_status,
196 time_passed(suspend_until))
197
198 prod_for = ""
199 if prod_time >= 0: prod_for = " for %s" % time_passed(prod_time)
200 res['human'] = "Status is %s for %s%s; Prod-level is %s%s" % \
201 (status, time_passed(status_time), suspend, prod, prod_for)
202 if not info:
203 res['human'] = "Not in database"
204
205 return res
206
207 time_spans = {
208 'd': 24*3600,
209 'w': 7*24*3600,
210 'm': 30*24*3600,
211 'y': 365*24*3600
212 }
213
214 def parse_for(default, arg):
215 if arg == None:
216 return default
217 re_time = re.compile(r"(\d+)([dwmy])")
218 res = 0
219 for t in re_time.finditer(arg):
220 res += int(t.group(1)) * time_spans[t.group(2)]
221 return res
222
223 def time_passed(t, multiplier=1):
224 return "%sd" % int(multiplier*(now - t)/24/3600)
225
226 def write_status(file, id, summary, withMail=1, sender=None):
227 try:
228 status = open(Cnf["Dir::Database"] + "/" + file + ".summary", "a")
229 except IOError:
230 print "Cannot open summary file to write!"
231 sys.exit(1)
232
233 nomail = ""
234 if not withMail:
235 nomail = " nomail %s" % pwd.getpwuid(os.getuid())[0]
236 sender_str = ""
237 re_sender = re.compile(r"<(.+@.+)>")
238 if sender:
239 result = re_sender.search(sender)
240 if result:
241 sender_str = " from %s" % result.group(1)
242
243 string = str(int(id)) + nomail + sender_str + ": " + summary + "\n"
244 status.write(string)
245 status.close()
246
247 if info.has_key(file):
248 del(info[file])
249
250
251 def parse_history(history):
252 lines = [ ]
253 all_contacts = history.keys()
254 all_contacts.sort()
255 for when in all_contacts:
256 lines.append(" %s: %s" % (get_time(when, "%Y-%m-%d"), history[when]))
257 return lines
258
259 def get_time(when, format):
260 return time.strftime(format, time.gmtime(when))
261
262 def searchCarnivore(query, regex=False):
263 return carnivore.search(query)
264
265 def get(carnivoreId):
266 return miaEntry(carnivore.get(carnivoreId))
267
268 class miaEntry:
269 def __init__(self, carnivore):
270 self.ce = carnivore
271 self.id = carnivore.id
272
273 def __repr__(self):
274 return "miaEntry("+(self.ce.ldap + self.ce.email)[0]+")"
275
276 def getHistory(self):
277 mia_db = None
278 role = False
279 for id in self.ce.ldap + self.ce.email:
280 mia_status = read_history(id)
281 if id.find("@lists.") >= 0:
282 role = True
283 if mia_status and mia_db:
284 sys.stderr.write("Maintainer has duplicate entries in MIA db: %s and %s\n"
285 % (self.mia_path_id, id))
286 if mia_status:
287 mia_db = mia_status
288 self.mia_path_id = id
289 if role and mia_db:
290 sys.stderr.write("Looks like role but has MIA entry: %s \n"
291 % self.mia_path_id)
292 sys.exit(1)
293 if role:
294 return {0: 'role; Detected mailinglist address'}
295 if not mia_db:
296 return {}
297 return mia_db
298
299 def getStatus(self):
300 return parse_status(self.getHistory())
301
302 def statusIsAtLeast(self, status):
303 return regular_statusses[self.getStatus()['status']] \
304 >= regular_statusses[status]
305
306 def getPrettyprintedText(self):
307 text = self.ce.getPrettyprintedText()
308
309 history = self.getHistory()
310 text += "X-MIA: "+parse_status(history)['human']+"\n"
311 text += "\n".join(parse_history(history))
312
313 return text
314
315 def prettyPrint(self):
316 print self.getPrettyprintedText() + "\n"
317
318 # 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