Commit ba579478 authored by Michael Kozono's avatar Michael Kozono Committed by Robert Speicher

Move code into new module

Because it is not related to selective sync only. The scope
includes code related to object storage.
parent aa4e5b62
......@@ -43,7 +43,7 @@ module Geo
end
def replicables
current_node(fdw: false).attachments
::Upload.replicables_for_geo_node
end
def syncable
......
......@@ -3,11 +3,6 @@
module Geo::SelectiveSync
extend ActiveSupport::Concern
# @return [ActiveRecord::Relation<Upload>] scope of everything that should be synced
def attachments
attachments_selective_sync_scope.merge(attachments_object_storage_scope)
end
def projects_outside_selected_namespaces
return project_model.none unless selective_sync_by_namespaces?
......@@ -44,24 +39,35 @@ module Geo::SelectiveSync
selective_sync_type == 'shards'
end
private
# @return [ActiveRecord::Relation<Upload>] scope observing object storage settings
def attachments_object_storage_scope
return uploads_model.all if sync_object_storage?
uploads_model.with_files_stored_locally
end
# @return [ActiveRecord::Relation<Upload>] scope observing selective sync settings
def attachments_selective_sync_scope
if selective_sync?
uploads_model.where(group_attachments.or(project_attachments).or(other_attachments))
# This method should only be used when:
#
# - Selective sync is enabled
# - A replicable model is associated to Namespace but not to any Project
#
# When selectively syncing by namespace: We must sync every replicable of
# every selected namespace and descendent namespaces.
#
# When selectively syncing by shard: We must sync every replicable of every
# namespace of every project in those shards. We must also sync every ancestor
# of those namespaces.
#
# When selective sync is disabled: This method raises, instead of returning
# the technically correct `Namespace.all`, because it is easy for it to become
# part of an unnecessarily complex and inefficient query.
#
# @return [ActiveRecord::Relation<Namespace>] returns namespaces based on selective sync settings
def namespaces_for_group_owned_replicables
if selective_sync_by_namespaces?
selected_namespaces_and_descendants
elsif selective_sync_by_shards?
selected_leaf_namespaces_and_ancestors
else
uploads_model.all
raise 'This scope should not be needed without selective sync'
end
end
private
def selected_namespaces_and_descendants
relation = selected_namespaces_and_descendants_cte.apply_to(namespaces_model.all)
read_only(relation)
......@@ -119,35 +125,6 @@ module Geo::SelectiveSync
relation
end
def group_attachments
namespaces =
if selective_sync_by_namespaces?
selected_namespaces_and_descendants
elsif selective_sync_by_shards?
selected_leaf_namespaces_and_ancestors
else
namespaces_model.none
end
attachments_for_model_type_with_id_in('Namespace', namespaces.select(:id))
end
def project_attachments
attachments_for_model_type_with_id_in('Project', projects.select(:id))
end
def other_attachments
uploads_table[:model_type].not_in(%w[Namespace Project])
end
def attachments_for_model_type_with_id_in(model_type, model_ids)
uploads_table[:model_type]
.eq(model_type)
.and(
uploads_table[:model_id].in(model_ids.arel)
)
end
# This concern doesn't define a geo_node_namespace_links relation. That's
# done in ::GeoNode or ::Geo::Fdw::GeoNode respectively. So when we use the
# same code from the two places, they act differently - the first doesn't
......@@ -178,13 +155,4 @@ module Geo::SelectiveSync
def projects_table
project_model.arel_table
end
def uploads_model
raise NotImplementedError,
"#{self.class} does not implement #{__method__}"
end
def uploads_table
uploads_model.arel_table
end
end
......@@ -15,6 +15,46 @@ module EE
scope :syncable, -> { with_files_stored_locally }
end
class_methods do
# @return [ActiveRecord::Relation<Upload>] scope of everything that should be synced to this node
def replicables_for_geo_node(node = ::Gitlab::Geo.current_node)
selective_sync_scope(node).merge(object_storage_scope(node))
end
private
# @return [ActiveRecord::Relation<Upload>] scope observing object storage settings of the given node
def object_storage_scope(node)
return all if node.sync_object_storage?
with_files_stored_locally
end
# @return [ActiveRecord::Relation<Upload>] scope observing selective sync settings of the given node
def selective_sync_scope(node)
if node.selective_sync?
group_attachments(node).or(project_attachments(node)).or(other_attachments)
else
all
end
end
# @return [ActiveRecord::Relation<Upload>] scope of Namespace-associated uploads observing selective sync settings of the given node
def group_attachments(node)
where(model_type: 'Namespace', model_id: node.namespaces_for_group_owned_replicables.select(:id))
end
# @return [ActiveRecord::Relation<Upload>] scope of Project-associated uploads observing selective sync settings of the given node
def project_attachments(node)
where(model_type: 'Project', model_id: node.projects.select(:id))
end
# @return [ActiveRecord::Relation<Upload>] scope of uploads which are not associated with Namespace or Project
def other_attachments
where.not(model_type: %w[Namespace Project])
end
end
def log_geo_deleted_event
::Geo::UploadDeletedEventStore.new(self).create!
end
......
......@@ -80,10 +80,6 @@ module Geo
def project_model
Geo::Fdw::Project
end
def uploads_model
Geo::Fdw::Upload
end
end
end
end
......@@ -4,6 +4,117 @@ require 'spec_helper'
RSpec.describe Upload do
include EE::GeoHelpers
using RSpec::Parameterized::TableSyntax
describe '.replicables_for_geo_node' do
# Selective sync is configured relative to the upload's model. Take care not
# to specify a model_factory that contradicts factory.
#
# Permutations of sync_object_storage combined with object-stored-uploads
# are tested in code, because the logic is simple, and to do it in the table
# would quadruple its size and have too much duplication.
where(:selective_sync_namespaces, :selective_sync_shards, :factory, :model_factory, :is_upload_included) do
nil | nil | [:upload] | [:project] | true
nil | nil | [:upload, :issuable_upload] | [:project] | true
nil | nil | [:upload, :namespace_upload] | [:group] | true
nil | nil | [:upload, :favicon_upload] | [:appearance] | true
# selective sync by shard
nil | :model | [:upload] | [:project] | true
nil | :other | [:upload] | [:project] | false
nil | :model_project | [:upload, :namespace_upload] | [:group] | true
nil | :other | [:upload, :namespace_upload] | [:group] | false
nil | :other | [:upload, :favicon_upload] | [:appearance] | true
# selective sync by namespace
:model_parent | nil | [:upload] | [:project] | true
:model_parent_parent | nil | [:upload] | [:project, :in_subgroup] | true
:model | nil | [:upload, :namespace_upload] | [:group] | true
:model_parent | nil | [:upload, :namespace_upload] | [:group, :nested] | true
:other | nil | [:upload] | [:project] | false
:other | nil | [:upload] | [:project, :in_subgroup] | false
:other | nil | [:upload, :namespace_upload] | [:group] | false
:other | nil | [:upload, :namespace_upload] | [:group, :nested] | false
:other | nil | [:upload, :favicon_upload] | [:appearance] | true
end
with_them do
subject(:upload_included) { described_class.replicables_for_geo_node.include?(upload) }
let(:model) { create(*model_factory) }
let(:node) { create_geo_node(model, sync_object_storage: sync_object_storage) }
before do
stub_current_geo_node(node)
end
context 'when sync object storage is enabled' do
let(:sync_object_storage) { true }
context 'when the upload is locally stored' do
let(:upload) { create(*factory, model: model) }
it { is_expected.to eq(is_upload_included) }
end
context 'when the upload is object stored' do
let(:upload) { create(*factory, :object_storage, model: model) }
it { is_expected.to eq(is_upload_included) }
end
end
context 'when sync object storage is disabled' do
let(:sync_object_storage) { false }
context 'when the upload is locally stored' do
let(:upload) { create(*factory, model: model) }
it { is_expected.to eq(is_upload_included) }
end
context 'when the upload is object stored' do
let(:upload) { create(*factory, :object_storage, model: model) }
it { is_expected.to be_falsey }
end
end
end
def create_geo_node(model, sync_object_storage:)
node = build(:geo_node)
if selective_sync_namespaces
node.selective_sync_type = 'namespaces'
elsif selective_sync_shards
node.selective_sync_type = 'shards'
end
case selective_sync_namespaces
when :model
node.namespaces = [model]
when :model_parent
node.namespaces = [model.parent]
when :model_parent_parent
node.namespaces = [model.parent.parent]
when :other
node.namespaces = [create(:group)]
end
case selective_sync_shards
when :model
node.selective_sync_shards = [model.repository_storage]
when :model_project
project = create(:project, namespace: model)
node.selective_sync_shards = [project.repository_storage]
when :other
node.selective_sync_shards = ['other_shard_name']
end
node.sync_object_storage = sync_object_storage
node.save!
node
end
end
describe '#destroy' do
subject { create(:upload, checksum: '8710d2c16809c79fee211a9693b64038a8aae99561bc86ce98a9b46b45677fe4') }
......
......@@ -389,4 +389,8 @@ FactoryBot.define do
create(:design, project: project, issue: issue)
end
end
trait :in_subgroup do
namespace factory: [:group, :nested]
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