diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index de8251411549cfd28301a58444570df1ad449207..a9d25a0a67906f1cd92c00d3b05a1ddb3f98c221 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -757,6 +757,8 @@ - 1 - - search_zoekt_initial_indexing_event - 1 +- - search_zoekt_lost_node_event + - 1 - - search_zoekt_namespace_indexer - 1 - - search_zoekt_namespace_initial_indexing diff --git a/doc/integration/exact_code_search/zoekt.md b/doc/integration/exact_code_search/zoekt.md index 8d8eb7f319ca2c5894b831219a9bbcdcd3b1d915..783afc75d446af90c0c4c7252b2d8bbe4d695e0c 100644 --- a/doc/integration/exact_code_search/zoekt.md +++ b/doc/integration/exact_code_search/zoekt.md @@ -56,6 +56,23 @@ To enable [exact code search](../../user/search/exact_code_search.md) in GitLab: 1. Select the **Enable indexing for exact code search** and **Enable exact code search** checkboxes. 1. Select **Save changes**. +## Delete offline nodes automatically + +Prerequisites: + +- You must have administrator access to the instance. + +You can automatically delete Zoekt nodes that are offline for more than 12 hours +and their related indices, repositories, and tasks. + +To delete offline nodes automatically: + +1. On the left sidebar, at the bottom, select **Admin**. +1. Select **Settings > Search**. +1. Expand **Exact code search configuration**. +1. Select the **Delete offline nodes automatically after 12 hours** checkbox. +1. Select **Save changes**. + ## Index root namespaces automatically Prerequisites: diff --git a/ee/app/events/search/zoekt/lost_node_event.rb b/ee/app/events/search/zoekt/lost_node_event.rb new file mode 100644 index 0000000000000000000000000000000000000000..0c96cafd11944e79c4a9ca6139173aaee783d8e0 --- /dev/null +++ b/ee/app/events/search/zoekt/lost_node_event.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Search + module Zoekt + class LostNodeEvent < ::Gitlab::EventStore::Event + def schema + { + 'type' => 'object', + 'properties' => { + 'zoekt_node_id' => { 'type' => 'integer' } + }, + 'required' => %w[zoekt_node_id] + } + end + end + end +end diff --git a/ee/app/helpers/ee/application_settings_helper.rb b/ee/app/helpers/ee/application_settings_helper.rb index a1ef2515f422338f4d7056fc6f594a039453d983..c47bee24bc42c14908738b416a093cae96745fdb 100644 --- a/ee/app/helpers/ee/application_settings_helper.rb +++ b/ee/app/helpers/ee/application_settings_helper.rb @@ -76,6 +76,7 @@ def visible_attributes :duo_features_enabled, :lock_duo_features_enabled, :duo_availability, + :zoekt_auto_delete_lost_nodes, :zoekt_auto_index_root_namespace, :zoekt_cpu_to_tasks_ratio, :zoekt_indexing_enabled, @@ -251,6 +252,12 @@ def sync_purl_types_checkboxes(form) def zoekt_settings_checkboxes(form) [ + form.gitlab_ui_checkbox_component( + :zoekt_auto_delete_lost_nodes, + format(_("Delete offline nodes automatically after %{label}"), + label: ::Search::Zoekt::Node::LOST_DURATION_THRESHOLD.inspect), + checkbox_options: { checked: @application_setting.zoekt_auto_delete_lost_nodes, multiple: false } + ), form.gitlab_ui_checkbox_component( :zoekt_auto_index_root_namespace, _('Index root namespaces automatically'), diff --git a/ee/app/models/ee/application_setting.rb b/ee/app/models/ee/application_setting.rb index 0aa44fff2f21e0b2d7679fad94986f9427abd8a4..94fe028f986966e3e25aa19fff76ba4d46f4d818 100644 --- a/ee/app/models/ee/application_setting.rb +++ b/ee/app/models/ee/application_setting.rb @@ -21,6 +21,7 @@ module ApplicationSetting use_clickhouse_for_analytics: [:boolean, { default: false }] jsonb_accessor :zoekt_settings, + zoekt_auto_delete_lost_nodes: [:boolean, { default: true }], zoekt_indexing_enabled: [:boolean, { default: false }], zoekt_indexing_paused: [:boolean, { default: false }], zoekt_search_enabled: [:boolean, { default: false }], diff --git a/ee/app/models/search/zoekt/node.rb b/ee/app/models/search/zoekt/node.rb index ab7d4eb419ed5491993056254ed9605aae93545c..03139243991f6b0b33c0c06e93fedf85ac35065b 100644 --- a/ee/app/models/search/zoekt/node.rb +++ b/ee/app/models/search/zoekt/node.rb @@ -7,6 +7,8 @@ class Node < ApplicationRecord DEFAULT_CONCURRENCY_LIMIT = 20 MAX_CONCURRENCY_LIMIT = 200 + LOST_DURATION_THRESHOLD = 12.hours + ONLINE_DURATION_THRESHOLD = 1.minute WATERMARK_LIMIT_LOW = 0.6 WATERMARK_LIMIT_HIGH = 0.7 WATERMARK_LIMIT_CRITICAL = 0.85 @@ -29,7 +31,8 @@ class Node < ApplicationRecord attribute :metadata, :ind_jsonb # for indifferent access scope :by_name, ->(*names) { where("metadata->>'name' IN (?)", names) } - scope :online, -> { where(last_seen_at: 1.minute.ago..) } + scope :lost, -> { where(last_seen_at: ..LOST_DURATION_THRESHOLD.ago) } + scope :online, -> { where(last_seen_at: ONLINE_DURATION_THRESHOLD.ago..) } def self.find_or_initialize_by_task_request(params) params = params.with_indifferent_access @@ -50,6 +53,15 @@ def self.find_or_initialize_by_task_request(params) end end + def self.marking_lost_enabled? + return false if Feature.disabled?(:zoekt_internal_api_register_nodes, Feature.current_request) + return false if Gitlab::CurrentSettings.zoekt_indexing_paused? + return false unless Gitlab::CurrentSettings.zoekt_indexing_enabled? + return false unless Gitlab::CurrentSettings.zoekt_auto_delete_lost_nodes? + + true + end + def concurrency_limit override = metadata['concurrency_override'].to_i return override if override > 0 @@ -95,6 +107,10 @@ def storage_percent_used used_bytes / total_bytes.to_f end + + def lost? + last_seen_at <= LOST_DURATION_THRESHOLD.ago + end end end end diff --git a/ee/app/models/search/zoekt/repository.rb b/ee/app/models/search/zoekt/repository.rb index 4fb01a59b56e6066103a7424e3d5ae0c7bf6c537..ea1d65c6968b42b569d9de057e478c4275777c62 100644 --- a/ee/app/models/search/zoekt/repository.rb +++ b/ee/app/models/search/zoekt/repository.rb @@ -45,6 +45,8 @@ class Repository < ApplicationRecord where(state: [:orphaned, :pending_deletion]) end + scope :for_zoekt_indices, ->(indices) { where(zoekt_index: indices) } + def self.create_tasks(project_id:, zoekt_index:, task_type:, perform_at:) project = Project.find_by_id(project_id) find_or_initialize_by(project_identifier: project_id, project: project, zoekt_index: zoekt_index).tap do |item| diff --git a/ee/app/services/search/zoekt/scheduling_service.rb b/ee/app/services/search/zoekt/scheduling_service.rb index 098b847c645f497f0d6c16d8f9d224bacca365d2..4e806b461bc32b17b7d7722445c87c2a8218115e 100644 --- a/ee/app/services/search/zoekt/scheduling_service.rb +++ b/ee/app/services/search/zoekt/scheduling_service.rb @@ -6,19 +6,20 @@ class SchedulingService include Gitlab::Loggable TASKS = %i[ + auto_index_self_managed dot_com_rollout eviction - remove_expired_subscriptions - node_assignment - mark_indices_as_ready - initial_indexing - auto_index_self_managed - update_replica_states - report_metrics index_should_be_marked_as_orphaned_check index_to_delete_check + initial_indexing + lost_nodes_check + mark_indices_as_ready + node_assignment + remove_expired_subscriptions repo_should_be_marked_as_orphaned_check repo_to_delete_check + report_metrics + update_replica_states ].freeze BUFFER_FACTOR = 3 @@ -379,6 +380,17 @@ def repo_to_delete_check end end end + + def lost_nodes_check + return false if Rails.env.development? + return false unless Node.marking_lost_enabled? + + execute_every 10.minutes, cache_key: :lost_nodes_check do + Node.lost.select(:id).find_each do |node| + Gitlab::EventStore.publish(Search::Zoekt::LostNodeEvent.new(data: { zoekt_node_id: node.id })) + end + end + end end end end diff --git a/ee/app/validators/json_schemas/application_setting_zoekt_settings.json b/ee/app/validators/json_schemas/application_setting_zoekt_settings.json index 5debded421c80a0cad8a7ceb998b63907e34d098..ef95b32d61eaf825bc3dac0c79ddf142b89035d4 100644 --- a/ee/app/validators/json_schemas/application_setting_zoekt_settings.json +++ b/ee/app/validators/json_schemas/application_setting_zoekt_settings.json @@ -4,6 +4,10 @@ "type": "object", "additionalProperties": false, "properties": { + "zoekt_auto_delete_lost_nodes": { + "type": "boolean", + "description": "If lost nodes and related records should get automatically deleted" + }, "zoekt_auto_index_root_namespace": { "type": "boolean", "description": "If all root namespaces need to be indexed automatically for the instance" diff --git a/ee/app/workers/all_queues.yml b/ee/app/workers/all_queues.yml index 4481806d6fac8517324cbc2f961575f50b8aa9b4..fce13ece967653af4cd1b50cfd28403ec8141c61 100644 --- a/ee/app/workers/all_queues.yml +++ b/ee/app/workers/all_queues.yml @@ -2208,6 +2208,15 @@ :weight: 1 :idempotent: true :tags: [] +- :name: search_zoekt_lost_node_event + :worker_name: Search::Zoekt::LostNodeEventWorker + :feature_category: :global_search + :has_external_dependencies: false + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: search_zoekt_namespace_indexer :worker_name: Search::Zoekt::NamespaceIndexerWorker :feature_category: :global_search diff --git a/ee/app/workers/search/zoekt/lost_node_event_worker.rb b/ee/app/workers/search/zoekt/lost_node_event_worker.rb new file mode 100644 index 0000000000000000000000000000000000000000..a1137373193bfb37dc26ce697a015528396e1013 --- /dev/null +++ b/ee/app/workers/search/zoekt/lost_node_event_worker.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Search + module Zoekt + class LostNodeEventWorker + include Gitlab::EventStore::Subscriber + prepend ::Geo::SkipSecondary + + feature_category :global_search + idempotent! + deduplicate :until_executed + + BATCH_SIZE = 10_000 + + def handle_event(event) + return false unless Search::Zoekt::Node.marking_lost_enabled? + + node = Node.find_by_id(event.data[:zoekt_node_id]) + return unless node + return unless node.lost? + + log_metadata = {} + start_time = Time.current + ApplicationRecord.transaction do + node.lock! + count = 0 + node.tasks.each_batch(of: BATCH_SIZE) { |batch| count += batch.delete_all } + log_metadata[:deleted_tasks_count] = count + indices = node.indices + unless indices.empty? + count = 0 + Repository.for_zoekt_indices(indices).each_batch(of: BATCH_SIZE) { |batch| count += batch.delete_all } + log_metadata[:deleted_repos_count] = count + count = 0 + indices.each_batch(of: BATCH_SIZE) { |batch| count += batch.delete_all } + log_metadata[:deleted_indices_count] = count + end + + node.delete + end + log_metadata[:transaction_time] = Time.current - start_time + log_hash_metadata_on_done(node_id: node.id, node_name: node[:metadata][:name], metadata: log_metadata) + end + end + end +end diff --git a/ee/lib/ee/gitlab/event_store.rb b/ee/lib/ee/gitlab/event_store.rb index 1238077757902de53187303b37b178109440b139..53f3359397cf976408e3031396567a3574e01945 100644 --- a/ee/lib/ee/gitlab/event_store.rb +++ b/ee/lib/ee/gitlab/event_store.rb @@ -177,6 +177,9 @@ def subscribe_to_zoekt_events(store) store.subscribe ::Search::Zoekt::InitialIndexingEventWorker, to: ::Search::Zoekt::InitialIndexingEvent + + store.subscribe ::Search::Zoekt::LostNodeEventWorker, + to: ::Search::Zoekt::LostNodeEvent end end end diff --git a/ee/spec/factories/zoekt/nodes.rb b/ee/spec/factories/zoekt/nodes.rb index c8dc6f69b6234527e08b9e1d49903d6315d7f1c4..3c2227bc37b3d7385b8deb9d8d6794bcc4ffbf1b 100644 --- a/ee/spec/factories/zoekt/nodes.rb +++ b/ee/spec/factories/zoekt/nodes.rb @@ -17,6 +17,14 @@ total_bytes { 100_000_000 } end + trait :offline do + last_seen_at { (Search::Zoekt::Node::ONLINE_DURATION_THRESHOLD + 1.minute).ago } + end + + trait :lost do + last_seen_at { (Search::Zoekt::Node::LOST_DURATION_THRESHOLD + 1.minute).ago } + end + trait :not_enough_free_space do total_bytes { 100_000_000 } used_bytes { 90_000_000 } diff --git a/ee/spec/helpers/ee/application_settings_helper_spec.rb b/ee/spec/helpers/ee/application_settings_helper_spec.rb index e3126e9e7b905e8fbee12a23ad297eb40872e7ee..8269c3acc03e9a08f68b482fdc5d71974948a1b9 100644 --- a/ee/spec/helpers/ee/application_settings_helper_spec.rb +++ b/ee/spec/helpers/ee/application_settings_helper_spec.rb @@ -15,7 +15,7 @@ it 'contains zoekt parameters' do expected_fields = %i[ - zoekt_auto_index_root_namespace zoekt_indexing_enabled + zoekt_auto_delete_lost_nodes zoekt_auto_index_root_namespace zoekt_indexing_enabled zoekt_indexing_paused zoekt_search_enabled zoekt_cpu_to_tasks_ratio ] expect(visible_attributes).to include(*expected_fields) @@ -203,6 +203,7 @@ let_it_be(:application_setting) { build(:application_setting) } before do + application_setting.zoekt_auto_delete_lost_nodes = true application_setting.zoekt_auto_index_root_namespace = false application_setting.zoekt_indexing_enabled = true application_setting.zoekt_indexing_paused = false @@ -213,10 +214,11 @@ it 'returns correctly checked checkboxes' do helper.gitlab_ui_form_for(application_setting, url: advanced_search_admin_application_settings_path) do |form| result = helper.zoekt_settings_checkboxes(form) - expect(result[0]).not_to have_checked_field('Index all the namespaces', with: 1) - expect(result[1]).to have_checked_field('Enable indexing for exact code search', with: 1) - expect(result[2]).not_to have_checked_field('Pause indexing for exact code search', with: 1) - expect(result[3]).to have_checked_field('Enable exact code search', with: 1) + expect(result[0]).to have_checked_field("Delete offline nodes automatically after #{::Search::Zoekt::Node::LOST_DURATION_THRESHOLD.inspect}", with: 1) + expect(result[1]).not_to have_checked_field('Index all the namespaces', with: 1) + expect(result[2]).to have_checked_field('Enable indexing for exact code search', with: 1) + expect(result[3]).not_to have_checked_field('Pause indexing for exact code search', with: 1) + expect(result[4]).to have_checked_field('Enable exact code search', with: 1) end end end diff --git a/ee/spec/lib/ee/gitlab/event_store_spec.rb b/ee/spec/lib/ee/gitlab/event_store_spec.rb index 1dea806eb6cf6473c974e0760e19addebef20e1c..7ac073a451003920474029a8763cccc5a0018c23 100644 --- a/ee/spec/lib/ee/gitlab/event_store_spec.rb +++ b/ee/spec/lib/ee/gitlab/event_store_spec.rb @@ -52,6 +52,7 @@ Search::Zoekt::OrphanedRepoEvent, Search::Zoekt::RepoMarkedAsToDeleteEvent, Search::Zoekt::TaskFailedEvent, + Search::Zoekt::LostNodeEvent, Security::PolicyDeletedEvent, ::Members::MembershipModifiedByAdminEvent ]) diff --git a/ee/spec/models/application_setting_spec.rb b/ee/spec/models/application_setting_spec.rb index f76c07e1bc29051e570990b55d726827ad05bb27..287f2f62e7898b82412bfcf03a8dec5ebea75c00 100644 --- a/ee/spec/models/application_setting_spec.rb +++ b/ee/spec/models/application_setting_spec.rb @@ -24,6 +24,7 @@ it { expect(setting.receptive_cluster_agents_enabled).to eq(false) } it { expect(setting.security_approval_policies_limit).to eq(5) } it { expect(setting.use_clickhouse_for_analytics).to eq(false) } + it { expect(setting.zoekt_auto_delete_lost_nodes).to eq(true) } it { expect(setting.zoekt_auto_index_root_namespace).to eq(false) } it { expect(setting.zoekt_cpu_to_tasks_ratio).to eq(1.0) } it { expect(setting.zoekt_indexing_enabled).to eq(false) } diff --git a/ee/spec/models/search/zoekt/node_spec.rb b/ee/spec/models/search/zoekt/node_spec.rb index 1a4e09ceff3e8c938412949b1827725db554c706..7b75d18241b4fd7ab271c7b0430ec8bc2c12d47e 100644 --- a/ee/spec/models/search/zoekt/node_spec.rb +++ b/ee/spec/models/search/zoekt/node_spec.rb @@ -24,9 +24,18 @@ end describe 'scopes' do - describe '.online' do - let_it_be(:online_node) { create(:zoekt_node, last_seen_at: 1.second.ago) } + describe '.lost', :freeze_time do let_it_be(:offline_node) { create(:zoekt_node, last_seen_at: 10.minutes.ago) } + let_it_be(:lost_node) { create(:zoekt_node, :lost) } + + it 'returns all the lost nodes' do + expect(described_class.lost).to contain_exactly(lost_node) + end + end + + describe '.online', :freeze_time do + let_it_be(:online_node) { create(:zoekt_node) } + let_it_be(:offline_node) { create(:zoekt_node, :offline) } it 'returns nodes considered to be online' do expect(described_class.online).to contain_exactly(node, online_node) @@ -130,6 +139,52 @@ end end + describe '.marking_lost_enabled?', :zoekt_settings_enabled do + it 'returns true' do + expect(described_class.marking_lost_enabled?).to eq true + end + + context 'when FF zoekt_internal_api_register_nodes is disabled' do + before do + stub_feature_flags(zoekt_internal_api_register_nodes: false) + end + + it 'returns false' do + expect(described_class.marking_lost_enabled?).to eq false + end + end + + context 'when application setting zoekt_indexing_paused? is enabled' do + before do + stub_ee_application_setting(zoekt_indexing_paused: true) + end + + it 'returns false' do + expect(described_class.marking_lost_enabled?).to eq false + end + end + + context 'when application setting zoekt_indexing_enabled? is disabled' do + before do + stub_ee_application_setting(zoekt_indexing_enabled: false) + end + + it 'returns false' do + expect(described_class.marking_lost_enabled?).to eq false + end + end + + context 'when application setting zoekt_auto_delete_lost_nodes? is disabled' do + before do + stub_ee_application_setting(zoekt_auto_delete_lost_nodes: false) + end + + it 'returns false' do + expect(described_class.marking_lost_enabled?).to eq false + end + end + end + describe '#backoff' do it 'returns a NodeBackoff' do expect(::Search::Zoekt::NodeBackoff).to receive(:new).with(node).and_return(:backoff) diff --git a/ee/spec/models/search/zoekt/repository_spec.rb b/ee/spec/models/search/zoekt/repository_spec.rb index 191919f3001c9af96471ab8c270052c80dd2001f..61966dde540511b4da739223eddf6b852f77d980 100644 --- a/ee/spec/models/search/zoekt/repository_spec.rb +++ b/ee/spec/models/search/zoekt/repository_spec.rb @@ -45,6 +45,21 @@ expect(described_class.non_ready).to contain_exactly zoekt_repository end end + + describe '.for_zoekt_indices' do + let_it_be(:zoekt_index) { create(:zoekt_index) } + let_it_be(:zoekt_index2) { create(:zoekt_index) } + let_it_be(:zoekt_index3) { create(:zoekt_index) } + let_it_be(:zoekt_repository) { create(:zoekt_repository, zoekt_index: zoekt_index) } + let_it_be(:zoekt_repository2) { create(:zoekt_repository, zoekt_index: zoekt_index) } + let_it_be(:zoekt_repository3) { create(:zoekt_repository, zoekt_index: zoekt_index2) } + + it 'returns records for matching zoekt indices' do + create(:zoekt_repository, zoekt_index: zoekt_index3) + expect(described_class.for_zoekt_indices([zoekt_index, zoekt_index2])).to contain_exactly zoekt_repository, + zoekt_repository2, zoekt_repository3 + end + end end describe '.create_tasks', :freeze_time do diff --git a/ee/spec/services/search/zoekt/scheduling_service_spec.rb b/ee/spec/services/search/zoekt/scheduling_service_spec.rb index ef2c3abf04dfc9ae0d036c37d8f0b663a894a1a3..44170e41083c86eaaab5db896c2fdff6c8e78a0a 100644 --- a/ee/spec/services/search/zoekt/scheduling_service_spec.rb +++ b/ee/spec/services/search/zoekt/scheduling_service_spec.rb @@ -636,4 +636,57 @@ .with(expected_data) end end + + describe '#lost_nodes_check', :zoekt_settings_enabled do + let(:task) { :lost_nodes_check } + let_it_be_with_reload(:lost_node) { create(:zoekt_node, :lost) } + + before do + create(:zoekt_node) + end + + it 'publishes LostNodeEvent' do + expect { execute_task }.to publish_event(Search::Zoekt::LostNodeEvent).with({ zoekt_node_id: lost_node.id }) + end + + context 'when there are no lost nodes' do + before do + Search::Zoekt::Node.update_all(last_seen_at: Time.current) + end + + it 'does not publish LostNodeEvent' do + expect { execute_task }.to not_publish_event(Search::Zoekt::LostNodeEvent) + end + end + + context 'when on development environment' do + before do + allow(Rails.env).to receive(:development?).and_return(true) + end + + it 'does not publish LostNodeEvent' do + expect { execute_task }.to not_publish_event(Search::Zoekt::LostNodeEvent) + end + end + + context 'when marking_lost_enabled? is false' do + before do + allow(Search::Zoekt::Node).to receive(:marking_lost_enabled?).and_return false + end + + it 'does not publish LostNodeEvent' do + expect { execute_task }.to not_publish_event(Search::Zoekt::LostNodeEvent) + end + end + + context 'when marking_lost_enabled? is true' do + before do + allow(Search::Zoekt::Node).to receive(:marking_lost_enabled?).and_return true + end + + it 'publishes LostNodeEvent' do + expect { execute_task }.to publish_event(Search::Zoekt::LostNodeEvent).with({ zoekt_node_id: lost_node.id }) + end + end + end end diff --git a/ee/spec/workers/search/zoekt/lost_node_event_worker_spec.rb b/ee/spec/workers/search/zoekt/lost_node_event_worker_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..4fe4b1fbd507c7f1faaf7e8844710f0077ca11a8 --- /dev/null +++ b/ee/spec/workers/search/zoekt/lost_node_event_worker_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Search::Zoekt::LostNodeEventWorker, :zoekt_settings_enabled, feature_category: :global_search do + let_it_be_with_reload(:node) { create(:zoekt_node, :lost) } + let_it_be(:zoekt_index) { create(:zoekt_index, node: node) } + let_it_be(:zoekt_repository) { create(:zoekt_repository, zoekt_index: zoekt_index) } + let_it_be(:zoekt_task) { create(:zoekt_task, zoekt_repository: zoekt_repository, node: node) } + let(:data) do + { zoekt_node_id: node.id } + end + + let(:event) { Search::Zoekt::LostNodeEvent.new(data: data) } + let(:worker) { described_class.new } + + subject(:execute_event) { worker.perform(event.class.name, event.data) } + + it_behaves_like 'worker with data consistency', described_class, data_consistency: :always + + it_behaves_like 'subscribes to event' + + it_behaves_like 'an idempotent worker' do + before do + allow(Search::Zoekt::Node).to receive(:marking_lost_enabled?).and_return true + end + + context 'when node can not be found' do + let(:data) do + { zoekt_node_id: non_existing_record_id } + end + + it 'does not deletes anything' do + expect { execute_event }.not_to change { Search::Zoekt::Node.count } + expect { execute_event }.not_to change { Search::Zoekt::Task.count } + expect { execute_event }.not_to change { Search::Zoekt::Repository.count } + expect { execute_event }.not_to change { Search::Zoekt::Index.count } + end + end + + context 'when marking_lost_enabled? is false' do + before do + allow(Search::Zoekt::Node).to receive(:marking_lost_enabled?).and_return false + end + + it 'does not deletes anything' do + expect { execute_event }.not_to change { Search::Zoekt::Node.count } + expect { execute_event }.not_to change { Search::Zoekt::Task.count } + expect { execute_event }.not_to change { Search::Zoekt::Repository.count } + expect { execute_event }.not_to change { Search::Zoekt::Index.count } + end + end + + context 'when node is not lost' do + before do + node.update_column :last_seen_at, Time.zone.now + end + + it 'does not deletes anything' do + expect { execute_event }.not_to change { Search::Zoekt::Node.count } + expect { execute_event }.not_to change { Search::Zoekt::Task.count } + expect { execute_event }.not_to change { Search::Zoekt::Repository.count } + expect { execute_event }.not_to change { Search::Zoekt::Index.count } + end + end + + it 'deletes the given node, tasks, indices and repositories attached to this node' do + expect(Search::Zoekt::Node.all).to include(node) + expect(Search::Zoekt::Task.all).to include(zoekt_task) + expect(Search::Zoekt::Repository.all).to include(zoekt_repository) + expect(Search::Zoekt::Index.all).to include(zoekt_index) + tasks_count = node.tasks.count + indices_count = node.indices.count + repos_count = node.indices.reduce(0) { |sum, index| sum + index.zoekt_repositories.count } + log_data = { + node_id: node.id, node_name: node.metadata[:name], metadata: hash_including( + deleted_tasks_count: tasks_count, deleted_repos_count: repos_count, deleted_indices_count: indices_count, + transaction_time: a_kind_of(Float) + ) + } + expect(worker).to receive(:log_hash_metadata_on_done).with(log_data) + execute_event + expect(Search::Zoekt::Node.all).not_to include(node) + expect(Search::Zoekt::Task.all).not_to include(zoekt_task) + expect(Search::Zoekt::Repository.all).not_to include(zoekt_repository) + expect(Search::Zoekt::Index.all).not_to include(zoekt_index) + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 61a5a96d863b88de9039bc032fec0d778037d72e..c7087cb623dd7fcd29f24ccf7dbe356eabc4c447 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -18131,6 +18131,9 @@ msgstr "" msgid "Delete label: %{labelName}" msgstr "" +msgid "Delete offline nodes automatically after %{label}" +msgstr "" + msgid "Delete pipeline" msgstr ""