Commit 15ebe4d9 authored by Tiago Botelho's avatar Tiago Botelho

[EE] Removes all the irrelevant import related code and columns

Clears the import related columns and code from the Project
model over to the ProjectImportState model
parent 9197f53c
...@@ -39,7 +39,7 @@ module EE ...@@ -39,7 +39,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.force_import_job! project.import_state.force_import_job!
flash[:notice] = "The repository is being updated..." flash[:notice] = "The repository is being updated..."
end end
......
module MirrorHelper module MirrorHelper
def render_mirror_failed_message(raw_message:) def render_mirror_failed_message(raw_message:)
mirror_last_update_at = @project.mirror_last_update_at mirror_last_update_at = @project.import_state.last_update_at
message = "The repository failed to update #{time_ago_with_tooltip(mirror_last_update_at)}.".html_safe message = "The repository failed to update #{time_ago_with_tooltip(mirror_last_update_at)}.".html_safe
return message if raw_message return message if raw_message
......
...@@ -14,6 +14,10 @@ module EE ...@@ -14,6 +14,10 @@ module EE
include EE::DeploymentPlatform include EE::DeploymentPlatform
include EachBatch include EachBatch
ignore_column :mirror_last_update_at,
:mirror_last_successful_update_at,
:next_execution_timestamp
before_save :set_override_pull_mirror_available, unless: -> { ::Gitlab::CurrentSettings.mirror_available } before_save :set_override_pull_mirror_available, unless: -> { ::Gitlab::CurrentSettings.mirror_available }
before_save :set_next_execution_timestamp_to_now, if: ->(project) { project.mirror? && project.mirror_changed? && project.import_state } before_save :set_next_execution_timestamp_to_now, if: ->(project) { project.mirror? && project.mirror_changed? && project.import_state }
...@@ -59,11 +63,9 @@ module EE ...@@ -59,11 +63,9 @@ module EE
scope :mirror, -> { where(mirror: true) } scope :mirror, -> { where(mirror: true) }
scope :inner_joins_import_state, -> { joins("INNER JOIN project_mirror_data import_state ON import_state.project_id = projects.id") }
scope :mirrors_to_sync, ->(freeze_at) do scope :mirrors_to_sync, ->(freeze_at) do
mirror mirror
.inner_joins_import_state .joins_import_state
.where.not(import_state: { status: [:scheduled, :started] }) .where.not(import_state: { status: [:scheduled, :started] })
.where("import_state.next_execution_timestamp <= ?", freeze_at) .where("import_state.next_execution_timestamp <= ?", freeze_at)
.where("import_state.retry_count <= ?", ::Gitlab::Mirror::MAX_RETRY) .where("import_state.retry_count <= ?", ::Gitlab::Mirror::MAX_RETRY)
...@@ -83,6 +85,10 @@ module EE ...@@ -83,6 +85,10 @@ module EE
delegate :actual_shared_runners_minutes_limit, delegate :actual_shared_runners_minutes_limit,
:shared_runners_minutes_used?, to: :shared_runners_limit_namespace :shared_runners_minutes_used?, to: :shared_runners_limit_namespace
delegate :last_update_succeeded?, :last_update_failed?,
:ever_updated_successfully?, :hard_failed?,
to: :import_state, prefix: :mirror, allow_nil: true
validates :repository_size_limit, validates :repository_size_limit,
numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true } numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true }
...@@ -144,101 +150,10 @@ module EE ...@@ -144,101 +150,10 @@ module EE
end end
alias_method :mirror?, :mirror alias_method :mirror?, :mirror
def mirror_updated?
mirror? && self.mirror_last_update_at
end
def mirror_waiting_duration
return unless mirror?
(import_state.last_update_started_at.to_i -
import_state.last_update_scheduled_at.to_i).seconds
end
def mirror_update_duration
return unless mirror?
(mirror_last_update_at.to_i -
import_state.last_update_started_at.to_i).seconds
end
def mirror_with_content? def mirror_with_content?
mirror? && !empty_repo? mirror? && !empty_repo?
end end
def import_state_args
super.merge(last_update_at: self[:mirror_last_update_at],
last_successful_update_at: self[:mirror_last_successful_update_at])
end
def mirror_last_update_at=(new_value)
ensure_import_state
import_state&.last_update_at = new_value
end
def mirror_last_update_at
ensure_import_state
import_state&.last_update_at
end
def mirror_last_successful_update_at=(new_value)
ensure_import_state
import_state&.last_successful_update_at = new_value
end
def mirror_last_successful_update_at
ensure_import_state
import_state&.last_successful_update_at
end
override :import_in_progress?
def import_in_progress?
# If we're importing while we do have a repository, we're simply updating the mirror.
super && !mirror_with_content?
end
def mirror_about_to_update?
return false unless mirror_with_content?
return false if mirror_hard_failed?
return false if updating_mirror?
self.import_state.next_execution_timestamp <= Time.now
end
def updating_mirror?
(import_scheduled? || import_started?) && mirror_with_content?
end
def mirror_last_update_status
return unless mirror_updated?
if self.mirror_last_update_at == self.mirror_last_successful_update_at
:success
else
:failed
end
end
def mirror_last_update_succeeded?
mirror_last_update_status == :success
end
def mirror_last_update_failed?
mirror_last_update_status == :failed
end
def mirror_ever_updated_successfully?
mirror_updated? && self.mirror_last_successful_update_at
end
def mirror_hard_failed?
self.import_state.retry_limit_exceeded?
end
def fetch_mirror def fetch_mirror
return unless mirror? return unless mirror?
...@@ -293,19 +208,6 @@ module EE ...@@ -293,19 +208,6 @@ module EE
config.address&.gsub(wildcard, full_path) config.address&.gsub(wildcard, full_path)
end end
def force_import_job!
return if mirror_about_to_update? || updating_mirror?
import_state = self.import_state
import_state.set_next_execution_to_now
import_state.reset_retry_count if import_state.retry_limit_exceeded?
import_state.save!
UpdateAllMirrorsWorker.perform_async
end
override :add_import_job override :add_import_job
def add_import_job def add_import_job
return if gitlab_custom_project_template_import? return if gitlab_custom_project_template_import?
......
...@@ -72,6 +72,58 @@ module EE ...@@ -72,6 +72,58 @@ module EE
end end
end end
override :in_progress?
def in_progress?
# If we're importing while we do have a repository, we're simply updating the mirror.
super && !project.mirror_with_content?
end
def mirror_waiting_duration
return unless mirror?
(last_update_started_at.to_i - last_update_scheduled_at.to_i).seconds
end
def mirror_update_duration
return unless mirror?
(last_update_at.to_i - last_update_started_at.to_i).seconds
end
def updating_mirror?
(scheduled? || started?) && project.mirror_with_content?
end
def mirror_update_due?
return false unless project.mirror_with_content?
return false if hard_failed?
return false if updating_mirror?
next_execution_timestamp <= Time.now
end
def last_update_status
return unless state_updated?
if last_update_at == last_successful_update_at
:success
else
:failed
end
end
def last_update_succeeded?
last_update_status == :success
end
def last_update_failed?
last_update_status == :failed
end
def ever_updated_successfully?
state_updated? && last_successful_update_at
end
def reset_retry_count def reset_retry_count
self.retry_count = 0 self.retry_count = 0
end end
...@@ -91,8 +143,19 @@ module EE ...@@ -91,8 +143,19 @@ module EE
self.next_execution_timestamp = timestamp + delay self.next_execution_timestamp = timestamp + delay
end end
def force_import_job!
return if mirror_update_due? || updating_mirror?
set_next_execution_to_now
reset_retry_count if hard_failed?
save!
UpdateAllMirrorsWorker.perform_async
end
def set_next_execution_to_now def set_next_execution_to_now
return unless project.mirror? return unless mirror?
self.next_execution_timestamp = Time.now self.next_execution_timestamp = Time.now
end end
...@@ -100,9 +163,14 @@ module EE ...@@ -100,9 +163,14 @@ module EE
def retry_limit_exceeded? def retry_limit_exceeded?
self.retry_count > ::Gitlab::Mirror::MAX_RETRY self.retry_count > ::Gitlab::Mirror::MAX_RETRY
end end
alias_method :hard_failed?, :retry_limit_exceeded?
private private
def state_updated?
mirror? && last_update_at
end
def base_delay(timestamp) def base_delay(timestamp)
return 0 unless self.last_update_started_at return 0 unless self.last_update_started_at
......
...@@ -34,7 +34,7 @@ module EE ...@@ -34,7 +34,7 @@ module EE
log_audit_events log_audit_events
sync_wiki_on_enable if !wiki_was_enabled && project.wiki_enabled? sync_wiki_on_enable if !wiki_was_enabled && project.wiki_enabled?
project.force_import_job! if params[:mirror].present? && project.mirror? project.import_state.force_import_job! if params[:mirror].present? && project.mirror?
end end
result result
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Repository mirroring on #{@project.full_path} has been paused due to too many failures. The last failure was: Repository mirroring on #{@project.full_path} has been paused due to too many failures. The last failure was:
%pre %pre
= @project.import_error = @project.import_state.last_error
%p %p
To resume mirroring update your #{link_to("repository mirroring settings", project_settings_repository_url(@project))}. To resume mirroring update your #{link_to("repository mirroring settings", project_settings_repository_url(@project))}.
Repository mirroring on <%= @project.full_path %> has been paused due to too many failures. The last failure was: Repository mirroring on <%= @project.full_path %> has been paused due to too many failures. The last failure was:
<%= @project.import_error %> <%= @project.import_state.last_error %>
To resume mirroring update your repository settings at <%= project_settings_repository_url(@project) %>. To resume mirroring update your repository settings at <%= project_settings_repository_url(@project) %>.
- if @project.mirror - return unless @project.mirror
%tr
%td= @project.username_only_import_url - import_state = @project.import_state
%td= _('Pull')
%td= @project.mirror_last_update_at.present? ? time_ago_with_tooltip(@project.mirror_last_update_at) : _('Never') %tr
%td %td= @project.username_only_import_url
- if @project.import_error.present? %td= _('Pull')
.badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(@project.import_error.try(:strip)) }= _('Error') %td= import_state.last_update_at.present? ? time_ago_with_tooltip(import_state.last_update_at) : _('Never')
%td.mirror-action-buttons %td
.btn-group.mirror-actions-group.pull-right{ role: 'group' } - if import_state&.last_error.present?
- if @project.mirror? .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(import_state.last_error.try(:strip)) }= _('Error')
- if @project.mirror_about_to_update? || @project.updating_mirror? %td.mirror-action-buttons
%button.btn.disabled{ type: 'button', data: { container: 'body', toggle: 'tooltip' }, title: _('Updating') }= icon("refresh spin") .btn-group.mirror-actions-group.pull-right{ role: 'group' }
- else - if @project.mirror?
= link_to update_now_project_mirror_path(@project), method: :post, class: 'btn js-force-update-mirror', data: { container: 'body', toggle: 'tooltip' }, title: _('Update now') do - if import_state.mirror_update_due? || import_state.updating_mirror?
= icon("refresh") %button.btn.disabled{ type: 'button', data: { container: 'body', toggle: 'tooltip' }, title: _('Updating') }= icon("refresh spin")
%button.js-delete-mirror.js-delete-pull-mirror.btn.btn-danger{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o') - else
= link_to update_now_project_mirror_path(@project), method: :post, class: 'btn js-force-update-mirror', data: { container: 'body', toggle: 'tooltip' }, title: _('Update now') do
= icon("refresh")
%button.js-delete-mirror.js-delete-pull-mirror.btn.btn-danger{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
.card-body .card-body
%pre %pre
:preserve :preserve
#{h(@project.import_error.try(:strip))} #{h(@project.import_state.last_error.try(:strip))}
- last_successful_update_at = @project.mirror_last_successful_update_at - last_successful_update_at = @project.import_state.last_successful_update_at
- raw_message = local_assigns.fetch(:raw_message, false) - raw_message = local_assigns.fetch(:raw_message, false)
- case @project.mirror_last_update_status - case @project.import_state.last_update_status
- when :success - when :success
Updated #{time_ago_with_tooltip(last_successful_update_at)}. Updated #{time_ago_with_tooltip(last_successful_update_at)}.
- when :failed - when :failed
......
- if @project.mirror? && can?(current_user, :push_code, @project) - if @project.mirror? && can?(current_user, :push_code, @project)
.append-bottom-default .append-bottom-default
- if @project.mirror_about_to_update? - if @project.import_state.mirror_update_due?
%span.btn.disabled %span.btn.disabled
= icon("refresh spin") = icon("refresh spin")
Update Scheduled&hellip; Update Scheduled&hellip;
- elsif @project.updating_mirror? - elsif @project.import_state.updating_mirror?
%span.btn.disabled %span.btn.disabled
= icon("refresh spin") = icon("refresh spin")
Updating&hellip; Updating&hellip;
...@@ -14,4 +14,4 @@ ...@@ -14,4 +14,4 @@
Update Now Update Now
- if @project.mirror_last_update_succeeded? - if @project.mirror_last_update_succeeded?
%p.inline.prepend-left-10 %p.inline.prepend-left-10
Successfully updated #{time_ago_with_tooltip(@project.mirror_last_successful_update_at)}. Successfully updated #{time_ago_with_tooltip(@project.import_state.last_successful_update_at)}.
...@@ -8,7 +8,7 @@ module EE ...@@ -8,7 +8,7 @@ module EE
# Explicitly enqueue mirror for update so # Explicitly enqueue mirror for update so
# that upstream remote is created and fetched # that upstream remote is created and fetched
project.force_import_job! if project.mirror? project.import_state.force_import_job! if project.mirror?
end end
override :template_import? override :template_import?
......
class ProjectImportScheduleWorker class ProjectImportScheduleWorker
ImportStateNotFound = Class.new(StandardError)
include ApplicationWorker include ApplicationWorker
prepend WaitableWorker prepend WaitableWorker
sidekiq_options retry: false
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(project_id) def perform(project_id)
project = Project.find_by(id: project_id) import_state = ProjectImportState.find_by(project_id: project_id)
project&.import_schedule raise ImportStateNotFound unless import_state
import_state.schedule
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
...@@ -45,38 +45,31 @@ class RepositoryUpdateMirrorWorker ...@@ -45,38 +45,31 @@ class RepositoryUpdateMirrorWorker
end end
def start_mirror(project) def start_mirror(project)
if start(project) import_state = project.import_state
Rails.logger.info("Mirror update for #{project.full_path} started. Waiting duration: #{project.mirror_waiting_duration}")
metric_mirror_waiting_duration_seconds.observe({}, project.mirror_waiting_duration)
Gitlab::Metrics.add_event_with_values( if start(import_state)
:mirrors_running, Rails.logger.info("Mirror update for #{project.full_path} started. Waiting duration: #{import_state.mirror_waiting_duration}")
{ duration: project.mirror_waiting_duration }, metric_mirror_waiting_duration_seconds.observe({}, import_state.mirror_waiting_duration)
{ path: project.full_path })
true true
else else
Rails.logger.info("Project #{project.full_path} was in inconsistent state: #{project.import_status}") Rails.logger.info("Project #{project.full_path} was in inconsistent state: #{import_state.status}")
false false
end end
end end
def fail_mirror(project, message) def fail_mirror(project, message)
project.mark_import_as_failed(message) project.import_state.mark_as_failed(message)
Rails.logger.error("Mirror update for #{project.full_path} failed with the following message: #{message}") Rails.logger.error("Mirror update for #{project.full_path} failed with the following message: #{message}")
Gitlab::Metrics.add_event(:mirrors_failed)
end end
def finish_mirror(project) def finish_mirror(project)
project.import_finish import_state = project.import_state
import_state.finish
Rails.logger.info("Mirror update for #{project.full_path} successfully finished. Update duration: #{project.mirror_update_duration}}.") Rails.logger.info("Mirror update for #{project.full_path} successfully finished. Update duration: #{import_state.mirror_update_duration}}.")
Gitlab::Metrics.add_event_with_values( metric_mirror_update_duration_seconds.observe({}, import_state.mirror_update_duration)
:mirrors_finished,
{ duration: project.mirror_update_duration })
metric_mirror_update_duration_seconds.observe({}, project.mirror_update_duration)
end end
def metric_mirror_update_duration_seconds def metric_mirror_update_duration_seconds
......
...@@ -51,7 +51,7 @@ module API ...@@ -51,7 +51,7 @@ module API
break render_api_error!('The project is not mirrored', 400) unless project.mirror? break render_api_error!('The project is not mirrored', 400) unless project.mirror?
project.force_import_job! project.import_state.force_import_job!
status 200 status 200
end end
......
...@@ -120,7 +120,7 @@ describe Projects::MirrorsController do ...@@ -120,7 +120,7 @@ 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::Project).to receive(:force_import_job!) expect_any_instance_of(EE::ProjectImportState).to receive(:force_import_job!)
put :update_now, { namespace_id: project.namespace.to_param, project_id: project.to_param } put :update_now, { namespace_id: project.namespace.to_param, project_id: project.to_param }
end end
......
...@@ -192,7 +192,7 @@ describe ProjectsController do ...@@ -192,7 +192,7 @@ describe ProjectsController do
end end
it 'updates repository mirror attributes' do it 'updates repository mirror attributes' do
expect_any_instance_of(EE::Project).to receive(:force_import_job!).once expect_any_instance_of(EE::ProjectImportState).to receive(:force_import_job!).once
put :update, put :update,
namespace_id: project.namespace, namespace_id: project.namespace,
......
...@@ -24,7 +24,7 @@ describe 'Project mirror', :js do ...@@ -24,7 +24,7 @@ describe 'Project mirror', :js do
it 'forces import' do it 'forces import' do
import_state.update(last_update_at: timestamp - 8.minutes) import_state.update(last_update_at: timestamp - 8.minutes)
expect_any_instance_of(EE::Project).to receive(:force_import_job!) 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)
...@@ -38,7 +38,7 @@ describe 'Project mirror', :js do ...@@ -38,7 +38,7 @@ describe 'Project mirror', :js do
it 'does not force import' do it 'does not force import' do
import_state.update(last_update_at: timestamp - 3.minutes) import_state.update(last_update_at: timestamp - 3.minutes)
expect_any_instance_of(EE::Project).not_to receive(:force_import_job!) expect_any_instance_of(EE::ProjectImportState).not_to receive(:force_import_job!)
Timecop.freeze(timestamp) do Timecop.freeze(timestamp) do
visit project_mirror_path(project) visit project_mirror_path(project)
......
require 'rails_helper' require 'rails_helper'
describe ProjectImportState, type: :model do describe ProjectImportState, type: :model do
describe 'Project import job' do
let(:project) { import_state.project }
before do
allow_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository)
.with(project.import_url).and_return(true)
# Works around https://github.com/rspec/rspec-mocks/issues/910
allow(Project).to receive(:find).with(project.id).and_return(project)
expect(project.repository).to receive(:after_import).and_call_original
expect(project.wiki.repository).to receive(:after_import).and_call_original
end
context 'with a mirrored project' do
let(:import_state) { create(:import_state, :mirror) }
it 'calls RepositoryImportWorker and inserts in front of the mirror scheduler queue' do
allow_any_instance_of(EE::Project).to receive(:repository_exists?).and_return(false, true)
expect_any_instance_of(EE::ProjectImportState).to receive(:force_import_job!)
expect(RepositoryImportWorker).to receive(:perform_async).with(import_state.project_id).and_call_original
expect { import_state.schedule }.to change { import_state.jid }
end
end
end
describe 'transitions' do
context 'state transition: [:started] => [:finished]' do
context 'elasticsearch indexing disabled' do
before do
stub_ee_application_setting(elasticsearch_indexing: false)
end
it 'does not index the repository' do
import_state = create(:import_state, :started, import_type: :github)
expect(ElasticCommitIndexerWorker).not_to receive(:perform_async)
import_state.finish
end
end
context 'elasticsearch indexing enabled' do
let(:import_state) { create(:import_state, :started, import_type: :github) }
before do
stub_ee_application_setting(elasticsearch_indexing: true)
end
context 'no index status' do
it 'schedules a full index of the repository' do
expect(ElasticCommitIndexerWorker).to receive(:perform_async).with(import_state.project_id, nil)
import_state.finish
end
end
context 'with index status' do
let!(:index_status) { import_state.project.create_index_status!(indexed_at: Time.now, last_commit: 'foo') }
it 'schedules a progressive index of the repository' do
expect(ElasticCommitIndexerWorker).to receive(:perform_async).with(import_state.project_id, index_status.last_commit)
import_state.finish
end
end
end
end
end
describe 'when create' do describe 'when create' do
it 'sets next execution timestamp to now' do it 'sets next execution timestamp to now' do
Timecop.freeze(Time.now) do Timecop.freeze(Time.now) do
...@@ -11,19 +81,251 @@ describe ProjectImportState, type: :model do ...@@ -11,19 +81,251 @@ describe ProjectImportState, type: :model do
end end
end end
describe '#reset_retry_count' do describe '#in_progress?' do
let(:import_state) { create(:import_state, :mirror, :finished, retry_count: 3) } let(:traits) { [] }
let(:import_state) { create(:import_state, *traits, import_url: Project::UNKNOWN_IMPORT_URL) }
it 'resets retry_count to 0' do shared_examples 'import in progress' do |status|
expect { import_state.reset_retry_count }.to change { import_state.retry_count }.from(3).to(0) context 'when project is not a mirror and repository is empty' do
let(:traits) { [status] }
it 'returns true' do
expect(import_state.in_progress?).to be_truthy
end
end
context 'when project is a mirror' do
let(:traits) { [status, :mirror] }
context 'when repository is empty' do
it 'returns true' do
expect(import_state.in_progress?).to be_truthy
end
end
end
context 'when repository is not empty' do
let(:traits) { [status, :repository] }
it 'returns true' do
expect(import_state.in_progress?).to be_truthy
end
end
context 'when project is a mirror and repository is not empty' do
let(:traits) { [status, :mirror, :repository] }
it 'returns false' do
expect(import_state.in_progress?).to be_falsey
end
end
end
context 'when import status is scheduled' do
it_behaves_like 'import in progress', :scheduled
end
context 'when import status is started' do
it_behaves_like 'import in progress', :started
end
context 'when import status is finished' do
let(:traits) { [:finished] }
it 'returns false' do
expect(import_state.in_progress?).to be_falsey
end
end end
end end
describe '#increment_retry_count' do describe 'hard failing a mirror' do
let(:import_state) { create(:import_state, :mirror, :finished) } it 'sends a notification' do
import_state = create(:import_state, :mirror, :started, retry_count: Gitlab::Mirror::MAX_RETRY)
it 'increments retry_count' do expect_any_instance_of(EE::NotificationService).to receive(:mirror_was_hard_failed).with(import_state.project)
expect { import_state.increment_retry_count }.to change { import_state.retry_count }.from(0).to(1)
import_state.fail_op
end
end
describe '#mirror_waiting_duration' do
it 'returns nil if not mirror' do
import_state = create(:import_state, :scheduled)
expect(import_state.mirror_waiting_duration).to be_nil
end
it 'returns in seconds the time spent in the queue' do
import_state = create(:import_state, :scheduled, :mirror)
import_state.last_update_started_at = import_state.last_update_scheduled_at + 5.minutes
expect(import_state.mirror_waiting_duration).to eq(300)
end
end
describe '#mirror_update_duration' do
it 'returns nil if not mirror' do
import_state = create(:import_state, :started)
expect(import_state.mirror_update_duration).to be_nil
end
it 'returns in seconds the time spent updating' do
import_state = create(:import_state, :started, :mirror)
import_state.last_update_at = import_state.last_update_started_at + 5.minutes
expect(import_state.mirror_update_duration).to eq(300)
end
end
describe '#updating_mirror?' do
shared_examples 'updating mirror' do |status|
context 'with repository' do
it 'returns false' do
import_state = create(:import_state, status, :repository)
expect(import_state.updating_mirror?).to be_falsey
end
end
context 'with mirror' do
it 'returns false' do
import_state = create(:import_state, status, :mirror)
expect(import_state.updating_mirror?).to be_falsey
end
end
context 'with mirror and repository' do
it 'returns false' do
import_state = create(:import_state, status, :mirror, :repository)
expect(import_state.updating_mirror?).to be_truthy
end
end
end
context 'when scheduled' do
it_behaves_like 'updating mirror', :scheduled
end
context 'when started' do
it_behaves_like 'updating mirror', :started
end
end
describe '#mirror_update_due?' do
context 'when mirror is expected to run soon' do
it 'returns true' do
import_state = create(:import_state,
:finished,
:mirror,
:repository,
next_execution_timestamp: Time.now - 2.minutes)
expect(import_state.mirror_update_due?).to be true
end
end
context 'when mirror has no content' do
it 'returns false' do
import_state = create(:import_state, :finished, :mirror)
import_state.next_execution_timestamp = Time.now - 2.minutes
expect(import_state.mirror_update_due?).to be false
end
end
context 'when mirror is hard_failed' do
it 'returns false' do
import_state = create(:import_state, :hard_failed, :mirror, :repository)
expect(import_state.mirror_update_due?).to be false
end
end
context 'mirror is updating' do
it 'returns false when scheduled' do
import_state = create(:import_state, :scheduled, :mirror, :repository)
expect(import_state.mirror_update_due?).to be false
end
end
end
describe '#last_update_status' do
context 'when not a mirror' do
it 'returns nil' do
import_state = create(:import_state)
expect(import_state.last_update_status).to be_nil
end
end
context 'when mirror' do
let(:import_state) { create(:import_state, :mirror) }
context 'when mirror has not updated' do
it 'returns nil' do
expect(import_state.last_update_status).to be_nil
end
end
context 'when mirror has updated' do
let(:timestamp) { Time.now }
before do
import_state.last_update_at = timestamp
end
context 'when last update time equals the time of the last successful update' do
it 'returns success' do
import_state.last_successful_update_at = timestamp
expect(import_state.last_update_status).to eq(:success)
end
end
context 'when last update time does not equal the time of the last successful update' do
it 'returns failed' do
import_state.last_successful_update_at = timestamp - 1.minute
expect(import_state.last_update_status).to eq(:failed)
end
end
end
end
end
describe '#ever_updated_successfully' do
it 'returns false when project is not a mirror' do
import_state = create(:import_state)
expect(import_state.ever_updated_successfully?).to be_falsey
end
context 'when mirror' do
let(:import_state) { create(:import_state, :mirror) }
it 'returns false when project never updated' do
expect(import_state.ever_updated_successfully?).to be_falsey
end
it 'returns false when first update failed' do
import_state.last_update_at = Time.now
expect(import_state.ever_updated_successfully?).to be_falsey
end
it 'returns true when a successful update timestamp exists' do
# It does not matter if the last update was successful or not
import_state.last_update_at = Time.now
import_state.last_successful_update_at = Time.now - 5.minutes
expect(import_state.ever_updated_successfully?).to be_truthy
end
end end
end end
...@@ -123,4 +425,62 @@ describe ProjectImportState, type: :model do ...@@ -123,4 +425,62 @@ describe ProjectImportState, type: :model do
end end
end end
end end
describe '#force_import_job!' do
it 'returns nil if mirror is about to update' do
import_state = create(:import_state,
:repository,
:mirror,
next_execution_timestamp: Time.now - 2.minutes)
expect(import_state.force_import_job!).to be_nil
end
it 'returns nil when mirror is updating' do
import_state = create(:import_state, :repository, :mirror, :started)
expect(import_state.force_import_job!).to be_nil
end
it 'sets next execution timestamp to now and schedules UpdateAllMirrorsWorker' do
timestamp = 1.second.from_now.change(usec: 0)
import_state = create(:import_state, :mirror)
expect(UpdateAllMirrorsWorker).to receive(:perform_async)
Timecop.freeze(timestamp) do
expect { import_state.force_import_job! }.to change(import_state, :next_execution_timestamp).to(timestamp)
end
end
context 'when mirror is hard failed' do
it 'resets retry count and schedules a mirroring worker' do
timestamp = Time.now
import_state = create(:import_state, :mirror, :hard_failed)
expect(UpdateAllMirrorsWorker).to receive(:perform_async)
Timecop.freeze(timestamp) do
expect { import_state.force_import_job! }.to change(import_state, :retry_count).to(0)
expect(import_state.next_execution_timestamp).to be_like_time(timestamp)
end
end
end
end
describe '#reset_retry_count' do
let(:import_state) { create(:import_state, :mirror, :finished, retry_count: 3) }
it 'resets retry_count to 0' do
expect { import_state.reset_retry_count }.to change { import_state.retry_count }.from(3).to(0)
end
end
describe '#increment_retry_count' do
let(:import_state) { create(:import_state, :mirror, :finished) }
it 'increments retry_count' do
expect { import_state.increment_retry_count }.to change { import_state.retry_count }.from(0).to(1)
end
end
end end
...@@ -233,17 +233,6 @@ describe Project do ...@@ -233,17 +233,6 @@ describe Project do
end end
end end
describe 'hard failing a mirror' do
it 'sends a notification' do
project = create(:project, :mirror, :import_started)
project.import_state.update(retry_count: Gitlab::Mirror::MAX_RETRY)
expect_any_instance_of(EE::NotificationService).to receive(:mirror_was_hard_failed).with(project)
project.import_fail
end
end
describe '#push_rule' do describe '#push_rule' do
let(:project) { create(:project, push_rule: create(:push_rule)) } let(:project) { create(:project, push_rule: create(:push_rule)) }
...@@ -433,33 +422,6 @@ describe Project do ...@@ -433,33 +422,6 @@ describe Project do
end end
end end
describe '#force_import_job!' do
it 'sets next execution timestamp to now and schedules UpdateAllMirrorsWorker' do
timestamp = 1.second.from_now.change(usec: 0)
project = create(:project, :mirror)
expect(UpdateAllMirrorsWorker).to receive(:perform_async)
Timecop.freeze(timestamp) do
expect { project.force_import_job! }.to change(project.import_state, :next_execution_timestamp).to(timestamp)
end
end
context 'when mirror is hard failed' do
it 'resets retry count and schedules a mirroring worker' do
timestamp = Time.now
project = create(:project, :mirror, :import_hard_failed)
expect(UpdateAllMirrorsWorker).to receive(:perform_async)
Timecop.freeze(timestamp) do
expect { project.force_import_job! }.to change(project.import_state, :retry_count).to(0)
expect(project.import_state.next_execution_timestamp).to be_like_time(timestamp)
end
end
end
end
describe '#fetch_mirror' do describe '#fetch_mirror' do
where(:import_url, :auth_method, :expected) do where(:import_url, :auth_method, :expected) do
'http://foo:bar@example.com' | 'password' | 'http://foo:bar@example.com' 'http://foo:bar@example.com' | 'password' | 'http://foo:bar@example.com'
...@@ -488,185 +450,6 @@ describe Project do ...@@ -488,185 +450,6 @@ describe Project do
end end
end end
describe '#mirror_waiting_duration' do
it 'returns in seconds the time spent in the queue' do
project = create(:project, :mirror, :import_scheduled)
import_state = project.import_state
import_state.update(last_update_started_at: import_state.last_update_scheduled_at + 5.minutes)
expect(project.mirror_waiting_duration).to eq(300)
end
end
describe '#mirror_update_duration' do
it 'returns in seconds the time spent updating' do
project = create(:project, :mirror, :import_started)
project.update(mirror_last_update_at: project.import_state.last_update_started_at + 5.minutes)
expect(project.mirror_update_duration).to eq(300)
end
end
describe '#mirror_about_to_update?' do
context 'when mirror is expected to run soon' do
it 'returns true' do
timestamp = Time.now
project = create(:project, :mirror, :import_finished, :repository)
project.mirror_last_update_at = timestamp - 3.minutes
project.import_state.next_execution_timestamp = timestamp - 2.minutes
expect(project.mirror_about_to_update?).to be true
end
end
context 'when mirror was scheduled' do
it 'returns false' do
project = create(:project, :mirror, :import_scheduled, :repository)
expect(project.mirror_about_to_update?).to be false
end
end
context 'when mirror is hard_failed' do
it 'returns false' do
project = create(:project, :mirror, :import_hard_failed)
expect(project.mirror_about_to_update?).to be false
end
end
end
describe '#import_in_progress?' do
let(:traits) { [] }
let(:project) { create(:project, *traits, import_url: Project::UNKNOWN_IMPORT_URL) }
shared_examples 'import in progress' do
context 'when project is a mirror' do
before do
traits << :mirror
end
context 'when repository is empty' do
it 'returns true' do
expect(project.import_in_progress?).to be_truthy
end
end
context 'when repository is not empty' do
before do
traits << :repository
end
it 'returns false' do
expect(project.import_in_progress?).to be_falsey
end
end
end
context 'when project is not a mirror' do
it 'returns true' do
expect(project.import_in_progress?).to be_truthy
end
end
end
context 'when import status is scheduled' do
before do
traits << :import_scheduled
end
it_behaves_like 'import in progress'
end
context 'when import status is started' do
before do
traits << :import_started
end
it_behaves_like 'import in progress'
end
context 'when import status is finished' do
before do
traits << :import_finished
end
it 'returns false' do
expect(project.import_in_progress?).to be_falsey
end
end
end
describe '#updating_mirror?' do
context 'when repository is empty' do
it 'returns false' do
project = create(:project, :mirror, :import_started)
expect(project.updating_mirror?).to be false
end
end
context 'when project is not a mirror' do
it 'returns false' do
project = create(:project, :import_started)
expect(project.updating_mirror?).to be false
end
end
context 'when mirror is started' do
it 'returns true' do
project = create(:project, :mirror, :import_started, :repository)
expect(project.updating_mirror?).to be true
end
end
context 'when mirror is scheduled' do
it 'returns true' do
project = create(:project, :mirror, :import_scheduled, :repository)
expect(project.updating_mirror?).to be true
end
end
end
describe '#mirror_last_update_status' do
let(:project) { create(:project, :mirror) }
context 'when mirror has not updated' do
it 'returns nil' do
expect(project.mirror_last_update_status).to be_nil
end
end
context 'when mirror has updated' do
let(:timestamp) { Time.now }
before do
project.mirror_last_update_at = timestamp
end
context 'when last update time equals the time of the last successful update' do
it 'returns success' do
project.mirror_last_successful_update_at = timestamp
expect(project.mirror_last_update_status).to eq(:success)
end
end
context 'when last update time does not equal the time of the last successful update' do
it 'returns failed' do
project.mirror_last_successful_update_at = Time.now - 1.minute
expect(project.mirror_last_update_status).to eq(:failed)
end
end
end
end
describe '#any_runners_limit' do describe '#any_runners_limit' do
let(:project) { create(:project, shared_runners_enabled: shared_runners_enabled) } let(:project) { create(:project, shared_runners_enabled: shared_runners_enabled) }
let(:specific_runner) { create(:ci_runner, :project) } let(:specific_runner) { create(:ci_runner, :project) }
...@@ -1313,79 +1096,6 @@ describe Project do ...@@ -1313,79 +1096,6 @@ describe Project do
end end
end end
describe 'project import state transitions' do
context 'state transition: [:started] => [:finished]' do
context 'elasticsearch indexing disabled' do
before do
stub_ee_application_setting(elasticsearch_indexing: false)
end
it 'does not index the repository' do
project = create(:project, :import_started, import_type: :github)
expect(ElasticCommitIndexerWorker).not_to receive(:perform_async)
project.import_finish
end
end
context 'elasticsearch indexing enabled' do
let(:project) { create(:project, :import_started, import_type: :github) }
before do
stub_ee_application_setting(elasticsearch_indexing: true)
end
context 'no index status' do
it 'schedules a full index of the repository' do
expect(ElasticCommitIndexerWorker).to receive(:perform_async).with(project.id, nil)
project.import_finish
end
end
context 'with index status' do
let!(:index_status) { project.create_index_status!(indexed_at: Time.now, last_commit: 'foo') }
it 'schedules a progressive index of the repository' do
expect(ElasticCommitIndexerWorker).to receive(:perform_async).with(project.id, index_status.last_commit)
project.import_finish
end
end
end
end
end
describe 'Project import job' do
let(:project) { create(:project, import_url: generate(:url)) }
before do
allow_any_instance_of(Gitlab::Shell).to receive(:import_repository)
.with(project.repository_storage, project.disk_path, project.import_url)
.and_return(true)
# Works around https://github.com/rspec/rspec-mocks/issues/910
allow(described_class).to receive(:find).with(project.id).and_return(project)
expect(project.repository).to receive(:after_import)
.and_call_original
expect(project.wiki.repository).to receive(:after_import)
.and_call_original
end
context 'with a mirrored project' do
let(:project) { create(:project, :mirror) }
it 'calls RepositoryImportWorker and inserts in front of the mirror scheduler queue' do
allow_any_instance_of(described_class).to receive(:repository_exists?).and_return(false, true)
expect_any_instance_of(EE::Project).to receive(:force_import_job!)
expect(RepositoryImportWorker).to receive(:perform_async).with(project.id).and_call_original
expect { project.import_schedule }.to change { project.import_jid }
end
end
end
describe '#licensed_features' do describe '#licensed_features' do
let(:plan_license) { :free_plan } let(:plan_license) { :free_plan }
let(:global_license) { create(:license) } let(:global_license) { create(:license) }
...@@ -1624,6 +1334,8 @@ describe Project do ...@@ -1624,6 +1334,8 @@ describe Project do
let(:wiki_updated_service) { instance_double('::Geo::RepositoryUpdatedService') } let(:wiki_updated_service) { instance_double('::Geo::RepositoryUpdatedService') }
before do before do
create(:import_state, project: project)
allow(::Geo::RepositoryUpdatedService) allow(::Geo::RepositoryUpdatedService)
.to receive(:new) .to receive(:new)
.with(project.repository) .with(project.repository)
......
...@@ -163,7 +163,7 @@ describe API::ProjectMirror do ...@@ -163,7 +163,7 @@ describe API::ProjectMirror do
end end
it 'syncs the mirror' do it 'syncs the mirror' do
expect(project_mirrored).to receive(:force_import_job!) expect(project_mirrored.import_state).to receive(:force_import_job!)
do_post do_post
end end
...@@ -182,7 +182,7 @@ describe API::ProjectMirror do ...@@ -182,7 +182,7 @@ describe API::ProjectMirror do
end end
it "doesn't sync the mirror" do it "doesn't sync the mirror" do
expect(project_mirrored).not_to receive(:force_import_job!) expect(project_mirrored.import_state).not_to receive(:force_import_job!)
post api("/projects/#{project_mirrored.id}/mirror/pull"), {}, { 'X-Hub-Signature' => 'signature' } post api("/projects/#{project_mirrored.id}/mirror/pull"), {}, { 'X-Hub-Signature' => 'signature' }
end end
......
...@@ -177,7 +177,7 @@ describe API::Projects do ...@@ -177,7 +177,7 @@ describe API::Projects do
mirror_params[:mirror_user_id] = admin.id mirror_params[:mirror_user_id] = admin.id
project.add_maintainer(admin) project.add_maintainer(admin)
expect_any_instance_of(EE::Project).to receive(:force_import_job!).once expect_any_instance_of(EE::ProjectImportState).to receive(:force_import_job!).once
put(api("/projects/#{project.id}", admin), mirror_params) put(api("/projects/#{project.id}", admin), mirror_params)
...@@ -194,7 +194,7 @@ describe API::Projects do ...@@ -194,7 +194,7 @@ describe API::Projects do
end end
it 'updates mirror related attributes' do it 'updates mirror related attributes' do
expect_any_instance_of(EE::Project).to receive(:force_import_job!).once expect_any_instance_of(EE::ProjectImportState).to receive(:force_import_job!).once
put(api("/projects/#{project.id}", user), mirror_params) put(api("/projects/#{project.id}", user), mirror_params)
......
...@@ -22,7 +22,7 @@ describe Projects::UpdateService, '#execute' do ...@@ -22,7 +22,7 @@ describe Projects::UpdateService, '#execute' do
} }
stub_licensed_features(repository_mirrors: true) stub_licensed_features(repository_mirrors: true)
expect(project).to receive(:force_import_job!).once expect_any_instance_of(EE::ProjectImportState).to receive(:force_import_job!).once
update_project(project, user, opts) update_project(project, user, opts)
end end
......
...@@ -42,7 +42,7 @@ describe 'shared/_mirror_status.html.haml' do ...@@ -42,7 +42,7 @@ describe 'shared/_mirror_status.html.haml' do
context 'with a previous successful update' do context 'with a previous successful update' do
it 'renders failure message' do it 'renders failure message' do
@project.mirror_last_successful_update_at = Time.now - 1.minute @project.import_state.last_successful_update_at = Time.now - 1.minute
render 'shared/mirror_status', raw_message: true render 'shared/mirror_status', raw_message: true
......
require 'spec_helper'
describe ProjectImportScheduleWorker do
describe '#perform' do
it 'schedules an import for a project' do
import_state = create(:import_state)
allow_any_instance_of(EE::Project).to receive(:add_import_job).and_return(nil)
expect do
subject.perform(import_state.project_id)
end.to change { import_state.reload.status }.from("none").to("scheduled")
end
context 'when project is not found' do
it 'raises ImportStateNotFound' do
expect { subject.perform(-1) }.to raise_error(described_class::ImportStateNotFound)
end
end
context 'when project does not have import state' do
it 'raises ImportStateNotFound' do
project = create(:project)
expect { subject.perform(project.id) }.to raise_error(described_class::ImportStateNotFound)
end
end
end
end
...@@ -7,14 +7,15 @@ describe RepositoryImportWorker do ...@@ -7,14 +7,15 @@ describe RepositoryImportWorker do
stub_licensed_features(custom_project_templates: true) stub_licensed_features(custom_project_templates: true)
error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found } error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
project.update(import_jid: '123', import_type: 'gitlab_custom_project_template') project.update(import_type: 'gitlab_custom_project_template')
project.import_state.update(jid: '123')
expect_any_instance_of(Projects::ImportService).to receive(:execute).and_return({ status: :error, message: error }) expect_any_instance_of(Projects::ImportService).to receive(:execute).and_return({ status: :error, message: error })
expect do expect do
subject.perform(project.id) subject.perform(project.id)
end.to raise_error(RuntimeError, error) end.to raise_error(RuntimeError, error)
expect(project.reload.import_error).not_to be_nil expect(project.import_state.reload.last_error).not_to be_nil
end end
context 'when project is a mirror' do context 'when project is a mirror' do
...@@ -24,7 +25,7 @@ describe RepositoryImportWorker do ...@@ -24,7 +25,7 @@ describe RepositoryImportWorker do
expect_any_instance_of(Projects::ImportService).to receive(:execute) expect_any_instance_of(Projects::ImportService).to receive(:execute)
.and_return({ status: :ok }) .and_return({ status: :ok })
expect_any_instance_of(EE::Project).to receive(:force_import_job!) expect_any_instance_of(EE::ProjectImportState).to receive(:force_import_job!)
subject.perform(project.id) subject.perform(project.id)
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