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