merge of '16fca46de883fd9f3759f9964bfe144b42a54a2f'
[openembedded.git] / 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         return False
241
242     import urllib2
243     param = { "bugid" : bug_number, "action" : "insert", "data" : file, "description" : "Build log", "ispatch" : "0", "contenttypemethod" : "list", "contenttypeselection" : "text/plain", "comment" : text }
244
245     try:
246         result = poster.open( attach_query, param )
247     except urllib2.HTTPError, e:
248         print e.geturl()
249         print e.info()
250         return False
251     except Exception, e:
252         print e
253         return False
254
255     print >> debug, result.read()
256     if result.code != 200:
257         return False
258     else:
259         return True
260
261
262 addhandler seppuku_eventhandler
263 python seppuku_eventhandler() {
264     """
265     Report task failures to the bugzilla
266     and succeeded builds to the box
267     """
268     from bb.event import NotHandled, getName
269     from bb import data, mkdirhier, build
270     import bb, os, glob
271
272     event = e
273     data = e.data
274     name = getName(event)
275     if name == "MsgNote":
276        # avoid recursion
277        return NotHandled
278
279     # Try to load our exotic libraries
280     try:
281         import MultipartPostHandler
282     except:
283         bb.note("You need to put the MultipartPostHandler into your PYTHONPATH. Download it from http://pipe.scs.fsu.edu/PostHandler/MultipartPostHandler.py")
284         return NotHandled
285
286     try:
287         import urllib2, cookielib
288     except:
289         bb.note("Failed to import the cookielib and urllib2, make sure to use python2.4")
290         return NotHandled
291
292     if name == "PkgFailed":
293         if not bb.data.getVar('SEPPUKU_AUTOBUILD', data, True) == "0":
294             build.exec_task('do_clean', data)
295     elif name == "TaskFailed":
296         cj = cookielib.CookieJar()
297         opener  = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
298         poster  = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj),MultipartPostHandler.MultipartPostHandler)
299         login   = bb.data.getVar("SEPPUKU_LOGIN", data, True)
300         query   = bb.data.getVar("SEPPUKU_QUERY", data, True)
301         newbug  = bb.data.getVar("SEPPUKU_NEWREPORT",  data, True)
302         reopen  = bb.data.getVar("SEPPUKU_ADDCOMMENT",  data, True)
303         attach  = bb.data.getVar("SEPPUKU_ATTACHMENT", data, True)
304         user    = bb.data.getVar("SEPPUKU_USER",  data, True)
305         passw   = bb.data.getVar("SEPPUKU_PASS",  data, True)
306         product = bb.data.getVar("SEPPUKU_PRODUCT", data, True)
307         component = bb.data.getVar("SEPPUKU_COMPONENT", data, True)
308         # evil hack to figure out what is going on
309         debug_file = open(os.path.join(bb.data.getVar("TMPDIR", data, True),"..","seppuku-log"),"a")
310
311         if not seppuku_login(opener, login, user, passw):
312             bb.note("Login to bugzilla failed")
313             print >> debug_file, "Login to bugzilla failed"
314             return NotHandled
315         else:
316             print >> debug_file, "Logged into the box"
317
318         file = None
319         if name == "TaskFailed":
320             bugname = "%(package)s-%(pv)s-%(pr)s-%(task)s" % { "package" : bb.data.getVar("PN", data, True),
321                                                                "pv"      : bb.data.getVar("PV", data, True),
322                                                                "pr"      : bb.data.getVar("PR", data, True),
323                                                                "task"    : e.task }
324             log_file = glob.glob("%s/log.%s.*" % (bb.data.getVar('T', event.data, True), event.task))
325             text     = "The package failed to build at %s for machine %s" % (bb.data.getVar('DATETIME', data, True), bb.data.getVar( 'MACHINE', data, True ) )
326             if len(log_file) != 0:
327                 print >> debug_file, "Adding log file %s" % log_file[0]
328                 file = open(log_file[0], 'r')
329             else:
330                 print >> debug_file, "No log file found for the glob"
331         else:
332             print >> debug_file, "Unknown name '%s'" % name
333             assert False
334
335         (bug_open, bug_number) = seppuku_find_bug_report(debug_file, opener, query, product, component, bugname)
336         print >> debug_file, "Bug is open: %s and bug number: %s" % (bug_open, bug_number)
337
338         # The bug is present and still open, attach an error log
339         if bug_number and bug_open:
340             print >> debug_file, "The bug is known as '%s'" % bug_number
341             if file:
342                 if not seppuku_create_attachment(debug_file, poster, attach, product, component, bug_number, text, file):
343                      print >> debug_file, "Failed to attach the build log for bug #%s" % bug_number
344                 else:
345                      print >> debug_file, "Created an attachment for '%s' '%s' '%s'" % (product, component, bug_number)
346             else:
347                      print >> debug_file, "Not trying to create an attachment for bug #%s" % bug_number
348             return NotHandled
349
350         if bug_number and not bug_open:
351             if not seppuku_reopen_bug(poster, reopen, product, component, bug_number, bugname, text):
352                 print >> debug_file, "Failed to reopen the bug #%s" % bug_number
353             else:
354                 print >> debug_file, "Reopened the bug #%s" % bug_number
355         else:   
356             bug_number = seppuku_file_bug(poster, newbug, product, component, bugname, text)
357             if not bug_number:
358                 print >> debug_file, "Couldn't acquire a new bug_numer, filing a bugreport failed"
359             else:
360                 print >> debug_file, "The new bug_number: '%s'" % bug_number
361
362         if bug_number and file:
363             if not seppuku_create_attachment(debug_file, poster, attach, product, component, bug_number, text, file):
364                 print >> debug_file, "Failed to attach the build log for bug #%" % bug_number
365             else:
366                 print >> debug_file, "Created an attachment for '%s' '%s' '%s'" % (product, component, bug_number)
367         else:
368             print >> debug_file, "Not trying to create an attachment for bug #%" % bug_number
369
370     return NotHandled
371 }