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