]> git.pld-linux.org Git - projects/pld-builder.new.git/blob - PLD_Builder/rpm_builder.py
Limit number for make -jX to number of cpus online + 1.
[projects/pld-builder.new.git] / PLD_Builder / rpm_builder.py
1 # vi: encoding=utf-8 ts=8 sts=4 sw=4 et
2
3 import sys
4 import os
5 import atexit
6 import time
7 import string
8 import urllib2
9
10 from config import config, init_conf
11 from bqueue import B_Queue
12 import lock
13 import util
14 import loop
15 import path
16 import status
17 import log
18 import chroot
19 import ftp
20 import buildlogs
21 import notify
22 import build
23 import report
24 import install
25
26 # *HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*
27 import socket
28
29 socket.myorigsocket=socket.socket
30
31 def mysocket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
32     s=socket.myorigsocket(family, type, proto)
33     s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
34     return s
35
36 socket.socket=mysocket
37 # *HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*
38
39 # this code is duplicated in srpm_builder, but we
40 # might want to handle some cases differently here
41 def pick_request(q):
42     def mycmp(r1, r2):
43         if r1.kind != 'group' or r2.kind != 'group':
44             raise Exception, "non-group requests"
45         pri_diff = cmp(r1.priority, r2.priority)
46         if pri_diff == 0:
47             return cmp(r1.time, r2.time)
48         else:
49             return pri_diff
50     q.requests.sort(mycmp)
51     ret = q.requests[0]
52     return ret
53
54 def check_skip_build(r, b):
55     src_url = config.control_url + "/srpms/" + r.id + "/skipme"
56     good  = False
57     b.log_line("checking if we should skip the build")
58     while not good:
59         try:
60             headers = { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }
61             req = urllib2.Request(url=src_url, headers=headers)
62             f = urllib2.urlopen(req)
63             good = True
64         except urllib2.HTTPError, error:
65                 return False
66         except urllib2.URLError, error:
67             # see errno.h
68             if error.errno in [-3, 60, 61, 110, 111]:
69                 b.log_line("unable to connect... trying again")
70                 continue
71             else:
72                 return False
73         f.close()
74         return True
75     return False
76
77 def fetch_src(r, b):
78     src_url = config.control_url + "/srpms/" + r.id + "/" + b.src_rpm
79     b.log_line("fetching %s" % src_url)
80     start = time.time()
81     good = False
82     while not good:
83         try:
84             headers = { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }
85             req = urllib2.Request(url=src_url, headers=headers)
86             f = urllib2.urlopen(req)
87             good = True
88         except urllib2.HTTPError, error:
89             # fail in a way where cron job will retry
90             msg = "unable to fetch file, http code: %d" % error.code
91             b.log_line(msg)
92             queue_time = time.time() - r.time
93             # 6 hours
94             if error.code != 404 or (queue_time >= 0 and queue_time < (6 * 60 * 60)):
95                 raise IOError, msg
96             else:
97                 msg = "in queue for more than 6 hours, download failing"
98                 b.log_line(msg)
99                 return False
100         except urllib2.URLError, error:
101             # see errno.h
102             if error.errno in [-3, 60, 61, 110, 111]:
103                 b.log_line("unable to connect... trying again")
104                 continue
105             else:
106                 raise
107
108     o = chroot.popen("cat > %s" % b.src_rpm, mode = "w")
109
110     try:
111         bytes = util.sendfile(f, o)
112     except IOError, e:
113         b.log_line("error: unable to write to `%s': %s" % (b.src_rpm, e))
114         raise
115
116     f.close()
117     o.close()
118     t = time.time() - start
119     if t == 0:
120         b.log_line("fetched %d bytes" % bytes)
121     else:
122         b.log_line("fetched %d bytes, %.1f K/s" % (bytes, bytes / 1024.0 / t))
123
124 def prepare_env():
125     chroot.run("""
126         test ! -f /proc/uptime && mount /proc 2>/dev/null
127         test ! -c /dev/full && rm -f /dev/full && mknod -m 666 /dev/full c 1 7
128         test ! -c /dev/null && rm -f /dev/null && mknod -m 666 /dev/null c 1 3
129         test ! -c /dev/random && rm -f /dev/random && mknod -m 644 /dev/random c 1 8
130         test ! -c /dev/urandom && rm -f /dev/urandom && mknod -m 644 /dev/urandom c 1 9
131         test ! -c /dev/zero && rm -f /dev/zero && mknod -m 666 /dev/zero c 1 5
132
133         # make neccessary files readable for builder user
134         # TODO: see if they really aren't readable for builder
135         for db in Packages Name Basenames Providename Pubkeys; do
136             db=/var/lib/rpm/$db
137             test -f $db && chmod a+r $db
138         done
139
140         # try to limit network access for builder account
141         /bin/setfacl -m u:builder:--- /etc/resolv.conf
142     """, 'root')
143
144 def build_rpm(r, b):
145     status.push("building %s" % b.spec)
146     b.log_line("request from: %s" % r.requester)
147
148     if check_skip_build(r, b):
149         b.log_line("build skipped due to src builder request")
150         res = "SKIP_REQUESTED"
151         return res
152
153     b.log_line("started at: %s" % time.asctime())
154     fetch_src(r, b)
155     b.log_line("installing srpm: %s" % b.src_rpm)
156     res = chroot.run("""
157         install -d rpm/SPECS rpm/SOURCES
158         rpm -Uhv %s
159     """ % b.src_rpm, logfile = b.logfile)
160     chroot.run("rm -f %s" % b.src_rpm, logfile = b.logfile)
161     b.files = []
162     tmpdir = "/tmp/B." + b.b_id[0:6]
163     if res:
164         b.log_line("error: installing src rpm failed")
165         res = "FAIL_SRPM_INSTALL"
166     else:
167         prepare_env()
168         chroot.run("install -m 700 -d %s" % tmpdir)
169
170         b.default_target(config.arch)
171         rpmbuild_opt = "%s %s %s" % (b.target_string(), b.kernel_string(), b.bconds_string())
172         # check for build arch before filling BR
173         cmd = "cd rpm/SPECS; TMPDIR=%s exec nice -n %s rpmbuild -bp --short-circuit --nodeps --define 'prep exit 0' %s %s" % \
174             (tmpdir, config.nice, rpmbuild_opt, b.spec)
175         res = chroot.run(cmd, logfile = b.logfile)
176         if res:
177             res = "UNSUPP"
178             b.log_line("error: build arch check (%s) failed" % cmd)
179
180         if not res:
181             if ("no-install-br" not in r.flags) and not install.uninstall_self_conflict(b):
182                 res = "FAIL_DEPS_UNINSTALL"
183             if ("no-install-br" not in r.flags) and not install.install_br(r, b):
184                 res = "FAIL_DEPS_INSTALL"
185             if not res:
186                 max_jobs = max(min(int(os.sysconf('SC_NPROCESSORS_ONLN') + 1), config.max_jobs), 1)
187                 if r.max_jobs > 0:
188                     max_jobs = max(min(config.max_jobs, r.max_jobs), 1)
189                 cmd = "echo build-id: %s; cd rpm/SPECS; TMPDIR=%s exec nice -n %s rpmbuild -bb --define '_smp_mflags -j%d' %s %s" % \
190                             (r.id, tmpdir, config.nice, max_jobs, rpmbuild_opt, b.spec)
191                 b.log_line("building RPM using: %s" % cmd)
192                 begin_time = time.time()
193                 res = chroot.run(cmd, logfile = b.logfile)
194                 end_time = time.time()
195                 b.log_line("ended at: %s, done in %s" % (time.asctime(), time.strftime("%ddays %Hh %Mm %Ss", time.gmtime(end_time - begin_time))))
196                 if res:
197                     res = "FAIL"
198                 files = util.collect_files(b.logfile)
199                 if len(files) > 0:
200                     r.chroot_files.extend(files)
201                 else:
202                     b.log_line("error: No files produced.")
203                     last_section = util.find_last_section(b.logfile)
204                     if last_section == None:
205                         res = "FAIL"
206                     else:
207                         res = "FAIL_%s" % last_section.upper()
208                 b.files = files
209
210     chroot.run("rm -rf %s; cd rpm/SPECS; rpmbuild --nodeps --nobuild " \
211                          "--clean --rmspec --rmsource %s" % \
212                          (tmpdir, b.spec), logfile = b.logfile)
213     chroot.run("chmod -R u+rwX rpm/BUILD/*; rm -rf rpm/BUILD/*", logfile = b.logfile)
214
215     def ll(l):
216         util.append_to(b.logfile, l)
217  
218     if b.files != []:
219         rpm_cache_dir = config.rpm_cache_dir
220         if "test-build" not in r.flags:
221             # NOTE: copying to cache dir doesn't mean that build failed, so ignore result
222             b.log_line("copy rpm files to cache_dir: %s" % rpm_cache_dir)
223             chroot.run(
224                     "cp -f %s %s && poldek --mo=nodiff --mkidxz -s %s/" % \
225                         (string.join(b.files), rpm_cache_dir, rpm_cache_dir),
226                      logfile = b.logfile, user = "root"
227             )
228         else:
229             ll("test-build: not copying to " + rpm_cache_dir)
230         ll("Begin-PLD-Builder-Info")
231         if "upgrade" in r.flags:
232             b.upgraded = install.upgrade_from_batch(r, b)
233         else:
234             ll("not upgrading")
235         ll("End-PLD-Builder-Info")
236
237     for f in b.files:
238         local = r.tmp_dir + os.path.basename(f)
239         chroot.cp(f, outfile = local, rm = True)
240         ftp.add(local)
241
242     def uploadinfo(b):
243         c="file:SRPMS:%s\n" % b.src_rpm
244         for f in b.files:
245             c=c + "file:ARCH:%s\n" % os.path.basename(f)
246         c=c + "END\n"
247         return c
248
249     if config.gen_upinfo and b.files != [] and 'test-build' not in r.flags:
250         fname = r.tmp_dir + b.src_rpm + ".uploadinfo"
251         f = open(fname, "w")
252         f.write(uploadinfo(b))
253         f.close()
254         ftp.add(fname, "uploadinfo")
255
256     status.pop()
257
258     return res
259
260 def handle_request(r):
261     ftp.init(r)
262     buildlogs.init(r)
263     build.build_all(r, build_rpm)
264     report.send_report(r, is_src = False)
265     ftp.flush()
266     notify.send(r)
267
268 def check_load():
269     do_exit = 0
270     try:
271         f = open("/proc/loadavg")
272         if float(string.split(f.readline())[2]) > config.max_load:
273             do_exit = 1
274     except:
275         pass
276     if do_exit:
277         sys.exit(0)
278
279 def main_for(builder):
280     msg = ""
281
282     init_conf(builder)
283
284     q = B_Queue(path.queue_file + "-" + config.builder)
285     q.lock(0)
286     q.read()
287     if q.requests == []:
288         q.unlock()
289         return
290     req = pick_request(q)
291     q.unlock()
292
293     # high priority tasks have priority < 0, normal tasks >= 0
294     if req.priority >= 0:
295
296         # allow only one build in given builder at once
297         if not lock.lock("building-rpm-for-%s" % config.builder, non_block = 1):
298             return
299         # don't kill server
300         check_load()
301         # not more then job_slots builds at once
302         locked = 0
303         for slot in range(config.job_slots):
304             if lock.lock("building-rpm-slot-%d" % slot, non_block = 1):
305                 locked = 1
306                 break
307         if not locked:
308             return
309
310         # record fact that we got lock for this builder, load balancer
311         # will use it for fair-queuing
312         l = lock.lock("got-lock")
313         f = open(path.got_lock_file, "a")
314         f.write(config.builder + "\n")
315         f.close()
316         l.close()
317     else:
318         msg = "HIGH PRIORITY: "
319     
320     msg += "handling request %s (%d) for %s from %s, priority %s" \
321             % (req.id, req.no, config.builder, req.requester, req.priority)
322     log.notice(msg)
323     status.push(msg)
324     handle_request(req)
325     status.pop()
326
327     def otherreqs(r):
328         if r.no==req.no:
329             return False
330         else:
331             return True
332
333     q = B_Queue(path.queue_file + "-" + config.builder)
334     q.lock(0)
335     q.read()
336     previouslen=len(q.requests)
337     q.requests=filter(otherreqs, q.requests)
338     if len(q.requests)<previouslen:
339         q.write()
340     q.unlock()
341     
342 def main():
343     if len(sys.argv) < 2:
344         raise Exception, "fatal: need to have builder name as first arg"
345     return main_for(sys.argv[1])
346     
347 if __name__ == '__main__':
348     loop.run_loop(main)
This page took 0.051577 seconds and 4 git commands to generate.