Commit f1b444d3 authored by Robert Speicher's avatar Robert Speicher

Merge branch '4644-geo-selective-sync-by-shard' into 'master'

Resolve "Geo selective sync by repository shard"

Closes #4644

See merge request gitlab-org/gitlab-ee!4286
parents 7b60afd8 9398b481
...@@ -12,14 +12,25 @@ const onPrimaryCheckboxChange = function onPrimaryCheckboxChange(e, $namespaces) ...@@ -12,14 +12,25 @@ const onPrimaryCheckboxChange = function onPrimaryCheckboxChange(e, $namespaces)
$namespaces.toggleClass('hidden', e.currentTarget.checked); $namespaces.toggleClass('hidden', e.currentTarget.checked);
}; };
const onSelectiveSyncTypeChange = function onSelectiveSyncTypeChange(e, $byNamespaces, $byShards) {
$byNamespaces.toggleClass('hidden', e.target.value !== 'namespaces');
$byShards.toggleClass('hidden', e.target.value !== 'shards');
};
export default function geoNodeForm($container) { export default function geoNodeForm($container) {
const $namespaces = $('.js-hide-if-geo-primary', $container); const $namespaces = $('.js-hide-if-geo-primary', $container);
const $primaryCheckbox = $('input[type="checkbox"]', $container); const $primaryCheckbox = $('input[type="checkbox"]', $container);
const $selectiveSyncTypeSelect = $('.js-geo-node-selective-sync-type', $container);
const $select2Dropdown = $('.js-geo-node-namespaces', $container); const $select2Dropdown = $('.js-geo-node-namespaces', $container);
const $syncByNamespaces = $('.js-sync-by-namespace', $container);
const $syncByShards = $('.js-sync-by-shard', $container);
$primaryCheckbox.on('change', e => $primaryCheckbox.on('change', e =>
onPrimaryCheckboxChange(e, $namespaces)); onPrimaryCheckboxChange(e, $namespaces));
$selectiveSyncTypeSelect.on('change', e =>
onSelectiveSyncTypeChange(e, $syncByNamespaces, $syncByShards));
$select2Dropdown.select2({ $select2Dropdown.select2({
placeholder: s__('Geo|Select groups to replicate.'), placeholder: s__('Geo|Select groups to replicate.'),
multiple: true, multiple: true,
......
...@@ -97,12 +97,12 @@ module ApplicationSettingsHelper ...@@ -97,12 +97,12 @@ module ApplicationSettingsHelper
] ]
end end
def repository_storages_options_for_select def repository_storages_options_for_select(selected)
options = Gitlab.config.repositories.storages.map do |name, storage| options = Gitlab.config.repositories.storages.map do |name, storage|
["#{name} - #{storage['path']}", name] ["#{name} - #{storage['path']}", name]
end end
options_for_select(options, @application_setting.repository_storages) options_for_select(options, selected)
end end
def sidekiq_queue_options_for_select def sidekiq_queue_options_for_select
......
...@@ -575,7 +575,8 @@ ...@@ -575,7 +575,8 @@
.form-group .form-group
= f.label :repository_storages, 'Storage paths for new projects', class: 'control-label col-sm-2' = f.label :repository_storages, 'Storage paths for new projects', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.select :repository_storages, repository_storages_options_for_select, {include_hidden: false}, multiple: true, class: 'form-control' = f.select :repository_storages, repository_storages_options_for_select(@application_setting.repository_storages),
{include_hidden: false}, multiple: true, class: 'form-control'
.help-block .help-block
Manage repository storage paths. Learn more in the Manage repository storage paths. Learn more in the
= succeed "." do = succeed "." do
......
---
title: Implement selective synchronization by repository shard for Geo
merge_request: 4286
author:
type: added
...@@ -1014,6 +1014,8 @@ ActiveRecord::Schema.define(version: 20180201101405) do ...@@ -1014,6 +1014,8 @@ ActiveRecord::Schema.define(version: 20180201101405) do
t.integer "files_max_capacity", default: 10, null: false t.integer "files_max_capacity", default: 10, null: false
t.integer "repos_max_capacity", default: 25, null: false t.integer "repos_max_capacity", default: 25, null: false
t.string "url", null: false t.string "url", null: false
t.string "selective_sync_type"
t.text "selective_sync_shards"
end end
add_index "geo_nodes", ["access_key"], name: "index_geo_nodes_on_access_key", using: :btree add_index "geo_nodes", ["access_key"], name: "index_geo_nodes_on_access_key", using: :btree
......
...@@ -216,19 +216,27 @@ Currently, this is what is synced: ...@@ -216,19 +216,27 @@ Currently, this is what is synced:
* Issues, merge requests, snippets, and comment attachments * Issues, merge requests, snippets, and comment attachments
* Users, groups, and project avatars * Users, groups, and project avatars
## Selective replication ## Selective synchronization
GitLab Geo supports selective replication, which allows admins to choose which GitLab Geo supports selective synchronization, which allows admins to choose
groups should be replicated by secondary nodes. which projects should be synchronized by secondary nodes.
It is important to note that selective replication: It is important to note that selective synchronization does not:
1. Does not restrict permissions from secondary nodes. 1. Restrict permissions from secondary nodes.
1. Does not hide projects metadata from secondary nodes. Since Geo currently 1. Hide project metadata from secondary nodes.
relies on PostgreSQL replication, all project metadata gets replicated to * Since Geo currently relies on PostgreSQL replication, all project metadata
secondary nodes, but repositories that have not been selected will be empty. gets replicated to secondary nodes, but repositories that have not been
1. Secondary nodes won't pull repositories that do not belong to the selected selected will be empty.
groups to be replicated. 1. Reduce the number of events generated for the Geo event log
* The primary generates events as long as any secondaries are present.
Selective synchronization restrictions are implemented on the secondaries,
not the primary.
A subset of projects can be chosen, either by group or by storage shard. The
former is ideal for replicating data belonging to a subset of users, while the
latter is more suited to progressively rolling out Geo to a large GitLab
instance.
## Upgrading Geo ## Upgrading Geo
......
...@@ -121,9 +121,9 @@ method to be enabled. Navigate to **Admin Area ➔ Settings** ...@@ -121,9 +121,9 @@ method to be enabled. Navigate to **Admin Area ➔ Settings**
Read [Verify proper functioning of the secondary node](configuration.md#step-6-verify-proper-functioning-of-the-secondary-node). Read [Verify proper functioning of the secondary node](configuration.md#step-6-verify-proper-functioning-of-the-secondary-node).
## Selective replication ## Selective synchronization
Read [Selective replication](configuration.md#selective-replication). Read [Selective synchronization](configuration.md#selective-synchronization).
## Troubleshooting ## Troubleshooting
......
...@@ -22,12 +22,11 @@ changes on the primary! ...@@ -22,12 +22,11 @@ changes on the primary!
Secondaries have a number of additional settings available: Secondaries have a number of additional settings available:
| Setting | Description| | Setting | Description |
|--------------------------|------------| |---------------------------|-------------|
| Public Key | The SSH public key of the user that your GitLab instance runs on (unless changed, should be the user `git`). | | Selective synchronization | Enable Geo [selective sync](../../gitlab-geo/configuration.md#selective-synchronization) for this secondary. |
| Groups to replicate | Enable Geo selective sync for this secondary - only the selected groups will be synchronized. | | Repository sync capacity | Number of concurrent requests this secondary will make to the primary when backfilling repositories. |
| Repository sync capacity | Number of concurrent requests this secondary will make to the primary when backfilling repositories. | | File sync capacity | Number of concurrent requests this secondary will make to the primary when backfilling files. |
| File sync capacity | Number of concurrent requests this secondary will make to the primary when backfilling files. |
## Geo backfill ## Geo backfill
......
...@@ -106,7 +106,7 @@ ...@@ -106,7 +106,7 @@
/> />
<geo-node-sync-settings <geo-node-sync-settings
v-else-if="isCustomTypeSync" v-else-if="isCustomTypeSync"
:namespaces="itemValue.namespaces" :selective-sync-type="itemValue.selectiveSyncType"
:last-event="itemValue.lastEvent" :last-event="itemValue.lastEvent"
:cursor-last-event="itemValue.cursorLastEvent" :cursor-last-event="itemValue.cursorLastEvent"
/> />
......
...@@ -150,7 +150,7 @@ ...@@ -150,7 +150,7 @@
}, },
syncSettings() { syncSettings() {
return { return {
namespaces: this.nodeDetails.namespaces, selectiveSyncType: this.nodeDetails.selectiveSyncType,
lastEvent: this.nodeDetails.lastEvent, lastEvent: this.nodeDetails.lastEvent,
cursorLastEvent: this.nodeDetails.cursorLastEvent, cursorLastEvent: this.nodeDetails.cursorLastEvent,
}; };
......
...@@ -14,9 +14,10 @@ ...@@ -14,9 +14,10 @@
icon, icon,
}, },
props: { props: {
namespaces: { selectiveSyncType: {
type: Array, type: String,
required: true, required: false,
default: null,
}, },
lastEvent: { lastEvent: {
type: Object, type: Object,
...@@ -30,7 +31,11 @@ ...@@ -30,7 +31,11 @@
computed: { computed: {
syncType() { syncType() {
return this.namespaces.length > 0 ? s__('GeoNodes|Selective') : s__('GeoNodes|Full'); if (this.selectiveSyncType === null || this.selectiveSyncType === '') {
return s__('GeoNodes|Full');
}
return `${s__('GeoNodes|Selective')} (${this.selectiveSyncType})`;
}, },
eventTimestampEmpty() { eventTimestampEmpty() {
return this.lastEvent.timeStamp === 0 || this.cursorLastEvent.timeStamp === 0; return this.lastEvent.timeStamp === 0 || this.cursorLastEvent.timeStamp === 0;
......
...@@ -81,7 +81,7 @@ export default class GeoNodesStore { ...@@ -81,7 +81,7 @@ export default class GeoNodesStore {
id: rawNodeDetails.cursor_last_event_id || 0, id: rawNodeDetails.cursor_last_event_id || 0,
timeStamp: rawNodeDetails.cursor_last_event_timestamp, timeStamp: rawNodeDetails.cursor_last_event_timestamp,
}, },
namespaces: rawNodeDetails.namespaces, selectiveSyncType: rawNodeDetails.selective_sync_type,
dbReplicationLag: rawNodeDetails.db_replication_lag_seconds, dbReplicationLag: rawNodeDetails.db_replication_lag_seconds,
}; };
} }
......
...@@ -87,6 +87,8 @@ class Admin::GeoNodesController < Admin::ApplicationController ...@@ -87,6 +87,8 @@ class Admin::GeoNodesController < Admin::ApplicationController
params.require(:geo_node).permit( params.require(:geo_node).permit(
:url, :url,
:primary, :primary,
:selective_sync_type,
:selective_sync_shards,
:namespace_ids, :namespace_ids,
:repos_max_capacity, :repos_max_capacity,
:files_max_capacity :files_max_capacity
......
...@@ -68,7 +68,16 @@ module Geo ...@@ -68,7 +68,16 @@ module Geo
private private
def group_uploads def group_uploads
namespace_ids = Gitlab::GroupHierarchy.new(current_node.namespaces).base_and_descendants.select(:id) namespace_ids =
if current_node.selective_sync_by_namespaces?
Gitlab::GroupHierarchy.new(current_node.namespaces).base_and_descendants.select(:id)
elsif current_node.selective_sync_by_shards?
leaf_groups = Namespace.where(id: current_node.projects.select(:namespace_id))
Gitlab::GroupHierarchy.new(leaf_groups).base_and_ancestors.select(:id)
else
Namespace.none
end
arel_namespace_ids = Arel::Nodes::SqlLiteral.new(namespace_ids.to_sql) arel_namespace_ids = Arel::Nodes::SqlLiteral.new(namespace_ids.to_sql)
upload_table[:model_type].eq('Namespace').and(upload_table[:model_id].in(arel_namespace_ids)) upload_table[:model_type].eq('Namespace').and(upload_table[:model_id].in(arel_namespace_ids))
......
...@@ -19,6 +19,17 @@ module EE ...@@ -19,6 +19,17 @@ module EE
end end
end end
def selective_sync_type_options_for_select(geo_node)
options_for_select(
[
[s_('Geo|All projects'), ''],
[s_('Geo|Projects in certain groups'), 'namespaces'],
[s_('Geo|Projects in certain storage shards'), 'shards']
],
geo_node.selective_sync_type
)
end
def status_loading_icon def status_loading_icon
icon "spinner spin fw", class: 'js-geo-node-loading' icon "spinner spin fw", class: 'js-geo-node-loading'
end end
......
class GeoNode < ActiveRecord::Base class GeoNode < ActiveRecord::Base
include Presentable include Presentable
SELECTIVE_SYNC_TYPES = %w[namespaces shards].freeze
# Array of repository storages to synchronize for selective sync by shards
serialize :selective_sync_shards, Array # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :oauth_application, class_name: 'Doorkeeper::Application', dependent: :destroy # rubocop: disable Cop/ActiveRecordDependent belongs_to :oauth_application, class_name: 'Doorkeeper::Application', dependent: :destroy # rubocop: disable Cop/ActiveRecordDependent
has_many :geo_node_namespace_links has_many :geo_node_namespace_links
...@@ -18,6 +23,12 @@ class GeoNode < ActiveRecord::Base ...@@ -18,6 +23,12 @@ class GeoNode < ActiveRecord::Base
validates :access_key, presence: true validates :access_key, presence: true
validates :encrypted_secret_access_key, presence: true validates :encrypted_secret_access_key, presence: true
validates :selective_sync_type, inclusion: {
in: SELECTIVE_SYNC_TYPES,
allow_blank: true,
allow_nil: true
}
validate :check_not_adding_primary_as_secondary, if: :secondary? validate :check_not_adding_primary_as_secondary, if: :secondary?
after_save :expire_cache! after_save :expire_cache!
...@@ -119,13 +130,26 @@ class GeoNode < ActiveRecord::Base ...@@ -119,13 +130,26 @@ class GeoNode < ActiveRecord::Base
end end
def projects def projects
if selective_sync? return Project.all unless selective_sync?
Project.where(namespace_id: Gitlab::GroupHierarchy.new(namespaces).base_and_descendants.select(:id))
if selective_sync_by_namespaces?
query = Gitlab::GroupHierarchy.new(namespaces).base_and_descendants
Project.where(namespace_id: query.select(:id))
elsif selective_sync_by_shards?
Project.where(repository_storage: selective_sync_shards)
else else
Project.all Project.none
end end
end end
def selective_sync_by_namespaces?
selective_sync_type == 'namespaces'
end
def selective_sync_by_shards?
selective_sync_type == 'shards'
end
def projects_include?(project_id) def projects_include?(project_id)
return true unless selective_sync? return true unless selective_sync?
...@@ -133,7 +157,7 @@ class GeoNode < ActiveRecord::Base ...@@ -133,7 +157,7 @@ class GeoNode < ActiveRecord::Base
end end
def selective_sync? def selective_sync?
namespaces.exists? selective_sync_type.present?
end end
def replication_slots_count def replication_slots_count
......
class GeoNodeStatus < ActiveRecord::Base class GeoNodeStatus < ActiveRecord::Base
belongs_to :geo_node belongs_to :geo_node
delegate :selective_sync_type, to: :geo_node
# Whether we were successful in reaching this node # Whether we were successful in reaching this node
attr_accessor :success, :version, :revision attr_accessor :success, :version, :revision
attr_writer :health_status attr_writer :health_status
......
module Geo module Geo
class NodeUpdateService class NodeUpdateService
attr_reader :geo_node, :old_namespace_ids, :params attr_reader :geo_node, :old_namespace_ids, :old_shards, :params
def initialize(geo_node, params) def initialize(geo_node, params)
@geo_node = geo_node @geo_node = geo_node
@old_namespace_ids = geo_node.namespace_ids @old_namespace_ids = geo_node.namespace_ids
@old_shards = geo_node.selective_sync_shards
@params = params.dup @params = params.dup
@params[:namespace_ids] = @params[:namespace_ids].to_s.split(',') @params[:namespace_ids] = @params[:namespace_ids].to_s.split(',')
end end
def execute def execute
return false unless geo_node.update(params) return false unless geo_node.update(params)
if geo_node.secondary? && namespaces_changed?(geo_node) Geo::RepositoriesChangedEventStore.new(geo_node).create if selective_sync_changed?
Geo::RepositoriesChangedEventStore.new(geo_node).create
end
true true
end end
private private
def namespaces_changed?(geo_node) def selective_sync_changed?
return false unless geo_node.secondary?
geo_node.selective_sync_type_changed? ||
selective_sync_by_namespaces_changed? ||
selective_sync_by_shards_changed?
end
def selective_sync_by_namespaces_changed?
return false unless geo_node.selective_sync_by_namespaces?
geo_node.namespace_ids.any? && geo_node.namespace_ids != old_namespace_ids geo_node.namespace_ids.any? && geo_node.namespace_ids != old_namespace_ids
end end
def selective_sync_by_shards_changed?
return false unless geo_node.selective_sync_by_shards?
geo_node.selective_sync_shards.any? && geo_node.selective_sync_shards != old_shards
end
end end
end end
...@@ -11,12 +11,27 @@ ...@@ -11,12 +11,27 @@
= form.check_box :primary = form.check_box :primary
%strong This is a primary node %strong This is a primary node
.form-group.js-hide-if-geo-primary{ class: ('hidden' unless geo_node.secondary?) } .js-hide-if-geo-primary{ class: ('hidden' unless geo_node.secondary?) }
= form.label :namespace_ids, s_('Geo|Groups to replicate'), class: 'control-label' .form-group
.col-sm-10 = form.label :selective_sync_type, s_('Selective synchronization'), class: 'control-label'
= hidden_field_tag "#{form.object_name}[namespace_ids]", geo_node.namespace_ids.join(","), class: 'js-geo-node-namespaces', data: { selected: node_namespaces_options(geo_node.namespaces).to_json } .col-sm-10
.help-block = form.select :selective_sync_type, selective_sync_type_options_for_select(geo_node),
#{ s_("Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all.") } {}, { class: "form-control js-geo-node-selective-sync-type" }
.form-group.js-sync-by-namespace{ class: ('hidden' unless geo_node.selective_sync_by_namespaces?) }
= form.label :namespace_ids, s_('Geo|Groups to synchronize'), class: 'control-label'
.col-sm-10
= hidden_field_tag "#{form.object_name}[namespace_ids]", geo_node.namespace_ids.join(","), class: 'js-geo-node-namespaces', data: { selected: node_namespaces_options(geo_node.namespaces).to_json }
.help-block
#{ s_("Choose which groups you wish to synchronize to this secondary node.") }
.form-group.js-sync-by-shard{ class: ('hidden' unless geo_node.selective_sync_by_shards?) }
= form.label :selective_sync_shards, s_('Geo|Shards to synchronize'), class: 'control-label'
.col-sm-10
= form.select :selective_sync_shards, repository_storages_options_for_select(geo_node.selective_sync_shards),
{ include_hidden: false }, multiple: true, class: 'form-control'
.help-block
#{ s_("Choose which shards you wish to synchronize to this secondary node.") }
.form-group.js-hide-if-geo-primary{ class: ('hidden' unless geo_node.secondary?) } .form-group.js-hide-if-geo-primary{ class: ('hidden' unless geo_node.secondary?) }
= form.label :repos_max_capacity, s_('Geo|Repository sync capacity'), class: 'control-label' = form.label :repos_max_capacity, s_('Geo|Repository sync capacity'), class: 'control-label'
......
...@@ -12,7 +12,7 @@ module Geo ...@@ -12,7 +12,7 @@ module Geo
return unless Gitlab::Geo.geo_database_configured? return unless Gitlab::Geo.geo_database_configured?
return unless Gitlab::Geo.secondary? return unless Gitlab::Geo.secondary?
shards = healthy_shards shards = selective_sync_filter(healthy_shards)
Gitlab::Geo::ShardHealthCache.update(shards) Gitlab::Geo::ShardHealthCache.update(shards)
...@@ -32,5 +32,11 @@ module Geo ...@@ -32,5 +32,11 @@ module Geo
.compact .compact
.uniq .uniq
end end
def selective_sync_filter(shards)
return shards unless ::Gitlab::Geo.current_node&.selective_sync_by_shards?
shards & ::Gitlab::Geo.current_node.selective_sync_shards
end
end end
end end
class GeoSelectiveSyncByShard < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
add_column :geo_nodes, :selective_sync_type, :string
add_column :geo_nodes, :selective_sync_shards, :text
# Nodes with associated namespaces should be set to 'namespaces'
connection.execute(<<~SQL)
UPDATE geo_nodes
SET selective_sync_type = 'namespaces'
WHERE id IN(
SELECT DISTINCT geo_node_id
FROM geo_node_namespace_links
)
SQL
end
def down
remove_column :geo_nodes, :selective_sync_type
remove_column :geo_nodes, :selective_sync_shards
end
end
...@@ -1226,6 +1226,9 @@ module API ...@@ -1226,6 +1226,9 @@ module API
expose :version expose :version
expose :revision expose :revision
expose :selective_sync_type
# Deprecated: remove in API v5. We use selective_sync_type instead now.
expose :namespaces, using: NamespaceBasic expose :namespaces, using: NamespaceBasic
expose :updated_at expose :updated_at
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
"last_event_timestamp", "last_event_timestamp",
"cursor_last_event_id", "cursor_last_event_id",
"cursor_last_event_timestamp", "cursor_last_event_timestamp",
"selective_sync_type",
"namespaces", "namespaces",
"version", "version",
"revision", "revision",
...@@ -72,6 +73,7 @@ ...@@ -72,6 +73,7 @@
"cursor_last_event_id": { "type": ["integer", "null"] }, "cursor_last_event_id": { "type": ["integer", "null"] },
"cursor_last_event_timestamp": { "type": ["integer", "null"] }, "cursor_last_event_timestamp": { "type": ["integer", "null"] },
"last_successful_status_check_timestamp": { "type": ["integer", "null"] }, "last_successful_status_check_timestamp": { "type": ["integer", "null"] },
"selective_sync_type": { "type": ["string", "null"] },
"namespaces": { "type": "array" }, "namespaces": { "type": "array" },
"storage_shards": { "type": "array" }, "storage_shards": { "type": "array" },
"storage_shards_match": { "type": "boolean" }, "storage_shards_match": { "type": "boolean" },
......
...@@ -9,7 +9,7 @@ describe Geo::AttachmentRegistryFinder, :geo do ...@@ -9,7 +9,7 @@ describe Geo::AttachmentRegistryFinder, :geo do
let(:synced_subgroup) { create(:group, parent: synced_group) } let(:synced_subgroup) { create(:group, parent: synced_group) }
let(:unsynced_group) { create(:group) } let(:unsynced_group) { create(:group) }
let(:synced_project) { create(:project, group: synced_group) } let(:synced_project) { create(:project, group: synced_group) }
let(:unsynced_project) { create(:project, group: unsynced_group) } let(:unsynced_project) { create(:project, group: unsynced_group, repository_storage: 'broken') }
let!(:upload_1) { create(:upload, model: synced_group) } let!(:upload_1) { create(:upload, model: synced_group) }
let!(:upload_2) { create(:upload, model: unsynced_group) } let!(:upload_2) { create(:upload, model: unsynced_group) }
...@@ -54,21 +54,12 @@ describe Geo::AttachmentRegistryFinder, :geo do ...@@ -54,21 +54,12 @@ describe Geo::AttachmentRegistryFinder, :geo do
end end
context 'with selective sync' do context 'with selective sync' do
it 'returns synced avatars, attachment, personal snippets and files' do it 'falls back to legacy queries' do
create(:geo_file_registry, :avatar, file_id: upload_1.id) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
create(:geo_file_registry, :avatar, file_id: upload_2.id)
create(:geo_file_registry, :avatar, file_id: upload_3.id)
create(:geo_file_registry, :avatar, file_id: upload_4.id)
create(:geo_file_registry, :avatar, file_id: upload_5.id, success: false)
create(:geo_file_registry, :avatar, file_id: upload_6.id)
create(:geo_file_registry, :avatar, file_id: upload_7.id)
create(:geo_file_registry, :lfs, file_id: lfs_object.id)
secondary.update_attribute(:namespaces, [synced_group]) expect(subject).to receive(:legacy_find_synced_attachments)
synced_attachments = subject.find_synced_attachments
expect(synced_attachments.pluck(:id)).to match_array([upload_1.id, upload_3.id, upload_6.id, upload_7.id]) subject.find_synced_attachments
end end
end end
end end
...@@ -94,21 +85,12 @@ describe Geo::AttachmentRegistryFinder, :geo do ...@@ -94,21 +85,12 @@ describe Geo::AttachmentRegistryFinder, :geo do
end end
context 'with selective sync' do context 'with selective sync' do
it 'returns failed avatars, attachment, personal snippets and files' do it 'falls back to legacy queries' do
create(:geo_file_registry, :avatar, file_id: upload_1.id, success: false) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
create(:geo_file_registry, :avatar, file_id: upload_2.id)
create(:geo_file_registry, :avatar, file_id: upload_3.id, success: false)
create(:geo_file_registry, :avatar, file_id: upload_4.id)
create(:geo_file_registry, :avatar, file_id: upload_5.id)
create(:geo_file_registry, :avatar, file_id: upload_6.id, success: false)
create(:geo_file_registry, :avatar, file_id: upload_7.id, success: false)
create(:geo_file_registry, :lfs, file_id: lfs_object.id, success: false)
secondary.update_attribute(:namespaces, [synced_group])
failed_attachments = subject.find_failed_attachments expect(subject).to receive(:legacy_find_failed_attachments)
expect(failed_attachments.pluck(:id)).to match_array([upload_1.id, upload_3.id, upload_6.id, upload_7.id]) subject.find_failed_attachments
end end
end end
end end
...@@ -163,7 +145,7 @@ describe Geo::AttachmentRegistryFinder, :geo do ...@@ -163,7 +145,7 @@ describe Geo::AttachmentRegistryFinder, :geo do
expect(synced_attachments).to match_array([upload_1, upload_2, upload_6, upload_7]) expect(synced_attachments).to match_array([upload_1, upload_2, upload_6, upload_7])
end end
context 'with selective sync' do context 'with selective sync by namespace' do
it 'returns synced avatars, attachment, personal snippets and files' do it 'returns synced avatars, attachment, personal snippets and files' do
create(:geo_file_registry, :avatar, file_id: upload_1.id) create(:geo_file_registry, :avatar, file_id: upload_1.id)
create(:geo_file_registry, :avatar, file_id: upload_2.id) create(:geo_file_registry, :avatar, file_id: upload_2.id)
...@@ -174,13 +156,32 @@ describe Geo::AttachmentRegistryFinder, :geo do ...@@ -174,13 +156,32 @@ describe Geo::AttachmentRegistryFinder, :geo do
create(:geo_file_registry, :avatar, file_id: upload_7.id) create(:geo_file_registry, :avatar, file_id: upload_7.id)
create(:geo_file_registry, :lfs, file_id: lfs_object.id) create(:geo_file_registry, :lfs, file_id: lfs_object.id)
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
synced_attachments = subject.find_synced_attachments synced_attachments = subject.find_synced_attachments
expect(synced_attachments).to match_array([upload_1, upload_3, upload_6, upload_7]) expect(synced_attachments).to match_array([upload_1, upload_3, upload_6, upload_7])
end end
end end
context 'with selective sync by shard' do
it 'returns synced avatars, attachment, personal snippets and files' do
create(:geo_file_registry, :avatar, file_id: upload_1.id)
create(:geo_file_registry, :avatar, file_id: upload_2.id)
create(:geo_file_registry, :avatar, file_id: upload_3.id)
create(:geo_file_registry, :avatar, file_id: upload_4.id)
create(:geo_file_registry, :avatar, file_id: upload_5.id, success: false)
create(:geo_file_registry, :avatar, file_id: upload_6.id)
create(:geo_file_registry, :avatar, file_id: upload_7.id)
create(:geo_file_registry, :lfs, file_id: lfs_object.id)
secondary.update!(selective_sync_type: 'shards', selective_sync_shards: ['default'])
synced_attachments = subject.find_synced_attachments
expect(synced_attachments).to match_array([upload_1, upload_3, upload_6])
end
end
end end
describe '#find_failed_attachments' do describe '#find_failed_attachments' do
...@@ -203,24 +204,43 @@ describe Geo::AttachmentRegistryFinder, :geo do ...@@ -203,24 +204,43 @@ describe Geo::AttachmentRegistryFinder, :geo do
expect(failed_attachments).to match_array([upload_3, upload_6, upload_7]) expect(failed_attachments).to match_array([upload_3, upload_6, upload_7])
end end
context 'with selective sync' do context 'with selective sync by namespace' do
it 'returns failed avatars, attachment, personal snippets and files' do it 'returns failed avatars, attachment, personal snippets and files' do
create(:geo_file_registry, :avatar, file_id: upload_1.id, success: false) create(:geo_file_registry, :avatar, file_id: upload_1.id, success: false)
create(:geo_file_registry, :avatar, file_id: upload_2.id) create(:geo_file_registry, :avatar, file_id: upload_2.id)
create(:geo_file_registry, :avatar, file_id: upload_3.id, success: false) create(:geo_file_registry, :avatar, file_id: upload_3.id, success: false)
create(:geo_file_registry, :avatar, file_id: upload_4.id) create(:geo_file_registry, :avatar, file_id: upload_4.id, success: false)
create(:geo_file_registry, :avatar, file_id: upload_5.id) create(:geo_file_registry, :avatar, file_id: upload_5.id)
create(:geo_file_registry, :avatar, file_id: upload_6.id, success: false) create(:geo_file_registry, :avatar, file_id: upload_6.id, success: false)
create(:geo_file_registry, :avatar, file_id: upload_7.id, success: false) create(:geo_file_registry, :avatar, file_id: upload_7.id, success: false)
create(:geo_file_registry, :lfs, file_id: lfs_object.id, success: false) create(:geo_file_registry, :lfs, file_id: lfs_object.id, success: false)
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
failed_attachments = subject.find_failed_attachments failed_attachments = subject.find_failed_attachments
expect(failed_attachments).to match_array([upload_1, upload_3, upload_6, upload_7]) expect(failed_attachments).to match_array([upload_1, upload_3, upload_6, upload_7])
end end
end end
context 'with selective sync by shard' do
it 'returns failed avatars, attachment, personal snippets and files' do
create(:geo_file_registry, :avatar, file_id: upload_1.id, success: false)
create(:geo_file_registry, :avatar, file_id: upload_2.id)
create(:geo_file_registry, :avatar, file_id: upload_3.id, success: false)
create(:geo_file_registry, :avatar, file_id: upload_4.id, success: false)
create(:geo_file_registry, :avatar, file_id: upload_5.id)
create(:geo_file_registry, :avatar, file_id: upload_6.id, success: false)
create(:geo_file_registry, :avatar, file_id: upload_7.id, success: false)
create(:geo_file_registry, :lfs, file_id: lfs_object.id, success: false)
secondary.update!(selective_sync_type: 'shards', selective_sync_shards: ['default'])
failed_attachments = subject.find_failed_attachments
expect(failed_attachments).to match_array([upload_1, upload_3, upload_6])
end
end
end end
describe '#find_unsynced_attachments' do describe '#find_unsynced_attachments' do
......
...@@ -36,7 +36,7 @@ describe Geo::JobArtifactRegistryFinder, :geo do ...@@ -36,7 +36,7 @@ describe Geo::JobArtifactRegistryFinder, :geo do
context 'with selective sync' do context 'with selective sync' do
before do before do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
end end
it 'delegates to #legacy_find_synced_job_artifacts' do it 'delegates to #legacy_find_synced_job_artifacts' do
...@@ -72,7 +72,7 @@ describe Geo::JobArtifactRegistryFinder, :geo do ...@@ -72,7 +72,7 @@ describe Geo::JobArtifactRegistryFinder, :geo do
context 'with selective sync' do context 'with selective sync' do
before do before do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
end end
it 'delegates to #legacy_find_failed_job_artifacts' do it 'delegates to #legacy_find_failed_job_artifacts' do
......
...@@ -36,7 +36,7 @@ describe Geo::LfsObjectRegistryFinder, :geo do ...@@ -36,7 +36,7 @@ describe Geo::LfsObjectRegistryFinder, :geo do
context 'with selective sync' do context 'with selective sync' do
before do before do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
end end
it 'delegates to #legacy_find_synced_lfs_objects' do it 'delegates to #legacy_find_synced_lfs_objects' do
...@@ -78,7 +78,7 @@ describe Geo::LfsObjectRegistryFinder, :geo do ...@@ -78,7 +78,7 @@ describe Geo::LfsObjectRegistryFinder, :geo do
context 'with selective sync' do context 'with selective sync' do
before do before do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
end end
it 'delegates to #legacy_find_failed_lfs_objects' do it 'delegates to #legacy_find_failed_lfs_objects' do
......
...@@ -34,7 +34,7 @@ describe Geo::ProjectRegistryFinder, :geo do ...@@ -34,7 +34,7 @@ describe Geo::ProjectRegistryFinder, :geo do
context 'with selective sync' do context 'with selective sync' do
before do before do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
end end
it 'delegates to #legacy_find_synced_repositories' do it 'delegates to #legacy_find_synced_repositories' do
...@@ -85,7 +85,7 @@ describe Geo::ProjectRegistryFinder, :geo do ...@@ -85,7 +85,7 @@ describe Geo::ProjectRegistryFinder, :geo do
context 'with selective sync' do context 'with selective sync' do
before do before do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
end end
it 'delegates to #legacy_find_synced_wiki' do it 'delegates to #legacy_find_synced_wiki' do
...@@ -125,7 +125,7 @@ describe Geo::ProjectRegistryFinder, :geo do ...@@ -125,7 +125,7 @@ describe Geo::ProjectRegistryFinder, :geo do
context 'with selective sync' do context 'with selective sync' do
before do before do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
end end
it 'delegates to #find_failed_repositories' do it 'delegates to #find_failed_repositories' do
...@@ -165,7 +165,7 @@ describe Geo::ProjectRegistryFinder, :geo do ...@@ -165,7 +165,7 @@ describe Geo::ProjectRegistryFinder, :geo do
context 'with selective sync' do context 'with selective sync' do
before do before do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
end end
it 'delegates to #find_failed_wikis' do it 'delegates to #find_failed_wikis' do
...@@ -212,7 +212,7 @@ describe Geo::ProjectRegistryFinder, :geo do ...@@ -212,7 +212,7 @@ describe Geo::ProjectRegistryFinder, :geo do
context 'with selective sync' do context 'with selective sync' do
before do before do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
end end
it 'delegates to #legacy_find_filtered_failed_projects' do it 'delegates to #legacy_find_filtered_failed_projects' do
...@@ -265,7 +265,7 @@ describe Geo::ProjectRegistryFinder, :geo do ...@@ -265,7 +265,7 @@ describe Geo::ProjectRegistryFinder, :geo do
end end
it 'delegates to #legacy_find_unsynced_projects when node has selective sync' do it 'delegates to #legacy_find_unsynced_projects when node has selective sync' do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
expect(subject).to receive(:legacy_find_unsynced_projects).and_call_original expect(subject).to receive(:legacy_find_unsynced_projects).and_call_original
...@@ -290,7 +290,7 @@ describe Geo::ProjectRegistryFinder, :geo do ...@@ -290,7 +290,7 @@ describe Geo::ProjectRegistryFinder, :geo do
end end
it 'delegates to #legacy_find_projects_updated_recently when node has selective sync' do it 'delegates to #legacy_find_projects_updated_recently when node has selective sync' do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
expect(subject).to receive(:legacy_find_projects_updated_recently).and_call_original expect(subject).to receive(:legacy_find_projects_updated_recently).and_call_original
......
...@@ -204,7 +204,13 @@ describe Gitlab::Geo::LogCursor::Daemon, :postgresql, :clean_gitlab_redis_shared ...@@ -204,7 +204,13 @@ describe Gitlab::Geo::LogCursor::Daemon, :postgresql, :clean_gitlab_redis_shared
end end
it 'does not replay events for projects that do not belong to selected namespaces to replicate' do it 'does not replay events for projects that do not belong to selected namespaces to replicate' do
secondary.update!(namespaces: [group_2]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [group_2])
expect { daemon.run_once! }.not_to change(Geo::ProjectRegistry, :count)
end
it 'does not replay events for projects that do not belong to selected shards to replicate' do
secondary.update!(selective_sync_type: 'shards', selective_sync_shards: ['broken'])
expect { daemon.run_once! }.not_to change(Geo::ProjectRegistry, :count) expect { daemon.run_once! }.not_to change(Geo::ProjectRegistry, :count)
end end
......
...@@ -21,6 +21,10 @@ describe GeoNode, type: :model do ...@@ -21,6 +21,10 @@ describe GeoNode, type: :model do
it { is_expected.to have_many(:namespaces).through(:geo_node_namespace_links) } it { is_expected.to have_many(:namespaces).through(:geo_node_namespace_links) }
end end
context 'validations' do
it { is_expected.to validate_inclusion_of(:selective_sync_type).in_array([nil, *GeoNode::SELECTIVE_SYNC_TYPES]) }
end
context 'default values' do context 'default values' do
where(:attribute, :value) do where(:attribute, :value) do
:url | Gitlab::Routing.url_helpers.root_url :url | Gitlab::Routing.url_helpers.root_url
...@@ -254,27 +258,43 @@ describe GeoNode, type: :model do ...@@ -254,27 +258,43 @@ describe GeoNode, type: :model do
end end
describe '#projects_include?' do describe '#projects_include?' do
let(:unsynced_project) { create(:project) } let(:unsynced_project) { create(:project, repository_storage: 'broken') }
it 'returns true without namespace restrictions' do it 'returns true without selective sync' do
expect(node.projects_include?(unsynced_project.id)).to eq true expect(node.projects_include?(unsynced_project.id)).to eq true
end end
context 'with namespace restrictions' do context 'selective sync by namespaces' do
let(:synced_group) { create(:group) } let(:synced_group) { create(:group) }
before do before do
node.update_attribute(:namespaces, [synced_group]) node.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
end end
it 'returns true when project belongs to one of the namespaces' do it 'returns true when project belongs to one of the namespaces' do
project_in_synced_group = create(:project, group: synced_group) project_in_synced_group = create(:project, group: synced_group)
expect(node.projects_include?(project_in_synced_group.id)).to eq true expect(node.projects_include?(project_in_synced_group.id)).to be_truthy
end
it 'returns false when project does not belong to one of the namespaces' do
expect(node.projects_include?(unsynced_project.id)).to be_falsy
end
end
context 'selective sync by shards' do
before do
node.update!(selective_sync_type: 'shards', selective_sync_shards: ['default'])
end
it 'returns true when project belongs to one of the namespaces' do
project_in_synced_shard = create(:project)
expect(node.projects_include?(project_in_synced_shard.id)).to be_truthy
end end
it 'returns false when project does not belong to one of the namespaces' do it 'returns false when project does not belong to one of the namespaces' do
expect(node.projects_include?(unsynced_project.id)).to eq false expect(node.projects_include?(unsynced_project.id)).to be_falsy
end end
end end
end end
...@@ -285,28 +305,54 @@ describe GeoNode, type: :model do ...@@ -285,28 +305,54 @@ describe GeoNode, type: :model do
let(:nested_group_1) { create(:group, parent: group_1) } let(:nested_group_1) { create(:group, parent: group_1) }
let!(:project_1) { create(:project, group: group_1) } let!(:project_1) { create(:project, group: group_1) }
let!(:project_2) { create(:project, group: nested_group_1) } let!(:project_2) { create(:project, group: nested_group_1) }
let!(:project_3) { create(:project, group: group_2) } let!(:project_3) { create(:project, group: group_2, repository_storage: 'broken') }
it 'returns all projects without selective sync' do it 'returns all projects without selective sync' do
expect(node.projects).to match_array([project_1, project_2, project_3]) expect(node.projects).to match_array([project_1, project_2, project_3])
end end
it 'returns projects that belong to the namespaces with selective sync' do it 'returns projects that belong to the namespaces with selective sync by namespace' do
node.update_attribute(:namespaces, [group_1, nested_group_1]) node.update!(selective_sync_type: 'namespaces', namespaces: [group_1, nested_group_1])
expect(node.projects).to match_array([project_1, project_2]) expect(node.projects).to match_array([project_1, project_2])
end end
it 'returns projects that belong to the shards with selective sync by shard' do
node.update!(selective_sync_type: 'shards', selective_sync_shards: ['default'])
expect(node.projects).to match_array([project_1, project_2])
end
it 'returns nothing if an unrecognised selective sync type is used' do
node.update_attribute(:selective_sync_type, 'unknown')
expect(node.projects).to be_empty
end
end end
describe '#selective_sync?' do describe '#selective_sync?' do
it 'returns true when Geo node has namespace restrictions' do subject { node.selective_sync? }
node.update_attribute(:namespaces, [create(:group)])
it 'returns true when selective sync is by namespaces' do
node.update!(selective_sync_type: 'namespaces')
is_expected.to be_truthy
end
it 'returns true when selective sync is by shards' do
node.update!(selective_sync_type: 'shards')
expect(node.selective_sync?).to be true is_expected.to be_truthy
end end
it 'returns false when Geo node does not have namespace restrictions' do it 'returns false when selective sync is disabled' do
expect(node.selective_sync?).to be false node.update!(
selective_sync_type: '',
namespaces: [create(:group)],
selective_sync_shards: ['default']
)
is_expected.to be_falsy
end end
end end
end end
...@@ -132,7 +132,7 @@ describe GeoNodeStatus, :geo do ...@@ -132,7 +132,7 @@ describe GeoNodeStatus, :geo do
end end
it 'returns the right percentage with group restrictions' do it 'returns the right percentage with group restrictions' do
secondary.update_attribute(:namespaces, [group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [group])
create(:geo_file_registry, :avatar, file_id: upload_1.id) create(:geo_file_registry, :avatar, file_id: upload_1.id)
create(:geo_file_registry, :avatar, file_id: upload_2.id) create(:geo_file_registry, :avatar, file_id: upload_2.id)
...@@ -191,7 +191,7 @@ describe GeoNodeStatus, :geo do ...@@ -191,7 +191,7 @@ describe GeoNodeStatus, :geo do
end end
it 'returns the right percentage with group restrictions' do it 'returns the right percentage with group restrictions' do
secondary.update_attribute(:namespaces, [group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [group])
create(:geo_file_registry, :lfs, file_id: lfs_object_project.lfs_object_id, success: true) create(:geo_file_registry, :lfs, file_id: lfs_object_project.lfs_object_id, success: true)
expect(subject.lfs_objects_synced_in_percentage).to be_within(0.0001).of(50) expect(subject.lfs_objects_synced_in_percentage).to be_within(0.0001).of(50)
...@@ -266,7 +266,7 @@ describe GeoNodeStatus, :geo do ...@@ -266,7 +266,7 @@ describe GeoNodeStatus, :geo do
end end
it 'returns the right number of failed repos with group restrictions' do it 'returns the right number of failed repos with group restrictions' do
secondary.update_attribute(:namespaces, [group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [group])
expect(subject.repositories_failed_count).to eq(1) expect(subject.repositories_failed_count).to eq(1)
end end
...@@ -285,7 +285,7 @@ describe GeoNodeStatus, :geo do ...@@ -285,7 +285,7 @@ describe GeoNodeStatus, :geo do
end end
it 'returns the right number of failed repos with group restrictions' do it 'returns the right number of failed repos with group restrictions' do
secondary.update_attribute(:namespaces, [group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [group])
expect(subject.wikis_failed_count).to eq(1) expect(subject.wikis_failed_count).to eq(1)
end end
...@@ -309,7 +309,7 @@ describe GeoNodeStatus, :geo do ...@@ -309,7 +309,7 @@ describe GeoNodeStatus, :geo do
end end
it 'returns the right percentage with group restrictions' do it 'returns the right percentage with group restrictions' do
secondary.update_attribute(:namespaces, [group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [group])
create(:geo_project_registry, :synced, project: project_1) create(:geo_project_registry, :synced, project: project_1)
expect(subject.repositories_synced_in_percentage).to be_within(0.0001).of(50) expect(subject.repositories_synced_in_percentage).to be_within(0.0001).of(50)
...@@ -336,7 +336,7 @@ describe GeoNodeStatus, :geo do ...@@ -336,7 +336,7 @@ describe GeoNodeStatus, :geo do
end end
it 'returns the right percentage with group restrictions' do it 'returns the right percentage with group restrictions' do
secondary.update_attribute(:namespaces, [group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [group])
create(:geo_project_registry, :synced, project: project_1) create(:geo_project_registry, :synced, project: project_1)
expect(subject.wikis_synced_in_percentage).to be_within(0.0001).of(50) expect(subject.wikis_synced_in_percentage).to be_within(0.0001).of(50)
......
require 'spec_helper' require 'spec_helper'
describe Geo::NodeUpdateService do describe Geo::NodeUpdateService do
let(:groups) { create_list(:group, 2) } set(:primary) { create(:geo_node, :primary) }
let!(:primary) { create(:geo_node, :primary) }
let(:geo_node) { create(:geo_node) } let(:geo_node) { create(:geo_node) }
let(:geo_node_with_restrictions) { create(:geo_node, namespace_ids: [groups.first.id]) } let(:groups) { create_list(:group, 2) }
let(:namespace_ids) { groups.map(&:id).join(',') }
describe '#execute' do describe '#execute' do
it 'updates the node' do it 'updates the node' do
...@@ -31,22 +32,58 @@ describe Geo::NodeUpdateService do ...@@ -31,22 +32,58 @@ describe Geo::NodeUpdateService do
expect(service.execute).to eq false expect(service.execute).to eq false
end end
it 'logs an event to the Geo event log when namespaces change' do context 'selective sync disabled' do
service = described_class.new(geo_node, namespace_ids: groups.map(&:id).join(',')) it 'does not log an event to the Geo event log when adding restrictions' do
service = described_class.new(geo_node, namespace_ids: namespace_ids, selective_sync_shards: ['default'])
expect { service.execute }.to change(Geo::RepositoriesChangedEvent, :count).by(1) expect { service.execute }.not_to change(Geo::RepositoriesChangedEvent, :count)
end
end end
it 'does not log an event to the Geo event log when removing namespace restrictions' do context 'selective sync by namespaces' do
service = described_class.new(geo_node_with_restrictions, namespace_ids: '') let(:restricted_geo_node) { create(:geo_node, selective_sync_type: 'namespaces', namespaces: [create(:group)]) }
it 'logs an event to the Geo event log when adding namespace restrictions' do
service = described_class.new(restricted_geo_node, namespace_ids: namespace_ids)
expect { service.execute }.to change(Geo::RepositoriesChangedEvent, :count).by(1)
end
it 'does not log an event to the Geo event log when removing namespace restrictions' do
service = described_class.new(restricted_geo_node, namespace_ids: '')
expect { service.execute }.not_to change(Geo::RepositoriesChangedEvent, :count)
end
expect { service.execute }.not_to change(Geo::RepositoriesChangedEvent, :count) it 'does not log an event to the Geo event log when node is a primary node' do
primary.update!(selective_sync_type: 'namespaces')
service = described_class.new(primary, namespace_ids: namespace_ids)
expect { service.execute }.not_to change(Geo::RepositoriesChangedEvent, :count)
end
end end
it 'does not log an event to the Geo event log when node is a primary node' do context 'selective sync by shards' do
service = described_class.new(primary, namespace_ids: groups.map(&:id).join(',')) let(:restricted_geo_node) { create(:geo_node, selective_sync_type: 'shards', selective_sync_shards: ['default']) }
it 'logs an event to the Geo event log when adding shard restrictions' do
service = described_class.new(restricted_geo_node, selective_sync_shards: %w[default broken])
expect { service.execute }.to change(Geo::RepositoriesChangedEvent, :count).by(1)
end
it 'does not log an event to the Geo event log when removing shard restrictions' do
service = described_class.new(restricted_geo_node, selective_sync_shards: [])
expect { service.execute }.not_to change(Geo::RepositoriesChangedEvent, :count)
end
it 'does not log an event to the Geo event log when node is a primary node' do
primary.update!(selective_sync_type: 'shards')
service = described_class.new(primary, selective_sync_shards: %w[default broken'])
expect { service.execute }.not_to change(Geo::RepositoriesChangedEvent, :count) expect { service.execute }.not_to change(Geo::RepositoriesChangedEvent, :count)
end
end end
end end
end end
...@@ -181,7 +181,7 @@ describe Geo::FileDownloadDispatchWorker, :geo do ...@@ -181,7 +181,7 @@ describe Geo::FileDownloadDispatchWorker, :geo do
before do before do
allow(ProjectCacheWorker).to receive(:perform_async).and_return(true) allow(ProjectCacheWorker).to receive(:perform_async).and_return(true)
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
end end
it 'does not perform Geo::FileDownloadWorker for LFS object that does not belong to selected namespaces to replicate' do it 'does not perform Geo::FileDownloadWorker for LFS object that does not belong to selected namespaces to replicate' do
......
...@@ -10,7 +10,7 @@ describe Geo::RepositoriesCleanUpWorker do ...@@ -10,7 +10,7 @@ describe Geo::RepositoriesCleanUpWorker do
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(:geo_node) { create(:geo_node, namespaces: [synced_group]) } let(:geo_node) { create(:geo_node, selective_sync_type: 'namespaces', namespaces: [synced_group]) }
context 'legacy storage' do context 'legacy storage' do
it 'performs GeoRepositoryDestroyWorker for each project that does not belong to selected namespaces to replicate' do it 'performs GeoRepositoryDestroyWorker for each project that does not belong to selected namespaces to replicate' do
......
...@@ -104,7 +104,7 @@ describe Geo::RepositoryShardSyncWorker, :geo, :delete, :clean_gitlab_redis_cach ...@@ -104,7 +104,7 @@ describe Geo::RepositoryShardSyncWorker, :geo, :delete, :clean_gitlab_redis_cach
context 'when node has namespace restrictions' do context 'when node has namespace restrictions' do
before do before do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
end end
it 'does not perform Geo::ProjectSyncWorker for projects that do not belong to selected namespaces to replicate' do it 'does not perform Geo::ProjectSyncWorker for projects that do not belong to selected namespaces to replicate' do
......
...@@ -9,6 +9,8 @@ describe Geo::RepositorySyncWorker, :geo, :clean_gitlab_redis_cache do ...@@ -9,6 +9,8 @@ describe Geo::RepositorySyncWorker, :geo, :clean_gitlab_redis_cache do
let!(:project_in_synced_group) { create(:project, group: synced_group) } let!(:project_in_synced_group) { create(:project, group: synced_group) }
let!(:unsynced_project) { create(:project) } let!(:unsynced_project) { create(:project) }
let(:healthy_shard) { project_in_synced_group.repository.storage }
subject { described_class.new } subject { described_class.new }
before do before do
...@@ -32,6 +34,21 @@ describe Geo::RepositorySyncWorker, :geo, :clean_gitlab_redis_cache do ...@@ -32,6 +34,21 @@ describe Geo::RepositorySyncWorker, :geo, :clean_gitlab_redis_cache do
Sidekiq::Testing.inline! { subject.perform } Sidekiq::Testing.inline! { subject.perform }
end end
it 'skips backfill for projects on shards excluded by selective sync' do
secondary.update!(selective_sync_type: 'shards', selective_sync_shards: [healthy_shard])
# Report both shards as healthy
expect(Gitlab::HealthChecks::FsShardsCheck).to receive(:readiness)
.and_return([result(true, healthy_shard), result(true, 'broken')])
expect(Gitlab::HealthChecks::GitalyCheck).to receive(:readiness)
.and_return([result(true, healthy_shard), result(true, 'broken')])
expect(Geo::RepositoryShardSyncWorker).to receive(:perform_async).with('default')
expect(Geo::RepositoryShardSyncWorker).not_to receive(:perform_async).with('broken')
Sidekiq::Testing.inline! { subject.perform }
end
it 'skips backfill for projects on missing shards' do it 'skips backfill for projects on missing shards' do
missing_not_synced = create(:project, group: synced_group) missing_not_synced = create(:project, group: synced_group)
missing_not_synced.update_column(:repository_storage, 'unknown') missing_not_synced.update_column(:repository_storage, 'unknown')
...@@ -52,17 +69,14 @@ describe Geo::RepositorySyncWorker, :geo, :clean_gitlab_redis_cache do ...@@ -52,17 +69,14 @@ describe Geo::RepositorySyncWorker, :geo, :clean_gitlab_redis_cache do
it 'skips backfill for projects with downed Gitaly server' do it 'skips backfill for projects with downed Gitaly server' do
create(:project, group: synced_group, repository_storage: 'broken') create(:project, group: synced_group, repository_storage: 'broken')
unhealthy_dirty = create(:project, group: synced_group, repository_storage: 'broken') unhealthy_dirty = create(:project, group: synced_group, repository_storage: 'broken')
healthy_shard = project_in_synced_group.repository.storage
create(:geo_project_registry, :synced, :repository_dirty, project: unhealthy_dirty) create(:geo_project_registry, :synced, :repository_dirty, project: unhealthy_dirty)
# Report only one healthy shard # Report only one healthy shard
allow(Gitlab::HealthChecks::FsShardsCheck).to receive(:readiness).and_return( expect(Gitlab::HealthChecks::FsShardsCheck).to receive(:readiness)
[Gitlab::HealthChecks::Result.new(true, nil, { shard: healthy_shard }), .and_return([result(true, healthy_shard), result(true, 'broken')])
Gitlab::HealthChecks::Result.new(true, nil, { shard: 'broken' })]) expect(Gitlab::HealthChecks::GitalyCheck).to receive(:readiness)
allow(Gitlab::HealthChecks::GitalyCheck).to receive(:readiness).and_return( .and_return([result(true, healthy_shard), result(false, 'broken')])
[Gitlab::HealthChecks::Result.new(true, nil, { shard: healthy_shard }),
Gitlab::HealthChecks::Result.new(false, nil, { shard: 'broken' })])
expect(Geo::RepositoryShardSyncWorker).to receive(:perform_async).with(healthy_shard) expect(Geo::RepositoryShardSyncWorker).to receive(:perform_async).with(healthy_shard)
expect(Geo::RepositoryShardSyncWorker).not_to receive(:perform_async).with('broken') expect(Geo::RepositoryShardSyncWorker).not_to receive(:perform_async).with('broken')
...@@ -71,4 +85,8 @@ describe Geo::RepositorySyncWorker, :geo, :clean_gitlab_redis_cache do ...@@ -71,4 +85,8 @@ describe Geo::RepositorySyncWorker, :geo, :clean_gitlab_redis_cache do
end end
end end
end end
def result(success, shard)
Gitlab::HealthChecks::Result.new(success, nil, { shard: shard })
end
end end
...@@ -6,13 +6,13 @@ import { mockNodeDetails } from '../mock_data'; ...@@ -6,13 +6,13 @@ import { mockNodeDetails } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper'; import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = ( const createComponent = (
namespaces = mockNodeDetails.namespaces, selectiveSyncType = mockNodeDetails.selectiveSyncType,
lastEvent = mockNodeDetails.lastEvent, lastEvent = mockNodeDetails.lastEvent,
cursorLastEvent = mockNodeDetails.cursorLastEvent) => { cursorLastEvent = mockNodeDetails.cursorLastEvent) => {
const Component = Vue.extend(geoNodeSyncSettingsComponent); const Component = Vue.extend(geoNodeSyncSettingsComponent);
return mountComponent(Component, { return mountComponent(Component, {
namespaces, selectiveSyncType,
lastEvent, lastEvent,
cursorLastEvent, cursorLastEvent,
}); });
...@@ -23,7 +23,7 @@ describe('GeoNodeSyncSettingsComponent', () => { ...@@ -23,7 +23,7 @@ describe('GeoNodeSyncSettingsComponent', () => {
describe('syncType', () => { describe('syncType', () => {
it('returns string representing sync type', () => { it('returns string representing sync type', () => {
const vm = createComponent(); const vm = createComponent();
expect(vm.syncType).toBe('Selective'); expect(vm.syncType).toBe('Selective (namespaces)');
vm.$destroy(); vm.$destroy();
}); });
}); });
......
...@@ -66,6 +66,7 @@ export const rawMockNodeDetails = { ...@@ -66,6 +66,7 @@ export const rawMockNodeDetails = {
last_successful_status_check_timestamp: 1515142330, last_successful_status_check_timestamp: 1515142330,
version: '10.4.0-pre', version: '10.4.0-pre',
revision: 'b93c51849b', revision: 'b93c51849b',
selective_sync_type: 'namespaces',
namespaces: [ namespaces: [
{ {
id: 54, id: 54,
...@@ -151,31 +152,6 @@ export const mockNodeDetails = { ...@@ -151,31 +152,6 @@ export const mockNodeDetails = {
id: 3, id: 3,
timeStamp: 1511255200, timeStamp: 1511255200,
}, },
namespaces: [ selectiveSyncType: 'namespaces',
{
id: 54,
name: 'platform',
path: 'platform',
kind: 'group',
full_path: 'platform',
parent_id: null,
},
{
id: 4,
name: 'Twitter',
path: 'twitter',
kind: 'group',
full_path: 'twitter',
parent_id: null,
},
{
id: 3,
name: 'Documentcloud',
path: 'documentcloud',
kind: 'group',
full_path: 'documentcloud',
parent_id: null,
},
],
dbReplicationLag: 0, dbReplicationLag: 0,
}; };
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