Commit 439f8196 authored by Douwe Maan's avatar Douwe Maan

Merge branch '3691-geo-file-backfill' into 'master'

Prevent failed file syncs from stalling Geo backfill

Closes #3691

See merge request gitlab-org/gitlab-ee!3101
parents 42dfbd02 39a44e03
...@@ -20,7 +20,9 @@ class GeoNodeStatus { ...@@ -20,7 +20,9 @@ class GeoNodeStatus {
this.$repositoriesSynced = $('.js-repositories-synced', this.$status); this.$repositoriesSynced = $('.js-repositories-synced', this.$status);
this.$repositoriesFailed = $('.js-repositories-failed', this.$status); this.$repositoriesFailed = $('.js-repositories-failed', this.$status);
this.$lfsObjectsSynced = $('.js-lfs-objects-synced', this.$status); this.$lfsObjectsSynced = $('.js-lfs-objects-synced', this.$status);
this.$lfsObjectsFailed = $('.js-lfs-objects-failed', this.$status);
this.$attachmentsSynced = $('.js-attachments-synced', this.$status); this.$attachmentsSynced = $('.js-attachments-synced', this.$status);
this.$attachmentsFailed = $('.js-attachments-failed', this.$status);
this.$lastEventSeen = $('.js-last-event-seen', this.$status); this.$lastEventSeen = $('.js-last-event-seen', this.$status);
this.$lastCursorEvent = $('.js-last-cursor-event', this.$status); this.$lastCursorEvent = $('.js-last-cursor-event', this.$status);
this.$health = $('.js-health', this.$status); this.$health = $('.js-health', this.$status);
...@@ -78,15 +80,21 @@ class GeoNodeStatus { ...@@ -78,15 +80,21 @@ class GeoNodeStatus {
status.lfs_objects_count, status.lfs_objects_count,
status.lfs_objects_synced_in_percentage); status.lfs_objects_synced_in_percentage);
const lfsFailedText = gl.text.addDelimiter(status.lfs_objects_failed_count);
const attachmentText = GeoNodeStatus.formatCountAndPercentage( const attachmentText = GeoNodeStatus.formatCountAndPercentage(
status.attachments_synced_count, status.attachments_synced_count,
status.attachments_count, status.attachments_count,
status.attachments_synced_in_percentage); status.attachments_synced_in_percentage);
const attachmentFailedText = gl.text.addDelimiter(status.attachments_failed_count);
this.$repositoriesSynced.text(repoText); this.$repositoriesSynced.text(repoText);
this.$repositoriesFailed.text(repoFailedText); this.$repositoriesFailed.text(repoFailedText);
this.$lfsObjectsSynced.text(lfsText); this.$lfsObjectsSynced.text(lfsText);
this.$lfsObjectsFailed.text(lfsFailedText);
this.$attachmentsSynced.text(attachmentText); this.$attachmentsSynced.text(attachmentText);
this.$attachmentsFailed.text(attachmentFailedText);
const eventDate = gl.utils.formatDate(new Date(status.last_event_date)); const eventDate = gl.utils.formatDate(new Date(status.last_event_date));
const cursorDate = gl.utils.formatDate(new Date(status.cursor_last_event_date)); const cursorDate = gl.utils.formatDate(new Date(status.cursor_last_event_date));
......
class Geo::FileRegistry < Geo::BaseRegistry class Geo::FileRegistry < Geo::BaseRegistry
scope :failed, -> { where(success: false) }
scope :synced, -> { where(success: true) }
end end
...@@ -100,7 +100,7 @@ class GeoNodeStatus ...@@ -100,7 +100,7 @@ class GeoNodeStatus
def lfs_objects_synced_count def lfs_objects_synced_count
@lfs_objects_synced_count ||= begin @lfs_objects_synced_count ||= begin
relation = Geo::FileRegistry.where(file_type: :lfs) relation = Geo::FileRegistry.synced.where(file_type: :lfs)
if Gitlab::Geo.current_node.restricted_project_ids if Gitlab::Geo.current_node.restricted_project_ids
relation = relation.where(file_id: lfs_objects.pluck(:id)) relation = relation.where(file_id: lfs_objects.pluck(:id))
...@@ -114,6 +114,14 @@ class GeoNodeStatus ...@@ -114,6 +114,14 @@ class GeoNodeStatus
@lfs_objects_synced_count = value.to_i @lfs_objects_synced_count = value.to_i
end end
def lfs_objects_failed_count
@lfs_objects_failed_count ||= Geo::FileRegistry.failed.where(file_type: :lfs).count
end
def lfs_objects_failed_count=(value)
@lfs_objects_failed_count = value.to_i
end
def lfs_objects_synced_in_percentage def lfs_objects_synced_in_percentage
sync_percentage(lfs_objects_count, lfs_objects_synced_count) sync_percentage(lfs_objects_count, lfs_objects_synced_count)
end end
...@@ -129,7 +137,7 @@ class GeoNodeStatus ...@@ -129,7 +137,7 @@ class GeoNodeStatus
def attachments_synced_count def attachments_synced_count
@attachments_synced_count ||= begin @attachments_synced_count ||= begin
upload_ids = attachments.pluck(:id) upload_ids = attachments.pluck(:id)
synced_ids = Geo::FileRegistry.where(file_type: [:attachment, :avatar, :file]).pluck(:file_id) synced_ids = Geo::FileRegistry.synced.where(file_type: Geo::FileService::DEFAULT_OBJECT_TYPES).pluck(:file_id)
(synced_ids & upload_ids).length (synced_ids & upload_ids).length
end end
...@@ -139,6 +147,14 @@ class GeoNodeStatus ...@@ -139,6 +147,14 @@ class GeoNodeStatus
@attachments_synced_count = value.to_i @attachments_synced_count = value.to_i
end end
def attachments_failed_count
@attachments_failed_count ||= Geo::FileRegistry.failed.where(file_type: Geo::FileService::DEFAULT_OBJECT_TYPES).count
end
def attachments_failed_count=(value)
@attachments_failed_count = value.to_i
end
def attachments_synced_in_percentage def attachments_synced_in_percentage
sync_percentage(attachments_count, attachments_synced_count) sync_percentage(attachments_count, attachments_synced_count)
end end
......
...@@ -10,6 +10,7 @@ class GeoNodeStatusEntity < Grape::Entity ...@@ -10,6 +10,7 @@ class GeoNodeStatusEntity < Grape::Entity
expose :attachments_count expose :attachments_count
expose :attachments_synced_count expose :attachments_synced_count
expose :attachments_failed_count
expose :attachments_synced_in_percentage do |node| expose :attachments_synced_in_percentage do |node|
number_to_percentage(node.attachments_synced_in_percentage, precision: 2) number_to_percentage(node.attachments_synced_in_percentage, precision: 2)
end end
...@@ -18,6 +19,7 @@ class GeoNodeStatusEntity < Grape::Entity ...@@ -18,6 +19,7 @@ class GeoNodeStatusEntity < Grape::Entity
expose :lfs_objects_count expose :lfs_objects_count
expose :lfs_objects_synced_count expose :lfs_objects_synced_count
expose :lfs_objects_failed_count
expose :lfs_objects_synced_in_percentage do |node| expose :lfs_objects_synced_in_percentage do |node|
number_to_percentage(node.lfs_objects_synced_in_percentage, precision: 2) number_to_percentage(node.lfs_objects_synced_in_percentage, precision: 2)
end end
......
...@@ -11,7 +11,7 @@ module Geo ...@@ -11,7 +11,7 @@ module Geo
success: success, success: success,
bytes_downloaded: bytes_downloaded, bytes_downloaded: bytes_downloaded,
download_time_s: (Time.now - start_time).to_f.round(3)) download_time_s: (Time.now - start_time).to_f.round(3))
update_registry(bytes_downloaded) if success update_registry(bytes_downloaded, success: success)
end end
end end
...@@ -37,13 +37,14 @@ module Geo ...@@ -37,13 +37,14 @@ module Geo
end end
end end
def update_registry(bytes_downloaded) def update_registry(bytes_downloaded, success:)
transfer = Geo::FileRegistry.find_or_initialize_by( transfer = Geo::FileRegistry.find_or_initialize_by(
file_type: object_type, file_type: object_type,
file_id: object_db_id file_id: object_db_id
) )
transfer.bytes = bytes_downloaded transfer.bytes = bytes_downloaded
transfer.success = success
transfer.save transfer.save
end end
......
...@@ -62,10 +62,18 @@ ...@@ -62,10 +62,18 @@
%span.help-block %span.help-block
LFS objects synced: LFS objects synced:
%strong.node-info.js-lfs-objects-synced %strong.node-info.js-lfs-objects-synced
%p
%span.help-block
LFS objects failed:
%strong.node-info.js-lfs-objects-failed
%p %p
%span.help-block %span.help-block
Attachments synced: Attachments synced:
%strong.node-info.js-attachments-synced %strong.node-info.js-attachments-synced
%p
%span.help-block
Attachments failed:
%strong.node-info.js-attachments-failed
%p %p
.advanced-geo-node-status-container .advanced-geo-node-status-container
.advanced-status.hidden .advanced-status.hidden
......
...@@ -9,33 +9,47 @@ module Geo ...@@ -9,33 +9,47 @@ module Geo
end end
def load_pending_resources def load_pending_resources
unsynced = find_unsynced_objects
failed = find_failed_objects
interleave(unsynced, failed)
end
def find_unsynced_objects
lfs_object_ids = find_lfs_object_ids lfs_object_ids = find_lfs_object_ids
objects_ids = find_object_ids objects_ids = find_object_ids
interleave(lfs_object_ids, objects_ids) interleave(lfs_object_ids, objects_ids)
end end
def find_object_ids def find_failed_objects
downloaded_ids = find_downloaded_ids(Geo::FileService::DEFAULT_OBJECT_TYPES) Geo::FileRegistry
.failed
.limit(db_retrieve_batch_size)
.pluck(:file_id, :file_type)
end
unsynched_downloads = filter_downloaded_ids( def find_object_ids
current_node.uploads, downloaded_ids, Upload.table_name) unsynced_downloads = filter_registry_ids(
current_node.uploads,
Geo::FileService::DEFAULT_OBJECT_TYPES,
Upload.table_name
)
unsynched_downloads unsynced_downloads
.order(created_at: :desc)
.limit(db_retrieve_batch_size) .limit(db_retrieve_batch_size)
.pluck(:id, :uploader) .pluck(:id, :uploader)
.map { |id, uploader| [id, uploader.sub(/Uploader\z/, '').underscore] } .map { |id, uploader| [id, uploader.sub(/Uploader\z/, '').underscore] }
end end
def find_lfs_object_ids def find_lfs_object_ids
downloaded_ids = find_downloaded_ids([:lfs]) unsynced_downloads = filter_registry_ids(
current_node.lfs_objects,
[:lfs],
LfsObject.table_name
)
unsynched_downloads = filter_downloaded_ids( unsynced_downloads
current_node.lfs_objects, downloaded_ids, LfsObject.table_name)
unsynched_downloads
.order(created_at: :desc)
.limit(db_retrieve_batch_size) .limit(db_retrieve_batch_size)
.pluck(:id) .pluck(:id)
.map { |id| [id, :lfs] } .map { |id| [id, :lfs] }
...@@ -45,12 +59,14 @@ module Geo ...@@ -45,12 +59,14 @@ module Geo
# plucks a list of file IDs from one into the other. This will not scale # plucks a list of file IDs from one into the other. This will not scale
# well with the number of synchronized files--the query will increase # well with the number of synchronized files--the query will increase
# linearly in size--so this should be replaced with postgres_fdw ASAP. # linearly in size--so this should be replaced with postgres_fdw ASAP.
def filter_downloaded_ids(objects, downloaded_ids, table_name) def filter_registry_ids(objects, file_types, table_name)
return objects if downloaded_ids.empty? registry_ids = pluck_registry_ids(Geo::FileRegistry, file_types)
return objects if registry_ids.empty?
joined_relation = objects.joins(<<~SQL) joined_relation = objects.joins(<<~SQL)
LEFT OUTER JOIN LEFT OUTER JOIN
(VALUES #{downloaded_ids.map { |id| "(#{id}, 't')" }.join(',')}) (VALUES #{registry_ids.map { |id| "(#{id}, 't')" }.join(',')})
file_registry(file_id, registry_present) file_registry(file_id, registry_present)
ON #{table_name}.id = file_registry.file_id ON #{table_name}.id = file_registry.file_id
SQL SQL
...@@ -58,9 +74,9 @@ module Geo ...@@ -58,9 +74,9 @@ module Geo
joined_relation.where(file_registry: { registry_present: [nil, false] }) joined_relation.where(file_registry: { registry_present: [nil, false] })
end end
def find_downloaded_ids(file_types) def pluck_registry_ids(relation, file_types)
downloaded_ids = Geo::FileRegistry.where(file_type: file_types).pluck(:file_id) ids = relation.where(file_type: file_types).pluck(:file_id)
(downloaded_ids + scheduled_file_ids(file_types)).uniq (ids + scheduled_file_ids(file_types)).uniq
end end
def scheduled_file_ids(types) def scheduled_file_ids(types)
......
---
title: Prevent failed file syncs from stalling Geo backfill
merge_request: 3101
author:
type: fixed
class AddFileRegistrySuccess < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
# Ensure existing rows are recorded as successes
add_column_with_default :file_registry, :success, :boolean, default: true, allow_null: false
change_column :file_registry, :success, :boolean, default: false
end
def down
# Prevent failures from being converted into successes
false_value = Arel::Nodes::False.new.to_sql(Geo::BaseRegistry)
connection.execute("DELETE FROM file_registry WHERE success = #{false_value}")
remove_column :file_registry, :success
end
end
class AddFileRegistrySuccessIndex < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :file_registry, :success
end
def down
remove_concurrent_index :file_registry, :success
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20171005045404) do ActiveRecord::Schema.define(version: 20171009162209) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -25,10 +25,12 @@ ActiveRecord::Schema.define(version: 20171005045404) do ...@@ -25,10 +25,12 @@ ActiveRecord::Schema.define(version: 20171005045404) do
t.integer "bytes", limit: 8 t.integer "bytes", limit: 8
t.string "sha256" t.string "sha256"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.boolean "success", default: false, null: false
end end
add_index "file_registry", ["file_type", "file_id"], name: "index_file_registry_on_file_type_and_file_id", unique: true, using: :btree add_index "file_registry", ["file_type", "file_id"], name: "index_file_registry_on_file_type_and_file_id", unique: true, using: :btree
add_index "file_registry", ["file_type"], name: "index_file_registry_on_file_type", using: :btree add_index "file_registry", ["file_type"], name: "index_file_registry_on_file_type", using: :btree
add_index "file_registry", ["success"], name: "index_file_registry_on_success", using: :btree
create_table "project_registry", force: :cascade do |t| create_table "project_registry", force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
......
...@@ -1018,8 +1018,10 @@ module API ...@@ -1018,8 +1018,10 @@ module API
expose :repositories_failed_count expose :repositories_failed_count
expose :lfs_objects_count expose :lfs_objects_count
expose :lfs_objects_synced_count expose :lfs_objects_synced_count
expose :lfs_objects_failed_count
expose :attachments_count expose :attachments_count
expose :attachments_synced_count expose :attachments_synced_count
expose :attachments_failed_count
expose :last_event_id expose :last_event_id
expose :last_event_date expose :last_event_date
expose :cursor_last_event_id expose :cursor_last_event_id
......
...@@ -264,8 +264,10 @@ describe Admin::GeoNodesController, :postgresql do ...@@ -264,8 +264,10 @@ describe Admin::GeoNodesController, :postgresql do
id: 1, id: 1,
health: nil, health: nil,
attachments_count: 329, attachments_count: 329,
attachments_failed_count: 13,
attachments_synced_count: 141, attachments_synced_count: 141,
lfs_objects_count: 256, lfs_objects_count: 256,
lfs_objects_failed_count: 12,
lfs_objects_synced_count: 123, lfs_objects_synced_count: 123,
repositories_count: 10, repositories_count: 10,
repositories_synced_count: 5, repositories_synced_count: 5,
......
...@@ -2,6 +2,7 @@ FactoryGirl.define do ...@@ -2,6 +2,7 @@ FactoryGirl.define do
factory :geo_file_registry, class: Geo::FileRegistry do factory :geo_file_registry, class: Geo::FileRegistry do
sequence(:file_id) sequence(:file_id)
file_type :file file_type :file
success true
trait :avatar do trait :avatar do
file_type :avatar file_type :avatar
......
...@@ -5,8 +5,10 @@ ...@@ -5,8 +5,10 @@
"healthy", "healthy",
"health", "health",
"attachments_count", "attachments_count",
"attachments_failed_count",
"attachments_synced_count", "attachments_synced_count",
"lfs_objects_count", "lfs_objects_count",
"lfs_objects_failed_count",
"lfs_objects_synced_count", "lfs_objects_synced_count",
"db_replication_lag", "db_replication_lag",
"repositories_count", "repositories_count",
...@@ -22,10 +24,12 @@ ...@@ -22,10 +24,12 @@
"healthy": { "type": "boolean" }, "healthy": { "type": "boolean" },
"health": { "type": "string" }, "health": { "type": "string" },
"attachments_count": { "type": "integer" }, "attachments_count": { "type": "integer" },
"attachments_failed_count": { "type": "integer" },
"attachments_synced_count": { "type": "integer" }, "attachments_synced_count": { "type": "integer" },
"attachments_synced_in_percentage": { "type": "string" }, "attachments_synced_in_percentage": { "type": "string" },
"db_replication_lag": { "type": ["integer", "null"] }, "db_replication_lag": { "type": ["integer", "null"] },
"lfs_objects_count": { "type": "integer" }, "lfs_objects_count": { "type": "integer" },
"lfs_objects_failed_count": { "type": "integer" },
"lfs_objects_synced_count": { "type": "integer" }, "lfs_objects_synced_count": { "type": "integer" },
"lfs_objects_synced_in_percentage": { "type": "string" }, "lfs_objects_synced_in_percentage": { "type": "string" },
"repositories_count": { "type": "integer" }, "repositories_count": { "type": "integer" },
......
require 'spec_helper'
describe Geo::FileRegistry do
set(:failed) { create(:geo_file_registry, success: false) }
set(:synced) { create(:geo_file_registry, success: true) }
describe '.failed' do
it 'returns registries in the failed state' do
expect(described_class.failed).to contain_exactly(failed)
end
end
describe '.synced' do
it 'returns registries in the synced state' do
expect(described_class.synced).to contain_exactly(synced)
end
end
end
...@@ -39,6 +39,17 @@ describe GeoNodeStatus do ...@@ -39,6 +39,17 @@ describe GeoNodeStatus do
end end
describe '#attachments_synced_count' do describe '#attachments_synced_count' do
it 'only counts successful syncs' do
create_list(:user, 3, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png'))
uploads = Upload.all.pluck(:id)
create(:geo_file_registry, :avatar, file_id: uploads[0])
create(:geo_file_registry, :avatar, file_id: uploads[1])
create(:geo_file_registry, :avatar, file_id: uploads[2], success: false)
expect(subject.attachments_synced_count).to eq(2)
end
it 'does not count synced files that were replaced' do it 'does not count synced files that were replaced' do
user = create(:user, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) user = create(:user, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png'))
...@@ -68,6 +79,21 @@ describe GeoNodeStatus do ...@@ -68,6 +79,21 @@ describe GeoNodeStatus do
end end
end end
describe '#attachments_failed_count' do
it 'counts failed avatars, attachment, personal snippets and files' do
# These two should be ignored
create(:geo_file_registry, :lfs, success: false)
create(:geo_file_registry)
create(:geo_file_registry, file_type: :personal_file, success: false)
create(:geo_file_registry, file_type: :attachment, success: false)
create(:geo_file_registry, :avatar, success: false)
create(:geo_file_registry, success: false)
expect(subject.attachments_failed_count).to eq(4)
end
end
describe '#attachments_synced_in_percentage' do describe '#attachments_synced_in_percentage' do
let(:avatar) { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png')) } let(:avatar) { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png')) }
let(:upload_1) { create(:upload, model: group, path: avatar) } let(:upload_1) { create(:upload, model: group, path: avatar) }
...@@ -113,6 +139,20 @@ describe GeoNodeStatus do ...@@ -113,6 +139,20 @@ describe GeoNodeStatus do
end end
end end
describe '#lfs_objects_failed' do
it 'counts failed LFS objects' do
# These four should be ignored
create(:geo_file_registry, success: false)
create(:geo_file_registry, :avatar, success: false)
create(:geo_file_registry, file_type: :attachment, success: false)
create(:geo_file_registry, :lfs)
create(:geo_file_registry, :lfs, success: false)
expect(subject.lfs_objects_failed_count).to eq(1)
end
end
describe '#lfs_objects_synced_in_percentage' do describe '#lfs_objects_synced_in_percentage' do
let(:lfs_object_project) { create(:lfs_objects_project, project: project_1) } let(:lfs_object_project) { create(:lfs_objects_project, project: project_1) }
...@@ -218,8 +258,10 @@ describe GeoNodeStatus do ...@@ -218,8 +258,10 @@ describe GeoNodeStatus do
allow(Gitlab::Geo::HealthCheck).to receive(:db_replication_lag).and_return(nil) allow(Gitlab::Geo::HealthCheck).to receive(:db_replication_lag).and_return(nil)
subject.attachments_count = nil subject.attachments_count = nil
subject.attachments_synced_count = nil subject.attachments_synced_count = nil
subject.attachments_failed_count = nil
subject.lfs_objects_count = nil subject.lfs_objects_count = nil
subject.lfs_objects_synced_count = nil subject.lfs_objects_synced_count = nil
subject.lfs_objects_failed_count = nil
subject.repositories_count = nil subject.repositories_count = nil
subject.repositories_synced_count = nil subject.repositories_synced_count = nil
subject.repositories_failed_count = nil subject.repositories_failed_count = nil
...@@ -235,9 +277,11 @@ describe GeoNodeStatus do ...@@ -235,9 +277,11 @@ describe GeoNodeStatus do
expect(subject.repositories_failed_count).to be_zero expect(subject.repositories_failed_count).to be_zero
expect(subject.lfs_objects_count).to be_zero expect(subject.lfs_objects_count).to be_zero
expect(subject.lfs_objects_synced_count).to be_zero expect(subject.lfs_objects_synced_count).to be_zero
expect(subject.lfs_objects_failed_count).to be_zero
expect(subject.lfs_objects_synced_in_percentage).to be_zero expect(subject.lfs_objects_synced_in_percentage).to be_zero
expect(subject.attachments_count).to be_zero expect(subject.attachments_count).to be_zero
expect(subject.attachments_synced_count).to be_zero expect(subject.attachments_synced_count).to be_zero
expect(subject.attachments_failed_count).to be_zero
expect(subject.attachments_synced_in_percentage).to be_zero expect(subject.attachments_synced_in_percentage).to be_zero
expect(subject.last_event_id).to be_nil expect(subject.last_event_id).to be_nil
expect(subject.last_event_date).to be_nil expect(subject.last_event_date).to be_nil
......
...@@ -6,8 +6,10 @@ describe GeoNodeStatusEntity, :postgresql do ...@@ -6,8 +6,10 @@ describe GeoNodeStatusEntity, :postgresql do
id: 1, id: 1,
health: '', health: '',
attachments_count: 329, attachments_count: 329,
attachments_failed_count: 25,
attachments_synced_count: 141, attachments_synced_count: 141,
lfs_objects_count: 256, lfs_objects_count: 256,
lfs_objects_failed_count: 12,
lfs_objects_synced_count: 123, lfs_objects_synced_count: 123,
repositories_count: 10, repositories_count: 10,
repositories_synced_count: 5, repositories_synced_count: 5,
...@@ -29,9 +31,11 @@ describe GeoNodeStatusEntity, :postgresql do ...@@ -29,9 +31,11 @@ describe GeoNodeStatusEntity, :postgresql do
it { is_expected.to have_key(:healthy) } it { is_expected.to have_key(:healthy) }
it { is_expected.to have_key(:health) } it { is_expected.to have_key(:health) }
it { is_expected.to have_key(:attachments_count) } it { is_expected.to have_key(:attachments_count) }
it { is_expected.to have_key(:attachments_failed_count) }
it { is_expected.to have_key(:attachments_synced_count) } it { is_expected.to have_key(:attachments_synced_count) }
it { is_expected.to have_key(:attachments_synced_in_percentage) } it { is_expected.to have_key(:attachments_synced_in_percentage) }
it { is_expected.to have_key(:lfs_objects_count) } it { is_expected.to have_key(:lfs_objects_count) }
it { is_expected.to have_key(:lfs_objects_failed_count) }
it { is_expected.to have_key(:lfs_objects_synced_count) } it { is_expected.to have_key(:lfs_objects_synced_count) }
it { is_expected.to have_key(:lfs_objects_synced_in_percentage) } it { is_expected.to have_key(:lfs_objects_synced_in_percentage) }
it { is_expected.to have_key(:repositories_count) } it { is_expected.to have_key(:repositories_count) }
......
...@@ -8,6 +8,8 @@ describe Geo::FileDownloadService do ...@@ -8,6 +8,8 @@ describe Geo::FileDownloadService do
before do before do
stub_current_geo_node(secondary) stub_current_geo_node(secondary)
allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return(true)
end end
describe '#execute' do describe '#execute' do
...@@ -15,15 +17,18 @@ describe Geo::FileDownloadService do ...@@ -15,15 +17,18 @@ describe Geo::FileDownloadService do
let(:user) { create(:user, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) } let(:user) { create(:user, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) }
let(:upload) { Upload.find_by(model: user, uploader: 'AvatarUploader') } let(:upload) { Upload.find_by(model: user, uploader: 'AvatarUploader') }
subject { described_class.new(:avatar, upload.id) } subject(:execute!) { described_class.new(:avatar, upload.id).execute }
it 'downloads a user avatar' do it 'downloads a user avatar' do
allow_any_instance_of(Gitlab::ExclusiveLease) stub_transfer(Gitlab::Geo::FileTransfer, 100)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::FileTransfer) expect { execute! }.to change { Geo::FileRegistry.synced.count }.by(1)
.to receive(:download_from_primary).and_return(100) end
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::FileTransfer, -1)
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1) expect { execute! }.to change { Geo::FileRegistry.failed.count }.by(1)
end end
end end
...@@ -31,15 +36,18 @@ describe Geo::FileDownloadService do ...@@ -31,15 +36,18 @@ describe Geo::FileDownloadService do
let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) } let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) }
let(:upload) { Upload.find_by(model: group, uploader: 'AvatarUploader') } let(:upload) { Upload.find_by(model: group, uploader: 'AvatarUploader') }
subject { described_class.new(:avatar, upload.id) } subject(:execute!) { described_class.new(:avatar, upload.id).execute }
it 'downloads a group avatar' do it 'downloads a group avatar' do
allow_any_instance_of(Gitlab::ExclusiveLease) stub_transfer(Gitlab::Geo::FileTransfer, 100)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::FileTransfer) expect { execute! }.to change { Geo::FileRegistry.synced.count }.by(1)
.to receive(:download_from_primary).and_return(100) end
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::FileTransfer, -1)
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1) expect { execute! }.to change { Geo::FileRegistry.failed.count }.by(1)
end end
end end
...@@ -47,15 +55,18 @@ describe Geo::FileDownloadService do ...@@ -47,15 +55,18 @@ describe Geo::FileDownloadService do
let(:project) { create(:project, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) } let(:project) { create(:project, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) }
let(:upload) { Upload.find_by(model: project, uploader: 'AvatarUploader') } let(:upload) { Upload.find_by(model: project, uploader: 'AvatarUploader') }
subject { described_class.new(:avatar, upload.id) } subject(:execute!) { described_class.new(:avatar, upload.id).execute }
it 'downloads a project avatar' do it 'downloads a project avatar' do
allow_any_instance_of(Gitlab::ExclusiveLease) stub_transfer(Gitlab::Geo::FileTransfer, 100)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::FileTransfer)
.to receive(:download_from_primary).and_return(100)
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1) expect { execute! }.to change { Geo::FileRegistry.synced.count }.by(1)
end
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::FileTransfer, -1)
expect { execute! }.to change { Geo::FileRegistry.failed.count }.by(1)
end end
end end
...@@ -63,30 +74,36 @@ describe Geo::FileDownloadService do ...@@ -63,30 +74,36 @@ describe Geo::FileDownloadService do
let(:note) { create(:note, :with_attachment) } let(:note) { create(:note, :with_attachment) }
let(:upload) { Upload.find_by(model: note, uploader: 'AttachmentUploader') } let(:upload) { Upload.find_by(model: note, uploader: 'AttachmentUploader') }
subject { described_class.new(:attachment, upload.id) } subject(:execute!) { described_class.new(:attachment, upload.id).execute }
it 'downloads the attachment' do it 'downloads the attachment' do
allow_any_instance_of(Gitlab::ExclusiveLease) stub_transfer(Gitlab::Geo::FileTransfer, 100)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::FileTransfer)
.to receive(:download_from_primary).and_return(100)
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1) expect { execute! }.to change { Geo::FileRegistry.synced.count }.by(1)
end
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::FileTransfer, -1)
expect { execute! }.to change { Geo::FileRegistry.failed.count }.by(1)
end end
end end
context 'with a snippet' do context 'with a snippet' do
let(:upload) { create(:upload, :personal_snippet) } let(:upload) { create(:upload, :personal_snippet) }
subject { described_class.new(:personal_file, upload.id) } subject(:execute!) { described_class.new(:personal_file, upload.id).execute }
it 'downloads the file' do it 'downloads the file' do
allow_any_instance_of(Gitlab::ExclusiveLease) stub_transfer(Gitlab::Geo::FileTransfer, 100)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::FileTransfer)
.to receive(:download_from_primary).and_return(100)
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1) expect { execute! }.to change { Geo::FileRegistry.synced.count }.by(1)
end
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::FileTransfer, -1)
expect { execute! }.to change { Geo::FileRegistry.failed.count }.by(1)
end end
end end
...@@ -101,12 +118,15 @@ describe Geo::FileDownloadService do ...@@ -101,12 +118,15 @@ describe Geo::FileDownloadService do
end end
it 'downloads the file' do it 'downloads the file' do
allow_any_instance_of(Gitlab::ExclusiveLease) stub_transfer(Gitlab::Geo::FileTransfer, 100)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::FileTransfer)
.to receive(:download_from_primary).and_return(100)
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1) expect { subject.execute }.to change { Geo::FileRegistry.synced.count }.by(1)
end
it 'registers when the download fails' do
stub_transfer(Gitlab::Geo::FileTransfer, -1)
expect { subject.execute }.to change { Geo::FileRegistry.failed.count }.by(1)
end end
end end
...@@ -115,18 +135,21 @@ describe Geo::FileDownloadService do ...@@ -115,18 +135,21 @@ describe Geo::FileDownloadService do
subject { described_class.new(:lfs, lfs_object.id) } subject { described_class.new(:lfs, lfs_object.id) }
before do it 'downloads an LFS object' do
allow_any_instance_of(Gitlab::ExclusiveLease) stub_transfer(Gitlab::Geo::LfsTransfer, 100)
.to receive(:try_obtain).and_return(true)
allow_any_instance_of(Gitlab::Geo::LfsTransfer) expect { subject.execute }.to change { Geo::FileRegistry.synced.count }.by(1)
.to receive(:download_from_primary).and_return(100)
end end
it 'downloads an LFS object' do it 'registers when the download fails' do
expect { subject.execute }.to change { Geo::FileRegistry.count }.by(1) stub_transfer(Gitlab::Geo::LfsTransfer, -1)
expect { subject.execute }.to change { Geo::FileRegistry.failed.count }.by(1)
end end
it 'logs a message' do it 'logs a message' do
stub_transfer(Gitlab::Geo::LfsTransfer, 100)
expect(Gitlab::Geo::Logger).to receive(:info).with(hash_including(:message, :download_time_s, success: true, bytes_downloaded: 100)).and_call_original expect(Gitlab::Geo::Logger).to receive(:info).with(hash_including(:message, :download_time_s, success: true, bytes_downloaded: 100)).and_call_original
subject.execute subject.execute
...@@ -138,5 +161,10 @@ describe Geo::FileDownloadService do ...@@ -138,5 +161,10 @@ describe Geo::FileDownloadService do
expect { described_class.new(:bad, 1).execute }.to raise_error(NameError) expect { described_class.new(:bad, 1).execute }.to raise_error(NameError)
end end
end end
def stub_transfer(kls, result)
instance = double("(instance of #{kls})", download_from_primary: result)
allow(kls).to receive(:new).and_return(instance)
end
end end
end end
...@@ -84,6 +84,27 @@ describe Geo::FileDownloadDispatchWorker, :postgresql do ...@@ -84,6 +84,27 @@ describe Geo::FileDownloadDispatchWorker, :postgresql do
end end
end end
context 'with a failed file' do
let!(:failed_registry) { create(:geo_file_registry, :lfs, file_id: 999, success: false) }
it 'does not stall backfill' do
unsynced = create(:lfs_object, :with_file)
stub_const('Geo::BaseSchedulerWorker::DB_RETRIEVE_BATCH_SIZE', 1)
expect(GeoFileDownloadWorker).not_to receive(:perform_async).with(:lfs, failed_registry.file_id)
expect(GeoFileDownloadWorker).to receive(:perform_async).with(:lfs, unsynced.id)
subject.perform
end
it 'retries failed files' do
expect(GeoFileDownloadWorker).to receive(:perform_async).with('lfs', failed_registry.file_id)
subject.perform
end
end
context 'when node has namespace restrictions' do context 'when node has namespace restrictions' do
let(:synced_group) { create(:group) } let(:synced_group) { create(:group) }
let!(:project_in_synced_group) { create(:project, group: synced_group) } let!(:project_in_synced_group) { create(:project, group: synced_group) }
......
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