summaryrefslogtreecommitdiff
path: root/spec/services/ci
diff options
context:
space:
mode:
Diffstat (limited to 'spec/services/ci')
-rw-r--r--spec/services/ci/append_build_trace_service_spec.rb2
-rw-r--r--spec/services/ci/create_downstream_pipeline_service_spec.rb47
-rw-r--r--spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb13
-rw-r--r--spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/needs_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb3
-rw-r--r--spec/services/ci/job_artifacts/create_service_spec.rb47
-rw-r--r--spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb20
-rw-r--r--spec/services/ci/pipeline_processing/shared_processing_service.rb2
-rw-r--r--spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb1
-rw-r--r--spec/services/ci/register_job_service_spec.rb147
-rw-r--r--spec/services/ci/retry_build_service_spec.rb10
-rw-r--r--spec/services/ci/update_build_queue_service_spec.rb368
-rw-r--r--spec/services/ci/update_build_state_service_spec.rb42
14 files changed, 458 insertions, 248 deletions
diff --git a/spec/services/ci/append_build_trace_service_spec.rb b/spec/services/ci/append_build_trace_service_spec.rb
index a0a7f594881..8812680b665 100644
--- a/spec/services/ci/append_build_trace_service_spec.rb
+++ b/spec/services/ci/append_build_trace_service_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe Ci::AppendBuildTraceService do
expect(::Gitlab::ErrorTracking)
.to receive(:log_exception)
- .with(anything, hash_including(chunk_index: 0, chunk_store: 'redis'))
+ .with(anything, hash_including(chunk_index: 0, chunk_store: 'redis_trace_chunks'))
result = described_class
.new(build, content_range: '0-128')
diff --git a/spec/services/ci/create_downstream_pipeline_service_spec.rb b/spec/services/ci/create_downstream_pipeline_service_spec.rb
index 8bab7856375..18bd59a17f0 100644
--- a/spec/services/ci/create_downstream_pipeline_service_spec.rb
+++ b/spec/services/ci/create_downstream_pipeline_service_spec.rb
@@ -136,7 +136,7 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do
bridge_id: bridge.id, project_id: bridge.project.id)
.and_call_original
expect(Ci::CreatePipelineService).not_to receive(:new)
- expect(service.execute(bridge)).to be_nil
+ expect(service.execute(bridge)).to eq({ message: "Already has a downstream pipeline", status: :error })
end
end
@@ -393,6 +393,51 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do
end
end
end
+
+ context 'when multi-project pipeline runs from child pipelines bridge job' do
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(rspec: { script: 'rspec' }))
+ end
+
+ # instantiate new service, to clear memoized values from child pipeline run
+ subject(:execute_with_trigger_project_bridge) do
+ described_class.new(upstream_project, user).execute(trigger_project_bridge)
+ end
+
+ let!(:child_pipeline) do
+ service.execute(bridge)
+ bridge.downstream_pipeline
+ end
+
+ let!(:trigger_downstream_project) do
+ {
+ trigger: {
+ project: downstream_project.full_path,
+ branch: 'feature'
+ }
+ }
+ end
+
+ let!(:trigger_project_bridge) do
+ create(
+ :ci_bridge, status: :pending,
+ user: user,
+ options: trigger_downstream_project,
+ pipeline: child_pipeline
+ )
+ end
+
+ it 'creates a new pipeline' do
+ expect { execute_with_trigger_project_bridge }
+ .to change { Ci::Pipeline.count }.by(1)
+
+ new_pipeline = trigger_project_bridge.downstream_pipeline
+
+ expect(new_pipeline.child?).to eq(false)
+ expect(new_pipeline.triggered_by_pipeline).to eq child_pipeline
+ expect(trigger_project_bridge.reload).not_to be_failed
+ end
+ end
end
end
diff --git a/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb b/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb
index 9ccf289df7c..7193e5bd7d4 100644
--- a/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb
+++ b/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb
@@ -14,7 +14,6 @@ RSpec.describe Ci::CreatePipelineService do
before do
stub_ci_pipeline_yaml_file(config)
- stub_feature_flags(ci_raise_job_rules_without_workflow_rules_warning: true)
end
context 'when created successfully' do
@@ -35,18 +34,6 @@ RSpec.describe Ci::CreatePipelineService do
/jobs:test may allow multiple pipelines to run/
)
end
-
- context 'when feature flag is disabled for the particular warning' do
- before do
- stub_feature_flags(ci_raise_job_rules_without_workflow_rules_warning: false)
- end
-
- it 'does not contain warnings' do
- expect(pipeline.error_messages.map(&:content)).to be_empty
-
- expect(pipeline.warning_messages.map(&:content)).to be_empty
- end
- end
end
context 'when no warnings are raised' do
diff --git a/spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb b/spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb
index b3b8e34dd8e..7fd32288893 100644
--- a/spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb
+++ b/spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb
@@ -53,6 +53,8 @@ RSpec.describe Ci::CreatePipelineService, '#execute' do
end
context 'when sidekiq processes the job', :sidekiq_inline do
+ let_it_be(:runner) { create(:ci_runner, :online) }
+
it 'transitions to pending status and triggers a downstream pipeline' do
pipeline = create_pipeline!
diff --git a/spec/services/ci/create_pipeline_service/needs_spec.rb b/spec/services/ci/create_pipeline_service/needs_spec.rb
index 4521067cd52..3b4a6178b8f 100644
--- a/spec/services/ci/create_pipeline_service/needs_spec.rb
+++ b/spec/services/ci/create_pipeline_service/needs_spec.rb
@@ -211,7 +211,7 @@ RSpec.describe Ci::CreatePipelineService do
deploy_a = processables.find { |processable| processable.name == 'deploy_a' }
deploy_b = processables.find { |processable| processable.name == 'deploy_b' }
- expect(pipeline).to be_persisted
+ expect(pipeline).to be_created_successfully
expect(build_a.status).to eq('pending')
expect(test_a.status).to eq('created')
expect(test_b.status).to eq('pending')
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 9fdce1ae926..052727401dd 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Ci::CreatePipelineService do
let_it_be(:project, reload: true) { create(:project, :repository) }
let_it_be(:user, reload: true) { project.owner }
+ let_it_be(:runner) { create(:ci_runner, :online, tag_list: %w[postgres mysql ruby]) }
let(:ref_name) { 'refs/heads/master' }
@@ -532,7 +533,7 @@ RSpec.describe Ci::CreatePipelineService do
it 'pull it from Auto-DevOps' do
pipeline = execute_service
expect(pipeline).to be_auto_devops_source
- expect(pipeline.builds.map(&:name)).to match_array(%w[brakeman-sast build code_quality eslint-sast secret_detection_default_branch semgrep-sast test])
+ expect(pipeline.builds.map(&:name)).to match_array(%w[brakeman-sast build code_quality eslint-sast secret_detection semgrep-sast test])
end
end
diff --git a/spec/services/ci/job_artifacts/create_service_spec.rb b/spec/services/ci/job_artifacts/create_service_spec.rb
index 97c65dc005e..e6d9f208096 100644
--- a/spec/services/ci/job_artifacts/create_service_spec.rb
+++ b/spec/services/ci/job_artifacts/create_service_spec.rb
@@ -203,53 +203,6 @@ RSpec.describe Ci::JobArtifacts::CreateService do
end
end
- context 'when artifact type is cluster_applications' do
- let(:artifacts_file) do
- file_to_upload('spec/fixtures/helm/helm_list_v2_prometheus_missing.json.gz', sha256: artifacts_sha256)
- end
-
- let(:params) do
- {
- 'artifact_type' => 'cluster_applications',
- 'artifact_format' => 'gzip'
- }.with_indifferent_access
- end
-
- it 'calls cluster applications parse service' do
- expect_next_instance_of(Clusters::ParseClusterApplicationsArtifactService) do |service|
- expect(service).to receive(:execute).once.and_call_original
- end
-
- subject
- end
-
- context 'when there is a deployment cluster' do
- let(:user) { project.owner }
-
- before do
- job.update!(user: user)
- end
-
- it 'calls cluster applications parse service with job and job user', :aggregate_failures do
- expect(Clusters::ParseClusterApplicationsArtifactService).to receive(:new).with(job, user).and_call_original
-
- subject
- end
- end
-
- context 'when ci_synchronous_artifact_parsing feature flag is disabled' do
- before do
- stub_feature_flags(ci_synchronous_artifact_parsing: false)
- end
-
- it 'does not call parse service' do
- expect(Clusters::ParseClusterApplicationsArtifactService).not_to receive(:new)
-
- expect(subject[:status]).to eq(:success)
- end
- end
- end
-
shared_examples 'rescues object storage error' do |klass, message, expected_message|
it "handles #{klass}" do
allow_next_instance_of(JobArtifactUploader) do |uploader|
diff --git a/spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb b/spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb
new file mode 100644
index 00000000000..2aa810e8ea1
--- /dev/null
+++ b/spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::PipelineCreation::StartPipelineService do
+ let(:pipeline) { build(:ci_pipeline) }
+
+ subject(:service) { described_class.new(pipeline) }
+
+ describe '#execute' do
+ it 'calls the pipeline process service' do
+ expect(Ci::ProcessPipelineService)
+ .to receive(:new)
+ .with(pipeline)
+ .and_return(double('service', execute: true))
+
+ service.execute
+ end
+ end
+end
diff --git a/spec/services/ci/pipeline_processing/shared_processing_service.rb b/spec/services/ci/pipeline_processing/shared_processing_service.rb
index 13c924a3089..34d9b60217f 100644
--- a/spec/services/ci/pipeline_processing/shared_processing_service.rb
+++ b/spec/services/ci/pipeline_processing/shared_processing_service.rb
@@ -859,6 +859,8 @@ RSpec.shared_examples 'Pipeline Processing Service' do
end
context 'when a bridge job has parallel:matrix config', :sidekiq_inline do
+ let_it_be(:runner) { create(:ci_runner, :online) }
+
let(:parent_config) do
<<-EOY
test:
diff --git a/spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb b/spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb
index 572808cd2db..9c8e6fd3292 100644
--- a/spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb
+++ b/spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb
@@ -3,6 +3,7 @@
RSpec.shared_context 'Pipeline Processing Service Tests With Yaml' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.owner }
+ let_it_be(:runner) { create(:ci_runner, :online) }
where(:test_file_path) do
Dir.glob(Rails.root.join('spec/services/ci/pipeline_processing/test_cases/*.yml'))
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index 839a3c53f07..c4b1e2133ed 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -11,9 +11,37 @@ module Ci
let!(:shared_runner) { create(:ci_runner, :instance) }
let!(:specific_runner) { create(:ci_runner, :project, projects: [project]) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
- let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
+ let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) }
describe '#execute' do
+ context 'checks database loadbalancing stickiness' do
+ subject { described_class.new(shared_runner).execute }
+
+ before do
+ project.update!(shared_runners_enabled: false)
+ end
+
+ it 'result is valid if replica did caught-up' do
+ allow(Gitlab::Database::LoadBalancing).to receive(:enable?)
+ .and_return(true)
+
+ expect(Gitlab::Database::LoadBalancing::Sticking).to receive(:all_caught_up?)
+ .with(:runner, shared_runner.id) { true }
+
+ expect(subject).to be_valid
+ end
+
+ it 'result is invalid if replica did not caught-up' do
+ allow(Gitlab::Database::LoadBalancing).to receive(:enable?)
+ .and_return(true)
+
+ expect(Gitlab::Database::LoadBalancing::Sticking).to receive(:all_caught_up?)
+ .with(:runner, shared_runner.id) { false }
+
+ expect(subject).not_to be_valid
+ end
+ end
+
shared_examples 'handles runner assignment' do
context 'runner follow tag list' do
it "picks build with the same tag" do
@@ -76,11 +104,11 @@ module Ci
let!(:project3) { create :project, shared_runners_enabled: true }
let!(:pipeline3) { create :ci_pipeline, project: project3 }
let!(:build1_project1) { pending_job }
- let!(:build2_project1) { FactoryBot.create :ci_build, pipeline: pipeline }
- let!(:build3_project1) { FactoryBot.create :ci_build, pipeline: pipeline }
- let!(:build1_project2) { FactoryBot.create :ci_build, pipeline: pipeline2 }
- let!(:build2_project2) { FactoryBot.create :ci_build, pipeline: pipeline2 }
- let!(:build1_project3) { FactoryBot.create :ci_build, pipeline: pipeline3 }
+ let!(:build2_project1) { create(:ci_build, :pending, :queued, pipeline: pipeline) }
+ let!(:build3_project1) { create(:ci_build, :pending, :queued, pipeline: pipeline) }
+ let!(:build1_project2) { create(:ci_build, :pending, :queued, pipeline: pipeline2) }
+ let!(:build2_project2) { create(:ci_build, :pending, :queued, pipeline: pipeline2) }
+ let!(:build1_project3) { create(:ci_build, :pending, :queued, pipeline: pipeline3) }
context 'when using fair scheduling' do
context 'when all builds are pending' do
@@ -227,17 +255,17 @@ module Ci
let!(:pipeline3) { create(:ci_pipeline, project: project3) }
let!(:build1_project1) { pending_job }
- let!(:build2_project1) { create(:ci_build, pipeline: pipeline) }
- let!(:build3_project1) { create(:ci_build, pipeline: pipeline) }
- let!(:build1_project2) { create(:ci_build, pipeline: pipeline2) }
- let!(:build2_project2) { create(:ci_build, pipeline: pipeline2) }
- let!(:build1_project3) { create(:ci_build, pipeline: pipeline3) }
+ let!(:build2_project1) { create(:ci_build, :queued, pipeline: pipeline) }
+ let!(:build3_project1) { create(:ci_build, :queued, pipeline: pipeline) }
+ let!(:build1_project2) { create(:ci_build, :queued, pipeline: pipeline2) }
+ let!(:build2_project2) { create(:ci_build, :queued, pipeline: pipeline2) }
+ let!(:build1_project3) { create(:ci_build, :queued, pipeline: pipeline3) }
# these shouldn't influence the scheduling
let!(:unrelated_group) { create(:group) }
let!(:unrelated_project) { create(:project, group_runners_enabled: true, group: unrelated_group) }
let!(:unrelated_pipeline) { create(:ci_pipeline, project: unrelated_project) }
- let!(:build1_unrelated_project) { create(:ci_build, pipeline: unrelated_pipeline) }
+ let!(:build1_unrelated_project) { create(:ci_build, :pending, :queued, pipeline: unrelated_pipeline) }
let!(:unrelated_group_runner) { create(:ci_runner, :group, groups: [unrelated_group]) }
it 'does not consider builds from other group runners' do
@@ -318,7 +346,7 @@ module Ci
subject { described_class.new(specific_runner).execute }
context 'with multiple builds are in queue' do
- let!(:other_build) { create :ci_build, pipeline: pipeline }
+ let!(:other_build) { create(:ci_build, :pending, :queued, pipeline: pipeline) }
before do
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_project_runner)
@@ -359,7 +387,7 @@ module Ci
let!(:specific_runner) { create(:ci_runner, :project, projects: [project]) }
context 'when a job is protected' do
- let!(:pending_job) { create(:ci_build, :protected, pipeline: pipeline) }
+ let!(:pending_job) { create(:ci_build, :pending, :queued, :protected, pipeline: pipeline) }
it 'picks the job' do
expect(execute(specific_runner)).to eq(pending_job)
@@ -367,7 +395,7 @@ module Ci
end
context 'when a job is unprotected' do
- let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
+ let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) }
it 'picks the job' do
expect(execute(specific_runner)).to eq(pending_job)
@@ -375,7 +403,7 @@ module Ci
end
context 'when protected attribute of a job is nil' do
- let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
+ let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) }
before do
pending_job.update_attribute(:protected, nil)
@@ -391,7 +419,7 @@ module Ci
let!(:specific_runner) { create(:ci_runner, :project, :ref_protected, projects: [project]) }
context 'when a job is protected' do
- let!(:pending_job) { create(:ci_build, :protected, pipeline: pipeline) }
+ let!(:pending_job) { create(:ci_build, :pending, :queued, :protected, pipeline: pipeline) }
it 'picks the job' do
expect(execute(specific_runner)).to eq(pending_job)
@@ -399,7 +427,7 @@ module Ci
end
context 'when a job is unprotected' do
- let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
+ let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) }
it 'does not pick the job' do
expect(execute(specific_runner)).to be_nil
@@ -407,7 +435,7 @@ module Ci
end
context 'when protected attribute of a job is nil' do
- let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
+ let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) }
before do
pending_job.update_attribute(:protected, nil)
@@ -421,7 +449,7 @@ module Ci
context 'runner feature set is verified' do
let(:options) { { artifacts: { reports: { junit: "junit.xml" } } } }
- let!(:pending_job) { create(:ci_build, :pending, pipeline: pipeline, options: options) }
+ let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline, options: options) }
subject { execute(specific_runner, params) }
@@ -457,7 +485,7 @@ module Ci
shared_examples 'validation is active' do
context 'when depended job has not been completed yet' do
- let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
+ let!(:pre_stage_job) { create(:ci_build, :pending, :queued, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
it { expect(subject).to eq(pending_job) }
end
@@ -494,7 +522,7 @@ module Ci
shared_examples 'validation is not active' do
context 'when depended job has not been completed yet' do
- let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
+ let!(:pre_stage_job) { create(:ci_build, :pending, :queued, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
it { expect(subject).to eq(pending_job) }
end
@@ -519,7 +547,7 @@ module Ci
let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0) }
let!(:pending_job) do
- create(:ci_build, :pending,
+ create(:ci_build, :pending, :queued,
pipeline: pipeline, stage_idx: 1,
options: { script: ["bash"], dependencies: ['test'] })
end
@@ -530,7 +558,7 @@ module Ci
end
context 'when build is degenerated' do
- let!(:pending_job) { create(:ci_build, :pending, :degenerated, pipeline: pipeline) }
+ let!(:pending_job) { create(:ci_build, :pending, :queued, :degenerated, pipeline: pipeline) }
subject { execute(specific_runner, {}) }
@@ -545,7 +573,7 @@ module Ci
context 'when build has data integrity problem' do
let!(:pending_job) do
- create(:ci_build, :pending, pipeline: pipeline)
+ create(:ci_build, :pending, :queued, pipeline: pipeline)
end
before do
@@ -570,7 +598,7 @@ module Ci
context 'when build fails to be run!' do
let!(:pending_job) do
- create(:ci_build, :pending, pipeline: pipeline)
+ create(:ci_build, :pending, :queued, pipeline: pipeline)
end
before do
@@ -612,12 +640,12 @@ module Ci
context 'when only some builds can be matched by runner' do
let!(:specific_runner) { create(:ci_runner, :project, projects: [project], tag_list: %w[matching]) }
- let!(:pending_job) { create(:ci_build, pipeline: pipeline, tag_list: %w[matching]) }
+ let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline, tag_list: %w[matching]) }
before do
# create additional matching and non-matching jobs
- create_list(:ci_build, 2, pipeline: pipeline, tag_list: %w[matching])
- create(:ci_build, pipeline: pipeline, tag_list: %w[non-matching])
+ create_list(:ci_build, 2, :pending, :queued, pipeline: pipeline, tag_list: %w[matching])
+ create(:ci_build, :pending, :queued, pipeline: pipeline, tag_list: %w[non-matching])
end
it 'observes queue size of only matching jobs' do
@@ -665,7 +693,7 @@ module Ci
end
context 'when there is another build in queue' do
- let!(:next_pending_job) { create(:ci_build, pipeline: pipeline) }
+ let!(:next_pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) }
it 'skips this build and picks another build' do
expect(Gitlab::Ci::Queue::Metrics.queue_operations_total).to receive(:increment)
@@ -683,11 +711,7 @@ module Ci
end
end
- context 'when ci_register_job_service_one_by_one is enabled' do
- before do
- stub_feature_flags(ci_register_job_service_one_by_one: true)
- end
-
+ context 'when a long queue is created' do
it 'picks builds one-by-one' do
expect(Ci::Build).to receive(:find).with(pending_job.id).and_call_original
@@ -697,9 +721,17 @@ module Ci
include_examples 'handles runner assignment'
end
- context 'when ci_register_job_service_one_by_one is disabled' do
+ context 'when joining with pending builds table' do
+ before do
+ stub_feature_flags(ci_pending_builds_queue_join: true)
+ end
+
+ include_examples 'handles runner assignment'
+ end
+
+ context 'when not joining with pending builds table' do
before do
- stub_feature_flags(ci_register_job_service_one_by_one: false)
+ stub_feature_flags(ci_pending_builds_queue_join: false)
end
include_examples 'handles runner assignment'
@@ -747,8 +779,8 @@ module Ci
end
context 'when project already has running jobs' do
- let!(:build2) { create( :ci_build, :running, pipeline: pipeline, runner: shared_runner) }
- let!(:build3) { create( :ci_build, :running, pipeline: pipeline, runner: shared_runner) }
+ let!(:build2) { create(:ci_build, :running, pipeline: pipeline, runner: shared_runner) }
+ let!(:build3) { create(:ci_build, :running, pipeline: pipeline, runner: shared_runner) }
it 'counts job queuing time histogram with expected labels' do
allow(attempt_counter).to receive(:increment)
@@ -831,42 +863,21 @@ module Ci
end
context 'when max queue depth is reached' do
- let!(:pending_job) { create(:ci_build, :pending, :degenerated, pipeline: pipeline) }
- let!(:pending_job_2) { create(:ci_build, :pending, :degenerated, pipeline: pipeline) }
- let!(:pending_job_3) { create(:ci_build, :pending, pipeline: pipeline) }
+ let!(:pending_job) { create(:ci_build, :pending, :queued, :degenerated, pipeline: pipeline) }
+ let!(:pending_job_2) { create(:ci_build, :pending, :queued, :degenerated, pipeline: pipeline) }
+ let!(:pending_job_3) { create(:ci_build, :pending, :queued, pipeline: pipeline) }
before do
stub_const("#{described_class}::MAX_QUEUE_DEPTH", 2)
end
- context 'when feature is enabled' do
- before do
- stub_feature_flags(gitlab_ci_builds_queue_limit: true)
- end
+ it 'returns 409 conflict' do
+ expect(Ci::Build.pending.unstarted.count).to eq 3
- it 'returns 409 conflict' do
- expect(Ci::Build.pending.unstarted.count).to eq 3
+ result = described_class.new(specific_runner).execute
- result = described_class.new(specific_runner).execute
-
- expect(result).not_to be_valid
- expect(result.build).to be_nil
- end
- end
-
- context 'when feature is disabled' do
- before do
- stub_feature_flags(gitlab_ci_builds_queue_limit: false)
- end
-
- it 'returns a valid result' do
- expect(Ci::Build.pending.unstarted.count).to eq 3
-
- result = described_class.new(specific_runner).execute
-
- expect(result).to be_valid
- expect(result.build).to eq pending_job_3
- end
+ expect(result).not_to be_valid
+ expect(result.build).to be_nil
end
end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index 86bda868625..c71bec31984 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Ci::RetryBuildService do
job_artifacts_metadata job_artifacts_trace job_artifacts_junit
job_artifacts_sast job_artifacts_secret_detection job_artifacts_dependency_scanning
job_artifacts_container_scanning job_artifacts_dast
- job_artifacts_license_management job_artifacts_license_scanning
+ job_artifacts_license_scanning
job_artifacts_performance job_artifacts_browser_performance job_artifacts_load_performance
job_artifacts_lsif job_artifacts_terraform job_artifacts_cluster_applications
job_artifacts_codequality job_artifacts_metrics scheduled_at
@@ -59,13 +59,14 @@ RSpec.describe Ci::RetryBuildService do
metadata runner_session trace_chunks upstream_pipeline_id
artifacts_file artifacts_metadata artifacts_size commands
resource resource_group_id processed security_scans author
- pipeline_id report_results pending_state pages_deployments].freeze
+ pipeline_id report_results pending_state pages_deployments
+ queuing_entry runtime_metadata].freeze
shared_examples 'build duplication' do
let_it_be(:another_pipeline) { create(:ci_empty_pipeline, project: project) }
let_it_be(:build) do
- create(:ci_build, :failed, :expired, :erased, :queued, :coverage, :tags,
+ create(:ci_build, :failed, :picked, :expired, :erased, :queued, :coverage, :tags,
:allowed_to_fail, :on_tag, :triggered, :teardown_environment, :resource_group,
description: 'my-job', stage: 'test', stage_id: stage.id,
pipeline: pipeline, auto_canceled_by: another_pipeline,
@@ -73,9 +74,6 @@ RSpec.describe Ci::RetryBuildService do
end
before_all do
- # Test correctly behaviour of deprecated artifact because it can be still in use
- stub_feature_flags(drop_license_management_artifact: false)
-
# Make sure that build has both `stage_id` and `stage` because FactoryBot
# can reset one of the fields when assigning another. We plan to deprecate
# and remove legacy `stage` column in the future.
diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb
index 2d9f80a249d..44d7809b85f 100644
--- a/spec/services/ci/update_build_queue_service_spec.rb
+++ b/spec/services/ci/update_build_queue_service_spec.rb
@@ -4,154 +4,344 @@ require 'spec_helper'
RSpec.describe Ci::UpdateBuildQueueService do
let(:project) { create(:project, :repository) }
- let(:build) { create(:ci_build, pipeline: pipeline) }
let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ describe 'pending builds queue push / pop' do
+ describe '#push' do
+ let(:transition) { double('transition') }
+
+ before do
+ allow(transition).to receive(:to).and_return('pending')
+ allow(transition).to receive(:within_transaction).and_yield
+ end
+
+ context 'when pending build can be created' do
+ it 'creates a new pending build in transaction' do
+ queued = subject.push(build, transition)
+
+ expect(queued).to eq build.id
+ end
+
+ it 'increments queue push metric' do
+ metrics = spy('metrics')
+
+ described_class.new(metrics).push(build, transition)
+
+ expect(metrics)
+ .to have_received(:increment_queue_operation)
+ .with(:build_queue_push)
+ end
+ end
- shared_examples 'refreshes runner' do
- it 'ticks runner queue value' do
- expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
+ context 'when invalid transition is detected' do
+ it 'raises an error' do
+ allow(transition).to receive(:to).and_return('created')
+
+ expect { subject.push(build, transition) }
+ .to raise_error(described_class::InvalidQueueTransition)
+ end
+ end
+
+ context 'when duplicate entry exists' do
+ before do
+ ::Ci::PendingBuild.create!(build: build, project: project)
+ end
+
+ it 'does nothing and returns build id' do
+ queued = subject.push(build, transition)
+
+ expect(queued).to eq build.id
+ end
+ end
end
- end
- shared_examples 'does not refresh runner' do
- it 'ticks runner queue value' do
- expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
+ describe '#pop' do
+ let(:transition) { double('transition') }
+
+ before do
+ allow(transition).to receive(:from).and_return('pending')
+ allow(transition).to receive(:within_transaction).and_yield
+ end
+
+ context 'when pending build exists' do
+ before do
+ Ci::PendingBuild.create!(build: build, project: project)
+ end
+
+ it 'removes pending build in a transaction' do
+ dequeued = subject.pop(build, transition)
+
+ expect(dequeued).to eq build.id
+ end
+
+ it 'increments queue pop metric' do
+ metrics = spy('metrics')
+
+ described_class.new(metrics).pop(build, transition)
+
+ expect(metrics)
+ .to have_received(:increment_queue_operation)
+ .with(:build_queue_pop)
+ end
+ end
+
+ context 'when pending build does not exist' do
+ it 'does nothing if there is no pending build to remove' do
+ dequeued = subject.pop(build, transition)
+
+ expect(dequeued).to be_nil
+ end
+ end
+
+ context 'when invalid transition is detected' do
+ it 'raises an error' do
+ allow(transition).to receive(:from).and_return('created')
+
+ expect { subject.pop(build, transition) }
+ .to raise_error(described_class::InvalidQueueTransition)
+ end
+ end
end
end
- shared_examples 'matching build' do
- context 'when there is a online runner that can pick build' do
+ describe 'shared runner builds tracking' do
+ let(:runner) { create(:ci_runner, :instance_type) }
+ let(:build) { create(:ci_build, runner: runner, pipeline: pipeline) }
+
+ describe '#track' do
+ let(:transition) { double('transition') }
+
before do
- runner.update!(contacted_at: 30.minutes.ago)
+ allow(transition).to receive(:to).and_return('running')
+ allow(transition).to receive(:within_transaction).and_yield
end
- it_behaves_like 'refreshes runner'
+ 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 eq build.id
+ end
+
+ it 'increments new shared runner build metric' do
+ metrics = spy('metrics')
- it 'avoids running redundant queries' do
- expect(Ci::Runner).not_to receive(:owned_or_instance_wide)
+ described_class.new(metrics).track(build, transition)
- subject.execute(build)
+ expect(metrics)
+ .to have_received(:increment_queue_operation)
+ .with(:shared_runner_build_new)
+ end
end
- context 'when feature flag ci_reduce_queries_when_ticking_runner_queue is disabled' do
+ context 'when invalid transition is detected' do
+ it 'raises an error' do
+ allow(transition).to receive(:to).and_return('pending')
+
+ expect { subject.track(build, transition) }
+ .to raise_error(described_class::InvalidQueueTransition)
+ end
+ end
+
+ context 'when duplicate entry exists' do
before do
- stub_feature_flags(ci_reduce_queries_when_ticking_runner_queue: false)
- stub_feature_flags(ci_runners_short_circuit_assignable_for: false)
+ ::Ci::RunningBuild.create!(
+ build: build, project: project, runner: runner, runner_type: runner.runner_type
+ )
end
- it 'runs redundant queries using `owned_or_instance_wide` scope' do
- expect(Ci::Runner).to receive(:owned_or_instance_wide).and_call_original
+ it 'does nothing and returns build id' do
+ build_id = subject.track(build, transition)
- subject.execute(build)
+ expect(build_id).to eq build.id
end
end
end
- end
- shared_examples 'mismatching tags' do
- context 'when there is no runner that can pick build due to tag mismatch' do
+ describe '#untrack' do
+ let(:transition) { double('transition') }
+
before do
- build.tag_list = [:docker]
+ allow(transition).to receive(:from).and_return('running')
+ allow(transition).to receive(:within_transaction).and_yield
end
- it_behaves_like 'does not refresh runner'
+ context 'when shared runner build tracking entry exists' do
+ before do
+ Ci::RunningBuild.create!(
+ build: build, project: project, runner: runner, runner_type: runner.runner_type
+ )
+ end
+
+ it 'removes shared runner build' do
+ build_id = subject.untrack(build, transition)
+
+ expect(build_id).to eq build.id
+ end
+
+ it 'increments shared runner build done metric' do
+ metrics = spy('metrics')
+
+ described_class.new(metrics).untrack(build, transition)
+
+ expect(metrics)
+ .to have_received(:increment_queue_operation)
+ .with(:shared_runner_build_done)
+ 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
+ end
+ end
+
+ context 'when invalid transition is detected' do
+ it 'raises an error' do
+ allow(transition).to receive(:from).and_return('pending')
+
+ expect { subject.untrack(build, transition) }
+ .to raise_error(described_class::InvalidQueueTransition)
+ end
+ end
end
end
- shared_examples 'recent runner queue' do
- context 'when there is runner with expired cache' do
- before do
- runner.update!(contacted_at: Ci::Runner.recent_queue_deadline)
+ describe '#tick' do
+ shared_examples 'refreshes runner' do
+ it 'ticks runner queue value' do
+ expect { subject.tick(build) }.to change { runner.ensure_runner_queue_value }
end
+ end
- it_behaves_like 'does not refresh runner'
+ shared_examples 'does not refresh runner' do
+ it 'ticks runner queue value' do
+ expect { subject.tick(build) }.not_to change { runner.ensure_runner_queue_value }
+ end
end
- end
- context 'when updating specific runners' do
- let(:runner) { create(:ci_runner, :project, projects: [project]) }
+ shared_examples 'matching build' do
+ context 'when there is a online runner that can pick build' do
+ before do
+ runner.update!(contacted_at: 30.minutes.ago)
+ end
- it_behaves_like 'matching build'
- it_behaves_like 'mismatching tags'
- it_behaves_like 'recent runner queue'
+ it_behaves_like 'refreshes runner'
- context 'when the runner is assigned to another project' do
- let(:another_project) { create(:project) }
- let(:runner) { create(:ci_runner, :project, projects: [another_project]) }
+ it 'avoids running redundant queries' do
+ expect(Ci::Runner).not_to receive(:owned_or_instance_wide)
- it_behaves_like 'does not refresh runner'
+ subject.tick(build)
+ end
+ end
end
- end
- context 'when updating shared runners' do
- let(:runner) { create(:ci_runner, :instance) }
-
- it_behaves_like 'matching build'
- it_behaves_like 'mismatching tags'
- it_behaves_like 'recent runner queue'
+ shared_examples 'mismatching tags' do
+ context 'when there is no runner that can pick build due to tag mismatch' do
+ before do
+ build.tag_list = [:docker]
+ end
- context 'when there is no runner that can pick build due to being disabled on project' do
- before do
- build.project.shared_runners_enabled = false
+ it_behaves_like 'does not refresh runner'
end
+ end
- it_behaves_like 'does not refresh runner'
+ shared_examples 'recent runner queue' do
+ context 'when there is runner with expired cache' do
+ before do
+ runner.update!(contacted_at: Ci::Runner.recent_queue_deadline)
+ end
+
+ it_behaves_like 'does not refresh runner'
+ end
end
- 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]) }
+ context 'when updating specific runners' do
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
- it_behaves_like 'matching build'
- it_behaves_like 'mismatching tags'
- it_behaves_like 'recent runner queue'
+ it_behaves_like 'matching build'
+ it_behaves_like 'mismatching tags'
+ it_behaves_like 'recent runner queue'
- context 'when there is no runner that can pick build due to being disabled on project' do
- before do
- build.project.group_runners_enabled = false
- end
+ context 'when the runner is assigned to another project' do
+ let(:another_project) { create(:project) }
+ let(:runner) { create(:ci_runner, :project, projects: [another_project]) }
- it_behaves_like 'does not refresh runner'
+ it_behaves_like 'does not refresh runner'
+ end
end
- end
- context 'avoids N+1 queries', :request_store do
- let!(:build) { create(:ci_build, pipeline: pipeline, tag_list: %w[a b]) }
- let!(:project_runner) { create(:ci_runner, :project, :online, projects: [project], tag_list: %w[a b c]) }
+ context 'when updating shared runners' do
+ let(:runner) { create(:ci_runner, :instance) }
- context 'when ci_preload_runner_tags and ci_reduce_queries_when_ticking_runner_queue are enabled' do
- before do
- stub_feature_flags(
- ci_reduce_queries_when_ticking_runner_queue: true,
- ci_preload_runner_tags: true
- )
+ it_behaves_like 'matching build'
+ it_behaves_like 'mismatching tags'
+ it_behaves_like 'recent runner queue'
+
+ context 'when there is no runner that can pick build due to being disabled on project' do
+ before do
+ build.project.shared_runners_enabled = false
+ end
+
+ it_behaves_like 'does not refresh runner'
end
+ end
- it 'does execute the same amount of queries regardless of number of runners' do
- control_count = ActiveRecord::QueryRecorder.new { subject.execute(build) }.count
+ context 'when updating group runners' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
- create_list(:ci_runner, 10, :project, :online, projects: [project], tag_list: %w[b c d])
+ it_behaves_like 'matching build'
+ it_behaves_like 'mismatching tags'
+ it_behaves_like 'recent runner queue'
- expect { subject.execute(build) }.not_to exceed_all_query_limit(control_count)
+ context 'when there is no runner that can pick build due to being disabled on project' do
+ before do
+ build.project.group_runners_enabled = false
+ end
+
+ it_behaves_like 'does not refresh runner'
end
end
- context 'when ci_preload_runner_tags and ci_reduce_queries_when_ticking_runner_queue are disabled' do
- before do
- stub_feature_flags(
- ci_reduce_queries_when_ticking_runner_queue: false,
- ci_preload_runner_tags: false
- )
+ context 'avoids N+1 queries', :request_store do
+ let!(:build) { create(:ci_build, pipeline: pipeline, tag_list: %w[a b]) }
+ let!(:project_runner) { create(:ci_runner, :project, :online, projects: [project], tag_list: %w[a b c]) }
+
+ context 'when ci_preload_runner_tags is enabled' do
+ before do
+ stub_feature_flags(
+ ci_preload_runner_tags: true
+ )
+ end
+
+ it 'does execute the same amount of queries regardless of number of runners' do
+ control_count = ActiveRecord::QueryRecorder.new { subject.tick(build) }.count
+
+ create_list(:ci_runner, 10, :project, :online, projects: [project], tag_list: %w[b c d])
+
+ expect { subject.tick(build) }.not_to exceed_all_query_limit(control_count)
+ end
end
- it 'does execute more queries for more runners' do
- control_count = ActiveRecord::QueryRecorder.new { subject.execute(build) }.count
+ context 'when ci_preload_runner_tags are disabled' do
+ before do
+ stub_feature_flags(
+ ci_preload_runner_tags: false
+ )
+ end
+
+ it 'does execute more queries for more runners' do
+ control_count = ActiveRecord::QueryRecorder.new { subject.tick(build) }.count
- create_list(:ci_runner, 10, :project, :online, projects: [project], tag_list: %w[b c d])
+ create_list(:ci_runner, 10, :project, :online, projects: [project], tag_list: %w[b c d])
- expect { subject.execute(build) }.to exceed_all_query_limit(control_count)
+ expect { subject.tick(build) }.to exceed_all_query_limit(control_count)
+ end
end
end
end
diff --git a/spec/services/ci/update_build_state_service_spec.rb b/spec/services/ci/update_build_state_service_spec.rb
index 63190cc5d49..5bb3843da8f 100644
--- a/spec/services/ci/update_build_state_service_spec.rb
+++ b/spec/services/ci/update_build_state_service_spec.rb
@@ -3,8 +3,9 @@
require 'spec_helper'
RSpec.describe Ci::UpdateBuildStateService do
- let(:project) { create(:project) }
- let(:pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+
let(:build) { create(:ci_build, :running, pipeline: pipeline) }
let(:metrics) { spy('metrics') }
@@ -14,6 +15,24 @@ RSpec.describe Ci::UpdateBuildStateService do
stub_feature_flags(ci_enable_live_trace: true)
end
+ context 'when build has unknown failure reason' do
+ let(:params) do
+ {
+ output: { checksum: 'crc32:12345678', bytesize: 123 },
+ state: 'failed',
+ failure_reason: 'no idea here',
+ exit_code: 42
+ }
+ end
+
+ it 'updates a build status' do
+ result = subject.execute
+
+ expect(build).to be_failed
+ expect(result.status).to eq 200
+ end
+ end
+
context 'when build does not have checksum' do
context 'when state has changed' do
let(:params) { { state: 'success' } }
@@ -47,25 +66,6 @@ RSpec.describe Ci::UpdateBuildStateService do
end
end
- context 'when request payload carries a trace' do
- let(:params) { { state: 'success', trace: 'overwritten' } }
-
- it 'overwrites a trace' do
- result = subject.execute
-
- expect(build.trace.raw).to eq 'overwritten'
- expect(result.status).to eq 200
- end
-
- it 'updates overwrite operation metric' do
- execute_with_stubbed_metrics!
-
- expect(metrics)
- .to have_received(:increment_trace_operation)
- .with(operation: :overwrite)
- end
- end
-
context 'when state is unknown' do
let(:params) { { state: 'unknown' } }