]> git.pld-linux.org Git - projects/pld-builder.new.git/blob - PLD_Builder/rpm_builder.py
Clear tmp earlier.
[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("/bin/kill --verbose -9 -1", logfile = b.logfile)
186
187     b.log_line("cleaning up /tmp")
188     chroot.run("rm -rf /tmp/B.*", logfile = b.logfile)
189
190     fetch_src(r, b)
191     b.log_line("installing srpm: %s" % b.src_rpm)
192     res = chroot.run("""
193         set -ex;
194         install -d %(topdir)s/{BUILD,RPMS};
195         LC_ALL=en_US.UTF-8 rpm -qp --changelog %(src_rpm)s;
196         rpm -Uhv --nodeps %(rpmdefs)s %(src_rpm)s;
197         rm -f %(src_rpm)s;
198     """ % {
199         'topdir' : b.get_topdir(),
200         'rpmdefs' : b.rpmbuild_opts(),
201         'src_rpm' : b.src_rpm
202     }, logfile = b.logfile)
203     b.files = []
204
205     tmpdir = b.tmpdir()
206     if res:
207         b.log_line("error: installing src rpm failed")
208         res = "FAIL_SRPM_INSTALL"
209     else:
210         prepare_env()
211         chroot.run("set -x; install -m 700 -d %s" % tmpdir, logfile=b.logfile)
212         b.default_target(config.arch)
213         # check for build arch before filling BR
214         cmd = "set -ex; TMPDIR=%(tmpdir)s exec nice -n %(nice)s " \
215             "rpmbuild -bp --short-circuit --nodeps %(rpmdefs)s --define 'prep exit 0' %(topdir)s/%(spec)s" % {
216             'tmpdir': tmpdir,
217             'nice' : config.nice,
218             'topdir' : b.get_topdir(),
219             'rpmdefs' : b.rpmbuild_opts(),
220             'spec': b.spec,
221         }
222         res = chroot.run(cmd, logfile = b.logfile)
223         if res:
224             res = "UNSUPP"
225             b.log_line("error: build arch check (%s) failed" % cmd)
226
227         if not res:
228             if ("no-install-br" not in r.flags) and not install.uninstall_self_conflict(b):
229                 res = "FAIL_DEPS_UNINSTALL"
230             if ("no-install-br" not in r.flags) and not install.install_br(r, b):
231                 res = "FAIL_DEPS_INSTALL"
232             if not res:
233                 max_jobs = max(min(int(os.sysconf('SC_NPROCESSORS_ONLN') + 1), config.max_jobs), 1)
234                 if r.max_jobs > 0:
235                     max_jobs = max(min(config.max_jobs, r.max_jobs), 1)
236                 cmd = "set -ex; : build-id: %(r_id)s; TMPDIR=%(tmpdir)s exec nice -n %(nice)s " \
237                     "rpmbuild -bb --define '_smp_mflags -j%(max_jobs)d' --define '_make_opts -Otarget' --define '_pld_builder 1' %(rpmdefs)s %(topdir)s/%(spec)s" % {
238                     'r_id' : r.id,
239                     'tmpdir': tmpdir,
240                     'nice' : config.nice,
241                     'rpmdefs' : b.rpmbuild_opts(),
242                     'topdir' : b.get_topdir(),
243                     'max_jobs' : max_jobs,
244                     'spec': b.spec,
245                 }
246                 b.log_line("building RPM using: %s" % cmd)
247                 begin_time = time.time()
248                 res = chroot.run(cmd, logfile = b.logfile)
249                 end_time = time.time()
250                 b.log_line("ended at: %s, done in %s" % (time.asctime(), datetime.timedelta(0, end_time - begin_time)))
251                 if res:
252                     res = "FAIL"
253                 files = util.collect_files(b.logfile, basedir = b.get_topdir())
254                 if len(files) > 0:
255                     r.chroot_files.extend(files)
256                 else:
257                     b.log_line("error: No files produced.")
258                     last_section = util.find_last_section(b.logfile)
259                     if last_section == None:
260                         res = "FAIL"
261                     else:
262                         res = "FAIL_%s" % last_section.upper()
263                 b.files = files
264
265     # cleanup tmp and build files
266     chroot.run("""
267         set -ex;
268         chmod -R u+rwX %(topdir)s/BUILD;
269         rm -rf %(topdir)s/{tmp,BUILD}
270     """ % {
271         'topdir' : b.get_topdir(),
272     }, logfile = b.logfile)
273
274     def ll(l):
275         util.append_to(b.logfile, l)
276
277     if b.files != []:
278         rpm_cache_dir = config.rpm_cache_dir
279         if "test-build" not in r.flags:
280             # NOTE: copying to cache dir doesn't mean that build failed, so ignore result
281             b.log_line("copy rpm files to cache_dir: %s" % rpm_cache_dir)
282             chroot.run(
283                     "cp -f %s %s && poldek --mo=nodiff --mkidxz -s %s/" % \
284                         (string.join(b.files), rpm_cache_dir, rpm_cache_dir),
285                      logfile = b.logfile, user = "root"
286             )
287         else:
288             ll("test-build: not copying to " + rpm_cache_dir)
289         ll("Begin-PLD-Builder-Info")
290         if "upgrade" in r.flags:
291             b.upgraded = install.upgrade_from_batch(r, b)
292         else:
293             ll("not upgrading")
294         ll("End-PLD-Builder-Info")
295
296     for f in b.files:
297         local = r.tmp_dir + os.path.basename(f)
298         chroot.cp(f, outfile = local, rm = True)
299         ftp.add(local)
300
301     # cleanup all remains from this build
302     chroot.run("""
303         set -ex;
304         rm -rf %(topdir)s;
305     """ % {
306         'topdir' : b.get_topdir(),
307     }, logfile = b.logfile)
308
309     def uploadinfo(b):
310         c="file:SRPMS:%s\n" % b.src_rpm
311         for f in b.files:
312             c=c + "file:ARCH:%s\n" % os.path.basename(f)
313         c=c + "END\n"
314         return c
315
316     if config.gen_upinfo and b.files != [] and 'test-build' not in r.flags:
317         fname = r.tmp_dir + b.src_rpm + ".uploadinfo"
318         f = open(fname, "w")
319         f.write(uploadinfo(b))
320         f.close()
321         ftp.add(fname, "uploadinfo")
322
323     status.pop()
324
325     return res
326
327 def handle_request(r):
328     ftp.init(r)
329     buildlogs.init(r)
330     build.build_all(r, build_rpm)
331     report.send_report(r, is_src = False)
332     ftp.flush()
333     notify.send(r)
334
335 def check_load():
336     do_exit = 0
337     try:
338         f = open("/proc/loadavg")
339         if float(string.split(f.readline())[2]) > config.max_load:
340             do_exit = 1
341     except:
342         pass
343     if do_exit:
344         sys.exit(0)
345
346 def main_for(builder):
347     msg = ""
348
349     init_conf(builder)
350
351     q = B_Queue(path.queue_file + "-" + config.builder)
352     q.lock(0)
353     q.read()
354     if q.requests == []:
355         q.unlock()
356         return
357     req = pick_request(q)
358     q.unlock()
359
360     # high priority tasks have priority < 0, normal tasks >= 0
361     if req.priority >= 0:
362
363         # allow only one build in given builder at once
364         if not lock.lock("building-rpm-for-%s" % config.builder, non_block = 1):
365             return
366         # don't kill server
367         check_load()
368         # not more then job_slots builds at once
369         locked = 0
370         for slot in range(config.job_slots):
371             if lock.lock("building-rpm-slot-%d" % slot, non_block = 1):
372                 locked = 1
373                 break
374         if not locked:
375             return
376
377         # record fact that we got lock for this builder, load balancer
378         # will use it for fair-queuing
379         l = lock.lock("got-lock")
380         f = open(path.got_lock_file, "a")
381         f.write(config.builder + "\n")
382         f.close()
383         l.close()
384     else:
385         # be able to avoid locking with very low priority
386         if req.priority > -1000:
387             # don't kill server
388             check_load()
389             # allow only one build in given builder at once
390             if not lock.lock("building-high-priority-rpm-for-%s" % config.builder, non_block = 1):
391                 return
392
393         msg = "HIGH PRIORITY: "
394
395     msg += "handling request %s (%d) for %s from %s, priority %s" \
396             % (req.id, req.no, config.builder, req.requester, req.priority)
397     log.notice(msg)
398     status.push(msg)
399     handle_request(req)
400     status.pop()
401
402     def otherreqs(r):
403         if r.no==req.no:
404             return False
405         else:
406             return True
407
408     q = B_Queue(path.queue_file + "-" + config.builder)
409     q.lock(0)
410     q.read()
411     previouslen=len(q.requests)
412     q.requests=filter(otherreqs, q.requests)
413     if len(q.requests)<previouslen:
414         q.write()
415     q.unlock()
416
417 def main():
418     if len(sys.argv) < 2:
419         raise Exception, "fatal: need to have builder name as first arg"
420     return main_for(sys.argv[1])
421
422 if __name__ == '__main__':
423     loop.run_loop(main)
This page took 0.129504 seconds and 4 git commands to generate.