Commit ed73d4f2 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 2349eabc
......@@ -5,9 +5,11 @@ import initNotes from '~/init_notes';
import snippetEmbed from '~/snippet/snippet_embed';
document.addEventListener('DOMContentLoaded', () => {
if (!gon.features.snippetsVue) {
new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
initNotes();
new ZenMode(); // eslint-disable-line no-new
snippetEmbed();
}
});
# frozen_string_literal: true
module Clusters
class KnativeServingNamespaceFinder
attr_reader :cluster
def initialize(cluster)
@cluster = cluster
end
def execute
cluster.kubeclient&.get_namespace(Clusters::Kubernetes::KNATIVE_SERVING_NAMESPACE)
rescue Kubeclient::ResourceNotFoundError
nil
end
end
end
......@@ -9,9 +9,9 @@ module Clusters
end
def execute
cluster&.kubeclient&.get_cluster_role_bindings&.find do |resource|
resource.metadata.name == Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME
end
cluster.kubeclient&.get_cluster_role_binding(Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME)
rescue Kubeclient::ResourceNotFoundError
nil
end
end
end
......@@ -22,9 +22,28 @@ class DeploymentsFinder
private
# rubocop: disable CodeReuse/ActiveRecord
def init_collection
project.deployments
project
.deployments
.includes(
:user,
environment: [],
deployable: {
job_artifacts: [],
pipeline: {
project: {
route: [],
namespace: :route
}
},
project: {
namespace: :route
}
}
)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def sort(items)
......@@ -43,6 +62,9 @@ class DeploymentsFinder
order_by = ALLOWED_SORT_VALUES.include?(params[:order_by]) ? params[:order_by] : DEFAULT_SORT_VALUE
order_direction = ALLOWED_SORT_DIRECTIONS.include?(params[:sort]) ? params[:sort] : DEFAULT_SORT_DIRECTION
{ order_by => order_direction }
{ order_by => order_direction }.tap do |sort_values|
sort_values['id'] = 'desc' if sort_values['updated_at']
sort_values['id'] = sort_values.delete('created_at') if sort_values['created_at'] # Sorting by `id` produces the same result as sorting by `created_at`
end
end
end
......@@ -84,13 +84,17 @@ module Clusters
# ensures headers containing auth data are appended to original k8s client options
options = kube_client.rest_client.options.merge(headers: kube_client.headers)
Gitlab::PrometheusClient.new(proxy_url, options)
rescue Kubeclient::HttpError
rescue Kubeclient::HttpError, Errno::ECONNRESET, Errno::ECONNREFUSED
# If users have mistakenly set parameters or removed the depended clusters,
# `proxy_url` could raise an exception because gitlab can not communicate with the cluster.
# Since `PrometheusAdapter#can_query?` is eargely loaded on environement pages in gitlab,
# we need to silence the exceptions
end
def configured?
kube_client.present? && available?
end
private
def disable_prometheus_integration
......
......@@ -16,6 +16,14 @@ module PrometheusAdapter
raise NotImplementedError
end
# This is a light-weight check if a prometheus client is properly configured.
def configured?
raise NotImplemented
end
# This is a heavy-weight check if a prometheus is properly configured and accesible from GitLab.
# This actually sends a request to an external service and often it could take a long time,
# Please consider using `configured?` instead if the process is running on unicorn/puma threads.
def can_query?
prometheus_client.present?
end
......
......@@ -193,11 +193,11 @@ class Environment < ApplicationRecord
end
def has_metrics?
available? && prometheus_adapter&.can_query?
available? && prometheus_adapter&.configured?
end
def metrics
prometheus_adapter.query(:environment, self) if has_metrics?
prometheus_adapter.query(:environment, self) if has_metrics? && prometheus_adapter.can_query?
end
def prometheus_status
......
......@@ -374,9 +374,17 @@ class Project < ApplicationRecord
scope :pending_delete, -> { where(pending_delete: true) }
scope :without_deleted, -> { where(pending_delete: false) }
scope :with_storage_feature, ->(feature) { where('storage_version >= :version', version: HASHED_STORAGE_FEATURES[feature]) }
scope :without_storage_feature, ->(feature) { where('storage_version < :version OR storage_version IS NULL', version: HASHED_STORAGE_FEATURES[feature]) }
scope :with_unmigrated_storage, -> { where('storage_version < :version OR storage_version IS NULL', version: LATEST_STORAGE_VERSION) }
scope :with_storage_feature, ->(feature) do
where(arel_table[:storage_version].gteq(HASHED_STORAGE_FEATURES[feature]))
end
scope :without_storage_feature, ->(feature) do
where(arel_table[:storage_version].lt(HASHED_STORAGE_FEATURES[feature])
.or(arel_table[:storage_version].eq(nil)))
end
scope :with_unmigrated_storage, -> do
where(arel_table[:storage_version].lt(LATEST_STORAGE_VERSION)
.or(arel_table[:storage_version].eq(nil)))
end
# last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push
scope :sorted_by_activity, -> { reorder(Arel.sql("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC")) }
......
......@@ -95,6 +95,10 @@ class PrometheusService < MonitoringService
self_monitoring_project? && internal_prometheus_url?
end
def configured?
should_return_client?
end
private
def self_monitoring_project?
......
......@@ -23,6 +23,21 @@ class Upload < ApplicationRecord
after_destroy :delete_file!, if: -> { uploader_class <= FileUploader }
class << self
def inner_join_local_uploads_projects
upload_table = Upload.arel_table
project_table = Project.arel_table
join_statement = upload_table.project(upload_table[Arel.star])
.join(project_table)
.on(
upload_table[:model_type].eq('Project')
.and(upload_table[:model_id].eq(project_table[:id]))
.and(upload_table[:store].eq(ObjectStorage::Store::LOCAL))
)
joins(join_statement.join_sources)
end
##
# FastDestroyAll concerns
def begin_fast_destroy
......
......@@ -71,9 +71,9 @@ module Clusters
end
def knative_serving_namespace
kubeclient.core_client.get_namespaces.find do |namespace|
namespace.metadata.name == Clusters::Kubernetes::KNATIVE_SERVING_NAMESPACE
end
kubeclient.get_namespace(Clusters::Kubernetes::KNATIVE_SERVING_NAMESPACE)
rescue Kubeclient::ResourceNotFoundError
nil
end
def create_role_or_cluster_role_binding
......
......@@ -4,9 +4,12 @@
- breadcrumb_title @snippet.to_reference
- page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
= render 'shared/snippets/header'
- if Feature.enabled?(:snippets_vue)
#js-snippet-view{ 'data-qa-selector': 'snippet_view' }
- else
= render 'shared/snippets/header'
.personal-snippets
.personal-snippets
%article.file-holder.snippet-file-content
= render 'shared/snippets/blob'
......
......@@ -14,7 +14,7 @@ module HashedStorage
try_obtain_lease do
project = Project.without_deleted.find_by(id: project_id)
break unless project
break unless project && project.storage_upgradable?
old_disk_path ||= Storage::LegacyProject.new(project).disk_path
......
---
title: Added lightweight check when retrieving Prometheus metrics.
merge_request: 21099
author:
type: performance
---
title: 'Hashed Storage attachments migration: exclude files in object storage as they
are all hashed already'
merge_request: 20338
author:
type: changed
---
title: Optimize Deployments endpoint by preloading associations and make record ordering more consistent
merge_request: 20848
author:
type: changed
# frozen_string_literal: true
class ChangeUpdatedAtIndexAndAddIndexToIdOnDeployments < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
PROJECT_ID_INDEX_PARAMS = [[:project_id, :id], order: { id: :desc }]
OLD_UPDATED_AT_INDEX_PARAMS = [[:project_id, :updated_at]]
NEW_UPDATED_AT_INDEX_PARAMS = [[:project_id, :updated_at, :id], order: { updated_at: :desc, id: :desc }]
def up
add_concurrent_index :deployments, *NEW_UPDATED_AT_INDEX_PARAMS
remove_concurrent_index :deployments, *OLD_UPDATED_AT_INDEX_PARAMS
add_concurrent_index :deployments, *PROJECT_ID_INDEX_PARAMS
end
def down
add_concurrent_index :deployments, *OLD_UPDATED_AT_INDEX_PARAMS
remove_concurrent_index :deployments, *NEW_UPDATED_AT_INDEX_PARAMS
remove_concurrent_index :deployments, *PROJECT_ID_INDEX_PARAMS
end
end
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_12_02_031812) do
ActiveRecord::Schema.define(version: 2019_12_04_070713) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
......@@ -1339,10 +1339,11 @@ ActiveRecord::Schema.define(version: 2019_12_02_031812) do
t.index ["environment_id", "iid", "project_id"], name: "index_deployments_on_environment_id_and_iid_and_project_id"
t.index ["environment_id", "status"], name: "index_deployments_on_environment_id_and_status"
t.index ["id"], name: "partial_index_deployments_for_legacy_successful_deployments", where: "((finished_at IS NULL) AND (status = 2))"
t.index ["project_id", "id"], name: "index_deployments_on_project_id_and_id", order: { id: :desc }
t.index ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true
t.index ["project_id", "status", "created_at"], name: "index_deployments_on_project_id_and_status_and_created_at"
t.index ["project_id", "status"], name: "index_deployments_on_project_id_and_status"
t.index ["project_id", "updated_at"], name: "index_deployments_on_project_id_and_updated_at"
t.index ["project_id", "updated_at", "id"], name: "index_deployments_on_project_id_and_updated_at_and_id", order: { updated_at: :desc, id: :desc }
end
create_table "description_versions", force: :cascade do |t|
......
......@@ -8,7 +8,7 @@ module Gitlab
def unmet?
deployment_cluster.present? &&
deployment_cluster.managed? &&
(missing_namespace? || missing_knative_version_role_binding?)
(missing_namespace? || need_knative_version_role_binding?)
end
def complete!
......@@ -23,8 +23,8 @@ module Gitlab
kubernetes_namespace.nil? || kubernetes_namespace.service_account_token.blank?
end
def missing_knative_version_role_binding?
knative_version_role_binding.nil?
def need_knative_version_role_binding?
!knative_serving_namespace.nil? && knative_version_role_binding.nil?
end
def deployment_cluster
......@@ -35,6 +35,14 @@ module Gitlab
build.deployment.environment
end
def knative_serving_namespace
strong_memoize(:knative_serving_namespace) do
Clusters::KnativeServingNamespaceFinder.new(
deployment_cluster
).execute
end
end
def knative_version_role_binding
strong_memoize(:knative_version_role_binding) do
Clusters::KnativeVersionRoleBindingFinder.new(
......
......@@ -42,6 +42,7 @@ module Gitlab
# Initialize gon.features with any flags that should be
# made globally available to the frontend
push_frontend_feature_flag(:suppress_ajax_navigation_errors, default_enabled: true)
push_frontend_feature_flag(:snippets_vue, default_enabled: false)
end
# Exposes the state of a feature flag to the frontend code.
......
......@@ -47,23 +47,13 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def self.legacy_attachments_relation
Upload.joins(<<~SQL).where('projects.storage_version < :version OR projects.storage_version IS NULL', version: Project::HASHED_STORAGE_FEATURES[:attachments])
JOIN projects
ON (uploads.model_type='Project' AND uploads.model_id=projects.id)
SQL
Upload.inner_join_local_uploads_projects.merge(Project.without_storage_feature(:attachments))
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def self.hashed_attachments_relation
Upload.joins(<<~SQL).where('projects.storage_version >= :version', version: Project::HASHED_STORAGE_FEATURES[:attachments])
JOIN projects
ON (uploads.model_type='Project' AND uploads.model_id=projects.id)
SQL
Upload.inner_join_local_uploads_projects.merge(Project.with_storage_feature(:attachments))
end
# rubocop: enable CodeReuse/ActiveRecord
def self.relation_summary(relation_name, relation)
relation_count = relation.count
......
......@@ -146,7 +146,10 @@ module QA
end
def finished_loading?
has_no_css?('.fa-spinner', wait: Capybara.default_max_wait_time)
# The number of selectors should be able to be reduced after
# migration to the new spinner is complete.
# https://gitlab.com/groups/gitlab-org/-/epics/956
has_no_css?('.gl-spinner, .fa-spinner, .spinner', wait: Capybara.default_max_wait_time)
end
def finished_loading_block?
......
......@@ -31,7 +31,7 @@ module QA
end
def wait_for_search_to_complete
has_css?('.select2-active')
has_css?('.select2-active', wait: 1)
has_no_css?('.select2-active', wait: 30)
end
end
......
......@@ -6,7 +6,7 @@ module QA
module Common
# Click the Expand button present in the specified section
#
# @param [Symbol] and `element` name defined in a `view` block
# @param [Symbol] element_name `element` name defined in a `view` block
def expand_section(element_name)
within_element(element_name) do
# Because it is possible to click the button before the JS toggle code is bound
......@@ -14,6 +14,7 @@ module QA
click_button 'Expand' unless has_css?('button', text: 'Collapse', wait: 1)
has_content?('Collapse')
finished_loading?
end
yield if block_given?
......
......@@ -5,6 +5,10 @@ require 'spec_helper'
describe 'Internal Snippets', :js do
let(:internal_snippet) { create(:personal_snippet, :internal) }
before do
stub_feature_flags(snippets_vue: false)
end
describe 'normal user' do
before do
sign_in(create(:user))
......
......@@ -16,6 +16,7 @@ describe 'Comments on personal snippets', :js do
let!(:other_note) { create(:note_on_personal_snippet) }
before do
stub_feature_flags(snippets_vue: false)
sign_in user
visit snippet_path(snippet)
......
......@@ -6,6 +6,7 @@ describe 'Private Snippets', :js do
let(:user) { create(:user) }
before do
stub_feature_flags(snippets_vue: false)
sign_in(user)
end
......
......@@ -3,6 +3,10 @@
require 'spec_helper'
describe 'Public Snippets', :js do
before do
stub_feature_flags(snippets_vue: false)
end
it 'Unauthenticated user should see public snippets' do
public_snippet = create(:personal_snippet, :public)
......
......@@ -6,6 +6,10 @@ describe 'Snippet', :js do
let(:project) { create(:project, :repository) }
let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content) }
before do
stub_feature_flags(snippets_vue: false)
end
context 'Ruby file' do
let(:file_name) { 'popen.rb' }
let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data }
......
......@@ -7,6 +7,7 @@ describe 'User creates snippet', :js do
before do
stub_feature_flags(allow_possible_spam: false)
stub_feature_flags(snippets_vue: false)
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
Gitlab::CurrentSettings.update!(
......
......@@ -8,6 +8,7 @@ describe 'User creates snippet', :js do
let(:user) { create(:user) }
before do
stub_feature_flags(snippets_vue: false)
sign_in(user)
visit new_snippet_path
end
......
......@@ -10,6 +10,8 @@ describe 'User deletes snippet' do
before do
sign_in(user)
stub_feature_flags(snippets_vue: false)
visit snippet_path(snippet)
end
......
......@@ -12,6 +12,7 @@ describe 'User edits snippet', :js do
let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, author: user) }
before do
stub_feature_flags(snippets_vue: false)
sign_in(user)
visit edit_snippet_path(snippet)
......
......@@ -6,11 +6,38 @@ describe 'Snippets' do
context 'when the project has snippets' do
let(:project) { create(:project, :public) }
let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
before do
allow(Snippet).to receive(:default_per_page).and_return(1)
visit snippets_path(username: project.owner.username)
visit project_snippets_path(project)
end
it_behaves_like 'paginated snippets'
end
describe 'rendering engine' do
let_it_be(:snippet) { create(:personal_snippet, :public) }
let(:snippets_vue_feature_flag_enabled) { true }
before do
stub_feature_flags(snippets_vue: snippets_vue_feature_flag_enabled)
visit snippet_path(snippet)
end
it 'renders Vue application' do
expect(page).to have_selector('#js-snippet-view')
expect(page).not_to have_selector('.personal-snippets')
end
context 'when feature flag is disabled' do
let(:snippets_vue_feature_flag_enabled) { false }
it 'renders HAML application and not Vue' do
expect(page).not_to have_selector('#js-snippet-view')
expect(page).to have_selector('.personal-snippets')
end
end
end
end
......@@ -32,13 +32,13 @@ describe DeploymentsFinder do
let(:params) { { order_by: order_by, sort: sort } }
let!(:deployment_1) { create(:deployment, :success, project: project, iid: 11, ref: 'master', created_at: Time.now, updated_at: Time.now) }
let!(:deployment_1) { create(:deployment, :success, project: project, iid: 11, ref: 'master', created_at: 2.days.ago, updated_at: Time.now) }
let!(:deployment_2) { create(:deployment, :success, project: project, iid: 12, ref: 'feature', created_at: 1.day.ago, updated_at: 2.hours.ago) }
let!(:deployment_3) { create(:deployment, :success, project: project, iid: 8, ref: 'patch', created_at: 2.days.ago, updated_at: 1.hour.ago) }
let!(:deployment_3) { create(:deployment, :success, project: project, iid: 8, ref: 'patch', created_at: Time.now, updated_at: 1.hour.ago) }
where(:order_by, :sort, :ordered_deployments) do
'created_at' | 'asc' | [:deployment_3, :deployment_2, :deployment_1]
'created_at' | 'desc' | [:deployment_1, :deployment_2, :deployment_3]
'created_at' | 'asc' | [:deployment_1, :deployment_2, :deployment_3]
'created_at' | 'desc' | [:deployment_3, :deployment_2, :deployment_1]
'id' | 'asc' | [:deployment_1, :deployment_2, :deployment_3]
'id' | 'desc' | [:deployment_3, :deployment_2, :deployment_1]
'iid' | 'asc' | [:deployment_3, :deployment_1, :deployment_2]
......@@ -57,5 +57,41 @@ describe DeploymentsFinder do
end
end
end
describe 'transform `created_at` sorting to `id` sorting' do
let(:params) { { order_by: 'created_at', sort: 'asc' } }
it 'sorts by only one column' do
expect(subject.order_values.size).to eq(1)
end
it 'sorts by `id`' do
expect(subject.order_values.first.to_sql).to eq(Deployment.arel_table[:id].asc.to_sql)
end
end
describe 'tie-breaker for `updated_at` sorting' do
let(:params) { { order_by: 'updated_at', sort: 'asc' } }
it 'sorts by two columns' do
expect(subject.order_values.size).to eq(2)
end
it 'adds `id` sorting as the second order column' do
order_value = subject.order_values[1]
expect(order_value.to_sql).to eq(Deployment.arel_table[:id].desc.to_sql)
end
it 'uses the `id DESC` as tie-breaker when ordering' do
updated_at = Time.now
deployment_1 = create(:deployment, :success, project: project, updated_at: updated_at)
deployment_2 = create(:deployment, :success, project: project, updated_at: updated_at)
deployment_3 = create(:deployment, :success, project: project, updated_at: updated_at)
expect(subject).to eq([deployment_3, deployment_2, deployment_1])
end
end
end
end
......@@ -17,6 +17,7 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do
end
before do
stub_feature_flags(snippets_vue: false)
sign_in(admin)
allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo'])
end
......
......@@ -38,6 +38,21 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
.and_return(double(execute: kubernetes_namespace))
end
context 'and the knative-serving namespace is missing' do
before do
allow(Clusters::KnativeServingNamespaceFinder).to receive(:new)
.and_return(double(execute: false))
end
it { is_expected.to be_truthy }
end
context 'and the knative-serving namespace exists' do
before do
allow(Clusters::KnativeServingNamespaceFinder).to receive(:new)
.and_return(double(execute: true))
end
context 'and the knative version role binding is missing' do
before do
allow(Clusters::KnativeVersionRoleBindingFinder).to receive(:new)
......@@ -63,6 +78,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
end
end
end
end
context 'and no cluster to deploy to' do
let(:cluster) { nil }
......
......@@ -53,6 +53,16 @@ describe Clusters::Applications::Prometheus do
end
describe '#prometheus_client' do
shared_examples 'exception caught for prometheus client' do
before do
allow(kube_client).to receive(:proxy_url).and_raise(exception)
end
it 'returns nil' do
expect(subject.prometheus_client).to be_nil
end
end
context 'cluster is nil' do
it 'returns nil' do
expect(subject.cluster).to be_nil
......@@ -98,12 +108,18 @@ describe Clusters::Applications::Prometheus do
end
context 'when cluster is not reachable' do
before do
allow(kube_client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
it_behaves_like 'exception caught for prometheus client' do
let(:exception) { Kubeclient::HttpError.new(401, 'Unauthorized', nil) }
end
end
it 'returns nil' do
expect(subject.prometheus_client).to be_nil
context 'when there is a socket error while contacting cluster' do
it_behaves_like 'exception caught for prometheus client' do
let(:exception) { Errno::ECONNREFUSED }
end
it_behaves_like 'exception caught for prometheus client' do
let(:exception) { Errno::ECONNRESET }
end
end
end
......@@ -289,4 +305,28 @@ describe Clusters::Applications::Prometheus do
end
end
end
describe '#configured?' do
let(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
subject { prometheus.configured? }
context 'when a kubenetes client is present' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
it { is_expected.to be_truthy }
context 'when it is not availalble' do
let(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) }
it { is_expected.to be_falsey }
end
end
context 'when a kubenetes client is not present' do
let(:cluster) { create(:cluster) }
it { is_expected.to be_falsy }
end
end
end
......@@ -824,6 +824,14 @@ describe Environment, :use_clean_rails_memory_store_caching do
context 'and no deployments' do
it { is_expected.to be_truthy }
end
context 'and the prometheus adapter is not configured' do
before do
allow(environment.prometheus_adapter).to receive(:configured?).and_return(false)
end
it { is_expected.to be_falsy }
end
end
context 'without a monitoring service' do
......@@ -858,6 +866,14 @@ describe Environment, :use_clean_rails_memory_store_caching do
is_expected.to eq(:fake_metrics)
end
context 'and the prometheus client is not present' do
before do
allow(environment.prometheus_adapter).to receive(:promethus_client).and_return(nil)
end
it { is_expected.to be_nil }
end
end
context 'when the environment does not have metrics' do
......
......@@ -332,4 +332,40 @@ describe API::Deployments do
end
end
end
context 'prevent N + 1 queries' do
context 'when the endpoint returns multiple records' do
let(:project) { create(:project) }
def create_record
create(:deployment, :success, project: project)
end
def request_with_query_count
ActiveRecord::QueryRecorder.new { trigger_request }.count
end
def trigger_request
get api("/projects/#{project.id}/deployments?order_by=updated_at&sort=asc", user)
end
before do
create_record
end
it 'succeeds' do
trigger_request
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to eq(1)
end
it 'does not increase the query count' do
expect { create_record }.not_to change { request_with_query_count }
expect(json_response.size).to eq(2)
end
end
end
end
......@@ -22,7 +22,6 @@ describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
before do
stub_kubeclient_discover(api_url)
stub_kubeclient_get_namespaces(api_url)
stub_kubeclient_get_service_account_error(api_url, 'gitlab')
stub_kubeclient_create_service_account(api_url)
stub_kubeclient_get_secret_error(api_url, 'gitlab-token')
......@@ -31,6 +30,7 @@ describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
stub_kubeclient_get_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace)
stub_kubeclient_put_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace)
stub_kubeclient_get_namespace(api_url, namespace: namespace)
stub_kubeclient_get_namespace(api_url, namespace: Clusters::Kubernetes::KNATIVE_SERVING_NAMESPACE)
stub_kubeclient_get_service_account_error(api_url, "#{namespace}-service-account", namespace: namespace)
stub_kubeclient_create_service_account(api_url, namespace: namespace)
stub_kubeclient_create_secret(api_url, namespace: namespace)
......
......@@ -141,7 +141,7 @@ describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
before do
cluster.platform_kubernetes.rbac!
stub_kubeclient_get_namespaces(api_url)
stub_kubeclient_get_namespace(api_url, namespace: Clusters::Kubernetes::KNATIVE_SERVING_NAMESPACE)
stub_kubeclient_get_role_binding_error(api_url, role_binding_name, namespace: namespace)
stub_kubeclient_create_role_binding(api_url, namespace: namespace)
stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
......
......@@ -5,13 +5,13 @@ require 'spec_helper'
describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
describe '#perform' do
let(:project) { create(:project, :empty_repo, :legacy_storage) }
let(:lease_key) { "project_migrate_hashed_storage_worker:#{project.id}" }
let(:lease_timeout) { described_class::LEASE_TIMEOUT }
let(:migration_service) { ::Projects::HashedStorage::MigrationService }
let(:lease_timeout) { described_class::LEASE_TIMEOUT }
describe '#perform' do
it 'skips when project no longer exists' do
stub_exclusive_lease(lease_key(-1), 'uuid', timeout: lease_timeout)
expect(migration_service).not_to receive(:new)
subject.perform(-1)
......@@ -19,14 +19,41 @@ describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state d
it 'skips when project is pending delete' do
pending_delete_project = create(:project, :empty_repo, pending_delete: true)
stub_exclusive_lease(lease_key(pending_delete_project.id), 'uuid', timeout: lease_timeout)
expect(migration_service).not_to receive(:new)
subject.perform(pending_delete_project.id)
end
it 'delegates migration to service class when we have exclusive lease' do
stub_exclusive_lease(lease_key, 'uuid', timeout: lease_timeout)
it 'skips when project is already migrated' do
migrated_project = create(:project, :empty_repo)
stub_exclusive_lease(lease_key(migrated_project.id), 'uuid', timeout: lease_timeout)
expect(migration_service).not_to receive(:new)
subject.perform(migrated_project.id)
end
context 'with exclusive lease available' do
it 'delegates migration to service class' do
project = create(:project, :empty_repo, :legacy_storage)
stub_exclusive_lease(lease_key(project.id), 'uuid', timeout: lease_timeout)
service_spy = spy
allow(migration_service)
.to receive(:new).with(project, project.full_path, logger: subject.logger)
.and_return(service_spy)
subject.perform(project.id)
expect(service_spy).to have_received(:execute)
end
it 'delegates migration to service class with correct path in a partially migrated project' do
project = create(:project, :empty_repo, storage_version: 1)
stub_exclusive_lease(lease_key(project.id), 'uuid', timeout: lease_timeout)
service_spy = spy
......@@ -38,13 +65,21 @@ describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state d
expect(service_spy).to have_received(:execute)
end
end
context 'with exclusive lease taken' do
it 'skips when it cant acquire the exclusive lease' do
stub_exclusive_lease_taken(lease_key, timeout: lease_timeout)
project = create(:project, :empty_repo, :legacy_storage)
stub_exclusive_lease_taken(lease_key(project.id), timeout: lease_timeout)
expect(migration_service).not_to receive(:new)
subject.perform(project.id)
end
end
end
def lease_key(key)
"project_migrate_hashed_storage_worker:#{key}"
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment