Commit 418e8f81 authored by James Lopez's avatar James Lopez

Merge branch '118753-debounce-pull-mirror' into 'master'

Debounce pull mirror invocation

See merge request gitlab-org/gitlab!30157
parents f8bac6c6 79052fc8
...@@ -41,7 +41,7 @@ module EE ...@@ -41,7 +41,7 @@ module EE
project.update_remote_mirrors project.update_remote_mirrors
flash[:notice] = _('The remote repository is being updated...') flash[:notice] = _('The remote repository is being updated...')
else else
project.import_state.force_import_job! StartPullMirroringService.new(project, current_user, pause_on_hard_failure: false).execute
flash[:notice] = _('The repository is being updated...') flash[:notice] = _('The repository is being updated...')
end end
......
# frozen_string_literal: true # frozen_string_literal: true
class StartPullMirroringService < BaseService class StartPullMirroringService < BaseService
INTERVAL = 5.minutes
def execute def execute
return error('Mirroring for the project is on pause', 403) if project.import_state.hard_failed? import_state = project.import_state
if import_state.hard_failed?
return error('Mirroring for the project is on pause', 403) if params[:pause_on_hard_failure]
import_state.reset_retry_count
end
if update_now?(import_state)
import_state.force_import_job!
else
import_state.update(next_execution_timestamp: INTERVAL.since(import_state.last_update_at))
end
project.import_state.force_import_job!
success success
end end
private
def update_now?(import_state)
import_state.last_update_at.nil? || import_state.last_update_at < INTERVAL.ago
end
end end
---
title: Debounce pull mirror invocation
merge_request: 30157
author:
type: performance
...@@ -59,7 +59,7 @@ module API ...@@ -59,7 +59,7 @@ module API
end end
def start_pull_mirroring def start_pull_mirroring
result = StartPullMirroringService.new(project, mirror_user).execute result = StartPullMirroringService.new(project, mirror_user, pause_on_hard_failure: true).execute
render_api_error!(result[:message], result[:http_status]) if result[:status] == :error render_api_error!(result[:message], result[:http_status]) if result[:status] == :error
end end
......
...@@ -122,9 +122,11 @@ describe Projects::MirrorsController do ...@@ -122,9 +122,11 @@ describe Projects::MirrorsController do
project = create(:project, :mirror) project = create(:project, :mirror)
sign_in(project.owner) sign_in(project.owner)
expect_any_instance_of(EE::ProjectImportState).to receive(:force_import_job!) Sidekiq::Testing.fake! do
expect { put :update_now, params: { namespace_id: project.namespace.to_param, project_id: project.to_param } }
put :update_now, params: { namespace_id: project.namespace.to_param, project_id: project.to_param } .to change { UpdateAllMirrorsWorker.jobs.size }
.by(1)
end
end end
end end
......
...@@ -19,18 +19,20 @@ describe 'Project mirror', :js do ...@@ -19,18 +19,20 @@ describe 'Project mirror', :js do
let(:timestamp) { Time.now } let(:timestamp) { Time.now }
before do before do
import_state.update(next_execution_timestamp: timestamp + 10.minutes) import_state.update(last_update_at: 6.minutes.ago, next_execution_timestamp: timestamp + 10.minutes)
end end
context 'when able to force update' do context 'when able to force update' do
it 'forces import' do it 'forces import' do
expect_any_instance_of(EE::ProjectImportState).to receive(:force_import_job!)
Timecop.freeze(timestamp) do Timecop.freeze(timestamp) do
visit project_mirror_path(project) visit project_mirror_path(project)
end end
Sidekiq::Testing.fake! { find('.js-force-update-mirror').click } Sidekiq::Testing.fake! do
expect { find('.js-force-update-mirror').click }
.to change { UpdateAllMirrorsWorker.jobs.size }
.by(1)
end
end end
end end
......
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
describe API::ProjectMirror do describe API::ProjectMirror do
describe 'POST /projects/:id/mirror/pull' do describe 'POST /projects/:id/mirror/pull' do
let(:visibility) { Gitlab::VisibilityLevel::PUBLIC } let(:visibility) { Gitlab::VisibilityLevel::PUBLIC }
let(:project_mirrored) { create(:project, :repository, :mirror, :import_finished, visibility: visibility) } let(:project_mirrored) { create(:project, :repository, :mirror, visibility: visibility) }
def do_post(user: nil, params: {}, headers: { 'X-Hub-Signature' => 'signature' }) def do_post(user: nil, params: {}, headers: { 'X-Hub-Signature' => 'signature' })
api_path = api("/projects/#{project_mirrored.id}/mirror/pull", user) api_path = api("/projects/#{project_mirrored.id}/mirror/pull", user)
...@@ -267,30 +267,28 @@ describe API::ProjectMirror do ...@@ -267,30 +267,28 @@ describe API::ProjectMirror do
it 'triggers the pull mirroring operation' do it 'triggers the pull mirroring operation' do
project_member(:maintainer, user) project_member(:maintainer, user)
expect(StartPullMirroringService) Sidekiq::Testing.fake! do
.to receive(:new) expect { do_post(user: user, headers: {}) }
.with(project_mirrored, user) .to change { UpdateAllMirrorsWorker.jobs.size }
.and_call_original .by(1)
do_post(user: user, headers: {})
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
end end
end
context 'is authenticated as owner' do context 'is authenticated as owner' do
it 'triggers the pull mirroring operation' do it 'triggers the pull mirroring operation' do
expect(StartPullMirroringService) Sidekiq::Testing.fake! do
.to receive(:new) expect { do_post(user: project_mirrored.creator, headers: {}) }
.with(project_mirrored, project_mirrored.creator) .to change { UpdateAllMirrorsWorker.jobs.size }
.and_call_original .by(1)
do_post(user: project_mirrored.creator, headers: {})
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
end end
end end
end
context 'when repository_mirrors feature is not available' do context 'when repository_mirrors feature is not available' do
before do before do
......
...@@ -7,16 +7,88 @@ describe StartPullMirroringService do ...@@ -7,16 +7,88 @@ describe StartPullMirroringService do
let(:import_state) { create(:import_state, project: project) } let(:import_state) { create(:import_state, project: project) }
let(:user) { create(:user) } let(:user) { create(:user) }
subject { described_class.new(project, user) } subject { described_class.new(project, user, pause_on_hard_failure: pause_on_hard_failure) }
shared_examples_for 'retry count did not reset' do
it 'does not reset import state retry_count' do
expect { execute }.not_to change { import_state.retry_count }
end
end
shared_examples_for 'pull mirroring has started' do
shared_examples_for 'force mirror update' do
it 'enqueues UpdateAllMirrorsWorker' do
Sidekiq::Testing.fake! do
expect { execute }
.to change { UpdateAllMirrorsWorker.jobs.size }
.by(1)
expect(execute[:status]).to eq(:success)
end
end
end
it_behaves_like 'force mirror update'
context 'when project mirror has been updated in the last 5 minutes' do
it 'schedules next execution' do
Timecop.freeze(Time.current) do
import_state.update(last_update_at: 3.minutes.ago)
expect { execute }
.to change { import_state.next_execution_timestamp }
.to(2.minutes.from_now)
.and not_change { UpdateAllMirrorsWorker.jobs.size }
end
end
end
context 'when project mirror has been updated more than 5 minutes ago' do
before do
import_state.update(last_update_at: 6.minutes.ago)
end
it_behaves_like 'force mirror update'
end
end
context 'when pause_on_hard_failure is false' do
let(:pause_on_hard_failure) { false }
context "when retried more than #{Gitlab::Mirror::MAX_RETRY} times" do context "when retried more than #{Gitlab::Mirror::MAX_RETRY} times" do
before do before do
import_state.update(retry_count: Gitlab::Mirror::MAX_RETRY + 1) import_state.update(retry_count: Gitlab::Mirror::MAX_RETRY + 1)
end end
it_behaves_like 'pull mirroring has started'
it 'resets the import state retry_count' do
expect { execute }.to change { import_state.retry_count }.to(0)
end
end
context 'when does not reach the max retry limit yet' do
before do
import_state.update(retry_count: Gitlab::Mirror::MAX_RETRY - 1)
end
it_behaves_like 'pull mirroring has started'
it_behaves_like 'retry count did not reset'
end
end
context 'when pause_on_hard_failure is true' do
let(:pause_on_hard_failure) { true }
context "when retried more than #{Gitlab::Mirror::MAX_RETRY} times" do
before do
import_state.update(retry_count: Gitlab::Mirror::MAX_RETRY + 1)
end
it_behaves_like 'retry count did not reset'
it 'does not start pull mirroring' do it 'does not start pull mirroring' do
expect(UpdateAllMirrorsWorker).not_to receive(:perform_async) expect { execute }.to not_change { UpdateAllMirrorsWorker.jobs.size }
expect(subject.execute[:status]).to eq(:error) expect(execute[:status]).to eq(:error)
end end
end end
...@@ -25,10 +97,12 @@ describe StartPullMirroringService do ...@@ -25,10 +97,12 @@ describe StartPullMirroringService do
import_state.update(retry_count: Gitlab::Mirror::MAX_RETRY - 1) import_state.update(retry_count: Gitlab::Mirror::MAX_RETRY - 1)
end end
it 'starts pull mirroring' do it_behaves_like 'pull mirroring has started'
expect(UpdateAllMirrorsWorker).to receive(:perform_async).once it_behaves_like 'retry count did not reset'
expect(import_state.reload.retry_count).not_to eq(0)
expect(subject.execute[:status]).to eq(:success)
end end
end end
def execute
subject.execute
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