Commit 1ef39a79 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch '5447-geo-use-git-clone-for-first-sync-instead-of-git-fetch' into 'master'

Geo: Use `git clone` for first sync instead of `git fetch`

See merge request gitlab-org/gitlab!77143
parents ebc6ad98 81d2c45c
......@@ -481,7 +481,7 @@ gem 'ssh_data', '~> 1.2'
gem 'spamcheck', '~> 0.1.0'
# Gitaly GRPC protocol definitions
gem 'gitaly', '~> 14.9.0.pre.rc4'
gem 'gitaly', '~> 14.9.0-rc5'
# KAS GRPC protocol definitions
gem 'kas-grpc', '~> 0.0.2'
......
......@@ -455,7 +455,7 @@ GEM
rails (>= 3.2.0)
git (1.7.0)
rchardet (~> 1.8)
gitaly (14.9.0.pre.rc4)
gitaly (14.9.0.pre.rc5)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab (4.16.1)
......@@ -534,7 +534,7 @@ GEM
signet (~> 0.12)
google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
google-protobuf (3.19.1)
google-protobuf (3.19.4)
googleapis-common-protos-types (1.3.0)
google-protobuf (~> 3.14)
googleauth (0.14.0)
......@@ -1492,7 +1492,7 @@ DEPENDENCIES
gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly (~> 14.9.0.pre.rc4)
gitaly (~> 14.9.0.pre.rc5)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 3.0)
......
......@@ -941,6 +941,10 @@ class Repository
end
end
def clone_as_mirror(url, http_authorization_header: "")
import_repository(url, http_authorization_header: http_authorization_header, mirror: true)
end
def fetch_as_mirror(url, forced: false, refmap: :all_refs, prune: true, http_authorization_header: "")
fetch_remote(url, refmap: refmap, forced: forced, prune: prune, http_authorization_header: http_authorization_header)
end
......
---
name: geo_use_clone_on_first_sync
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77143
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/357462
milestone: '14.10'
type: development
group: group::geo
default_enabled: false
......@@ -78,14 +78,20 @@ module Geo
redownload_repository
@new_repository = true
elsif repository.exists?
fetch_geo_mirror(repository)
fetch_geo_mirror
else
ensure_repository
# Because we ensure a repository exists by this point, we need to
# mark it as new, even if fetching the mirror fails, we should run
# housekeeping to enable object deduplication to run
@new_repository = true
fetch_geo_mirror(repository)
if Feature.enabled?('geo_use_clone_on_first_sync', default_enabled: :yaml)
clone_geo_mirror
@new_repository = true
else
ensure_repository
# Because we ensure a repository exists by this point, we need to
# mark it as new, even if fetching the mirror fails, we should run
# housekeeping to enable object deduplication to run
@new_repository = true
fetch_geo_mirror
end
end
update_root_ref
......@@ -102,9 +108,13 @@ module Geo
log_info("Attempting to fetch repository via git")
# `git fetch` needs an empty bare repository to fetch into
temp_repo.create_repository
fetch_geo_mirror(temp_repo)
if Feature.enabled?('geo_use_clone_on_first_sync', default_enabled: :yaml)
clone_geo_mirror(target_repository: temp_repo)
temp_repo.create_repository unless temp_repo.exists?
else
temp_repo.create_repository
fetch_geo_mirror(target_repository: temp_repo)
end
set_temp_repository_as_main
ensure
......@@ -115,9 +125,19 @@ module Geo
::Gitlab::Geo.current_node
end
def fetch_geo_mirror(repository)
# Updates an existing repository using JWT authentication mechanism
#
# @param [Repository] target_repository specify a different temporary repository (default: current repository)
def fetch_geo_mirror(target_repository: repository)
# Fetch the repository, using a JWT header for authentication
repository.fetch_as_mirror(replicator.remote_url, forced: true, http_authorization_header: replicator.jwt_authentication_header)
target_repository.fetch_as_mirror(replicator.remote_url, forced: true, http_authorization_header: replicator.jwt_authentication_header)
end
# Clone a Geo repository using JWT authentication mechanism
#
# @param [Repository] target_repository specify a different temporary repository (default: current repository)
def clone_geo_mirror(target_repository: repository)
target_repository.clone_as_mirror(replicator.remote_url, http_authorization_header: replicator.jwt_authentication_header)
end
# Use snapshotting for redownloads *only* when enabled.
......
......@@ -41,6 +41,10 @@ module Geo
LEASE_TIMEOUT
end
def repository
raise NotImplementedError, 'Define a reference to the repository'
end
private
def fetch_repository
......@@ -57,14 +61,21 @@ module Geo
redownload_repository
@new_repository = true
elsif repository.exists?
fetch_geo_mirror(repository)
fetch_geo_mirror
else
ensure_repository
# Because we ensure a repository exists by this point, we need to
# mark it as new, even if fetching the mirror fails, we should run
# housekeeping to enable object deduplication to run
@new_repository = true
fetch_geo_mirror(repository)
if Feature.enabled?('geo_use_clone_on_first_sync', default_enabled: :yaml)
clone_geo_mirror
@new_repository = true
else
ensure_repository
# Because we ensure a repository exists by this point, we need to
# mark it as new, even if fetching the mirror fails, we should run
# housekeeping to enable object deduplication to run
@new_repository = true
fetch_geo_mirror
end
end
update_root_ref
......@@ -85,9 +96,13 @@ module Geo
log_info("Attempting to fetch repository via git")
# `git fetch` needs an empty bare repository to fetch into
temp_repo.create_repository
fetch_geo_mirror(temp_repo)
if Feature.enabled?('geo_use_clone_on_first_sync', default_enabled: :yaml)
clone_geo_mirror(target_repository: temp_repo)
else
# `git fetch` needs an empty bare repository to fetch into
temp_repo.create_repository
fetch_geo_mirror(target_repository: temp_repo)
end
set_temp_repository_as_main
ensure
......@@ -98,9 +113,22 @@ module Geo
::Gitlab::Geo.current_node
end
def fetch_geo_mirror(repository)
# Updates an existing repository using JWT authentication mechanism
#
# @param [Repository] target_repository specify a different temporary repository (default: current repository)
def fetch_geo_mirror(target_repository: repository)
# Fetch the repository, using a JWT header for authentication
repository.fetch_as_mirror(remote_url, forced: true, http_authorization_header: jwt_authentication_header)
target_repository.fetch_as_mirror(remote_url,
forced: true,
http_authorization_header: jwt_authentication_header)
end
# Clone a Geo repository using JWT authentication mechanism
#
# @param [Repository] target_repository specify a different temporary repository (default: current repository)
def clone_geo_mirror(target_repository: repository)
target_repository.clone_as_mirror(remote_url,
http_authorization_header: jwt_authentication_header)
end
# Build a JWT header for authentication
......
......@@ -10,11 +10,13 @@ RSpec.describe Geo::DesignRepositorySyncService do
let_it_be(:secondary) { create(:geo_node) }
let(:user) { create(:user) }
let(:project) { create(:project_empty_repo, namespace: create(:namespace, owner: user)) }
let(:project) { create(:project_empty_repo, :design_repo, namespace: create(:namespace, owner: user)) }
let(:repository) { project.design_repository }
let(:temp_repo) { subject.send(:temp_repo) }
let(:lease_key) { "geo_sync_service:design:#{project.id}" }
let(:lease_uuid) { 'uuid'}
let(:lease_uuid) { 'uuid' }
let(:url_to_repo) { "#{primary.url}#{project.full_path}.design.git" }
subject { described_class.new(project) }
......@@ -26,8 +28,6 @@ RSpec.describe Geo::DesignRepositorySyncService do
it_behaves_like 'geo base sync fetch'
describe '#execute' do
let(:url_to_repo) { "#{primary.url}#{project.full_path}.design.git" }
before do
# update_highest_role uses exclusive key too:
allow(Gitlab::ExclusiveLease).to receive(:new).and_call_original
......@@ -36,11 +36,11 @@ RSpec.describe Geo::DesignRepositorySyncService do
stub_exclusive_lease("geo_project_housekeeping:#{project.id}")
allow(repository).to receive(:fetch_as_mirror).and_return(true)
allow(repository).to receive(:clone_as_mirror).and_return(true)
allow(repository)
.to receive(:find_remote_root_ref)
.with(url_to_repo, anything)
.and_return('master')
allow(repository).to receive(:find_remote_root_ref)
.with(url_to_repo, anything)
.and_return('master')
allow_any_instance_of(Geo::ProjectHousekeepingService).to receive(:execute)
.and_return(nil)
......@@ -51,87 +51,97 @@ RSpec.describe Geo::DesignRepositorySyncService do
include_context 'lease handling'
it 'fetches project repository with JWT credentials' do
expect(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.once
it 'voids the failure message when it succeeds after an error' do
registry = create(:geo_design_registry, project: project, last_sync_failure: 'error')
subject.execute
expect { subject.execute }.to change { registry.reload.last_sync_failure }.to(nil)
end
it 'expires repository caches' do
expect_any_instance_of(Repository).to receive(:expire_all_method_caches).once
expect_any_instance_of(Repository).to receive(:expire_branch_cache).once
expect_any_instance_of(Repository).to receive(:expire_content_cache).once
it 'execute repository cache expiration' do
expect(subject).to receive(:expire_repository_caches)
subject.execute
end
it 'voids the failure message when it succeeds after an error' do
registry = create(:geo_design_registry, project: project, last_sync_failure: 'error')
context 'with existing repository' do
before do
subject.send(:ensure_repository)
end
expect { subject.execute }.to change { registry.reload.last_sync_failure}.to(nil)
end
it 'fetches project repository with JWT credentials' do
expect(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.once
it 'rescues when Gitlab::Shell::Error is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Shell::Error)
subject.execute
end
expect { subject.execute }.not_to raise_error
end
it 'rescues when Gitlab::Shell::Error is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Shell::Error)
it 'rescues exception when Gitlab::Git::Repository::NoRepository is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
expect { subject.execute }.not_to raise_error
end
expect { subject.execute }.not_to raise_error
end
it 'rescues exception when Gitlab::Git::Repository::NoRepository is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
it 'increases retry count when Gitlab::Git::Repository::NoRepository is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
expect { subject.execute }.not_to raise_error
end
subject.execute
it 'increases retry count when Gitlab::Git::Repository::NoRepository is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
expect(Geo::DesignRegistry.last).to have_attributes(
retry_count: 1
)
end
subject.execute
it 'marks sync as successful if no repository found' do
registry = create(:geo_design_registry, project: project)
expect(Geo::DesignRegistry.last).to have_attributes(retry_count: 1)
end
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Shell::Error.new(Gitlab::GitAccessDesign::ERROR_MESSAGES[:no_repo]))
it 'marks sync as successful if no repository found' do
registry = create(:geo_design_registry, project: project)
subject.execute
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Shell::Error.new(Gitlab::GitAccessDesign::ERROR_MESSAGES[:no_repo]))
expect(registry.reload).to have_attributes(
state: 'synced',
missing_on_primary: true
)
end
subject.execute
it 'marks resync as true after a failure' do
described_class.new(project).execute
expect(registry.reload).to have_attributes(state: 'synced', missing_on_primary: true)
end
expect(Geo::DesignRegistry.last.state).to eq 'synced'
it 'marks resync as true after a failure' do
described_class.new(project).execute
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
expect(Geo::DesignRegistry.last.state).to eq 'synced'
subject.execute
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
expect(Geo::DesignRegistry.last.state).to eq 'failed'
subject.execute
expect(Geo::DesignRegistry.last.state).to eq 'failed'
end
end
context 'with a never synced repository' do
it 'clones project repository with JWT credentials' do
allow(repository).to receive(:exists?) { false }
expect(repository).to receive(:clone_as_mirror)
.with(url_to_repo, http_authorization_header: anything)
.once
subject.execute
end
end
it_behaves_like 'sync retries use the snapshot RPC' do
let(:repository) { project.design_repository }
let(:retry_count) { Geo::DesignRegistry::RETRIES_BEFORE_REDOWNLOAD }
def registry_with_retry_count(retries)
......@@ -140,6 +150,18 @@ RSpec.describe Geo::DesignRepositorySyncService do
end
end
describe '#expire_repository_caches' do
it 'expires repository caches' do
subject.send(:ensure_repository)
expect(repository).to receive(:expire_all_method_caches).once
expect(repository).to receive(:expire_branch_cache).once
expect(repository).to receive(:expire_content_cache).once
subject.send(:expire_repository_caches)
end
end
context 'race condition when RepositoryUpdatedEvent was processed during a sync' do
let(:registry) { subject.send(:registry) }
......@@ -150,4 +172,53 @@ RSpec.describe Geo::DesignRepositorySyncService do
subject.send(:mark_sync_as_successful)
end
end
context 'when the repository is redownloaded' do
context 'with geo_use_clone_on_first_sync flag disabled' do
before do
stub_feature_flags(geo_use_clone_on_first_sync: false)
allow(subject).to receive(:redownload?).and_return(true)
end
it 'creates a new repository and fetches with JWT credentials' do
expect(temp_repo).to receive(:create_repository)
expect(temp_repo).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.once
expect(subject).to receive(:set_temp_repository_as_main)
subject.execute
end
it 'cleans temporary repo after redownload' do
expect(subject).to receive(:fetch_geo_mirror).with(target_repository: temp_repo)
expect(subject).to receive(:clean_up_temporary_repository).twice.and_call_original
subject.execute
end
end
context 'with geo_use_clone_on_first_sync flag enabled' do
before do
stub_feature_flags(geo_use_clone_on_first_sync: true)
allow(subject).to receive(:redownload?).and_return(true)
end
it 'clones a new repository with JWT credentials' do
expect(temp_repo).to receive(:clone_as_mirror)
.with(url_to_repo, http_authorization_header: anything)
.once
expect(subject).to receive(:set_temp_repository_as_main)
subject.execute
end
it 'cleans temporary repo after redownload' do
expect(subject).to receive(:clone_geo_mirror).with(target_repository: temp_repo)
expect(subject).to receive(:clean_up_temporary_repository).twice.and_call_original
subject.execute
end
end
end
end
......@@ -14,8 +14,9 @@ RSpec.describe Geo::FrameworkRepositorySyncService, :geo do
let_it_be(:replicator) { snippet.snippet_repository.replicator }
let(:repository) { snippet.repository }
let(:temp_repo) { subject.send(:temp_repo) }
let(:lease_key) { "geo_sync_ssf_service:snippet_repository:#{replicator.model_record.id}" }
let(:lease_uuid) { 'uuid'}
let(:lease_uuid) { 'uuid' }
let(:registry) { replicator.registry }
subject { described_class.new(replicator) }
......@@ -49,23 +50,25 @@ RSpec.describe Geo::FrameworkRepositorySyncService, :geo do
before do
stub_exclusive_lease(lease_key, lease_uuid)
allow_any_instance_of(Repository).to receive(:fetch_as_mirror)
.and_return(true)
allow(repository).to receive(:fetch_as_mirror).and_return(true)
allow(repository).to receive(:clone_as_mirror).and_return(true)
allow_any_instance_of(Repository)
.to receive(:find_remote_root_ref)
.with(url_to_repo, anything)
.and_return('master')
# Simulates a successful clone, by making sure a repository is created
allow(temp_repo).to receive(:clone_as_mirror) do
temp_repo.create_repository
end
allow(repository).to receive(:find_remote_root_ref)
.with(url_to_repo, anything)
.and_return('master')
end
include_context 'lease handling'
it 'fetches project repository with JWT credentials' do
expect(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.once
it 'voids the failure message when it succeeds after an error' do
registry.update!(last_sync_failure: 'error')
subject.execute
expect { subject.execute }.to change { registry.reload.last_sync_failure}.to(nil)
end
it 'expires repository caches' do
......@@ -76,72 +79,106 @@ RSpec.describe Geo::FrameworkRepositorySyncService, :geo do
subject.execute
end
it 'voids the failure message when it succeeds after an error' do
registry.update!(last_sync_failure: 'error')
context 'with existing repository' do
it 'fetches project repository with JWT credentials' do
expect(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.once
expect { subject.execute }.to change { registry.reload.last_sync_failure}.to(nil)
end
subject.execute
end
it 'rescues when Gitlab::Shell::Error is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Shell::Error)
it 'rescues when Gitlab::Shell::Error is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Shell::Error)
expect { subject.execute }.not_to raise_error
end
expect { subject.execute }.not_to raise_error
end
it 'rescues exception and fires after_create hook when Gitlab::Git::Repository::NoRepository is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
it 'rescues exception and fires after_create hook when Gitlab::Git::Repository::NoRepository is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
expect(repository).to receive(:after_create)
expect(repository).to receive(:after_create)
expect { subject.execute }.not_to raise_error
end
expect { subject.execute }.not_to raise_error
end
it 'increases retry count when Gitlab::Git::Repository::NoRepository is raised' do
registry.save!
it 'increases retry count when Gitlab::Git::Repository::NoRepository is raised' do
registry.save!
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
subject.execute
subject.execute
expect(registry.reload).to have_attributes(
state: Geo::SnippetRepositoryRegistry::STATE_VALUES[:failed],
retry_count: 1
)
end
expect(registry.reload).to have_attributes(
state: Geo::SnippetRepositoryRegistry::STATE_VALUES[:failed],
retry_count: 1)
end
it 'marks sync as successful if no repository found' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Shell::Error.new(Gitlab::GitAccessSnippet::ERROR_MESSAGES[:no_repo]))
it 'marks sync as successful if no repository found' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Shell::Error.new(Gitlab::GitAccessSnippet::ERROR_MESSAGES[:no_repo]))
expect(replicator.class).to receive(:no_repo_message).once.and_call_original
expect(replicator.class).to receive(:no_repo_message).once.and_call_original
subject.execute
subject.execute
expect(registry).to have_attributes(
state: Geo::SnippetRepositoryRegistry::STATE_VALUES[:synced],
missing_on_primary: true)
end
it 'marks sync as failed' do
subject.execute
expect(registry.synced?).to be true
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
subject.execute
expect(registry).to have_attributes(
state: Geo::SnippetRepositoryRegistry::STATE_VALUES[:synced],
missing_on_primary: true
)
expect(registry.reload.failed?).to be true
end
end
it 'marks sync as failed' do
subject.execute
context 'with a never synced repository' do
context 'with geo_use_clone_on_first_sync flag enabled' do
before do
stub_feature_flags(geo_use_clone_on_first_sync: true)
allow(repository).to receive(:exists?) { false }
end
expect(registry.synced?).to be true
it 'clones repository with JWT credentials' do
expect(repository).to receive(:clone_as_mirror)
.with(url_to_repo, http_authorization_header: anything)
.once
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
subject.execute
end
end
subject.execute
context 'with geo_use_clone_on_first_sync flag disabled' do
before do
stub_feature_flags(geo_use_clone_on_first_sync: false)
allow(repository).to receive(:exists?) { false }
end
it 'fetches repository with JWT credentials' do
expect(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.once
expect(registry.reload.failed?).to be true
subject.execute
end
end
end
context 'tracking database' do
......@@ -175,10 +212,9 @@ RSpec.describe Geo::FrameworkRepositorySyncService, :geo do
context 'with non empty repositories' do
context 'when HEAD change' do
before do
allow(repository)
.to receive(:find_remote_root_ref)
.with(url_to_repo, anything)
.and_return('feature')
allow(repository).to receive(:find_remote_root_ref)
.with(url_to_repo, anything)
.and_return('feature')
end
it 'syncs gitattributes to info/attributes' do
......@@ -223,21 +259,60 @@ RSpec.describe Geo::FrameworkRepositorySyncService, :geo do
expect(registry).to have_attributes(last_synced_at: be_present,
retry_count: 1,
retry_at: be_present,
last_sync_failure: 'Error syncing repository: shell error'
)
last_sync_failure: 'Error syncing repository: shell error')
end
end
end
context 'retries' do
it 'tries to fetch repo' do
registry.update!(retry_count: described_class::RETRIES_BEFORE_REDOWNLOAD - 1)
context 'with repository previously synced' do
it 'tries to fetch repo' do
registry.update!(retry_count: described_class::RETRIES_BEFORE_REDOWNLOAD - 1)
expect(subject).to receive(:sync_repository)
expect(subject).to receive(:sync_repository)
subject.execute
subject.execute
end
it 'tries to redownload when should_be_redownloaded' do
allow(subject).to receive(:should_be_redownloaded?) { true }
expect(subject).to receive(:redownload_repository)
subject.execute
end
it 'successfully redownloads the repository even if the retry time exceeds max value' do
timestamp = Time.current.utc
registry.update!(
retry_count: described_class::RETRIES_BEFORE_REDOWNLOAD + 2000,
retry_at: timestamp,
force_to_redownload: true
)
subject.execute
# The repository should be redownloaded and cleared without errors. If
# the timestamp were not capped, we would have seen a "timestamp out
# of range" in the first update to the registry.
registry.reload
expect(registry.retry_at).to be_nil
end
end
context 'no repository' do
it 'does not raise an error' do
registry.update!(force_to_redownload: true)
expect(repository).to receive(:expire_exists_cache).twice.and_call_original
expect(subject).not_to receive(:fail_registry_sync!)
subject.execute
end
end
end
context 'when repository is redownloaded' do
it 'sets the redownload flag to false after success' do
registry.update!(retry_count: described_class::RETRIES_BEFORE_REDOWNLOAD + 1, force_to_redownload: true)
......@@ -263,53 +338,50 @@ RSpec.describe Geo::FrameworkRepositorySyncService, :geo do
expect(File.directory?(repo_path)).to be true
end
it 'tries to redownload repo when force_redownload flag is set' do
registry.update!(
retry_count: described_class::RETRIES_BEFORE_REDOWNLOAD - 1,
force_to_redownload: true
)
expect(subject).to receive(:sync_repository)
context 'with geo_use_clone_on_first_sync flag disabled' do
before do
stub_feature_flags(geo_use_clone_on_first_sync: false)
allow(subject).to receive(:should_be_redownloaded?) { true }
end
subject.execute
end
it 'creates a new repository and fetches with JWT credentials' do
expect(temp_repo).to receive(:create_repository)
expect(temp_repo).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.once
expect(subject).to receive(:set_temp_repository_as_main)
it 'cleans temporary repo after redownload' do
registry.update!(
retry_count: described_class::RETRIES_BEFORE_REDOWNLOAD - 1,
force_to_redownload: true
)
subject.execute
end
expect(subject).to receive(:fetch_geo_mirror)
expect(subject).to receive(:clean_up_temporary_repository).twice.and_call_original
expect(subject.gitlab_shell).to receive(:repository_exists?).twice.with(replicator.model_record.repository_storage, /.git$/)
it 'cleans temporary repo after redownload' do
expect(subject).to receive(:fetch_geo_mirror).with(target_repository: temp_repo)
expect(subject).to receive(:clean_up_temporary_repository).twice.and_call_original
expect(subject.gitlab_shell).to receive(:repository_exists?).twice.with(replicator.model_record.repository_storage, /.git$/)
subject.execute
subject.execute
end
end
it 'successfully redownloads the repository even if the retry time exceeds max value' do
timestamp = Time.current.utc
registry.update!(
retry_count: described_class::RETRIES_BEFORE_REDOWNLOAD + 2000,
retry_at: timestamp,
force_to_redownload: true
)
subject.execute
context 'with geo_use_clone_on_first_sync flag enabled' do
before do
stub_feature_flags(geo_use_clone_on_first_sync: true)
allow(subject).to receive(:should_be_redownloaded?) { true }
end
# The repository should be redownloaded and cleared without errors. If
# the timestamp were not capped, we would have seen a "timestamp out
# of range" in the first update to the registry.
registry.reload
expect(registry.retry_at).to be_nil
end
it 'clones a new repository with JWT credentials' do
expect(temp_repo).to receive(:clone_as_mirror)
.with(url_to_repo, http_authorization_header: anything)
.once
expect(subject).to receive(:set_temp_repository_as_main)
context 'no repository' do
it 'does not raise an error' do
registry.update!(force_to_redownload: true)
subject.execute
end
expect(repository).to receive(:expire_exists_cache).twice.and_call_original
expect(subject).not_to receive(:fail_registry_sync!)
it 'cleans temporary repo after redownload' do
expect(subject).to receive(:clone_geo_mirror).with(target_repository: temp_repo)
expect(subject).to receive(:clean_up_temporary_repository).twice.and_call_original
expect(subject.gitlab_shell).to receive(:repository_exists?).twice.with(replicator.model_record.repository_storage, /.git$/)
subject.execute
end
......
......@@ -3,7 +3,16 @@
require 'spec_helper'
RSpec.describe Geo::RepositoryBaseSyncService do
include ::EE::GeoHelpers
let(:project) { build('project') }
let(:repository) { project.repository }
let_it_be(:geo_primary) { create(:geo_node, :primary) }
let_it_be(:geo_secondary) { create(:geo_node, :secondary) }
before do
stub_current_geo_node(geo_secondary)
end
subject { described_class.new(project) }
......@@ -15,4 +24,38 @@ RSpec.describe Geo::RepositoryBaseSyncService do
expect(subject.lease_key).to eq('geo_sync_service:wiki:999')
end
end
describe '#lease_timeout' do
it 'returns a lease timeout value' do
expect(subject.lease_timeout). to eq(8.hours)
end
end
describe '#repository' do
it 'raises a NotImplementedError' do
expect { subject.repository }.to raise_error(NotImplementedError)
end
end
context 'with a repository defined' do
before do
allow(subject).to receive(:repository) { repository }
end
describe '#fetch_geo_mirror' do
it 'delegates to repository#fetch_as_mirror' do
expect(repository).to receive(:fetch_as_mirror)
subject.send(:fetch_geo_mirror)
end
end
describe '#clone_geo_mirror' do
it 'delegates to repository#clone_as_mirror' do
expect(repository).to receive(:clone_as_mirror)
subject.send(:clone_geo_mirror)
end
end
end
end
......@@ -11,8 +11,10 @@ RSpec.describe Geo::RepositorySyncService, :geo do
let_it_be(:project) { create(:project_empty_repo) }
let(:repository) { project.repository }
let(:temp_repo) { subject.send(:temp_repo) }
let(:lease_key) { "geo_sync_service:repository:#{project.id}" }
let(:lease_uuid) { 'uuid'}
let(:url_to_repo) { "#{primary.url}#{project.full_path}.git" }
subject { described_class.new(project) }
......@@ -25,14 +27,17 @@ RSpec.describe Geo::RepositorySyncService, :geo do
it_behaves_like 'reschedules sync due to race condition instead of waiting for backfill'
describe '#execute' do
let(:url_to_repo) { "#{primary.url}#{project.full_path}.git" }
before do
stub_exclusive_lease(lease_key, lease_uuid)
stub_exclusive_lease("geo_project_housekeeping:#{project.id}")
allow_any_instance_of(Repository).to receive(:fetch_as_mirror)
.and_return(true)
allow(repository).to receive(:fetch_as_mirror).and_return(true)
allow(repository).to receive(:clone_as_mirror).and_return(true)
# Simulates a successful clone, by making sure a repository is created
allow(temp_repo).to receive(:clone_as_mirror) do
temp_repo.create_repository
end
allow(repository)
.to receive(:find_remote_root_ref)
......@@ -289,95 +294,93 @@ RSpec.describe Geo::RepositorySyncService, :geo do
end
context 'retries' do
it 'tries to fetch repo' do
create(:geo_project_registry, project: project, repository_retry_count: Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD - 1)
context 'with repository previously synced' do
it 'tries to fetch repo' do
create(:geo_project_registry, project: project, repository_retry_count: Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD - 1)
expect(subject).to receive(:sync_repository)
expect(subject).to receive(:sync_repository)
subject.execute
end
subject.execute
end
it 'sets the redownload flag to false after success' do
registry = create(:geo_project_registry, project: project, repository_retry_count: Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD + 1, force_to_redownload_repository: true)
it 'sets the redownload flag to false after success' do
registry = create(:geo_project_registry, project: project, repository_retry_count: Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD + 1, force_to_redownload_repository: true)
subject.execute
subject.execute
expect(registry.reload.force_to_redownload_repository).to be false
end
expect(registry.reload.force_to_redownload_repository).to be false
end
it 'tries to redownload repo' do
create(:geo_project_registry, project: project, repository_retry_count: Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD + 1)
it 'tries to redownload repo' do
create(:geo_project_registry, project: project, repository_retry_count: Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD + 1)
expect(subject).to receive(:sync_repository).and_call_original
expect(subject.gitlab_shell).to receive(:mv_repository).twice.and_call_original
expect(subject).to receive(:sync_repository).and_call_original
expect(subject.gitlab_shell).to receive(:mv_repository).twice.and_call_original
expect(subject.gitlab_shell).to receive(:remove_repository).twice.and_call_original
expect(subject.gitlab_shell).to receive(:remove_repository).twice.and_call_original
subject.execute
subject.execute
repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
project.repository.path
end
repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
project.repository.path
end
expect(File.directory?(repo_path)).to be true
end
expect(File.directory?(repo_path)).to be true
end
it 'tries to redownload repo when force_redownload flag is set' do
create(
:geo_project_registry,
project: project,
repository_retry_count: Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD - 1,
force_to_redownload_repository: true
)
it 'tries to redownload repo when force_redownload flag is set' do
create(
:geo_project_registry,
project: project,
repository_retry_count: Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD - 1,
force_to_redownload_repository: true
)
expect(subject).to receive(:sync_repository)
expect(subject).to receive(:sync_repository)
subject.execute
end
subject.execute
end
it 'cleans temporary repo after redownload' do
create(
:geo_project_registry,
project: project,
repository_retry_count: Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD - 1,
force_to_redownload_repository: true
)
it 'cleans temporary repo after redownload' do
create(
:geo_project_registry,
project: project,
repository_retry_count: Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD - 1,
force_to_redownload_repository: true
)
expect(subject).to receive(:fetch_geo_mirror)
expect(subject).to receive(:clean_up_temporary_repository).twice.and_call_original
expect(subject.gitlab_shell).to receive(:repository_exists?).twice.with(project.repository_storage, /.git$/)
expect(subject).to receive(:clone_geo_mirror).with(target_repository: temp_repo)
expect(subject).to receive(:clean_up_temporary_repository).twice.and_call_original
expect(subject.gitlab_shell).to receive(:repository_exists?).twice.with(project.repository_storage, /.git$/)
subject.execute
end
subject.execute
end
it 'successfully redownloads the repository even if the retry time exceeds max value' do
timestamp = Time.current.utc
registry = create(
:geo_project_registry,
project: project,
repository_retry_count: Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD + 2000,
repository_retry_at: timestamp,
force_to_redownload_repository: true
)
it 'successfully redownloads the repository even if the retry time exceeds max value' do
timestamp = Time.current.utc
registry = create(
:geo_project_registry,
project: project,
repository_retry_count: Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD + 2000,
repository_retry_at: timestamp,
force_to_redownload_repository: true
)
subject.execute
subject.execute
# The repository should be redownloaded and cleared without errors. If
# the timestamp were not capped, we would have seen a "timestamp out
# of range" in the first update to the project registry.
registry.reload
expect(registry.repository_retry_at).to be_nil
# The repository should be redownloaded and cleared without errors. If
# the timestamp were not capped, we would have seen a "timestamp out
# of range" in the first update to the project registry.
registry.reload
expect(registry.repository_retry_at).to be_nil
end
end
context 'no repository' do
let(:project) { create(:project) }
it 'does not raise an error' do
create(
:geo_project_registry,
project: project,
force_to_redownload_repository: true
)
create(:geo_project_registry, project: project, force_to_redownload_repository: true)
expect(project.repository).to receive(:expire_exists_cache).exactly(3).times.and_call_original
expect(subject).not_to receive(:fail_registry_sync!)
......@@ -412,22 +415,59 @@ RSpec.describe Geo::RepositorySyncService, :geo do
end
context 'when the repository is redownloaded' do
before do
allow(subject).to receive(:redownload?).and_return(true)
allow(subject).to receive(:redownload_repository).and_return(nil)
end
context 'with geo_use_clone_on_first_sync flag disabled' do
before do
stub_feature_flags(geo_use_clone_on_first_sync: false)
allow(subject).to receive(:redownload?).and_return(true)
end
it "indicates the repository is new" do
expect(Geo::ProjectHousekeepingService).to receive(:new).with(project, new_repository: true).and_call_original
it 'creates a new repository and fetches with JWT credentials' do
expect(temp_repo).to receive(:create_repository)
expect(temp_repo).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.once
expect(subject).to receive(:set_temp_repository_as_main)
subject.execute
subject.execute
end
it 'cleans temporary repo after redownload' do
expect(subject).to receive(:fetch_geo_mirror).with(target_repository: temp_repo)
expect(subject).to receive(:clean_up_temporary_repository).twice.and_call_original
subject.execute
end
it "indicates the repository is not new even with errors" do
allow(subject).to receive(:redownload_repository).and_raise(Gitlab::Shell::Error)
expect(Geo::ProjectHousekeepingService).to receive(:new).with(project, new_repository: false).and_call_original
subject.execute
end
end
it "indicates the repository is not new even with errors" do
allow(subject).to receive(:redownload_repository).and_raise(Gitlab::Shell::Error)
expect(Geo::ProjectHousekeepingService).to receive(:new).with(project, new_repository: false).and_call_original
context 'with geo_use_clone_on_first_sync flag enabled' do
before do
stub_feature_flags(geo_use_clone_on_first_sync: true)
allow(subject).to receive(:redownload?).and_return(true)
end
subject.execute
it 'clones a new repository with JWT credentials' do
expect(temp_repo).to receive(:clone_as_mirror)
.with(url_to_repo, http_authorization_header: anything)
.once
expect(subject).to receive(:set_temp_repository_as_main)
expect(Geo::ProjectHousekeepingService).to receive(:new).with(project, new_repository: true).and_call_original
subject.execute
end
it 'cleans temporary repo after redownload' do
expect(subject).to receive(:clone_geo_mirror).with(target_repository: temp_repo)
expect(subject).to receive(:clean_up_temporary_repository).twice.and_call_original
subject.execute
end
end
end
......@@ -435,19 +475,47 @@ RSpec.describe Geo::RepositorySyncService, :geo do
before do
allow(repository).to receive(:exists?).and_return(false)
allow(subject).to receive(:fetch_geo_mirror).and_return(nil)
allow(subject).to receive(:clone_geo_mirror).and_return(nil)
end
it "indicates the repository is new" do
expect(Geo::ProjectHousekeepingService).to receive(:new).with(project, new_repository: true).and_call_original
context 'with geo_use_clone_on_first_sync flag enabled' do
before do
stub_feature_flags(geo_use_clone_on_first_sync: true)
end
subject.execute
it "dont indicates the repository is new when there were errors" do
allow(subject).to receive(:clone_geo_mirror).and_raise(Gitlab::Shell::Error)
expect(Geo::ProjectHousekeepingService).to receive(:new).with(project, new_repository: false).and_call_original
subject.execute
end
it "indicates the repository is new if successful" do
expect(Geo::ProjectHousekeepingService).to receive(:new).with(project, new_repository: true).and_call_original
subject.execute
end
end
it "indicates the repository is new when there were errors" do
allow(subject).to receive(:fetch_geo_mirror).and_raise(Gitlab::Shell::Error)
expect(Geo::ProjectHousekeepingService).to receive(:new).with(project, new_repository: true).and_call_original
context 'with geo_use_clone_on_first_sync flag disabled' do
before do
stub_feature_flags(geo_use_clone_on_first_sync: false)
end
subject.execute
it "indicates the repository is new when there were errors" do
allow(subject).to receive(:fetch_geo_mirror).and_raise(Gitlab::Shell::Error)
expect(Geo::ProjectHousekeepingService).to receive(:new).with(project, new_repository: true).and_call_original
subject.execute
end
it "indicates the repository is new if successful" do
expect(Geo::ProjectHousekeepingService).to receive(:new).with(project, new_repository: true).and_call_original
subject.execute
end
end
end
......
......@@ -8,11 +8,13 @@ RSpec.describe Geo::WikiSyncService, :geo do
let_it_be(:primary) { create(:geo_node, :primary) }
let_it_be(:secondary) { create(:geo_node) }
let_it_be(:project) { create(:project_empty_repo) }
let_it_be(:project) { create(:project_empty_repo, :wiki_repo) }
let(:repository) { project.wiki.repository }
let(:temp_repo) { subject.send(:temp_repo) }
let(:lease_key) { "geo_sync_service:wiki:#{project.id}" }
let(:lease_uuid) { 'uuid'}
let(:url_to_repo) { "#{primary.url}#{project.full_path}.wiki.git" }
subject { described_class.new(project) }
......@@ -25,88 +27,89 @@ RSpec.describe Geo::WikiSyncService, :geo do
it_behaves_like 'reschedules sync due to race condition instead of waiting for backfill'
describe '#execute' do
let(:url_to_repo) { "#{primary.url}#{project.full_path}.wiki.git" }
before do
stub_exclusive_lease(lease_key, lease_uuid)
allow_any_instance_of(Repository).to receive(:fetch_as_mirror)
.and_return(true)
allow(repository).to receive(:fetch_as_mirror).and_return(true)
allow(repository).to receive(:clone_as_mirror).and_return(true)
# Simulates a successful clone, by making sure a repository is created
allow(temp_repo).to receive(:clone_as_mirror) do
temp_repo.create_repository
end
end
include_context 'lease handling'
it 'fetches wiki repository with JWT credentials' do
expect(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.once
context 'with existing repository' do
it 'fetches wiki repository with JWT credentials' do
expect(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.once
subject.execute
end
subject.execute
end
it 'voids the failure message when it succeeds after an error' do
allow(repository).to receive(:update_root_ref)
it 'voids the failure message when it succeeds after an error' do
allow(repository).to receive(:update_root_ref)
registry = create(:geo_project_registry, project: project, last_wiki_sync_failure: 'error')
registry = create(:geo_project_registry, project: project, last_wiki_sync_failure: 'error')
expect { subject.execute }.to change { registry.reload.last_wiki_sync_failure }.to(nil)
end
expect { subject.execute }.to change { registry.reload.last_wiki_sync_failure }.to(nil)
end
it 'rescues exception when Gitlab::Shell::Error is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Shell::Error)
it 'rescues exception when Gitlab::Shell::Error is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Shell::Error)
expect { subject.execute }.not_to raise_error
end
expect { subject.execute }.not_to raise_error
end
it 'rescues exception when Gitlab::Git::Repository::NoRepository is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
it 'rescues exception when Gitlab::Git::Repository::NoRepository is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
expect { subject.execute }.not_to raise_error
end
expect { subject.execute }.not_to raise_error
end
it 'increases retry count when Gitlab::Git::Repository::NoRepository is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
it 'increases retry count when Gitlab::Git::Repository::NoRepository is raised' do
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
subject.execute
subject.execute
expect(Geo::ProjectRegistry.last).to have_attributes(
resync_wiki: true,
wiki_retry_count: 1
)
end
expect(Geo::ProjectRegistry.last).to have_attributes(resync_wiki: true,
wiki_retry_count: 1)
end
it 'marks sync as successful if no repository found' do
registry = create(:geo_project_registry, project: project)
it 'marks sync as successful if no repository found' do
registry = create(:geo_project_registry, project: project)
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Shell::Error.new(Gitlab::GitAccessWiki::ERROR_MESSAGES[:no_repo]))
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Shell::Error.new(Gitlab::GitAccessWiki::ERROR_MESSAGES[:no_repo]))
subject.execute
subject.execute
expect(registry.reload).to have_attributes(
resync_wiki: false,
last_wiki_successful_sync_at: be_present,
wiki_missing_on_primary: true
)
end
expect(registry.reload).to have_attributes(resync_wiki: false,
last_wiki_successful_sync_at: be_present,
wiki_missing_on_primary: true)
end
it 'marks resync as true after a failure' do
described_class.new(project).execute
it 'marks resync as true after a failure' do
described_class.new(project).execute
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
allow(repository).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.and_raise(Gitlab::Git::Repository::NoRepository)
subject.execute
subject.execute
expect(Geo::ProjectRegistry.last.resync_wiki).to be true
expect(Geo::ProjectRegistry.last.resync_wiki).to be true
end
end
context 'wiki repository presumably exists on primary' do
......@@ -147,9 +150,7 @@ RSpec.describe Geo::WikiSyncService, :geo do
context 'tracking database' do
context 'temporary repositories' do
include_examples 'cleans temporary repositories' do
let(:repository) { project.wiki.repository }
end
include_examples 'cleans temporary repositories'
end
it 'creates a new registry if does not exists' do
......@@ -230,14 +231,10 @@ RSpec.describe Geo::WikiSyncService, :geo do
let(:project) { create(:project, :repository) }
it 'does not raise an error' do
create(
:geo_project_registry,
project: project,
force_to_redownload_wiki: true
)
allow(project.wiki.repository).to receive(:update_root_ref)
expect(project.wiki.repository).to receive(:expire_exists_cache).exactly(3).times.and_call_original
create(:geo_project_registry, project: project, force_to_redownload_wiki: true)
allow(repository).to receive(:update_root_ref)
expect(repository).to receive(:expire_exists_cache).exactly(3).times.and_call_original
expect(subject).not_to receive(:fail_registry_sync!)
subject.execute
......@@ -246,7 +243,6 @@ RSpec.describe Geo::WikiSyncService, :geo do
end
it_behaves_like 'sync retries use the snapshot RPC' do
let(:repository) { project.wiki.repository }
let(:retry_count) { Geo::ProjectRegistry::RETRIES_BEFORE_REDOWNLOAD }
def registry_with_retry_count(retries)
......@@ -254,4 +250,53 @@ RSpec.describe Geo::WikiSyncService, :geo do
end
end
end
context 'when the repository is redownloaded' do
context 'with geo_use_clone_on_first_sync flag disabled' do
before do
stub_feature_flags(geo_use_clone_on_first_sync: false)
allow(subject).to receive(:redownload?).and_return(true)
end
it 'creates a new repository and fetches with JWT credentials' do
expect(temp_repo).to receive(:create_repository)
expect(temp_repo).to receive(:fetch_as_mirror)
.with(url_to_repo, forced: true, http_authorization_header: anything)
.once
expect(subject).to receive(:set_temp_repository_as_main)
subject.execute
end
it 'cleans temporary repo after redownload' do
expect(subject).to receive(:fetch_geo_mirror).with(target_repository: temp_repo)
expect(subject).to receive(:clean_up_temporary_repository).twice.and_call_original
subject.execute
end
end
context 'with geo_use_clone_on_first_sync flag enabled' do
before do
stub_feature_flags(geo_use_clone_on_first_sync: true)
allow(subject).to receive(:redownload?).and_return(true)
end
it 'clones a new repository with JWT credentials' do
expect(temp_repo).to receive(:clone_as_mirror)
.with(url_to_repo, http_authorization_header: anything)
.once
expect(subject).to receive(:set_temp_repository_as_main)
subject.execute
end
it 'cleans temporary repo after redownload' do
expect(subject).to receive(:clone_geo_mirror).with(target_repository: temp_repo)
expect(subject).to receive(:clean_up_temporary_repository).twice.and_call_original
subject.execute
end
end
end
end
......@@ -62,9 +62,14 @@ RSpec.shared_examples 'geo base sync fetch' do
describe '#fetch_repository' do
let(:fetch_repository) { subject.send(:fetch_repository) }
let(:temp_repo) { subject.send(:temp_repo) }
before do
allow(subject).to receive(:fetch_geo_mirror).and_return(true)
allow(subject).to receive(:clone_geo_mirror).and_return(true)
allow(subject).to receive(:clone_geo_mirror).with(target_repository: temp_repo) do
temp_repo.create_repository
end
allow(repository).to receive(:update_root_ref)
end
......@@ -74,25 +79,27 @@ RSpec.shared_examples 'geo base sync fetch' do
fetch_repository
end
it 'fetches repository from geo node' do
is_expected.to receive(:fetch_geo_mirror).with(subject.send(:repository))
fetch_repository
end
it 'syncs the HEAD ref' do
expect(repository).to receive(:update_root_ref)
fetch_repository
end
context 'repository does not exist' do
before do
allow_any_instance_of(Repository).to receive(:exists?) { false }
context 'with existing repository' do
it 'fetches repository from geo node' do
subject.send(:ensure_repository)
is_expected.to receive(:fetch_geo_mirror)
fetch_repository
end
end
context 'with a never synced repository' do
it 'clones repository from geo node' do
allow(repository).to receive(:exists?) { false }
it 'ensures repository is created' do
is_expected.to receive(:ensure_repository)
is_expected.to receive(:clone_geo_mirror)
fetch_repository
end
......@@ -109,9 +116,11 @@ RSpec.shared_examples 'sync retries use the snapshot RPC' do
end
it 'does not attempt to snapshot for initial sync' do
allow(repository).to receive(:exists?) { false }
expect(repository).not_to receive_create_from_snapshot
expect(temp_repo).not_to receive_create_from_snapshot
expect(subject).to receive(:fetch_geo_mirror).with(repository)
expect(subject).to receive(:clone_geo_mirror)
subject.execute
end
......@@ -121,7 +130,7 @@ RSpec.shared_examples 'sync retries use the snapshot RPC' do
expect(repository).not_to receive_create_from_snapshot
expect(temp_repo).not_to receive_create_from_snapshot
expect(subject).to receive(:fetch_geo_mirror).with(repository)
expect(subject).to receive(:fetch_geo_mirror)
subject.execute
end
......@@ -132,16 +141,17 @@ RSpec.shared_examples 'sync retries use the snapshot RPC' do
it 'attempts to snapshot' do
expect(repository).not_to receive_create_from_snapshot
expect(temp_repo).to receive_create_from_snapshot
expect(subject).not_to receive(:fetch_geo_mirror).with(temp_repo)
expect(subject).not_to receive(:fetch_geo_mirror)
expect(subject).not_to receive(:clone_geo_mirror)
expect(subject).to receive(:set_temp_repository_as_main)
subject.execute
end
it 'attempts to fetch if snapshotting raises an exception' do
it 'attempts to clone if snapshotting raises an exception' do
expect(repository).not_to receive_create_from_snapshot
expect(temp_repo).to receive_create_from_snapshot.and_raise(ArgumentError)
expect(subject).to receive(:fetch_geo_mirror).with(temp_repo)
expect(subject).to receive(:clone_geo_mirror)
subject.execute
end
......
......@@ -841,11 +841,11 @@ module Gitlab
end
end
def import_repository(url)
def import_repository(url, http_authorization_header: '', mirror: false)
raise ArgumentError, "don't use disk paths with import_repository: #{url.inspect}" if url.start_with?('.', '/')
wrapped_gitaly_errors do
gitaly_repository_client.import_repository(url)
gitaly_repository_client.import_repository(url, http_authorization_header: http_authorization_header, mirror: mirror)
end
end
......
......@@ -145,10 +145,12 @@ module Gitlab
)
end
def import_repository(source)
def import_repository(source, http_authorization_header: '', mirror: false)
request = Gitaly::CreateRepositoryFromURLRequest.new(
repository: @gitaly_repo,
url: source
url: source,
http_authorization_header: http_authorization_header,
mirror: mirror
)
GitalyClient.call(
......
......@@ -2448,7 +2448,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
it 'delegates to Gitaly' do
expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |svc|
expect(svc).to receive(:import_repository).with(url).and_return(nil)
expect(svc).to receive(:import_repository).with(url, http_authorization_header: '', mirror: false).and_return(nil)
end
repository.import_repository(url)
......
......@@ -22,7 +22,7 @@ RSpec.describe ProjectImportState, type: :model do
before do
allow_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository)
.with(project.import_url).and_return(true)
.with(project.import_url, http_authorization_header: '', mirror: false).and_return(true)
# Works around https://github.com/rspec/rspec-mocks/issues/910
allow(Project).to receive(:find).with(project.id).and_return(project)
......
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