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