merge of '99919cb8b538ef795301795c0abf7a23fa66d751'
[openembedded.git] / classes / tinderclient.bbclass
1 def tinder_http_post(server, selector, content_type, body):
2     import httplib
3     # now post it
4     for i in range(0,5):
5        try:
6            h = httplib.HTTP(server)
7            h.putrequest('POST', selector)
8            h.putheader('content-type', content_type)
9            h.putheader('content-length', str(len(body)))
10            h.endheaders()
11            h.send(body)
12            errcode, errmsg, headers = h.getreply()
13            #print errcode, errmsg, headers
14            return (errcode,errmsg, headers, h.file)
15        except:
16            print "Error sending the report!"
17            # try again
18            pass
19
20     # return some garbage
21     return (-1, "unknown", "unknown", None)
22
23 def tinder_form_data(bound, dict, log):
24     output = []
25     # for each key in the dictionary
26     for name in dict:
27         output.append( "--" + bound )
28         output.append( 'Content-Disposition: form-data; name="%s"' % name )
29         output.append( "" )
30         output.append( dict[name] )
31     if log:
32         output.append( "--" + bound )
33         output.append( 'Content-Disposition: form-data; name="log"; filename="log.txt"' )
34         output.append( '' )
35         output.append( log )
36     output.append( '--' + bound + '--' )
37     output.append( '' )
38
39     return "\r\n".join(output)
40
41 def tinder_time_string():
42     """
43     Return the time as GMT
44     """
45     return ""
46
47 def tinder_format_http_post(d,status,log):
48     """
49     Format the Tinderbox HTTP post with the data needed
50     for the tinderbox to be happy.
51     """
52
53     from bb import data, build
54     import os,random
55
56     # the variables we will need to send on this form post
57     variables =  {
58         "tree"         : data.getVar('TINDER_TREE',    d, True),
59         "machine_name" : data.getVar('TINDER_MACHINE', d, True),
60         "os"           : os.uname()[0],
61         "os_version"   : os.uname()[2],
62         "compiler"     : "gcc",
63         "clobber"      : data.getVar('TINDER_CLOBBER', d, True)
64     }
65
66     # optionally add the status
67     if status:
68         variables["status"] = str(status)
69
70     # try to load the machine id
71     # we only need on build_status.pl but sending it
72     # always does not hurt
73     try:
74         f = file(data.getVar('TMPDIR',d,True)+'/tinder-machine.id', 'r')
75         id = f.read()
76         variables['machine_id'] = id
77     except:
78         pass
79
80     # the boundary we will need
81     boundary = "----------------------------------%d" % int(random.random()*1000000000000)
82
83     # now format the body
84     body = tinder_form_data( boundary, variables, log )
85
86     return ("multipart/form-data; boundary=%s" % boundary),body
87
88
89 def tinder_build_start(d):
90     """
91     Inform the tinderbox that a build is starting. We do this
92     by posting our name and tree to the build_start.pl script
93     on the server.
94     """
95     from bb import data
96
97     # get the body and type
98     content_type, body = tinder_format_http_post(d,None,None)
99     server = data.getVar('TINDER_HOST', d, True )
100     url    = data.getVar('TINDER_URL',  d, True )
101
102     selector = url + "/xml/build_start.pl"
103
104     #print "selector %s and url %s" % (selector, url)
105
106     # now post it
107     errcode, errmsg, headers, h_file = tinder_http_post(server,selector,content_type, body)
108     #print errcode, errmsg, headers
109     report = h_file.read()
110
111     # now let us find the machine id that was assigned to us
112     search = "<machine id='"
113     report = report[report.find(search)+len(search):]
114     report = report[0:report.find("'")]
115
116     import bb
117     bb.note("Machine ID assigned by tinderbox: %s" % report )
118
119     # now we will need to save the machine number
120     # we will override any previous numbers
121     f = file(data.getVar('TMPDIR', d, True)+"/tinder-machine.id", 'w')
122     f.write(report)
123
124
125 def tinder_send_http(d, status, _log):
126     """
127     Send this log as build status
128     """
129     from bb import data
130
131
132     # get the body and type
133     server = data.getVar('TINDER_HOST', d, True )
134     url    = data.getVar('TINDER_URL',  d, True )
135
136     selector = url + "/xml/build_status.pl"
137
138     # now post it - in chunks of 10.000 charachters
139     new_log = _log
140     while len(new_log) > 0:
141         content_type, body = tinder_format_http_post(d,status,new_log[0:18000])
142         errcode, errmsg, headers, h_file = tinder_http_post(server,selector,content_type, body)
143         #print errcode, errmsg, headers
144         #print h.file.read()
145         new_log = new_log[18000:]
146
147
148 def tinder_print_info(d):
149     """
150     Print the TinderBox Info
151         Including informations of the BaseSystem and the Tree
152         we use.
153     """
154
155     from   bb import data
156     import os
157     # get the local vars
158
159     time    = tinder_time_string()
160     ops     = os.uname()[0]
161     version = os.uname()[2]
162     url     = data.getVar( 'TINDER_URL' , d, True )
163     tree    = data.getVar( 'TINDER_TREE', d, True )
164     branch  = data.getVar( 'TINDER_BRANCH', d, True )
165     srcdate = data.getVar( 'SRCDATE', d, True )
166     machine = data.getVar( 'MACHINE', d, True )
167     distro  = data.getVar( 'DISTRO',  d, True )
168     bbfiles = data.getVar( 'BBFILES', d, True )
169     tarch   = data.getVar( 'TARGET_ARCH', d, True )
170     fpu     = data.getVar( 'TARGET_FPU', d, True )
171     oerev   = data.getVar( 'OE_REVISION', d, True ) or "unknown"
172
173     # there is a bug with tipple quoted strings
174     # i will work around but will fix the original
175     # bug as well
176     output = []
177     output.append("== Tinderbox Info" )
178     output.append("Time: %(time)s" )
179     output.append("OS: %(ops)s" )
180     output.append("%(version)s" )
181     output.append("Compiler: gcc" )
182     output.append("Tinderbox Client: 0.1" )
183     output.append("Tinderbox Client Last Modified: yesterday" )
184     output.append("Tinderbox Protocol: 0.1" )
185     output.append("URL: %(url)s" )
186     output.append("Tree: %(tree)s" )
187     output.append("Config:" )
188     output.append("branch = '%(branch)s'" )
189     output.append("TARGET_ARCH = '%(tarch)s'" )
190     output.append("TARGET_FPU = '%(fpu)s'" )
191     output.append("SRCDATE = '%(srcdate)s'" )
192     output.append("MACHINE = '%(machine)s'" )
193     output.append("DISTRO = '%(distro)s'" )
194     output.append("BBFILES = '%(bbfiles)s'" )
195     output.append("OEREV = '%(oerev)s'" )
196     output.append("== End Tinderbox Client Info" )
197
198     # now create the real output
199     return "\n".join(output) % vars()
200
201
202 def tinder_print_env():
203     """
204     Print the environment variables of this build
205     """
206     from bb import data
207     import os
208
209     time_start = tinder_time_string()
210     time_end   = tinder_time_string()
211
212     # build the environment
213     env = ""
214     for var in os.environ:
215         env += "%s=%s\n" % (var, os.environ[var])
216
217     output = []
218     output.append( "---> TINDERBOX RUNNING env %(time_start)s" )
219     output.append( env )
220     output.append( "<--- TINDERBOX FINISHED (SUCCESS) %(time_end)s" )
221
222     return "\n".join(output) % vars()
223
224 def tinder_tinder_start(d, event):
225     """
226     PRINT the configuration of this build
227     """
228
229     time_start = tinder_time_string()
230     config = tinder_print_info(d)
231     #env    = tinder_print_env()
232     time_end   = tinder_time_string()
233     packages = " ".join( event.getPkgs() ) 
234
235     output = []
236     output.append( "---> TINDERBOX PRINTING CONFIGURATION %(time_start)s" )
237     output.append( config )
238     #output.append( env    )
239     output.append( "<--- TINDERBOX FINISHED PRINTING CONFIGURATION %(time_end)s" )
240     output.append( "---> TINDERBOX BUILDING '%(packages)s'" )
241     output.append( "<--- TINDERBOX STARTING BUILD NOW" )
242
243     output.append( "" )
244
245     return "\n".join(output) % vars()
246
247 def tinder_do_tinder_report(event):
248     """
249     Report to the tinderbox:
250         On the BuildStart we will inform the box directly
251         On the other events we will write to the TINDER_LOG and
252         when the Task is finished we will send the report.
253
254     The above is not yet fully implemented. Currently we send
255     information immediately. The caching/queuing needs to be
256     implemented. Also sending more or less information is not
257     implemented yet.
258
259     We have two temporary files stored in the TMP directory. One file
260     contains the assigned machine id for the tinderclient. This id gets
261     assigned when we connect the box and start the build process the second
262     file is used to workaround an EventHandler limitation. If BitBake is ran
263     with the continue option we want the Build to fail even if we get the
264     BuildCompleted Event. In this case we have to look up the status and
265     send it instead of 100/success.
266     """
267     from bb.event import getName
268     from bb import data, mkdirhier, build
269     import os, glob
270
271     # variables
272     name = getName(event)
273     log  = ""
274     status = 1
275     # Check what we need to do Build* shows we start or are done
276     if name == "BuildStarted":
277         tinder_build_start(event.data)
278         log = tinder_tinder_start(event.data,event)
279
280         try:
281             # truncate the tinder log file
282             f = file(data.getVar('TINDER_LOG', event.data, True), 'w')
283             f.write("")
284             f.close()
285         except:
286             pass
287
288         try:
289             # write a status to the file. This is needed for the -k option
290             # of BitBake
291             g = file(data.getVar('TMPDIR', event.data, True)+"/tinder-status", 'w')
292             g.write("")
293             g.close()
294         except IOError:
295             pass
296
297     # Append the Task-Log (compile,configure...) to the log file
298     # we will send to the server
299     if name == "TaskSucceeded" or name == "TaskFailed":
300         log_file = glob.glob("%s/log.%s.*" % (data.getVar('T', event.data, True), event.task))
301
302         if len(log_file) != 0:
303             to_file  = data.getVar('TINDER_LOG', event.data, True)
304             log     += "".join(open(log_file[0], 'r').readlines())
305
306     # set the right 'HEADER'/Summary for the TinderBox
307     if name == "TaskStarted":
308         log += "---> TINDERBOX Task %s started\n" % event.task
309     elif name == "TaskSucceeded":
310         log += "<--- TINDERBOX Task %s done (SUCCESS)\n" % event.task
311     elif name == "TaskFailed":
312         log += "<--- TINDERBOX Task %s failed (FAILURE)\n" % event.task
313     elif name == "PkgStarted":
314         log += "---> TINDERBOX Package %s started\n" % data.getVar('PF', event.data, True)
315     elif name == "PkgSucceeded":
316         log += "<--- TINDERBOX Package %s done (SUCCESS)\n" % data.getVar('PF', event.data, True)
317     elif name == "PkgFailed":
318         if not data.getVar('TINDER_AUTOBUILD', event.data, True) == "0":
319             build.exec_task('do_clean', event.data)
320         log += "<--- TINDERBOX Package %s failed (FAILURE)\n" % data.getVar('PF', event.data, True)
321         status = 200
322         # remember the failure for the -k case
323         h = file(data.getVar('TMPDIR', event.data, True)+"/tinder-status", 'w')
324         h.write("200")
325     elif name == "BuildCompleted":
326         log += "Build Completed\n"
327         status = 100
328         # Check if we have a old status...
329         try:
330             h = file(data.getVar('TMPDIR',event.data,True)+'/tinder-status', 'r')
331             status = int(h.read())
332         except:
333             pass
334
335     elif name == "MultipleProviders":
336         log += "---> TINDERBOX Multiple Providers\n"
337         log += "multiple providers are available (%s);\n" % ", ".join(event.getCandidates())
338         log += "consider defining PREFERRED_PROVIDER_%s\n" % event.getItem()
339         log += "is runtime: %d\n" % event.isRuntime()
340         log += "<--- TINDERBOX Multiple Providers\n"
341     elif name == "NoProvider":
342         log += "Error: No Provider for: %s\n" % event.getItem()
343         log += "Error:Was Runtime: %d\n" % event.isRuntime()
344         status = 200
345         # remember the failure for the -k case
346         h = file(data.getVar('TMPDIR', event.data, True)+"/tinder-status", 'w')
347         h.write("200")
348
349     # now post the log
350     if len(log) == 0:
351         return
352
353     # for now we will use the http post method as it is the only one
354     log_post_method = tinder_send_http
355     log_post_method(event.data, status, log)
356
357
358 # we want to be an event handler
359 addhandler tinderclient_eventhandler
360 python tinderclient_eventhandler() {
361     from bb import note, error, data
362     from bb.event import NotHandled
363     do_tinder_report = data.getVar('TINDER_REPORT', e.data, True)
364     if do_tinder_report and do_tinder_report == "1":
365         tinder_do_tinder_report(e)
366
367     return NotHandled
368 }