aboutsummaryrefslogtreecommitdiffstats
path: root/lib/toaster/bldcontrol/bbcontroller.py
blob: d2b2a236bdf52b81eeef6cd0bccfb727b078a55b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#
# ex:ts=4:sw=4:sts=4:et
# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
#
# BitBake Toaster Implementation
#
# Copyright (C) 2014        Intel Corporation
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# 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 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.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


import os
import sys
import re
from django.db import transaction
from django.db.models import Q
from bldcontrol.models import BuildEnvironment, BRLayer, BRVariable, BRTarget
import subprocess

from toastermain import settings


# load Bitbake components
path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
sys.path.insert(0, path)
import bb.server.xmlrpc

class BitbakeController(object):
    """ This is the basic class that controlls a bitbake server.
        It is outside the scope of this class on how the server is started and aquired
    """

    def __init__(self, connection):
        self.connection = connection

    def _runCommand(self, command):
        result, error = self.connection.connection.runCommand(command)
        if error:
            raise Exception(error)
        return result

    def disconnect(self):
        return self.connection.removeClient()

    def setVariable(self, name, value):
        return self._runCommand(["setVariable", name, value])

    def build(self, targets, task = None):
        if task is None:
            task = "build"
        return self._runCommand(["buildTargets", targets, task])



def getBuildEnvironmentController(**kwargs):
    """ Gets you a BuildEnvironmentController that encapsulates a build environment,
        based on the query dictionary sent in.

        This is used to retrieve, for example, the currently running BE from inside
        the toaster UI, or find a new BE to start a new build in it.

        The return object MUST always be a BuildEnvironmentController.
    """
    be = BuildEnvironment.objects.filter(Q(**kwargs))[0]
    if be.betype == BuildEnvironment.TYPE_LOCAL:
        return LocalhostBEController(be)
    elif be.betype == BuildEnvironment.TYPE_SSH:
        return SSHBEController(be)
    else:
        raise Exception("FIXME: Implement BEC for type %s" % str(be.betype))



class BuildEnvironmentController(object):
    """ BuildEnvironmentController (BEC) is the abstract class that defines the operations that MUST
        or SHOULD be supported by a Build Environment. It is used to establish the framework, and must
        not be instantiated directly by the user.

        Use the "getBuildEnvironmentController()" function to get a working BEC for your remote.

        How the BuildEnvironments are discovered is outside the scope of this class.

        You must derive this class to teach Toaster how to operate in your own infrastructure.
        We provide some specific BuildEnvironmentController classes that can be used either to
        directly set-up Toaster infrastructure, or as a model for your own infrastructure set:

            * Localhost controller will run the Toaster BE on the same account as the web server
        (current user if you are using the the Django development web server)
        on the local machine, with the "build/" directory under the "poky/" source checkout directory.
        Bash is expected to be available.

            * SSH controller will run the Toaster BE on a remote machine, where the current user
        can connect without raise Exception("FIXME: implement")word (set up with either ssh-agent or raise Exception("FIXME: implement")phrase-less key authentication)

    """
    def __init__(self, be):
        """ Takes a BuildEnvironment object as parameter that points to the settings of the BE.
        """
        self.be = be
        self.connection = None

    def startBBServer(self):
        """ Starts a  BB server with Toaster toasterui set up to record the builds, an no controlling UI.
            After this method executes, self.be bbaddress/bbport MUST point to a running and free server,
            and the bbstate MUST be  updated to "started".
        """
        raise Exception("Must override in order to actually start the BB server")

    def stopBBServer(self):
        """ Stops the currently running BB server.
            The bbstate MUST be updated to "stopped".
            self.connection must be none.
        """

    def setLayers(self,ls):
        """ Sets the layer variables in the config file, after validating local layer paths.
            The layer paths must be in a list of BRLayer object
        """
        raise Exception("Must override setLayers")


    def getBBController(self):
        """ returns a BitbakeController to an already started server; this is the point where the server
            starts if needed; or reconnects to the server if we can
        """
        if not self.connection:
            self.startBBServer()
            self.be.lock = BuildEnvironment.LOCK_RUNNING
            self.be.save()

        server = bb.server.xmlrpc.BitBakeXMLRPCClient()
        server.initServer()
        server.saveConnectionDetails("%s:%s" % (self.be.bbaddress, self.be.bbport))
        self.connection = server.establishConnection([])

        self.be.bbtoken = self.connection.transport.connection_token
        self.be.save()

        return BitbakeController(self.connection)

    def getArtifact(path):
        """ This call returns an artifact identified by the 'path'. How 'path' is interpreted as
            up to the implementing BEC. The return MUST be a REST URL where a GET will actually return
            the content of the artifact, e.g. for use as a "download link" in a web UI.
        """
        raise Exception("Must return the REST URL of the artifact")

    def release(self):
        """ This stops the server and releases any resources. After this point, all resources
            are un-available for further reference
        """
        raise Exception("Must override BE release")

class ShellCmdException(Exception):
    pass

class LocalhostBEController(BuildEnvironmentController):
    """ Implementation of the BuildEnvironmentController for the localhost;
        this controller manages the default build directory,
        the server setup and system start and stop for the localhost-type build environment

        The address field is used as working directory; if not set, the build/ directory
        is created
    """

    def __init__(self, be):
        super(LocalhostBEController, self).__init__(be)
        from os.path import dirname as DN
        self.cwd = DN(DN(DN(DN(DN(os.path.realpath(__file__))))))
        if self.be.address is None or len(self.be.address) == 0:
            self.be.address = "build"
            self.be.save()
        self.bwd = self.be.address
        self.dburl = settings.getDATABASE_URL()

        # transform relative paths to absolute ones
        if not self.bwd.startswith("/"):
            self.bwd = os.path.join(self.cwd, self.bwd)
        self._createBE()

    def _shellcmd(self, command):
        p = subprocess.Popen(command, cwd=self.cwd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        (out,err) = p.communicate()
        if p.returncode:
            if len(err) == 0:
                err = "command: %s" % command
            else:
                err = "command: %s \n%s" % (command, err)
            raise ShellCmdException(err)
        else:
            return out

    def _createBE(self):
        assert self.cwd and os.path.exists(self.cwd)
        self._shellcmd("bash -c \"source %s/oe-init-build-env %s\"" % (self.cwd, self.bwd))

    def startBBServer(self):
        assert self.cwd and os.path.exists(self.cwd)
        print self._shellcmd("bash -c \"source %s/oe-init-build-env %s && DATABASE_URL=%s source toaster start noweb && sleep 1\"" % (self.cwd, self.bwd, self.dburl))
        # FIXME unfortunate sleep 1 - we need to make sure that bbserver is started and the toaster ui is connected
        # but since they start async without any return, we just wait a bit
        print "Started server"
        assert self.cwd and os.path.exists(self.bwd)
        self.be.bbaddress = "localhost"
        self.be.bbport = "8200"
        self.be.bbstate = BuildEnvironment.SERVER_STARTED
        self.be.save()

    def stopBBServer(self):
        assert self.cwd
        print self._shellcmd("bash -c \"source %s/oe-init-build-env %s && %s source toaster stop\"" %
            (self.cwd, self.bwd, (lambda: "" if self.be.bbtoken is None else "BBTOKEN=%s" % self.be.bbtoken)()))
        self.be.bbstate = BuildEnvironment.SERVER_STOPPED
        self.be.save()
        print "Stopped server"

    def setLayers(self, layers):
        assert self.cwd is not None
        layerconf = os.path.join(self.bwd, "conf/bblayers.conf")
        if not os.path.exists(layerconf):
            raise Exception("BE is not consistent: bblayers.conf file missing at ", layerconf)
        return True

    def release(self):
        assert self.cwd and os.path.exists(self.bwd)
        import shutil
        shutil.rmtree(os.path.join(self.cwd, "build"))
        assert not os.path.exists(self.bwd)