Commit 184ceb91 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'ci-resource-group-status-transition' into 'master'

Ci resource group status transition (v2)

See merge request gitlab-org/gitlab!21899
parents 2be380d2 b4847c73
......@@ -20,6 +20,7 @@
}
.ci-status-icon-pending,
.ci-status-icon-waiting-for-resource,
.ci-status-icon-failed-with-warnings,
.ci-status-icon-success-with-warnings {
svg {
......
......@@ -795,6 +795,7 @@
}
&.ci-status-icon-pending,
&.ci-status-icon-waiting-for-resource,
&.ci-status-icon-success-with-warnings {
@include mini-pipeline-graph-color($white, $orange-100, $orange-200, $orange-500, $orange-600, $orange-700);
}
......
......@@ -42,6 +42,7 @@
}
&.ci-pending,
&.ci-waiting-for-resource,
&.ci-failed-with-warnings,
&.ci-success-with-warnings {
@include status-color($orange-100, $orange-500, $orange-700);
......
......@@ -62,6 +62,7 @@ module CiStatusHelper
status.humanize
end
# rubocop:disable Metrics/CyclomaticComplexity
def ci_icon_for_status(status, size: 16)
if detailed_status?(status)
return sprite_icon(status.icon, size: size)
......@@ -77,6 +78,8 @@ module CiStatusHelper
'status_failed'
when 'pending'
'status_pending'
when 'waiting_for_resource'
'status_pending'
when 'preparing'
'status_preparing'
when 'running'
......@@ -97,6 +100,7 @@ module CiStatusHelper
sprite_icon(icon_name, size: size)
end
# rubocop:enable Metrics/CyclomaticComplexity
def ci_icon_class_for_status(status)
group = detailed_status?(status) ? status.group : status.dasherize
......
......@@ -206,9 +206,25 @@ module Ci
state_machine :status do
event :enqueue do
transition [:created, :skipped, :manual, :scheduled] => :waiting_for_resource, if: :requires_resource?
transition [:created, :skipped, :manual, :scheduled] => :preparing, if: :any_unmet_prerequisites?
end
event :enqueue_scheduled do
transition scheduled: :waiting_for_resource, if: :requires_resource?
transition scheduled: :preparing, if: :any_unmet_prerequisites?
transition scheduled: :pending
end
event :enqueue_waiting_for_resource do
transition waiting_for_resource: :preparing, if: :any_unmet_prerequisites?
transition waiting_for_resource: :pending
end
event :enqueue_preparing do
transition preparing: :pending
end
event :actionize do
transition created: :manual
end
......@@ -221,14 +237,8 @@ module Ci
transition scheduled: :manual
end
event :enqueue_scheduled do
transition scheduled: :preparing, if: ->(build) do
build.scheduled_at&.past? && build.any_unmet_prerequisites?
end
transition scheduled: :pending, if: ->(build) do
build.scheduled_at&.past? && !build.any_unmet_prerequisites?
end
before_transition on: :enqueue_scheduled do |build|
build.scheduled_at.nil? || build.scheduled_at.past? # If false is returned, it stops the transition
end
before_transition scheduled: any do |build|
......@@ -239,6 +249,27 @@ module Ci
build.scheduled_at = build.options_scheduled_at
end
before_transition any => :waiting_for_resource do |build|
build.waiting_for_resource_at = Time.now
end
before_transition on: :enqueue_waiting_for_resource do |build|
next unless build.requires_resource?
build.resource_group.assign_resource_to(build) # If false is returned, it stops the transition
end
after_transition any => :waiting_for_resource do |build|
build.run_after_commit do
Ci::ResourceGroups::AssignResourceFromResourceGroupWorker
.perform_async(build.resource_group_id)
end
end
before_transition on: :enqueue_preparing do |build|
build.any_unmet_prerequisites? # If false is returned, it stops the transition
end
after_transition created: :scheduled do |build|
build.run_after_commit do
Ci::BuildScheduleWorker.perform_at(build.scheduled_at, build.id)
......@@ -267,6 +298,16 @@ module Ci
end
end
after_transition any => ::Ci::Build.completed_statuses do |build|
next unless build.resource_group_id.present?
next unless build.resource_group.release_resource_from(build)
build.run_after_commit do
Ci::ResourceGroups::AssignResourceFromResourceGroupWorker
.perform_async(build.resource_group_id)
end
end
after_transition any => [:success, :failed, :canceled] do |build|
build.run_after_commit do
BuildFinishedWorker.perform_async(id)
......@@ -439,6 +480,11 @@ module Ci
end
end
def requires_resource?
Feature.enabled?(:ci_resource_group, project) &&
self.resource_group_id.present?
end
def has_environment?
environment.present?
end
......
......@@ -97,10 +97,14 @@ module Ci
state_machine :status, initial: :created do
event :enqueue do
transition [:created, :preparing, :skipped, :scheduled] => :pending
transition [:created, :waiting_for_resource, :preparing, :skipped, :scheduled] => :pending
transition [:success, :failed, :canceled] => :running
end
event :request_resource do
transition any - [:waiting_for_resource] => :waiting_for_resource
end
event :prepare do
transition any - [:preparing] => :preparing
end
......@@ -137,7 +141,7 @@ module Ci
# Do not add any operations to this state_machine
# Create a separate worker for each new operation
before_transition [:created, :preparing, :pending] => :running do |pipeline|
before_transition [:created, :waiting_for_resource, :preparing, :pending] => :running do |pipeline|
pipeline.started_at = Time.now
end
......@@ -160,7 +164,7 @@ module Ci
end
end
after_transition [:created, :preparing, :pending] => :running do |pipeline|
after_transition [:created, :waiting_for_resource, :preparing, :pending] => :running do |pipeline|
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
......@@ -168,7 +172,7 @@ module Ci
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
after_transition [:created, :preparing, :pending, :running] => :success do |pipeline|
after_transition [:created, :waiting_for_resource, :preparing, :pending, :running] => :success do |pipeline|
pipeline.run_after_commit { PipelineSuccessWorker.perform_async(pipeline.id) }
end
......@@ -319,7 +323,7 @@ module Ci
end
def self.bridgeable_statuses
::Ci::Pipeline::AVAILABLE_STATUSES - %w[created preparing pending]
::Ci::Pipeline::AVAILABLE_STATUSES - %w[created waiting_for_resource preparing pending]
end
def stages_count
......@@ -578,6 +582,7 @@ module Ci
new_status = latest_builds_status.to_s
case new_status
when 'created' then nil
when 'waiting_for_resource' then request_resource
when 'preparing' then prepare
when 'pending' then enqueue
when 'running' then run
......
......@@ -39,10 +39,14 @@ module Ci
state_machine :status, initial: :created do
event :enqueue do
transition [:created, :preparing] => :pending
transition [:created, :waiting_for_resource, :preparing] => :pending
transition [:success, :failed, :canceled, :skipped] => :running
end
event :request_resource do
transition any - [:waiting_for_resource] => :waiting_for_resource
end
event :prepare do
transition any - [:preparing] => :preparing
end
......@@ -81,6 +85,7 @@ module Ci
new_status = latest_stage_status.to_s
case new_status
when 'created' then nil
when 'waiting_for_resource' then request_resource
when 'preparing' then prepare
when 'pending' then enqueue
when 'running' then run
......
......@@ -96,7 +96,7 @@ class CommitStatus < ApplicationRecord
# A CommitStatus will never have prerequisites, but this event
# is shared by Ci::Build, which cannot progress unless prerequisites
# are satisfied.
transition [:created, :preparing, :skipped, :manual, :scheduled] => :pending, unless: :any_unmet_prerequisites?
transition [:created, :skipped, :manual, :scheduled] => :pending, if: :all_met_to_become_pending?
end
event :run do
......@@ -104,22 +104,22 @@ class CommitStatus < ApplicationRecord
end
event :skip do
transition [:created, :preparing, :pending] => :skipped
transition [:created, :waiting_for_resource, :preparing, :pending] => :skipped
end
event :drop do
transition [:created, :preparing, :pending, :running, :scheduled] => :failed
transition [:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled] => :failed
end
event :success do
transition [:created, :preparing, :pending, :running] => :success
transition [:created, :waiting_for_resource, :preparing, :pending, :running] => :success
end
event :cancel do
transition [:created, :preparing, :pending, :running, :manual, :scheduled] => :canceled
transition [:created, :waiting_for_resource, :preparing, :pending, :running, :manual, :scheduled] => :canceled
end
before_transition [:created, :preparing, :skipped, :manual, :scheduled] => :pending do |commit_status|
before_transition [:created, :waiting_for_resource, :preparing, :skipped, :manual, :scheduled] => :pending do |commit_status|
commit_status.queued_at = Time.now
end
......@@ -218,10 +218,18 @@ class CommitStatus < ApplicationRecord
false
end
def all_met_to_become_pending?
!any_unmet_prerequisites? && !requires_resource?
end
def any_unmet_prerequisites?
false
end
def requires_resource?
false
end
def auto_canceled?
canceled? && auto_canceled_by_id?
end
......
......@@ -5,16 +5,16 @@ module HasStatus
DEFAULT_STATUS = 'created'
BLOCKED_STATUS = %w[manual scheduled].freeze
AVAILABLE_STATUSES = %w[created preparing pending running success failed canceled skipped manual scheduled].freeze
AVAILABLE_STATUSES = %w[created waiting_for_resource preparing pending running success failed canceled skipped manual scheduled].freeze
STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze
ACTIVE_STATUSES = %w[preparing pending running].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
ORDERED_STATUSES = %w[failed preparing pending running manual scheduled canceled success skipped created].freeze
ORDERED_STATUSES = %w[failed preparing pending running waiting_for_resource manual scheduled canceled success skipped created].freeze
PASSED_WITH_WARNINGS_STATUSES = %w[failed canceled].to_set.freeze
EXCLUDE_IGNORED_STATUSES = %w[manual failed canceled].to_set.freeze
STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
failed: 4, canceled: 5, skipped: 6, manual: 7,
scheduled: 8, preparing: 9 }.freeze
scheduled: 8, preparing: 9, waiting_for_resource: 10 }.freeze
UnknownStatusError = Class.new(StandardError)
......@@ -29,6 +29,7 @@ module HasStatus
manual = scope_relevant.manual.select('count(*)').to_sql
scheduled = scope_relevant.scheduled.select('count(*)').to_sql
preparing = scope_relevant.preparing.select('count(*)').to_sql
waiting_for_resource = scope_relevant.waiting_for_resource.select('count(*)').to_sql
pending = scope_relevant.pending.select('count(*)').to_sql
running = scope_relevant.running.select('count(*)').to_sql
skipped = scope_relevant.skipped.select('count(*)').to_sql
......@@ -46,6 +47,7 @@ module HasStatus
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
WHEN (#{running})+(#{pending})>0 THEN 'running'
WHEN (#{waiting_for_resource})>0 THEN 'waiting_for_resource'
WHEN (#{manual})>0 THEN 'manual'
WHEN (#{scheduled})>0 THEN 'scheduled'
WHEN (#{preparing})>0 THEN 'preparing'
......@@ -95,6 +97,7 @@ module HasStatus
state_machine :status, initial: :created do
state :created, value: 'created'
state :waiting_for_resource, value: 'waiting_for_resource'
state :preparing, value: 'preparing'
state :pending, value: 'pending'
state :running, value: 'running'
......@@ -107,6 +110,7 @@ module HasStatus
end
scope :created, -> { with_status(:created) }
scope :waiting_for_resource, -> { with_status(:waiting_for_resource) }
scope :preparing, -> { with_status(:preparing) }
scope :relevant, -> { without_status(:created) }
scope :running, -> { with_status(:running) }
......@@ -117,8 +121,8 @@ module HasStatus
scope :skipped, -> { with_status(:skipped) }
scope :manual, -> { with_status(:manual) }
scope :scheduled, -> { with_status(:scheduled) }
scope :alive, -> { with_status(:created, :preparing, :pending, :running) }
scope :alive_or_scheduled, -> { with_status(:created, :preparing, :pending, :running, :scheduled) }
scope :alive, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running) }
scope :alive_or_scheduled, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled) }
scope :created_or_pending, -> { with_status(:created, :pending) }
scope :running_or_pending, -> { with_status(:running, :pending) }
scope :finished, -> { with_status(:success, :failed, :canceled) }
......@@ -126,7 +130,7 @@ module HasStatus
scope :incomplete, -> { without_statuses(completed_statuses) }
scope :cancelable, -> do
where(status: [:running, :preparing, :pending, :created, :scheduled])
where(status: [:running, :waiting_for_resource, :preparing, :pending, :created, :scheduled])
end
scope :without_statuses, -> (names) do
......
......@@ -11,7 +11,7 @@ module Ci
def execute
prerequisites.each(&:complete!)
build.enqueue!
build.enqueue_preparing!
rescue => e
Gitlab::ErrorTracking.track_exception(e, build_id: build.id)
......
# frozen_string_literal: true
module Ci
module ResourceGroups
class AssignResourceFromResourceGroupService < ::BaseService
# rubocop: disable CodeReuse/ActiveRecord
def execute(resource_group)
free_resources = resource_group.resources.free.count
resource_group.builds.waiting_for_resource.take(free_resources).each do |build|
build.enqueue_waiting_for_resource
end
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
......@@ -103,6 +103,7 @@
- pipeline_processing:stage_update
- pipeline_processing:update_head_pipeline_for_merge_request
- pipeline_processing:ci_build_schedule
- pipeline_processing:ci_resource_groups_assign_resource_from_resource_group
- deployment:deployments_success
- deployment:deployments_finished
......
# frozen_string_literal: true
module Ci
module ResourceGroups
class AssignResourceFromResourceGroupWorker
include ApplicationWorker
include PipelineQueue
queue_namespace :pipeline_processing
feature_category :continuous_delivery
def perform(resource_group_id)
::Ci::ResourceGroup.find_by_id(resource_group_id).try do |resource_group|
Ci::ResourceGroups::AssignResourceFromResourceGroupService.new(resource_group.project, nil)
.execute(resource_group)
end
end
end
end
end
......@@ -4443,7 +4443,8 @@ type Pipeline {
startedAt: Time
"""
Status of the pipeline (CREATED, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED)
Status of the pipeline (CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING,
RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED)
"""
status: PipelineStatusEnum!
......@@ -4521,6 +4522,7 @@ enum PipelineStatusEnum {
SCHEDULED
SKIPPED
SUCCESS
WAITING_FOR_RESOURCE
}
type Project {
......
......@@ -12203,7 +12203,7 @@
},
{
"name": "status",
"description": "Status of the pipeline (CREATED, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED)",
"description": "Status of the pipeline (CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED)",
"args": [
],
......@@ -12344,6 +12344,12 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "WAITING_FOR_RESOURCE",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "PREPARING",
"description": null,
......
......@@ -635,7 +635,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `iid` | String! | Internal ID of the pipeline |
| `sha` | String! | SHA of the pipeline's commit |
| `beforeSha` | String | Base SHA of the source branch |
| `status` | PipelineStatusEnum! | Status of the pipeline (CREATED, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED) |
| `status` | PipelineStatusEnum! | Status of the pipeline (CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED) |
| `detailedStatus` | DetailedStatus! | Detailed status of the pipeline |
| `duration` | Int | Duration of the pipeline in seconds |
| `coverage` | Float | Coverage percentage |
......
......@@ -11,6 +11,7 @@ module Gitlab
Status::Build::Manual,
Status::Build::Canceled,
Status::Build::Created,
Status::Build::WaitingForResource,
Status::Build::Preparing,
Status::Build::Pending,
Status::Build::Skipped],
......
# frozen_string_literal: true
module Gitlab
module Ci
module Status
module Build
class WaitingForResource < Status::Extended
##
# TODO: image is shared with 'pending'
# until we get a dedicated one
#
def illustration
{
image: 'illustrations/pending_job_empty.svg',
size: 'svg-430',
title: _('This job is waiting for resource: ') + subject.resource_group.key
}
end
def self.matches?(build, _)
build.waiting_for_resource?
end
end
end
end
end
end
......@@ -25,6 +25,8 @@ module Gitlab
# 2. In other cases we assume that status is of that type
# based on what statuses are no longer valid based on the
# data set that we have
# rubocop: disable Metrics/CyclomaticComplexity
# rubocop: disable Metrics/PerceivedComplexity
def status
return if none?
......@@ -43,6 +45,8 @@ module Gitlab
'pending'
elsif any_of?(:running, :pending)
'running'
elsif any_of?(:waiting_for_resource)
'waiting_for_resource'
elsif any_of?(:manual)
'manual'
elsif any_of?(:scheduled)
......@@ -56,6 +60,8 @@ module Gitlab
end
end
end
# rubocop: enable Metrics/CyclomaticComplexity
# rubocop: enable Metrics/PerceivedComplexity
def warnings?
@status_set.include?(:success_with_warnings)
......
......@@ -20,7 +20,7 @@ module Gitlab
def core_status
Gitlab::Ci::Status
.const_get(@status.capitalize, false)
.const_get(@status.to_s.camelize, false)
.new(@subject, @user)
.extend(self.class.common_helpers)
end
......
# frozen_string_literal: true
module Gitlab
module Ci
module Status
class WaitingForResource < Status::Core
def text
s_('CiStatusText|waiting')
end
def label
s_('CiStatusLabel|waiting for resource')
end
def icon
'status_pending'
end
def favicon
'favicon_pending'
end
def group
'waiting-for-resource'
end
end
end
end
end
......@@ -3361,6 +3361,9 @@ msgstr ""
msgid "CiStatusLabel|waiting for manual action"
msgstr ""
msgid "CiStatusLabel|waiting for resource"
msgstr ""
msgid "CiStatusText|blocked"
msgstr ""
......@@ -3391,6 +3394,9 @@ msgstr ""
msgid "CiStatusText|skipped"
msgstr ""
msgid "CiStatusText|waiting"
msgstr ""
msgid "CiStatus|running"
msgstr ""
......@@ -18511,6 +18517,9 @@ msgstr ""
msgid "This job is stuck because you don't have any active runners that can run this job."
msgstr ""
msgid "This job is waiting for resource: "
msgstr ""
msgid "This job requires a manual action"
msgstr ""
......
......@@ -77,6 +77,10 @@ FactoryBot.define do
status { 'created' }
end
trait :waiting_for_resource do
status { 'waiting_for_resource' }
end
trait :preparing do
status { 'preparing' }
end
......
......@@ -35,6 +35,10 @@ FactoryBot.define do
status { 'pending' }
end
trait :waiting_for_resource do
status { 'waiting_for_resource' }
end
trait :preparing do
status { 'preparing' }
end
......
......@@ -606,6 +606,117 @@ describe 'Pipeline', :js do
end
end
context 'when build requires resource', :sidekiq_inline do
let_it_be(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:resource_group) { create(:ci_resource_group, project: project) }
let!(:test_job) do
create(:ci_build, :pending, stage: 'test', name: 'test',
stage_idx: 1, pipeline: pipeline, project: project)
end
let!(:deploy_job) do
create(:ci_build, :created, stage: 'deploy', name: 'deploy',
stage_idx: 2, pipeline: pipeline, project: project, resource_group: resource_group)
end
describe 'GET /:project/pipelines/:id' do
subject { visit project_pipeline_path(project, pipeline) }
it 'shows deploy job as created' do
subject
within('.pipeline-header-container') do
expect(page).to have_content('pending')
end
within('.pipeline-graph') do
within '.stage-column:nth-child(1)' do
expect(page).to have_content('test')
expect(page).to have_css('.ci-status-icon-pending')
end
within '.stage-column:nth-child(2)' do
expect(page).to have_content('deploy')
expect(page).to have_css('.ci-status-icon-created')
end
end
end
context 'when test job succeeded' do
before do
test_job.success!
end
it 'shows deploy job as pending' do
subject
within('.pipeline-header-container') do
expect(page).to have_content('running')
end
within('.pipeline-graph') do
within '.stage-column:nth-child(1)' do
expect(page).to have_content('test')
expect(page).to have_css('.ci-status-icon-success')
end
within '.stage-column:nth-child(2)' do
expect(page).to have_content('deploy')
expect(page).to have_css('.ci-status-icon-pending')
end
end
end
end
context 'when test job succeeded but there are no available resources' do
let(:another_job) { create(:ci_build, :running, project: project, resource_group: resource_group) }
before do
resource_group.assign_resource_to(another_job)
test_job.success!
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
context 'when resource is released from another job' do
before do
another_job.success!
end
it 'shows deploy job as pending' do
subject
within('.pipeline-header-container') do
expect(page).to have_content('running')
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-pending')
end
end
end
end
end
end
end
describe 'GET /:project/pipelines/:id/builds' do
include_context 'pipeline builds'
......
......@@ -22,7 +22,7 @@ describe Gitlab::Ci::Status::External::Factory do
end
let(:expected_status) do
Gitlab::Ci::Status.const_get(simple_status.capitalize, false)
Gitlab::Ci::Status.const_get(simple_status.to_s.camelize, false)
end
it "fabricates a core status #{simple_status}" do
......
......@@ -13,7 +13,7 @@ describe Gitlab::Ci::Status::Factory do
let(:resource) { double('resource', status: simple_status) }
let(:expected_status) do
Gitlab::Ci::Status.const_get(simple_status.capitalize, false)
Gitlab::Ci::Status.const_get(simple_status.to_s.camelize, false)
end
it "fabricates a core status #{simple_status}" do
......
......@@ -18,7 +18,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
let(:pipeline) { create(:ci_pipeline, status: simple_status) }
let(:expected_status) do
Gitlab::Ci::Status.const_get(simple_status.capitalize, false)
Gitlab::Ci::Status.const_get(simple_status.camelize, false)
end
it "matches correct core status for #{simple_status}" do
......
......@@ -34,7 +34,7 @@ describe Gitlab::Ci::Status::Stage::Factory do
it "fabricates a core status #{core_status}" do
expect(status).to be_a(
Gitlab::Ci::Status.const_get(core_status.capitalize, false))
Gitlab::Ci::Status.const_get(core_status.camelize, false))
end
it 'extends core status with common stage methods' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Status::WaitingForResource do
subject do
described_class.new(double('subject'), double('user'))
end
describe '#text' do
it { expect(subject.text).to eq 'waiting' }
end
describe '#label' do
it { expect(subject.label).to eq 'waiting for resource' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'status_pending' }
end
describe '#favicon' do
it { expect(subject.favicon).to eq 'favicon_pending' }
end
describe '#group' do
it { expect(subject.group).to eq 'waiting-for-resource' }
end
end
......@@ -1119,6 +1119,60 @@ describe Ci::Build do
end
end
describe 'state transition with resource group' do
let(:resource_group) { create(:ci_resource_group, project: project) }
context 'when build status is created' do
let(:build) { create(:ci_build, :created, project: project, resource_group: resource_group) }
it 'is waiting for resource when build is enqueued' do
expect(Ci::ResourceGroups::AssignResourceFromResourceGroupWorker).to receive(:perform_async).with(resource_group.id)
expect { build.enqueue! }.to change { build.status }.from('created').to('waiting_for_resource')
expect(build.waiting_for_resource_at).not_to be_nil
end
context 'when build is waiting for resource' do
before do
build.update_column(:status, 'waiting_for_resource')
end
it 'is enqueued when build requests resource' do
expect { build.enqueue_waiting_for_resource! }.to change { build.status }.from('waiting_for_resource').to('pending')
end
it 'releases a resource when build finished' do
expect(build.resource_group).to receive(:release_resource_from).with(build).and_call_original
expect(Ci::ResourceGroups::AssignResourceFromResourceGroupWorker).to receive(:perform_async).with(build.resource_group_id)
build.enqueue_waiting_for_resource!
build.success!
end
context 'when build has prerequisites' do
before do
allow(build).to receive(:any_unmet_prerequisites?) { true }
end
it 'is preparing when build is enqueued' do
expect { build.enqueue_waiting_for_resource! }.to change { build.status }.from('waiting_for_resource').to('preparing')
end
end
context 'when there are no available resources' do
before do
resource_group.assign_resource_to(create(:ci_build))
end
it 'stays as waiting for resource when build requests resource' do
expect { build.enqueue_waiting_for_resource }.not_to change { build.status }
end
end
end
end
end
describe '#on_stop' do
subject { build.on_stop }
......
......@@ -1750,7 +1750,7 @@ describe Ci::Pipeline, :mailer do
subject { described_class.bridgeable_statuses }
it { is_expected.to be_an(Array) }
it { is_expected.not_to include('created', 'preparing', 'pending') }
it { is_expected.not_to include('created', 'waiting_for_resource', 'preparing', 'pending') }
end
describe '#status', :sidekiq_might_not_need_inline do
......@@ -1760,6 +1760,17 @@ describe Ci::Pipeline, :mailer do
subject { pipeline.reload.status }
context 'on waiting for resource' do
before do
allow(build).to receive(:requires_resource?) { true }
allow(Ci::ResourceGroups::AssignResourceFromResourceGroupWorker).to receive(:perform_async)
build.enqueue
end
it { is_expected.to eq('waiting_for_resource') }
end
context 'on prepare' do
before do
# Prevent skipping directly to 'pending'
......
......@@ -105,6 +105,18 @@ describe Ci::Stage, :models do
end
end
context 'when build is waiting for resource' do
before do
create(:ci_build, :waiting_for_resource, stage_id: stage.id)
end
it 'updates status to waiting for resource' do
expect { stage.update_status }
.to change { stage.reload.status }
.to 'waiting_for_resource'
end
end
context 'when stage is skipped because is empty' do
it 'updates status to skipped' do
expect { stage.update_status }
......
......@@ -634,6 +634,30 @@ describe CommitStatus do
end
end
describe '#all_met_to_become_pending?' do
subject { commit_status.all_met_to_become_pending? }
let(:commit_status) { create(:commit_status) }
it { is_expected.to eq(true) }
context 'when build requires a resource' do
before do
allow(commit_status).to receive(:requires_resource?) { true }
end
it { is_expected.to eq(false) }
end
context 'when build has a prerequisite' do
before do
allow(commit_status).to receive(:any_unmet_prerequisites?) { true }
end
it { is_expected.to eq(false) }
end
end
describe '#enqueue' do
let!(:current_time) { Time.new(2018, 4, 5, 14, 0, 0) }
......@@ -654,12 +678,6 @@ describe CommitStatus do
it_behaves_like 'commit status enqueued'
end
context 'when initial state is :preparing' do
let(:commit_status) { create(:commit_status, :preparing) }
it_behaves_like 'commit status enqueued'
end
context 'when initial state is :skipped' do
let(:commit_status) { create(:commit_status, :skipped) }
......
......@@ -39,6 +39,22 @@ describe HasStatus do
it { is_expected.to eq 'running' }
end
context 'all waiting for resource' do
let!(:statuses) do
[create(type, status: :waiting_for_resource), create(type, status: :waiting_for_resource)]
end
it { is_expected.to eq 'waiting_for_resource' }
end
context 'at least one waiting for resource' do
let!(:statuses) do
[create(type, status: :success), create(type, status: :waiting_for_resource)]
end
it { is_expected.to eq 'waiting_for_resource' }
end
context 'all preparing' do
let!(:statuses) do
[create(type, status: :preparing), create(type, status: :preparing)]
......@@ -219,7 +235,7 @@ describe HasStatus do
end
end
%i[created preparing running pending success
%i[created waiting_for_resource preparing running pending success
failed canceled skipped].each do |status|
it_behaves_like 'having a job', status
end
......@@ -265,7 +281,7 @@ describe HasStatus do
describe '.alive' do
subject { CommitStatus.alive }
%i[running pending preparing created].each do |status|
%i[running pending waiting_for_resource preparing created].each do |status|
it_behaves_like 'containing the job', status
end
......@@ -277,7 +293,7 @@ describe HasStatus do
describe '.alive_or_scheduled' do
subject { CommitStatus.alive_or_scheduled }
%i[running pending preparing created scheduled].each do |status|
%i[running pending waiting_for_resource preparing created scheduled].each do |status|
it_behaves_like 'containing the job', status
end
......@@ -313,7 +329,7 @@ describe HasStatus do
describe '.cancelable' do
subject { CommitStatus.cancelable }
%i[running pending preparing created scheduled].each do |status|
%i[running pending waiting_for_resource preparing created scheduled].each do |status|
it_behaves_like 'containing the job', status
end
......
......@@ -14,7 +14,7 @@ describe Ci::PrepareBuildService do
shared_examples 'build enqueueing' do
it 'enqueues the build' do
expect(build).to receive(:enqueue).once
expect(build).to receive(:enqueue_preparing).once
subject
end
......@@ -34,7 +34,7 @@ describe Ci::PrepareBuildService do
context 'prerequisites fail to complete' do
before do
allow(build).to receive(:enqueue).and_return(false)
allow(build).to receive(:enqueue_preparing).and_return(false)
end
it 'drops the build' do
......
......@@ -261,12 +261,16 @@ describe Ci::ProcessPipelineService, '#execute' do
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'scheduled' })
enqueue_scheduled('rollout10%')
Timecop.travel 2.minutes.from_now do
enqueue_scheduled('rollout10%')
end
succeed_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'success', 'rollout100%': 'scheduled' })
enqueue_scheduled('rollout100%')
Timecop.travel 2.minutes.from_now do
enqueue_scheduled('rollout100%')
end
succeed_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'success', 'rollout100%': 'success', 'cleanup': 'pending' })
......@@ -328,7 +332,9 @@ describe Ci::ProcessPipelineService, '#execute' do
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'scheduled' })
enqueue_scheduled('rollout10%')
Timecop.travel 2.minutes.from_now do
enqueue_scheduled('rollout10%')
end
fail_running_or_pending
expect(builds_names_and_statuses).to eq({ 'build': 'success', 'rollout10%': 'failed' })
......@@ -394,7 +400,9 @@ describe Ci::ProcessPipelineService, '#execute' do
expect(process_pipeline).to be_truthy
expect(builds_names_and_statuses).to eq({ 'delayed1': 'scheduled', 'delayed2': 'scheduled' })
enqueue_scheduled('delayed1')
Timecop.travel 2.minutes.from_now do
enqueue_scheduled('delayed1')
end
expect(builds_names_and_statuses).to eq({ 'delayed1': 'pending', 'delayed2': 'scheduled' })
expect(pipeline.reload.status).to eq 'running'
......@@ -413,7 +421,9 @@ describe Ci::ProcessPipelineService, '#execute' do
expect(process_pipeline).to be_truthy
expect(builds_names_and_statuses).to eq({ 'delayed': 'scheduled' })
enqueue_scheduled('delayed')
Timecop.travel 2.minutes.from_now do
enqueue_scheduled('delayed')
end
fail_running_or_pending
expect(builds_names_and_statuses).to eq({ 'delayed': 'failed', 'job': 'pending' })
......@@ -906,7 +916,7 @@ describe Ci::ProcessPipelineService, '#execute' do
end
def enqueue_scheduled(name)
builds.scheduled.find_by(name: name).enqueue
builds.scheduled.find_by(name: name).enqueue_scheduled
end
def retry_build(name)
......
# frozen_string_literal: true
require 'spec_helper'
describe Ci::ResourceGroups::AssignResourceFromResourceGroupService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
describe '#execute' do
subject { service.execute(resource_group) }
let(:resource_group) { create(:ci_resource_group, project: project) }
let!(:build) { create(:ci_build, :waiting_for_resource, project: project, user: user, resource_group: resource_group) }
context 'when there is an available resource' do
it 'requests resource' do
subject
expect(build.reload).to be_pending
expect(build.resource).to be_present
end
context 'when failed to request resource' do
before do
allow_next_instance_of(Ci::Build) do |build|
allow(build).to receive(:enqueue_waiting_for_resource) { false }
end
end
it 'has a build waiting for resource' do
subject
expect(build).to be_waiting_for_resource
end
end
context 'when the build has already retained a resource' do
before do
resource_group.assign_resource_to(build)
build.update_column(:status, :pending)
end
it 'has a pending build' do
subject
expect(build).to be_pending
end
end
end
context 'when there are no available resources' do
before do
resource_group.assign_resource_to(create(:ci_build))
end
it 'does not request resource' do
expect_any_instance_of(Ci::Build).not_to receive(:enqueue_waiting_for_resource)
subject
end
end
end
end
......@@ -26,6 +26,18 @@ describe Ci::RunScheduledBuildService do
expect(build).to be_pending
end
context 'when build requires resource' do
let(:resource_group) { create(:ci_resource_group, project: project) }
before do
build.update!(resource_group: resource_group)
end
it 'transits to waiting for resource status' do
expect { subject }.to change { build.status }.from('scheduled').to('waiting_for_resource')
end
end
end
context 'when scheduled_at is not expired' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Ci::ResourceGroups::AssignResourceFromResourceGroupWorker do
let(:worker) { described_class.new }
describe '#perform' do
subject { worker.perform(resource_group_id) }
context 'when resource group exists' do
let(:resource_group) { create(:ci_resource_group) }
let(:resource_group_id) { resource_group.id }
it 'executes AssignResourceFromResourceGroupService' do
expect_next_instance_of(Ci::ResourceGroups::AssignResourceFromResourceGroupService, resource_group.project, nil) do |service|
expect(service).to receive(:execute).with(resource_group)
end
subject
end
end
context 'when build does not exist' do
let(:resource_group_id) { 123 }
it 'does not execute AssignResourceFromResourceGroupService' do
expect(Ci::ResourceGroups::AssignResourceFromResourceGroupService).not_to receive(:new)
subject
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