add usage srategies for node

This commit is contained in:
oleg 2016-03-10 23:34:42 +03:00
parent 16d4367741
commit 84747099b6
5 changed files with 148 additions and 9 deletions

View File

@ -35,9 +35,19 @@ exports.Distributor = Distributor;
// do deferred initialization (e.g. create nodes after all plugins load)
Distributor.prototype.init = function() {
var self = this;
// nodes to execute builds
var self = this,
namesHash = {};
self.nodes = _(self.nodes).map(function(nodeParams) {
if (!nodeParams.name) {
nodeParams.name = nodeParams.type;
}
if (namesHash[nodeParams.name]) {
throw new Error('Node name `' + nodeParams.name + ' already used');
}
namesHash[nodeParams.name] = 1;
return self._createNode(nodeParams);
});
};
@ -195,7 +205,7 @@ Distributor.prototype._updateWaitReasons = function() {
});
var waitReason = _(waitReasons).compact().join(', ');
// set only non-empty reasons
// set only non-empty and new reasons
if (waitReason && waitReason !== item.build.waitReason) {
self._updateBuild(item.build, {waitReason: waitReason});
}

View File

@ -6,11 +6,20 @@ var _ = require('underscore');
function Node(params) {
this.type = params.type;
this.maxExecutorsCount = params.maxExecutorsCount;
this.name = params.name;
this.usageStrategy = params.usageStrategy || 'maximum';
if (!this.usageStrategiesHash[this.usageStrategy]) {
throw new Error('Unknown usage strategy: ' + this.usageStrategy);
}
this.executors = {};
}
exports.Node = Node;
Node.prototype.usageStrategiesHash = {maximum: 1, specificProject: 1};
Node.prototype._getBlockerExecutor = function(getBlockers, getTarget) {
return _(this.executors).find(function(executor) {
var target = getTarget(executor);
@ -27,10 +36,20 @@ Node.prototype._getBlockerExecutor = function(getBlockers, getTarget) {
Node.prototype.getExecutorWaitReason = function(project) {
var waitReason;
if (_(this.executors).size() >= this.maxExecutorsCount) {
waitReason = 'All executors are busy';
var targetNodeNames = project.node && project.node.target;
if (targetNodeNames && !_(targetNodeNames).isArray()) {
targetNodeNames = [targetNodeNames];
}
if (targetNodeNames && !_(targetNodeNames).contains(this.name)) {
waitReason = this.name + ': not a target node';
} else if (this.usageStrategy === 'specificProject' && !targetNodeNames) {
waitReason = this.name + ': only for specific projects';
} else if (_(this.executors).size() >= this.maxExecutorsCount) {
waitReason = this.name + ': all executors are busy';
} else if (project.name in this.executors) {
waitReason = 'Project already running on node';
waitReason = this.name + ': project already running on node';
} else {
var blockerExecutor;
@ -58,7 +77,7 @@ Node.prototype.getExecutorWaitReason = function(project) {
if (blockerExecutor) {
waitReason = (
'Blocked by currently running "' +
this.name + ': blocked by currently running "' +
blockerExecutor.project.name + '"'
);
}

View File

@ -21,6 +21,7 @@ module.exports = function(config, callback) {
properties: {
name: {type: 'string'},
type: {type: 'string'},
usageStrategy: {type: 'string'},
maxExecutorsCount: {type: 'integer'},
options: {type: 'object'}
}

View File

@ -93,7 +93,7 @@ describe('Distributor blocking with max 2 executors count', function() {
var spy = updateBuildSpy;
expect(spy.getCall(3).args[0].project.name).equal('project2');
expect(spy.getCall(3).args[1].waitReason).equal(
'Blocked by currently running "project1"'
'local: blocked by currently running "project1"'
);
});

View File

@ -1,7 +1,8 @@
'use strict';
var createNode = require('../lib/node').createNode,
expect = require('expect.js');
expect = require('expect.js'),
_ = require('underscore');
describe('Node', function() {
@ -9,6 +10,114 @@ describe('Node', function() {
project1 = {name: 'project1'},
project2 = {name: 'project2'};
var createNodeMock = function(params) {
params = params || {};
var node = createNode(_({
name: 'executor1',
type: 'local',
maxExecutorsCount: 1,
usageStrategy: 'maximum'
}).extend(params));
// only for testing
if (params.executors) {
node.executors = params.executors;
}
return node;
};
describe('wait reason', function() {
it('should be not a target node when node target is not match', function() {
var waitReason = createNodeMock({
name: 'executor1'
}).getExecutorWaitReason({
name: 'project1',
node: {target: 'other executor'}
});
expect(waitReason).eql('executor1: not a target node');
});
it('should be falsy when node target match', function() {
var waitReason = createNodeMock({
name: 'executor1'
}).getExecutorWaitReason({
name: 'project1',
node: {target: 'executor1'}
});
expect(waitReason).not.ok();
});
it('should be falsy when node target (array) match', function() {
var waitReason = createNodeMock({
name: 'executor1'
}).getExecutorWaitReason({
name: 'project1',
node: {target: ['executor1']}
});
expect(waitReason).not.ok();
});
it('should be only for specific projects when target is not set', function() {
var waitReason = createNodeMock({
usageStrategy: 'specificProject'
}).getExecutorWaitReason({
name: 'project1'
});
expect(waitReason).eql('executor1: only for specific projects');
});
it('should be all executors are busy when true', function() {
var waitReason = createNodeMock({
maxExecutorsCount: 1,
executors: {project2: 1}
}).getExecutorWaitReason({
name: 'project1'
});
expect(waitReason).eql('executor1: all executors are busy');
});
it('should be project already running on node when true', function() {
var waitReason = createNodeMock({
maxExecutorsCount: 2,
executors: {project1: 1}
}).getExecutorWaitReason({
name: 'project1'
});
expect(waitReason).eql('executor1: project already running on node');
});
it('should be blocked by project when blocked by executing', function() {
var waitReason = createNodeMock({
maxExecutorsCount: 2,
executors: {project2: {project: {name: 'project2'}}}
}).getExecutorWaitReason({
name: 'project1',
blockedBy: ['project2']
});
expect(waitReason).eql(
'executor1: blocked by currently running "project2"'
);
});
it('should be blocked by project when executing blocks it', function() {
var waitReason = createNodeMock({
maxExecutorsCount: 2,
executors: {project2: {project: {
name: 'project2',
blocks: ['project1']
}}}
}).getExecutorWaitReason({
name: 'project1'
});
expect(waitReason).eql(
'executor1: blocked by currently running "project2"'
);
});
});
var expectNodeHasFreeExecutor = function(project, value) {
it('should' + (value ? ' ' : ' not ') + 'has free executors for ' +
project.name, function() {