Commit ada11f37 authored by David Fernandez's avatar David Fernandez Committed by Luke Duncalfe

Add Container Registry migration notification APIs

Those API endpoints are meant to be used solely by the Container
Registry. As such, they are not part of the public API.

Changelog: added
parent e5435114
...@@ -96,7 +96,7 @@ class ContainerRepository < ApplicationRecord ...@@ -96,7 +96,7 @@ class ContainerRepository < ApplicationRecord
end end
event :abort_import do event :abort_import do
transition %i[pre_importing importing] => :import_aborted transition ACTIVE_MIGRATION_STATES.map(&:to_sym) => :import_aborted
end end
event :skip_import do event :skip_import do
...@@ -205,6 +205,11 @@ class ContainerRepository < ApplicationRecord ...@@ -205,6 +205,11 @@ class ContainerRepository < ApplicationRecord
super super
end end
def finish_pre_import_and_start_import
# nothing to do between those two transitions for now.
finish_pre_import && start_import
end
# rubocop: disable CodeReuse/ServiceClass # rubocop: disable CodeReuse/ServiceClass
def registry def registry
@registry ||= begin @registry ||= begin
...@@ -287,10 +292,18 @@ class ContainerRepository < ApplicationRecord ...@@ -287,10 +292,18 @@ class ContainerRepository < ApplicationRecord
update!(expiration_policy_started_at: Time.zone.now) update!(expiration_policy_started_at: Time.zone.now)
end end
def migration_in_active_state?
migration_state.in?(ACTIVE_MIGRATION_STATES)
end
def migration_importing? def migration_importing?
migration_state == 'importing' migration_state == 'importing'
end end
def migration_pre_importing?
migration_state == 'pre_importing'
end
def migration_pre_import def migration_pre_import
return :error unless gitlab_api_client.supports_gitlab_api? return :error unless gitlab_api_client.supports_gitlab_api?
......
...@@ -301,6 +301,7 @@ module API ...@@ -301,6 +301,7 @@ module API
mount ::API::Internal::Pages mount ::API::Internal::Pages
mount ::API::Internal::Kubernetes mount ::API::Internal::Kubernetes
mount ::API::Internal::MailRoom mount ::API::Internal::MailRoom
mount ::API::Internal::ContainerRegistry::Migration
version 'v3', using: :path do version 'v3', using: :path do
# Although the following endpoints are kept behind V3 namespace, # Although the following endpoints are kept behind V3 namespace,
......
...@@ -6,7 +6,7 @@ module API ...@@ -6,7 +6,7 @@ module API
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
rescue_from Faraday::Error, ContainerRegistry::Path::InvalidRegistryPathError do |e| rescue_from Faraday::Error, ::ContainerRegistry::Path::InvalidRegistryPathError do |e|
service_unavailable!('We are having trouble connecting to the Container Registry. If this error persists, please review the troubleshooting documentation.') service_unavailable!('We are having trouble connecting to the Container Registry. If this error persists, please review the troubleshooting documentation.')
end end
end end
......
# frozen_string_literal: true
module API
module Internal
module ContainerRegistry
class Migration < ::API::Base
feature_category :container_registry
STATUS_PRE_IMPORT_COMPLETE = 'pre_import_complete'
STATUS_PRE_IMPORT_FAILED = 'pre_import_failed'
STATUS_IMPORT_COMPLETE = 'import_complete'
STATUS_IMPORT_FAILED = 'import_failed'
POSSIBLE_VALUES = [
STATUS_PRE_IMPORT_COMPLETE,
STATUS_PRE_IMPORT_FAILED,
STATUS_IMPORT_COMPLETE,
STATUS_IMPORT_FAILED
].freeze
before { authenticate! }
helpers do
def authenticate!
secret_token = Gitlab.config.registry.notification_secret
unauthorized! unless Devise.secure_compare(secret_token, headers['Authorization'])
end
def find_repository!(path)
::ContainerRepository.find_by_path!(::ContainerRegistry::Path.new(path))
end
end
params do
requires :repository_path, type: String, desc: 'The container repository path'
requires :status, type: String, values: POSSIBLE_VALUES, desc: 'The migration step status'
end
put 'internal/registry/repositories/*repository_path/migration/status' do
repository = find_repository!(declared_params[:repository_path])
unless repository.migration_in_active_state?
bad_request!("Wrong migration state (#{repository.migration_state})")
end
case declared_params[:status]
when STATUS_PRE_IMPORT_COMPLETE
unless repository.finish_pre_import_and_start_import
bad_request!("Couldn't transition from pre_importing to importing")
end
when STATUS_IMPORT_COMPLETE
unless repository.finish_import
bad_request!("Couldn't transition from importing to import_done")
end
when STATUS_IMPORT_FAILED, STATUS_PRE_IMPORT_FAILED
repository.abort_import
end
status 200
end
end
end
end
end
...@@ -312,6 +312,21 @@ RSpec.describe ContainerRepository, :aggregate_failures do ...@@ -312,6 +312,21 @@ RSpec.describe ContainerRepository, :aggregate_failures do
expect { repository.skip_import }.to raise_error(ArgumentError) expect { repository.skip_import }.to raise_error(ArgumentError)
end end
end end
describe '#finish_pre_import_and_start_import' do
let_it_be_with_reload(:repository) { create(:container_repository, :pre_importing) }
subject { repository.finish_pre_import_and_start_import }
before do |example|
unless example.metadata[:skip_import_success]
allow(repository).to receive(:migration_import).and_return(:ok)
end
end
it_behaves_like 'transitioning from allowed states', %w[pre_importing]
it_behaves_like 'transitioning to importing'
end
end end
describe '#tag' do describe '#tag' do
...@@ -819,6 +834,18 @@ RSpec.describe ContainerRepository, :aggregate_failures do ...@@ -819,6 +834,18 @@ RSpec.describe ContainerRepository, :aggregate_failures do
it { is_expected.to eq([repository]) } it { is_expected.to eq([repository]) }
end end
describe '#migration_in_active_state?' do
subject { container_repository.migration_in_active_state? }
ContainerRepository::MIGRATION_STATES.each do |state|
context "when in #{state} migration_state" do
let(:container_repository) { create(:container_repository, state.to_sym)}
it { is_expected.to eq(state == 'importing' || state == 'pre_importing') }
end
end
end
describe '#migration_importing?' do describe '#migration_importing?' do
subject { container_repository.migration_importing? } subject { container_repository.migration_importing? }
...@@ -831,6 +858,18 @@ RSpec.describe ContainerRepository, :aggregate_failures do ...@@ -831,6 +858,18 @@ RSpec.describe ContainerRepository, :aggregate_failures do
end end
end end
describe '#migration_pre_importing?' do
subject { container_repository.migration_pre_importing? }
ContainerRepository::MIGRATION_STATES.each do |state|
context "when in #{state} migration_state" do
let(:container_repository) { create(:container_repository, state.to_sym)}
it { is_expected.to eq(state == 'pre_importing') }
end
end
end
context 'with repositories' do context 'with repositories' do
let_it_be_with_reload(:repository) { create(:container_repository, :cleanup_unscheduled) } let_it_be_with_reload(:repository) { create(:container_repository, :cleanup_unscheduled) }
let_it_be(:other_repository) { create(:container_repository, :cleanup_unscheduled) } let_it_be(:other_repository) { create(:container_repository, :cleanup_unscheduled) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Internal::ContainerRegistry::Migration do
let_it_be_with_reload(:repository) { create(:container_repository) }
let(:secret_token) { 'secret_token' }
let(:sent_token) { secret_token }
let(:repository_path) { repository.path }
let(:status) { 'pre_import_complete' }
let(:params) { { path: repository.path, status: status } }
before do
allow(Gitlab.config.registry).to receive(:notification_secret) { secret_token }
end
describe 'PUT /internal/registry/repositories/:path/migration/status' do
subject do
put api("/internal/registry/repositories/#{repository_path}/migration/status"),
params: params,
headers: { 'Authorization' => sent_token }
end
shared_examples 'returning an error' do |with_message: nil, returning_status: :bad_request|
it "returns bad request response" do
expect { subject }
.not_to change { repository.reload.migration_state }
expect(response).to have_gitlab_http_status(returning_status)
expect(response.body).to include(with_message) if with_message
end
end
context 'with a valid sent token' do
shared_examples 'updating the repository migration status' do |from:, to:|
it "updates the migration status from #{from} to #{to}" do
expect { subject }
.to change { repository.reload.migration_state }.from(from).to(to)
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'with status pre_import_complete' do
let(:status) { 'pre_import_complete' }
it_behaves_like 'returning an error', with_message: 'Wrong migration state (default)'
context 'with repository in pre_importing migration state' do
let(:repository) { create(:container_repository, :pre_importing) }
before do
allow_next_found_instance_of(ContainerRepository) do |found_repository|
allow(found_repository).to receive(:migration_import).and_return(:ok)
end
end
it_behaves_like 'updating the repository migration status', from: 'pre_importing', to: 'importing'
context 'with a failing transition' do
before do
allow_next_found_instance_of(ContainerRepository) do |found_repository|
allow(found_repository).to receive(:finish_pre_import_and_start_import).and_return(false)
end
end
it_behaves_like 'returning an error', with_message: "Couldn't transition from pre_importing to importing"
end
end
context 'with repository in importing migration state' do
let(:repository) { create(:container_repository, :importing) }
it_behaves_like 'returning an error', with_message: "Couldn't transition from pre_importing to importing"
end
end
context 'with status import_complete' do
let(:status) { 'import_complete' }
it_behaves_like 'returning an error', with_message: 'Wrong migration state (default)'
context 'with repository in importing migration state' do
let(:repository) { create(:container_repository, :importing) }
let(:transition_result) { true }
it_behaves_like 'updating the repository migration status', from: 'importing', to: 'import_done'
context 'with a failing transition' do
before do
allow_next_found_instance_of(ContainerRepository) do |found_repository|
allow(found_repository).to receive(:finish_import).and_return(false)
end
end
it_behaves_like 'returning an error', with_message: "Couldn't transition from importing to import_done"
end
end
context 'with repository in pre_importing migration state' do
let(:repository) { create(:container_repository, :pre_importing) }
it_behaves_like 'returning an error', with_message: "Couldn't transition from importing to import_done"
end
end
%w[pre_import_failed import_failed].each do |status|
context 'with status pre_import_failed' do
let(:status) { 'pre_import_failed' }
it_behaves_like 'returning an error', with_message: 'Wrong migration state (default)'
context 'with repository in importing migration state' do
let(:repository) { create(:container_repository, :importing) }
it_behaves_like 'updating the repository migration status', from: 'importing', to: 'import_aborted'
end
context 'with repository in pre_importing migration state' do
let(:repository) { create(:container_repository, :pre_importing) }
it_behaves_like 'updating the repository migration status', from: 'pre_importing', to: 'import_aborted'
end
end
end
context 'with a non existing path' do
let(:repository_path) { 'this/does/not/exist' }
it_behaves_like 'returning an error', returning_status: :not_found
end
context 'with invalid status' do
let(:params) { super().merge(status: nil).compact }
it_behaves_like 'returning an error', returning_status: :bad_request
end
context 'with invalid path' do
let(:repository_path) { nil }
it_behaves_like 'returning an error', returning_status: :not_found
end
end
context 'with an invalid sent token' do
let(:sent_token) { 'not_valid' }
it_behaves_like 'returning an error', returning_status: :unauthorized
end
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