Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e5f8220301
commit
6c68583a42
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
where(arel_table[:created_at].lteq(timestamp))
|
||||
.where(arel_table[:contacted_at].eq(nil).or(arel_table[:contacted_at].lteq(timestamp)))
|
||||
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(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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue