Commit cd1f348f authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'pipeline-processing-test-all-combinations' into 'master'

Test and fix all combinations of pipeline processing

See merge request gitlab-org/gitlab!30845
parents 82493cba a2f63172
...@@ -115,8 +115,11 @@ module Ci ...@@ -115,8 +115,11 @@ module Ci
state_machine :status, initial: :created do state_machine :status, initial: :created do
event :enqueue do event :enqueue do
transition [:created, :waiting_for_resource, :preparing, :skipped, :scheduled] => :pending transition [:created, :manual, :waiting_for_resource, :preparing, :skipped, :scheduled] => :pending
transition [:success, :failed, :canceled] => :running transition [:success, :failed, :canceled] => :running
# this is needed to ensure tests to be covered
transition [:running] => :running
end end
event :request_resource do event :request_resource do
......
...@@ -42,8 +42,7 @@ module Ci ...@@ -42,8 +42,7 @@ module Ci
state_machine :status, initial: :created do state_machine :status, initial: :created do
event :enqueue do event :enqueue do
transition [:created, :waiting_for_resource, :preparing] => :pending transition any - [:pending] => :pending
transition [:success, :failed, :canceled, :skipped] => :running
end end
event :request_resource do event :request_resource do
......
...@@ -95,7 +95,7 @@ module Ci ...@@ -95,7 +95,7 @@ module Ci
def processable_status(processable) def processable_status(processable)
if processable.scheduling_type_dag? if processable.scheduling_type_dag?
# Processable uses DAG, get status of all dependent needs # Processable uses DAG, get status of all dependent needs
@collection.status_for_names(processable.aggregated_needs_names.to_a) @collection.status_for_names(processable.aggregated_needs_names.to_a, dag: true)
else else
# Processable uses Stages, get status of prior stage # Processable uses Stages, get status of prior stage
@collection.status_for_prior_stage_position(processable.stage_idx.to_i) @collection.status_for_prior_stage_position(processable.stage_idx.to_i)
......
...@@ -32,14 +32,14 @@ module Ci ...@@ -32,14 +32,14 @@ module Ci
# This methods gets composite status of all processables # This methods gets composite status of all processables
def status_of_all def status_of_all
status_for_array(all_statuses) status_for_array(all_statuses, dag: false)
end end
# This methods gets composite status for processables with given names # This methods gets composite status for processables with given names
def status_for_names(names) def status_for_names(names, dag:)
name_statuses = all_statuses_by_name.slice(*names) name_statuses = all_statuses_by_name.slice(*names)
status_for_array(name_statuses.values) status_for_array(name_statuses.values, dag: dag)
end end
# This methods gets composite status for processables before given stage # This methods gets composite status for processables before given stage
...@@ -48,7 +48,7 @@ module Ci ...@@ -48,7 +48,7 @@ module Ci
stage_statuses = all_statuses_grouped_by_stage_position stage_statuses = all_statuses_grouped_by_stage_position
.select { |stage_position, _| stage_position < position } .select { |stage_position, _| stage_position < position }
status_for_array(stage_statuses.values.flatten) status_for_array(stage_statuses.values.flatten, dag: false)
end end
end end
...@@ -65,7 +65,7 @@ module Ci ...@@ -65,7 +65,7 @@ module Ci
strong_memoize("status_for_stage_position_#{current_position}") do strong_memoize("status_for_stage_position_#{current_position}") do
stage_statuses = all_statuses_grouped_by_stage_position[current_position].to_a stage_statuses = all_statuses_grouped_by_stage_position[current_position].to_a
status_for_array(stage_statuses.flatten) status_for_array(stage_statuses.flatten, dag: false)
end end
end end
...@@ -76,7 +76,14 @@ module Ci ...@@ -76,7 +76,14 @@ module Ci
private private
def status_for_array(statuses) def status_for_array(statuses, dag:)
# TODO: This is hack to support
# the same exact behaviour for Atomic and Legacy processing
# that DAG is blocked from executing if dependent is not "complete"
if dag && statuses.any? { |status| HasStatus::COMPLETED_STATUSES.exclude?(status[:status]) }
return 'pending'
end
result = Gitlab::Ci::Status::Composite result = Gitlab::Ci::Status::Composite
.new(statuses) .new(statuses)
.status .status
......
...@@ -53,6 +53,29 @@ describe Ci::Pipeline, :mailer do ...@@ -53,6 +53,29 @@ describe Ci::Pipeline, :mailer do
end end
end end
describe '#set_status' do
where(:from_status, :to_status) do
from_status_names = described_class.state_machines[:status].states.map(&:name)
to_status_names = from_status_names - [:created] # we never want to transition into created
from_status_names.product(to_status_names)
end
with_them do
it do
pipeline.status = from_status.to_s
if from_status != to_status
expect(pipeline.set_status(to_status.to_s))
.to eq(true)
else
expect(pipeline.set_status(to_status.to_s))
.to eq(false), "loopback transitions are not allowed"
end
end
end
end
describe '.processables' do describe '.processables' do
before do before do
create(:ci_build, name: 'build', pipeline: pipeline) create(:ci_build, name: 'build', pipeline: pipeline)
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
require 'spec_helper' require 'spec_helper'
describe Ci::Stage, :models do describe Ci::Stage, :models do
let(:stage) { create(:ci_stage_entity) } let_it_be(:pipeline) { create(:ci_empty_pipeline) }
let(:stage) { create(:ci_stage_entity, pipeline: pipeline, project: pipeline.project) }
it_behaves_like 'having unique enum values' it_behaves_like 'having unique enum values'
...@@ -55,6 +56,29 @@ describe Ci::Stage, :models do ...@@ -55,6 +56,29 @@ describe Ci::Stage, :models do
end end
end end
describe '#set_status' do
where(:from_status, :to_status) do
from_status_names = described_class.state_machines[:status].states.map(&:name)
to_status_names = from_status_names - [:created] # we never want to transition into created
from_status_names.product(to_status_names)
end
with_them do
it do
stage.status = from_status.to_s
if from_status != to_status
expect(stage.set_status(to_status.to_s))
.to eq(true)
else
expect(stage.set_status(to_status.to_s))
.to eq(false), "loopback transitions are not allowed"
end
end
end
end
describe '#update_status' do describe '#update_status' do
context 'when stage objects needs to be updated' do context 'when stage objects needs to be updated' do
before do before do
......
...@@ -18,7 +18,7 @@ describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do ...@@ -18,7 +18,7 @@ describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do
it 'does update existing status of processable' do it 'does update existing status of processable' do
collection.set_processable_status(test_a.id, 'success', 100) collection.set_processable_status(test_a.id, 'success', 100)
expect(collection.status_for_names(['test-a'])).to eq('success') expect(collection.status_for_names(['test-a'], dag: false)).to eq('success')
end end
it 'ignores a missing processable' do it 'ignores a missing processable' do
...@@ -33,15 +33,18 @@ describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do ...@@ -33,15 +33,18 @@ describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do
end end
describe '#status_for_names' do describe '#status_for_names' do
where(:names, :status) do where(:names, :status, :dag) do
%w[build-a] | 'success' %w[build-a] | 'success' | false
%w[build-a build-b] | 'failed' %w[build-a build-b] | 'failed' | false
%w[build-a test-a] | 'running' %w[build-a test-a] | 'running' | false
%w[build-a] | 'success' | true
%w[build-a build-b] | 'failed' | true
%w[build-a test-a] | 'pending' | true
end end
with_them do with_them do
it 'returns composite status of given names' do it 'returns composite status of given names' do
expect(collection.status_for_names(names)).to eq(status) expect(collection.status_for_names(names, dag: dag)).to eq(status)
end end
end end
end end
......
...@@ -2,17 +2,19 @@ ...@@ -2,17 +2,19 @@
require 'spec_helper' require 'spec_helper'
require_relative 'shared_processing_service.rb' require_relative 'shared_processing_service.rb'
# require_relative 'shared_processing_service_tests_with_yaml.rb' require_relative 'shared_processing_service_tests_with_yaml.rb'
describe Ci::PipelineProcessing::AtomicProcessingService do describe Ci::PipelineProcessing::AtomicProcessingService do
before do before do
stub_feature_flags(ci_atomic_processing: true) stub_feature_flags(ci_atomic_processing: true)
# This feature flag is implicit
# Atomic Processing does not process statuses differently
stub_feature_flags(ci_composite_status: true)
end end
it_behaves_like 'Pipeline Processing Service' it_behaves_like 'Pipeline Processing Service'
# TODO: This needs to be enabled. There is a different behavior when using `needs` depending on it_behaves_like 'Pipeline Processing Service Tests With Yaml'
# a `manual` job. More info: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29405#note_327520605
# it_behaves_like 'Pipeline Processing Service Tests With Yaml'
private private
......
...@@ -7,11 +7,25 @@ require_relative 'shared_processing_service_tests_with_yaml.rb' ...@@ -7,11 +7,25 @@ require_relative 'shared_processing_service_tests_with_yaml.rb'
describe Ci::PipelineProcessing::LegacyProcessingService do describe Ci::PipelineProcessing::LegacyProcessingService do
before do before do
stub_feature_flags(ci_atomic_processing: false) stub_feature_flags(ci_atomic_processing: false)
stub_feature_flags(ci_composite_status: false)
end end
it_behaves_like 'Pipeline Processing Service' context 'when ci_composite_status is enabled' do
it_behaves_like 'Pipeline Processing Service Tests With Yaml' before do
stub_feature_flags(ci_composite_status: true)
end
it_behaves_like 'Pipeline Processing Service'
it_behaves_like 'Pipeline Processing Service Tests With Yaml'
end
context 'when ci_composite_status is disabled' do
before do
stub_feature_flags(ci_composite_status: false)
end
it_behaves_like 'Pipeline Processing Service'
it_behaves_like 'Pipeline Processing Service Tests With Yaml'
end
private private
......
...@@ -816,10 +816,10 @@ shared_examples 'Pipeline Processing Service' do ...@@ -816,10 +816,10 @@ shared_examples 'Pipeline Processing Service' do
context 'when a needed job is skipped', :sidekiq_inline do context 'when a needed job is skipped', :sidekiq_inline do
let!(:linux_build) { create_build('linux:build', stage: 'build', stage_idx: 0) } let!(:linux_build) { create_build('linux:build', stage: 'build', stage_idx: 0) }
let!(:linux_rspec) { create_build('linux:rspec', stage: 'test', stage_idx: 1) } let!(:linux_rspec) { create_build('linux:rspec', stage: 'test', stage_idx: 1) }
let!(:deploy) do let!(:deploy) { create_build('deploy', stage: 'deploy', stage_idx: 2, scheduling_type: :dag) }
create_build('deploy', stage: 'deploy', stage_idx: 2, scheduling_type: :dag, needs: [
create(:ci_build_need, name: 'linux:rspec') before do
]) create(:ci_build_need, build: deploy, name: 'linux:build')
end end
it 'skips the jobs depending on it' do it 'skips the jobs depending on it' do
...@@ -836,6 +836,23 @@ shared_examples 'Pipeline Processing Service' do ...@@ -836,6 +836,23 @@ shared_examples 'Pipeline Processing Service' do
end end
end end
context 'when a needed job is manual', :sidekiq_inline do
let!(:linux_build) { create_build('linux:build', stage: 'build', stage_idx: 0, when: 'manual', allow_failure: true) }
let!(:deploy) { create_build('deploy', stage: 'deploy', stage_idx: 1, scheduling_type: :dag) }
before do
create(:ci_build_need, build: deploy, name: 'linux:build')
end
it 'makes deploy DAG to be waiting for optional manual to finish' do
expect(process_pipeline).to be_truthy
expect(stages).to eq(%w(skipped created))
expect(all_builds.manual).to contain_exactly(linux_build)
expect(all_builds.created).to contain_exactly(deploy)
end
end
private private
def all_builds def all_builds
......
...@@ -18,36 +18,40 @@ shared_context 'Pipeline Processing Service Tests With Yaml' do ...@@ -18,36 +18,40 @@ shared_context 'Pipeline Processing Service Tests With Yaml' do
project.add_developer(user) project.add_developer(user)
end end
it 'follows transitions', :sidekiq_inline do it 'follows transitions' do
expect(pipeline).to be_persisted expect(pipeline).to be_persisted
check_expectation(test_file.dig('init', 'expect')) Sidekiq::Worker.drain_all # ensure that all async jobs are executed
check_expectation(test_file.dig('init', 'expect'), "init")
test_file['transitions'].each do |transition| test_file['transitions'].each_with_index do |transition, idx|
event_on_jobs(transition['event'], transition['jobs']) event_on_jobs(transition['event'], transition['jobs'])
check_expectation(transition['expect']) Sidekiq::Worker.drain_all # ensure that all async jobs are executed
check_expectation(transition['expect'], "transition:#{idx}")
end end
end end
private private
def check_expectation(expectation) def check_expectation(expectation, message)
expectation.each do |key, value| expect(current_state.deep_stringify_keys).to eq(expectation), message
case key end
when 'pipeline'
expect(pipeline.reload.status).to eq(value) def current_state
when 'stages' # reload pipeline and all relations
expect(pipeline.stages.pluck(:name, :status).to_h).to eq(value) pipeline.reload
when 'jobs'
expect(pipeline.builds.latest.pluck(:name, :status).to_h).to eq(value) {
end pipeline: pipeline.status,
end stages: pipeline.ordered_stages.pluck(:name, :status).to_h,
jobs: pipeline.statuses.latest.pluck(:name, :status).to_h
}
end end
def event_on_jobs(event, job_names) def event_on_jobs(event, job_names)
builds = pipeline.builds.latest.where(name: job_names).to_a statuses = pipeline.statuses.latest.by_name(job_names).to_a
expect(builds.count).to eq(job_names.count) # ensure that we have the same counts expect(statuses.count).to eq(job_names.count) # ensure that we have the same counts
builds.each { |build| build.public_send("#{event}!") } statuses.each { |status| status.public_send("#{event}!") }
end end
end end
end end
...@@ -24,9 +24,9 @@ transitions: ...@@ -24,9 +24,9 @@ transitions:
- event: enqueue - event: enqueue
jobs: [test] jobs: [test]
expect: expect:
pipeline: manual pipeline: pending
stages: stages:
test: manual test: pending
deploy: created deploy: created
jobs: jobs:
test: pending test: pending
......
...@@ -26,7 +26,7 @@ transitions: ...@@ -26,7 +26,7 @@ transitions:
expect: expect:
pipeline: pending pipeline: pending
stages: stages:
test: running test: pending
deploy: created deploy: created
jobs: jobs:
test: pending test: pending
......
...@@ -27,7 +27,7 @@ transitions: ...@@ -27,7 +27,7 @@ transitions:
expect: expect:
pipeline: pending pipeline: pending
stages: stages:
test: running test: pending
deploy: created deploy: created
jobs: jobs:
test: pending test: pending
......
...@@ -23,9 +23,9 @@ transitions: ...@@ -23,9 +23,9 @@ transitions:
- event: enqueue - event: enqueue
jobs: [test] jobs: [test]
expect: expect:
pipeline: manual pipeline: pending
stages: stages:
test: manual test: pending
deploy: created deploy: created
jobs: jobs:
test: pending test: pending
......
...@@ -36,7 +36,7 @@ transitions: ...@@ -36,7 +36,7 @@ transitions:
expect: expect:
pipeline: running pipeline: running
stages: stages:
test: running test: pending
deploy: success deploy: success
jobs: jobs:
test: pending test: pending
......
...@@ -26,7 +26,7 @@ transitions: ...@@ -26,7 +26,7 @@ transitions:
expect: expect:
pipeline: pending pipeline: pending
stages: stages:
test: running test: pending
deploy: skipped deploy: skipped
jobs: jobs:
test: pending test: pending
......
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