diff --git a/GITLAB_ELASTICSEARCH_INDEXER_VERSION b/GITLAB_ELASTICSEARCH_INDEXER_VERSION index 43270543f7786f5b0a89f03caa73843633aecf22..7e7f33c2e387472f8ead1e579e675455d6b52b6d 100644 --- a/GITLAB_ELASTICSEARCH_INDEXER_VERSION +++ b/GITLAB_ELASTICSEARCH_INDEXER_VERSION @@ -1 +1 @@ -4.3.6 +4.3.7 diff --git a/ee/app/services/ee/search/project_service.rb b/ee/app/services/ee/search/project_service.rb index b9ce0d01d7f8a731cb408348271ac4b6750d666b..c80f3bd12e2c8a1bcde883442e460c7798d14c3d 100644 --- a/ee/app/services/ee/search/project_service.rb +++ b/ee/app/services/ee/search/project_service.rb @@ -20,11 +20,14 @@ def execute sort = params[:sort] if project.is_a?(Array) - project_ids = Array(project).map(&:id) + project_id_root_ancestor_id_hash = project.to_h { |p| [p.id, p.root_ancestor.id] } + project_ids = project_id_root_ancestor_id_hash.keys + root_ancestor_ids = project_id_root_ancestor_id_hash.values ::Gitlab::Elastic::SearchResults.new( current_user, search, project_ids, + root_ancestor_ids: root_ancestor_ids, public_and_internal_projects: false, order_by: order_by, sort: sort, @@ -35,6 +38,7 @@ def execute current_user, search, project: project, + root_ancestor_ids: [project.root_ancestor.id], repository_ref: repository_ref, order_by: order_by, sort: sort, diff --git a/ee/app/workers/elastic_delete_project_worker.rb b/ee/app/workers/elastic_delete_project_worker.rb index 58318eddb380a307dbfe11a4708af831ebe8fd13..89972e8d726042054aa5c91534a39f3bf8c295f7 100644 --- a/ee/app/workers/elastic_delete_project_worker.rb +++ b/ee/app/workers/elastic_delete_project_worker.rb @@ -13,14 +13,13 @@ class ElasticDeleteProjectWorker def perform(project_id, es_id) remove_project_and_children_documents(project_id, es_id) + helper.remove_wikis_from_the_standalone_index(project_id, 'Project') # Wikis have different routing that's why one more query is needed. IndexStatus.for_project(project_id).delete_all end private def indices - helper = Gitlab::Elastic::Helper.default - # some standalone indices may not be created yet if pending advanced search migrations exist standalone_indices = helper.standalone_indices_proxies.select do |klass| alias_name = helper.klass_to_alias_name(klass: klass) @@ -31,7 +30,7 @@ def indices end def remove_project_and_children_documents(project_id, es_id) - Gitlab::Elastic::Helper.default.client.delete_by_query({ + helper.client.delete_by_query({ index: indices, routing: es_id, body: { @@ -70,4 +69,8 @@ def remove_project_and_children_documents(project_id, es_id) } }) end + + def helper + Gitlab::Elastic::Helper.default + end end diff --git a/ee/app/workers/search/wiki/elastic_delete_group_wiki_worker.rb b/ee/app/workers/search/wiki/elastic_delete_group_wiki_worker.rb index 3bb45601eb8a96fdaa5eacf8fd6c92f273eb7573..0cf29b901528d5e38bb71c92da78f180b209c076 100644 --- a/ee/app/workers/search/wiki/elastic_delete_group_wiki_worker.rb +++ b/ee/app/workers/search/wiki/elastic_delete_group_wiki_worker.rb @@ -16,30 +16,7 @@ class ElasticDeleteGroupWikiWorker idempotent! def perform(group_id) - remove_group_wiki_documents(group_id) - end - - private - - def remove_group_wiki_documents(group_id) - Gitlab::Elastic::Helper.default.client.delete_by_query( - { - index: Elastic::Latest::WikiConfig.index_name, - routing: "group_#{group_id}", - conflicts: 'proceed', - body: { - query: { - bool: { - filter: { - term: { - rid: "wiki_group_#{group_id}" - } - } - } - } - } - } - ) + Gitlab::Elastic::Helper.default.remove_wikis_from_the_standalone_index(group_id, 'Group') end end end diff --git a/ee/elastic/docs/20230720000000_reindex_wikis_to_fix_routing.yml b/ee/elastic/docs/20230720000000_reindex_wikis_to_fix_routing.yml new file mode 100644 index 0000000000000000000000000000000000000000..28f9669c802a2249bf895eeb06ca96778afc948c --- /dev/null +++ b/ee/elastic/docs/20230720000000_reindex_wikis_to_fix_routing.yml @@ -0,0 +1,10 @@ +--- +name: ReindexWikisToFixRouting +version: '20230720000000' +description: Change the wiki docs routing from project_{id} or group_{id} to n_{id} +group: group::global search +milestone: 16.3 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125137 +obsolete: false +marked_obsolete_by_url: +marked_obsolete_in_milestone: diff --git a/ee/elastic/migrate/20230720000000_reindex_wikis_to_fix_routing.rb b/ee/elastic/migrate/20230720000000_reindex_wikis_to_fix_routing.rb new file mode 100644 index 0000000000000000000000000000000000000000..e205a4e5606861575ae32086eef91736289f4fde --- /dev/null +++ b/ee/elastic/migrate/20230720000000_reindex_wikis_to_fix_routing.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +class ReindexWikisToFixRouting < Elastic::Migration + include Elastic::MigrationHelper + + batched! + throttle_delay 5.minutes + batch_size 200 + retry_on_failure + + ELASTIC_TIMEOUT = '5m' + SCHEMA_VERSION = 23_07 + + def migrate + if completed? + log 'Migration Completed', total_remaining: remaining_documents_count + return + end + + remaining_rids_to_reindex.each do |rid| + match = rid.match(/wiki_(project|group)_(\d+)/) + ElasticWikiIndexerWorker.perform_in(rand(throttle_delay).seconds, match[2], match[1].capitalize, force: true) + end + end + + def completed? + total_remaining = remaining_documents_count + set_migration_state(documents_remaining: total_remaining) + log('Checking if migration is finished', total_remaining: total_remaining) + total_remaining == 0 + end + + private + + def remaining_rids_to_reindex + results = client.search( + index: index_name, + body: { + size: 0, query: query_with_old_schema_version, aggs: { rids: { terms: { size: batch_size, field: 'rid' } } } + } + ) + rids_hist = results.dig('aggregations', 'rids', 'buckets') || [] + rids_hist.pluck('key') # rubocop: disable CodeReuse/ActiveRecord + end + + def remaining_documents_count + helper.refresh_index(index_name: index_name) + client.count(index: index_name, body: { query: query_with_old_schema_version })['count'] + end + + def query_with_old_schema_version + { range: { schema_version: { lt: SCHEMA_VERSION } } } + end + + def index_name + Elastic::Latest::WikiConfig.index_name + end +end diff --git a/ee/lib/elastic/latest/git_instance_proxy.rb b/ee/lib/elastic/latest/git_instance_proxy.rb index 5f01a8af06aa06e86334a42bc9fbe2afe0aac843..d850705a04a5547dbcfe767055f4b6559b7f85bf 100644 --- a/ee/lib/elastic/latest/git_instance_proxy.rb +++ b/ee/lib/elastic/latest/git_instance_proxy.rb @@ -11,8 +11,11 @@ def methods_for_all_write_targets end end - def es_parent - project_id ? "project_#{project_id}" : "group_#{group_id}" + def es_parent(is_wiki = false) + return "project_#{project_id}" unless is_wiki + return unless ::Elastic::DataMigrationService.migration_has_finished?(:reindex_wikis_to_fix_routing) + + "n_#{project ? project.root_ancestor.id : group.root_ancestor.id}" end def elastic_search(query, type: 'all', page: 1, per: 20, options: {}) @@ -32,28 +35,28 @@ def blob_aggregations(query, options) self.class.blob_aggregations(query, repository_specific_options(options)) end - # If wiki is true and migrate_wikis_to_separate_index is finished then set + # If is_wiki is true and migrate_wikis_to_separate_index is finished then set # index as (#{env}-wikis) # rid as (wiki_project_#{id}) for ProjectWiki and (wiki_group_#{id}) for GroupWiki # If add_suffix_project_in_wiki_rid has not finished then rid might not have prefix(project/group) then # run delete_query_by_rid with sending rid as 'wiki_#{project_id}' - def delete_index_for_commits_and_blobs(wiki: false) - types = wiki ? %w[wiki_blob] : %w[commit blob] + def delete_index_for_commits_and_blobs(is_wiki: false) + types = is_wiki ? %w[wiki_blob] : %w[commit blob] - if (wiki && ::Elastic::DataMigrationService.migration_has_finished?(:migrate_wikis_to_separate_index)) || types.include?('commit') - index, rid = if wiki - [::Elastic::Latest::WikiConfig.index_name, "wiki_#{es_parent}"] + if (is_wiki && ::Elastic::DataMigrationService.migration_has_finished?(:migrate_wikis_to_separate_index)) || types.include?('commit') + index, rid = if is_wiki + [::Elastic::Latest::WikiConfig.index_name, wiki_rid] else [::Elastic::Latest::CommitConfig.index_name, project_id] end - response = delete_query_by_rid(index, rid) + response = delete_query_by_rid(index, rid, is_wiki) # Consider to delete wikis by older rid(without suffix _project) as well - if wiki && project_id && !::Elastic::DataMigrationService.migration_has_finished?(:add_suffix_project_in_wiki_rid) - response = delete_query_by_rid(index, "wiki_#{project_id}") + if is_wiki && project_id && !::Elastic::DataMigrationService.migration_has_finished?(:add_suffix_project_in_wiki_rid) + response = delete_query_by_rid(index, "wiki_#{project_id}", is_wiki) end - return response if wiki # if condition can be removed once the blob gets migrated to the separate index + return response if is_wiki # if condition can be removed once the blob gets migrated to the separate index end client.delete_by_query( @@ -115,6 +118,10 @@ def delete_index_for_commits_and_blobs(wiki: false) private + def wiki_rid + project ? "wiki_project_#{project_id}" : "wiki_group_#{group_id}" + end + def repository_id raise NotImplementedError end @@ -127,24 +134,26 @@ def repository_specific_options(options) options end - def delete_query_by_rid(index, rid) + def delete_query_by_rid(index, rid, is_wiki) client.delete_by_query( - index: index, - routing: es_parent, - conflicts: 'proceed', - body: { - query: { - bool: { - filter: [ - { - term: { - rid: rid + { + index: index, + routing: es_parent(is_wiki), + conflicts: 'proceed', + body: { + query: { + bool: { + filter: [ + { + term: { + rid: rid + } } - } - ] + ] + } } } - } + }.compact ) end end diff --git a/ee/lib/elastic/latest/routing.rb b/ee/lib/elastic/latest/routing.rb index 65c8af5c5b14f2b709cc0968362f7ba11d40cdb9..74d9b4ce7fade7ec95df25489bf744ef31b7591a 100644 --- a/ee/lib/elastic/latest/routing.rb +++ b/ee/lib/elastic/latest/routing.rb @@ -31,10 +31,10 @@ def routing_options(options) private - def build_routing(ids) + def build_routing(ids, prefix: 'project') return [] if ids.count > ES_ROUTING_MAX_COUNT - ids.map { |id| "project_#{id}" }.join(',') + ids.map { |id| "#{prefix}_#{id}" }.join(',') end def routing_disabled?(options) diff --git a/ee/lib/elastic/latest/wiki_class_proxy.rb b/ee/lib/elastic/latest/wiki_class_proxy.rb index abe94e41c4c5af0b0394525e61aa11556e82bcc6..ff48ba956ed4246be95edea5cb62f38ecdacd9d7 100644 --- a/ee/lib/elastic/latest/wiki_class_proxy.rb +++ b/ee/lib/elastic/latest/wiki_class_proxy.rb @@ -3,8 +3,8 @@ module Elastic module Latest class WikiClassProxy < ApplicationClassProxy - include GitClassProxy include Routing + include GitClassProxy def es_type 'wiki_blob' @@ -16,12 +16,12 @@ def elastic_search_as_wiki_page(*args, **kwargs) end end - # Disable the routing for group level search - # Will be enabled from MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125137 def routing_options(options) - return {} if options[:group_ids].present? + return {} if routing_disabled?(options) - super + ids = options[:root_ancestor_ids].presence || [] + routing = build_routing(ids, prefix: 'n') + { routing: routing.presence }.compact end end end diff --git a/ee/lib/gitlab/elastic/helper.rb b/ee/lib/gitlab/elastic/helper.rb index 0a8847369c1ae6bba6d75190ef36dbae81e6d4dd..456b536e0a39d921034fb874fd46f514b7407f03 100644 --- a/ee/lib/gitlab/elastic/helper.rb +++ b/ee/lib/gitlab/elastic/helper.rb @@ -449,6 +449,21 @@ def create_index(index_name:, alias_name:, with_alias:, settings:, mappings:, op def get_alias_info(pattern) client.indices.get_alias(index: pattern) end + + def remove_wikis_from_the_standalone_index(container_id, container_type) + return unless %w[Group Project].include?(container_type) && Wiki.use_separate_indices? + + container = container_type.constantize.find_by_id(container_id) + route = if container && ::Elastic::DataMigrationService.migration_has_finished?(:reindex_wikis_to_fix_routing) + "n_#{container.root_ancestor.id}" + end + + client.delete_by_query({ + index: ::Elastic::Latest::WikiConfig.index_name, + routing: route, + body: { query: { bool: { filter: { term: { rid: "wiki_#{container_type.downcase}_#{container_id}" } } } } } + }.compact) + end end end end diff --git a/ee/lib/gitlab/elastic/indexer.rb b/ee/lib/gitlab/elastic/indexer.rb index e97b6be55269c16c6517ac00bb27e701db42e46d..de502c827ae9477a32a033092c80cc66027326b9 100644 --- a/ee/lib/gitlab/elastic/indexer.rb +++ b/ee/lib/gitlab/elastic/indexer.rb @@ -132,7 +132,7 @@ def run_indexer!(base_sha, to_sha, target) # # @return: whether the index has been purged def purge_unreachable_commits_from_index!(target) - target.delete_index_for_commits_and_blobs(wiki: index_wiki?) + target.delete_index_for_commits_and_blobs(is_wiki: index_wiki?) rescue ::Elasticsearch::Transport::Transport::Errors::BadRequest => e Gitlab::ErrorTracking.track_exception(e, group_id: group&.id, project_id: project&.id) end diff --git a/ee/lib/gitlab/elastic/project_search_results.rb b/ee/lib/gitlab/elastic/project_search_results.rb index 75b5d94a8fd5a3062d527852e1ea1e3ebe23e198..c20b085d289a4d23af0ef6e79b6db82c57188c5c 100644 --- a/ee/lib/gitlab/elastic/project_search_results.rb +++ b/ee/lib/gitlab/elastic/project_search_results.rb @@ -10,11 +10,11 @@ class ProjectSearchResults < Gitlab::Elastic::SearchResults attr_reader :project, :repository_ref, :filters - def initialize(current_user, query, project:, repository_ref: nil, order_by: nil, sort: nil, filters: {}) + def initialize(current_user, query, project:, root_ancestor_ids: nil, repository_ref: nil, order_by: nil, sort: nil, filters: {}) @project = project @repository_ref = repository_ref.presence || project.default_branch - super(current_user, query, [project.id], public_and_internal_projects: false, order_by: order_by, sort: sort, filters: filters) + super(current_user, query, [project.id], root_ancestor_ids: root_ancestor_ids, public_and_internal_projects: false, order_by: order_by, sort: sort, filters: filters) end private @@ -43,7 +43,7 @@ def wiki_blobs(page: 1, per_page: DEFAULT_PER_PAGE, count_only: false) query, page: (page || 1).to_i, per: per_page, - options: base_options.merge(count_only: count_only) + options: scope_options(:wiki_blobs).merge(count_only: count_only) ) end end diff --git a/ee/lib/gitlab/elastic/search_results.rb b/ee/lib/gitlab/elastic/search_results.rb index 4f60266d599448e796e318b4cbcc0f3b0f82f3ee..64b037a01c4de96a919cd4a6958abdce1b514056 100644 --- a/ee/lib/gitlab/elastic/search_results.rb +++ b/ee/lib/gitlab/elastic/search_results.rb @@ -9,16 +9,17 @@ class SearchResults ELASTIC_COUNT_LIMIT = 10000 DEFAULT_PER_PAGE = Gitlab::SearchResults::DEFAULT_PER_PAGE - attr_reader :current_user, :query, :public_and_internal_projects, :order_by, :sort, :filters + attr_reader :current_user, :query, :public_and_internal_projects, :order_by, :sort, :filters, :root_ancestor_ids # Limit search results by passed projects # It allows us to search only for projects user has access to attr_reader :limit_project_ids - def initialize(current_user, query, limit_project_ids = nil, public_and_internal_projects: true, order_by: nil, sort: nil, filters: {}) + def initialize(current_user, query, limit_project_ids = nil, root_ancestor_ids: nil, public_and_internal_projects: true, order_by: nil, sort: nil, filters: {}) @current_user = current_user @query = query @limit_project_ids = limit_project_ids + @root_ancestor_ids = root_ancestor_ids @public_and_internal_projects = public_and_internal_projects @order_by = order_by @sort = sort @@ -299,6 +300,8 @@ def scope_options(scope) base_options.merge(admin: current_user&.admin?, routing_disabled: true) # rubocop:disable Cop/UserAdmin when :blobs base_options.merge(filters.slice(:language)) + when :wiki_blobs + base_options.merge(root_ancestor_ids: root_ancestor_ids, routing_disabled: !reindex_wikis_to_fix_routing_done?) else base_options end @@ -446,6 +449,10 @@ def self.get_highlight_content(result) content_key = use_separate_wiki_index?(result['_source']['type']) ? 'content' : 'blob.content' result.dig('highlight', content_key)&.first || '' end + + def reindex_wikis_to_fix_routing_done? + ::Elastic::DataMigrationService.migration_has_finished?(:reindex_wikis_to_fix_routing) + end end end end diff --git a/ee/spec/elastic/migrate/20230702000000_backfill_existing_group_wiki_spec.rb b/ee/spec/elastic/migrate/20230702000000_backfill_existing_group_wiki_spec.rb index 1eee3bc8c8e78186078f76b7db5135702583adad..8c1ad31da202f26a14597f0967f5511d90c2c915 100644 --- a/ee/spec/elastic/migrate/20230702000000_backfill_existing_group_wiki_spec.rb +++ b/ee/spec/elastic/migrate/20230702000000_backfill_existing_group_wiki_spec.rb @@ -98,7 +98,7 @@ def remove_all_group_wikis helper.client.delete_by_query( index: Elastic::Latest::WikiConfig.index_name, - routing: "group_#{group.id},group_#{group2.id}", + routing: "n_#{group.id},n_#{group2.id}", conflicts: 'proceed', refresh: true, body: { query: { regexp: { rid: "wiki_group_[0-9].*" } } } diff --git a/ee/spec/elastic/migrate/20230720000000_reindex_wikis_to_fix_routing_spec.rb b/ee/spec/elastic/migrate/20230720000000_reindex_wikis_to_fix_routing_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..f48f9c6606b1710e173af18cf218960380a30074 --- /dev/null +++ b/ee/spec/elastic/migrate/20230720000000_reindex_wikis_to_fix_routing_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' +require File.expand_path('ee/elastic/migrate/20230720000000_reindex_wikis_to_fix_routing.rb') + +RSpec.describe ReindexWikisToFixRouting, :elastic_clean, :sidekiq_inline, feature_category: :global_search do + let(:migration) { described_class.new(20230720000000) } + let(:helper) { Gitlab::Elastic::Helper.new } + let(:client) { ::Gitlab::Search::Client.new } + let_it_be(:project) { create(:project, :wiki_repo) } + let_it_be(:group) { create(:group) } + let_it_be(:group_wiki) { create(:group_wiki, group: group) } + let_it_be(:project_wiki) { create(:project_wiki, project: project) } + + before do + stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true) + allow(migration).to receive(:helper).and_return(helper) + set_elasticsearch_migration_to :reindex_wikis_to_fix_routing, including: false + allow(migration).to receive(:client).and_return(client) + [project_wiki, group_wiki].each do |wiki| + wiki.create_page('index_page', 'Bla bla term') + wiki.create_page('index_page2', 'Bla bla term') + wiki.index_wiki_blobs + end + ensure_elasticsearch_index! # ensure objects are indexed + end + + describe 'migration_options' do + it 'has migration options set', :aggregate_failures do + expect(migration).to be_batched + expect(migration.batch_size).to eq 200 + expect(migration.throttle_delay).to eq(5.minutes) + expect(migration).to be_retry_on_failure + end + end + + describe '.migrate' do + context 'if migration is completed' do + it 'performs logging and does not call ElasticWikiIndexerWorker' do + expect(migration).to receive(:log).with("Setting migration_state to #{{ documents_remaining: 0 }.to_json}").once + expect(migration).to receive(:log).with('Checking if migration is finished', { total_remaining: 0 }).once + expect(migration).to receive(:log).with('Migration Completed', { total_remaining: 0 }).once + expect(ElasticWikiIndexerWorker).not_to receive(:perform_in) + migration.migrate + end + end + + context 'if migration is not completed' do + before do + set_old_schema_version_in_three_documents! + end + + it 'performs logging and calls ElasticWikiIndexerWorker' do + expect(migration).to receive(:log).with("Setting migration_state to #{{ documents_remaining: 3 }.to_json}").once + expect(migration).to receive(:log).with('Checking if migration is finished', { total_remaining: 3 }).once + delay = a_value_between(0, migration.throttle_delay.seconds) + expect(ElasticWikiIndexerWorker).to receive(:perform_in).with(delay, project.id.to_s, project.class.name, + force: true) + expect(ElasticWikiIndexerWorker).to receive(:perform_in).with(delay, group.id.to_s, group.class.name, + force: true) + migration.migrate + end + end + end + + describe '.completed?' do + subject { migration.completed? } + + context 'when all the documents have the new schema_version(2307)' do + # With the 4.3.7 GITLAB_ELASTICSEARCH_INDEXER_VERSION all the new wikis will have schema_version 2307 + it 'returns true' do + is_expected.to be true + end + end + + context 'when some items are missing new schema_version' do + before do + set_old_schema_version_in_three_documents! + end + + it 'returns false' do + is_expected.to be false + end + end + end + + def set_old_schema_version_in_three_documents! + client.update_by_query(index: Elastic::Latest::WikiConfig.index_name, max_docs: 3, refresh: true, + body: { script: { lang: 'painless', source: 'ctx._source.schema_version = 2305' } } + ) + end +end diff --git a/ee/spec/lib/ee/gitlab/elastic/helper_spec.rb b/ee/spec/lib/ee/gitlab/elastic/helper_spec.rb index 698e438db2b8821e234d7310cfe10590f9172983..50b569d665dc497a46d716ace7a910b6a8054571 100644 --- a/ee/spec/lib/ee/gitlab/elastic/helper_spec.rb +++ b/ee/spec/lib/ee/gitlab/elastic/helper_spec.rb @@ -737,4 +737,90 @@ it { is_expected.to eq(false) } end end + + describe '#remove_wikis_from_the_standalone_index' do + context 'container_type is other than Group or Project' do + it 'not calls delete_by_query' do + expect(helper.client).not_to receive(:delete_by_query) + helper.remove_wikis_from_the_standalone_index(1, 'Random') + end + end + + context 'Wiki does not use separate indices' do + let_it_be(:project) { create(:project) } + + it 'not calls delete_by_query' do + expect(helper.client).not_to receive(:delete_by_query) + helper.remove_wikis_from_the_standalone_index(project.id, project.class.name) + end + end + + context 'Wiki uses separate indices' do + include ElasticsearchHelpers + let(:body) do + { query: { bool: { filter: { term: { rid: rid } } } } } + end + + let(:index) { Elastic::Latest::WikiConfig.index_name } + + before do + allow(Wiki).to receive(:use_separate_indices?).and_return true + end + + context 'container not found' do + let(:rid) { "wiki_project_#{non_existing_record_id}" } + + it 'calls delete_by_query without routing' do + expect(helper.client).to receive(:delete_by_query).with({ body: body, index: index }) + helper.remove_wikis_from_the_standalone_index(non_existing_record_id, 'Project') + end + end + + context 'container found is Group' do + let_it_be(:group) { create :group } + let(:rid) { "wiki_group_#{group.id}" } + + context 'migration reindex_wikis_to_fix_routing is finished' do + before do + set_elasticsearch_migration_to :reindex_wikis_to_fix_routing, including: true + end + + it 'calls delete_by_query with routing' do + expect(helper.client).to receive(:delete_by_query).with({ body: body, index: index, routing: "n_#{group.root_ancestor.id}" }) + helper.remove_wikis_from_the_standalone_index(group.id, group.class.name) + end + end + + context 'migration reindex_wikis_to_fix_routing is not finished' do + it 'calls delete_by_query without routing' do + expect(helper.client).to receive(:delete_by_query).with({ body: body, index: index }) + helper.remove_wikis_from_the_standalone_index(group.id, group.class.name) + end + end + end + + context 'container found is Project' do + let_it_be(:project) { create :project } + let(:rid) { "wiki_project_#{project.id}" } + + context 'migration reindex_wikis_to_fix_routing is finished' do + before do + set_elasticsearch_migration_to :reindex_wikis_to_fix_routing, including: true + end + + it 'calls delete_by_query with routing' do + expect(helper.client).to receive(:delete_by_query).with({ body: body, index: index, routing: "n_#{project.root_ancestor.id}" }) + helper.remove_wikis_from_the_standalone_index(project.id, project.class.name) + end + end + + context 'migration reindex_wikis_to_fix_routing is not finished' do + it 'calls delete_by_query without routing' do + expect(helper.client).to receive(:delete_by_query).with({ body: body, index: index }) + helper.remove_wikis_from_the_standalone_index(project.id, project.class.name) + end + end + end + end + end end diff --git a/ee/spec/lib/elastic/latest/git_instance_proxy_spec.rb b/ee/spec/lib/elastic/latest/git_instance_proxy_spec.rb index 461abd3e8e20ead73fe2d797abe4fedb8d25a9f7..c7eb7cc64d18358010cd4dd91562a4b94f856f89 100644 --- a/ee/spec/lib/elastic/latest/git_instance_proxy_spec.rb +++ b/ee/spec/lib/elastic/latest/git_instance_proxy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Elastic::Latest::GitInstanceProxy do +RSpec.describe Elastic::Latest::GitInstanceProxy, feature_category: :global_search do let(:project) { create(:project, :repository) } let(:included_class) { Elastic::Latest::RepositoryInstanceProxy } @@ -18,13 +18,54 @@ end describe '#es_parent' do - it 'contains project id for ProjectWiki repository' do - expect(included_class.new(project.wiki.repository).es_parent).to eq("project_#{project.id}") + context 'for wiki is false' do + it 'contains project id' do + expect(included_class.new(project.repository).es_parent).to eq("project_#{project.id}") + end end - it 'contains group id for GroupWiki repository' do - group = create(:group) - expect(included_class.new(group.wiki.repository).es_parent).to eq("group_#{group.id}") + context 'for wiki is true' do + include ElasticsearchHelpers + context 'when migration reindex_wikis_to_fix_routing is finished' do + before do + set_elasticsearch_migration_to(:reindex_wikis_to_fix_routing, including: true) + end + + context 'for ProjectWiki repository' do + it "contains project's root ancestor id" do + repository = project.wiki.repository + expect(included_class.new(repository).es_parent(true)).to eq "n_#{project.root_ancestor.id}" + end + end + + context 'for GroupWiki repository' do + let_it_be(:group) { create :group } + + it "contains group's root ancestor id" do + expect(included_class.new(group.wiki.repository).es_parent(true)).to eq "n_#{group.root_ancestor.id}" + end + end + end + + context 'when migration reindex_wikis_to_fix_routing is not finished' do + before do + set_elasticsearch_migration_to(:reindex_wikis_to_fix_routing, including: false) + end + + context 'for ProjectWiki repository' do + it 'returns nil' do + expect(included_class.new(project.wiki.repository).es_parent(true)).to be nil + end + end + + context 'for GroupWiki repository' do + let_it_be(:group) { create :group } + + it 'returns nil' do + expect(included_class.new(project.wiki.repository).es_parent(true)).to be nil + end + end + end end end diff --git a/ee/spec/lib/elastic/latest/routing_spec.rb b/ee/spec/lib/elastic/latest/routing_spec.rb index 31156fa43d7b4064d3d6227c86c9414deeaa787f..1b94b88b1fe5faae72941f355072b98843b6ba0b 100644 --- a/ee/spec/lib/elastic/latest/routing_spec.rb +++ b/ee/spec/lib/elastic/latest/routing_spec.rb @@ -5,25 +5,12 @@ RSpec.describe Elastic::Latest::Routing, feature_category: :global_search do let(:proxified_class) { Issue } let(:included_class) { Elastic::Latest::ApplicationClassProxy } - let(:project_ids) { [1, 2, 3] } + let(:ids) { [1, 2, 3] } let(:project_routing) { 'project_1,project_2,project_3' } + let(:n_routing) { 'n_1,n_2,n_3' } subject { included_class.new(proxified_class) } - describe '#search' do - it 'calls routing_options with empty hash' do - expect(subject).to receive(:routing_options).and_return({}) - - subject.search('q') - end - - it 'calls routing_options with correct routing' do - expect(subject).to receive(:routing_options).and_return({ routing: project_routing }) - - subject.search('q', project_ids: project_ids) - end - end - describe '#routing_options' do it 'returns correct options for project_id' do expect(subject.routing_options({ project_id: 1 })).to eq({ routing: 'project_1' }) @@ -34,7 +21,7 @@ end it 'returns correct options for project_ids' do - expect(subject.routing_options({ project_ids: project_ids })).to eq({ routing: project_routing }) + expect(subject.routing_options({ project_ids: ids })).to eq({ routing: project_routing }) end it 'returns empty hash when provided an empty array' do @@ -46,7 +33,7 @@ end it 'returns empty hash when public projects flag is passed' do - expect(subject.routing_options({ project_ids: project_ids, public_and_internal_projects: true })).to eq({}) + expect(subject.routing_options({ project_ids: ids, public_and_internal_projects: true })).to eq({}) end it 'returns empty hash when routing_disabled flag is passed' do @@ -54,7 +41,7 @@ end it 'uses project_ids rather than repository_id when both are supplied' do - options = { project_ids: project_ids, repository_id: 'wiki_5' } + options = { project_ids: ids, repository_id: 'wiki_5' } expect(subject.routing_options(options)).to eq({ routing: project_routing }) end diff --git a/ee/spec/lib/elastic/latest/wiki_class_proxy_spec.rb b/ee/spec/lib/elastic/latest/wiki_class_proxy_spec.rb index 37ea42eac07a2afd773f98334655acad62b0924c..05e9e5adeeba433c98b18684e6e19b4f55ab87d0 100644 --- a/ee/spec/lib/elastic/latest/wiki_class_proxy_spec.rb +++ b/ee/spec/lib/elastic/latest/wiki_class_proxy_spec.rb @@ -2,12 +2,12 @@ require 'spec_helper' -RSpec.describe Elastic::Latest::WikiClassProxy, :elastic, feature_category: :global_search do +RSpec.describe Elastic::Latest::WikiClassProxy, feature_category: :global_search do let_it_be(:project) { create(:project, :wiki_repo) } subject { described_class.new(project.wiki.class, use_separate_indices: ProjectWiki.use_separate_indices?) } - describe '#elastic_search_as_wiki_page' do + describe '#elastic_search_as_wiki_page', :elastic do let!(:page) { create(:wiki_page, wiki: project.wiki) } before do @@ -32,9 +32,40 @@ end end - it 'names elasticsearch queries' do + it 'names elasticsearch queries', :elastic do subject.elastic_search_as_wiki_page('*') assert_named_queries('doc:is_a:wiki_blob', 'blob:match:search_terms') end + + describe '#routing_options' do + let(:n_routing) { 'n_1,n_2,n_3' } + let(:ids) { [1, 2, 3] } + let(:default_ops) { { root_ancestor_ids: ids, scope: 'wiki_blob' } } + + context 'when routing is disabled' do + context 'and option routing_disabled is set' do + it 'returns empty hash' do + expect(subject.routing_options(default_ops.merge(routing_disabled: true))).to be_empty + end + end + + context 'and option public_and_internal_projects is set' do + it 'returns empty hash' do + expect(subject.routing_options(default_ops.merge(public_and_internal_projects: true))).to be_empty + end + end + end + + context 'when ids count are more than 128' do + it 'returns empty hash' do + max_count = Elastic::Latest::Routing::ES_ROUTING_MAX_COUNT + expect(subject.routing_options(default_ops.merge(root_ancestor_ids: 1.upto(max_count + 1).to_a))).to be_empty + end + end + + it 'returns routing hash' do + expect(subject.routing_options(default_ops)).to eq({ routing: n_routing }) + end + end end diff --git a/ee/spec/lib/gitlab/elastic/project_search_results_spec.rb b/ee/spec/lib/gitlab/elastic/project_search_results_spec.rb index 56eaf276fa5dc28b17f499f14a96d59051da5306..604c0a71c5466f8b2e07a906439bf7ef65ca426f 100644 --- a/ee/spec/lib/gitlab/elastic/project_search_results_spec.rb +++ b/ee/spec/lib/gitlab/elastic/project_search_results_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do +RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic, feature_category: :global_search do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public, :repository) } diff --git a/ee/spec/workers/search/wiki/elastic_delete_group_wiki_worker_spec.rb b/ee/spec/workers/search/wiki/elastic_delete_group_wiki_worker_spec.rb index 896e3977c1e3a24050f257b49d40c34b4288995e..ac449e40fa6d4937fbc2e25c884c7c8fe392c73d 100644 --- a/ee/spec/workers/search/wiki/elastic_delete_group_wiki_worker_spec.rb +++ b/ee/spec/workers/search/wiki/elastic_delete_group_wiki_worker_spec.rb @@ -21,25 +21,25 @@ end it 'removes all the wikis in the Elastic of the passed group' do - expect(get_wiki_documents_count(group.id, group.class.name)).to be 1 + expect(get_wiki_documents_count(group)).to be 1 described_class.new.perform(group.id) refresh_index! - expect(get_wiki_documents_count(group.id, group.class.name)).to be 0 - expect(get_wiki_documents_count(group2.id, group2.class.name)).to be 1 - expect(get_wiki_documents_count(project.id, project.class.name)).to be 1 + expect(get_wiki_documents_count(group)).to be 0 + expect(get_wiki_documents_count(group2)).to be 1 + expect(get_wiki_documents_count(project)).to be 1 end - def get_wiki_documents_count(container_id, container_type) + def get_wiki_documents_count(container) Gitlab::Elastic::Helper.default.client.search( { index: Elastic::Latest::WikiConfig.index_name, - routing: "#{container_type.downcase}_#{container_id}", + routing: "n_#{container.root_ancestor.id}", body: { query: { bool: { filter: { term: { - rid: "wiki_#{container_type.downcase}_#{container_id}" + rid: "wiki_#{container.class.name.downcase}_#{container.id}" } } }