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