blob: 834e32b36ff5e54136c8e8b1b13e70f19a358045 [file] [log] [blame]
Brad Bishopc342db32019-05-15 21:57:59 -04001#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4
Brad Bishopd7bf8c12018-02-25 22:55:05 -05005from django.core.management.base import BaseCommand
Patrick Williamsc124f4f2015-09-15 14:41:29 -05006from django.db import transaction
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05007from django.db.models import Q
8
9from bldcontrol.bbcontroller import getBuildEnvironmentController
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050010from bldcontrol.models import BuildRequest, BuildEnvironment
11from bldcontrol.models import BRError, BRVariable
12
Patrick Williamsc0f7c042017-02-23 20:41:17 -060013from orm.models import Build, LogMessage, Target
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050014
Patrick Williamsc124f4f2015-09-15 14:41:29 -050015import logging
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050016import traceback
Patrick Williamsc0f7c042017-02-23 20:41:17 -060017import signal
Brad Bishop6e60e8b2018-02-01 10:27:11 -050018import os
Patrick Williamsc124f4f2015-09-15 14:41:29 -050019
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050020logger = logging.getLogger("toaster")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050021
Brad Bishop6e60e8b2018-02-01 10:27:11 -050022
Brad Bishopd7bf8c12018-02-25 22:55:05 -050023class Command(BaseCommand):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060024 args = ""
25 help = "Schedules and executes build requests as possible. "\
26 "Does not return (interrupt with Ctrl-C)"
Patrick Williamsc124f4f2015-09-15 14:41:29 -050027
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050028 @transaction.atomic
Patrick Williamsc124f4f2015-09-15 14:41:29 -050029 def _selectBuildEnvironment(self):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060030 bec = getBuildEnvironmentController(lock=BuildEnvironment.LOCK_FREE)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050031 bec.be.lock = BuildEnvironment.LOCK_LOCK
32 bec.be.save()
33 return bec
34
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050035 @transaction.atomic
Patrick Williamsc124f4f2015-09-15 14:41:29 -050036 def _selectBuildRequest(self):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050037 br = BuildRequest.objects.filter(state=BuildRequest.REQ_QUEUED).first()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050038 return br
39
40 def schedule(self):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050041 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050042 # select the build environment and the request to build
43 br = self._selectBuildRequest()
44 if br:
45 br.state = BuildRequest.REQ_INPROGRESS
46 br.save()
47 else:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050048 return
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050049
Patrick Williamsc124f4f2015-09-15 14:41:29 -050050 try:
51 bec = self._selectBuildEnvironment()
52 except IndexError as e:
53 # we could not find a BEC; postpone the BR
54 br.state = BuildRequest.REQ_QUEUED
55 br.save()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080056 logger.debug("runbuilds: No build env (%s)" % e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050057 return
58
Brad Bishop6e60e8b2018-02-01 10:27:11 -050059 logger.info("runbuilds: starting build %s, environment %s" %
Patrick Williamsc0f7c042017-02-23 20:41:17 -060060 (br, bec.be))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050061
62 # let the build request know where it is being executed
63 br.environment = bec.be
64 br.save()
65
66 # this triggers an async build
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050067 bec.triggerBuild(br.brbitbake, br.brlayer_set.all(),
68 br.brvariable_set.all(), br.brtarget_set.all(),
69 "%d:%d" % (br.pk, bec.be.pk))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050070
71 except Exception as e:
72 logger.error("runbuilds: Error launching build %s" % e)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060073 traceback.print_exc()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050074 if "[Errno 111] Connection refused" in str(e):
75 # Connection refused, read toaster_server.out
76 errmsg = bec.readServerLogFile()
77 else:
78 errmsg = str(e)
79
Patrick Williamsc0f7c042017-02-23 20:41:17 -060080 BRError.objects.create(req=br, errtype=str(type(e)), errmsg=errmsg,
81 traceback=traceback.format_exc())
Patrick Williamsc124f4f2015-09-15 14:41:29 -050082 br.state = BuildRequest.REQ_FAILED
83 br.save()
84 bec.be.lock = BuildEnvironment.LOCK_FREE
85 bec.be.save()
Brad Bishopd7bf8c12018-02-25 22:55:05 -050086 # Cancel the pending build and report the exception to the UI
87 log_object = LogMessage.objects.create(
88 build = br.build,
89 level = LogMessage.EXCEPTION,
90 message = errmsg)
91 log_object.save()
92 br.build.outcome = Build.FAILED
93 br.build.save()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050094
95 def archive(self):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060096 for br in BuildRequest.objects.filter(state=BuildRequest.REQ_ARCHIVE):
Brad Bishop6e60e8b2018-02-01 10:27:11 -050097 if br.build is None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050098 br.state = BuildRequest.REQ_FAILED
Patrick Williamsd7e96312015-09-22 08:09:05 -050099 else:
100 br.state = BuildRequest.REQ_COMPLETED
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500101 br.save()
102
103 def cleanup(self):
104 from django.utils import timezone
105 from datetime import timedelta
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500106 # environments locked for more than 30 seconds
107 # they should be unlocked
108 BuildEnvironment.objects.filter(
109 Q(buildrequest__state__in=[BuildRequest.REQ_FAILED,
110 BuildRequest.REQ_COMPLETED,
111 BuildRequest.REQ_CANCELLING]) &
112 Q(lock=BuildEnvironment.LOCK_LOCK) &
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600113 Q(updated__lt=timezone.now() - timedelta(seconds=30))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500114 ).update(lock=BuildEnvironment.LOCK_FREE)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500115
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500116 # update all Builds that were in progress and failed to start
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500117 for br in BuildRequest.objects.filter(
118 state=BuildRequest.REQ_FAILED,
119 build__outcome=Build.IN_PROGRESS):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500120 # transpose the launch errors in ToasterExceptions
121 br.build.outcome = Build.FAILED
122 for brerror in br.brerror_set.all():
123 logger.debug("Saving error %s" % brerror)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500124 LogMessage.objects.create(build=br.build,
125 level=LogMessage.EXCEPTION,
126 message=brerror.errmsg)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500127 br.build.save()
128
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500129 # we don't have a true build object here; hence, toasterui
130 # didn't have a change to release the BE lock
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500131 br.environment.lock = BuildEnvironment.LOCK_FREE
132 br.environment.save()
133
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500134 # update all BuildRequests without a build created
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600135 for br in BuildRequest.objects.filter(build=None):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500136 br.build = Build.objects.create(project=br.project,
137 completed_on=br.updated,
138 started_on=br.created)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500139 br.build.outcome = Build.FAILED
140 try:
141 br.build.machine = br.brvariable_set.get(name='MACHINE').value
142 except BRVariable.DoesNotExist:
143 pass
144 br.save()
145 # transpose target information
146 for brtarget in br.brtarget_set.all():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500147 Target.objects.create(build=br.build,
148 target=brtarget.target,
149 task=brtarget.task)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500150 # transpose the launch errors in ToasterExceptions
151 for brerror in br.brerror_set.all():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500152 LogMessage.objects.create(build=br.build,
153 level=LogMessage.EXCEPTION,
154 message=brerror.errmsg)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500155
156 br.build.save()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500157
158 # Make sure the LOCK is removed for builds which have been fully
159 # cancelled
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500160 for br in BuildRequest.objects.filter(
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600161 Q(build__outcome=Build.CANCELLED) &
162 Q(state=BuildRequest.REQ_CANCELLING) &
163 ~Q(environment=None)):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500164 br.environment.lock = BuildEnvironment.LOCK_FREE
165 br.environment.save()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500166
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600167 def runbuild(self):
168 try:
169 self.cleanup()
170 except Exception as e:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500171 logger.warning("runbuilds: cleanup exception %s" % str(e))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600172
173 try:
174 self.archive()
175 except Exception as e:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500176 logger.warning("runbuilds: archive exception %s" % str(e))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600177
178 try:
179 self.schedule()
180 except Exception as e:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500181 logger.warning("runbuilds: schedule exception %s" % str(e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500182
Andrew Geissler9aee5002022-03-30 16:27:02 +0000183 # Test to see if a build pre-maturely died due to a bitbake crash
184 def check_dead_builds(self):
185 do_cleanup = False
186 try:
187 for br in BuildRequest.objects.filter(state=BuildRequest.REQ_INPROGRESS):
188 # Get the build directory
189 if br.project.builddir:
190 builddir = br.project.builddir
191 else:
192 builddir = '%s-toaster-%d' % (br.environment.builddir,br.project.id)
193 # Check log to see if there is a recent traceback
194 toaster_ui_log = os.path.join(builddir, 'toaster_ui.log')
195 test_file = os.path.join(builddir, '._toaster_check.txt')
196 os.system("tail -n 50 %s > %s" % (os.path.join(builddir, 'toaster_ui.log'),test_file))
197 traceback_text = ''
198 is_traceback = False
199 with open(test_file,'r') as test_file_fd:
200 test_file_tail = test_file_fd.readlines()
201 for line in test_file_tail:
202 if line.startswith('Traceback (most recent call last):'):
203 traceback_text = line
204 is_traceback = True
205 elif line.startswith('NOTE: ToasterUI waiting for events'):
206 # Ignore any traceback before new build start
207 traceback_text = ''
208 is_traceback = False
209 elif line.startswith('Note: Toaster traceback auto-stop'):
210 # Ignore any traceback before this previous traceback catch
211 traceback_text = ''
212 is_traceback = False
213 elif is_traceback:
214 traceback_text += line
215 # Test the results
216 is_stop = False
217 if is_traceback:
218 # Found a traceback
219 errtype = 'Bitbake crash'
220 errmsg = 'Bitbake crash\n' + traceback_text
221 state = BuildRequest.REQ_FAILED
222 # Clean up bitbake files
223 bitbake_lock = os.path.join(builddir, 'bitbake.lock')
224 if os.path.isfile(bitbake_lock):
225 os.remove(bitbake_lock)
226 bitbake_sock = os.path.join(builddir, 'bitbake.sock')
227 if os.path.isfile(bitbake_sock):
228 os.remove(bitbake_sock)
229 if os.path.isfile(test_file):
230 os.remove(test_file)
231 # Add note to ignore this traceback on next check
232 os.system('echo "Note: Toaster traceback auto-stop" >> %s' % toaster_ui_log)
233 is_stop = True
234 # Add more tests here
235 #elif ...
236 # Stop the build request?
237 if is_stop:
238 brerror = BRError(
239 req = br,
240 errtype = errtype,
241 errmsg = errmsg,
242 traceback = traceback_text,
243 )
244 brerror.save()
245 br.state = state
246 br.save()
247 do_cleanup = True
248 # Do cleanup
249 if do_cleanup:
250 self.cleanup()
251 except Exception as e:
252 logger.error("runbuilds: Error in check_dead_builds %s" % e)
253
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500254 def handle(self, **options):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500255 pidfile_path = os.path.join(os.environ.get("BUILDDIR", "."),
256 ".runbuilds.pid")
257
258 with open(pidfile_path, 'w') as pidfile:
259 pidfile.write("%s" % os.getpid())
260
Andrew Geissler9aee5002022-03-30 16:27:02 +0000261 # Clean up any stale/failed builds from previous Toaster run
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600262 self.runbuild()
263
264 signal.signal(signal.SIGUSR1, lambda sig, frame: None)
265
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500266 while True:
Andrew Geissler9aee5002022-03-30 16:27:02 +0000267 sigset = signal.sigtimedwait([signal.SIGUSR1], 5)
268 if sigset:
269 for sig in sigset:
270 # Consume each captured pending event
271 self.runbuild()
272 else:
273 # Check for build exceptions
274 self.check_dead_builds()
275