xref: /OK3568_Linux_fs/yocto/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#
2*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
3*4882a593Smuzhiyun#
4*4882a593Smuzhiyun
5*4882a593Smuzhiyunfrom django.core.management.base import BaseCommand
6*4882a593Smuzhiyunfrom django.db import transaction
7*4882a593Smuzhiyunfrom django.db.models import Q
8*4882a593Smuzhiyun
9*4882a593Smuzhiyunfrom bldcontrol.bbcontroller import getBuildEnvironmentController
10*4882a593Smuzhiyunfrom bldcontrol.models import BuildRequest, BuildEnvironment
11*4882a593Smuzhiyunfrom bldcontrol.models import BRError, BRVariable
12*4882a593Smuzhiyun
13*4882a593Smuzhiyunfrom orm.models import Build, LogMessage, Target
14*4882a593Smuzhiyun
15*4882a593Smuzhiyunimport logging
16*4882a593Smuzhiyunimport traceback
17*4882a593Smuzhiyunimport signal
18*4882a593Smuzhiyunimport os
19*4882a593Smuzhiyun
20*4882a593Smuzhiyunlogger = logging.getLogger("toaster")
21*4882a593Smuzhiyun
22*4882a593Smuzhiyun
23*4882a593Smuzhiyunclass Command(BaseCommand):
24*4882a593Smuzhiyun    args = ""
25*4882a593Smuzhiyun    help = "Schedules and executes build requests as possible. "\
26*4882a593Smuzhiyun           "Does not return (interrupt with Ctrl-C)"
27*4882a593Smuzhiyun
28*4882a593Smuzhiyun    @transaction.atomic
29*4882a593Smuzhiyun    def _selectBuildEnvironment(self):
30*4882a593Smuzhiyun        bec = getBuildEnvironmentController(lock=BuildEnvironment.LOCK_FREE)
31*4882a593Smuzhiyun        bec.be.lock = BuildEnvironment.LOCK_LOCK
32*4882a593Smuzhiyun        bec.be.save()
33*4882a593Smuzhiyun        return bec
34*4882a593Smuzhiyun
35*4882a593Smuzhiyun    @transaction.atomic
36*4882a593Smuzhiyun    def _selectBuildRequest(self):
37*4882a593Smuzhiyun        br = BuildRequest.objects.filter(state=BuildRequest.REQ_QUEUED).first()
38*4882a593Smuzhiyun        return br
39*4882a593Smuzhiyun
40*4882a593Smuzhiyun    def schedule(self):
41*4882a593Smuzhiyun        try:
42*4882a593Smuzhiyun            # select the build environment and the request to build
43*4882a593Smuzhiyun            br = self._selectBuildRequest()
44*4882a593Smuzhiyun            if br:
45*4882a593Smuzhiyun                br.state = BuildRequest.REQ_INPROGRESS
46*4882a593Smuzhiyun                br.save()
47*4882a593Smuzhiyun            else:
48*4882a593Smuzhiyun                return
49*4882a593Smuzhiyun
50*4882a593Smuzhiyun            try:
51*4882a593Smuzhiyun                bec = self._selectBuildEnvironment()
52*4882a593Smuzhiyun            except IndexError as e:
53*4882a593Smuzhiyun                # we could not find a BEC; postpone the BR
54*4882a593Smuzhiyun                br.state = BuildRequest.REQ_QUEUED
55*4882a593Smuzhiyun                br.save()
56*4882a593Smuzhiyun                logger.debug("runbuilds: No build env (%s)" % e)
57*4882a593Smuzhiyun                return
58*4882a593Smuzhiyun
59*4882a593Smuzhiyun            logger.info("runbuilds: starting build %s, environment %s" %
60*4882a593Smuzhiyun                        (br, bec.be))
61*4882a593Smuzhiyun
62*4882a593Smuzhiyun            # let the build request know where it is being executed
63*4882a593Smuzhiyun            br.environment = bec.be
64*4882a593Smuzhiyun            br.save()
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun            # this triggers an async build
67*4882a593Smuzhiyun            bec.triggerBuild(br.brbitbake, br.brlayer_set.all(),
68*4882a593Smuzhiyun                             br.brvariable_set.all(), br.brtarget_set.all(),
69*4882a593Smuzhiyun                             "%d:%d" % (br.pk, bec.be.pk))
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun        except Exception as e:
72*4882a593Smuzhiyun            logger.error("runbuilds: Error launching build %s" % e)
73*4882a593Smuzhiyun            traceback.print_exc()
74*4882a593Smuzhiyun            if "[Errno 111] Connection refused" in str(e):
75*4882a593Smuzhiyun                # Connection refused, read toaster_server.out
76*4882a593Smuzhiyun                errmsg = bec.readServerLogFile()
77*4882a593Smuzhiyun            else:
78*4882a593Smuzhiyun                errmsg = str(e)
79*4882a593Smuzhiyun
80*4882a593Smuzhiyun            BRError.objects.create(req=br, errtype=str(type(e)), errmsg=errmsg,
81*4882a593Smuzhiyun                                   traceback=traceback.format_exc())
82*4882a593Smuzhiyun            br.state = BuildRequest.REQ_FAILED
83*4882a593Smuzhiyun            br.save()
84*4882a593Smuzhiyun            bec.be.lock = BuildEnvironment.LOCK_FREE
85*4882a593Smuzhiyun            bec.be.save()
86*4882a593Smuzhiyun            # Cancel the pending build and report the exception to the UI
87*4882a593Smuzhiyun            log_object = LogMessage.objects.create(
88*4882a593Smuzhiyun                            build = br.build,
89*4882a593Smuzhiyun                            level = LogMessage.EXCEPTION,
90*4882a593Smuzhiyun                            message = errmsg)
91*4882a593Smuzhiyun            log_object.save()
92*4882a593Smuzhiyun            br.build.outcome = Build.FAILED
93*4882a593Smuzhiyun            br.build.save()
94*4882a593Smuzhiyun
95*4882a593Smuzhiyun    def archive(self):
96*4882a593Smuzhiyun        for br in BuildRequest.objects.filter(state=BuildRequest.REQ_ARCHIVE):
97*4882a593Smuzhiyun            if br.build is None:
98*4882a593Smuzhiyun                br.state = BuildRequest.REQ_FAILED
99*4882a593Smuzhiyun            else:
100*4882a593Smuzhiyun                br.state = BuildRequest.REQ_COMPLETED
101*4882a593Smuzhiyun            br.save()
102*4882a593Smuzhiyun
103*4882a593Smuzhiyun    def cleanup(self):
104*4882a593Smuzhiyun        from django.utils import timezone
105*4882a593Smuzhiyun        from datetime import timedelta
106*4882a593Smuzhiyun        # environments locked for more than 30 seconds
107*4882a593Smuzhiyun        # they should be unlocked
108*4882a593Smuzhiyun        BuildEnvironment.objects.filter(
109*4882a593Smuzhiyun            Q(buildrequest__state__in=[BuildRequest.REQ_FAILED,
110*4882a593Smuzhiyun                                       BuildRequest.REQ_COMPLETED,
111*4882a593Smuzhiyun                                       BuildRequest.REQ_CANCELLING]) &
112*4882a593Smuzhiyun            Q(lock=BuildEnvironment.LOCK_LOCK) &
113*4882a593Smuzhiyun            Q(updated__lt=timezone.now() - timedelta(seconds=30))
114*4882a593Smuzhiyun        ).update(lock=BuildEnvironment.LOCK_FREE)
115*4882a593Smuzhiyun
116*4882a593Smuzhiyun        # update all Builds that were in progress and failed to start
117*4882a593Smuzhiyun        for br in BuildRequest.objects.filter(
118*4882a593Smuzhiyun                state=BuildRequest.REQ_FAILED,
119*4882a593Smuzhiyun                build__outcome=Build.IN_PROGRESS):
120*4882a593Smuzhiyun            # transpose the launch errors in ToasterExceptions
121*4882a593Smuzhiyun            br.build.outcome = Build.FAILED
122*4882a593Smuzhiyun            for brerror in br.brerror_set.all():
123*4882a593Smuzhiyun                logger.debug("Saving error %s" % brerror)
124*4882a593Smuzhiyun                LogMessage.objects.create(build=br.build,
125*4882a593Smuzhiyun                                          level=LogMessage.EXCEPTION,
126*4882a593Smuzhiyun                                          message=brerror.errmsg)
127*4882a593Smuzhiyun            br.build.save()
128*4882a593Smuzhiyun
129*4882a593Smuzhiyun            # we don't have a true build object here; hence, toasterui
130*4882a593Smuzhiyun            # didn't have a change to release the BE lock
131*4882a593Smuzhiyun            br.environment.lock = BuildEnvironment.LOCK_FREE
132*4882a593Smuzhiyun            br.environment.save()
133*4882a593Smuzhiyun
134*4882a593Smuzhiyun        # update all BuildRequests without a build created
135*4882a593Smuzhiyun        for br in BuildRequest.objects.filter(build=None):
136*4882a593Smuzhiyun            br.build = Build.objects.create(project=br.project,
137*4882a593Smuzhiyun                                            completed_on=br.updated,
138*4882a593Smuzhiyun                                            started_on=br.created)
139*4882a593Smuzhiyun            br.build.outcome = Build.FAILED
140*4882a593Smuzhiyun            try:
141*4882a593Smuzhiyun                br.build.machine = br.brvariable_set.get(name='MACHINE').value
142*4882a593Smuzhiyun            except BRVariable.DoesNotExist:
143*4882a593Smuzhiyun                pass
144*4882a593Smuzhiyun            br.save()
145*4882a593Smuzhiyun            # transpose target information
146*4882a593Smuzhiyun            for brtarget in br.brtarget_set.all():
147*4882a593Smuzhiyun                Target.objects.create(build=br.build,
148*4882a593Smuzhiyun                                      target=brtarget.target,
149*4882a593Smuzhiyun                                      task=brtarget.task)
150*4882a593Smuzhiyun            # transpose the launch errors in ToasterExceptions
151*4882a593Smuzhiyun            for brerror in br.brerror_set.all():
152*4882a593Smuzhiyun                LogMessage.objects.create(build=br.build,
153*4882a593Smuzhiyun                                          level=LogMessage.EXCEPTION,
154*4882a593Smuzhiyun                                          message=brerror.errmsg)
155*4882a593Smuzhiyun
156*4882a593Smuzhiyun            br.build.save()
157*4882a593Smuzhiyun
158*4882a593Smuzhiyun        # Make sure the LOCK is removed for builds which have been fully
159*4882a593Smuzhiyun        # cancelled
160*4882a593Smuzhiyun        for br in BuildRequest.objects.filter(
161*4882a593Smuzhiyun                      Q(build__outcome=Build.CANCELLED) &
162*4882a593Smuzhiyun                      Q(state=BuildRequest.REQ_CANCELLING) &
163*4882a593Smuzhiyun                      ~Q(environment=None)):
164*4882a593Smuzhiyun            br.environment.lock = BuildEnvironment.LOCK_FREE
165*4882a593Smuzhiyun            br.environment.save()
166*4882a593Smuzhiyun
167*4882a593Smuzhiyun    def runbuild(self):
168*4882a593Smuzhiyun        try:
169*4882a593Smuzhiyun            self.cleanup()
170*4882a593Smuzhiyun        except Exception as e:
171*4882a593Smuzhiyun            logger.warning("runbuilds: cleanup exception %s" % str(e))
172*4882a593Smuzhiyun
173*4882a593Smuzhiyun        try:
174*4882a593Smuzhiyun            self.archive()
175*4882a593Smuzhiyun        except Exception as e:
176*4882a593Smuzhiyun            logger.warning("runbuilds: archive exception %s" % str(e))
177*4882a593Smuzhiyun
178*4882a593Smuzhiyun        try:
179*4882a593Smuzhiyun            self.schedule()
180*4882a593Smuzhiyun        except Exception as e:
181*4882a593Smuzhiyun            logger.warning("runbuilds: schedule exception %s" % str(e))
182*4882a593Smuzhiyun
183*4882a593Smuzhiyun    # Test to see if a build pre-maturely died due to a bitbake crash
184*4882a593Smuzhiyun    def check_dead_builds(self):
185*4882a593Smuzhiyun        do_cleanup = False
186*4882a593Smuzhiyun        try:
187*4882a593Smuzhiyun            for br in BuildRequest.objects.filter(state=BuildRequest.REQ_INPROGRESS):
188*4882a593Smuzhiyun                # Get the build directory
189*4882a593Smuzhiyun                if br.project.builddir:
190*4882a593Smuzhiyun                    builddir =  br.project.builddir
191*4882a593Smuzhiyun                else:
192*4882a593Smuzhiyun                    builddir = '%s-toaster-%d' % (br.environment.builddir,br.project.id)
193*4882a593Smuzhiyun                # Check log to see if there is a recent traceback
194*4882a593Smuzhiyun                toaster_ui_log = os.path.join(builddir, 'toaster_ui.log')
195*4882a593Smuzhiyun                test_file = os.path.join(builddir, '._toaster_check.txt')
196*4882a593Smuzhiyun                os.system("tail -n 50 %s > %s" % (os.path.join(builddir, 'toaster_ui.log'),test_file))
197*4882a593Smuzhiyun                traceback_text = ''
198*4882a593Smuzhiyun                is_traceback = False
199*4882a593Smuzhiyun                with open(test_file,'r') as test_file_fd:
200*4882a593Smuzhiyun                    test_file_tail = test_file_fd.readlines()
201*4882a593Smuzhiyun                    for line in test_file_tail:
202*4882a593Smuzhiyun                        if line.startswith('Traceback (most recent call last):'):
203*4882a593Smuzhiyun                            traceback_text = line
204*4882a593Smuzhiyun                            is_traceback = True
205*4882a593Smuzhiyun                        elif line.startswith('NOTE: ToasterUI waiting for events'):
206*4882a593Smuzhiyun                            # Ignore any traceback before new build start
207*4882a593Smuzhiyun                            traceback_text = ''
208*4882a593Smuzhiyun                            is_traceback = False
209*4882a593Smuzhiyun                        elif line.startswith('Note: Toaster traceback auto-stop'):
210*4882a593Smuzhiyun                            # Ignore any traceback before this previous traceback catch
211*4882a593Smuzhiyun                            traceback_text = ''
212*4882a593Smuzhiyun                            is_traceback = False
213*4882a593Smuzhiyun                        elif is_traceback:
214*4882a593Smuzhiyun                            traceback_text += line
215*4882a593Smuzhiyun                # Test the results
216*4882a593Smuzhiyun                is_stop = False
217*4882a593Smuzhiyun                if is_traceback:
218*4882a593Smuzhiyun                    # Found a traceback
219*4882a593Smuzhiyun                    errtype = 'Bitbake crash'
220*4882a593Smuzhiyun                    errmsg = 'Bitbake crash\n' + traceback_text
221*4882a593Smuzhiyun                    state = BuildRequest.REQ_FAILED
222*4882a593Smuzhiyun                    # Clean up bitbake files
223*4882a593Smuzhiyun                    bitbake_lock = os.path.join(builddir, 'bitbake.lock')
224*4882a593Smuzhiyun                    if os.path.isfile(bitbake_lock):
225*4882a593Smuzhiyun                        os.remove(bitbake_lock)
226*4882a593Smuzhiyun                    bitbake_sock = os.path.join(builddir, 'bitbake.sock')
227*4882a593Smuzhiyun                    if os.path.isfile(bitbake_sock):
228*4882a593Smuzhiyun                        os.remove(bitbake_sock)
229*4882a593Smuzhiyun                    if os.path.isfile(test_file):
230*4882a593Smuzhiyun                        os.remove(test_file)
231*4882a593Smuzhiyun                    # Add note to ignore this traceback on next check
232*4882a593Smuzhiyun                    os.system('echo "Note: Toaster traceback auto-stop" >> %s' % toaster_ui_log)
233*4882a593Smuzhiyun                    is_stop = True
234*4882a593Smuzhiyun                # Add more tests here
235*4882a593Smuzhiyun                #elif ...
236*4882a593Smuzhiyun                # Stop the build request?
237*4882a593Smuzhiyun                if is_stop:
238*4882a593Smuzhiyun                    brerror = BRError(
239*4882a593Smuzhiyun                        req = br,
240*4882a593Smuzhiyun                        errtype = errtype,
241*4882a593Smuzhiyun                        errmsg = errmsg,
242*4882a593Smuzhiyun                        traceback = traceback_text,
243*4882a593Smuzhiyun                        )
244*4882a593Smuzhiyun                    brerror.save()
245*4882a593Smuzhiyun                    br.state = state
246*4882a593Smuzhiyun                    br.save()
247*4882a593Smuzhiyun                    do_cleanup = True
248*4882a593Smuzhiyun            # Do cleanup
249*4882a593Smuzhiyun            if do_cleanup:
250*4882a593Smuzhiyun                self.cleanup()
251*4882a593Smuzhiyun        except Exception as e:
252*4882a593Smuzhiyun            logger.error("runbuilds: Error in check_dead_builds %s" % e)
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun    def handle(self, **options):
255*4882a593Smuzhiyun        pidfile_path = os.path.join(os.environ.get("BUILDDIR", "."),
256*4882a593Smuzhiyun                                    ".runbuilds.pid")
257*4882a593Smuzhiyun
258*4882a593Smuzhiyun        with open(pidfile_path, 'w') as pidfile:
259*4882a593Smuzhiyun            pidfile.write("%s" % os.getpid())
260*4882a593Smuzhiyun
261*4882a593Smuzhiyun        # Clean up any stale/failed builds from previous Toaster run
262*4882a593Smuzhiyun        self.runbuild()
263*4882a593Smuzhiyun
264*4882a593Smuzhiyun        signal.signal(signal.SIGUSR1, lambda sig, frame: None)
265*4882a593Smuzhiyun
266*4882a593Smuzhiyun        while True:
267*4882a593Smuzhiyun            sigset = signal.sigtimedwait([signal.SIGUSR1], 5)
268*4882a593Smuzhiyun            if sigset:
269*4882a593Smuzhiyun                for sig in sigset:
270*4882a593Smuzhiyun                    # Consume each captured pending event
271*4882a593Smuzhiyun                    self.runbuild()
272*4882a593Smuzhiyun            else:
273*4882a593Smuzhiyun                # Check for build exceptions
274*4882a593Smuzhiyun                self.check_dead_builds()
275*4882a593Smuzhiyun
276