Show warning if build doesn't have runners with specified tags or runners didn't connect recently

Slightly refactor runner status detection: moving it to Runner class

Signed-off-by: Kamil Trzcinski <ayufan@ayufan.eu>
This commit is contained in:
Kamil Trzcinski 2015-10-12 21:12:31 +02:00
parent 2d0fcb4de2
commit 7af4f5215e
14 changed files with 256 additions and 22 deletions

View file

@ -27,6 +27,7 @@ v 8.1.0 (unreleased)
- Move CI triggers page to project settings area
- Move CI project settings page to CE project settings area
- Fix bug when removed file was not appearing in merge request diff
- Show warning when build cannot be served by any of the available CI runners
- Note the original location of a moved project when notifying users of the move
- Improve error message when merging fails
- Add support of multibyte characters in LDAP UID (Roman Petrov)

View file

@ -6,7 +6,7 @@ module Ci
@runners = Ci::Runner.order('id DESC')
@runners = @runners.search(params[:search]) if params[:search].present?
@runners = @runners.page(params[:page]).per(30)
@active_runners_cnt = Ci::Runner.where("contacted_at > ?", 1.minutes.ago).count
@active_runners_cnt = Ci::Runner.online.count
end
def show
@ -66,7 +66,7 @@ module Ci
end
def runner_params
params.require(:runner).permit(:token, :description, :tag_list, :contacted_at, :active)
params.require(:runner).permit(:token, :description, :tag_list, :active)
end
end
end

View file

@ -60,6 +60,6 @@ class Projects::RunnersController < Projects::ApplicationController
end
def runner_params
params.require(:runner).permit(:description, :tag_list, :contacted_at, :active)
params.require(:runner).permit(:description, :tag_list, :active)
end
end

View file

@ -1,20 +1,16 @@
module RunnersHelper
def runner_status_icon(runner)
unless runner.contacted_at
return content_tag :i, nil,
class: "fa fa-warning-sign",
title: "New runner. Has not connected yet"
status = runner.status
case status
when :not_connected
content_tag :i, nil,
class: "fa fa-warning-sign",
title: "New runner. Has not connected yet"
when :online, :offline, :paused
content_tag :i, nil,
class: "fa fa-circle runner-status-#{status}",
title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
end
status =
if runner.active?
runner.contacted_at > 3.hour.ago ? :online : :offline
else
:paused
end
content_tag :i, nil,
class: "fa fa-circle runner-status-#{status}",
title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
end
end

View file

@ -231,6 +231,18 @@ module Ci
end
end
def can_be_served?(runner)
(tag_list - runner.tag_list).empty?
end
def any_runners_online?
project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
end
def show_warning?
pending? && !any_runners_online?
end
private
def yaml_variables

View file

@ -115,12 +115,12 @@ module Ci
web_url
end
def any_runners?
if runners.active.any?
def any_runners?(&block)
if runners.active.any?(&block)
return true
end
shared_runners_enabled && Ci::Runner.shared.active.any?
shared_runners_enabled && Ci::Runner.shared.active.any?(&block)
end
def set_default_values

View file

@ -20,6 +20,8 @@
module Ci
class Runner < ActiveRecord::Base
extend Ci::Model
LAST_CONTACT_TIME = 5.minutes.ago
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
@ -33,6 +35,7 @@ module Ci
scope :shared, ->() { where(is_shared: true) }
scope :active, ->() { where(active: true) }
scope :paused, ->() { where(active: false) }
scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
acts_as_taggable
@ -65,6 +68,20 @@ module Ci
is_shared
end
def online?
contacted_at && contacted_at > LAST_CONTACT_TIME
end
def status
if contacted_at.nil?
:not_connected
elsif active?
online? ? :online : :offline
else
:paused
end
end
def belongs_to_one_project?
runner_projects.count == 1
end

View file

@ -88,4 +88,8 @@ class CommitStatus < ActiveRecord::Base
def retry_url
nil
end
def show_warning?
false
end
end

View file

@ -17,7 +17,7 @@ module Ci
builds = builds.order('created_at ASC')
build = builds.find do |build|
(build.tag_list - current_runner.tag_list).empty?
build.can_be_served?(current_runner)
end

View file

@ -39,6 +39,27 @@
.pull-right
= @build.updated_at.stamp('19:00 Aug 27')
- if @build.show_warning?
- unless @build.any_runners_online?
.bs-callout.bs-callout-warning
%p
- if no_runners_for_project?(@build.project)
This build is stuck, because the project doesn't have runners assigned.
- elsif @build.tags.any?
This build is stuck.
%br
This build is stuck, because you don't have any active runners online with these tags assigned to the project:
- @build.tags.each do |tag|
%span.label.label-primary
= tag
- else
This build is stuck, because you don't have any active runners online that can run this build.
%br
Go to
= link_to namespace_project_runners_path(@build.gl_project.namespace, @build.gl_project) do
Runners page
.row.prepend-top-default
.col-md-9
.clearfix

View file

@ -2,6 +2,10 @@
%td.status
= ci_status_with_icon(commit_status.status)
- if commit_status.show_warning?
.pull-right
%i.fa.fa-warning-sign.text-warning
%td.commit_status-link
- if commit_status.target_url
= link_to commit_status.target_url do

View file

@ -273,4 +273,105 @@ describe Ci::Build do
is_expected.to eq(['rec1', pusher_email])
end
end
describe :can_be_served? do
let(:runner) { FactoryGirl.create :ci_specific_runner }
before { build.project.runners << runner }
context 'runner without tags' do
it 'can handle builds without tags' do
expect(build.can_be_served?(runner)).to be_truthy
end
it 'cannot handle build with tags' do
build.tag_list = ['aa']
expect(build.can_be_served?(runner)).to be_falsey
end
end
context 'runner with tags' do
before { runner.tag_list = ['bb', 'cc'] }
it 'can handle builds without tags' do
expect(build.can_be_served?(runner)).to be_truthy
end
it 'can handle build with matching tags' do
build.tag_list = ['bb']
expect(build.can_be_served?(runner)).to be_truthy
end
it 'cannot handle build with not matching tags' do
build.tag_list = ['aa']
expect(build.can_be_served?(runner)).to be_falsey
end
end
end
describe :any_runners_online? do
subject { build.any_runners_online? }
context 'when no runners' do
it { is_expected.to be_falsey }
end
context 'if there are runner' do
let(:runner) { FactoryGirl.create :ci_specific_runner }
before do
build.project.runners << runner
runner.update_attributes(contacted_at: 1.second.ago)
end
it { is_expected.to be_truthy }
it 'that is inactive' do
runner.update_attributes(active: false)
is_expected.to be_falsey
end
it 'that is not online' do
runner.update_attributes(contacted_at: nil)
is_expected.to be_falsey
end
it 'that cannot handle build' do
expect_any_instance_of(Ci::Build).to receive(:can_be_served?).and_return(false)
is_expected.to be_falsey
end
end
end
describe :show_warning? do
subject { build.show_warning? }
%w(pending).each do |state|
context "if commit_status.status is #{state}" do
before { build.status = state }
it { is_expected.to be_truthy }
context "and there are specific runner" do
let(:runner) { FactoryGirl.create :ci_specific_runner, contacted_at: 1.second.ago }
before do
build.project.runners << runner
runner.save
end
it { is_expected.to be_falsey }
end
end
end
%w(success failed canceled running).each do |state|
context "if commit_status.status is #{state}" do
before { build.status = state }
it { is_expected.to be_falsey }
end
end
end
end

View file

@ -259,5 +259,18 @@ describe Ci::Project do
FactoryGirl.create(:ci_shared_runner)
expect(project.any_runners?).to be_falsey
end
it "checks the presence of specific runner" do
project = FactoryGirl.create(:ci_project)
specific_runner = FactoryGirl.create(:ci_specific_runner)
project.runners << specific_runner
expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
end
it "checks the presence of shared runner" do
project = FactoryGirl.create(:ci_project, shared_runners_enabled: true)
shared_runner = FactoryGirl.create(:ci_shared_runner)
expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
end
end
end

View file

@ -48,6 +48,71 @@ describe Ci::Runner do
it { expect(shared_runner.only_for?(project)).to be_truthy }
end
describe :online do
subject { Ci::Runner.online }
before do
@runner1 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.year.ago)
@runner2 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago)
end
it { is_expected.to eq([@runner2])}
end
describe :online? do
let(:runner) { FactoryGirl.create(:ci_shared_runner) }
subject { runner.online? }
context 'never contacted' do
before { runner.contacted_at = nil }
it { is_expected.to be_falsey }
end
context 'contacted long time ago time' do
before { runner.contacted_at = 1.year.ago }
it { is_expected.to be_falsey }
end
context 'contacted 1s ago' do
before { runner.contacted_at = 1.second.ago }
it { is_expected.to be_truthy }
end
end
describe :status do
let(:runner) { FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago) }
subject { runner.status }
context 'never connected' do
before { runner.contacted_at = nil }
it { is_expected.to eq(:not_connected) }
end
context 'contacted 1s ago' do
before { runner.contacted_at = 1.second.ago }
it { is_expected.to eq(:online) }
end
context 'contacted long time ago' do
before { runner.contacted_at = 1.year.ago }
it { is_expected.to eq(:offline) }
end
context 'inactive' do
before { runner.active = false }
it { is_expected.to eq(:paused) }
end
end
describe "belongs_to_one_project?" do
it "returns false if there are two projects runner assigned to" do
runner = FactoryGirl.create(:ci_specific_runner)