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