Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-12-23 00:10:39 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-23 00:10:39 +0300
commit6c68583a42998b0bb15971785f372197bfc55cd7 (patch)
tree1299dcd6bda4cf7240f85a450fcfddb1f4053320
parente5f8220301d524167441f8fac5fd5fcf5fc31e1f (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/rails/shared.gitlab-ci.yml2
-rw-r--r--app/finders/ci/runner_managers_finder.rb31
-rw-r--r--app/finders/ci/runners_finder.rb15
-rw-r--r--app/models/ci/runner.rb37
-rw-r--r--app/models/ci/runner_manager.rb33
-rw-r--r--app/models/concerns/ci/has_runner_status.rb50
-rw-r--r--app/views/admin/dashboard/_stats_users_table.html.haml2
-rw-r--r--app/views/admin/dashboard/stats.html.haml2
-rw-r--r--app/views/search/results/_blob_data.html.haml6
-rw-r--r--app/views/search/results/_blob_highlight.html.haml2
-rw-r--r--app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml2
-rw-r--r--app/views/shared/_file_highlight.html.haml2
-rw-r--r--app/views/shared/_visibility_radios.html.haml2
-rw-r--r--app/views/shared/members/_member.html.haml2
-rw-r--r--app/views/shared/users/_user.html.haml2
-rw-r--r--app/views/shared/web_hooks/_index.html.haml2
-rw-r--r--app/views/shared/wikis/_sidebar.html.haml2
-rw-r--r--app/views/shared/wikis/git_error.html.haml4
-rw-r--r--app/views/shared/wikis/show.html.haml2
-rw-r--r--doc/development/pipelines/internals.md1
-rw-r--r--doc/development/pipelines/performance.md39
-rw-r--r--locale/gitlab.pot70
-rw-r--r--qa/qa/page/alert/auto_devops_alert.rb13
-rw-r--r--qa/qa/page/component/visibility_setting.rb8
-rw-r--r--qa/qa/page/search/results.rb16
-rw-r--r--qa/qa/page/user/show.rb4
-rw-r--r--qa/qa/specs/features/api/10_govern/group_access_token_spec.rb13
-rw-r--r--qa/qa/specs/features/api/10_govern/project_access_token_spec.rb3
-rw-r--r--qa/qa/support/helpers/project.rb15
-rw-r--r--spec/finders/ci/runner_managers_finder_spec.rb77
-rw-r--r--spec/finders/ci/runners_finder_spec.rb12
-rw-r--r--spec/models/ci/runner_manager_spec.rb70
-rw-r--r--spec/models/ci/runner_spec.rb129
-rw-r--r--spec/support/shared_examples/ci/runner_with_status_scope_shared_examples.rb29
35 files changed, 514 insertions, 186 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index bc07e5fea91..cdeadda887a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -155,6 +155,7 @@ variables:
GIT_STRATEGY: "clone"
GIT_SUBMODULE_STRATEGY: "none"
GET_SOURCES_ATTEMPTS: "3"
+ # CI_FETCH_REPO_GIT_STRATEGY: "none" is from artifacts. "clone" is from cloning
CI_FETCH_REPO_GIT_STRATEGY: "none"
DEBIAN_VERSION: "bullseye"
UBI_VERSION: "8.6"
diff --git a/.gitlab/ci/rails/shared.gitlab-ci.yml b/.gitlab/ci/rails/shared.gitlab-ci.yml
index aa5b7c33385..1ad1f60820a 100644
--- a/.gitlab/ci/rails/shared.gitlab-ci.yml
+++ b/.gitlab/ci/rails/shared.gitlab-ci.yml
@@ -67,7 +67,7 @@ include:
extends:
- .rails-job-base
- .base-artifacts
- - .repo-from-artifacts # Comment this to clone instead of using artifacts
+ - .repo-from-artifacts
stage: test
variables:
RUBY_GC_MALLOC_LIMIT: 67108864
diff --git a/app/finders/ci/runner_managers_finder.rb b/app/finders/ci/runner_managers_finder.rb
new file mode 100644
index 00000000000..f24be74bbeb
--- /dev/null
+++ b/app/finders/ci/runner_managers_finder.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Ci
+ class RunnerManagersFinder
+ def initialize(runner:, params:)
+ @runner = runner
+ @params = params
+ end
+
+ def execute
+ items = runner_managers
+
+ filter_by_status(items)
+ end
+
+ private
+
+ attr_reader :runner, :params
+
+ def runner_managers
+ ::Ci::RunnerManager.for_runner(runner)
+ end
+
+ def filter_by_status(items)
+ status = params[:status]
+ return items if status.blank?
+
+ items.with_status(status)
+ end
+ end
+end
diff --git a/app/finders/ci/runners_finder.rb b/app/finders/ci/runners_finder.rb
index 945d332ff47..19642f58104 100644
--- a/app/finders/ci/runners_finder.rb
+++ b/app/finders/ci/runners_finder.rb
@@ -88,7 +88,7 @@ module Ci
end
def filter_by_status!
- filter_by!(:status_status, Ci::Runner::AVAILABLE_STATUSES)
+ @runners = @runners.with_status(@params[:status_status]) if @params[:status_status].present?
end
def filter_by_upgrade_status!
@@ -104,7 +104,10 @@ module Ci
end
def filter_by_runner_type!
- filter_by!(:type_type, Ci::Runner::AVAILABLE_TYPES)
+ runner_type = @params[:type_type]
+ return if runner_type.blank?
+
+ @runners = @runners.with_runner_type(runner_type)
end
def filter_by_tag_list!
@@ -137,14 +140,6 @@ module Ci
def request_tag_list!
@runners = @runners.with_tags if !@params[:preload].present? || @params.dig(:preload, :tag_name)
end
-
- def filter_by!(scope_name, available_scopes)
- scope = @params[scope_name]
-
- if scope.present? && available_scopes.include?(scope)
- @runners = @runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend
- end
- end
end
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 9c30beeeb59..f3fbfe8193b 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -14,6 +14,7 @@ module Ci
include Presentable
include EachBatch
include Ci::HasRunnerExecutor
+ include Ci::HasRunnerStatus
extend ::Gitlab::Utils::Override
@@ -85,22 +86,22 @@ module Ci
before_save :ensure_token
- scope :active, -> (value = true) { where(active: value) }
+ scope :active, ->(value = true) { where(active: value) }
scope :paused, -> { active(false) }
- scope :online, -> { where(arel_table[:contacted_at].gt(online_contact_time_deadline)) }
scope :recent, -> do
timestamp = stale_deadline
where(arel_table[:created_at].gteq(timestamp).or(arel_table[:contacted_at].gteq(timestamp)))
end
scope :stale, -> do
- timestamp = stale_deadline
+ stale_timestamp = stale_deadline
+
+ created_before_stale_deadline = arel_table[:created_at].lteq(stale_timestamp)
+ contacted_before_stale_deadline = arel_table[:contacted_at].lteq(stale_timestamp)
+ never_contacted = arel_table[:contacted_at].eq(nil)
- where(arel_table[:created_at].lteq(timestamp))
- .where(arel_table[:contacted_at].eq(nil).or(arel_table[:contacted_at].lteq(timestamp)))
+ where(created_before_stale_deadline).where(never_contacted.or(contacted_before_stale_deadline))
end
- scope :offline, -> { where(arel_table[:contacted_at].lteq(online_contact_time_deadline)) }
- scope :never_contacted, -> { where(contacted_at: nil) }
scope :ordered, -> { order(id: :desc) }
scope :with_recent_runner_queue, -> { where(arel_table[:contacted_at].gt(recent_queue_deadline)) }
@@ -220,6 +221,11 @@ module Ci
validate :exactly_one_group, if: :group_type?
scope :with_version_prefix, ->(value) { joins(:runner_managers).merge(RunnerManager.with_version_prefix(value)) }
+ scope :with_runner_type, ->(runner_type) do
+ return all if AVAILABLE_TYPES.exclude?(runner_type.to_s)
+
+ where(runner_type: runner_type)
+ end
acts_as_taggable
@@ -348,23 +354,6 @@ module Ci
description
end
- def online?
- contacted_at && contacted_at > self.class.online_contact_time_deadline
- end
-
- def stale?
- return false unless created_at
-
- [created_at, contacted_at].compact.max <= self.class.stale_deadline
- end
-
- def status
- return :stale if stale?
- return :never_contacted unless contacted_at
-
- online? ? :online : :offline
- end
-
# DEPRECATED
# TODO Remove in v5 in favor of `status` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/344648
def deprecated_rest_status
diff --git a/app/models/ci/runner_manager.rb b/app/models/ci/runner_manager.rb
index e6576859827..71005f4341a 100644
--- a/app/models/ci/runner_manager.rb
+++ b/app/models/ci/runner_manager.rb
@@ -5,10 +5,13 @@ module Ci
include FromUnion
include RedisCacheable
include Ci::HasRunnerExecutor
+ include Ci::HasRunnerStatus
# For legacy reasons, the table name is ci_runner_machines in the database
self.table_name = 'ci_runner_machines'
+ AVAILABLE_STATUSES = %w[online offline never_contacted stale].freeze
+
# The `UPDATE_CONTACT_COLUMN_EVERY` defines how often the Runner Machine DB entry can be updated
UPDATE_CONTACT_COLUMN_EVERY = (40.minutes)..(55.minutes)
@@ -36,13 +39,16 @@ module Ci
STALE_TIMEOUT = 7.days
scope :stale, -> do
- created_some_time_ago = arel_table[:created_at].lteq(STALE_TIMEOUT.ago)
- contacted_some_time_ago = arel_table[:contacted_at].lteq(STALE_TIMEOUT.ago)
+ stale_timestamp = stale_deadline
+
+ created_before_stale_deadline = arel_table[:created_at].lteq(stale_timestamp)
+ contacted_before_stale_deadline = arel_table[:contacted_at].lteq(stale_timestamp)
from_union(
- where(contacted_at: nil),
- where(contacted_some_time_ago),
- remove_duplicates: false).where(created_some_time_ago)
+ never_contacted,
+ where(contacted_before_stale_deadline),
+ remove_duplicates: false
+ ).where(created_before_stale_deadline)
end
scope :for_runner, ->(runner_id) do
@@ -114,25 +120,8 @@ module Ci
end
end
- def status
- return :stale if stale?
- return :never_contacted unless contacted_at
-
- online? ? :online : :offline
- end
-
private
- def online?
- contacted_at && contacted_at > self.class.online_contact_time_deadline
- end
-
- def stale?
- return false unless created_at
-
- [created_at, contacted_at].compact.max <= self.class.stale_deadline
- end
-
def persist_cached_data?
# Use a random threshold to prevent beating DB updates.
contacted_at_max_age = Random.rand(UPDATE_CONTACT_COLUMN_EVERY)
diff --git a/app/models/concerns/ci/has_runner_status.rb b/app/models/concerns/ci/has_runner_status.rb
new file mode 100644
index 00000000000..f6fb9940b44
--- /dev/null
+++ b/app/models/concerns/ci/has_runner_status.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Ci
+ module HasRunnerStatus
+ extend ActiveSupport::Concern
+
+ included do
+ scope :offline, -> { where(arel_table[:contacted_at].lteq(online_contact_time_deadline)) }
+ scope :never_contacted, -> { where(contacted_at: nil) }
+ scope :online, -> { where(arel_table[:contacted_at].gt(online_contact_time_deadline)) }
+
+ scope :with_status, ->(status) do
+ return all if available_statuses.exclude?(status.to_s)
+
+ public_send(status) # rubocop:disable GitlabSecurity/PublicSend -- safe to call
+ end
+ end
+
+ class_methods do
+ def available_statuses
+ self::AVAILABLE_STATUSES
+ end
+
+ def online_contact_time_deadline
+ raise NotImplementedError
+ end
+
+ def stale_deadline
+ raise NotImplementedError
+ end
+ end
+
+ def status
+ return :stale if stale?
+ return :never_contacted unless contacted_at
+
+ online? ? :online : :offline
+ end
+
+ def online?
+ contacted_at && contacted_at > self.class.online_contact_time_deadline
+ end
+
+ def stale?
+ return false unless created_at
+
+ [created_at, contacted_at].compact.max <= self.class.stale_deadline
+ end
+ end
+end
diff --git a/app/views/admin/dashboard/_stats_users_table.html.haml b/app/views/admin/dashboard/_stats_users_table.html.haml
index 473384b8961..a23674c79db 100644
--- a/app/views/admin/dashboard/_stats_users_table.html.haml
+++ b/app/views/admin/dashboard/_stats_users_table.html.haml
@@ -1,4 +1,4 @@
-%table.table.gl-text-gray-500
+%table.table.gl-text-gray-500.gl-w-full
%tr
%td.gl-p-5!
= s_('AdminArea|Users without a Group and Project')
diff --git a/app/views/admin/dashboard/stats.html.haml b/app/views/admin/dashboard/stats.html.haml
index 059460ae5b2..8c096e2936f 100644
--- a/app/views/admin/dashboard/stats.html.haml
+++ b/app/views/admin/dashboard/stats.html.haml
@@ -8,7 +8,7 @@
%p.gl-font-weight-bold.gl-mt-8
= s_('AdminArea|Totals')
-%table.gl-table.gl-text-gray-500
+%table.gl-table.gl-text-gray-500.gl-w-full
= render_if_exists 'admin/dashboard/stats_active_users_row', users_statistics: @users_statistics
%tr.bg-gray-light.gl-text-gray-900
diff --git a/app/views/search/results/_blob_data.html.haml b/app/views/search/results/_blob_data.html.haml
index c42367f45c5..6e11b625490 100644
--- a/app/views/search/results/_blob_data.html.haml
+++ b/app/views/search/results/_blob_data.html.haml
@@ -1,6 +1,6 @@
-.js-blob-result.gl-mt-3.gl-mb-5{ data: { qa_selector: 'result_item_content' } }
+.js-blob-result.gl-mt-3.gl-mb-5{ data: { testid: 'result-item-content' } }
.file-holder.file-holder-top-border
- .js-file-title.file-title{ data: { qa_selector: 'file_title_content' } }
+ .js-file-title.file-title{ data: { testid: 'file-title-content' } }
= link_to blob_link, data: {track_action: 'click_text', track_label: 'blob_path', track_property: 'search_result'} do
= sprite_icon('document')
%strong
@@ -8,7 +8,7 @@
= copy_file_path_button(path)
- if blob.data
- if blob.data.size > 0
- .file-content.code.term{ data: { qa_selector: 'file_text_content' } }
+ .file-content.code.term{ data: { testid: 'file-text-content' } }
= render 'search/results/blob_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link, blame_link: blame_link, highlight_line: blob.highlight_line
- else
.file-content.code
diff --git a/app/views/search/results/_blob_highlight.html.haml b/app/views/search/results/_blob_highlight.html.haml
index 37ffabad717..2a10bc1989b 100644
--- a/app/views/search/results/_blob_highlight.html.haml
+++ b/app/views/search/results/_blob_highlight.html.haml
@@ -3,7 +3,7 @@
#search-blob-content.file-content.code.js-syntax-highlight{ class: 'gl-py-3!' }
- if blob.present?
- .blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight, qa_selector: 'file_content' } }
+ .blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight } }
- blob_highlight = blob.present.highlight_and_trim(trim_length: 1024, ellipsis_svg: sprite_icon('ellipsis_h', size: 12, css_class: "gl-text-gray-700"))
- blob_highlight.lines.each_with_index do |line, index|
- i = index + offset
diff --git a/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml b/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml
index 0ff2ee935cc..05a3fd2abc9 100644
--- a/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml
+++ b/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml
@@ -1,5 +1,5 @@
- if show_auto_devops_implicitly_enabled_banner?(project, current_user)
- = render Pajamas::AlertComponent.new(alert_options: { class: 'auto-devops-implicitly-enabled-banner', data: { qa_selector: 'auto_devops_banner_content' } },
+ = render Pajamas::AlertComponent.new(alert_options: { class: 'auto-devops-implicitly-enabled-banner' },
close_button_options: { class: 'hide-auto-devops-implicitly-enabled-banner',
data: { project_id: project.id }}) do |c|
- c.with_body do
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 9dfbad20726..3b53a4b4e89 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -16,7 +16,7 @@
%a.file-line-num.diff-line-num{ class: line_class, href: "#L#{i}", id: "L#{i}", 'data-line-number' => i }
= i
- .blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight, qa_selector: 'file_content' } }
+ .blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight } }
%pre.code.highlight
%code
= highlighted_blob
diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml
index 1bac75e0ff5..b4013cb5b80 100644
--- a/app/views/shared/_visibility_radios.html.haml
+++ b/app/views/shared/_visibility_radios.html.haml
@@ -6,7 +6,7 @@
= form.gitlab_ui_radio_component model_method, level,
"#{visibility_level_icon(level)} #{visibility_level_label(level)}".html_safe,
help_text: '<span class="option-description">%{visibility_level_description}</span><span class="option-disabled-reason"></span>'.html_safe % { visibility_level_description: visibility_level_description(level, form_model)},
- radio_options: { checked: (selected_level == level), data: { track_label: "blank_project", track_action: "activate_form_input", track_property: "#{model_method}_#{level}", track_value: "", qa_selector: "#{visibility_level_label(level).downcase}_radio" } },
+ radio_options: { checked: (selected_level == level), data: { track_label: "blank_project", track_action: "activate_form_input", track_property: "#{model_method}_#{level}", track_value: "" } },
label_options: { class: 'js-visibility-level-radio' }
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 47ab2d5f4be..42eb9e5ca19 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -9,7 +9,7 @@
-# Note this is just for individual members. For groups please see shared/members/_group
-%li.member.js-member.py-2.px-3.d-flex.flex-column{ class: [dom_class(member), ("flex-md-row" unless force_mobile_view)], id: dom_id(member), data: { qa_selector: 'member_row' } }
+%li.member.js-member.py-2.px-3.d-flex.flex-column{ class: [dom_class(member), ("flex-md-row" unless force_mobile_view)], id: dom_id(member) }
%span.list-item-name.mb-2.m-md-0
- if user
= render Pajamas::AvatarComponent.new(user, size: 32, class: 'gl-mr-3 flex-shrink-0 flex-grow-0')
diff --git a/app/views/shared/users/_user.html.haml b/app/views/shared/users/_user.html.haml
index e3c1ca4d9cf..4dc276a45c2 100644
--- a/app/views/shared/users/_user.html.haml
+++ b/app/views/shared/users/_user.html.haml
@@ -7,7 +7,7 @@
.user-info
.block-truncated
- = link_to user.name, user_path(user), class: 'user js-user-link', data: { user_id: user.id, qa_selector: 'user_link', qa_username: user.username }
+ = link_to user.name, user_path(user), class: 'user js-user-link', data: { user_id: user.id, testid: 'user-link', qa_username: user.username }
.block-truncated
%span.gl-text-gray-900= user.to_reference
diff --git a/app/views/shared/web_hooks/_index.html.haml b/app/views/shared/web_hooks/_index.html.haml
index ccd86937e4f..c4670a3ac73 100644
--- a/app/views/shared/web_hooks/_index.html.haml
+++ b/app/views/shared/web_hooks/_index.html.haml
@@ -11,7 +11,7 @@
- c.with_body do
= gitlab_ui_form_for @hook, as: :hook, url: url, html: { class: 'js-webhook-form gl-new-card-add-form gl-m-3 gl-display-none js-toggle-content' } do |f|
= render partial: partial, locals: { form: f, hook: @hook }
- = f.submit _('Add webhook'), pajamas_button: true, data: { qa_selector: "create_webhook_button" }
+ = f.submit _('Add webhook'), pajamas_button: true
= render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'js-webhook-edit-close gl-ml-2 js-toggle-button' }) do
= _('Cancel')
- if hooks.any?
diff --git a/app/views/shared/wikis/_sidebar.html.haml b/app/views/shared/wikis/_sidebar.html.haml
index cd752d31643..dd4ea9e72ab 100644
--- a/app/views/shared/wikis/_sidebar.html.haml
+++ b/app/views/shared/wikis/_sidebar.html.haml
@@ -15,7 +15,7 @@
- if can?(current_user, :create_wiki, @wiki)
- edit_sidebar_url = wiki_page_path(@wiki, Wiki::SIDEBAR, action: :edit)
- link_class = (editing && @page&.slug == Wiki::SIDEBAR) ? 'active' : ''
- = link_to edit_sidebar_url, class: link_class, data: { qa_selector: 'edit_sidebar_link' } do
+ = link_to edit_sidebar_url, class: link_class do
= sprite_icon('pencil', css_class: 'gl-mr-2')
%span= _("Edit sidebar")
diff --git a/app/views/shared/wikis/git_error.html.haml b/app/views/shared/wikis/git_error.html.haml
index 12eddb4a61e..aee359b35b3 100644
--- a/app/views/shared/wikis/git_error.html.haml
+++ b/app/views/shared/wikis/git_error.html.haml
@@ -8,7 +8,7 @@
.wiki-page-header.top-area.gl-flex-direction-column.gl-lg-flex-direction-row
.gl-mt-5.gl-mb-3
.gl-display-flex.gl-justify-content-space-between
- %h2.gl-mt-0.gl-mb-5{ data: { qa_selector: 'wiki_page_title', testid: 'wiki_page_title' } }= @page ? @page.human_title : _('Failed to retrieve page')
- .js-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki-page-content' } }
+ %h2.gl-mt-0.gl-mb-5{ data: { testid: 'wiki-page-title' } }= @page ? @page.human_title : _('Failed to retrieve page')
+ .js-wiki-page-content.md.gl-pt-2{ data: { testid: 'wiki-page-content' } }
= _('The page could not be displayed because it timed out.')
= html_escape(_('You can view the source or %{linkStart}%{cloneIcon} clone the repository%{linkEnd}')) % { linkStart: "<a href=\"#{git_access_url}\">".html_safe, linkEnd: '</a>'.html_safe, cloneIcon: sprite_icon('download', css_class: 'gl-mr-2').html_safe }
diff --git a/app/views/shared/wikis/show.html.haml b/app/views/shared/wikis/show.html.haml
index a896aa29f52..7bb99d7a877 100644
--- a/app/views/shared/wikis/show.html.haml
+++ b/app/views/shared/wikis/show.html.haml
@@ -35,6 +35,6 @@
.gl-display-flex.gl-justify-content-space-between
%h2.gl-mt-0.gl-mb-5{ data: { testid: 'wiki-page-title' } }= @page.human_title
- .js-async-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki-page-content', tracking_context: wiki_page_tracking_context(@page).to_json, get_wiki_content_url: wiki_page_render_api_endpoint(@page) } }
+ .js-async-wiki-page-content.md.gl-pt-2{ data: { testid: 'wiki-page-content', tracking_context: wiki_page_tracking_context(@page).to_json, get_wiki_content_url: wiki_page_render_api_endpoint(@page) } }
= render 'shared/wikis/sidebar'
diff --git a/doc/development/pipelines/internals.md b/doc/development/pipelines/internals.md
index 1fdb2014c1f..b3b4cbec02d 100644
--- a/doc/development/pipelines/internals.md
+++ b/doc/development/pipelines/internals.md
@@ -149,6 +149,7 @@ that are scoped to a single [configuration keyword](../../ci/yaml/index.md#job-k
|------------------|-------------|
| `.default-retry` | Allows a job to [retry](../../ci/yaml/index.md#retry) upon `unknown_failure`, `api_failure`, `runner_system_failure`, `job_execution_timeout`, or `stuck_or_timeout_failure`. |
| `.default-before_script` | Allows a job to use a default `before_script` definition suitable for Ruby/Rails tasks that may need a database running (for example, tests). |
+| `.repo-from-artifacts` | Allows a job to fetch the repository from artifacts in `clone-gitlab-repo` instead of cloning. This should reduce GitLab.com Gitaly load and also slightly improve the speed because downloading from artifacts is faster than cloning. Note that this should be avoided to be used with jobs having `needs: []` because otherwise it'll start later and we normally want all jobs to start as soon as possible. Use this only on jobs which has other dependencies so that we don't wait longer than just cloning. Note that this behavior can be controlled via `CI_FETCH_REPO_GIT_STRATEGY`. See [Fetch repository via artifacts instead of cloning/fetching from Gitaly](performance.md#fetch-repository-via-artifacts-instead-of-cloningfetching-from-gitaly) for more details. |
| `.setup-test-env-cache` | Allows a job to use a default `cache` definition suitable for setting up test environment for subsequent Ruby/Rails tasks. |
| `.ruby-cache` | Allows a job to use a default `cache` definition suitable for Ruby tasks. |
| `.static-analysis-cache` | Allows a job to use a default `cache` definition suitable for static analysis tasks. |
diff --git a/doc/development/pipelines/performance.md b/doc/development/pipelines/performance.md
index 0cd4f4270fe..d9019e6053b 100644
--- a/doc/development/pipelines/performance.md
+++ b/doc/development/pipelines/performance.md
@@ -29,6 +29,45 @@ This works well for the following reasons:
- We use [shallow clone](../../ci/pipelines/settings.md#limit-the-number-of-changes-fetched-during-clone) to avoid downloading the full Git
history for every job.
+### Fetch repository via artifacts instead of cloning/fetching from Gitaly
+
+Lately we see errors from Gitaly look like this: (see [the issue](https://gitlab.com/gitlab-org/gitlab/-/issues/435456))
+
+```plaintext
+fatal: remote error: GitLab is currently unable to handle this request due to load.
+```
+
+While GitLab.com uses [pack-objects cache](../../administration/gitaly/configure_gitaly.md#pack-objects-cache),
+sometimes the load is still too heavy for Gitaly to handle, and
+[thundering herds](https://gitlab.com/gitlab-org/gitlab/-/issues/423830) can
+also be a concern that we have a lot of jobs cloning the repository around
+the same time.
+
+To mitigate and reduce loads for Gitaly, we changed some jobs to fetch the
+repository from artifacts in a job instead of all cloning from Gitaly at once.
+
+For now this applies to most of the RSpec jobs, which has the most concurrent
+jobs in most pipelines. This also slightly improved the speed because fetching
+from the artifacts is also slightly faster than cloning, at the cost of saving
+more artifacts for each pipeline.
+
+Based on the numbers on 2023-12-20 at [Fetch repo from artifacts for RSpec jobs](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140330),
+the extra storage cost was about 280M for each pipeline, and we save 15 seconds
+for each RSpec jobs.
+
+We do not apply this to jobs having no other job dependencies because we don't
+want to delay any jobs from starting.
+
+This behavior can be controlled by variable `CI_FETCH_REPO_GIT_STRATEGY`:
+
+- Set to `none` means jobs using `.repo-from-artifacts` fetch repository from
+ artifacts in job `clone-gitlab-repo` rather than cloning.
+- Set to `clone` means jobs using `.repo-from-artifacts` clone repository
+ as usual. Job `clone-gitlab-repo` does not run in this case.
+
+To disable it, set `CI_FETCH_REPO_GIT_STRATEGY` to `clone`. To enable it,
+set `CI_FETCH_REPO_GIT_STRATEGY` to `none`.
+
## Caching strategy
1. All jobs must only pull caches by default.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 863ed3e90ce..b17af61a3bf 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2912,6 +2912,9 @@ msgstr ""
msgid "Add approvers"
msgstr ""
+msgid "Add branch target"
+msgstr ""
+
msgid "Add child epic to an epic"
msgstr ""
@@ -3050,9 +3053,6 @@ msgstr ""
msgid "Add tag"
msgstr ""
-msgid "Add target branch rule"
-msgstr ""
-
msgid "Add text to the sign-in page. Markdown enabled."
msgstr ""
@@ -6516,6 +6516,9 @@ msgstr ""
msgid "Are you sure you want to delete this SSH key?"
msgstr ""
+msgid "Are you sure you want to delete this branch target?"
+msgstr ""
+
msgid "Are you sure you want to delete this comment?"
msgstr ""
@@ -6531,9 +6534,6 @@ msgstr ""
msgid "Are you sure you want to delete this pipeline? Doing so will expire all pipeline caches and delete all related objects, such as builds, logs, artifacts, and triggers. This action cannot be undone."
msgstr ""
-msgid "Are you sure you want to delete this target branch rule?"
-msgstr ""
-
msgid "Are you sure you want to deploy this environment?"
msgstr ""
@@ -8576,6 +8576,9 @@ msgstr ""
msgid "Branch name"
msgstr ""
+msgid "Branch name pattern"
+msgstr ""
+
msgid "Branch name template"
msgstr ""
@@ -8585,6 +8588,18 @@ msgstr ""
msgid "Branch rules"
msgstr ""
+msgid "Branch target"
+msgstr ""
+
+msgid "Branch target created."
+msgstr ""
+
+msgid "Branch target deleted."
+msgstr ""
+
+msgid "Branch target does not exist"
+msgstr ""
+
msgid "BranchRules|%{linkStart}Wildcards%{linkEnd} such as *-stable or production/ are supported"
msgstr ""
@@ -14365,6 +14380,9 @@ msgstr ""
msgid "Create a merge request"
msgstr ""
+msgid "Create a merge request branch target."
+msgstr ""
+
msgid "Create a new %{codeStart}.gitlab-ci.yml%{codeEnd} file at the root of the repository to get started."
msgstr ""
@@ -14524,9 +14542,6 @@ msgstr ""
msgid "Create requirement"
msgstr ""
-msgid "Create rules for target branches in merge requests."
-msgstr ""
-
msgid "Create service account"
msgstr ""
@@ -20404,10 +20419,10 @@ msgstr ""
msgid "Failed to create wiki"
msgstr ""
-msgid "Failed to delete custom emoji. Please try again."
+msgid "Failed to delete branch target"
msgstr ""
-msgid "Failed to delete target branch rule"
+msgid "Failed to delete custom emoji. Please try again."
msgstr ""
msgid "Failed to deploy to"
@@ -30182,6 +30197,9 @@ msgstr ""
msgid "Merge request author cannot push to target project"
msgstr ""
+msgid "Merge request branch workflow"
+msgstr ""
+
msgid "Merge request change summary"
msgstr ""
@@ -39378,12 +39396,18 @@ msgstr ""
msgid "ProtectedEnvironment|Environment"
msgstr ""
+msgid "ProtectedEnvironment|Environment '%{environment_name}' is already protected"
+msgstr ""
+
msgid "ProtectedEnvironment|Environments protected upstream"
msgstr ""
msgid "ProtectedEnvironment|Failed to load details for this group."
msgstr ""
+msgid "ProtectedEnvironment|Failed to protect the environment."
+msgstr ""
+
msgid "ProtectedEnvironment|No environments in this project are protected."
msgstr ""
@@ -41364,9 +41388,6 @@ msgstr ""
msgid "Ruby"
msgstr ""
-msgid "Rule name"
-msgstr ""
-
msgid "Rule name is already taken."
msgstr ""
@@ -48223,21 +48244,6 @@ msgstr ""
msgid "Target branch"
msgstr ""
-msgid "Target branch rule"
-msgstr ""
-
-msgid "Target branch rule created."
-msgstr ""
-
-msgid "Target branch rule deleted."
-msgstr ""
-
-msgid "Target branch rule does not exist"
-msgstr ""
-
-msgid "Target branch rules"
-msgstr ""
-
msgid "Target branch: %{target_branch}"
msgstr ""
@@ -49263,10 +49269,10 @@ msgstr ""
msgid "The vulnerability is no longer detected. Verify the vulnerability has been remediated before changing its status."
msgstr ""
-msgid "There are currently no mirrored repositories."
+msgid "There are currently no merge request branch targets"
msgstr ""
-msgid "There are currently no target branch rules"
+msgid "There are currently no mirrored repositories."
msgstr ""
msgid "There are no GPG keys associated with this account."
@@ -56282,7 +56288,7 @@ msgstr ""
msgid "You have insufficient permissions to create organizations"
msgstr ""
-msgid "You have insufficient permissions to delete a target branch rule"
+msgid "You have insufficient permissions to delete a branch target"
msgstr ""
msgid "You have insufficient permissions to manage alerts for this project"
diff --git a/qa/qa/page/alert/auto_devops_alert.rb b/qa/qa/page/alert/auto_devops_alert.rb
deleted file mode 100644
index 26801c4996c..00000000000
--- a/qa/qa/page/alert/auto_devops_alert.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Alert
- class AutoDevopsAlert < Page::Base
- view 'app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml' do
- element :auto_devops_banner_content
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/component/visibility_setting.rb b/qa/qa/page/component/visibility_setting.rb
index f07b9c1c455..3ea3ad52f10 100644
--- a/qa/qa/page/component/visibility_setting.rb
+++ b/qa/qa/page/component/visibility_setting.rb
@@ -6,14 +6,6 @@ module QA
module VisibilitySetting
extend QA::Page::PageConcern
- def self.included(base)
- super
-
- base.view 'app/views/shared/_visibility_radios.html.haml' do
- element :visibility_radio, 'qa_selector: "#{visibility_level_label(level).downcase}_radio"' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
- end
- end
-
def set_visibility(visibility)
find('label', text: visibility.capitalize).click
end
diff --git a/qa/qa/page/search/results.rb b/qa/qa/page/search/results.rb
index 5d856875e0b..aa0a032be01 100644
--- a/qa/qa/page/search/results.rb
+++ b/qa/qa/page/search/results.rb
@@ -5,9 +5,9 @@ module QA
module Search
class Results < QA::Page::Base
view 'app/views/search/results/_blob_data.html.haml' do
- element :result_item_content
- element :file_title_content
- element :file_text_content
+ element 'result-item-content'
+ element 'file-title-content'
+ element 'file-text-content'
end
view 'app/views/shared/projects/_project.html.haml' do
@@ -23,19 +23,19 @@ module QA
end
def has_project_in_search_result?(project_name)
- has_element?(:result_item_content, text: project_name)
+ has_element?('result-item-content', text: project_name)
end
def has_file_in_project?(file_name, project_name)
- within_element(:result_item_content, text: project_name) do
- has_element?(:file_title_content, text: file_name)
+ within_element('result-item-content', text: project_name) do
+ has_element?('file-title-content', text: file_name)
end
end
def has_file_in_project_with_content?(file_text, file_path)
- within_element(:result_item_content,
+ within_element('result-item-content',
text: file_path) do
- has_element?(:file_text_content, text: file_text)
+ has_element?('file-text-content', text: file_text)
end
end
diff --git a/qa/qa/page/user/show.rb b/qa/qa/page/user/show.rb
index aba79256340..2a73700f991 100644
--- a/qa/qa/page/user/show.rb
+++ b/qa/qa/page/user/show.rb
@@ -9,7 +9,7 @@ module QA
end
view 'app/views/shared/users/_user.html.haml' do
- element :user_link
+ element 'user-link'
end
view 'app/views/users/_overview.html.haml' do
@@ -25,7 +25,7 @@ module QA
end
def click_user_link(username)
- click_element(:user_link, username: username)
+ click_element('user-link', username: username)
end
def has_activity?(activity)
diff --git a/qa/qa/specs/features/api/10_govern/group_access_token_spec.rb b/qa/qa/specs/features/api/10_govern/group_access_token_spec.rb
index 3e190608268..5cd78a91d14 100644
--- a/qa/qa/specs/features/api/10_govern/group_access_token_spec.rb
+++ b/qa/qa/specs/features/api/10_govern/group_access_token_spec.rb
@@ -6,13 +6,20 @@ module QA
include QA::Support::Helpers::Project
let(:group_access_token) { create(:group_access_token) }
- let(:api_client) { Runtime::API::Client.new(:gitlab, personal_access_token: group_access_token.token) }
+
+ let(:api_client_with_group_token) do
+ Runtime::API::Client.new(:gitlab, personal_access_token: group_access_token.token)
+ end
+
let(:project) do
create(:project, name: 'project-for-group-access-token', group: group_access_token.group)
end
before do
wait_until_project_is_ready(project)
+ # Associating a group access token to a project requires a job to be processed in sidekiq
+ # We need to be sure that this has happened or else we may get flaky test failures
+ wait_until_token_associated_to_project(project, api_client_with_group_token)
end
it(
@@ -21,7 +28,7 @@ module QA
) do
expect do
create(:file,
- api_client: api_client,
+ api_client: api_client_with_group_token,
project: project,
branch: "new_branch_#{SecureRandom.hex(8)}")
rescue StandardError => e
@@ -36,7 +43,7 @@ module QA
) do
expect do
create(:commit,
- api_client: api_client,
+ api_client: api_client_with_group_token,
project: project,
branch: "new_branch_#{SecureRandom.hex(8)}",
start_branch: project.default_branch,
diff --git a/qa/qa/specs/features/api/10_govern/project_access_token_spec.rb b/qa/qa/specs/features/api/10_govern/project_access_token_spec.rb
index 8f4a04ef389..e76160a8bb5 100644
--- a/qa/qa/specs/features/api/10_govern/project_access_token_spec.rb
+++ b/qa/qa/specs/features/api/10_govern/project_access_token_spec.rb
@@ -11,6 +11,9 @@ module QA
before do
wait_until_project_is_ready(project)
+ # Associating an access token to a project requires a job to be processed in sidekiq
+ # We need to be sure that this has happened or else we may get flaky test failures
+ wait_until_token_associated_to_project(project, user_api_client)
end
context 'for the same project' do
diff --git a/qa/qa/support/helpers/project.rb b/qa/qa/support/helpers/project.rb
index 8909d8338f4..85165f13fe1 100644
--- a/qa/qa/support/helpers/project.rb
+++ b/qa/qa/support/helpers/project.rb
@@ -6,12 +6,23 @@ module QA
module Project
def wait_until_project_is_ready(project)
# Repository can take a short time to become ready after project is created
- Support::Retrier.retry_on_exception(sleep_interval: 5) do
+ Support::Retrier.retry_on_exception(sleep_interval: 1, max_attempts: 60) do
create(:commit, project: project, commit_message: 'Add new file', actions: [
- { action: 'create', file_path: 'new_file', content: '# This is a new file' }
+ { action: 'create', file_path: SecureRandom.hex(4), content: '# This is a new file' }
])
end
end
+
+ def wait_until_token_associated_to_project(project, api_client)
+ Support::Retrier.retry_on_exception(sleep_interval: 1, max_attempts: 60) do
+ create(
+ :commit,
+ project: project,
+ actions: [{ action: 'create', file_path: SecureRandom.hex(4) }],
+ api_client: api_client
+ )
+ end
+ end
end
end
end
diff --git a/spec/finders/ci/runner_managers_finder_spec.rb b/spec/finders/ci/runner_managers_finder_spec.rb
new file mode 100644
index 00000000000..c62c05d415e
--- /dev/null
+++ b/spec/finders/ci/runner_managers_finder_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::RunnerManagersFinder, '#execute', feature_category: :fleet_visibility do
+ subject(:runner_managers) { described_class.new(runner: runner, params: params).execute }
+
+ let_it_be(:runner) { create(:ci_runner) }
+
+ describe 'filter by status' do
+ before_all do
+ freeze_time
+ end
+
+ after :all do
+ unfreeze_time
+ end
+
+ let_it_be(:offline_runner_manager) { create(:ci_runner_machine, runner: runner, contacted_at: 2.hours.ago) }
+ let_it_be(:online_runner_manager) { create(:ci_runner_machine, runner: runner, contacted_at: 1.second.ago) }
+ let_it_be(:never_contacted_runner_manager) { create(:ci_runner_machine, runner: runner, contacted_at: nil) }
+ let_it_be(:stale_runner_manager) do
+ create(
+ :ci_runner_machine,
+ runner: runner,
+ created_at: Ci::RunnerManager.stale_deadline - 1.second,
+ contacted_at: nil
+ )
+ end
+
+ let(:params) { { status: status } }
+
+ context 'for offline' do
+ let(:status) { :offline }
+
+ it { is_expected.to contain_exactly(offline_runner_manager) }
+ end
+
+ context 'for online' do
+ let(:status) { :online }
+
+ it { is_expected.to contain_exactly(online_runner_manager) }
+ end
+
+ context 'for stale' do
+ let(:status) { :stale }
+
+ it { is_expected.to contain_exactly(stale_runner_manager) }
+ end
+
+ context 'for never_contacted' do
+ let(:status) { :never_contacted }
+
+ it { is_expected.to contain_exactly(never_contacted_runner_manager, stale_runner_manager) }
+ end
+
+ context 'for invalid status' do
+ let(:status) { :invalid_status }
+
+ it 'returns all runner managers' do
+ expect(runner_managers).to contain_exactly(
+ offline_runner_manager, online_runner_manager, never_contacted_runner_manager, stale_runner_manager
+ )
+ end
+ end
+ end
+
+ context 'without any filters' do
+ let(:params) { {} }
+
+ let_it_be(:runner_manager) { create(:ci_runner_machine, runner: runner) }
+
+ it 'returns all runner managers' do
+ expect(runner_managers).to contain_exactly(runner_manager)
+ end
+ end
+end
diff --git a/spec/finders/ci/runners_finder_spec.rb b/spec/finders/ci/runners_finder_spec.rb
index fbe44244dba..98e66ca6f31 100644
--- a/spec/finders/ci/runners_finder_spec.rb
+++ b/spec/finders/ci/runners_finder_spec.rb
@@ -112,7 +112,7 @@ RSpec.describe Ci::RunnersFinder, feature_category: :fleet_visibility do
context 'by status' do
Ci::Runner::AVAILABLE_STATUSES.each do |status|
it "calls the corresponding :#{status} scope on Ci::Runner" do
- expect(Ci::Runner).to receive(status.to_sym).and_call_original
+ expect(Ci::Runner).to receive(:with_status).with(status).and_call_original
described_class.new(current_user: admin, params: { status_status: status }).execute
end
@@ -134,10 +134,14 @@ RSpec.describe Ci::RunnersFinder, feature_category: :fleet_visibility do
end
context 'by runner type' do
- it 'calls the corresponding scope on Ci::Runner' do
- expect(Ci::Runner).to receive(:project_type).and_call_original
+ Ci::Runner.runner_types.each_key do |runner_type|
+ context "when runner type is #{runner_type}" do
+ it "calls the corresponding scope on Ci::Runner" do
+ expect(Ci::Runner).to receive(:with_runner_type).with(runner_type).and_call_original
- described_class.new(current_user: admin, params: { type_type: 'project_type' }).execute
+ described_class.new(current_user: admin, params: { type_type: runner_type }).execute
+ end
+ end
end
end
diff --git a/spec/models/ci/runner_manager_spec.rb b/spec/models/ci/runner_manager_spec.rb
index 02a72afe0c6..ca762f7d618 100644
--- a/spec/models/ci/runner_manager_spec.rb
+++ b/spec/models/ci/runner_manager_spec.rb
@@ -41,20 +41,70 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
end
end
- describe '.stale', :freeze_time do
- subject { described_class.stale.ids }
+ describe 'status scopes' do
+ let_it_be(:runner) { create(:ci_runner, :instance) }
- let!(:runner_manager1) { create(:ci_runner_machine, :stale) }
- let!(:runner_manager2) { create(:ci_runner_machine, :stale, contacted_at: nil) }
- let!(:runner_manager3) { create(:ci_runner_machine, created_at: 6.months.ago, contacted_at: Time.current) }
- let!(:runner_manager4) { create(:ci_runner_machine, created_at: 5.days.ago) }
- let!(:runner_manager5) do
- create(:ci_runner_machine, created_at: (7.days - 1.second).ago, contacted_at: (7.days - 1.second).ago)
+ let_it_be(:offline_runner_manager) { create(:ci_runner_machine, runner: runner, contacted_at: 2.hours.ago) }
+ let_it_be(:online_runner_manager) { create(:ci_runner_machine, runner: runner, contacted_at: 1.second.ago) }
+ let_it_be(:never_contacted_runner_manager) { create(:ci_runner_machine, runner: runner, contacted_at: nil) }
+
+ describe '.online' do
+ subject(:runner_managers) { described_class.online }
+
+ it 'returns online runner managers' do
+ expect(runner_managers).to contain_exactly(online_runner_manager)
+ end
end
- it 'returns stale runner managers' do
- is_expected.to match_array([runner_manager1.id, runner_manager2.id])
+ describe '.offline' do
+ subject(:runner_managers) { described_class.offline }
+
+ it 'returns offline runner managers' do
+ expect(runner_managers).to contain_exactly(offline_runner_manager)
+ end
end
+
+ describe '.never_contacted' do
+ subject(:runner_managers) { described_class.never_contacted }
+
+ it 'returns never contacted runner managers' do
+ expect(runner_managers).to contain_exactly(never_contacted_runner_manager)
+ end
+ end
+
+ describe '.stale', :freeze_time do
+ subject { described_class.stale }
+
+ let!(:stale_runner_manager1) do
+ create(
+ :ci_runner_machine,
+ runner: runner,
+ created_at: described_class.stale_deadline - 1.second,
+ contacted_at: nil
+ )
+ end
+
+ let!(:stale_runner_manager2) do
+ create(
+ :ci_runner_machine,
+ runner: runner,
+ created_at: 8.days.ago,
+ contacted_at: described_class.stale_deadline - 1.second
+ )
+ end
+
+ it 'returns stale runner managers' do
+ is_expected.to contain_exactly(stale_runner_manager1, stale_runner_manager2)
+ end
+ end
+
+ include_examples 'runner with status scope'
+ end
+
+ describe '.available_statuses' do
+ subject { described_class.available_statuses }
+
+ it { is_expected.to eq(%w[online offline never_contacted stale]) }
end
describe '.online_contact_time_deadline', :freeze_time do
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index bb9ac084ed6..1a989e757b6 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -557,19 +557,6 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
end
end
- describe '.stale', :freeze_time do
- subject { described_class.stale }
-
- let!(:runner1) { create(:ci_runner, :instance, created_at: 4.months.ago, contacted_at: 3.months.ago + 1.second) }
- let!(:runner2) { create(:ci_runner, :instance, created_at: 4.months.ago, contacted_at: 3.months.ago) }
- let!(:runner3) { create(:ci_runner, :instance, created_at: 3.months.ago, contacted_at: nil) }
- let!(:runner4) { create(:ci_runner, :instance, created_at: 2.months.ago, contacted_at: nil) }
-
- it 'returns stale runners' do
- is_expected.to match_array([runner2, runner3])
- end
- end
-
describe '#stale?', :clean_gitlab_redis_cache, :freeze_time do
let(:runner) { build(:ci_runner, :instance) }
@@ -632,15 +619,6 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
end
end
- describe '.online', :freeze_time do
- subject { described_class.online }
-
- let!(:runner1) { create(:ci_runner, :instance, contacted_at: 2.hours.ago) }
- let!(:runner2) { create(:ci_runner, :instance, contacted_at: 1.second.ago) }
-
- it { is_expected.to match_array([runner2]) }
- end
-
describe '#online?', :clean_gitlab_redis_cache, :freeze_time do
let(:runner) { build(:ci_runner, :instance) }
@@ -715,15 +693,6 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
end
end
- describe '.offline' do
- subject { described_class.offline }
-
- let!(:runner1) { create(:ci_runner, :instance, contacted_at: 2.hours.ago) }
- let!(:runner2) { create(:ci_runner, :instance, contacted_at: 1.second.ago) }
-
- it { is_expected.to eq([runner1]) }
- end
-
describe '.with_running_builds' do
subject { described_class.with_running_builds }
@@ -2126,4 +2095,102 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
end
end
end
+
+ describe 'status scopes' do
+ let_it_be(:online_runner) { create(:ci_runner, :instance, contacted_at: 1.second.ago) }
+ let_it_be(:offline_runner) { create(:ci_runner, :instance, contacted_at: 2.hours.ago) }
+ let_it_be(:never_contacted_runner) { create(:ci_runner, :instance, contacted_at: nil) }
+
+ describe '.online' do
+ subject(:runners) { described_class.online }
+
+ it 'returns online runners' do
+ expect(runners).to contain_exactly(online_runner)
+ end
+ end
+
+ describe '.offline' do
+ subject(:runners) { described_class.offline }
+
+ it 'returns offline runners' do
+ expect(runners).to contain_exactly(offline_runner)
+ end
+ end
+
+ describe '.never_contacted' do
+ subject(:runners) { described_class.never_contacted }
+
+ it 'returns never contacted runners' do
+ expect(runners).to contain_exactly(never_contacted_runner)
+ end
+ end
+
+ describe '.stale', :freeze_time do
+ subject { described_class.stale }
+
+ let!(:stale_runner1) do
+ create(:ci_runner, :instance, created_at: described_class.stale_deadline - 1.second, contacted_at: nil)
+ end
+
+ let!(:stale_runner2) do
+ create(:ci_runner, :instance, created_at: 4.months.ago, contacted_at: described_class.stale_deadline - 1.second)
+ end
+
+ it 'returns stale runners' do
+ is_expected.to contain_exactly(stale_runner1, stale_runner2)
+ end
+ end
+
+ include_examples 'runner with status scope'
+ end
+
+ describe '.available_statuses' do
+ subject { described_class.available_statuses }
+
+ it { is_expected.to eq(%w[active paused online offline never_contacted stale]) }
+ end
+
+ describe '.online_contact_time_deadline', :freeze_time do
+ subject { described_class.online_contact_time_deadline }
+
+ it { is_expected.to eq(2.hours.ago) }
+ end
+
+ describe '.stale_deadline', :freeze_time do
+ subject { described_class.stale_deadline }
+
+ it { is_expected.to eq(3.months.ago) }
+ end
+
+ describe '.with_runner_type' do
+ subject { described_class.with_runner_type(runner_type) }
+
+ let_it_be(:instance_runner) { create(:ci_runner, :instance) }
+ let_it_be(:group_runner) { create(:ci_runner, :group) }
+ let_it_be(:project_runner) { create(:ci_runner, :project) }
+
+ context 'with instance_type' do
+ let(:runner_type) { 'instance_type' }
+
+ it { is_expected.to contain_exactly(instance_runner) }
+ end
+
+ context 'with group_type' do
+ let(:runner_type) { 'group_type' }
+
+ it { is_expected.to contain_exactly(group_runner) }
+ end
+
+ context 'with project_type' do
+ let(:runner_type) { 'project_type' }
+
+ it { is_expected.to contain_exactly(project_runner) }
+ end
+
+ context 'with invalid runner type' do
+ let(:runner_type) { 'invalid runner type' }
+
+ it { is_expected.to contain_exactly(instance_runner, group_runner, project_runner) }
+ end
+ end
end
diff --git a/spec/support/shared_examples/ci/runner_with_status_scope_shared_examples.rb b/spec/support/shared_examples/ci/runner_with_status_scope_shared_examples.rb
new file mode 100644
index 00000000000..510721d66b2
--- /dev/null
+++ b/spec/support/shared_examples/ci/runner_with_status_scope_shared_examples.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'runner with status scope' do
+ describe '.with_status' do
+ subject(:scope) { described_class.with_status(status) }
+
+ described_class::AVAILABLE_STATUSES.each do |status|
+ context "with #{status} status" do
+ let(:status) { status }
+
+ it "calls corresponding :#{status} scope" do
+ expect(described_class).to receive(status.to_sym).and_call_original
+
+ scope
+ end
+ end
+ end
+
+ context 'with invalid status' do
+ let(:status) { :invalid_status }
+
+ it 'returns all records' do
+ expect(described_class).to receive(:all).at_least(:once).and_call_original
+
+ expect { scope }.not_to raise_error
+ end
+ end
+ end
+end