193 lines
7.1 KiB
Python
193 lines
7.1 KiB
Python
|
from django.core.management.base import BaseCommand
|
||
|
from django.db import transaction
|
||
|
from django.db.models import Q
|
||
|
|
||
|
from bldcontrol.bbcontroller import getBuildEnvironmentController
|
||
|
from bldcontrol.models import BuildRequest, BuildEnvironment
|
||
|
from bldcontrol.models import BRError, BRVariable
|
||
|
|
||
|
from orm.models import Build, LogMessage, Target
|
||
|
|
||
|
import logging
|
||
|
import traceback
|
||
|
import signal
|
||
|
import os
|
||
|
|
||
|
logger = logging.getLogger("toaster")
|
||
|
|
||
|
|
||
|
class Command(BaseCommand):
|
||
|
args = ""
|
||
|
help = "Schedules and executes build requests as possible. "\
|
||
|
"Does not return (interrupt with Ctrl-C)"
|
||
|
|
||
|
@transaction.atomic
|
||
|
def _selectBuildEnvironment(self):
|
||
|
bec = getBuildEnvironmentController(lock=BuildEnvironment.LOCK_FREE)
|
||
|
bec.be.lock = BuildEnvironment.LOCK_LOCK
|
||
|
bec.be.save()
|
||
|
return bec
|
||
|
|
||
|
@transaction.atomic
|
||
|
def _selectBuildRequest(self):
|
||
|
br = BuildRequest.objects.filter(state=BuildRequest.REQ_QUEUED).first()
|
||
|
return br
|
||
|
|
||
|
def schedule(self):
|
||
|
try:
|
||
|
# select the build environment and the request to build
|
||
|
br = self._selectBuildRequest()
|
||
|
if br:
|
||
|
br.state = BuildRequest.REQ_INPROGRESS
|
||
|
br.save()
|
||
|
else:
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
bec = self._selectBuildEnvironment()
|
||
|
except IndexError as e:
|
||
|
# we could not find a BEC; postpone the BR
|
||
|
br.state = BuildRequest.REQ_QUEUED
|
||
|
br.save()
|
||
|
logger.debug("runbuilds: No build env")
|
||
|
return
|
||
|
|
||
|
logger.info("runbuilds: starting build %s, environment %s" %
|
||
|
(br, bec.be))
|
||
|
|
||
|
# let the build request know where it is being executed
|
||
|
br.environment = bec.be
|
||
|
br.save()
|
||
|
|
||
|
# this triggers an async build
|
||
|
bec.triggerBuild(br.brbitbake, br.brlayer_set.all(),
|
||
|
br.brvariable_set.all(), br.brtarget_set.all(),
|
||
|
"%d:%d" % (br.pk, bec.be.pk))
|
||
|
|
||
|
except Exception as e:
|
||
|
logger.error("runbuilds: Error launching build %s" % e)
|
||
|
traceback.print_exc()
|
||
|
if "[Errno 111] Connection refused" in str(e):
|
||
|
# Connection refused, read toaster_server.out
|
||
|
errmsg = bec.readServerLogFile()
|
||
|
else:
|
||
|
errmsg = str(e)
|
||
|
|
||
|
BRError.objects.create(req=br, errtype=str(type(e)), errmsg=errmsg,
|
||
|
traceback=traceback.format_exc())
|
||
|
br.state = BuildRequest.REQ_FAILED
|
||
|
br.save()
|
||
|
bec.be.lock = BuildEnvironment.LOCK_FREE
|
||
|
bec.be.save()
|
||
|
# Cancel the pending build and report the exception to the UI
|
||
|
log_object = LogMessage.objects.create(
|
||
|
build = br.build,
|
||
|
level = LogMessage.EXCEPTION,
|
||
|
message = errmsg)
|
||
|
log_object.save()
|
||
|
br.build.outcome = Build.FAILED
|
||
|
br.build.save()
|
||
|
|
||
|
def archive(self):
|
||
|
for br in BuildRequest.objects.filter(state=BuildRequest.REQ_ARCHIVE):
|
||
|
if br.build is None:
|
||
|
br.state = BuildRequest.REQ_FAILED
|
||
|
else:
|
||
|
br.state = BuildRequest.REQ_COMPLETED
|
||
|
br.save()
|
||
|
|
||
|
def cleanup(self):
|
||
|
from django.utils import timezone
|
||
|
from datetime import timedelta
|
||
|
# environments locked for more than 30 seconds
|
||
|
# they should be unlocked
|
||
|
BuildEnvironment.objects.filter(
|
||
|
Q(buildrequest__state__in=[BuildRequest.REQ_FAILED,
|
||
|
BuildRequest.REQ_COMPLETED,
|
||
|
BuildRequest.REQ_CANCELLING]) &
|
||
|
Q(lock=BuildEnvironment.LOCK_LOCK) &
|
||
|
Q(updated__lt=timezone.now() - timedelta(seconds=30))
|
||
|
).update(lock=BuildEnvironment.LOCK_FREE)
|
||
|
|
||
|
# update all Builds that were in progress and failed to start
|
||
|
for br in BuildRequest.objects.filter(
|
||
|
state=BuildRequest.REQ_FAILED,
|
||
|
build__outcome=Build.IN_PROGRESS):
|
||
|
# transpose the launch errors in ToasterExceptions
|
||
|
br.build.outcome = Build.FAILED
|
||
|
for brerror in br.brerror_set.all():
|
||
|
logger.debug("Saving error %s" % brerror)
|
||
|
LogMessage.objects.create(build=br.build,
|
||
|
level=LogMessage.EXCEPTION,
|
||
|
message=brerror.errmsg)
|
||
|
br.build.save()
|
||
|
|
||
|
# we don't have a true build object here; hence, toasterui
|
||
|
# didn't have a change to release the BE lock
|
||
|
br.environment.lock = BuildEnvironment.LOCK_FREE
|
||
|
br.environment.save()
|
||
|
|
||
|
# update all BuildRequests without a build created
|
||
|
for br in BuildRequest.objects.filter(build=None):
|
||
|
br.build = Build.objects.create(project=br.project,
|
||
|
completed_on=br.updated,
|
||
|
started_on=br.created)
|
||
|
br.build.outcome = Build.FAILED
|
||
|
try:
|
||
|
br.build.machine = br.brvariable_set.get(name='MACHINE').value
|
||
|
except BRVariable.DoesNotExist:
|
||
|
pass
|
||
|
br.save()
|
||
|
# transpose target information
|
||
|
for brtarget in br.brtarget_set.all():
|
||
|
Target.objects.create(build=br.build,
|
||
|
target=brtarget.target,
|
||
|
task=brtarget.task)
|
||
|
# transpose the launch errors in ToasterExceptions
|
||
|
for brerror in br.brerror_set.all():
|
||
|
LogMessage.objects.create(build=br.build,
|
||
|
level=LogMessage.EXCEPTION,
|
||
|
message=brerror.errmsg)
|
||
|
|
||
|
br.build.save()
|
||
|
|
||
|
# Make sure the LOCK is removed for builds which have been fully
|
||
|
# cancelled
|
||
|
for br in BuildRequest.objects.filter(
|
||
|
Q(build__outcome=Build.CANCELLED) &
|
||
|
Q(state=BuildRequest.REQ_CANCELLING) &
|
||
|
~Q(environment=None)):
|
||
|
br.environment.lock = BuildEnvironment.LOCK_FREE
|
||
|
br.environment.save()
|
||
|
|
||
|
def runbuild(self):
|
||
|
try:
|
||
|
self.cleanup()
|
||
|
except Exception as e:
|
||
|
logger.warn("runbuilds: cleanup exception %s" % str(e))
|
||
|
|
||
|
try:
|
||
|
self.archive()
|
||
|
except Exception as e:
|
||
|
logger.warn("runbuilds: archive exception %s" % str(e))
|
||
|
|
||
|
try:
|
||
|
self.schedule()
|
||
|
except Exception as e:
|
||
|
logger.warn("runbuilds: schedule exception %s" % str(e))
|
||
|
|
||
|
def handle(self, **options):
|
||
|
pidfile_path = os.path.join(os.environ.get("BUILDDIR", "."),
|
||
|
".runbuilds.pid")
|
||
|
|
||
|
with open(pidfile_path, 'w') as pidfile:
|
||
|
pidfile.write("%s" % os.getpid())
|
||
|
|
||
|
self.runbuild()
|
||
|
|
||
|
signal.signal(signal.SIGUSR1, lambda sig, frame: None)
|
||
|
|
||
|
while True:
|
||
|
signal.pause()
|
||
|
self.runbuild()
|