diff options
-rw-r--r-- | .gitlab/ci/cache-repo.gitlab-ci.yml | 1 | ||||
-rw-r--r-- | app/models/ci/pipeline.rb | 34 | ||||
-rw-r--r-- | app/models/ci/sources/pipeline.rb | 3 | ||||
-rw-r--r-- | app/services/ci/pipeline_bridge_status_service.rb | 13 | ||||
-rw-r--r-- | app/workers/all_queues.yml | 6 | ||||
-rw-r--r-- | app/workers/ci/pipeline_bridge_status_worker.rb | 19 | ||||
-rw-r--r-- | doc/ci/junit_test_reports.md | 5 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 104 | ||||
-rw-r--r-- | spec/models/ci/sources/pipeline_spec.rb | 1 | ||||
-rw-r--r-- | spec/services/ci/pipeline_bridge_status_service_spec.rb | 27 | ||||
-rw-r--r-- | spec/workers/ci/pipeline_bridge_status_worker_spec.rb | 38 |
11 files changed, 248 insertions, 3 deletions
diff --git a/.gitlab/ci/cache-repo.gitlab-ci.yml b/.gitlab/ci/cache-repo.gitlab-ci.yml index 9dcb6f40589..670341eab65 100644 --- a/.gitlab/ci/cache-repo.gitlab-ci.yml +++ b/.gitlab/ci/cache-repo.gitlab-ci.yml @@ -34,6 +34,7 @@ cache-repo: - git clone --progress $CI_REPOSITORY_URL $CI_PROJECT_NAME - cd $CI_PROJECT_NAME - gcloud auth activate-service-account --key-file=$CI_REPO_CACHE_CREDENTIALS + - git remote rm origin - tar cf $TAR_FILENAME . - gzip $TAR_FILENAME - gsutil cp $TAR_FILENAME.gz gs://gitlab-ci-git-repo-cache/project-$CI_PROJECT_ID/gitlab-master.tar.gz diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2bf0ee57d39..addc8a7a2fc 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -16,6 +16,8 @@ module Ci include FromUnion include UpdatedAtFilterable + BridgeStatusError = Class.new(StandardError) + sha_attribute :source_sha sha_attribute :target_sha @@ -64,6 +66,7 @@ module Ci has_one :triggered_by_pipeline, through: :source_pipeline, source: :source_pipeline has_one :parent_pipeline, -> { merge(Ci::Sources::Pipeline.same_project) }, through: :source_pipeline, source: :source_pipeline has_one :source_job, through: :source_pipeline, source: :source_job + has_one :source_bridge, through: :source_pipeline, source: :source_bridge has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline @@ -204,6 +207,22 @@ module Ci end end + after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline| + next unless pipeline.bridge_triggered? + next unless pipeline.bridge_waiting? + + pipeline.run_after_commit do + ::Ci::PipelineBridgeStatusWorker.perform_async(pipeline.id) + end + end + + after_transition created: :pending do |pipeline| + next unless pipeline.bridge_triggered? + next if pipeline.bridge_waiting? + + pipeline.update_bridge_status! + end + after_transition any => [:success, :failed] do |pipeline| pipeline.run_after_commit do PipelineNotificationWorker.perform_async(pipeline.id) @@ -722,6 +741,21 @@ module Ci end end + def update_bridge_status! + raise ArgumentError unless bridge_triggered? + raise BridgeStatusError unless source_bridge.active? + + source_bridge.success! + end + + def bridge_triggered? + source_bridge.present? + end + + def bridge_waiting? + source_bridge&.dependent? + end + def child? parent_pipeline.present? end diff --git a/app/models/ci/sources/pipeline.rb b/app/models/ci/sources/pipeline.rb index d71e3b55b9a..f19aac213be 100644 --- a/app/models/ci/sources/pipeline.rb +++ b/app/models/ci/sources/pipeline.rb @@ -10,6 +10,7 @@ module Ci belongs_to :source_project, class_name: "Project", foreign_key: :source_project_id belongs_to :source_job, class_name: "CommitStatus", foreign_key: :source_job_id + belongs_to :source_bridge, class_name: "Ci::Bridge", foreign_key: :source_job_id belongs_to :source_pipeline, class_name: "Ci::Pipeline", foreign_key: :source_pipeline_id validates :project, presence: true @@ -23,5 +24,3 @@ module Ci end end end - -::Ci::Sources::Pipeline.prepend_if_ee('::EE::Ci::Sources::Pipeline') diff --git a/app/services/ci/pipeline_bridge_status_service.rb b/app/services/ci/pipeline_bridge_status_service.rb new file mode 100644 index 00000000000..19ed5026a3a --- /dev/null +++ b/app/services/ci/pipeline_bridge_status_service.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Ci + class PipelineBridgeStatusService < ::BaseService + def execute(pipeline) + return unless pipeline.bridge_triggered? + + pipeline.source_bridge.inherit_status_from_downstream!(pipeline) + end + end +end + +Ci::PipelineBridgeStatusService.prepend_if_ee('EE::Ci::PipelineBridgeStatusService') diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index b344e1e36b8..51b91e8e8be 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -531,6 +531,12 @@ :latency_sensitive: :resource_boundary: :unknown :weight: 3 +- :name: pipeline_default:ci_pipeline_bridge_status + :feature_category: :continuous_integration + :has_external_dependencies: + :latency_sensitive: true + :resource_boundary: :cpu + :weight: 3 - :name: pipeline_default:pipeline_metrics :feature_category: :continuous_integration :has_external_dependencies: diff --git a/app/workers/ci/pipeline_bridge_status_worker.rb b/app/workers/ci/pipeline_bridge_status_worker.rb new file mode 100644 index 00000000000..f196573deaa --- /dev/null +++ b/app/workers/ci/pipeline_bridge_status_worker.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Ci + class PipelineBridgeStatusWorker + include ::ApplicationWorker + include ::PipelineQueue + + latency_sensitive_worker! + worker_resource_boundary :cpu + + def perform(pipeline_id) + ::Ci::Pipeline.find_by_id(pipeline_id).try do |pipeline| + ::Ci::PipelineBridgeStatusService + .new(pipeline.project, pipeline.user) + .execute(pipeline) + end + end + end +end diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md index 8773f712110..c82f0ab3a42 100644 --- a/doc/ci/junit_test_reports.md +++ b/doc/ci/junit_test_reports.md @@ -221,7 +221,10 @@ with failed showing at the top, skipped next and successful cases last. ### Enabling the feature -This feature comes with the `:junit_pipeline_view` feature flag disabled by default. +This feature comes with the `:junit_pipeline_view` feature flag disabled by default. This +feature is disabled due to some performance issues with very large data sets. +When [the performance issue](https://gitlab.com/gitlab-org/gitlab/issues/37725) is resolved, the feature will be enabled by default. + To enable this feature, ask a GitLab administrator with Rails console access to run the following command: diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 09d6d661d81..18f3c4af08c 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1210,6 +1210,32 @@ describe Ci::Pipeline, :mailer do end end + context 'when pipeline is bridge triggered' do + before do + pipeline.source_bridge = create(:ci_bridge) + end + + context 'when source bridge is dependent on pipeline status' do + before do + allow(pipeline.source_bridge).to receive(:dependent?).and_return(true) + end + + it 'schedules the pipeline bridge worker' do + expect(::Ci::PipelineBridgeStatusWorker).to receive(:perform_async) + + pipeline.succeed! + end + end + + context 'when source bridge is not dependent on pipeline status' do + it 'does not schedule the pipeline bridge worker' do + expect(::Ci::PipelineBridgeStatusWorker).not_to receive(:perform_async) + + pipeline.succeed! + end + end + end + def auto_devops_pipelines_completed_total(status) Gitlab::Metrics.counter(:auto_devops_pipelines_completed_total, 'Number of completed auto devops pipelines').get(status: status) end @@ -2883,4 +2909,82 @@ describe Ci::Pipeline, :mailer do end end end + + describe 'upstream status interactions' do + context 'when a pipeline has an upstream status' do + context 'when an upstream status is a bridge' do + let(:bridge) { create(:ci_bridge, status: :pending) } + + before do + create(:ci_sources_pipeline, pipeline: pipeline, source_job: bridge) + end + + describe '#bridge_triggered?' do + it 'is a pipeline triggered by a bridge' do + expect(pipeline).to be_bridge_triggered + end + end + + describe '#source_job' do + it 'has a correct source job' do + expect(pipeline.source_job).to eq bridge + end + end + + describe '#source_bridge' do + it 'has a correct bridge source' do + expect(pipeline.source_bridge).to eq bridge + end + end + + describe '#update_bridge_status!' do + it 'can update bridge status if it is running' do + pipeline.update_bridge_status! + + expect(bridge.reload).to be_success + end + + it 'can not update bridge status if is not active' do + bridge.success! + + expect { pipeline.update_bridge_status! } + .to raise_error Ci::Pipeline::BridgeStatusError + end + end + end + + context 'when an upstream status is a build' do + let(:build) { create(:ci_build) } + + before do + create(:ci_sources_pipeline, pipeline: pipeline, source_job: build) + end + + describe '#bridge_triggered?' do + it 'is a pipeline that has not been triggered by a bridge' do + expect(pipeline).not_to be_bridge_triggered + end + end + + describe '#source_job' do + it 'has a correct source job' do + expect(pipeline.source_job).to eq build + end + end + + describe '#source_bridge' do + it 'does not have a bridge source' do + expect(pipeline.source_bridge).to be_nil + end + end + + describe '#update_bridge_status!' do + it 'can not update upstream job status' do + expect { pipeline.update_bridge_status! } + .to raise_error ArgumentError + end + end + end + end + end end diff --git a/spec/models/ci/sources/pipeline_spec.rb b/spec/models/ci/sources/pipeline_spec.rb index 63bee5bfb55..5023747b487 100644 --- a/spec/models/ci/sources/pipeline_spec.rb +++ b/spec/models/ci/sources/pipeline_spec.rb @@ -8,6 +8,7 @@ describe Ci::Sources::Pipeline do it { is_expected.to belong_to(:source_project) } it { is_expected.to belong_to(:source_job) } + it { is_expected.to belong_to(:source_bridge) } it { is_expected.to belong_to(:source_pipeline) } it { is_expected.to validate_presence_of(:project) } diff --git a/spec/services/ci/pipeline_bridge_status_service_spec.rb b/spec/services/ci/pipeline_bridge_status_service_spec.rb new file mode 100644 index 00000000000..95f16af3af9 --- /dev/null +++ b/spec/services/ci/pipeline_bridge_status_service_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::PipelineBridgeStatusService do + let(:user) { build(:user) } + let(:project) { build(:project) } + let(:pipeline) { build(:ci_pipeline, project: project) } + + describe '#execute' do + subject { described_class.new(project, user).execute(pipeline) } + + context 'when pipeline has upstream bridge' do + let(:bridge) { build(:ci_bridge) } + + before do + pipeline.source_bridge = bridge + end + + it 'calls inherit_status_from_downstream on upstream bridge' do + expect(bridge).to receive(:inherit_status_from_downstream!).with(pipeline) + + subject + end + end + end +end diff --git a/spec/workers/ci/pipeline_bridge_status_worker_spec.rb b/spec/workers/ci/pipeline_bridge_status_worker_spec.rb new file mode 100644 index 00000000000..d5f95a035fd --- /dev/null +++ b/spec/workers/ci/pipeline_bridge_status_worker_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::PipelineBridgeStatusWorker do + describe '#perform' do + subject { described_class.new.perform(pipeline_id) } + + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline) } + let(:pipeline_id) { pipeline.id } + + it 'calls the service' do + service = double('bridge status service') + + expect(Ci::PipelineBridgeStatusService) + .to receive(:new) + .with(pipeline.project, pipeline.user) + .and_return(service) + + expect(service).to receive(:execute).with(pipeline) + + subject + end + end + + context 'when pipeline does not exist' do + let(:pipeline_id) { 1234 } + + it 'does not call the service' do + expect(Ci::PipelineBridgeStatusService) + .not_to receive(:new) + + subject + end + end + end +end |