diff --git a/app/models/ci/running_build.rb b/app/models/ci/running_build.rb index e70ba3c97c31457f4f1a4ae566df43f50440667d..238eb7af6c81ca65639195c997d485f39c8f0a66 100644 --- a/app/models/ci/running_build.rb +++ b/app/models/ci/running_build.rb @@ -2,12 +2,6 @@ module Ci # This model represents metadata for a running build. - # Despite the generic RunningBuild name, in this first iteration it applies only to shared runners - # (see Ci::RunningBuild.upsert_shared_runner_build!). - # The decision to insert all of the running builds here was deferred to avoid the pressure on the database as - # at this time that was not necessary. - # We can reconsider the decision to limit this only to shared runners when there is more evidence that inserting all - # of the running builds there is worth the additional pressure. class RunningBuild < Ci::ApplicationRecord include Ci::Partitionable @@ -22,11 +16,15 @@ class RunningBuild < Ci::ApplicationRecord enum runner_type: ::Ci::Runner.runner_types - def self.upsert_shared_runner_build!(build) - unless build.shared_runner_build? + def self.upsert_build!(build) + unless add_ci_running_build?(build) raise ArgumentError, 'build has not been picked by a shared runner' end + if build.runner.nil? + raise ArgumentError, 'build has not been picked by a runner' + end + entry = self.new( build: build, project: build.project, @@ -38,5 +36,11 @@ def self.upsert_shared_runner_build!(build) self.upsert(entry.attributes.compact, returning: %w[build_id], unique_by: :build_id) end + + private_class_method def self.add_ci_running_build?(build) + return true if Feature.enabled?(:add_all_ci_running_builds, Project.actor_from_id(build.project_id)) + + build.shared_runner_build? + end end end diff --git a/app/services/ci/update_build_queue_service.rb b/app/services/ci/update_build_queue_service.rb index 40941dd4cd05ac282a1725e03712e6a9ee523de5..e467a49738fb82baea421378ec4894b2fc73649c 100644 --- a/app/services/ci/update_build_queue_service.rb +++ b/app/services/ci/update_build_queue_service.rb @@ -50,18 +50,19 @@ def remove!(build) end ## - # Add shared runner build tracking entry (used for queuing). + # Add runner build tracking entry (used for queuing and for runner fleet dashboard). # def track(build, transition) - return unless build.shared_runner_build? + return if build.runner.nil? + return unless add_ci_running_build?(build) raise InvalidQueueTransition unless transition.to == 'running' transition.within_transaction do - result = ::Ci::RunningBuild.upsert_shared_runner_build!(build) + result = ::Ci::RunningBuild.upsert_build!(build) unless result.empty? - metrics.increment_queue_operation(:shared_runner_build_new) + metrics.increment_queue_operation(:shared_runner_build_new) if build.shared_runner_build? result.rows.dig(0, 0) end @@ -69,11 +70,11 @@ def track(build, transition) end ## - # Remove a runtime build tracking entry for a shared runner build (used for - # queuing). + # Remove a runtime build tracking entry for a runner build (used for queuing and for runner fleet dashboard). # def untrack(build, transition) - return unless build.shared_runner_build? + return if build.runner.nil? + return unless remove_ci_running_build?(build) raise InvalidQueueTransition unless transition.from == 'running' @@ -81,7 +82,7 @@ def untrack(build, transition) removed = build.all_runtime_metadata.delete_all if removed > 0 - metrics.increment_queue_operation(:shared_runner_build_done) + metrics.increment_queue_operation(:shared_runner_build_done) if build.shared_runner_build? build.id end @@ -109,5 +110,17 @@ def tick_for(build, runners) runner.pick_build!(build) end end + + def add_ci_running_build?(build) + return true if Feature.enabled?(:add_all_ci_running_builds, Project.actor_from_id(build.project_id)) + + build.shared_runner_build? + end + + def remove_ci_running_build?(build) + return true if Feature.enabled?(:remove_all_ci_running_builds, Project.actor_from_id(build.project_id)) + + build.shared_runner_build? + end end end diff --git a/config/feature_flags/gitlab_com_derisk/add_all_ci_running_builds.yml b/config/feature_flags/gitlab_com_derisk/add_all_ci_running_builds.yml new file mode 100644 index 0000000000000000000000000000000000000000..252105574a11e751308fdb2c1d0d778be3d99df9 --- /dev/null +++ b/config/feature_flags/gitlab_com_derisk/add_all_ci_running_builds.yml @@ -0,0 +1,9 @@ +--- +name: add_all_ci_running_builds +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/437846 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147943 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/452166 +milestone: '16.11' +group: group::runner +type: gitlab_com_derisk +default_enabled: false diff --git a/config/feature_flags/gitlab_com_derisk/remove_all_ci_running_builds.yml b/config/feature_flags/gitlab_com_derisk/remove_all_ci_running_builds.yml new file mode 100644 index 0000000000000000000000000000000000000000..971feebe83dfec7d4abbb1220d021cf969d4a440 --- /dev/null +++ b/config/feature_flags/gitlab_com_derisk/remove_all_ci_running_builds.yml @@ -0,0 +1,9 @@ +--- +name: remove_all_ci_running_builds +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/437846 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147943 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/452166 +milestone: '16.11' +group: group::runner +type: gitlab_com_derisk +default_enabled: false diff --git a/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder.rb b/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder.rb index 2cd9afc5bdced02b7249429148c373a99832b30d..daf99eba89b5928afe8309cdc74b34dbcbefb7de 100644 --- a/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder.rb +++ b/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder.rb @@ -171,7 +171,10 @@ def create_build(pipeline, runner, job_status, index) ::Ci::Build.transaction do build = ::Ci::Build.new(importing: true, **build_attrs).tap(&:save!) - ::Ci::RunningBuild.upsert_shared_runner_build!(build) if build.running? && build.shared_runner_build? + if build.running? && + (Feature.enabled?(:add_all_ci_running_builds, build.project) || build.shared_runner_build?) + ::Ci::RunningBuild.upsert_build!(build) + end end end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 99a65ed13d2ce96f1fd97a152e79319413bbaca1..ddf57abc11b48a0e960a84015279bea6834933a9 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -310,7 +310,7 @@ runner factory: :ci_runner after(:create) do |build| - ::Ci::RunningBuild.upsert_shared_runner_build!(build) + ::Ci::RunningBuild.upsert_build!(build) end end diff --git a/spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb b/spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb index 930782dfadfe0bf73ceb754dbe6fbb2de2d07a90..5cd0670148f8fbc5472d931218a4330d3023af23 100644 --- a/spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb +++ b/spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb @@ -33,8 +33,8 @@ def runner_ids_for_project(runner_count, project) expect { seeder.seed }.to change { Ci::Build.count }.by(job_count) .and change { Ci::Pipeline.count }.by(4) - expect(Ci::Pipeline.where.not(started_at: nil).map(&:queued_duration)).to all(be < 5.minutes) - expect(Ci::Build.where.not(started_at: nil).map(&:queued_duration)).to all(be < 5.minutes) + expect(Ci::Pipeline.where.not(started_at: nil).map(&:queued_duration)).to all(be <= 5.minutes) + expect(Ci::Build.where.not(started_at: nil).map(&:queued_duration)).to all(be <= 5.minutes) projects_to_runners.first(3).each do |project| expect(Ci::Build.where(runner_id: project[:runner_ids])).not_to be_empty diff --git a/spec/models/ci/running_build_spec.rb b/spec/models/ci/running_build_spec.rb index 0a81eaf9ac0625b368ec1331eea45a14b4959040..cb19bd52281897234cf60ce7bda5e0e85ca6a1f0 100644 --- a/spec/models/ci/running_build_spec.rb +++ b/spec/models/ci/running_build_spec.rb @@ -9,12 +9,12 @@ let(:runner) { create(:ci_runner, :instance_type) } let(:build) { create(:ci_build, :running, runner: runner, pipeline: pipeline) } - describe '.upsert_shared_runner_build!' do + describe '.upsert_build!' do + subject(:upsert_build) { described_class.upsert_build!(build) } + context 'another pending entry does not exist' do it 'creates a new pending entry' do - result = described_class.upsert_shared_runner_build!(build) - - expect(result.rows.dig(0, 0)).to eq build.id + expect(upsert_build.rows.dig(0, 0)).to eq build.id expect(build.reload.runtime_metadata).to be_present end end @@ -25,18 +25,25 @@ end it 'returns a build id as a result' do - result = described_class.upsert_shared_runner_build!(build) - - expect(result.rows.dig(0, 0)).to eq build.id + expect(upsert_build.rows.dig(0, 0)).to eq build.id end end context 'when build has been picked by a project runner' do - let(:runner) { create(:ci_runner, :project) } + let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) } - it 'raises an error' do - expect { described_class.upsert_shared_runner_build!(build) } - .to raise_error(ArgumentError, 'build has not been picked by a shared runner') + it 'returns a build id as a result' do + expect(upsert_build.rows.dig(0, 0)).to eq build.id + end + + context 'with add_all_ci_running_builds FF disabled' do + before do + stub_feature_flags(add_all_ci_running_builds: false) + end + + it 'raises an error' do + expect { upsert_build }.to raise_error(ArgumentError, 'build has not been picked by a shared runner') + end end end @@ -44,8 +51,17 @@ let(:build) { create(:ci_build, pipeline: pipeline) } it 'raises an error' do - expect { described_class.upsert_shared_runner_build!(build) } - .to raise_error(ArgumentError, 'build has not been picked by a shared runner') + expect { upsert_build }.to raise_error(ArgumentError, 'build has not been picked by a runner') + end + + context 'with add_all_ci_running_builds FF disabled' do + before do + stub_feature_flags(add_all_ci_running_builds: false) + end + + it 'raises an error' do + expect { upsert_build }.to raise_error(ArgumentError, 'build has not been picked by a shared runner') + end end end end @@ -63,8 +79,8 @@ it 'assigns the same partition id as the one that build has', :aggregate_failures do expect(new_build.partition_id).to eq ci_testing_partition_id_for_check_constraints - described_class.upsert_shared_runner_build!(build) - described_class.upsert_shared_runner_build!(new_build) + described_class.upsert_build!(build) + described_class.upsert_build!(new_build) expect(build.reload.runtime_metadata.partition_id).to eq pipeline.partition_id expect(new_build.reload.runtime_metadata.partition_id).to eq ci_testing_partition_id_for_check_constraints diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb index f29e1a36914129c4bc604fd0522afc71ea62d3a2..07aa21f713ba3bb66c01d77cdf43c3e5ea00a27a 100644 --- a/spec/services/ci/register_job_service_spec.rb +++ b/spec/services/ci/register_job_service_spec.rb @@ -948,8 +948,8 @@ module Ci let(:build3) { create(:ci_build, :running, pipeline: pipeline, runner: shared_runner) } before do - ::Ci::RunningBuild.upsert_shared_runner_build!(build2) - ::Ci::RunningBuild.upsert_shared_runner_build!(build3) + ::Ci::RunningBuild.upsert_build!(build2) + ::Ci::RunningBuild.upsert_build!(build3) end it 'counts job queuing time histogram with expected labels' do diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb index c5959127f343b0fc6981442d6259d1e496b66c29..8c5fed4fe25d9c6bbb6f02870c95ade31c3db0bf 100644 --- a/spec/services/ci/update_build_queue_service_spec.rb +++ b/spec/services/ci/update_build_queue_service_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' RSpec.describe Ci::UpdateBuildQueueService, feature_category: :continuous_integration do - let(:project) { create(:project, :repository) } + let_it_be_with_refind(:project) { create(:project, :repository) } + let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } @@ -127,13 +128,16 @@ end end - describe 'shared runner builds tracking' do - let(:runner) { create(:ci_runner, :instance_type) } + describe 'runner builds tracking' do + let_it_be(:runner) { create(:ci_runner, :instance_type) } + let(:build) { create(:ci_build, runner: runner, pipeline: pipeline) } describe '#track' do let(:transition) { double('transition') } + subject(:build_id) { described_class.new.track(build, transition) } + before do allow(transition).to receive(:to).and_return('running') allow(transition).to receive(:within_transaction).and_yield @@ -141,7 +145,7 @@ context 'when a shared runner build can be tracked' do it 'creates a new shared runner build tracking entry' do - build_id = subject.track(build, transition) + expect { build_id }.to change { Ci::RunningBuild.count }.from(0).to(1) expect(build_id).to eq build.id end @@ -157,6 +161,48 @@ end end + context 'when a project runner build can be tracked' do + let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) } + + it 'creates a new project runner build tracking entry' do + expect { build_id }.to change { Ci::RunningBuild.count }.from(0).to(1) + + expect(build_id).to eq build.id + end + + it 'does not increment new shared runner build metric' do + metrics = spy('metrics') + + described_class.new(metrics).track(build, transition) + + expect(metrics) + .not_to have_received(:increment_queue_operation) + .with(:shared_runner_build_new) + end + + context 'with add_all_ci_running_builds FF disabled' do + before do + stub_feature_flags(add_all_ci_running_builds: false) + end + + it 'does not create a new project runner build tracking entry' do + is_expected.to be_nil + end + end + end + + context 'when runner is nil' do + let(:build) { create(:ci_build, runner: nil, pipeline: pipeline) } + + it 'does nothing' do + expect(transition).not_to receive(:to) + expect(transition).not_to receive(:within_transaction) + expect(::Ci::RunningBuild).not_to receive(:upsert_build!) + + expect(build_id).to be_nil + end + end + context 'when invalid transition is detected' do it 'raises an error' do allow(transition).to receive(:to).and_return('pending') @@ -172,9 +218,7 @@ end it 'does nothing and returns build id' do - build_id = subject.track(build, transition) - - expect(build_id).to eq build.id + is_expected.to eq build.id end end end @@ -182,6 +226,8 @@ describe '#untrack' do let(:transition) { double('transition') } + subject(:build_id) { described_class.new.untrack(build, transition) } + before do allow(transition).to receive(:from).and_return('running') allow(transition).to receive(:within_transaction).and_yield @@ -193,9 +239,7 @@ end it 'removes shared runner build' do - build_id = subject.untrack(build, transition) - - expect(build_id).to eq build.id + is_expected.to eq build.id end it 'increments shared runner build done metric' do @@ -209,11 +253,41 @@ end end + context 'when project runner build tracking entry exists' do + let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) } + + before do + create(:ci_running_build, build: build, project: project, runner: runner) + end + + it 'removes project runner build' do + is_expected.to eq build.id + end + + it 'does not increment shared runner build done metric' do + metrics = spy('metrics') + + described_class.new(metrics).untrack(build, transition) + + expect(metrics) + .not_to have_received(:increment_queue_operation) + .with(:shared_runner_build_done) + end + + context 'with remove_all_ci_running_builds FF disabled' do + before do + stub_feature_flags(remove_all_ci_running_builds: false) + end + + it 'does not remove project runner build' do + is_expected.to be_nil + end + end + end + context 'when tracking entry does not exist' do it 'does nothing if there is no tracking entry to remove' do - build_id = subject.untrack(build, transition) - - expect(build_id).to be_nil + is_expected.to be_nil end end @@ -278,7 +352,7 @@ end context 'when updating project runners' do - let(:runner) { create(:ci_runner, :project, projects: [project]) } + let_it_be_with_refind(:runner) { create(:ci_runner, :project, projects: [project]) } it_behaves_like 'matching build' it_behaves_like 'mismatching tags' @@ -293,7 +367,7 @@ end context 'when updating shared runners' do - let(:runner) { create(:ci_runner, :instance) } + let_it_be(:runner) { create(:ci_runner, :instance) } it_behaves_like 'matching build' it_behaves_like 'mismatching tags' @@ -309,9 +383,9 @@ end context 'when updating group runners' do - let(:group) { create(:group) } - let(:project) { create(:project, group: group) } - let(:runner) { create(:ci_runner, :group, groups: [group]) } + let_it_be(:group) { create(:group) } + let_it_be_with_refind(:project) { create(:project, group: group) } + let_it_be_with_refind(:runner) { create(:ci_runner, :group, groups: [group]) } it_behaves_like 'matching build' it_behaves_like 'mismatching tags'