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