3 let assert = require('assert');
5 require('./v3-models.js');
7 let BuildbotSyncer = require('./buildbot-syncer').BuildbotSyncer;
9 class BuildbotTriggerable {
10 constructor(config, remote, buildbotRemote, slaveInfo, logger)
12 this._name = config.triggerableName;
13 assert(typeof(this._name) == 'string', 'triggerableName must be specified');
15 this._lookbackCount = config.lookbackCount;
16 assert(typeof(this._lookbackCount) == 'number' && this._lookbackCount > 0, 'lookbackCount must be a number greater than 0');
18 this._remote = remote;
20 this._slaveInfo = slaveInfo;
21 assert(typeof(slaveInfo.name) == 'string', 'slave name must be specified');
22 assert(typeof(slaveInfo.password) == 'string', 'slave password must be specified');
24 this._syncers = BuildbotSyncer._loadConfig(buildbotRemote, config);
25 this._logger = logger || {log: function () { }, error: function () { }};
28 name() { return this._name; }
32 let syncerList = this._syncers;
33 let buildReqeustsByGroup = new Map;
36 this._logger.log(`Fetching build requests for ${this._name}...`);
37 return BuildRequest.fetchForTriggerable(this._name).then(function () {
38 let buildRequests = BuildRequest.all();
39 self._validateRequests(buildRequests);
40 buildReqeustsByGroup = BuildbotTriggerable._testGroupMapForBuildRequests(buildRequests);
41 return self._pullBuildbotOnAllSyncers(buildReqeustsByGroup);
42 }).then(function (updates) {
43 self._logger.log('Scheduling builds');
45 let testGroupList = Array.from(buildReqeustsByGroup.values()).sort(function (a, b) { return a.id - b.id; });
46 for (let group of testGroupList) {
47 let promise = self._scheduleNextRequestInGroupIfSlaveIsAvailable(group, updates);
49 promistList.push(promise);
51 return Promise.all(promistList);
53 // Pull all buildbots for the second time since the previous step may have scheduled more builds.
54 return self._pullBuildbotOnAllSyncers(buildReqeustsByGroup);
55 }).then(function (updates) {
56 // FIXME: Add a new API that just updates the requests.
57 return self._remote.postJSON(`/api/build-requests/${self._name}`, {
58 'slaveName': self._slaveInfo.name,
59 'slavePassword': self._slaveInfo.password,
60 'buildRequestUpdates': updates});
61 }).then(function (response) {
62 if (response['status'] != 'OK')
63 self._logger.log('Failed to update the build requests status: ' + response['status']);
67 _validateRequests(buildRequests)
69 let testPlatformPairs = {};
70 for (let request of buildRequests) {
71 if (!this._syncers.some(function (syncer) { return syncer.matchesConfiguration(request); })) {
72 let key = request.platform().id + '-' + request.test().id();
73 if (!(key in testPlatformPairs))
74 this._logger.error(`No matching configuration for "${request.test().fullName()}" on "${request.platform().name()}".`);
75 testPlatformPairs[key] = true;
80 _pullBuildbotOnAllSyncers(buildReqeustsByGroup)
83 let associatedRequests = new Set;
85 return Promise.all(this._syncers.map(function (syncer) {
86 return syncer.pullBuildbot(self._lookbackCount).then(function (entryList) {
87 for (let entry of entryList) {
88 let request = BuildRequest.findById(entry.buildRequestId());
91 associatedRequests.add(request);
93 let info = buildReqeustsByGroup.get(request.testGroupId());
94 assert(!info.syncer || info.syncer == syncer);
96 if (entry.slaveName()) {
97 assert(!info.slaveName || info.slaveName == entry.slaveName());
98 info.slaveName = entry.slaveName();
101 let newStatus = entry.buildRequestStatusIfUpdateIsNeeded(request);
103 self._logger.log(`Updating the status of build request ${request.id()} from ${request.status()} to ${newStatus}`);
104 updates[entry.buildRequestId()] = {status: newStatus, url: entry.url()};
105 } else if (!request.statusUrl()) {
106 self._logger.log(`Setting the status URL of build request ${request.id()} to ${entry.url()}`);
107 updates[entry.buildRequestId()] = {status: request.status(), url: entry.url()};
111 })).then(function () {
112 for (let request of BuildRequest.all()) {
113 if (request.hasStarted() && !request.hasFinished() && !associatedRequests.has(request)) {
114 self._logger.log(`Updating the status of build request ${request.id()} from ${request.status()} to failedIfNotCompleted`);
115 assert(!(request.id() in updates));
116 updates[request.id()] = {status: 'failedIfNotCompleted'};
119 }).then(function () { return updates; });
122 _scheduleNextRequestInGroupIfSlaveIsAvailable(groupInfo, pendingUpdates)
124 let orderedRequests = groupInfo.requests.sort(function (a, b) { return a.order() - b.order(); });
125 let nextRequest = null;
126 for (let request of orderedRequests) {
127 if (request.isScheduled() || (request.id() in pendingUpdates && pendingUpdates[request.id()]['status'] == 'scheduled'))
129 if (request.isPending() && !(request.id() in pendingUpdates)) {
130 nextRequest = request;
137 let firstRequest = !nextRequest.order();
139 this._logger.log(`Scheduling build request ${nextRequest.id()} on ${groupInfo.slaveName} in ${groupInfo.syncer.builderName()}`);
140 return groupInfo.syncer.scheduleRequest(request, groupInfo.slaveName);
143 for (let syncer of this._syncers) {
144 let promise = syncer.scheduleFirstRequestInGroupIfAvailable(nextRequest);
146 let slaveName = groupInfo.slaveName ? ` on ${groupInfo.slaveName}` : '';
147 this._logger.log(`Scheduling build request ${nextRequest.id()}${slaveName} in ${syncer.builderName()}`);
154 static _testGroupMapForBuildRequests(buildRequests)
157 for (let request of buildRequests) {
158 let groupId = request.testGroupId();
159 if (!map.has(groupId)) // Don't use real TestGroup objects to avoid executing postgres query in the server
160 map.set(groupId, {id: groupId, requests: [request], syncer: null, slaveName: null});
162 map.get(groupId).requests.push(request);
168 if (typeof module != 'undefined')
169 module.exports.BuildbotTriggerable = BuildbotTriggerable;