diff options
Diffstat (limited to 'spec/services/ci')
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' } } |