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