1# This file is part of Buildbot.  Buildbot is free software: you can
2# redistribute it and/or modify it under the terms of the GNU General Public
3# License as published by the Free Software Foundation, version 2.
4#
5# This program is distributed in the hope that it will be useful, but WITHOUT
6# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
8# details.
9#
10# You should have received a copy of the GNU General Public License along with
11# this program; if not, write to the Free Software Foundation, Inc., 51
12# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13#
14# Copyright Buildbot Team Members
15
16
17from twisted.internet import defer
18
19from buildbot.data import base
20from buildbot.data import exceptions
21from buildbot.data import types
22from buildbot.util import identifiers
23
24
25class Db2DataMixin:
26
27    def db2data(self, dbdict):
28        return {
29            'workerid': dbdict['id'],
30            'name': dbdict['name'],
31            'workerinfo': dbdict['workerinfo'],
32            'paused': dbdict['paused'],
33            'graceful': dbdict['graceful'],
34            'connected_to': [
35                {'masterid': id}
36                for id in dbdict['connected_to']],
37            'configured_on': [
38                {'masterid': c['masterid'],
39                 'builderid': c['builderid']}
40                for c in dbdict['configured_on']],
41        }
42
43
44class WorkerEndpoint(Db2DataMixin, base.Endpoint):
45
46    isCollection = False
47    pathPatterns = """
48        /workers/n:workerid
49        /workers/i:name
50        /masters/n:masterid/workers/n:workerid
51        /masters/n:masterid/workers/i:name
52        /masters/n:masterid/builders/n:builderid/workers/n:workerid
53        /masters/n:masterid/builders/n:builderid/workers/i:name
54        /builders/n:builderid/workers/n:workerid
55        /builders/n:builderid/workers/i:name
56    """
57
58    @defer.inlineCallbacks
59    def get(self, resultSpec, kwargs):
60        sldict = yield self.master.db.workers.getWorker(
61            workerid=kwargs.get('workerid'),
62            name=kwargs.get('name'),
63            masterid=kwargs.get('masterid'),
64            builderid=kwargs.get('builderid'))
65        if sldict:
66            return self.db2data(sldict)
67        return None
68
69    @defer.inlineCallbacks
70    def control(self, action, args, kwargs):
71        if action not in ("stop", "pause", "unpause", "kill"):
72            raise exceptions.InvalidControlException("action: {} is not supported".format(action))
73
74        worker = yield self.get(None, kwargs)
75        if worker is not None:
76            self.master.mq.produce(("control", "worker",
77                                    str(worker['workerid']), action),
78                                dict(reason=kwargs.get('reason', args.get('reason', 'no reason'))))
79        else:
80            raise exceptions.exceptions.InvalidPathError("worker not found")
81
82
83class WorkersEndpoint(Db2DataMixin, base.Endpoint):
84
85    isCollection = True
86    rootLinkName = 'workers'
87    pathPatterns = """
88        /workers
89        /masters/n:masterid/workers
90        /masters/n:masterid/builders/n:builderid/workers
91        /builders/n:builderid/workers
92    """
93
94    @defer.inlineCallbacks
95    def get(self, resultSpec, kwargs):
96        paused = resultSpec.popBooleanFilter('paused')
97        graceful = resultSpec.popBooleanFilter('graceful')
98        workers_dicts = yield self.master.db.workers.getWorkers(
99            builderid=kwargs.get('builderid'),
100            masterid=kwargs.get('masterid'),
101            paused=paused,
102            graceful=graceful)
103        return [self.db2data(w) for w in workers_dicts]
104
105
106class MasterBuilderEntityType(types.Entity):
107    masterid = types.Integer()
108    builderid = types.Integer()
109
110
111class MasterIdEntityType(types.Entity):
112    masterid = types.Integer()
113
114
115class Worker(base.ResourceType):
116
117    name = "worker"
118    plural = "workers"
119    endpoints = [WorkerEndpoint, WorkersEndpoint]
120    keyField = 'workerid'
121    eventPathPatterns = """
122        /workers/:workerid
123    """
124    subresources = ["Build"]
125
126    class EntityType(types.Entity):
127        workerid = types.Integer()
128        name = types.String()
129        connected_to = types.List(of=MasterIdEntityType("master_id", 'MasterId'))
130        configured_on = types.List(of=MasterBuilderEntityType("master_builder", 'MasterBuilder'))
131        workerinfo = types.JsonObject()
132        paused = types.Boolean()
133        graceful = types.Boolean()
134    entityType = EntityType(name, 'Worker')
135
136    @base.updateMethod
137    # returns a Deferred that returns None
138    def workerConfigured(self, workerid, masterid, builderids):
139        return self.master.db.workers.workerConfigured(
140            workerid=workerid,
141            masterid=masterid,
142            builderids=builderids)
143
144    @base.updateMethod
145    def findWorkerId(self, name):
146        if not identifiers.isIdentifier(50, name):
147            raise ValueError(
148                "Worker name %r is not a 50-character identifier" % (name,))
149        return self.master.db.workers.findWorkerId(name)
150
151    @base.updateMethod
152    @defer.inlineCallbacks
153    def workerConnected(self, workerid, masterid, workerinfo):
154        yield self.master.db.workers.workerConnected(
155            workerid=workerid,
156            masterid=masterid,
157            workerinfo=workerinfo)
158        bs = yield self.master.data.get(('workers', workerid))
159        self.produceEvent(bs, 'connected')
160
161    @base.updateMethod
162    @defer.inlineCallbacks
163    def workerDisconnected(self, workerid, masterid):
164        yield self.master.db.workers.workerDisconnected(
165            workerid=workerid,
166            masterid=masterid)
167        bs = yield self.master.data.get(('workers', workerid))
168        self.produceEvent(bs, 'disconnected')
169
170    @base.updateMethod
171    @defer.inlineCallbacks
172    def workerMissing(self, workerid, masterid, last_connection, notify):
173        bs = yield self.master.data.get(('workers', workerid))
174        bs['last_connection'] = last_connection
175        bs['notify'] = notify
176        self.produceEvent(bs, 'missing')
177
178    @base.updateMethod
179    @defer.inlineCallbacks
180    def setWorkerState(self, workerid, paused, graceful):
181        yield self.master.db.workers.setWorkerState(
182            workerid=workerid,
183            paused=paused,
184            graceful=graceful)
185        bs = yield self.master.data.get(('workers', workerid))
186        self.produceEvent(bs, 'state_updated')
187
188    @base.updateMethod
189    def deconfigureAllWorkersForMaster(self, masterid):
190        # unconfigure all workers for this master
191        return self.master.db.workers.deconfigureAllWorkersForMaster(
192            masterid=masterid)
193
194    def _masterDeactivated(self, masterid):
195        return self.deconfigureAllWorkersForMaster(masterid)
196