nci/lib/distributor.js

258 lines
6.0 KiB
JavaScript
Raw Normal View History

2015-04-09 18:55:29 +00:00
'use strict';
var Steppy = require('twostep').Steppy,
_ = require('underscore'),
2015-05-10 10:04:54 +00:00
Node = require('./node').Node,
EventEmitter = require('events').EventEmitter,
2015-05-21 21:09:16 +00:00
inherits = require('util').inherits,
notifier = require('./notifier'),
2015-07-05 18:03:58 +00:00
logger = require('./logger')('distributor');
2015-04-09 18:55:29 +00:00
function Distributor(params) {
var self = this;
// nodes to execute builds
self.nodes = _(params.nodes).map(function(nodeParams) {
return self._createNode(nodeParams);
});
// queued projects to build
self.queue = [];
2015-04-09 19:39:22 +00:00
2015-05-10 10:04:54 +00:00
self.saveBuild = params.saveBuild || function(build, callback) {
callback(null, build);
2015-04-09 19:39:22 +00:00
};
2015-06-13 18:59:32 +00:00
2015-06-14 13:40:20 +00:00
self.projects = params.projects;
2015-04-09 18:55:29 +00:00
}
2015-05-10 10:04:54 +00:00
inherits(Distributor, EventEmitter);
2015-04-09 18:55:29 +00:00
exports.Distributor = Distributor;
Distributor.prototype._createNode = function(nodeParams) {
return new Node(nodeParams);
};
Distributor.prototype._runNext = function(callback) {
var self = this;
Steppy(
function() {
2015-06-14 23:27:58 +00:00
// update wait reasons for all queue items before run
self._updateWaitReasons();
2015-04-09 18:55:29 +00:00
var node;
var queueItemIndex = _(self.queue).findIndex(function(item) {
node = _(self.nodes).find(function(node) {
return node.hasFreeExecutor(item.project);
});
return node;
});
// quit if we have no suitable project
2015-07-08 21:19:33 +00:00
if (queueItemIndex === -1) {
2015-04-09 18:55:29 +00:00
return callback();
}
var queueItem = self.queue.splice(queueItemIndex, 1)[0],
build = queueItem.build;
2015-04-09 18:55:29 +00:00
2015-05-10 10:04:54 +00:00
self._updateBuild(
build,
{startDate: Date.now(), status: 'in-progress', waitReason: ''},
2015-05-10 10:04:54 +00:00
this.slot()
);
2015-04-09 18:55:29 +00:00
var stepCallback = this.slot();
2015-06-17 20:04:10 +00:00
// run project on the first step two prevent parallel run next calls
var executor = node.run(build.project, build.params, function(err) {
2015-05-10 10:04:54 +00:00
self._updateBuild(
build,
{
endDate: Date.now(),
status: err ? 'error' : 'done',
2015-05-21 19:04:38 +00:00
completed: true,
2015-06-28 14:47:34 +00:00
error: err ? {
message: err.message, stderr: err.stderr
} : null
2015-05-10 10:04:54 +00:00
},
function(err, build) {
if (err) {
2015-07-05 18:03:58 +00:00
logger.error(
'Error during build update: ', err.stack || err
);
return stepCallback(err);
}
self._onBuildComplete(build, stepCallback);
2015-05-10 10:04:54 +00:00
}
);
2015-04-09 18:55:29 +00:00
});
2015-05-20 20:20:51 +00:00
executor.on('currentStep', function(stepName) {
self._updateBuild(build, {currentStep: stepName});
2015-05-05 23:11:28 +00:00
});
2015-07-11 21:27:02 +00:00
executor.on('stepTimingsChange', function(stepTimings) {
self._updateBuild(build, {stepTimings: stepTimings});
});
executor.on('data', function(data) {
2015-05-12 19:53:04 +00:00
self.emit('buildData', build, data);
});
2015-05-09 16:59:27 +00:00
executor.once('scmData', function(scmData) {
2015-05-10 10:04:54 +00:00
self._updateBuild(build, {scm: scmData});
// run the same project again if we don't reach the latest rev
if (!scmData.isLatest) {
self.run({
projectName: build.project.name,
initiator: {
type: 'build',
id: build.id,
number: build.number,
project: {name: build.project.name}
},
2015-07-05 18:03:58 +00:00
});
}
2015-05-09 16:59:27 +00:00
});
2015-06-14 23:27:58 +00:00
// update wait reasons for all queue items after run
self._updateWaitReasons();
2015-04-09 18:55:29 +00:00
},
callback
);
};
2015-06-14 23:27:58 +00:00
Distributor.prototype._updateWaitReasons = function() {
var self = this;
_(self.queue).each(function(item) {
var waitReasons = _(self.nodes).map(function(node) {
return node.getExecutorWaitReason(item.project);
});
var waitReason = _(waitReasons).compact().join(', ');
// set only non-empty reasons
if (waitReason && waitReason !== item.build.waitReason) {
2015-06-14 23:27:58 +00:00
self._updateBuild(item.build, {waitReason: waitReason});
}
});
};
Distributor.prototype._onBuildComplete = function(build, callback) {
2015-06-13 18:59:32 +00:00
var self = this;
Steppy(
function() {
2015-06-14 21:15:26 +00:00
// notify about build
notifier.send(build);
2015-06-13 18:59:32 +00:00
// process after build triggers
var triggerAfterGroup = this.makeGroup();
var after = build.project.trigger && build.project.trigger.after;
if (after) {
_(after).each(function(item) {
if (!item.status || item.status === build.status) {
2015-06-13 19:08:13 +00:00
self.run({
2015-06-14 14:21:04 +00:00
projectName: item.project,
initiator: {
type: 'build',
id: build.id,
number: build.number,
project: {name: build.project.name}
},
2015-06-13 19:08:13 +00:00
}, triggerAfterGroup.slot());
2015-06-13 18:59:32 +00:00
}
});
}
},
function(err, triggerAfterGroupResults) {
// try to run next project from the queue
self._runNext(this.slot());
},
callback
);
};
2015-05-10 10:04:54 +00:00
Distributor.prototype._updateBuild = function(build, changes, callback) {
var self = this;
2015-05-05 23:11:28 +00:00
callback = callback || _.noop;
2015-05-17 10:26:28 +00:00
var isWithNumber = Boolean(build.number);
2015-05-10 10:04:54 +00:00
Steppy(
function() {
_(build).extend(changes);
// skip saving to db of unimportant data
if (changes.currentStep && _(changes).keys().length === 1) {
this.pass(null);
} else {
self.saveBuild(build, this.slot());
}
},
function() {
2015-05-17 10:26:28 +00:00
// if number appear after save to db then add it to changes
// TODO: might be better to generate number right there (instead
// of hooks)
if (!isWithNumber && build.number) {
changes.number = build.number;
}
2015-05-10 10:04:54 +00:00
// emits only after get an id (at save build)
self.emit('buildUpdate', build, changes);
this.pass(build);
},
callback
);
2015-04-09 18:55:29 +00:00
};
2015-06-13 19:08:13 +00:00
Distributor.prototype.run = function(params, callback) {
2015-06-13 18:59:32 +00:00
var self = this,
project;
2015-07-05 18:03:58 +00:00
callback = callback || function(err) {
if (err) {
logger.error('Error during run: ', err.stack || err);
}
};
2015-04-09 18:55:29 +00:00
Steppy(
function() {
project = _(self.projects).chain()
.findWhere({name: params.projectName})
.clone()
.value();
if (params.withScmChangesOnly) {
self.nodes[0].hasScmChanges(project, this.slot());
} else {
this.pass(null);
}
},
function(err, hasScmChanges) {
if (params.withScmChangesOnly && !hasScmChanges) {
logger.log(
'Building of "%s" skipped coz no scm changes',
project.name
);
return callback();
}
2015-05-10 10:04:54 +00:00
self._updateBuild({}, {
2015-04-09 18:55:29 +00:00
project: project,
2015-06-14 14:21:04 +00:00
initiator: params.initiator,
2015-06-13 19:08:13 +00:00
params: params.params,
createDate: Date.now(),
2015-05-21 19:04:38 +00:00
status: 'queued',
completed: false
2015-04-09 18:55:29 +00:00
}, this.slot());
},
function(err, build) {
self.queue.push({project: project, build: build});
self._runNext(this.slot());
},
callback
);
};