]> git.pld-linux.org Git - projects/pld-builder.new.git/blob - PLD_Builder/rpm_builder.py
Make sure that you can delete all files (even with no write rights).
[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 string
8 import urllib2
9
10 from config import config, init_conf
11 from bqueue import B_Queue
12 import lock
13 import util
14 import loop
15 import path
16 import status
17 import log
18 import chroot
19 import ftp
20 import buildlogs
21 import notify
22 import build
23 import report
24 import install
25
26 # *HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*
27 import socket
28
29 socket.myorigsocket=socket.socket
30
31 def mysocket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
32     s=socket.myorigsocket(family, type, proto)
33     s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
34     return s
35
36 socket.socket=mysocket
37 # *HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*
38
39 # this code is duplicated in srpm_builder, but we
40 # might want to handle some cases differently here
41 def pick_request(q):
42     def mycmp(r1, r2):
43         if r1.kind != 'group' or r2.kind != 'group':
44             raise Exception, "non-group requests"
45         pri_diff = cmp(r1.priority, r2.priority)
46         if pri_diff == 0:
47             return cmp(r1.time, r2.time)
48         else:
49             return pri_diff
50     q.requests.sort(mycmp)
51     ret = q.requests[0]
52     return ret
53
54 def check_skip_build(r, b):
55     src_url = config.control_url + "/srpms/" + r.id + "/skipme"
56     good  = False
57     b.log_line("checking if we should skip the build")
58     while not good:
59         try:
60             headers = { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }
61             req = urllib2.Request(url=src_url, headers=headers)
62             f = urllib2.urlopen(req)
63             good = True
64         except urllib2.HTTPError, error:
65                 return False
66         except urllib2.URLError, error:
67             # see errno.h
68             if error.errno in [-3, 60, 61, 110, 111]:
69                 b.log_line("unable to connect... trying again")
70                 continue
71             else:
72                 return False
73         return True
74         f.close()
75     return False
76
77 def fetch_src(r, b):
78     src_url = config.control_url + "/srpms/" + r.id + "/" + b.src_rpm
79     b.log_line("fetching %s" % src_url)
80     start = time.time()
81     good = False
82     while not good:
83         try:
84             headers = { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }
85             req = urllib2.Request(url=src_url, headers=headers)
86             f = urllib2.urlopen(req)
87             good = True
88         except urllib2.HTTPError, error:
89             # fail in a way where cron job will retry
90             msg = "unable to fetch file, http code: %d" % error.code
91             b.log_line(msg)
92             queue_time = time.time() - r.time
93             # 6 hours
94             if error.code != 404 or (queue_time >= 0 and queue_time < (6 * 60 * 60)):
95                 raise IOError, msg
96             else:
97                 msg = "in queue for more than 6 hours, download failing"
98                 b.log_line(msg)
99                 return False
100         except urllib2.URLError, error:
101             # see errno.h
102             if error.errno in [-3, 60, 61, 110, 111]:
103                 b.log_line("unable to connect... trying again")
104                 continue
105             else:
106                 raise
107
108     o = chroot.popen("cat > %s" % b.src_rpm, mode = "w")
109
110     try:
111         bytes = util.sendfile(f, o)
112     except IOError, e:
113         b.log_line("error: unable to write to `%s': %s" % (b.src_rpm, e))
114         raise
115
116     f.close()
117     o.close()
118     t = time.time() - start
119     if t == 0:
120         b.log_line("fetched %d bytes" % bytes)
121     else:
122         b.log_line("fetched %d bytes, %.1f K/s" % (bytes, bytes / 1024.0 / t))
123
124 def prepare_env():
125     chroot.run("""
126         test ! -f /proc/uptime && mount /proc 2>/dev/null
127         test ! -c /dev/full && rm -f /dev/full && mknod -m 666 /dev/full c 1 7
128         test ! -c /dev/null && rm -f /dev/null && mknod -m 666 /dev/null c 1 3
129         test ! -c /dev/random && rm -f /dev/random && mknod -m 644 /dev/random c 1 8
130         test ! -c /dev/urandom && rm -f /dev/urandom && mknod -m 644 /dev/urandom c 1 9
131         test ! -c /dev/zero && rm -f /dev/zero && mknod -m 666 /dev/zero c 1 5
132
133         # make neccessary files readable for builder user
134         # TODO: see if they really aren't readable for builder
135         for db in Packages Name Basenames Providename Pubkeys; do
136             db=/var/lib/rpm/$db
137             test -f $db && chmod a+r $db
138         done
139
140         # try to limit network access for builder account
141         /bin/setfacl -m u:builder:--- /etc/resolv.conf
142     """, 'root')
143
144 def build_rpm(r, b):
145     status.push("building %s" % b.spec)
146     b.log_line("request from: %s" % r.requester)
147
148     if check_skip_build(r, b):
149         b.log_line("build skipped due to src builder request")
150         res = "SKIP_REQUESTED"
151         return res
152
153     b.log_line("started at: %s" % time.asctime())
154     fetch_src(r, b)
155     b.log_line("installing srpm: %s" % b.src_rpm)
156     res = chroot.run("rpm -U %s" % b.src_rpm, logfile = b.logfile)
157     chroot.run("rm -f %s" % b.src_rpm, logfile = b.logfile)
158     b.files = []
159     tmpdir = "/tmp/B." + b.b_id[0:6]
160     if res:
161         b.log_line("error: installing src rpm failed")
162         res = "FAIL_SRPM_INSTALL"
163     else:
164         prepare_env()
165         chroot.run("install -m 700 -d %s" % tmpdir)
166
167         b.default_target(config.arch)
168         rpmbuild_opt = "%s %s %s" % (b.target_string(), b.kernel_string(), b.bconds_string())
169         # check for build arch before filling BR
170         cmd = "cd rpm/SPECS; TMPDIR=%s exec nice -n %s rpmbuild -bp --short-circuit --nodeps --define 'prep exit 0' %s %s" % \
171             (tmpdir, config.nice, rpmbuild_opt, b.spec)
172         res = chroot.run(cmd, logfile = b.logfile)
173         if res:
174             res = "UNSUPP"
175             b.log_line("error: build arch check (%s) failed" % cmd)
176
177         if not res:
178             if ("no-install-br" not in r.flags) and not install.uninstall_self_conflict(b):
179                 res = "FAIL_DEPS_UNINSTALL"
180             if ("no-install-br" not in r.flags) and not install.install_br(r, b):
181                 res = "FAIL_DEPS_INSTALL"
182             if not res:
183                 max_jobs = max(min(int(os.sysconf('SC_NPROCESSORS_ONLN') * 1.5), config.max_jobs), 1)
184                 if r.max_jobs > 0:
185                     max_jobs = max(min(config.max_jobs, r.max_jobs), 1)
186                 cmd = "echo build-id: %s; cd rpm/SPECS; TMPDIR=%s exec nice -n %s rpmbuild -bb --define '_smp_mflags -j%d' %s %s" % \
187                             (r.id, tmpdir, config.nice, max_jobs, rpmbuild_opt, b.spec)
188                 b.log_line("building RPM using: %s" % cmd)
189                 begin_time = time.time()
190                 res = chroot.run(cmd, logfile = b.logfile)
191                 end_time = time.time()
192                 b.log_line("ended at: %s, done in %s" % (time.asctime(), time.strftime("%ddays %Hh %Mm %Ss", time.gmtime(end_time - begin_time))))
193                 if res:
194                     res = "FAIL"
195                 files = util.collect_files(b.logfile)
196                 if len(files) > 0:
197                     r.chroot_files.extend(files)
198                 else:
199                     b.log_line("error: No files produced.")
200                     last_section = util.find_last_section(b.logfile)
201                     if last_section == None:
202                         res = "FAIL"
203                     else:
204                         res = "FAIL_%s" % last_section.upper()
205                 b.files = files
206
207     chroot.run("rm -rf %s; cd rpm/SPECS; rpmbuild --nodeps --nobuild " \
208                          "--clean --rmspec --rmsource %s" % \
209                          (tmpdir, b.spec), logfile = b.logfile)
210     chroot.run("chmod -R u+rwX $HOME/rpm/BUILD/*; rm -rf $HOME/rpm/BUILD/*")
211
212     def ll(l):
213         util.append_to(b.logfile, l)
214  
215     if b.files != []:
216         rpm_cache_dir = config.rpm_cache_dir
217         if "test-build" not in r.flags:
218             # NOTE: copying to cache dir doesn't mean that build failed, so ignore result
219             b.log_line("copy rpm files to cache_dir: %s" % rpm_cache_dir)
220             chroot.run(
221                     "cp -f %s %s && poldek --mo=nodiff --mkidxz -s %s/" % \
222                         (string.join(b.files), rpm_cache_dir, rpm_cache_dir),
223                      logfile = b.logfile, user = "root"
224             )
225         else:
226             ll("test-build: not copying to " + rpm_cache_dir)
227         ll("Begin-PLD-Builder-Info")
228         if "upgrade" in r.flags:
229             b.upgraded = install.upgrade_from_batch(r, b)
230         else:
231             ll("not upgrading")
232         ll("End-PLD-Builder-Info")
233
234     for f in b.files:
235         local = r.tmp_dir + os.path.basename(f)
236         chroot.cp(f, outfile = local, rm = True)
237         ftp.add(local)
238
239     def uploadinfo(b):
240         c="file:SRPMS:%s\n" % b.src_rpm
241         for f in b.files:
242             c=c + "file:ARCH:%s\n" % os.path.basename(f)
243         c=c + "END\n"
244         return c
245
246     if config.gen_upinfo and b.files != [] and 'test-build' not in r.flags:
247         fname = r.tmp_dir + b.src_rpm + ".uploadinfo"
248         f = open(fname, "w")
249         f.write(uploadinfo(b))
250         f.close()
251         ftp.add(fname, "uploadinfo")
252
253     status.pop()
254
255     return res
256
257 def handle_request(r):
258     ftp.init(r)
259     buildlogs.init(r)
260     build.build_all(r, build_rpm)
261     report.send_report(r, is_src = False)
262     ftp.flush()
263     notify.send(r)
264
265 def check_load():
266     do_exit = 0
267     try:
268         f = open("/proc/loadavg")
269         if float(string.split(f.readline())[2]) > config.max_load:
270             do_exit = 1
271     except:
272         pass
273     if do_exit:
274         sys.exit(0)
275
276 def main_for(builder):
277     msg = ""
278
279     init_conf(builder)
280
281     q = B_Queue(path.queue_file + "-" + config.builder)
282     q.lock(0)
283     q.read()
284     if q.requests == []:
285         q.unlock()
286         return
287     req = pick_request(q)
288     q.unlock()
289
290     # high priority tasks have priority < 0, normal tasks >= 0
291     if req.priority >= 0:
292
293         # allow only one build in given builder at once
294         if not lock.lock("building-rpm-for-%s" % config.builder, non_block = 1):
295             return
296         # don't kill server
297         check_load()
298         # not more then job_slots builds at once
299         locked = 0
300         for slot in range(config.job_slots):
301             if lock.lock("building-rpm-slot-%d" % slot, non_block = 1):
302                 locked = 1
303                 break
304         if not locked:
305             return
306
307         # record fact that we got lock for this builder, load balancer
308         # will use it for fair-queuing
309         l = lock.lock("got-lock")
310         f = open(path.got_lock_file, "a")
311         f.write(config.builder + "\n")
312         f.close()
313         l.close()
314     else:
315         msg = "HIGH PRIORITY: "
316     
317     msg += "handling request %s (%d) for %s from %s, priority %s" \
318             % (req.id, req.no, config.builder, req.requester, req.priority)
319     log.notice(msg)
320     status.push(msg)
321     handle_request(req)
322     status.pop()
323
324     def otherreqs(r):
325         if r.no==req.no:
326             return False
327         else:
328             return True
329
330     q = B_Queue(path.queue_file + "-" + config.builder)
331     q.lock(0)
332     q.read()
333     previouslen=len(q.requests)
334     q.requests=filter(otherreqs, q.requests)
335     if len(q.requests)<previouslen:
336         q.write()
337     q.unlock()
338     
339 def main():
340     if len(sys.argv) < 2:
341         raise Exception, "fatal: need to have builder name as first arg"
342     return main_for(sys.argv[1])
343     
344 if __name__ == '__main__':
345     loop.run_loop(main)
This page took 0.143172 seconds and 4 git commands to generate.