merge of 'b0e8a7b18bc538b2926b839a1a3adad6234a53e0'
[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         key = urllib.unquote(key)
19         value = urllib.unquote(value)
20         param[key] = value
21
22     return (uri,param)
23
24
25
26 def seppuku_login(opener, login, user, password):
27     """
28     We need to post to query.cgi with the parameters
29     Bugzilla_login and Bugzilla_password and will scan
30     the resulting page then
31
32     @param opened = cookie enabled urllib2 opener
33     @param login = http://bugzilla.openmoko.org/cgi-bin/bugzilla/query.cgi?
34     @param user  = Your username
35     @param password  = Your password
36     """
37     import urllib
38     param = urllib.urlencode( {"GoAheadAndLogIn" : 1, "Bugzilla_login" : user, "Bugzilla_password" : password } )
39     result = opener.open(login + param)
40
41     if result.code != 200:
42         return False
43     txt = result.read()
44     if not '<a href="relogin.cgi">Log&nbsp;out</a>' in txt:
45         return False
46
47     return True
48
49 def seppuku_find_bug_report_old():
50     from HTMLParser import HTMLParser
51
52     class BugQueryExtractor(HTMLParser):
53         STATE_NONE             = 0
54         STATE_FOUND_TR         = 1
55         STATE_FOUND_NUMBER     = 2
56         STATE_FOUND_PRIO       = 3
57         STATE_FOUND_PRIO2      = 4
58         STATE_FOUND_NAME       = 5
59         STATE_FOUND_PLATFORM   = 6
60         STATE_FOUND_STATUS     = 7
61         STATE_FOUND_WHATEVER   = 8 # I don't know this field
62         STATE_FOUND_DESCRIPTION =9
63
64         def __init__(self):
65             HTMLParser.__init__(self)
66             self.state = self.STATE_NONE
67             self.bugs = []
68
69         def handle_starttag(self, tag, attr):
70             if self.state == self.STATE_NONE and tag.lower() == "tr":
71                 if len(attr) == 1 and attr[0] == ('class', 'bz_normal bz_P2 '):
72                     self.state = self.STATE_FOUND_TR
73             elif self.state == self.STATE_FOUND_TR and tag.lower() == "td":
74                 self.state += 1
75
76         def handle_endtag(self, tag):
77             if tag.lower() == "tr":
78                 if self.state != self.STATE_NONE:
79                     self.bugs.append( (self.bug,self.status) )
80                 self.state = self.STATE_NONE
81             if self.state > 1 and tag.lower() == "td":
82                 self.state += 1
83
84         def handle_data(self,data):
85             data = data.strip()
86
87             # skip garbage
88             if len(data) == 0:
89                 return
90
91             if self.state == self.STATE_FOUND_NUMBER:
92                 self.bug = data
93             elif self.state == self.STATE_FOUND_STATUS:
94                 self.status = data
95
96         def result(self):
97             return self.bugs
98
99     return BugQueryExtractor()
100
101
102
103 def seppuku_find_bug_report(opener, query, product, component, bugname):
104     """
105     Find a bug report with the sane name and return the bug id
106     and the status.
107
108     @param opener = urllib2 opener
109     @param query  = e.g. https://bugzilla.openmoko.org/cgi-bin/bugzilla/query.cgi?
110     @param product = search for this product
111     @param component = search for this component
112     @param bugname = the bug to search for
113
114     https://bugzilla.openmoko.org/cgi-bin/bugzilla/buglist.cgi?short_desc_type=substring&short_desc=manual+test+bug&product=OpenMoko&emailreporter2=1&emailtype2=substring&email2=freyther%40yahoo.com
115     but it does not support ctype=csv...
116     """
117     result = opener.open("%(query)s?product=%(product)s&component=%(component)s&short_desc_type=substring&short_desc=%(bugname)s" % vars())
118     if result.code != 200:
119         raise "Can not query the bugzilla at all"
120     txt = result.read()
121     scanner = seppuku_find_bug_report_old()
122     scanner.feed(txt)
123     if len(scanner.result()) == 0:
124         return (False,None)
125     else: # silently pick the first result
126         (number,status) = scanner.result()[0]
127         return (not status in ["CLOS", "RESO", "VERI"],number)
128
129 def seppuku_reopen_bug(poster, file, product, component, bug_number, bugname, text):
130     """
131     Reopen a bug report and append to the comment
132
133     Same as with opening a new report, some bits need to be inside the url
134
135     http://bugzilla.openmoko.org/cgi-bin/bugzilla/process_bug.cgi?id=239&bug_file_loc=http%3A%2F%2F&version=2007&longdesclength=2&product=OpenMoko&component=autobuilds&comment=bla&priority=P2&bug_severity=normal&op_sys=Linux&rep_platform=Neo1973&knob=reopen&target_milestone=Phase+0&short_desc=foo
136     """
137
138     import urllib2
139     (uri, param) = seppuku_spliturl( file )
140
141     # Prepare the post
142     param["product"]        = product
143     param["component"]      = component
144     param["longdesclength"] = 2
145     param["short_desc"]     = bugname
146     param["knob"]           = "reopen"
147     param["id"]             = bug_number
148     param["comment"]        = text
149
150     try:
151         result = poster.open( uri, param )
152     except urllib2.HTTPError, e:
153         print e.geturl()
154         print e.info()
155         return False
156     except Exception, e:
157         print e
158         return False
159
160     if result.code != 200:
161         return False
162     else:
163         return True
164
165 def seppuku_file_bug(poster, file, product, component, bugname, text):
166     """
167     Create a completely new bug report
168
169
170     http://bugzilla.openmoko.org/cgi-bin/bugzilla/post_bug.cgi?bug_file_loc=http%3A%2F%2F&version=2007&product=OpenMoko&component=autobuilds&short_desc=foo&comment=bla&priority=P2&bug_severity=normal&op_sys=Linux&rep_platform=Neo1973
171
172     You are forced to add some default values to the bugzilla query and stop with '&'
173
174     @param opener  urllib2 opener
175     @param file    The url used to file a bug report
176     @param product Product
177     @param component Component
178     @param bugname  Name of the to be created bug
179     @param text Text
180     """
181
182     import urllib2
183     (uri, param) = seppuku_spliturl( file )
184     param["product"]    = product
185     param["component"]  = component
186     param["short_desc"] = bugname
187     param["comment"]    = text
188
189     try:
190         result = poster.open( uri, param )
191     except urllib2.HTTPError, e:
192         print e.geturl()
193         print e.info()
194         return False
195     except Exception, e:
196         print e
197         return False
198
199     if result.code != 200:
200         return False
201     else:
202         return True
203
204 def seppuku_create_attachment(poster, attach_query, product, component, bug_number, text, file):
205     """
206
207     Create a new attachment for the failed report
208     """
209
210     if not bug_number:
211         import bb
212         bb.note("Can't create an attachment, the bug is not present")
213         return False
214
215     import urllib2
216     param = { "bugid" : bug_number, "action" : "insert", "data" : file, "description" : "Build log", "ispatch" : "0", "contenttypemethod" : "list", "contenttypeselection" : "text/plain", "comment" : text }
217
218     try:
219         result = poster.open( attach_query, param )
220     except urllib2.HTTPError, e:
221         print e.geturl()
222         print e.info()
223         return False
224     except Exception, e:
225         print e
226         return False
227
228     print result.read()
229     if result.code != 200:
230         return False
231     else:
232         return True
233
234
235 addhandler seppuku_eventhandler
236 python seppuku_eventhandler() {
237     """
238     Report task failures to the bugzilla
239     and succeeded builds to the box
240     """
241     from bb.event import NotHandled, getName
242     from bb import data, mkdirhier, build
243     import bb, os, glob
244
245     # Try to load our exotic libraries
246     try:
247         import MultipartPostHandler
248     except:
249         bb.note("You need to put the MultipartPostHandler into your PYTHONPATH. Download it from http://pipe.scs.fsu.edu/PostHandler/MultipartPostHandler.py")
250         return NotHandled
251
252     try:
253         import urllib2, cookielib
254     except:
255         bb.note("Failed to import the cookielib and urllib2, make sure to use python2.4")
256         return NotHandled
257
258     event = e
259     data = e.data
260     name = getName(event)
261     if name == "PkgFailed":
262         if not bb.data.getVar('SEPPUKU_AUTOBUILD', data, True) == "0":
263             build.exec_task('do_clean', data)
264     elif name == "TaskFailed" or name == "NoProvider":
265         cj = cookielib.CookieJar()
266         opener  = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
267         poster  = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj),MultipartPostHandler.MultipartPostHandler)
268         login   = bb.data.getVar("SEPPUKU_LOGIN", data, True)
269         query   = bb.data.getVar("SEPPUKU_QUERY", data, True)
270         newbug  = bb.data.getVar("SEPPUKU_NEWREPORT",  data, True)
271         reopen  = bb.data.getVar("SEPPUKU_ADDCOMMENT",  data, True)
272         attach  = bb.data.getVar("SEPPUKU_ATTACHMENT", data, True)
273         user    = bb.data.getVar("SEPPUKU_USER",  data, True)
274         passw   = bb.data.getVar("SEPPUKU_PASS",  data, True)
275         product = bb.data.getVar("SEPPUKU_PRODUCT", data, True)
276         component = bb.data.getVar("SEPPUKU_COMPONENT", data, True)
277
278         if not seppuku_login(opener, login, user, passw):
279             bb.note("Login to bugzilla failed")
280             return NotHandled
281         else:
282             print "Logged into the box"
283
284         if name == "TaskFailed":
285             bugname = "%(package)s-%(pv)s-%(pr)s-%(task)s" % { "package" : bb.data.getVar("PN", data, True),
286                                                                "pv"      : bb.data.getVar("PV", data, True),
287                                                                "pr"      : bb.data.getVar("PR", data, True),
288                                                                "task"    : e.task }
289             log_file = glob.glob("%s/log.%s.*" % (bb.data.getVar('T', event.data, True), event.task))
290             text     = "The package failed to build at %s" % bb.data.getVar('DATETIME', data, True) 
291             file     = open(log_file[0], 'r')
292         elif name == "NoProvider":
293             bugname = "noprovider for %s runtime: %s" % (event.getItem, event.getisRuntime)
294             text    = "Please fix it"
295             file    = None
296         else:
297             assert False
298
299         (bug_open, bug_number) = seppuku_find_bug_report(opener, query, product, component, bugname)
300
301         bb.note("Bug is open: %s and bug number: %s" % (bug_open, bug_number))
302
303         # The bug is present and still open, no need to attach an error log
304         if bug_number and bug_open:
305             bb.note("The bug is known as '%s'" % bug_number)
306             return NotHandled
307
308         if bug_number and not bug_open:
309             if not seppuku_reopen_bug(poster, reopen, product, component, bug_number, bugname, text):
310                 bb.note("Failed to reopen the bug report")
311         elif not seppuku_file_bug(poster, newbug, product, component, bugname, text):
312             bb.note("Filing a bugreport failed")
313         else:
314             # get the new bug number and create an attachment
315             (bug_open, bug_number) = seppuku_find_bug_report(opener, query, product, component, bugname)
316
317         if file:
318             if not seppuku_create_attachment(poster, attach, product, component, bug_number, text, file):
319                 bb.note("Failed to attach the build log")
320
321     return NotHandled
322 }