diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/lib/utils/datetime_utility.js | 48 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/markdown/header.vue | 6 | ||||
-rw-r--r-- | app/assets/stylesheets/framework/icons.scss | 1 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/issuable.scss | 2 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/pipelines.scss | 1 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/status.scss | 1 | ||||
-rw-r--r-- | app/helpers/ci_status_helper.rb | 4 | ||||
-rw-r--r-- | app/models/ci/build.rb | 62 | ||||
-rw-r--r-- | app/models/ci/pipeline.rb | 15 | ||||
-rw-r--r-- | app/models/ci/stage.rb | 7 | ||||
-rw-r--r-- | app/models/commit_status.rb | 20 | ||||
-rw-r--r-- | app/models/concerns/has_status.rb | 16 | ||||
-rw-r--r-- | app/services/ci/prepare_build_service.rb | 2 | ||||
-rw-r--r-- | app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb | 17 | ||||
-rw-r--r-- | app/workers/all_queues.yml | 1 | ||||
-rw-r--r-- | app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb | 20 |
16 files changed, 192 insertions, 31 deletions
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 794391da103..0ffb31b323b 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -470,7 +470,7 @@ export const pikadayToString = date => { */ export const parseSeconds = ( seconds, - { daysPerWeek = 5, hoursPerDay = 8, limitToHours = false } = {}, + { daysPerWeek = 5, hoursPerDay = 8, limitToHours = false, limitToDays = false } = {}, ) => { const DAYS_PER_WEEK = daysPerWeek; const HOURS_PER_DAY = hoursPerDay; @@ -486,8 +486,11 @@ export const parseSeconds = ( minutes: 1, }; - if (limitToHours) { + if (limitToDays || limitToHours) { timePeriodConstraints.weeks = 0; + } + + if (limitToHours) { timePeriodConstraints.days = 0; } @@ -612,3 +615,44 @@ export const secondsToDays = seconds => Math.round(seconds / 86400); * @return {Date} the date following the date provided */ export const dayAfter = date => new Date(newDate(date).setDate(date.getDate() + 1)); + +/** + * Mimics the behaviour of the rails distance_of_time_in_words function + * https://api.rubyonrails.org/v6.0.1/classes/ActionView/Helpers/DateHelper.html#method-i-distance_of_time_in_words + * 0 < -> 29 secs => less than a minute + * 30 secs < -> 1 min, 29 secs => 1 minute + * 1 min, 30 secs < -> 44 mins, 29 secs => [2..44] minutes + * 44 mins, 30 secs < -> 89 mins, 29 secs => about 1 hour + * 89 mins, 30 secs < -> 23 hrs, 59 mins, 29 secs => about[2..24]hours + * 23 hrs, 59 mins, 30 secs < -> 41 hrs, 59 mins, 29 secs => 1 day + * 41 hrs, 59 mins, 30 secs => x days + * + * @param {Number} seconds + * @return {String} approximated time + */ +export const approximateDuration = (seconds = 0) => { + if (!_.isNumber(seconds) || seconds < 0) { + return ''; + } + + const ONE_MINUTE_LIMIT = 90; // 1 minute 30s + const MINUTES_LIMIT = 2670; // 44 minutes 30s + const ONE_HOUR_LIMIT = 5370; // 89 minutes 30s + const HOURS_LIMIT = 86370; // 23 hours 59 minutes 30s + const ONE_DAY_LIMIT = 151170; // 41 hours 59 minutes 30s + + const { days = 0, hours = 0, minutes = 0 } = parseSeconds(seconds, { + daysPerWeek: 7, + hoursPerDay: 24, + limitToDays: true, + }); + + if (seconds < 30) { + return __('less than a minute'); + } else if (seconds < MINUTES_LIMIT) { + return n__('1 minute', '%d minutes', seconds < ONE_MINUTE_LIMIT ? 1 : minutes); + } else if (seconds < HOURS_LIMIT) { + return n__('about 1 hour', 'about %d hours', seconds < ONE_HOUR_LIMIT ? 1 : hours); + } + return n__('1 day', '%d days', seconds < ONE_DAY_LIMIT ? 1 : days); +}; diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index af4ac024e4f..fee5d6d5e3a 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -136,7 +136,11 @@ export default { > <strong>{{ __('New! Suggest changes directly') }}</strong> <p class="mb-2"> - {{ __('Suggest code changes which are immediately applied. Try it out!') }} + {{ + __( + 'Suggest code changes which can be immediately applied in one click. Try it out!', + ) + }} </p> <gl-button variant="primary" size="sm" @click="handleSuggestDismissed"> {{ __('Got it') }} diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index a53f5d85949..9ae313db4c1 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -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 { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 09b335f9ba2..11d5f4cd374 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -288,7 +288,7 @@ } .issuable-sidebar { - width: calc(100% + 100px); + width: 100%; height: 100%; overflow-y: scroll; overflow-x: hidden; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 364fe3da71e..72657a64794 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -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); } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 5d6a4b7cd13..4f3d6fb0d44 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -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); diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 4471d5b64b2..80d1b7e7edb 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -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 diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index f0930705a3f..5324d59c155 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -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 diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 42a40e06240..4cecf6f3fe4 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -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 diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 77ac8bfe875..96041e02337 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -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 diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 8d38835fb3b..9da49b44cbe 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -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 diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index c01fb4740e5..7fd21c29c82 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -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 diff --git a/app/services/ci/prepare_build_service.rb b/app/services/ci/prepare_build_service.rb index 5d024c45e5f..3f87c711270 100644 --- a/app/services/ci/prepare_build_service.rb +++ b/app/services/ci/prepare_build_service.rb @@ -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) diff --git a/app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb b/app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb new file mode 100644 index 00000000000..a4bcca8e8b3 --- /dev/null +++ b/app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb @@ -0,0 +1,17 @@ +# 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 diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 02acf360afc..96d75b2fd33 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -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 diff --git a/app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb b/app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb new file mode 100644 index 00000000000..62233d19516 --- /dev/null +++ b/app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb @@ -0,0 +1,20 @@ +# 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 |