Commit 334e8c59 authored by Furkan Ayhan's avatar Furkan Ayhan

Merge branch 'loosen-cyclical-pipelines-precondition' into 'master'

Loosen rule to detect cyclical pipelines

See merge request gitlab-org/gitlab!78171
parents c14cd084 9254fa57
...@@ -125,7 +125,9 @@ module Ci ...@@ -125,7 +125,9 @@ module Ci
config_checksum(pipeline) unless pipeline.child? config_checksum(pipeline) unless pipeline.child?
end end
pipeline_checksums.uniq.length != pipeline_checksums.length # To avoid false positives we allow 1 cycle in the ancestry and
# fail when 2 cycles are detected: A -> B -> A -> B -> A
pipeline_checksums.tally.any? { |_checksum, occurrences| occurrences > 2 }
end end
end end
...@@ -137,7 +139,7 @@ module Ci ...@@ -137,7 +139,7 @@ module Ci
end end
def config_checksum(pipeline) def config_checksum(pipeline)
[pipeline.project_id, pipeline.ref].hash [pipeline.project_id, pipeline.ref, pipeline.source].hash
end end
end end
end end
...@@ -441,44 +441,99 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do ...@@ -441,44 +441,99 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do
end end
end end
context 'when relationship between pipelines is cyclical' do describe 'cyclical dependency detection' do
before do shared_examples 'detects cyclical pipelines' do
pipeline_a = create(:ci_pipeline, project: upstream_project) it 'does not create a new pipeline' do
pipeline_b = create(:ci_pipeline, project: downstream_project) expect { service.execute(bridge) }
pipeline_c = create(:ci_pipeline, project: upstream_project) .not_to change { Ci::Pipeline.count }
end
it 'changes status of the bridge build' do
service.execute(bridge)
create_source_pipeline(pipeline_a, pipeline_b) expect(bridge.reload).to be_failed
create_source_pipeline(pipeline_b, pipeline_c) expect(bridge.failure_reason).to eq 'pipeline_loop_detected'
create_source_pipeline(pipeline_c, upstream_pipeline) end
end end
it 'does not create a new pipeline' do shared_examples 'passes cyclical pipeline precondition' do
expect { service.execute(bridge) } it 'creates a new pipeline' do
.not_to change { Ci::Pipeline.count } expect { service.execute(bridge) }
.to change { Ci::Pipeline.count }
end
it 'expect bridge build not to be failed' do
service.execute(bridge)
expect(bridge.reload).not_to be_failed
end
end end
it 'changes status of the bridge build' do context 'when pipeline ancestry contains 2 cycles of dependencies' do
service.execute(bridge) before do
# A(push on master) -> B(pipeline on master) -> A(push on master) ->
# B(pipeline on master) -> A(push on master)
pipeline_1 = create(:ci_pipeline, project: upstream_project, source: :push)
pipeline_2 = create(:ci_pipeline, project: downstream_project, source: :pipeline)
pipeline_3 = create(:ci_pipeline, project: upstream_project, source: :push)
pipeline_4 = create(:ci_pipeline, project: downstream_project, source: :pipeline)
create_source_pipeline(pipeline_1, pipeline_2)
create_source_pipeline(pipeline_2, pipeline_3)
create_source_pipeline(pipeline_3, pipeline_4)
create_source_pipeline(pipeline_4, upstream_pipeline)
end
expect(bridge.reload).to be_failed it_behaves_like 'detects cyclical pipelines'
expect(bridge.failure_reason).to eq 'pipeline_loop_detected'
context 'when ci_drop_cyclical_triggered_pipelines is not enabled' do
before do
stub_feature_flags(ci_drop_cyclical_triggered_pipelines: false)
end
it_behaves_like 'passes cyclical pipeline precondition'
end
end end
context 'when ci_drop_cyclical_triggered_pipelines is not enabled' do context 'when source in the ancestry differ' do
before do before do
stub_feature_flags(ci_drop_cyclical_triggered_pipelines: false) # A(push on master) -> B(pipeline on master) -> A(pipeline on master)
pipeline_1 = create(:ci_pipeline, project: upstream_project, source: :push)
pipeline_2 = create(:ci_pipeline, project: downstream_project, source: :pipeline)
upstream_pipeline.update!(source: :pipeline)
create_source_pipeline(pipeline_1, pipeline_2)
create_source_pipeline(pipeline_2, upstream_pipeline)
end end
it 'creates a new pipeline' do it_behaves_like 'passes cyclical pipeline precondition'
expect { service.execute(bridge) } end
.to change { Ci::Pipeline.count }
context 'when ref in the ancestry differ' do
before do
# A(push on master) -> B(pipeline on master) -> A(push on feature-1)
pipeline_1 = create(:ci_pipeline, ref: 'master', project: upstream_project, source: :push)
pipeline_2 = create(:ci_pipeline, ref: 'master', project: downstream_project, source: :pipeline)
upstream_pipeline.update!(ref: 'feature-1')
create_source_pipeline(pipeline_1, pipeline_2)
create_source_pipeline(pipeline_2, upstream_pipeline)
end end
it 'expect bridge build not to be failed' do it_behaves_like 'passes cyclical pipeline precondition'
service.execute(bridge) end
expect(bridge.reload).not_to be_failed context 'when only 1 cycle is detected' do
before do
# A(push on master) -> B(pipeline on master) -> A(push on master)
pipeline_1 = create(:ci_pipeline, ref: 'master', project: upstream_project, source: :push)
pipeline_2 = create(:ci_pipeline, ref: 'master', project: downstream_project, source: :pipeline)
create_source_pipeline(pipeline_1, pipeline_2)
create_source_pipeline(pipeline_2, upstream_pipeline)
end end
it_behaves_like 'passes cyclical pipeline precondition'
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment