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