1#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4
5from __future__ import unicode_literals
6from django.db import models
7from django.utils.encoding import force_str
8from orm.models import Project, Build, Layer_Version
9
10import logging
11logger = logging.getLogger("toaster")
12# a BuildEnvironment is the equivalent of the "build/" directory on the localhost
13class BuildEnvironment(models.Model):
14    SERVER_STOPPED = 0
15    SERVER_STARTED = 1
16    SERVER_STATE = (
17        (SERVER_STOPPED, "stopped"),
18        (SERVER_STARTED, "started"),
19    )
20
21    TYPE_LOCAL = 0
22    TYPE = (
23        (TYPE_LOCAL, "local"),
24    )
25
26    LOCK_FREE = 0
27    LOCK_LOCK = 1
28    LOCK_RUNNING = 2
29    LOCK_STATE = (
30        (LOCK_FREE, "free"),
31        (LOCK_LOCK, "lock"),
32        (LOCK_RUNNING, "running"),
33    )
34
35    address     = models.CharField(max_length = 254)
36    betype      = models.IntegerField(choices = TYPE)
37    bbaddress   = models.CharField(max_length = 254, blank = True)
38    bbport      = models.IntegerField(default = -1)
39    bbtoken     = models.CharField(max_length = 126, blank = True)
40    bbstate     = models.IntegerField(choices = SERVER_STATE, default = SERVER_STOPPED)
41    sourcedir   = models.CharField(max_length = 512, blank = True)
42    builddir    = models.CharField(max_length = 512, blank = True)
43    lock        = models.IntegerField(choices = LOCK_STATE, default = LOCK_FREE)
44    created     = models.DateTimeField(auto_now_add = True)
45    updated     = models.DateTimeField(auto_now = True)
46
47    def get_artifact(self, path):
48        if self.betype == BuildEnvironment.TYPE_LOCAL:
49            return open(path, "r")
50        raise NotImplementedError("FIXME: artifact download not implemented "\
51                                  "for build environment type %s" % \
52                                  self.get_betype_display())
53
54    def has_artifact(self, path):
55        import os
56        if self.betype == BuildEnvironment.TYPE_LOCAL:
57            return os.path.exists(path)
58        raise NotImplementedError("FIXME: has artifact not implemented for "\
59                                  "build environment type %s" % \
60                                  self.get_betype_display())
61
62# a BuildRequest is a request that the scheduler will build using a BuildEnvironment
63# the build request queue is the table itself, ordered by state
64
65class BuildRequest(models.Model):
66    REQ_CREATED = 0
67    REQ_QUEUED = 1
68    REQ_INPROGRESS = 2
69    REQ_FAILED = 3
70    REQ_DELETED = 4
71    REQ_CANCELLING = 5
72    REQ_COMPLETED = 6
73    REQ_ARCHIVE = 7
74
75    REQUEST_STATE = (
76        (REQ_CREATED, "created"),
77        (REQ_QUEUED, "queued"),
78        (REQ_INPROGRESS, "in progress"),
79        (REQ_FAILED, "failed"),
80        (REQ_DELETED, "deleted"),
81        (REQ_CANCELLING, "cancelling"),
82        (REQ_COMPLETED, "completed"),
83        (REQ_ARCHIVE, "archive"),
84    )
85
86    search_allowed_fields = ("brtarget__target", "build__project__name")
87
88    project     = models.ForeignKey(Project, on_delete=models.CASCADE)
89    build       = models.OneToOneField(Build, on_delete=models.CASCADE, null = True)     # TODO: toasterui should set this when Build is created
90    environment = models.ForeignKey(BuildEnvironment, on_delete=models.CASCADE, null = True)
91    state       = models.IntegerField(choices = REQUEST_STATE, default = REQ_CREATED)
92    created     = models.DateTimeField(auto_now_add = True)
93    updated     = models.DateTimeField(auto_now = True)
94
95    def __init__(self, *args, **kwargs):
96        super(BuildRequest, self).__init__(*args, **kwargs)
97        # Save the old state in case it's about to be modified
98        self.old_state = self.state
99
100    def save(self, *args, **kwargs):
101        # Check that the state we're trying to set is not going backwards
102        # e.g. from REQ_FAILED to REQ_INPROGRESS
103        if self.old_state != self.state and self.old_state > self.state:
104            logger.warning("Invalid state change requested: "
105                           "Cannot go from %s to %s - ignoring request" %
106                           (BuildRequest.REQUEST_STATE[self.old_state][1],
107                            BuildRequest.REQUEST_STATE[self.state][1])
108                          )
109            # Set property back to the old value
110            self.state = self.old_state
111            return
112
113        super(BuildRequest, self).save(*args, **kwargs)
114
115
116    def get_duration(self):
117        return (self.updated - self.created).total_seconds()
118
119    def get_sorted_target_list(self):
120        tgts = self.brtarget_set.order_by( 'target' );
121        return( tgts );
122
123    def get_machine(self):
124        return self.brvariable_set.get(name="MACHINE").value
125
126    def __str__(self):
127        return force_str('%s %s' % (self.project, self.get_state_display()))
128
129# These tables specify the settings for running an actual build.
130# They MUST be kept in sync with the tables in orm.models.Project*
131
132
133class BRLayer(models.Model):
134    req = models.ForeignKey(BuildRequest, on_delete=models.CASCADE)
135    name = models.CharField(max_length=100)
136    giturl = models.CharField(max_length=254, null=True)
137    local_source_dir = models.CharField(max_length=254, null=True)
138    commit = models.CharField(max_length=254, null=True)
139    dirpath = models.CharField(max_length=254, null=True)
140    layer_version = models.ForeignKey(Layer_Version, on_delete=models.CASCADE, null=True)
141
142class BRBitbake(models.Model):
143    req         = models.OneToOneField(BuildRequest, on_delete=models.CASCADE)    # only one bitbake for a request
144    giturl      = models.CharField(max_length =254)
145    commit      = models.CharField(max_length = 254)
146    dirpath     = models.CharField(max_length = 254)
147
148class BRVariable(models.Model):
149    req         = models.ForeignKey(BuildRequest, on_delete=models.CASCADE)
150    name        = models.CharField(max_length=100)
151    value       = models.TextField(blank = True)
152
153class BRTarget(models.Model):
154    req         = models.ForeignKey(BuildRequest, on_delete=models.CASCADE)
155    target      = models.CharField(max_length=100)
156    task        = models.CharField(max_length=100, null=True)
157
158class BRError(models.Model):
159    req         = models.ForeignKey(BuildRequest, on_delete=models.CASCADE)
160    errtype     = models.CharField(max_length=100)
161    errmsg      = models.TextField()
162    traceback   = models.TextField()
163
164    def __str__(self):
165        return "%s (%s)" % (self.errmsg, self.req)
166