diff --git a/lib/distributor.js b/lib/distributor.js index e526533..a341019 100644 --- a/lib/distributor.js +++ b/lib/distributor.js @@ -37,6 +37,9 @@ Distributor.prototype._runNext = function(callback) { Steppy( function() { + // update wait reasons for all queue items before run + self._updateWaitReasons(); + var node; var queueItemIndex = _(self.queue).findIndex(function(item) { node = _(self.nodes).find(function(node) { @@ -88,11 +91,28 @@ Distributor.prototype._runNext = function(callback) { executor.once('scmData', function(scmData) { self._updateBuild(build, {scm: scmData}); }); + + // update wait reasons for all queue items after run + self._updateWaitReasons(); }, callback ); }; +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(', '); + if (waitReason !== item.build.waitReason) { + self._updateBuild(item.build, {waitReason: waitReason}); + } + }); +}; + Distributor.prototype._onBuildComplete = function(err, build, callback) { var self = this; diff --git a/lib/node.js b/lib/node.js index 1057e8c..337b811 100644 --- a/lib/node.js +++ b/lib/node.js @@ -12,13 +12,21 @@ function Node(params) { exports.Node = Node; +Node.prototype.getExecutorWaitReason = function(project) { + var waitReason; + + if (_(this.executors).size() >= this.maxExecutorsCount) { + waitReason = 'All executors are busy'; + } else if (project.name in this.executors) { + waitReason = 'Project already running on node'; + } + + return waitReason; +}; + Node.prototype.hasFreeExecutor = function(project) { - return ( - // can't build same project twice at the same time on same node - project.name in this.executors === false && - _(this.executors).size() < this.maxExecutorsCount - ); -} + return !this.getExecutorWaitReason(project); +}; Node.prototype._createExecutor = function(project) { return createExecutor({ @@ -29,8 +37,12 @@ Node.prototype._createExecutor = function(project) { Node.prototype.run = function(project, params, callback) { var self = this; - if (!this.hasFreeExecutor(project)) { - throw new Error('No free executors for project: ' + project.name); + + var waitReason = this.getExecutorWaitReason(project); + if (waitReason) { + throw new Error( + 'Project "' + project.name + '" should wait because: ' + waitReason + ); } this.executors[project.name] = this._createExecutor(project); diff --git a/static/js/app/components/builds/item.jade b/static/js/app/components/builds/item.jade index f08c157..8f32991 100644 --- a/static/js/app/components/builds/item.jade +++ b/static/js/app/components/builds/item.jade @@ -30,6 +30,11 @@ mixin statusText(build) span # span= build.number + if build.waitReason + span ( + span= build.waitReason + span , waiting) + if build.status === 'in-progress' && build.currentStep span ( span= build.currentStep diff --git a/test/distributor.js b/test/distributor.js index 5329710..3e7fe89 100644 --- a/test/distributor.js +++ b/test/distributor.js @@ -69,14 +69,20 @@ describe('Distributor', function() { expect(changes.completed).equal(false); }); - it('build should be in-progress', function() { + it('build should have empty wait reason', function() { var changes = updateBuildSpy.getCall(1).args[1]; + expect(changes).only.have.keys('waitReason'); + expect(changes.waitReason).equal(''); + }); + + it('build should be in-progress', function() { + var changes = updateBuildSpy.getCall(2).args[1]; expect(changes).only.have.keys('startDate', 'status'); expect(changes.status).equal('in-progress'); }); it('build should be done', function() { - var changes = updateBuildSpy.getCall(2).args[1]; + var changes = updateBuildSpy.getCall(3).args[1]; expect(changes).only.have.keys( 'endDate', 'status', 'completed', 'error' ); @@ -85,8 +91,8 @@ describe('Distributor', function() { expect(changes.error).equal(null); }); - it('update build called 3 times in total', function() { - expect(updateBuildSpy.callCount).equal(3); + it('update build called 4 times in total', function() { + expect(updateBuildSpy.callCount).equal(4); }); after(function() { @@ -128,20 +134,26 @@ describe('Distributor', function() { expect(changes.status).equal('queued'); }); - it('build should be in-progress', function() { + it('build should have empty wait reason', function() { var changes = updateBuildSpy.getCall(1).args[1]; + expect(changes).only.have.keys('waitReason'); + expect(changes.waitReason).equal(''); + }); + + it('build should be in-progress', function() { + var changes = updateBuildSpy.getCall(2).args[1]; expect(changes.status).equal('in-progress'); }); it('build should be fail', function() { - var changes = updateBuildSpy.getCall(2).args[1]; + var changes = updateBuildSpy.getCall(3).args[1]; expect(changes.status).equal('error'); expect(changes.completed).equal(true); expect(changes.error).equal('Some error'); }); - it('update build called 3 times in total', function() { - expect(updateBuildSpy.callCount).equal(3); + it('update build called 4 times in total', function() { + expect(updateBuildSpy.callCount).equal(4); }); after(function() {