Commit 7d464a8f authored by Sean McGivern's avatar Sean McGivern

Merge branch 'kassio/github-importer-log-and-fail-on-exceptions' into 'master'

GithubImporter: Ensure to fail and log imports on exceptions

See merge request gitlab-org/gitlab!67454
parents b4fbef35 752512e0
...@@ -35,8 +35,24 @@ module Gitlab ...@@ -35,8 +35,24 @@ module Gitlab
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :imported) Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :imported)
info(project.id, message: 'importer finished') info(project.id, message: 'importer finished')
rescue KeyError => e
# This exception will be more useful in development when a new
# Representation is created but the developer forgot to add a
# `:github_id` field.
Gitlab::Import::ImportFailureService.track(
project_id: project.id,
error_source: importer_class.name,
exception: e,
fail_import: true
)
raise(e)
rescue StandardError => e rescue StandardError => e
error(project.id, e, hash) Gitlab::Import::ImportFailureService.track(
project_id: project.id,
error_source: importer_class.name,
exception: e
)
end end
def object_type def object_type
...@@ -62,22 +78,6 @@ module Gitlab ...@@ -62,22 +78,6 @@ module Gitlab
Logger.info(log_attributes(project_id, extra)) Logger.info(log_attributes(project_id, extra))
end end
def error(project_id, exception, data = {})
Logger.error(
log_attributes(
project_id,
message: 'importer failed',
'error.message': exception.message,
'github.data': data
)
)
Gitlab::ErrorTracking.track_and_raise_exception(
exception,
log_attributes(project_id, import_source: :github)
)
end
def log_attributes(project_id, extra = {}) def log_attributes(project_id, extra = {})
extra.merge( extra.merge(
project_id: project_id, project_id: project_id,
......
...@@ -17,13 +17,10 @@ module Gitlab ...@@ -17,13 +17,10 @@ module Gitlab
sidekiq_options dead: false, retry: 5 sidekiq_options dead: false, retry: 5
sidekiq_retries_exhausted do |msg, e| sidekiq_retries_exhausted do |msg, e|
Logger.error( Gitlab::Import::ImportFailureService.track(
event: :github_importer_exhausted, project_id: msg['args'][0],
message: msg['error_message'], exception: e,
class: msg['class'], fail_import: true
args: msg['args'],
exception_message: e.message,
exception_backtrace: e.backtrace
) )
end end
end end
......
...@@ -15,7 +15,14 @@ module Gitlab ...@@ -15,7 +15,14 @@ module Gitlab
info(project_id, message: 'stage finished') info(project_id, message: 'stage finished')
rescue StandardError => e rescue StandardError => e
error(project_id, e) Gitlab::Import::ImportFailureService.track(
project_id: project_id,
exception: e,
error_source: self.class.name,
fail_import: abort_on_failure
)
raise(e)
end end
# client - An instance of Gitlab::GithubImport::Client. # client - An instance of Gitlab::GithubImport::Client.
...@@ -34,25 +41,14 @@ module Gitlab ...@@ -34,25 +41,14 @@ module Gitlab
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
private def abort_on_failure
false
def info(project_id, extra = {})
Logger.info(log_attributes(project_id, extra))
end end
def error(project_id, exception) private
Logger.error(
log_attributes(
project_id,
message: 'stage failed',
'error.message': exception.message
)
)
Gitlab::ErrorTracking.track_and_raise_exception( def info(project_id, extra = {})
exception, Gitlab::GithubImport::Logger.info(log_attributes(project_id, extra))
log_attributes(project_id, import_source: :github)
)
end end
def log_attributes(project_id, extra = {}) def log_attributes(project_id, extra = {})
......
...@@ -28,7 +28,7 @@ module Gitlab ...@@ -28,7 +28,7 @@ module Gitlab
info(project.id, message: "starting importer", importer: 'Importer::RepositoryImporter') info(project.id, message: "starting importer", importer: 'Importer::RepositoryImporter')
importer = Importer::RepositoryImporter.new(project, client) importer = Importer::RepositoryImporter.new(project, client)
return unless importer.execute importer.execute
counter.increment counter.increment
...@@ -41,6 +41,10 @@ module Gitlab ...@@ -41,6 +41,10 @@ module Gitlab
'The number of imported GitHub repositories' 'The number of imported GitHub repositories'
) )
end end
def abort_on_failure
true
end
end end
end end
end end
......
...@@ -5,6 +5,8 @@ module Gitlab ...@@ -5,6 +5,8 @@ module Gitlab
module StuckImportJob module StuckImportJob
extend ActiveSupport::Concern extend ActiveSupport::Concern
StuckImportJobError = Class.new(StandardError)
IMPORT_JOBS_EXPIRATION = 24.hours.seconds.to_i IMPORT_JOBS_EXPIRATION = 24.hours.seconds.to_i
included do included do
...@@ -34,9 +36,9 @@ module Gitlab ...@@ -34,9 +36,9 @@ module Gitlab
end end
def mark_imports_without_jid_as_failed! def mark_imports_without_jid_as_failed!
enqueued_import_states_without_jid.each do |import_state| enqueued_import_states_without_jid
import_state.mark_as_failed(error_message) .each(&method(:mark_as_failed))
end.size .size
end end
def mark_imports_with_jid_as_failed! def mark_imports_with_jid_as_failed!
...@@ -58,9 +60,20 @@ module Gitlab ...@@ -58,9 +60,20 @@ module Gitlab
job_ids: completed_import_state_jids job_ids: completed_import_state_jids
) )
completed_import_states.each do |import_state| completed_import_states
import_state.mark_as_failed(error_message) .each(&method(:mark_as_failed))
end.size .size
end
def mark_as_failed(import_state)
raise StuckImportJobError, error_message
rescue StuckImportJobError => e
Gitlab::Import::ImportFailureService.track(
import_state: import_state,
exception: e,
error_source: self.class.name,
fail_import: true
)
end end
def enqueued_import_states def enqueued_import_states
......
...@@ -35,7 +35,11 @@ module Gitlab ...@@ -35,7 +35,11 @@ module Gitlab
yield object yield object
end end
rescue StandardError => e rescue StandardError => e
error(project.id, e) Gitlab::Import::ImportFailureService.track(
project_id: project.id,
error_source: importer_class.name,
exception: e
)
end end
end end
end end
......
...@@ -59,8 +59,6 @@ module Gitlab ...@@ -59,8 +59,6 @@ module Gitlab
Repositories::HousekeepingService.new(project, :gc).execute Repositories::HousekeepingService.new(project, :gc).execute
true true
rescue Gitlab::Git::Repository::NoRepository, Gitlab::Shell::Error => e
fail_import("Failed to import the repository: #{e.message}")
end end
def import_wiki_repository def import_wiki_repository
...@@ -70,7 +68,8 @@ module Gitlab ...@@ -70,7 +68,8 @@ module Gitlab
rescue ::Gitlab::Git::CommandError => e rescue ::Gitlab::Git::CommandError => e
if e.message !~ /repository not exported/ if e.message !~ /repository not exported/
project.create_wiki project.create_wiki
fail_import("Failed to import the wiki: #{e.message}")
raise e
else else
true true
end end
...@@ -84,11 +83,6 @@ module Gitlab ...@@ -84,11 +83,6 @@ module Gitlab
project.update_column(:last_repository_updated_at, Time.zone.now) project.update_column(:last_repository_updated_at, Time.zone.now)
end end
def fail_import(message)
project.import_state.mark_as_failed(message)
false
end
private private
def default_branch def default_branch
......
...@@ -49,9 +49,14 @@ module Gitlab ...@@ -49,9 +49,14 @@ module Gitlab
retval retval
rescue StandardError => e rescue StandardError => e
error(project.id, e) Gitlab::Import::ImportFailureService.track(
project_id: project.id,
error_source: self.class.name,
exception: e,
fail_import: abort_on_failure
)
raise e raise(e)
end end
# Imports all the objects in sequence in the current thread. # Imports all the objects in sequence in the current thread.
...@@ -165,6 +170,10 @@ module Gitlab ...@@ -165,6 +170,10 @@ module Gitlab
raise NotImplementedError raise NotImplementedError
end end
def abort_on_failure
false
end
# Any options to be passed to the method used for retrieving the data to # Any options to be passed to the method used for retrieving the data to
# import. # import.
def collection_options def collection_options
...@@ -177,21 +186,6 @@ module Gitlab ...@@ -177,21 +186,6 @@ module Gitlab
Logger.info(log_attributes(project_id, extra)) Logger.info(log_attributes(project_id, extra))
end end
def error(project_id, exception)
Logger.error(
log_attributes(
project_id,
message: 'importer failed',
'error.message': exception.message
)
)
Gitlab::ErrorTracking.track_exception(
exception,
log_attributes(project_id, import_source: :github)
)
end
def log_attributes(project_id, extra = {}) def log_attributes(project_id, extra = {})
extra.merge( extra.merge(
project_id: project_id, project_id: project_id,
......
# frozen_string_literal: true
module Gitlab
module Import
class ImportFailureService
def self.track(
exception:,
import_state: nil,
project_id: nil,
error_source: nil,
fail_import: false
)
new(
exception: exception,
import_state: import_state,
project_id: project_id,
error_source: error_source
).execute(fail_import: fail_import)
end
def initialize(exception:, import_state: nil, project_id: nil, error_source: nil)
if import_state.blank? && project_id.blank?
raise ArgumentError, 'import_state OR project_id must be provided'
end
if project_id.blank?
@import_state = import_state
@project = import_state.project
else
@project = Project.find(project_id)
@import_state = @project.import_state
end
@exception = exception
@error_source = error_source
end
def execute(fail_import:)
track_exception
persist_failure
import_state.mark_as_failed(exception.message) if fail_import
end
private
attr_reader :exception, :import_state, :project, :error_source
def track_exception
attributes = {
import_type: project.import_type,
project_id: project.id,
source: error_source
}
Gitlab::Import::Logger.error(
attributes.merge(
message: 'importer failed',
'error.message': exception.message
)
)
Gitlab::ErrorTracking.track_exception(exception, attributes)
end
def persist_failure
project.import_failures.create(
source: error_source,
exception_class: exception.class.to_s,
exception_message: exception.message.truncate(255),
correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id
)
end
end
end
end
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::LfsObjectsImporter do RSpec.describe Gitlab::GithubImport::Importer::LfsObjectsImporter do
let(:project) { double(:project, id: 4, import_source: 'foo/bar') } let_it_be(:project) { create(:project, :import_started) }
let(:client) { double(:client) } let(:client) { double(:client) }
let(:download_link) { "http://www.gitlab.com/lfs_objects/oid" } let(:download_link) { "http://www.gitlab.com/lfs_objects/oid" }
...@@ -61,24 +62,12 @@ RSpec.describe Gitlab::GithubImport::Importer::LfsObjectsImporter do ...@@ -61,24 +62,12 @@ RSpec.describe Gitlab::GithubImport::Importer::LfsObjectsImporter do
.and_raise(exception) .and_raise(exception)
end end
expect(Gitlab::GithubImport::Logger) expect(Gitlab::Import::ImportFailureService)
.to receive(:error) .to receive(:track)
.with(
message: 'importer failed',
project_id: project.id,
parallel: false,
importer: 'Gitlab::GithubImport::Importer::LfsObjectImporter',
'error.message': 'Invalid Project URL'
)
expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
.with( .with(
exception,
import_source: :github,
parallel: false,
project_id: project.id, project_id: project.id,
importer: 'Gitlab::GithubImport::Importer::LfsObjectImporter' exception: exception,
error_source: 'Gitlab::GithubImport::Importer::LfsObjectImporter'
).and_call_original ).and_call_original
importer.execute importer.execute
......
...@@ -211,17 +211,6 @@ RSpec.describe Gitlab::GithubImport::Importer::RepositoryImporter do ...@@ -211,17 +211,6 @@ RSpec.describe Gitlab::GithubImport::Importer::RepositoryImporter do
expect(importer.import_repository).to eq(true) expect(importer.import_repository).to eq(true)
end end
it 'marks the import as failed when an error was raised' do
expect(project).to receive(:ensure_repository)
.and_raise(Gitlab::Git::Repository::NoRepository)
expect(importer)
.to receive(:fail_import)
.and_return(false)
expect(importer.import_repository).to eq(false)
end
end end
describe '#import_wiki_repository' do describe '#import_wiki_repository' do
...@@ -234,28 +223,40 @@ RSpec.describe Gitlab::GithubImport::Importer::RepositoryImporter do ...@@ -234,28 +223,40 @@ RSpec.describe Gitlab::GithubImport::Importer::RepositoryImporter do
expect(importer.import_wiki_repository).to eq(true) expect(importer.import_wiki_repository).to eq(true)
end end
it 'marks the import as failed and creates an empty repo if an error was raised' do context 'when it raises a Gitlab::Git::CommandError' do
context 'when the error is not a "repository not exported"' do
it 'creates the wiki and re-raise the exception' do
exception = Gitlab::Git::CommandError.new
expect(wiki_repository) expect(wiki_repository)
.to receive(:import_repository) .to receive(:import_repository)
.with(importer.wiki_url) .with(importer.wiki_url)
.and_raise(Gitlab::Git::CommandError) .and_raise(exception)
expect(importer)
.to receive(:fail_import)
.and_return(false)
expect(project) expect(project)
.to receive(:create_wiki) .to receive(:create_wiki)
expect(importer.import_wiki_repository).to eq(false) expect { importer.import_wiki_repository }
.to raise_error(exception)
end end
end end
describe '#fail_import' do context 'when the error is a "repository not exported"' do
it 'marks the import as failed' do it 'returns true' do
expect(project.import_state).to receive(:mark_as_failed).with('foo') exception = Gitlab::Git::CommandError.new('repository not exported')
expect(wiki_repository)
.to receive(:import_repository)
.with(importer.wiki_url)
.and_raise(exception)
expect(importer.fail_import('foo')).to eq(false) expect(project)
.not_to receive(:create_wiki)
expect(importer.import_wiki_repository)
.to eq(true)
end
end
end end
end end
......
...@@ -5,6 +5,10 @@ require 'spec_helper' ...@@ -5,6 +5,10 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::ParallelScheduling do RSpec.describe Gitlab::GithubImport::ParallelScheduling do
let(:importer_class) do let(:importer_class) do
Class.new do Class.new do
def self.name
'MyImporter'
end
include(Gitlab::GithubImport::ParallelScheduling) include(Gitlab::GithubImport::ParallelScheduling)
def importer_class def importer_class
...@@ -21,7 +25,8 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling do ...@@ -21,7 +25,8 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling do
end end
end end
let(:project) { double(:project, id: 4, import_source: 'foo/bar') } let_it_be(:project) { create(:project, :import_started, import_source: 'foo/bar') }
let(:client) { double(:client) } let(:client) { double(:client) }
describe '#parallel?' do describe '#parallel?' do
...@@ -100,6 +105,7 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling do ...@@ -100,6 +105,7 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling do
importer.execute importer.execute
end end
context 'when abort_on_failure is false' do
it 'logs the error when it fails' do it 'logs the error when it fails' do
exception = StandardError.new('some error') exception = StandardError.new('some error')
...@@ -118,28 +124,90 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling do ...@@ -118,28 +124,90 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling do
importer: 'Class' importer: 'Class'
) )
expect(Gitlab::GithubImport::Logger) expect(Gitlab::Import::ImportFailureService)
.to receive(:error) .to receive(:track)
.with( .with(
message: 'importer failed',
project_id: project.id, project_id: project.id,
parallel: false, exception: exception,
importer: 'Class', error_source: 'MyImporter',
'error.message': 'some error' fail_import: false
) ).and_call_original
expect { importer.execute }
.to raise_error(exception)
expect(project.import_state.reload.status).to eq('started')
expect(project.import_failures).not_to be_empty
expect(project.import_failures.last.exception_class).to eq('StandardError')
expect(project.import_failures.last.exception_message).to eq('some error')
end
end
context 'when abort_on_failure is true' do
let(:importer_class) do
Class.new do
def self.name
'MyImporter'
end
include(Gitlab::GithubImport::ParallelScheduling)
expect(Gitlab::ErrorTracking) def importer_class
.to receive(:track_exception) Class
end
def object_type
:dummy
end
def collection_method
:issues
end
def abort_on_failure
true
end
end
end
it 'logs the error when it fails and marks import as failed' do
exception = StandardError.new('some error')
importer = importer_class.new(project, client, parallel: false)
expect(importer)
.to receive(:sequential_import)
.and_raise(exception)
expect(Gitlab::GithubImport::Logger)
.to receive(:info)
.with( .with(
exception, message: 'starting importer',
parallel: false, parallel: false,
project_id: project.id, project_id: project.id,
import_source: :github,
importer: 'Class' importer: 'Class'
) )
.and_call_original
expect { importer.execute }.to raise_error(exception) expect(Gitlab::Import::ImportFailureService)
.to receive(:track)
.with(
project_id: project.id,
exception: exception,
error_source: 'MyImporter',
fail_import: true
).and_call_original
expect { importer.execute }
.to raise_error(exception)
expect(project.import_state.reload.status).to eq('failed')
expect(project.import_state.last_error).to eq('some error')
expect(project.import_failures).not_to be_empty
expect(project.import_failures.last.exception_class).to eq('StandardError')
expect(project.import_failures.last.exception_message).to eq('some error')
end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Import::ImportFailureService do
let_it_be(:import_type) { 'import_type' }
let_it_be(:project) do
create(
:project,
:import_started,
import_type: import_type
)
end
let(:import_state) { project.import_state }
let(:exception) { StandardError.new('some error') }
shared_examples 'logs the exception and fails the import' do
it 'when the failure does not abort the import' do
expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
.with(
exception,
project_id: project.id,
import_type: import_type,
source: 'SomeImporter'
)
expect(Gitlab::Import::Logger)
.to receive(:error)
.with(
message: 'importer failed',
'error.message': 'some error',
project_id: project.id,
import_type: import_type,
source: 'SomeImporter'
)
described_class.track(**arguments)
expect(project.import_state.reload.status).to eq('failed')
expect(project.import_failures).not_to be_empty
expect(project.import_failures.last.exception_class).to eq('StandardError')
expect(project.import_failures.last.exception_message).to eq('some error')
end
end
shared_examples 'logs the exception and does not fail the import' do
it 'when the failure does not abort the import' do
expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
.with(
exception,
project_id: project.id,
import_type: import_type,
source: 'SomeImporter'
)
expect(Gitlab::Import::Logger)
.to receive(:error)
.with(
message: 'importer failed',
'error.message': 'some error',
project_id: project.id,
import_type: import_type,
source: 'SomeImporter'
)
described_class.track(**arguments)
expect(project.import_state.reload.status).to eq('started')
expect(project.import_failures).not_to be_empty
expect(project.import_failures.last.exception_class).to eq('StandardError')
expect(project.import_failures.last.exception_message).to eq('some error')
end
end
context 'when using the project as reference' do
context 'when it fails the import' do
let(:arguments) do
{
project_id: project.id,
exception: exception,
error_source: 'SomeImporter',
fail_import: true
}
end
it_behaves_like 'logs the exception and fails the import'
end
context 'when it does not fail the import' do
let(:arguments) do
{
project_id: project.id,
exception: exception,
error_source: 'SomeImporter',
fail_import: false
}
end
it_behaves_like 'logs the exception and does not fail the import'
end
end
context 'when using the import_state as reference' do
context 'when it fails the import' do
let(:arguments) do
{
import_state: import_state,
exception: exception,
error_source: 'SomeImporter',
fail_import: true
}
end
it_behaves_like 'logs the exception and fails the import'
end
context 'when it does not fail the import' do
let(:arguments) do
{
import_state: import_state,
exception: exception,
error_source: 'SomeImporter',
fail_import: false
}
end
it_behaves_like 'logs the exception and does not fail the import'
end
end
end
...@@ -21,6 +21,12 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do ...@@ -21,6 +21,12 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do
end.new end.new
end end
let_it_be(:project) { create(:project, :import_started) }
let(:importer_class) { double(:importer_class, name: 'klass_name') }
let(:importer_instance) { double(:importer_instance) }
let(:client) { double(:client) }
before do before do
stub_const('MockRepresantation', Class.new do stub_const('MockRepresantation', Class.new do
include Gitlab::GithubImport::Representation::ToHash include Gitlab::GithubImport::Representation::ToHash
...@@ -39,11 +45,6 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do ...@@ -39,11 +45,6 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do
end end
describe '#import', :clean_gitlab_redis_cache do describe '#import', :clean_gitlab_redis_cache do
let(:importer_class) { double(:importer_class, name: 'klass_name') }
let(:importer_instance) { double(:importer_instance) }
let(:project) { double(:project, full_path: 'foo/bar', id: 1) }
let(:client) { double(:client) }
before do before do
expect(worker) expect(worker)
.to receive(:importer_class) .to receive(:importer_class)
...@@ -65,7 +66,7 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do ...@@ -65,7 +66,7 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do
.with( .with(
github_id: 1, github_id: 1,
message: 'starting importer', message: 'starting importer',
project_id: 1, project_id: project.id,
importer: 'klass_name' importer: 'klass_name'
) )
...@@ -74,7 +75,7 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do ...@@ -74,7 +75,7 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do
.with( .with(
github_id: 1, github_id: 1,
message: 'importer finished', message: 'importer finished',
project_id: 1, project_id: project.id,
importer: 'klass_name' importer: 'klass_name'
) )
...@@ -106,59 +107,36 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do ...@@ -106,59 +107,36 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter do
importer: 'klass_name' importer: 'klass_name'
) )
expect(Gitlab::GithubImport::Logger) expect(Gitlab::Import::ImportFailureService)
.to receive(:error) .to receive(:track)
.with( .with(
github_id: 1,
message: 'importer failed',
project_id: project.id, project_id: project.id,
importer: 'klass_name', exception: exception,
'error.message': 'some error', error_source: 'klass_name'
'github.data': {
'github_id' => 1,
'number' => 10
}
) )
.and_call_original
expect(Gitlab::ErrorTracking) worker.import(project, client, { 'number' => 10, 'github_id' => 1 })
.to receive(:track_and_raise_exception)
.with(
exception,
import_source: :github,
github_id: 1,
project_id: 1,
importer: 'klass_name'
).and_call_original
expect { worker.import(project, client, { 'number' => 10, 'github_id' => 1 }) } expect(project.import_state.reload.status).to eq('started')
.to raise_error(exception)
expect(project.import_failures).not_to be_empty
expect(project.import_failures.last.exception_class).to eq('StandardError')
expect(project.import_failures.last.exception_message).to eq('some error')
end end
it 'logs error when representation does not have a github_id' do it 'logs error when representation does not have a github_id' do
expect(importer_class).not_to receive(:new) expect(importer_class).not_to receive(:new)
expect(Gitlab::GithubImport::Logger) expect(Gitlab::Import::ImportFailureService)
.to receive(:error) .to receive(:track)
.with( .with(
github_id: nil,
message: 'importer failed',
project_id: project.id, project_id: project.id,
importer: 'klass_name', exception: a_kind_of(KeyError),
'error.message': 'key not found: :github_id', error_source: 'klass_name',
'github.data': { fail_import: true
'number' => 10
}
) )
.and_call_original
expect(Gitlab::ErrorTracking)
.to receive(:track_and_raise_exception)
.with(
an_instance_of(KeyError),
import_source: :github,
github_id: nil,
project_id: 1,
importer: 'klass_name'
).and_call_original
expect { worker.import(project, client, { 'number' => 10 }) } expect { worker.import(project, client, { 'number' => 10 }) }
.to raise_error(KeyError, 'key not found: :github_id') .to raise_error(KeyError, 'key not found: :github_id')
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::GithubImport::StageMethods do RSpec.describe Gitlab::GithubImport::StageMethods do
let(:project) { create(:project) } let_it_be(:project) { create(:project, :import_started, import_url: 'https://t0ken@github.com/repo/repo.git') }
let(:worker) do let(:worker) do
Class.new do Class.new do
def self.name def self.name
...@@ -15,8 +16,6 @@ RSpec.describe Gitlab::GithubImport::StageMethods do ...@@ -15,8 +16,6 @@ RSpec.describe Gitlab::GithubImport::StageMethods do
end end
describe '#perform' do describe '#perform' do
let(:project) { create(:project, import_url: 'https://t0ken@github.com/repo/repo.git') }
it 'returns if no project could be found' do it 'returns if no project could be found' do
expect(worker).not_to receive(:try_import) expect(worker).not_to receive(:try_import)
...@@ -55,6 +54,7 @@ RSpec.describe Gitlab::GithubImport::StageMethods do ...@@ -55,6 +54,7 @@ RSpec.describe Gitlab::GithubImport::StageMethods do
worker.perform(project.id) worker.perform(project.id)
end end
context 'when abort_on_failure is false' do
it 'logs error when import fails' do it 'logs error when import fails' do
exception = StandardError.new('some error') exception = StandardError.new('some error')
...@@ -75,26 +75,79 @@ RSpec.describe Gitlab::GithubImport::StageMethods do ...@@ -75,26 +75,79 @@ RSpec.describe Gitlab::GithubImport::StageMethods do
import_stage: 'DummyStage' import_stage: 'DummyStage'
) )
expect(Gitlab::GithubImport::Logger) expect(Gitlab::Import::ImportFailureService)
.to receive(:error) .to receive(:track)
.with( .with(
message: 'stage failed',
project_id: project.id, project_id: project.id,
import_stage: 'DummyStage', exception: exception,
'error.message': 'some error' error_source: 'DummyStage',
) fail_import: false
).and_call_original
expect { worker.perform(project.id) }
.to raise_error(exception)
expect(project.import_state.reload.status).to eq('started')
expect(project.import_failures).not_to be_empty
expect(project.import_failures.last.exception_class).to eq('StandardError')
expect(project.import_failures.last.exception_message).to eq('some error')
end
end
context 'when abort_on_failure is true' do
let(:worker) do
Class.new do
def self.name
'DummyStage'
end
def abort_on_failure
true
end
include(Gitlab::GithubImport::StageMethods)
end.new
end
expect(Gitlab::ErrorTracking) it 'logs, captures and re-raises the exception and also marks the import as failed' do
.to receive(:track_and_raise_exception) exception = StandardError.new('some error')
allow(worker)
.to receive(:find_project)
.with(project.id)
.and_return(project)
expect(worker)
.to receive(:try_import)
.and_raise(exception)
expect(Gitlab::GithubImport::Logger)
.to receive(:info)
.with( .with(
exception, message: 'starting stage',
import_source: :github,
project_id: project.id, project_id: project.id,
import_stage: 'DummyStage' import_stage: 'DummyStage'
) )
.and_call_original
expect(Gitlab::Import::ImportFailureService)
.to receive(:track)
.with(
project_id: project.id,
exception: exception,
error_source: 'DummyStage',
fail_import: true
).and_call_original
expect { worker.perform(project.id) }.to raise_error(exception) expect { worker.perform(project.id) }.to raise_error(exception)
expect(project.import_state.reload.status).to eq('failed')
expect(project.import_state.last_error).to eq('some error')
expect(project.import_failures).not_to be_empty
expect(project.import_failures.last.exception_class).to eq('StandardError')
expect(project.import_failures.last.exception_message).to eq('some error')
end
end end
end end
...@@ -126,16 +179,14 @@ RSpec.describe Gitlab::GithubImport::StageMethods do ...@@ -126,16 +179,14 @@ RSpec.describe Gitlab::GithubImport::StageMethods do
end end
describe '#find_project' do describe '#find_project' do
let(:import_state) { create(:import_state, project: project) }
it 'returns a Project for an existing ID' do it 'returns a Project for an existing ID' do
import_state.update_column(:status, 'started') project.import_state.update_column(:status, 'started')
expect(worker.find_project(project.id)).to eq(project) expect(worker.find_project(project.id)).to eq(project)
end end
it 'returns nil for a project that failed importing' do it 'returns nil for a project that failed importing' do
import_state.update_column(:status, 'failed') project.import_state.update_column(:status, 'failed')
expect(worker.find_project(project.id)).to be_nil expect(worker.find_project(project.id)).to be_nil
end end
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Stage::ImportRepositoryWorker do RSpec.describe Gitlab::GithubImport::Stage::ImportRepositoryWorker do
let(:project) { double(:project, id: 4) } let(:project) { double(:project, id: 4) }
let(:worker) { described_class.new } let(:worker) { described_class.new }
describe '#import' do describe '#import' do
...@@ -36,15 +37,19 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportRepositoryWorker do ...@@ -36,15 +37,19 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportRepositoryWorker do
context 'when the import fails' do context 'when the import fails' do
it 'does not schedule the importing of the base data' do it 'does not schedule the importing of the base data' do
client = double(:client) client = double(:client)
exception_class = Gitlab::Git::Repository::NoRepository
expect_next_instance_of(Gitlab::GithubImport::Importer::RepositoryImporter) do |instance| expect_next_instance_of(Gitlab::GithubImport::Importer::RepositoryImporter) do |instance|
expect(instance).to receive(:execute).and_return(false) expect(instance).to receive(:execute).and_raise(exception_class)
end end
expect(Gitlab::GithubImport::Stage::ImportBaseDataWorker) expect(Gitlab::GithubImport::Stage::ImportBaseDataWorker)
.not_to receive(:perform_async) .not_to receive(:perform_async)
worker.import(client, project) expect(worker.abort_on_failure).to eq(true)
expect { worker.import(client, project) }
.to raise_error(exception_class)
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Import::StuckImportJob do
let_it_be(:project) { create(:project, :import_started, import_source: 'foo/bar') }
let(:worker) do
Class.new do
def self.name
'MyStuckProjectImportsWorker'
end
include(Gitlab::Import::StuckImportJob)
def track_metrics(...)
nil
end
def enqueued_import_states
ProjectImportState.with_status([:scheduled, :started])
end
end.new
end
it 'marks the stuck import project as failed and track the error on import_failures' do
worker.perform
expect(project.import_state.reload.status).to eq('failed')
expect(project.import_state.last_error).to eq('Import timed out. Import took longer than 86400 seconds')
expect(project.import_failures).not_to be_empty
expect(project.import_failures.last.exception_class).to eq('Gitlab::Import::StuckImportJob::StuckImportJobError')
expect(project.import_failures.last.exception_message).to eq('Import timed out. Import took longer than 86400 seconds')
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