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