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 18from twisted.python import log 19 20from buildbot.data import base 21from buildbot.data import resultspec 22from buildbot.data import types 23from buildbot.process.results import RETRY 24from buildbot.util import epoch2datetime 25 26# time, in minutes, after which a master that hasn't checked in will be 27# marked as inactive 28EXPIRE_MINUTES = 10 29 30 31def _db2data(master): 32 return dict(masterid=master['id'], 33 name=master['name'], 34 active=master['active'], 35 last_active=master['last_active']) 36 37 38class MasterEndpoint(base.Endpoint): 39 40 isCollection = False 41 pathPatterns = """ 42 /masters/n:masterid 43 /builders/n:builderid/masters/n:masterid 44 """ 45 46 @defer.inlineCallbacks 47 def get(self, resultSpec, kwargs): 48 # if a builder is given, only return the master if it's associated with 49 # this builder 50 if 'builderid' in kwargs: 51 builder = yield self.master.db.builders.getBuilder( 52 builderid=kwargs['builderid']) 53 if not builder or kwargs['masterid'] not in builder['masterids']: 54 return None 55 m = yield self.master.db.masters.getMaster(kwargs['masterid']) 56 return _db2data(m) if m else None 57 58 59class MastersEndpoint(base.Endpoint): 60 61 isCollection = True 62 pathPatterns = """ 63 /masters 64 /builders/n:builderid/masters 65 """ 66 rootLinkName = 'masters' 67 68 @defer.inlineCallbacks 69 def get(self, resultSpec, kwargs): 70 masterlist = yield self.master.db.masters.getMasters() 71 if 'builderid' in kwargs: 72 builder = yield self.master.db.builders.getBuilder( 73 builderid=kwargs['builderid']) 74 if builder: 75 masterids = set(builder['masterids']) 76 masterlist = [m for m in masterlist if m['id'] in masterids] 77 else: 78 masterlist = [] 79 return [_db2data(m) for m in masterlist] 80 81 82class Master(base.ResourceType): 83 84 name = "master" 85 plural = "masters" 86 endpoints = [MasterEndpoint, MastersEndpoint] 87 eventPathPatterns = """ 88 /masters/:masterid 89 """ 90 keyField = "masterid" 91 subresources = ["Builder"] 92 93 class EntityType(types.Entity): 94 masterid = types.Integer() 95 name = types.String() 96 active = types.Boolean() 97 last_active = types.DateTime() 98 entityType = EntityType(name, 'Master') 99 100 @base.updateMethod 101 @defer.inlineCallbacks 102 def masterActive(self, name, masterid): 103 activated = yield self.master.db.masters.setMasterState( 104 masterid=masterid, active=True) 105 if activated: 106 self.produceEvent( 107 dict(masterid=masterid, name=name, active=True), 108 'started') 109 110 @base.updateMethod 111 @defer.inlineCallbacks 112 def expireMasters(self, forceHouseKeeping=False): 113 too_old = epoch2datetime(self.master.reactor.seconds() - 60 * EXPIRE_MINUTES) 114 masters = yield self.master.db.masters.getMasters() 115 for m in masters: 116 if m['last_active'] is not None and m['last_active'] >= too_old: 117 continue 118 119 # mark the master inactive, and send a message on its behalf 120 deactivated = yield self.master.db.masters.setMasterState( 121 masterid=m['id'], active=False) 122 if deactivated: 123 yield self._masterDeactivated(m['id'], m['name']) 124 elif forceHouseKeeping: 125 yield self._masterDeactivatedHousekeeping(m['id'], m['name']) 126 127 @base.updateMethod 128 @defer.inlineCallbacks 129 def masterStopped(self, name, masterid): 130 deactivated = yield self.master.db.masters.setMasterState( 131 masterid=masterid, active=False) 132 if deactivated: 133 yield self._masterDeactivated(masterid, name) 134 135 @defer.inlineCallbacks 136 def _masterDeactivatedHousekeeping(self, masterid, name): 137 log.msg("doing housekeeping for master {} {}".format(masterid, name)) 138 139 # common code for deactivating a master 140 yield self.master.data.rtypes.worker._masterDeactivated( 141 masterid=masterid) 142 yield self.master.data.rtypes.builder._masterDeactivated( 143 masterid=masterid) 144 yield self.master.data.rtypes.scheduler._masterDeactivated( 145 masterid=masterid) 146 yield self.master.data.rtypes.changesource._masterDeactivated( 147 masterid=masterid) 148 149 # for each build running on that instance.. 150 builds = yield self.master.data.get(('builds',), 151 filters=[resultspec.Filter('masterid', 'eq', 152 [masterid]), 153 resultspec.Filter('complete', 'eq', [False])]) 154 for build in builds: 155 # stop any running steps.. 156 steps = yield self.master.data.get( 157 ('builds', build['buildid'], 'steps'), 158 filters=[resultspec.Filter('results', 'eq', [None])]) 159 for step in steps: 160 # finish remaining logs for those steps.. 161 logs = yield self.master.data.get( 162 ('steps', step['stepid'], 'logs'), 163 filters=[resultspec.Filter('complete', 'eq', 164 [False])]) 165 for _log in logs: 166 yield self.master.data.updates.finishLog( 167 logid=_log['logid']) 168 yield self.master.data.updates.finishStep( 169 stepid=step['stepid'], results=RETRY, hidden=False) 170 # then stop the build itself 171 yield self.master.data.updates.finishBuild( 172 buildid=build['buildid'], results=RETRY) 173 174 # unclaim all of the build requests owned by the deactivated instance 175 buildrequests = yield self.master.db.buildrequests.getBuildRequests( 176 complete=False, claimed=masterid) 177 yield self.master.db.buildrequests.unclaimBuildRequests( 178 brids=[br['buildrequestid'] for br in buildrequests]) 179 180 @defer.inlineCallbacks 181 def _masterDeactivated(self, masterid, name): 182 yield self._masterDeactivatedHousekeeping(masterid, name) 183 184 self.produceEvent( 185 dict(masterid=masterid, name=name, active=False), 186 'stopped') 187