Commit 7b83426d authored by Shinya Maeda's avatar Shinya Maeda

Apply 3 suggestion(s) to 1 file(s)

This commit fixes the documentation and adding tests.
parent c4aca6df
...@@ -3934,17 +3934,16 @@ For more information, see [Deployments Safety](../environments/deployment_safety ...@@ -3934,17 +3934,16 @@ For more information, see [Deployments Safety](../environments/deployment_safety
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/39057) in GitLab 13.9. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/39057) in GitLab 13.9.
You can define `resource_group` for downstream pipelines that are sensitive to You can define `resource_group` for downstream pipelines that are sensitive to concurrent
concurrent executions. Downstream pipelines can be triggered by [`trigger` keyword](#trigger) executions. The [`trigger` keyword](#trigger) can trigger downstream pipelines. The
and [`resource_group` keyword](#resource_group) can co-exist with it. This is useful to control the [`resource_group` keyword](#resource_group) can co-exist with it. This is useful to control the
concurency for deployment pipelines, while running non-sensitive jobs concurrently. concurrency for deployment pipelines, while running non-sensitive jobs concurrently.
In ths following example, we have two pipeline configurations in a project. This example has two pipeline configurations in a project. When a pipeline starts running,
When a pipeline started running, non-sensitive jobs are executed at first and they non-sensitive jobs are executed first and aren't affected by concurrent executions in other
won't be affected by the concurrent executions in the other pipelines. pipelines. However, GitLab ensures that there are no other deployment pipelines running before
However, when it's about to trigger a deployment (child) pipeline, GitLab ensures that triggering a deployment (child) pipeline. If other deployment pipelines are running, GitLab waits
there are no other deployment pipelines running, and if it's conflicted, until those pipelines finish before running another one.
the pipeline will wait until it's finished.
```yaml ```yaml
# .gitlab-ci.yml (parent pipeline) # .gitlab-ci.yml (parent pipeline)
...@@ -3981,9 +3980,9 @@ deployment: ...@@ -3981,9 +3980,9 @@ deployment:
script: echo "Deploying..." script: echo "Deploying..."
``` ```
Please note that [`strategy: depend`](#linking-pipelines-with-triggerstrategy) Note that you must define [`strategy: depend`](#linking-pipelines-with-triggerstrategy)
must be defined with the `trigger` keyword. This ensures that the lock won't with the `trigger` keyword. This ensures that the lock isn't released until the downstream pipeline
be relesed until the downstream pipeline has finished. finishes.
### `release` ### `release`
......
...@@ -17,8 +17,8 @@ module Gitlab ...@@ -17,8 +17,8 @@ module Gitlab
} }
end end
def self.matches?(build, _) def self.matches?(processable, _)
build.waiting_for_resource? processable.waiting_for_resource?
end end
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :ci_bridge, class: 'Ci::Bridge' do factory :ci_bridge, class: 'Ci::Bridge', parent: :ci_processable do
name { 'bridge' } name { 'bridge' }
stage { 'test' }
stage_idx { 0 }
ref { 'master' }
tag { false }
created_at { '2013-10-29 09:50:00 CET' } created_at { '2013-10-29 09:50:00 CET' }
status { :created } status { :created }
scheduling_type { 'stage' }
pipeline factory: :ci_pipeline
trait :variables do trait :variables do
yaml_variables do yaml_variables do
......
...@@ -3,15 +3,10 @@ ...@@ -3,15 +3,10 @@
include ActionDispatch::TestProcess include ActionDispatch::TestProcess
FactoryBot.define do FactoryBot.define do
factory :ci_build, class: 'Ci::Build' do factory :ci_build, class: 'Ci::Build', parent: :ci_processable do
name { 'test' } name { 'test' }
stage { 'test' }
stage_idx { 0 }
ref { 'master' }
tag { false }
add_attribute(:protected) { false } add_attribute(:protected) { false }
created_at { 'Di 29. Okt 09:50:00 CET 2013' } created_at { 'Di 29. Okt 09:50:00 CET 2013' }
scheduling_type { 'stage' }
pending pending
options do options do
...@@ -28,7 +23,6 @@ FactoryBot.define do ...@@ -28,7 +23,6 @@ FactoryBot.define do
] ]
end end
pipeline factory: :ci_pipeline
project { pipeline.project } project { pipeline.project }
trait :degenerated do trait :degenerated do
...@@ -79,10 +73,6 @@ FactoryBot.define do ...@@ -79,10 +73,6 @@ FactoryBot.define do
status { 'created' } status { 'created' }
end end
trait :waiting_for_resource do
status { 'waiting_for_resource' }
end
trait :preparing do trait :preparing do
status { 'preparing' } status { 'preparing' }
end end
...@@ -213,14 +203,6 @@ FactoryBot.define do ...@@ -213,14 +203,6 @@ FactoryBot.define do
trigger_request factory: :ci_trigger_request trigger_request factory: :ci_trigger_request
end end
trait :resource_group do
waiting_for_resource_at { 5.minutes.ago }
after(:build) do |build, evaluator|
build.resource_group = create(:ci_resource_group, project: build.project)
end
end
trait :with_deployment do trait :with_deployment do
after(:build) do |build, evaluator| after(:build) do |build, evaluator|
## ##
......
# frozen_string_literal: true
FactoryBot.define do
factory :ci_processable, class: 'Ci::Processable' do
name { 'processable' }
stage { 'test' }
stage_idx { 0 }
ref { 'master' }
tag { false }
pipeline factory: :ci_pipeline
project { pipeline.project }
scheduling_type { 'stage' }
trait :waiting_for_resource do
status { 'waiting_for_resource' }
end
trait :resource_group do
waiting_for_resource_at { 5.minutes.ago }
after(:build) do |processable, evaluator|
processable.resource_group = create(:ci_resource_group, project: processable.project)
end
end
end
end
...@@ -846,6 +846,28 @@ RSpec.describe 'Pipeline', :js do ...@@ -846,6 +846,28 @@ RSpec.describe 'Pipeline', :js do
end end
end end
end end
context 'when deploy job is a bridge to trigger a downstream pipeline' do
let!(:deploy_job) do
create(:ci_bridge, :created, stage: 'deploy', name: 'deploy',
stage_idx: 2, pipeline: pipeline, project: project, resource_group: resource_group)
end
it 'shows deploy job as waiting for resource' do
subject
within('.pipeline-header-container') do
expect(page).to have_content('waiting')
end
within('.pipeline-graph') do
within '.stage-column:nth-child(2)' do
expect(page).to have_content('deploy')
expect(page).to have_css('.ci-status-icon-waiting-for-resource')
end
end
end
end
end end
end end
end end
......
...@@ -383,14 +383,25 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do ...@@ -383,14 +383,25 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
end end
context 'when job is a bridge' do context 'when job is a bridge' do
let(:attributes) do let(:base_attributes) do
{ {
name: 'rspec', ref: 'master', options: { trigger: 'my/project' }, scheduling_type: :stage name: 'rspec', ref: 'master', options: { trigger: 'my/project' }, scheduling_type: :stage
} }
end end
let(:attributes) { base_attributes }
it { is_expected.to be_a(::Ci::Bridge) } it { is_expected.to be_a(::Ci::Bridge) }
it { is_expected.to be_valid } it { is_expected.to be_valid }
context 'when job belongs to a resource group' do
let(:attributes) { base_attributes.merge(resource_group_key: 'iOS') }
it 'returns a job with resource group' do
expect(subject.resource_group).not_to be_nil
expect(subject.resource_group.key).to eq('iOS')
end
end
end end
it 'memoizes a resource object' do it 'memoizes a resource object' do
......
...@@ -117,14 +117,31 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory do ...@@ -117,14 +117,31 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory do
end end
end end
context 'when bridge is waiting for resource' do
let(:bridge) { create_bridge(:waiting_for_resource, :resource_group) }
it 'matches correct core status' do
expect(factory.core_status).to be_a Gitlab::Ci::Status::WaitingForResource
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'waiting'
expect(status.group).to eq 'waiting-for-resource'
expect(status.icon).to eq 'status_pending'
expect(status.favicon).to eq 'favicon_pending'
expect(status.illustration).to include(:image, :size, :title)
expect(status).not_to have_details
end
end
private private
def create_bridge(trait) def create_bridge(*traits)
upstream_project = create(:project, :repository) upstream_project = create(:project, :repository)
downstream_project = create(:project, :repository) downstream_project = create(:project, :repository)
upstream_pipeline = create(:ci_pipeline, :running, project: upstream_project) upstream_pipeline = create(:ci_pipeline, :running, project: upstream_project)
trigger = { trigger: { project: downstream_project.full_path, branch: 'feature' } } trigger = { trigger: { project: downstream_project.full_path, branch: 'feature' } }
create(:ci_bridge, trait, options: trigger, pipeline: upstream_pipeline) create(:ci_bridge, *traits, options: trigger, pipeline: upstream_pipeline)
end end
end end
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Status::Bridge::WaitingForResource do
it { expect(described_class).to be < Gitlab::Ci::Status::Processable::WaitingForResource }
end
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Status::Build::WaitingForResource do
it { expect(described_class).to be < Gitlab::Ci::Status::Processable::WaitingForResource }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Status::Processable::WaitingForResource do
let(:user) { create(:user) }
subject do
processable = create(:ci_build, :waiting_for_resource, :resource_group)
described_class.new(Gitlab::Ci::Status::Core.new(processable, user))
end
describe '#illustration' do
it { expect(subject.illustration).to include(:image, :size, :title) }
end
describe '.matches?' do
subject {described_class.matches?(processable, user) }
context 'when processable is waiting for resource' do
let(:processable) { create(:ci_build, :waiting_for_resource) }
it 'is a correct match' do
expect(subject).to be true
end
end
context 'when processable is not waiting for resource' do
let(:processable) { create(:ci_build) }
it 'does not match' do
expect(subject).to be false
end
end
end
end
...@@ -80,6 +80,14 @@ RSpec.describe Ci::Bridge do ...@@ -80,6 +80,14 @@ RSpec.describe Ci::Bridge do
end end
end end
it "schedules downstream pipeline creation when the status is waiting for resource" do
bridge.status = :waiting_for_resource
expect(bridge).to receive(:schedule_downstream_pipeline!)
bridge.enqueue_waiting_for_resource!
end
it 'raises error when the status is failed' do it 'raises error when the status is failed' do
bridge.status = :failed bridge.status = :failed
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::CreatePipelineService, '#execute' do
let_it_be(:group) { create(:group, name: 'my-organization') }
let(:upstream_project) { create(:project, :repository, name: 'upstream', group: group) }
let(:downstram_project) { create(:project, :repository, name: 'downstream', group: group) }
let(:user) { create(:user) }
let(:service) do
described_class.new(upstream_project, user, ref: 'master')
end
before do
upstream_project.add_developer(user)
downstram_project.add_developer(user)
create_gitlab_ci_yml(upstream_project, upstream_config)
create_gitlab_ci_yml(downstram_project, downstream_config)
end
context 'with resource group', :aggregate_failures do
let(:upstream_config) do
<<~YAML
instrumentation_test:
stage: test
resource_group: iOS
trigger:
project: my-organization/downstream
strategy: depend
YAML
end
let(:downstream_config) do
<<~YAML
test:
script: echo "Testing..."
YAML
end
it 'creates bridge job with resource group' do
pipeline = create_pipeline!
test = pipeline.statuses.find_by(name: 'instrumentation_test')
expect(pipeline).to be_created_successfully
expect(pipeline.triggered_pipelines).not_to be_exist
expect(upstream_project.resource_groups.count).to eq(1)
expect(test).to be_a Ci::Bridge
expect(test).to be_waiting_for_resource
expect(test.resource_group.key).to eq('iOS')
end
context 'when sidekiq processes the job', :sidekiq_inline do
it 'transitions to pending status and triggers a downstream pipeline' do
pipeline = create_pipeline!
test = pipeline.statuses.find_by(name: 'instrumentation_test')
expect(test).to be_pending
expect(pipeline.triggered_pipelines.count).to eq(1)
end
context 'when the resource is occupied by the other bridge' do
before do
resource_group = create(:ci_resource_group, project: upstream_project, key: 'iOS')
resource_group.assign_resource_to(create(:ci_build, project: upstream_project))
end
it 'stays waiting for resource' do
pipeline = create_pipeline!
test = pipeline.statuses.find_by(name: 'instrumentation_test')
expect(test).to be_waiting_for_resource
expect(pipeline.triggered_pipelines.count).to eq(0)
end
end
end
end
def create_pipeline!
service.execute(:push)
end
def create_gitlab_ci_yml(project, content)
project.repository.create_file(user, '.gitlab-ci.yml', content, branch_name: 'master', message: 'test')
end
end
...@@ -84,19 +84,46 @@ RSpec.describe Ci::CreatePipelineService, '#execute' do ...@@ -84,19 +84,46 @@ RSpec.describe Ci::CreatePipelineService, '#execute' do
stage: test stage: test
resource_group: iOS resource_group: iOS
trigger: trigger:
include: include: path/to/child.yml
- local: path/to/child.yml strategy: depend
YAML YAML
end end
it 'creates bridge job but still resource group is no-op', :aggregate_failures do it 'creates bridge job with resource group', :aggregate_failures do
pipeline = create_pipeline! pipeline = create_pipeline!
test = pipeline.statuses.find_by(name: 'instrumentation_test') test = pipeline.statuses.find_by(name: 'instrumentation_test')
expect(pipeline).to be_created_successfully
expect(pipeline).to be_persisted expect(pipeline.triggered_pipelines).not_to be_exist
expect(test).to be_a Ci::Bridge
expect(project.resource_groups.count).to eq(1) expect(project.resource_groups.count).to eq(1)
expect(test).to be_a Ci::Bridge
expect(test).to be_waiting_for_resource
expect(test.resource_group.key).to eq('iOS')
end
context 'when sidekiq processes the job', :sidekiq_inline do
it 'transitions to pending status and triggers a downstream pipeline' do
pipeline = create_pipeline!
test = pipeline.statuses.find_by(name: 'instrumentation_test')
expect(test).to be_pending
expect(pipeline.triggered_pipelines.count).to eq(1)
end
context 'when the resource is occupied by the other bridge' do
before do
resource_group = create(:ci_resource_group, project: project, key: 'iOS')
resource_group.assign_resource_to(create(:ci_build, project: project))
end
it 'stays waiting for resource' do
pipeline = create_pipeline!
test = pipeline.statuses.find_by(name: 'instrumentation_test')
expect(test).to be_waiting_for_resource
expect(pipeline.triggered_pipelines.count).to eq(0)
end
end
end end
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