extras-buildsys/builder Builder.py, NONE, 1.1 BuilderMock.py, NONE, 1.1 main.py, NONE, 1.1 Config.py, 1.2, 1.3 Makefile, 1.4, 1.5 builder.py, 1.54, NONE
Daniel Williams (dcbw)
fedora-extras-commits at redhat.com
Fri Apr 28 03:17:42 UTC 2006
- Previous message (by thread): extras-buildsys ChangeLog,1.183,1.184
- Next message (by thread): extras-buildsys/common Commands.py, NONE, 1.1 HTTPServer.py, 1.12, 1.13 Makefile, 1.11, 1.12 URLopener.py, 1.1, 1.2
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
Author: dcbw
Update of /cvs/fedora/extras-buildsys/builder
In directory cvs-int.fedora.redhat.com:/tmp/cvs-serv5292/builder
Modified Files:
Config.py Makefile
Added Files:
Builder.py BuilderMock.py main.py
Removed Files:
builder.py
Log Message:
2006-04-27 Dan Williams <dcbw at redhat.com>
Commit partial rework of builder<->server communcation.
--- NEW FILE Builder.py ---
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Copyright 2006 Dan Williams <dcbw at redhat.com> and Red Hat, Inc.
import os
import sys
import socket
import time
import threading
import sha
import exceptions
import xmlrpclib
import OpenSSL
from plague import Commands
from plague import AuthedXMLRPCServer
from plague import HTTPServer
from plague import XMLRPCServerProxy
import Config
import BuilderMock
class BuilderInitException(Exception): pass
def get_hostname(cfg, bind_all):
cfg_hostname = cfg.get_str("Passive", "hostname")
if cfg_hostname and len(cfg_hostname):
return cfg_hostname
elif bind_all:
return ''
return socket.gethostname()
def determine_max_jobs(cfg):
""" Simple max job calculator based on number of CPUs """
import commands
max_jobs = 1
cmd = "/usr/bin/getconf _NPROCESSORS_ONLN"
(s, o) = commands.getstatusoutput(cmd)
if s == 0:
try:
max_jobs = int(o)
except ValueError:
pass
return max_jobs
def log(string):
sys.stdout.write(string + "\n")
sys.stdout.flush()
class PassiveBuilderRequestHandler:
def __init__(self, cfg, builder):
self._builder = builder
self._all_jobs = {} # unique id => awclass instance
self._building_jobs_lock = threading.Lock()
self._building_jobs = []
self._cfg = cfg
def _log(self, string):
if self._cfg.get_bool("General", "debug"):
print string
def notify_job_done(self, archjob):
self._building_jobs_lock.acquire()
if archjob in self._building_jobs:
self._building_jobs.remove(archjob)
self._building_jobs_lock.release()
def die(self, uniqid):
try:
job = self._all_jobs[uniqid]
job.die()
except KeyError:
pass
return 0
def files(self, uniqid):
try:
job = self._all_jobs[uniqid]
return job.files()
except KeyError:
pass
return []
def repo_unlocked(self, uniqid):
try:
job = self._all_jobs[uniqid]
job.repo_unlocked()
except KeyError:
pass
return 0
def building_jobs(self):
jobs = {}
self._building_jobs_lock.acquire()
building = 0
for job in self._building_jobs:
jobs[job.uniqid()] = job.status()
building = building + 1
free = self._max_jobs - building
self._building_jobs_lock.release()
return (jobs, free)
def num_slots(self):
(free_slots, max_slots) = self._builder.slots()
return max_slots
def job_status(self, uniqid):
try:
job = self._all_jobs[uniqid]
return job.status()
except KeyError:
pass
return ''
def supported_targets(self):
return self._builder.supported_targets()
class Builder(object):
""" Abstract builder base object """
def __init__(self, cfg):
self._cfg = cfg
self._certs = None
self._max_slots = determine_max_jobs(cfg)
self._seq_gen = Commands.SequenceGenerator()
self._building_jobs_lock = threading.Lock()
self._building_jobs = []
self._all_jobs = {}
if cfg.get_bool("SSL", "use_ssl"):
hostname = get_hostname(self._cfg, False)
key_file = os.path.join(cfg.get_str("SSL", "builder_key_and_cert_dir"), "%s.pem" % hostname)
self._certs = {}
self._certs['key_and_cert'] = key_file
self._certs['ca_cert'] = cfg.get_str("SSL", "ca_cert")
self._certs['peer_ca_cert'] = self._certs['ca_cert']
def _log(self, string):
if self._cfg.get_bool("General", "debug"):
log(string)
def new_builder(cfg, btype):
if btype == 'passive':
return PassiveBuilder(cfg)
elif btype == 'active':
return ActiveBuilder(cfg)
else:
return None
new_builder = staticmethod(new_builder)
def stop(self):
pass
def cleanup(self):
return
(building_jobs, free) = bcs.building_jobs()
for jobid in building_jobs.keys():
bcs.die(jobid)
# wait for the jobs to clean up before quitting
log("Waiting for running jobs to stop...")
while True:
(building_jobs, free) = bcs.building_jobs()
if len(building_jobs.keys()) == 0:
break
try:
log(".")
time.sleep(0.5)
except KeyboardInterrupt:
break
def slots(self):
self._building_jobs_lock.acquire()
free_slots = self._max_slots - len(self._building_jobs)
self._building_jobs_lock.release()
return (free_slots, self._max_slots)
def supported_targets(self):
targets = []
for t in self._cfg.targets():
td = t.target_dict()
td['supported_arches'] = t.arches()
targets.append(td)
return targets
def _get_target_cfg(self, target_dict):
target_cfg = None
# First try to find a target for buildarch specifically
try:
target_cfg = self._cfg.get_target(target_dict)
except Config.InvalidTargetException:
pass
if not target_cfg:
# If that doesn't work, just get a target that can build the arch
try:
target_cfg = self._cfg.get_target(target_dict, True)
except Config.InvalidTargetException:
pass
return target_cfg
def _new_job_for_arch(self, target_cfg, buildarch, srpm_url):
"""Creates a new mock build job given a particular build architecture."""
if buildarch != 'noarch' and not BuilderMock.BuilderClassDict.has_key(buildarch):
# we know nothing about the architecture 'buildarch'
return None
builder_class = None
if buildarch == 'noarch':
# Just grab the first available architecture from the ones we support
builder_class = BuilderMock.BuilderClassDict[target_cfg.arches()[0]]
elif buildarch in target_cfg.arches():
builder_class = BuilderMock.BuilderClassDict[buildarch]
# We'll throw a TypeError here if there's no available builder_class for this arch
return builder_class(self, target_cfg, buildarch, srpm_url)
def _start_new_job(self, target_dict, srpm_url):
target_str = Config.make_target_string(target_dict['distro'], target_dict['target'], target_dict['arch'], target_dict['repo'])
uniqid = -1
msg = "Success"
(free, max) = self.slots()
if free <= 0:
msg = "Error: Tried to build '%s' on target %s when already building" \
" maximum (%d) jobs" % (srpm_url, target_str, max)
self._log(msg)
return (uniqid, msg)
target_cfg = self._get_target_cfg(target_dict)
if not target_cfg:
msg = "Error: Tried to build '%s' on target %s which isn't supported" % (srpm_url, target_str)
self._log(msg)
return (uniqid, msg)
archjob = None
try:
archjob = self._new_job_for_arch(target_cfg, target_dict['arch'], srpm_url)
uniqid = archjob.uniqid()
self._all_jobs[uniqid] = archjob
self._building_jobs_lock.acquire()
self._building_jobs.append(archjob)
self._building_jobs_lock.release()
filename = os.path.basename(srpm_url)
msg = "%s: started %s on %s arch %s at time %d" % (uniqid, filename,
target_str, target_dict['arch'], archjob.starttime())
# job.start()
except (OSError, TypeError), err:
msg = "Failed request for %s on %s: '%s'" % (srpm_url,
target_str, err)
self._log(msg)
return (uniqid, msg)
class PassiveBuilder(Builder):
"""
Passive builders initiate no communication of their own. They wait
for the build server to contact them, and therefore may not be used
behind a firewall without holes being punched through it.
"""
def __init__(self, cfg):
Builder.__init__(self, cfg)
self._http_server = None
self._xmlrpc_server = None
def _start_servers(self):
# Start up the HTTP server thread which the build server
# pulls completed RPMs from
hostname = get_hostname(self._cfg, True)
port = cfg.get_int("Passive", "fileserver_port")
self._http_server = HTTPServer.PlgHTTPServerManager((hostname, port), work_dir, self._certs)
self._http_server.start()
log("Binding to address '%s' with arches: [%s]\n" % (hostname, string.join(build_arches, ",")))
xmlrpc_port = cfg.get_int("Passive", "xmlrpc_port")
try:
if cfg.get_bool("SSL", "use_ssl") == True:
self._xmlrpc_server = AuthedXMLRPCServer.AuthedSSLXMLRPCServer((hostname, xmlrpc_port), None, self._certs)
else:
self._xmlrpc_server = AuthedXMLRPCServer.AuthedXMLRPCServer((hostname, xmlrpc_port), None)
except socket.error, e:
if e[0] == 98:
raise BuilderInitException("Error: couldn't bind to address '%s:%s'. " \
"Is the builder already running?\n" % (hostname, xmlrpc_port))
brh = PassiveBuilderRequestHandler(cfg, self)
self._xmlrpc_server.register_instance(brh)
def work(self):
self._start_servers()
try:
self._xmlrpc_server.serve_forever()
except KeyboardInterrupt:
pass
def _stop_servers(self):
self._http_server.stop()
self._xmlrpc_server.stop()
try:
time.sleep(1)
except KeyboardInterrupt:
pass
self._xmlrpc_server.server_close()
def stop(self):
Builder.stop(self)
self._stop_servers()
# HACK: This class is a hack to work around SSL hanging issues,
# which cause the whole server to grind to a halt
class ActiveBuilderRequest(threading.Thread):
def __init__(self, server, address, cmds):
self._server = server
self._address = address
self._cmds = cmds
self.done = False
self.failed = False
self.response = None
threading.Thread.__init__(self)
def run(self):
self.setName("ActiveBuilderRequest: %s" % self._address)
try:
cmd_stream = Commands.serialize_to_command_stream(self._cmds)
self.response = self._server.request(cmd_stream)
except (socket.error, socket.timeout, OpenSSL.SSL.SysCallError, OpenSSL.SSL.Error, xmlrpclib.ProtocolError):
self.failed = True
except xmlrpclib.Fault, e:
print "Builder Error (%s) in request(): server replied '%s'" % (self._address, e)
self.failed = True
self.done = True
class ActiveBuilder(Builder, threading.Thread):
"""
Active builders initiate all communication to the builder server, and
therefore may be used from behind a firewall.
"""
_SERVER_CONTACT_INTERVAL = 20
def __init__(self, cfg):
Builder.__init__(self, cfg)
self._stop = False
self._last_comm = time.time() - self._SERVER_CONTACT_INTERVAL - 1
self._queued_cmds = []
self._xmlrpc_address = self._get_server_address(cfg.get_str("Active", "xmlrpc_port"))
self._server = XMLRPCServerProxy.PlgXMLRPCServerProxy(self._xmlrpc_address, self._certs, timeout=20)
threading.Thread.__init__(self)
def _get_server_address(self, port):
addr = self._cfg.get_str("Active", "server")
if addr.startswith("http://"):
addr = addr[7:]
elif addr.startswith("https://"):
addr = addr[8:]
if self._cfg.get_bool("SSL", "use_ssl"):
addr = "https://" + addr
else:
addr = "http://" + addr
return addr + ":" + port
def work(self):
self.start()
try:
while not self._stop:
time.sleep(60)
except KeyboardInterrupt:
pass
def _get_default_commands(self):
"""Return a python list of serialized commands that the builder
sends to the server every time it contacts the server."""
defcmds = []
# always send a target list
next_seq = self._seq_gen.next()
cmd = Commands.PlgCommandTargets(self.supported_targets(), next_seq)
defcmds.append(cmd)
# always send free & max slots
next_seq = self._seq_gen.next()
(free, max) = self.slots()
cmd = Commands.PlgCommandSlots(free, max, next_seq)
defcmds.append(cmd)
return defcmds
def _send_commands(self):
"""Send default commands, and any commands that we've queued up
since the last time we sent commands to the server."""
cmds = self._get_default_commands()
cmds = cmds + self._queued_cmds
# The actual XML-RPC request runs in a different thread because SSL
# calls sometimes hang
req = ActiveBuilderRequest(self._server, self._xmlrpc_address, cmds)
curtime = time.time()
req.start()
# Give the request 10s, otherwise forget about it
while time.time() - curtime < 10:
if req.done:
break
time.sleep(0.5)
if req.done and not req.failed:
self.queued_cmds = []
return req.response
return None
def _dispatch_server_command(self, cmd):
"""Process a single command from the server."""
if isinstance(cmd, Commands.PlgCommandNewJobReq):
(uniqid, msg) = self._start_new_job(cmd.target_dict(), cmd.srpm_url())
ack = Commands.PlgCommandNewJobAck(uniqid, msg, cmd.seq(), self._seq_gen.next())
self._queued_cmds.append(ack)
def _process_server_response(self, response):
"""Process the server's response."""
if not response:
# Something went wrong...
return
cmds = Commands.deserialize_command_stream(response)
for cmd in cmds:
self._dispatch_server_command(cmd)
def run(self):
"""Main builder loop, send commands to and receive commands from
the server every so often."""
while not self._stop:
if self._last_comm < time.time() - self._SERVER_CONTACT_INTERVAL:
self._last_comm = time.time()
resp = self._send_commands()
self._process_server_response(resp)
time.sleep(1)
def stop(self):
self._stop = True
--- NEW FILE BuilderMock.py ---
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Copyright 2005 Dan Williams <dcbw at redhat.com> and Red Hat, Inc.
import threading
import sys
import os
import socket
import shutil
import string
import fcntl
import urllib
import errno
import exceptions
import threading
import sha
import time
from plague import ExecUtils
from plague import FileDownloader
def log(string):
sys.stdout.write(string)
sys.stdout.flush()
def get_url_for_file(cfg, file_path):
""" Return a URL pointing to a particular file in our work dir """
# Ensure the file we're turning into a URL lives in our builder work dir
work_dir = cfg.get_str("Directories", "builder_work_dir")
if not file_path.startswith(work_dir):
return None
file_part = file_path[len(work_dir) + 1:]
port = "%s" % cfg.get_int("Network", "fileserver_port")
if cfg.get_bool("SSL", "use_ssl"):
method = "https://"
else:
method = "http://"
hostname = get_hostname(cfg, False)
full_url = "%s%s:%s/%s" % (method, hostname, port, file_part)
return urllib.quote(full_url)
def _generate_uniqid(target_str, srpm_url):
sum = sha.new()
sum.update('%d %s %s' % (time.time(), target_str, srpm_url))
return sum.hexdigest()
class BuilderMock(threading.Thread):
"""puts things together for an arch - baseclass for handling builds for
other arches"""
def __init__(self, controller, target_cfg, buildarch, srpm_url):
self._controller = controller
self.buildarch = buildarch
self._starttime = time.time()
self._endtime = 0
self._mockstarttime = 0
self._uniqid = _generate_uniqid(str(target_cfg), srpm_url)
self._status = 'init'
self._die = False
self._repo_locked = True
self._repo_wait_start = 0
self._files = []
self._childpid = 0
self._target_cfg = target_cfg
self._builder_cfg = target_cfg.parent_cfg()
self._srpm_url = srpm_url
self._srpm_tries = 0
self._log_fd = None
self._mock_config = None
self._done_status = ''
self._mock_log = None
self.buildroot = self._target_cfg.mock_config()
self._work_dir = self._builder_cfg.get_str("Directories", "builder_work_dir")
self._result_dir = os.path.join(self._work_dir, self._uniqid, "result")
if not os.path.exists(self._result_dir):
os.makedirs(self._result_dir)
self._state_dir = os.path.join(self._work_dir, self._uniqid, "mock-state")
if not os.path.exists(self._state_dir):
os.makedirs(self._state_dir)
logfile = os.path.join(self._result_dir, "job.log")
self._log_fd = open(logfile, "w+")
threading.Thread.__init__(self)
def starttime(self):
return self._starttime
def endtime(self):
return self._endtime
def die(self):
if self.is_done_status() or self._done_status == 'killed':
return True
self._die = True
return True
def _handle_death(self):
self._die = False
self._done_status = 'killed'
self._log("Killing build process...\n")
# Don't try to kill a running cleanup process
if self._status != 'cleanup':
# Kill a running non-cleanup mock process, if any
if self._childpid:
child_pgroup = 0 - self._childpid
try:
# Kill all members of the child's process group
os.kill(child_pgroup, 9)
except OSError, e:
self._log("ERROR: Couldn't kill child process group %d: %s\n" % (child_pgroup, e))
else:
# Ensure child process is reaped
self._log("Waiting for mock process %d to exit...\n" % self._childpid)
try:
(pid, status) = os.waitpid(self._childpid, 0)
except OSError, e:
pass
self._log("Mock process %d exited.\n" % self._childpid)
self._childpid = 0
# Start cleanup up the job
self._start_cleanup()
self._log("Killed.\n");
def _log(self, string):
if string and self._log_fd:
self._log_fd.write(string)
self._log_fd.flush()
os.fsync(self._log_fd.fileno())
if self._builder_cfg.get_bool("General", "debug"):
s = "%s: " % self._uniqid
sys.stdout.write(s + string)
sys.stdout.flush()
def dl_callback(self, dl_status, cb_data, err_msg):
url = cb_data
if dl_status == 'done':
self._status = 'downloaded'
self._log("Retrieved %s.\n" % url)
elif dl_status == 'failed':
# If job was cancelled, just return
if self.is_done_status():
return
# Retry up to 5 times
self._srpm_tries = self._srpm_tries + 1
if self._srpm_tries >= 5:
self._status = 'failed'
self._log("ERROR: Failed to retrieve %s.\n" % url)
else:
# retry the download
self._status = 'init'
self._log("ERROR: Failed to retrieve %s on attempt %d (%s). Trying again...\n" % (url, self._srpm_tries, err_msg))
def _copy_mock_output_to_log(self):
if self._mock_log and os.path.exists(self._mock_log):
ml = open(self._mock_log, "r")
line = "foo"
while len(line):
line = ml.readline()
if len(line):
self._log_fd.write(line)
ml.close()
os.remove(self._mock_log)
self._mock_log = None
def _start_build(self):
self._log("Starting step 'building' with command:\n")
if not os.path.exists(self._result_dir):
os.makedirs(self._result_dir)
if not os.path.exists(self._result_dir):
os.makedirs(self._result_dir)
# Set up build process arguments
args = []
builder_cmd = os.path.abspath(self._builder_cfg.get_str("General", "builder_cmd"))
cmd = builder_cmd
if self.arch_command and len(self.arch_command):
arg_list = self.arch_command.split()
for arg in arg_list:
args.append(arg)
cmd = os.path.abspath(arg_list[0])
args.append(builder_cmd)
args.append("-r")
args.append(self.buildroot)
args.append("--arch")
args.append(self.buildarch)
args.append("--resultdir=%s" % self._result_dir)
args.append("--statedir=%s" % self._state_dir)
args.append("--uniqueext=%s" % self._uniqid)
args.append(self._srpm_path)
self._log(" %s\n" % string.join(args))
self._mock_log = os.path.join(self._result_dir, "mock-output.log")
self._childpid = ExecUtils.exec_with_redirect(cmd, args, None, self._mock_log, self._mock_log)
self._mockstarttime = time.time()
self._status = 'prepping'
def _start_cleanup(self):
self._log("Cleaning up the buildroot...\n")
args = []
builder_cmd = os.path.abspath(self._builder_cfg.get_str("General", "builder_cmd"))
cmd = builder_cmd
if self.arch_command and len(self.arch_command):
arg_list = self.arch_command.split()
for arg in arg_list:
args.append(arg)
cmd = os.path.abspath(arg_list[0])
args.append(builder_cmd)
args.append("clean")
args.append("--uniqueext=%s" % self._uniqid)
args.append("-r")
args.append(self.buildroot)
self._log(" %s\n" % string.join(args))
self._childpid = ExecUtils.exec_with_redirect(cmd, args, None, None, None)
self._status = 'cleanup'
def _mock_is_prepping(self):
mock_status = self._get_mock_status()
if mock_status:
if mock_status == 'prep':
return True
elif mock_status == 'setu':
return True
return False
def _mock_using_repo(self):
mock_status = self._get_mock_status()
if mock_status:
if mock_status == 'init':
return True
elif mock_status == 'clea':
return True
elif mock_status == 'prep':
return True
elif mock_status == 'setu':
return True
return False
def _mock_is_closed(self):
mock_status = self._get_mock_status()
if mock_status and mock_status == "done":
return True
return False
def _get_mock_status(self):
mockstatusfile = os.path.join(self._state_dir, 'status')
if not os.path.exists(mockstatusfile):
return None
f = open(mockstatusfile, "r")
fcntl.fcntl(f.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
while True:
try:
f.seek(0, 0)
string = f.read(4)
except OSError, e:
if e.errno == errno.EAGAIN:
try:
time.sleep(0.25)
except KeyboardInterrupt:
pass
continue
else:
if len(string) < 4:
continue
break
f.close()
string = string.lower()
return string
def _read_mock_config(self):
mockconfigfile = os.path.join(self._result_dir, 'mockconfig.log')
if not os.path.exists(mockconfigfile):
return None
f = open(mockconfigfile, "r")
contents = {}
for line in f:
(item, loc) = line.split('=')
item = item.strip()
loc = loc.strip()
contents[item] = loc
f.close()
return contents
def _status_init(self):
self._log("Starting download of %s.\n" % self._srpm_url)
self._status = 'downloading'
target_dir = os.path.dirname(self._srpm_path)
try:
dl_thread = FileDownloader.FileDownloader(self.dl_callback, self._srpm_url, self._srpm_url,
target_dir, ['.src.rpm'], certs)
dl_thread.start()
except FileDownloader.FileNameException, e:
self._status = 'failed'
self._log("ERROR: Failed to begin SRPM download. Error: '%s' URL: %s\n" % (e, self._srpm_url))
def _status_downloading(self):
pass
def _status_downloaded(self):
# We can't start doing anything with yum until the build
# server tells us the repo is unlocked.
if not self._repo_locked:
self._start_build()
else:
# Only show this message once
if self._repo_wait_start <= 0:
self._log("Waiting for repository to unlock before starting the build...\n")
self._repo_wait_start = time.time()
# Kill a job in 'downloaded' state after 30 minutes because
# it's likely orphaned
if time.time() > (self._repo_wait_start + (60 * 30)):
self._log("Job waited too long for repo to unlock. Killing it...\n")
self.die()
def _watch_mock(self, good_exit, bad_exit):
(aux_pid, status) = os.waitpid(self._childpid, os.WNOHANG)
status = os.WEXITSTATUS(status)
if aux_pid:
self._childpid = 0
if status == 0:
self._done_status = good_exit
elif status > 0:
self._done_status = bad_exit
self._copy_mock_output_to_log()
self._start_cleanup()
def _status_prepping(self):
# Mock shouldn't exit at all during the prepping stage, if it does
# something is wrong
self._watch_mock('failed', 'failed')
if self._status != 'prepping':
return
# We need to make sure that mock has dumped the status file withing a certain
# amount of time, otherwise we can't tell what it's doing
mockstatusfile = os.path.join(self._state_dir, 'status')
if not os.path.exists(mockstatusfile):
# something is wrong if mock takes more than 15s to write the status file
if time.time() > self._mockstarttime + 15:
self._mockstarttime = 0
self._log("ERROR: Timed out waiting for the mock status file! %s\n" % mockstatusfile)
self.die()
else:
if not self._mock_config and self._mock_is_prepping():
self._mock_config = self._read_mock_config()
if not self._mock_using_repo():
self._status = 'building'
def _status_building(self):
self._watch_mock('done', 'failed')
def _status_cleanup(self):
(aux_pid, status) = os.waitpid(self._childpid, os.WNOHANG)
if aux_pid:
self._childpid = 0
# Mock exited
if self._mock_config:
if self._mock_config.has_key('rootdir'):
mock_root_dir = os.path.abspath(os.path.join(self._mock_config['rootdir'], "../"))
# Ensure we're actually deleteing the job's rootdir
if mock_root_dir.endswith(self._uniqid):
shutil.rmtree(mock_root_dir, ignore_errors=True)
if self._mock_config.has_key('statedir'):
shutil.rmtree(self._mock_config['statedir'], ignore_errors=True)
source_dir = os.path.abspath(os.path.join(self._mock_config['rootdir'], "../source"))
# Ensure we're actually deleteing the job's sourcedir
if source_dir.endswith(os.path.join(self._uniqid, "source")):
shutil.rmtree(source_dir, ignore_errors=True)
# Ensure child process is reaped, if any
if self._childpid:
try:
self._log("Waiting for child process %d to exit.\n" % self._childpid)
(pid, status) = os.waitpid(self._childpid, 0)
except OSError, e:
self._childpid = 0
pass
self._copy_mock_output_to_log()
self._files = self._find_files()
self._status = self._done_status
def _job_done(self):
self._log("-----------------------\n")
if self._status == 'done':
self._log("Job completed successfully.\n")
elif self._status == 'failed':
self._log("Job failed due to build errors! Please see build logs.\n")
elif self._status == 'killed':
self._log("Job failed because it was killed.\n")
self._log("\n\n")
if self._log_fd:
self._log_fd.close()
self._log_fd = None
def run(self):
# Print out a nice message at the start of the job
target_dict = self._target_cfg.target_dict()
target_str = "%s-%s-%s-%s" % (target_dict['distro'], target_dict['target'], target_dict['arch'], target_dict['repo'])
self._log("""Starting job:
Time: %s
Target: %s
UID: %s
Architecture: %s
SRPM: %s\n\n""" % (time.asctime(time.localtime(self._starttime)), target_str, self._uniqid, self.buildarch, self._srpm_url))
try:
srpm_filename = FileDownloader.get_base_filename_from_url(self._srpm_url, ['.src.rpm'])
self._srpm_path = os.path.join(self._work_dir, self._uniqid, "source", srpm_filename)
except FileDownloader.FileNameException, e:
self._log("ERROR: SRPM file name was invalid. Message: '%s'\n" % e)
self._status = 'failed'
# Main build job work loop
while not self.is_done_status():
if self._die:
self._handle_death()
# Execute operations for our current status
try:
func = getattr(self, "_status_%s" % self._status)
except AttributeError:
self._log("ERROR: internal builder inconsistency, didn't recognize status '%s'.\n" % self._status)
self._status = 'failed'
else:
func()
time.sleep(3)
self._job_done()
self._endtime = time.time()
if self._childpid:
self._log("ERROR: childpid was !NULL (%d)" % self._childpid)
self._controller.notify_job_done(self)
def _find_files(self):
# Grab the list of files in our job's result dir and URL encode them
files_in_dir = os.listdir(self._result_dir)
file_list = []
self._log("\n")
self._log("Output File List:\n")
self._log("-----------------\n")
for f in files_in_dir:
file_url = get_url_for_file(self._builder_cfg, os.path.join(self._result_dir, f))
if file_url:
file_list.append(file_url)
self._log(" Output File: %s\n" % urllib.unquote(file_url))
else:
self._log(" Error: Couldn't get file URL for file %s\n" % f)
self._log("-----------------\n")
return file_list
def status(self):
return self._status
def uniqid(self):
return self._uniqid
def files(self):
return self._files
def repo_unlocked(self):
self._repo_locked = False
def is_done_status(self):
if (self._status is 'done') or (self._status is 'killed') or (self._status is 'failed'):
return True
return False
class InvalidTargetError(exceptions.Exception): pass
class i386Arch(BuilderMock):
def __init__(self, controller, target_cfg, buildarch, srpm_url):
self.arch_command = '/usr/bin/setarch i686'
BuilderMock.__init__(self, controller, target_cfg, buildarch, srpm_url)
class x86_64Arch(BuilderMock):
def __init__(self, controller, target_cfg, buildarch, srpm_url):
self.arch_command = ''
BuilderMock.__init__(self, controller, target_cfg, buildarch, srpm_url)
class PPCArch(BuilderMock):
def __init__(self, controller, target_cfg, buildarch, srpm_url):
self.arch_command = '/usr/bin/setarch ppc32'
BuilderMock.__init__(self, controller, target_cfg, buildarch, srpm_url)
class PPC64Arch(BuilderMock):
def __init__(self, controller, target_cfg, buildarch, srpm_url):
self.arch_command = ''
BuilderMock.__init__(self, controller, target_cfg, buildarch, srpm_url)
class SparcArch(BuilderMock):
def __init__(self, controller, target_cfg, buildarch, srpm_url):
self.arch_command = '/usr/bin/sparc32'
BuilderMock.__init__(self, controller, target_cfg, buildarch, srpm_url)
class Sparc64Arch(BuilderMock):
def __init__(self, controller, target_cfg, buildarch, srpm_url):
self.arch_command = '/usr/bin/sparc64'
BuilderMock.__init__(self, controller, target_cfg, buildarch, srpm_url)
BuilderClassDict = {
'i386': i386Arch,
'i486': i386Arch,
'i586': i386Arch,
'i686': i386Arch,
'athlon': i386Arch,
'x86_64': x86_64Arch,
'amd64': x86_64Arch,
'ia32e': x86_64Arch,
'ppc': PPCArch,
'ppc32': PPCArch,
'ppc64': PPC64Arch,
'sparc': SparcArch,
'sparcv8': SparcArch,
'sparcv9': SparcArch,
'sparc64': Sparc64Arch
}
--- NEW FILE main.py ---
#!/usr/bin/python -t
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Copyright 2005 Dan Williams <dcbw at redhat.com> and Red Hat, Inc.
import os
import time
import sys
import signal
from plague import ArchUtils
from plague import daemonize
from optparse import OptionParser
sys.path.append('/usr/share/plague/builder')
import Config
import Builder
import BuilderMock
def log(string):
sys.stdout.write(string)
sys.stdout.flush()
def drop_privs(user):
"""
We can't and shouldn't run mock as root, so we drop privs.
We have to run the HTTP server as root though so it can chroot
to the fileserver directory.
"""
if os.getuid() != 0:
return
import pwd
import grp
eu = user
try:
uid = int(eu)
except ValueError:
try:
pwrec = pwd.getpwnam(eu)
except KeyError:
print "Username '%s' does not exist." % eu
return -1
uid = pwrec[2]
else:
try:
pwrec = pwd.getpwuid(uid)
except KeyError:
print "User ID %d doesn't exist." % uid
return -1
gid = pwrec[3]
if uid == 0:
print "You cannot use the superuser as the 'builder_user' option."
return -1
# Make ourself members of the mock group build_user's group
try:
mock_req = grp.getgrnam('mock')
except KeyError:
print "Mock group doesn't exist."
return -1
groups = [mock_req[2], gid]
os.setgroups(groups)
try:
os.setgid(gid)
except OSError:
print "Could drop group privileges. Error: '%s'" % sys.exc_info()
return -1
os.setuid(uid)
return 0
def determine_build_arches(cfg):
"""
Attempt to autodetect what architectures this machine can build for,
based on the kernel's uname. If that fails, fall back to options in
the config file.
"""
machine_arch = os.uname()[4]
arches = []
try:
arches = ArchUtils.supported_arches[machine_arch]
except KeyError:
print "Unknown machine type. Please update plague's ArchUtils.py file."
# Ok, grab from config file if we can't autodetermine
if not len(arches):
arches = cfg.get_list("General", "build_arches")
for arch in arches:
if not arch in BuilderMock.BuilderClassDict.keys():
print "Unknown arch '%s' is not supported." % arch
sys.exit(1)
return arches
builder = None
def exit_handler(signum, frame):
global builder
log("Received SIGTERM, quitting...\n")
builder.stop()
def main():
global builder
usage = "Usage: %s [-p <pidfile>] [-l <logfile>] [-d] -c <configfile>" % sys.argv[0]
parser = OptionParser(usage=usage)
parser.add_option("-p", "--pidfile", default=None,
help='file to write the PID to')
parser.add_option("-l", "--logfile", default=None,
help="location of file to write log output to")
parser.add_option("-d", "--daemon", default=False, action="store_true",
help="daemonize (i.e., detach from the terminal)")
parser.add_option("-c", "--configfile", default=None,
help="location of the builder config file")
(opts, args) = parser.parse_args()
if not opts.configfile:
log("Must specify a config file.\n")
sys.exit(1)
# Load in the config
cfg = Config.BuilderConfig(opts.configfile)
btype = cfg.get_str("General", "comm_type")
if btype != 'passive' and btype != 'active':
log("Builder communication type must be 'active' or 'passive', not '%s'. Exiting...\n" % btype)
sys.exit(1)
build_arches = determine_build_arches(cfg)
if not len(build_arches):
log("Cannot determine buildable arches for this builder. Exiting...\n")
sys.exit(1)
cfg.load_target_configs(build_arches)
if len(cfg.targets()) == 0:
log("No useable mock buildroots configured. Exiting...\n")
sys.exit(1)
if opts.daemon:
ret=daemonize.createDaemon()
if ret:
log("Daemonizing failed!\n")
sys.exit(2)
if opts.pidfile:
f = open(opts.pidfile, 'w', 1)
f.write('%d\n' % os.getpid())
f.flush()
f.close()
if opts.logfile:
logf=open(opts.logfile, 'a')
sys.stdout=logf
sys.stderr=logf
work_dir = cfg.get_str("Directories", "builder_work_dir")
if not os.path.exists(work_dir) or not os.access(work_dir, os.R_OK):
log("%s does not exist or is not readable.\n" % work_dir)
os._exit(1)
# Stop running as root
if drop_privs(cfg.get_str("General", "builder_user")) == -1:
builder.cleanup()
os._exit(1)
# Set up our termination handler
signal.signal(signal.SIGTERM, exit_handler)
builder = Builder.Builder.new_builder(cfg, btype)
# Start doing stuff
builder.work()
log("Shutting down...\n")
builder.stop()
builder.cleanup()
time.sleep(2)
log(" done.\n");
sys.stdout.flush()
os._exit(0)
if __name__ == '__main__':
main()
Index: Config.py
===================================================================
RCS file: /cvs/fedora/extras-buildsys/builder/Config.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- Config.py 31 Aug 2005 14:13:02 -0000 1.2
+++ Config.py 28 Apr 2006 03:17:35 -0000 1.3
@@ -22,6 +22,10 @@
class InvalidTargetException(Exception): pass
+def make_target_string(distro, target, arch, repo):
+ return "%s-%s-%s-%s" % (distro, target, arch, repo)
+
+
class BuilderConfig(BaseConfig.BaseConfig):
def __init__(self, filename):
BaseConfig.BaseConfig.__init__(self, filename)
@@ -77,15 +81,21 @@
self.set_option("General", "debug", "yes")
self.set_option("General", "builder_cmd", "/usr/bin/mock")
self.set_option("General", "builder_user", "plague-builder")
+ self.set_option("General", "comm_type", "active")
self.add_section("Directories")
self.set_option("Directories", "builder_work_dir", "/tmp/builder_work")
self.set_option("Directories", "target_configs_dir", "/etc/plague/builder/targets")
- self.add_section("Network")
- self.set_option("Network", "fileserver_port", "8889")
- self.set_option("Network", "xmlrpc_port", "8888")
- self.set_option("Network", "hostname", "")
+ self.add_section("Active")
+ self.set_option("Active", "server", "")
+ self.set_option("Active", "xmlrpc_port", "8886")
+ self.set_option("Active", "fileserver_port", "8887")
+
+ self.add_section("Passive")
+ self.set_option("Passive", "hostname", "")
+ self.set_option("Passive", "xmlrpc_port", "8888")
+ self.set_option("Passive", "fileserver_port", "8889")
self.add_section("SSL")
self.set_option("SSL", "use_ssl", "yes")
@@ -159,6 +169,10 @@
def parent_cfg(self):
return self._parent_cfg
+ def __repr__(self):
+ return make_target_string(self._distro, self._target, self._basearch, self._repo)
+ __str__ = __repr__
+
def save_default_config(self, filename=None):
self.add_section("General")
self.set_option("General", "distro", "fedora")
Index: Makefile
===================================================================
RCS file: /cvs/fedora/extras-buildsys/builder/Makefile,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- Makefile 25 Aug 2005 18:15:13 -0000 1.4
+++ Makefile 28 Apr 2006 03:17:35 -0000 1.5
@@ -14,11 +14,13 @@
CONFIGDIR=$(DESTDIR)$(ETCDIR)/$(PKGNAME)/builder
FILES = \
- Config.py
+ Config.py \
+ Builder.py \
+ BuilderMock.py
install:
$(MKDIR) -p $(DESTDIR)$(BINDIR)
- $(INSTALL) -m 755 builder.py $(DESTDIR)/$(BINDIR)/$(PKGNAME)-builder
+ $(INSTALL) -m 755 main.py $(DESTDIR)/$(BINDIR)/$(PKGNAME)-builder
$(MKDIR) -p $(OTHERINSTDIR)
for file in $(FILES); do $(INSTALL) -m 644 $$file $(OTHERINSTDIR)/$$file; done
$(MKDIR) -p $(CONFIGDIR)
--- builder.py DELETED ---
- Previous message (by thread): extras-buildsys ChangeLog,1.183,1.184
- Next message (by thread): extras-buildsys/common Commands.py, NONE, 1.1 HTTPServer.py, 1.12, 1.13 Makefile, 1.11, 1.12 URLopener.py, 1.1, 1.2
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
More information about the fedora-extras-commits
mailing list