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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/workers/container_registry')
-rw-r--r--spec/workers/container_registry/migration/enqueuer_worker_spec.rb178
-rw-r--r--spec/workers/container_registry/migration/guard_worker_spec.rb162
-rw-r--r--spec/workers/container_registry/migration/observer_worker_spec.rb57
3 files changed, 397 insertions, 0 deletions
diff --git a/spec/workers/container_registry/migration/enqueuer_worker_spec.rb b/spec/workers/container_registry/migration/enqueuer_worker_spec.rb
new file mode 100644
index 00000000000..12c14c35365
--- /dev/null
+++ b/spec/workers/container_registry/migration/enqueuer_worker_spec.rb
@@ -0,0 +1,178 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ContainerRegistry::Migration::EnqueuerWorker, :aggregate_failures do
+ let_it_be_with_reload(:container_repository) { create(:container_repository, created_at: 2.days.ago) }
+
+ let(:worker) { described_class.new }
+
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_application_setting(container_registry_import_created_before: 1.day.ago)
+ stub_container_registry_tags(repository: container_repository.path, tags: %w(tag1 tag2 tag3), with_manifest: true)
+ end
+
+ describe '#perform' do
+ subject { worker.perform }
+
+ shared_examples 'no action' do
+ it 'does not queue or change any repositories' do
+ subject
+
+ expect(container_repository.reload).to be_default
+ end
+ end
+
+ shared_examples 're-enqueuing based on capacity' do
+ context 'below capacity' do
+ before do
+ allow(ContainerRegistry::Migration).to receive(:capacity).and_return(9999)
+ end
+
+ it 're-enqueues the worker' do
+ expect(ContainerRegistry::Migration::EnqueuerWorker).to receive(:perform_async)
+
+ subject
+ end
+ end
+
+ context 'above capacity' do
+ before do
+ allow(ContainerRegistry::Migration).to receive(:capacity).and_return(-1)
+ end
+
+ it 'does not re-enqueue the worker' do
+ expect(ContainerRegistry::Migration::EnqueuerWorker).not_to receive(:perform_async)
+
+ subject
+ end
+ end
+ end
+
+ context 'with qualified repository' do
+ it 'starts the pre-import for the next qualified repository' do
+ method = worker.method(:next_repository)
+ allow(worker).to receive(:next_repository) do
+ next_qualified_repository = method.call
+ allow(next_qualified_repository).to receive(:migration_pre_import).and_return(:ok)
+ next_qualified_repository
+ end
+
+ expect(worker).to receive(:log_extra_metadata_on_done)
+ .with(:container_repository_id, container_repository.id)
+ expect(worker).to receive(:log_extra_metadata_on_done)
+ .with(:import_type, 'next')
+
+ subject
+
+ expect(container_repository.reload).to be_pre_importing
+ end
+
+ it_behaves_like 're-enqueuing based on capacity'
+ end
+
+ context 'migrations are disabled' do
+ before do
+ allow(ContainerRegistry::Migration).to receive(:enabled?).and_return(false)
+ end
+
+ it_behaves_like 'no action'
+ end
+
+ context 'above capacity' do
+ before do
+ create(:container_repository, :importing)
+ create(:container_repository, :importing)
+ allow(ContainerRegistry::Migration).to receive(:capacity).and_return(1)
+ end
+
+ it_behaves_like 'no action'
+
+ it 'does not re-enqueue the worker' do
+ expect(ContainerRegistry::Migration::EnqueuerWorker).not_to receive(:perform_async)
+
+ subject
+ end
+ end
+
+ context 'too soon before previous completed import step' do
+ before do
+ create(:container_repository, :import_done, migration_import_done_at: 1.minute.ago)
+ allow(ContainerRegistry::Migration).to receive(:enqueue_waiting_time).and_return(1.hour)
+ end
+
+ it_behaves_like 'no action'
+ end
+
+ context 'when an aborted import is available' do
+ let_it_be(:aborted_repository) { create(:container_repository, :import_aborted) }
+
+ it 'retries the import for the aborted repository' do
+ method = worker.method(:next_aborted_repository)
+ allow(worker).to receive(:next_aborted_repository) do
+ next_aborted_repository = method.call
+ allow(next_aborted_repository).to receive(:migration_import).and_return(:ok)
+ allow(next_aborted_repository.gitlab_api_client).to receive(:import_status).and_return('import_failed')
+ next_aborted_repository
+ end
+
+ expect(worker).to receive(:log_extra_metadata_on_done)
+ .with(:container_repository_id, aborted_repository.id)
+ expect(worker).to receive(:log_extra_metadata_on_done)
+ .with(:import_type, 'retry')
+
+ subject
+
+ expect(aborted_repository.reload).to be_importing
+ expect(container_repository.reload).to be_default
+ end
+
+ it_behaves_like 're-enqueuing based on capacity'
+ end
+
+ context 'when no repository qualifies' do
+ include_examples 'an idempotent worker' do
+ before do
+ allow(ContainerRepository).to receive(:ready_for_import).and_return(ContainerRepository.none)
+ end
+
+ it_behaves_like 'no action'
+ end
+ end
+
+ context 'over max tag count' do
+ before do
+ stub_application_setting(container_registry_import_max_tags_count: 2)
+ end
+
+ it 'skips the repository' do
+ subject
+
+ expect(container_repository.reload).to be_import_skipped
+ expect(container_repository.migration_skipped_reason).to eq('too_many_tags')
+ expect(container_repository.migration_skipped_at).not_to be_nil
+ end
+
+ it_behaves_like 're-enqueuing based on capacity'
+ end
+
+ context 'when an error occurs' do
+ before do
+ allow(ContainerRegistry::Migration).to receive(:max_tags_count).and_raise(StandardError)
+ end
+
+ it 'aborts the import' do
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
+ instance_of(StandardError),
+ next_repository_id: container_repository.id,
+ next_aborted_repository_id: nil
+ )
+
+ subject
+
+ expect(container_repository.reload).to be_import_aborted
+ end
+ end
+ end
+end
diff --git a/spec/workers/container_registry/migration/guard_worker_spec.rb b/spec/workers/container_registry/migration/guard_worker_spec.rb
new file mode 100644
index 00000000000..7d1df320d4e
--- /dev/null
+++ b/spec/workers/container_registry/migration/guard_worker_spec.rb
@@ -0,0 +1,162 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ContainerRegistry::Migration::GuardWorker, :aggregate_failures do
+ include_context 'container registry client'
+
+ let(:worker) { described_class.new }
+
+ describe '#perform' do
+ let(:pre_importing_migrations) { ::ContainerRepository.with_migration_states(:pre_importing) }
+ let(:pre_import_done_migrations) { ::ContainerRepository.with_migration_states(:pre_import_done) }
+ let(:importing_migrations) { ::ContainerRepository.with_migration_states(:importing) }
+ let(:import_aborted_migrations) { ::ContainerRepository.with_migration_states(:import_aborted) }
+ let(:import_done_migrations) { ::ContainerRepository.with_migration_states(:import_done) }
+
+ subject { worker.perform }
+
+ before do
+ stub_container_registry_config(enabled: true, api_url: registry_api_url, key: 'spec/fixtures/x509_certificate_pk.key')
+ allow(::ContainerRegistry::Migration).to receive(:max_step_duration).and_return(5.minutes)
+ end
+
+ context 'on gitlab.com' do
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(true)
+ end
+
+ shared_examples 'not aborting any migration' do
+ it 'will not abort the migration' do
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:stale_migrations_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:aborted_stale_migrations_count, 0)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:long_running_stale_migration_container_repository_ids, [stale_migration.id])
+
+ expect { subject }
+ .to not_change(pre_importing_migrations, :count)
+ .and not_change(pre_import_done_migrations, :count)
+ .and not_change(importing_migrations, :count)
+ .and not_change(import_done_migrations, :count)
+ .and not_change(import_aborted_migrations, :count)
+ .and not_change { stale_migration.reload.migration_state }
+ .and not_change { ongoing_migration.migration_state }
+ end
+ end
+
+ context 'with no stale migrations' do
+ it_behaves_like 'an idempotent worker'
+
+ it 'will not update any migration state' do
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:stale_migrations_count, 0)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:aborted_stale_migrations_count, 0)
+
+ expect { subject }
+ .to not_change(pre_importing_migrations, :count)
+ .and not_change(pre_import_done_migrations, :count)
+ .and not_change(importing_migrations, :count)
+ .and not_change(import_aborted_migrations, :count)
+ end
+ end
+
+ context 'with pre_importing stale migrations' do
+ let(:ongoing_migration) { create(:container_repository, :pre_importing) }
+ let(:stale_migration) { create(:container_repository, :pre_importing, migration_pre_import_started_at: 35.minutes.ago) }
+ let(:import_status) { 'test' }
+
+ before do
+ allow_next_instance_of(ContainerRegistry::GitlabApiClient) do |client|
+ allow(client).to receive(:import_status).and_return(import_status)
+ end
+ end
+
+ it 'will abort the migration' do
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:stale_migrations_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:aborted_stale_migrations_count, 1)
+
+ expect { subject }
+ .to change(pre_importing_migrations, :count).by(-1)
+ .and not_change(pre_import_done_migrations, :count)
+ .and not_change(importing_migrations, :count)
+ .and not_change(import_done_migrations, :count)
+ .and change(import_aborted_migrations, :count).by(1)
+ .and change { stale_migration.reload.migration_state }.from('pre_importing').to('import_aborted')
+ .and not_change { ongoing_migration.migration_state }
+ end
+
+ context 'the client returns pre_import_in_progress' do
+ let(:import_status) { 'pre_import_in_progress' }
+
+ it_behaves_like 'not aborting any migration'
+ end
+ end
+
+ context 'with pre_import_done stale migrations' do
+ let(:ongoing_migration) { create(:container_repository, :pre_import_done) }
+ let(:stale_migration) { create(:container_repository, :pre_import_done, migration_pre_import_done_at: 35.minutes.ago) }
+
+ before do
+ allow(::ContainerRegistry::Migration).to receive(:max_step_duration).and_return(5.minutes)
+ end
+
+ it 'will abort the migration' do
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:stale_migrations_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:aborted_stale_migrations_count, 1)
+
+ expect { subject }
+ .to not_change(pre_importing_migrations, :count)
+ .and change(pre_import_done_migrations, :count).by(-1)
+ .and not_change(importing_migrations, :count)
+ .and not_change(import_done_migrations, :count)
+ .and change(import_aborted_migrations, :count).by(1)
+ .and change { stale_migration.reload.migration_state }.from('pre_import_done').to('import_aborted')
+ .and not_change { ongoing_migration.migration_state }
+ end
+ end
+
+ context 'with importing stale migrations' do
+ let(:ongoing_migration) { create(:container_repository, :importing) }
+ let(:stale_migration) { create(:container_repository, :importing, migration_import_started_at: 35.minutes.ago) }
+ let(:import_status) { 'test' }
+
+ before do
+ allow_next_instance_of(ContainerRegistry::GitlabApiClient) do |client|
+ allow(client).to receive(:import_status).and_return(import_status)
+ end
+ end
+
+ it 'will abort the migration' do
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:stale_migrations_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:aborted_stale_migrations_count, 1)
+
+ expect { subject }
+ .to not_change(pre_importing_migrations, :count)
+ .and not_change(pre_import_done_migrations, :count)
+ .and change(importing_migrations, :count).by(-1)
+ .and not_change(import_done_migrations, :count)
+ .and change(import_aborted_migrations, :count).by(1)
+ .and change { stale_migration.reload.migration_state }.from('importing').to('import_aborted')
+ .and not_change { ongoing_migration.migration_state }
+ end
+
+ context 'the client returns import_in_progress' do
+ let(:import_status) { 'import_in_progress' }
+
+ it_behaves_like 'not aborting any migration'
+ end
+ end
+ end
+
+ context 'not on gitlab.com' do
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(false)
+ end
+
+ it 'is a no op' do
+ expect(::ContainerRepository).not_to receive(:with_stale_migration)
+ expect(worker).not_to receive(:log_extra_metadata_on_done)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/workers/container_registry/migration/observer_worker_spec.rb b/spec/workers/container_registry/migration/observer_worker_spec.rb
new file mode 100644
index 00000000000..fec6640d7ec
--- /dev/null
+++ b/spec/workers/container_registry/migration/observer_worker_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ContainerRegistry::Migration::ObserverWorker, :aggregate_failures do
+ let(:worker) { described_class.new }
+
+ describe '#perform' do
+ subject { worker.perform }
+
+ context 'when the migration feature flag is disabled' do
+ before do
+ stub_feature_flags(container_registry_migration_phase2_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect(worker).not_to receive(:log_extra_metadata_on_done)
+
+ subject
+ end
+ end
+
+ context 'when the migration is enabled' do
+ before do
+ create_list(:container_repository, 3)
+ create(:container_repository, :pre_importing)
+ create(:container_repository, :pre_import_done)
+ create_list(:container_repository, 2, :importing)
+ create(:container_repository, :import_aborted)
+ # batch_count is not allowed within a transaction but
+ # all rspec tests run inside of a transaction.
+ # This mocks the false positive.
+ allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false) # rubocop:disable Database/MultipleDatabases
+ end
+
+ it 'logs all the counts' do
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:default_count, 3)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:pre_importing_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:pre_import_done_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:importing_count, 2)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:import_done_count, 0)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:import_aborted_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:import_skipped_count, 0)
+
+ subject
+ end
+
+ context 'with load balancing enabled', :db_load_balancing do
+ it 'uses the replica' do
+ expect(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_replicas_for_read_queries).and_call_original
+
+ subject
+ end
+ end
+ end
+ end
+end