]> TLD Linux GIT Repositories - TLD.git/blobdiff - pld-builder.new/PLD_Builder/rpm_builder.py
- from https://github.com/pld-linux/pld-builder.new
[TLD.git] / pld-builder.new / PLD_Builder / rpm_builder.py
diff --git a/pld-builder.new/PLD_Builder/rpm_builder.py b/pld-builder.new/PLD_Builder/rpm_builder.py
new file mode 100644 (file)
index 0000000..e42c085
--- /dev/null
@@ -0,0 +1,400 @@
+# vi: encoding=utf-8 ts=8 sts=4 sw=4 et
+
+import sys
+import os
+import atexit
+import time
+import datetime
+import string
+import urllib
+import urllib2
+
+from config import config, init_conf
+from bqueue import B_Queue
+import lock
+import util
+import loop
+import path
+import status
+import log
+import chroot
+import ftp
+import buildlogs
+import notify
+import build
+import report
+import install
+
+# *HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*
+import socket
+
+socket.myorigsocket=socket.socket
+
+def mysocket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
+    s=socket.myorigsocket(family, type, proto)
+    s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+    return s
+
+socket.socket=mysocket
+# *HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*
+
+# this code is duplicated in srpm_builder, but we
+# might want to handle some cases differently here
+def pick_request(q):
+    def mycmp(r1, r2):
+        if r1.kind != 'group' or r2.kind != 'group':
+            raise Exception, "non-group requests"
+        pri_diff = cmp(r1.priority, r2.priority)
+        if pri_diff == 0:
+            return cmp(r1.time, r2.time)
+        else:
+            return pri_diff
+    q.requests.sort(mycmp)
+    ret = q.requests[0]
+    return ret
+
+def check_skip_build(r, b):
+    src_url = config.control_url + "/srpms/" + r.id + "/skipme"
+    good  = False
+    b.log_line("checking if we should skip the build")
+    while not good:
+        try:
+            headers = { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }
+            req = urllib2.Request(url=src_url, headers=headers)
+            f = urllib2.urlopen(req)
+            good = True
+        except urllib2.HTTPError, error:
+            return False
+        except urllib2.URLError, error:
+            # see errno.h
+            try:
+                errno = error.errno
+            except AttributeError:
+                # python 2.4
+                errno = error.reason[0]
+
+            if errno in [-3, 60, 61, 110, 111]:
+                b.log_line("unable to connect... trying again")
+                continue
+            else:
+                return False
+        f.close()
+        return True
+    return False
+
+def fetch_src(r, b):
+    src_url = config.control_url + "/srpms/" + r.id + "/" + urllib.quote(b.src_rpm)
+    b.log_line("fetching %s" % src_url)
+    start = time.time()
+    good = False
+    while not good:
+        try:
+            headers = { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }
+            req = urllib2.Request(url=src_url, headers=headers)
+            f = urllib2.urlopen(req)
+            good = True
+        except urllib2.HTTPError, error:
+            # fail in a way where cron job will retry
+            msg = "unable to fetch url %s, http code: %d" % (src_url, error.code)
+            b.log_line(msg)
+            queue_time = time.time() - r.time
+            # 6 hours
+            if error.code != 404 or (queue_time >= 0 and queue_time < (6 * 60 * 60)):
+                raise IOError, msg
+            else:
+                msg = "in queue for more than 6 hours, download failing"
+                b.log_line(msg)
+                return False
+        except urllib2.URLError, error:
+            # see errno.h
+            try:
+                errno = error.errno
+            except AttributeError:
+                # python 2.4
+                errno = error.reason[0]
+
+            if errno in [-3, 60, 61, 110, 111]:
+                b.log_line("unable to connect to %s... trying again" % (src_url))
+                continue
+            else:
+                raise
+
+    o = chroot.popen("cat > %s" % b.src_rpm, mode = "w")
+
+    try:
+        bytes = util.sendfile(f, o)
+    except IOError, e:
+        b.log_line("error: unable to write to `%s': %s" % (b.src_rpm, e))
+        raise
+
+    f.close()
+    o.close()
+    t = time.time() - start
+    if t == 0:
+        b.log_line("fetched %d bytes" % bytes)
+    else:
+        b.log_line("fetched %d bytes, %.1f K/s" % (bytes, bytes / 1024.0 / t))
+
+def prepare_env():
+    chroot.run("""
+        test ! -f /proc/uptime && mount /proc 2>/dev/null
+        test ! -c /dev/full && rm -f /dev/full && mknod -m 666 /dev/full c 1 7
+        test ! -c /dev/null && rm -f /dev/null && mknod -m 666 /dev/null c 1 3
+        test ! -c /dev/random && rm -f /dev/random && mknod -m 644 /dev/random c 1 8
+        test ! -c /dev/urandom && rm -f /dev/urandom && mknod -m 644 /dev/urandom c 1 9
+        test ! -c /dev/zero && rm -f /dev/zero && mknod -m 666 /dev/zero c 1 5
+
+        # need entry for "/" in mtab, for diskspace() to work in rpm
+        [ -z $(awk '$2 == "/" {print $1}' /etc/mtab) ] && mount -f -t rootfs rootfs /
+
+        # make neccessary files readable for builder user
+        # TODO: see if they really aren't readable for builder
+        for db in Packages Name Basenames Providename Pubkeys; do
+            db=/var/lib/rpm/$db
+            test -f $db && chmod a+r $db
+        done
+
+        # try to limit network access for builder account
+        /bin/setfacl -m u:builder:--- /etc/resolv.conf
+    """, 'root')
+
+def build_rpm(r, b):
+    if len(b.spec) <= 5:
+        # should not really get here
+        b.log_line("error: No .spec not given of malformed: '%s'" % b.spec)
+        res = "FAIL_INTERNAL"
+        return res
+
+    packagename = b.spec[:-5]
+    status.push("building %s (%s)" % (b.spec, packagename))
+    b.log_line("request from: %s" % r.requester)
+
+    if check_skip_build(r, b):
+        b.log_line("build skipped due to src builder request")
+        res = "SKIP_REQUESTED"
+        return res
+
+    b.log_line("started at: %s" % time.asctime())
+    fetch_src(r, b)
+    b.log_line("installing srpm: %s" % b.src_rpm)
+    res = chroot.run("""
+        # b.id %(bid)s
+        set -ex;
+        install -d rpm/packages/%(package)s rpm/BUILD/%(package)s;
+        rpm -Uhv %(rpmdefs)s %(src_rpm)s;
+        rm -f %(src_rpm)s;
+    """ % {
+        'bid' : b.b_id,
+        'package' : packagename,
+        'rpmdefs' : b.rpmbuild_opts(),
+        'src_rpm' : b.src_rpm
+    }, logfile = b.logfile)
+    b.files = []
+
+    # it's better to have TMPDIR and BUILD dir on same partition:
+    # + /usr/bin/bzip2 -dc /home/services/builder/rpm/packages/kernel/patch-2.6.27.61.bz2
+    # patch: **** Can't rename file /tmp/B.a1b1d3/poKWwRlp to drivers/scsi/hosts.c : No such file or directory
+    tmpdir = os.environ.get('HOME') + "/rpm/BUILD/%s/tmp" % packagename
+    if res:
+        b.log_line("error: installing src rpm failed")
+        res = "FAIL_SRPM_INSTALL"
+    else:
+        prepare_env()
+        chroot.run("install -m 700 -d %s" % tmpdir)
+
+        b.default_target(config.arch)
+        # check for build arch before filling BR
+        cmd = "set -ex; TMPDIR=%(tmpdir)s exec nice -n %(nice)s " \
+            "rpmbuild -bp --short-circuit --nodeps %(rpmdefs)s --define 'prep exit 0' rpm/packages/%(package)s/%(spec)s" % {
+            'tmpdir': tmpdir,
+            'nice' : config.nice,
+            'rpmdefs' : b.rpmbuild_opts(),
+            'package' : packagename,
+            'spec': b.spec,
+        }
+        res = chroot.run(cmd, logfile = b.logfile)
+        if res:
+            res = "UNSUPP"
+            b.log_line("error: build arch check (%s) failed" % cmd)
+
+        if not res:
+            if ("no-install-br" not in r.flags) and not install.uninstall_self_conflict(b):
+                res = "FAIL_DEPS_UNINSTALL"
+            if ("no-install-br" not in r.flags) and not install.install_br(r, b):
+                res = "FAIL_DEPS_INSTALL"
+            if not res:
+                max_jobs = max(min(int(os.sysconf('SC_NPROCESSORS_ONLN') + 1), config.max_jobs), 1)
+                if r.max_jobs > 0:
+                    max_jobs = max(min(config.max_jobs, r.max_jobs), 1)
+                cmd = "set -ex; : build-id: %(r_id)s; TMPDIR=%(tmpdir)s exec nice -n %(nice)s " \
+                    "rpmbuild -bb --define '_smp_mflags -j%(max_jobs)d' %(rpmdefs)s rpm/packages/%(package)s/%(spec)s" % {
+                    'r_id' : r.id,
+                    'tmpdir': tmpdir,
+                    'nice' : config.nice,
+                    'rpmdefs' : b.rpmbuild_opts(),
+                    'package' : packagename,
+                    'max_jobs' : max_jobs,
+                    'spec': b.spec,
+                }
+                b.log_line("building RPM using: %s" % cmd)
+                begin_time = time.time()
+                res = chroot.run(cmd, logfile = b.logfile)
+                end_time = time.time()
+                b.log_line("ended at: %s, done in %s" % (time.asctime(), datetime.timedelta(0, end_time - begin_time)))
+                if res:
+                    res = "FAIL"
+                files = util.collect_files(b.logfile)
+                if len(files) > 0:
+                    r.chroot_files.extend(files)
+                else:
+                    b.log_line("error: No files produced.")
+                    last_section = util.find_last_section(b.logfile)
+                    if last_section == None:
+                        res = "FAIL"
+                    else:
+                        res = "FAIL_%s" % last_section.upper()
+                b.files = files
+
+    chroot.run("""
+        set -ex;
+        rpmbuild %(rpmdefs)s --nodeps --nobuild --clean --rmspec --rmsource rpm/packages/%(package)s/%(spec)s
+        rm -rf %(tmpdir)s;
+        chmod -R u+rwX rpm/BUILD/%(package)s;
+        rm -rf rpm/BUILD/%(package)s;
+    """ %
+        {'tmpdir' : tmpdir, 'spec': b.spec, 'package' : packagename, 'rpmdefs' : b.rpmbuild_opts()}, logfile = b.logfile)
+
+    def ll(l):
+        util.append_to(b.logfile, l)
+
+    if b.files != []:
+        rpm_cache_dir = config.rpm_cache_dir
+        if "test-build" not in r.flags:
+            # NOTE: copying to cache dir doesn't mean that build failed, so ignore result
+            b.log_line("copy rpm files to cache_dir: %s" % rpm_cache_dir)
+            chroot.run(
+                    "cp -f %s %s && poldek --mo=nodiff --mkidxz -s %s/" % \
+                        (string.join(b.files), rpm_cache_dir, rpm_cache_dir),
+                     logfile = b.logfile, user = "root"
+            )
+        else:
+            ll("test-build: not copying to " + rpm_cache_dir)
+        ll("Begin-PLD-Builder-Info")
+        if "upgrade" in r.flags:
+            b.upgraded = install.upgrade_from_batch(r, b)
+        else:
+            ll("not upgrading")
+        ll("End-PLD-Builder-Info")
+
+    for f in b.files:
+        local = r.tmp_dir + os.path.basename(f)
+        chroot.cp(f, outfile = local, rm = True)
+        ftp.add(local)
+
+    def uploadinfo(b):
+        c="file:SRPMS:%s\n" % b.src_rpm
+        for f in b.files:
+            c=c + "file:ARCH:%s\n" % os.path.basename(f)
+        c=c + "END\n"
+        return c
+
+    if config.gen_upinfo and b.files != [] and 'test-build' not in r.flags:
+        fname = r.tmp_dir + b.src_rpm + ".uploadinfo"
+        f = open(fname, "w")
+        f.write(uploadinfo(b))
+        f.close()
+        ftp.add(fname, "uploadinfo")
+
+    status.pop()
+
+    return res
+
+def handle_request(r):
+    ftp.init(r)
+    buildlogs.init(r)
+    build.build_all(r, build_rpm)
+    report.send_report(r, is_src = False)
+    ftp.flush()
+    notify.send(r)
+
+def check_load():
+    do_exit = 0
+    try:
+        f = open("/proc/loadavg")
+        if float(string.split(f.readline())[2]) > config.max_load:
+            do_exit = 1
+    except:
+        pass
+    if do_exit:
+        sys.exit(0)
+
+def main_for(builder):
+    msg = ""
+
+    init_conf(builder)
+
+    q = B_Queue(path.queue_file + "-" + config.builder)
+    q.lock(0)
+    q.read()
+    if q.requests == []:
+        q.unlock()
+        return
+    req = pick_request(q)
+    q.unlock()
+
+    # high priority tasks have priority < 0, normal tasks >= 0
+    if req.priority >= 0:
+
+        # allow only one build in given builder at once
+        if not lock.lock("building-rpm-for-%s" % config.builder, non_block = 1):
+            return
+        # don't kill server
+        check_load()
+        # not more then job_slots builds at once
+        locked = 0
+        for slot in range(config.job_slots):
+            if lock.lock("building-rpm-slot-%d" % slot, non_block = 1):
+                locked = 1
+                break
+        if not locked:
+            return
+
+        # record fact that we got lock for this builder, load balancer
+        # will use it for fair-queuing
+        l = lock.lock("got-lock")
+        f = open(path.got_lock_file, "a")
+        f.write(config.builder + "\n")
+        f.close()
+        l.close()
+    else:
+        msg = "HIGH PRIORITY: "
+
+    msg += "handling request %s (%d) for %s from %s, priority %s" \
+            % (req.id, req.no, config.builder, req.requester, req.priority)
+    log.notice(msg)
+    status.push(msg)
+    handle_request(req)
+    status.pop()
+
+    def otherreqs(r):
+        if r.no==req.no:
+            return False
+        else:
+            return True
+
+    q = B_Queue(path.queue_file + "-" + config.builder)
+    q.lock(0)
+    q.read()
+    previouslen=len(q.requests)
+    q.requests=filter(otherreqs, q.requests)
+    if len(q.requests)<previouslen:
+        q.write()
+    q.unlock()
+
+def main():
+    if len(sys.argv) < 2:
+        raise Exception, "fatal: need to have builder name as first arg"
+    return main_for(sys.argv[1])
+
+if __name__ == '__main__':
+    loop.run_loop(main)