seppuku: add PN to comment line for attachment (as discussed on ml)
[vuplus_openembedded] / classes / seppuku.bbclass
1 #
2 # Small event handler to automatically open URLs and file
3 # bug reports at a bugzilla of your choiche
4 #
5 # This class requires python2.4 because of the urllib2 usage
6 #
7
8 def seppuku_spliturl(url):
9     """
10     Split GET URL to return the host base and the query
11     as a param dictionary
12     """
13     import urllib
14     (uri,query)  = urllib.splitquery(url)
15     param = {}
16     for par in query.split("&"):
17         (key,value) = urllib.splitvalue(par)
18         if not key or len(key) == 0 or not value:
19             continue
20         key = urllib.unquote(key)
21         value = urllib.unquote(value)
22         param[key] = value
23
24     return (uri,param)
25
26
27
28 def seppuku_login(opener, login, user, password):
29     """
30     We need to post to query.cgi with the parameters
31     Bugzilla_login and Bugzilla_password and will scan
32     the resulting page then
33
34     @param opened = cookie enabled urllib2 opener
35     @param login = http://bugs.openembedded.org/query.cgi?
36     @param user  = Your username
37     @param password  = Your password
38     """
39     import urllib
40     param = urllib.urlencode( {"GoAheadAndLogIn" : 1, "Bugzilla_login" : user, "Bugzilla_password" : password } )
41     result = opener.open(login + param)
42
43     if result.code != 200:
44         return False
45     txt = result.read()
46     if not '<a href="relogin.cgi">Log&nbsp;out</a>' in txt:
47         return False
48
49     return True
50
51 def seppuku_find_bug_report_old():
52     from HTMLParser import HTMLParser
53
54     class BugQueryExtractor(HTMLParser):
55         STATE_NONE             = 0
56         STATE_FOUND_TR         = 1
57         STATE_FOUND_NUMBER     = 2
58         STATE_FOUND_PRIO       = 3
59         STATE_FOUND_PRIO2      = 4
60         STATE_FOUND_NAME       = 5
61         STATE_FOUND_PLATFORM   = 6
62         STATE_FOUND_STATUS     = 7
63         STATE_FOUND_WHATEVER   = 8 # I don't know this field
64         STATE_FOUND_DESCRIPTION =9
65
66         def __init__(self):
67             HTMLParser.__init__(self)
68             self.state = self.STATE_NONE
69             self.bugs = []
70             self.bug  = None
71
72         def handle_starttag(self, tag, attr):
73             if self.state == self.STATE_NONE and tag.lower() == "tr":
74                 if len(attr) == 1 and attr[0][0] == 'class' and \
75                     ('bz_normal' in attr[0][1] or 'bz_blocker' in attr[0][1] or 'bz_enhancement' in attr[0][1] or 'bz_major' in attr[0][1] or 'bz_minor' in attr[0][1] or 'bz_trivial' in attr[0][1] or 'bz_critical' in attr[0][1] or 'bz_wishlist' in attr[0][1]) \
76                     and 'bz_P' in attr[0][1]:
77                     self.state = self.STATE_FOUND_TR
78             elif self.state == self.STATE_FOUND_TR and tag.lower() == "td":
79                 self.state += 1
80
81         def handle_endtag(self, tag):
82             if tag.lower() == "tr":
83                 if self.state != self.STATE_NONE:
84                     self.bugs.append( (self.bug,self.status) )
85                 self.state = self.STATE_NONE
86                 self.bug  = None
87             if self.state > 1 and tag.lower() == "td":
88                 self.state += 1
89
90         def handle_data(self,data):
91             data = data.strip()
92
93             # skip garbage
94             if len(data) == 0:
95                 return
96
97             if self.state == self.STATE_FOUND_NUMBER:
98                 """
99                 #1995 in bugs.oe.org has [SEC] additionally to the number and we want to ignore it
100                 """
101                 if not self.bug:
102                     self.bug = data
103             elif self.state == self.STATE_FOUND_STATUS:
104                 self.status = data
105
106         def result(self):
107             return self.bugs
108
109     return BugQueryExtractor()
110
111
112
113 def seppuku_find_bug_report(debug_file, opener, query, product, component, bugname):
114     """
115     Find a bug report with the sane name and return the bug id
116     and the status.
117
118     @param opener = urllib2 opener
119     @param query  = e.g. http://bugs.openembedded.org/query.cgi?
120     @param product = search for this product
121     @param component = search for this component
122     @param bugname = the bug to search for
123
124     http://bugs.openembedded.org/buglist.cgi?short_desc_type=substring&short_desc=manual+test+bug&product=Openembedded&emailreporter2=1&emailtype2=substring&email2=freyther%40yahoo.com
125     but it does not support ctype=csv...
126     """
127     import urllib
128     product   = urllib.quote(product)
129     component = urllib.quote(component)
130     bugname   = urllib.quote(bugname)
131
132     file = "%(query)sproduct=%(product)s&component=%(component)s&short_desc_type=substring&short_desc=%(bugname)s" % vars()
133     print >> debug_file, "Trying %s" % file
134     result = opener.open(file)
135     if result.code != 200:
136         raise "Can not query the bugzilla at all"
137     txt = result.read()
138     scanner = seppuku_find_bug_report_old()
139     scanner.feed(txt)
140     if len(scanner.result()) == 0:
141         print >> debug_file, "Scanner failed to scan the html site"
142         print >> debug_file, "%(query)sproduct=%(product)s&component=%(component)s&short_desc_type=substring&short_desc=%(bugname)s" % vars()
143         #print >> debug_file, txt
144         return (False,None)
145     else: # silently pick the first result
146         print >> debug_file, "Result of bug search is "
147         #print >> debug_file, txt
148         (number,status) = scanner.result()[0]
149         return (not status in ["CLOS", "RESO", "VERI"],number)
150
151 def seppuku_reopen_bug(poster, file, product, component, bug_number, bugname, text):
152     """
153     Reopen a bug report and append to the comment
154
155     Same as with opening a new report, some bits need to be inside the url
156
157     http://bugs.openembedded.org/process_bug.cgi?id=239&bug_file_loc=http%3A%2F%2F&version=Angstrom&longdesclength=2&product=Openembedded&component=Build&comment=bla&priority=P2&bug_severity=normal&op_sys=Linux&rep_platform=Other&knob=reopen&short_desc=foo
158     """
159
160     import urllib2
161     (uri, param) = seppuku_spliturl( file )
162
163     # Prepare the post
164     param["product"]        = product
165     param["component"]      = component
166     param["longdesclength"] = 2
167     param["short_desc"]     = bugname
168     param["knob"]           = "reopen"
169     param["id"]             = bug_number
170     param["comment"]        = text
171
172     try:
173         result = poster.open( uri, param )
174     except urllib2.HTTPError, e:
175         print e.geturl()
176         print e.info()
177         return False
178     except Exception, e:
179         print e
180         return False
181
182     if result.code != 200:
183         return False
184     else:
185         return True
186
187 def seppuku_file_bug(poster, file, product, component, bugname, text):
188     """
189     Create a completely new bug report
190
191
192     http://bugs.openembedded.org/post_bug.cgi?bug_file_loc=http%3A%2F%2F&version=Angstrom&product=Openembedded&component=Build&short_desc=foo&comment=bla&priority=P2&bug_severity=normal&op_sys=Linux&rep_platform=Other
193
194     You are forced to add some default values to the bugzilla query and stop with '&'
195
196     @param opener  urllib2 opener
197     @param file    The url used to file a bug report
198     @param product Product
199     @param component Component
200     @param bugname  Name of the to be created bug
201     @param text Text
202     """
203
204     import urllib2
205     (uri, param) = seppuku_spliturl( file )
206     param["product"]    = product
207     param["component"]  = component
208     param["short_desc"] = bugname
209     param["comment"]    = text
210
211     try:
212         result = poster.open( uri, param )
213     except urllib2.HTTPError, e:
214         print e.geturl()
215         print e.info()
216         return False
217     except Exception, e:
218         print e
219         return False
220
221     # scan the result for a bug number
222     # it will look like 
223     # '<title>Bug 2742 Submitted</title>'
224     import re
225     res = re.findall(("\>Bug (?P<int>\d+) Submitted"), result.read() )
226     if result.code != 200 or len(res) != 1:
227         return None 
228     else:
229         return res[0] 
230
231 def seppuku_create_attachment(debug, poster, attach_query, product, component, bug_number, text, file):
232     """
233
234     Create a new attachment for the failed report
235     """
236
237     if not bug_number:
238         import bb
239         bb.note("Can't create an attachment, no bugnumber passed to method")
240         print >> debug, "Can't create an attachment, no bugnumber passed to method"
241         return False
242
243     if not attach_query:
244         import bb
245         bb.note("Can't create an attachment, no attach_query passed to method")
246         print >> debug, "Can't create an attachment, no attach_query passed to method"
247         return False
248
249     logdescription = "Build log for machine %s" % (bb.data.getVar('MACHINE', data, True))
250
251     import urllib2
252     param = { "bugid" : bug_number, "action" : "insert", "data" : file, "description" : logdescription, "ispatch" : "0", "contenttypemethod" : "list", "contenttypeselection" : "text/plain", "comment" : text }
253
254     try:
255         result = poster.open( attach_query, param )
256     except urllib2.HTTPError, e:
257         print e.geturl()
258         print e.info()
259         return False
260     except Exception, e:
261         print e
262         print >> debug, "Got exception in poster.open( attach_query, param )"
263         print >> debug, "attach_query: %s  param: %s" % (attach_query, param )
264         return False
265
266     txt = result.read()
267     if result.code != 200:
268         print >> debug, "Got bad return code (%s)" % result.code
269         return False
270     else:
271         print >> debug, "Got good return code (200)" 
272         return True
273
274
275 addhandler seppuku_eventhandler
276 python seppuku_eventhandler() {
277     """
278     Report task failures to the bugzilla
279     and succeeded builds to the box
280     """
281     from bb.event import NotHandled, getName
282     from bb import data, mkdirhier, build
283     import bb, os, glob
284
285     event = e
286     data = e.data
287     name = getName(event)
288     if name == "MsgNote":
289        # avoid recursion
290        return NotHandled
291
292     # Try to load our exotic libraries
293     try:
294         import MultipartPostHandler
295     except:
296         bb.note("You need to put the MultipartPostHandler into your PYTHONPATH. Download it from http://pipe.scs.fsu.edu/PostHandler/MultipartPostHandler.py")
297         return NotHandled
298
299     try:
300         import urllib2, cookielib
301     except:
302         bb.note("Failed to import the cookielib and urllib2, make sure to use python2.4")
303         return NotHandled
304
305     if name == "PkgFailed":
306         if not bb.data.getVar('SEPPUKU_AUTOBUILD', data, True) == "0":
307             build.exec_func('do_clean', data)
308     elif name == "TaskFailed":
309         cj = cookielib.CookieJar()
310         opener  = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
311         poster  = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj),MultipartPostHandler.MultipartPostHandler)
312         login   = bb.data.getVar("SEPPUKU_LOGIN", data, True)
313         query   = bb.data.getVar("SEPPUKU_QUERY", data, True)
314         newbug  = bb.data.getVar("SEPPUKU_NEWREPORT",  data, True)
315         reopen  = bb.data.getVar("SEPPUKU_ADDCOMMENT",  data, True)
316         attach  = bb.data.getVar("SEPPUKU_ATTACHMENT", data, True)
317         user    = bb.data.getVar("SEPPUKU_USER",  data, True)
318         passw   = bb.data.getVar("SEPPUKU_PASS",  data, True)
319         product = bb.data.getVar("SEPPUKU_PRODUCT", data, True)
320         component = bb.data.getVar("SEPPUKU_COMPONENT", data, True)
321         # evil hack to figure out what is going on
322         debug_file = open(os.path.join(bb.data.getVar("TMPDIR", data, True),"..","seppuku-log"),"a")
323
324         if not seppuku_login(opener, login, user, passw):
325             bb.note("Login to bugzilla failed")
326             print >> debug_file, "Login to bugzilla failed"
327             return NotHandled
328         else:
329             print >> debug_file, "Logged into the box"
330
331         file = None
332         if name == "TaskFailed":
333             bugname = "%(package)s-%(pv)s-autobuild" % { "package" : bb.data.getVar("PN", data, True),
334                                                                "pv"      : bb.data.getVar("PV", data, True),
335                                                                }  
336             log_file = glob.glob("%s/log.%s.*" % (bb.data.getVar('T', event.data, True), event.task))
337             text     = "The %s step in %s failed at %s for machine %s" % (e.task, bb.data.getVar("PN", data, True), bb.data.getVar('DATETIME', data, True), bb.data.getVar( 'MACHINE', data, True ) )
338             if len(log_file) != 0:
339                 print >> debug_file, "Adding log file %s" % log_file[0]
340                 file = open(log_file[0], 'r')
341             else:
342                 print >> debug_file, "No log file found for the glob"
343         else:
344             print >> debug_file, "Unknown name '%s'" % name
345             assert False
346
347         (bug_open, bug_number) = seppuku_find_bug_report(debug_file, opener, query, product, component, bugname)
348         print >> debug_file, "Bug is open: %s and bug number: %s" % (bug_open, bug_number)
349
350         # The bug is present and still open, attach an error log
351         if bug_number and bug_open:
352             print >> debug_file, "The bug is known as '%s'" % bug_number
353             if file:
354                 if not seppuku_create_attachment(debug_file, poster, attach, product, component, bug_number, text, file):
355                      print >> debug_file, "Failed to attach the build log for bug #%s" % bug_number
356                 else:
357                      print >> debug_file, "Created an attachment for '%s' '%s' '%s'" % (product, component, bug_number)
358             else:
359                      print >> debug_file, "Not trying to create an attachment for bug #%s" % bug_number
360             return NotHandled
361
362         if bug_number and not bug_open:
363             if not seppuku_reopen_bug(poster, reopen, product, component, bug_number, bugname, text):
364                 print >> debug_file, "Failed to reopen the bug #%s" % bug_number
365             else:
366                 print >> debug_file, "Reopened the bug #%s" % bug_number
367         else:   
368             bug_number = seppuku_file_bug(poster, newbug, product, component, bugname, text)
369             if not bug_number:
370                 print >> debug_file, "Couldn't acquire a new bug_numer, filing a bugreport failed"
371             else:
372                 print >> debug_file, "The new bug_number: '%s'" % bug_number
373
374         if bug_number and file:
375             if not seppuku_create_attachment(debug_file, poster, attach, product, component, bug_number, text, file):
376                 print >> debug_file, "Failed to attach the build log for bug #%s" % bug_number
377             else:
378                 print >> debug_file, "Created an attachment for '%s' '%s' '%s'" % (product, component, bug_number)
379         else:
380             print >> debug_file, "Not trying to create an attachment for bug #%s" % bug_number
381
382     return NotHandled
383 }