From 3d5f710ae6849d3977f178ba0aeeda681eedae37 Mon Sep 17 00:00:00 2001
From: Dmitry Gruzd <dgruzd@gitlab.com>
Date: Mon, 19 Feb 2024 15:39:46 +0000
Subject: [PATCH] Add Zoekt dot_com_rollout scheduling task

---
 .../search/zoekt/scheduling_service.rb        | 55 +++++++++++++++++++
 .../beta/zoekt_dot_com_rollout.yml            |  9 +++
 .../search/zoekt/scheduling_service_spec.rb   | 36 ++++++++++++
 3 files changed, 100 insertions(+)
 create mode 100644 ee/config/feature_flags/beta/zoekt_dot_com_rollout.yml

diff --git a/ee/app/services/search/zoekt/scheduling_service.rb b/ee/app/services/search/zoekt/scheduling_service.rb
index a5141cb67693c..2a54977b40b2f 100644
--- a/ee/app/services/search/zoekt/scheduling_service.rb
+++ b/ee/app/services/search/zoekt/scheduling_service.rb
@@ -6,6 +6,7 @@ class SchedulingService
       include Gitlab::Loggable
 
       TASKS = %i[
+        dot_com_rollout
         remove_expired_subscriptions
         node_assignment
       ].freeze
@@ -13,6 +14,9 @@ class SchedulingService
       BUFFER_FACTOR = 3
       WATERMARK_LIMIT = 0.8
 
+      DOT_COM_ROLLOUT_TARGET_BYTES = 100.gigabytes
+      DOT_COM_ROLLOUT_LIMIT = 2000
+
       attr_reader :task
 
       def initialize(task)
@@ -36,6 +40,57 @@ def logger
         @logger ||= ::Zoekt::Logger.build
       end
 
+      def execute_every(period, cache_key:)
+        Rails.cache.fetch([self.class.name, :execute_every, cache_key], expires_in: period) do
+          yield
+        end
+      end
+
+      # A temporary task to simplify the .com Zoekt rollout
+      # rubocop:disable CodeReuse/ActiveRecord -- this is a temporary task, which will be removed after the rollout
+      def dot_com_rollout
+        return false unless ::Gitlab::Saas.feature_available?(:exact_code_search)
+        return false if Feature.disabled?(:zoekt_dot_com_rollout)
+        return false if EnabledNamespace.with_missing_indices.exists?
+
+        execute_every 6.hours, cache_key: :dot_com_rollout do
+          size = 0
+          sizes = {}
+
+          indexed_namespaces_ids = Search::Zoekt::EnabledNamespace.find_each.map(&:root_namespace_id).to_set
+          namespaces_to_add = GitlabSubscription.with_a_paid_hosted_plan
+                                                .where('end_date > ? OR end_date IS NULL', Date.today)
+          scope = Group.includes(:root_storage_statistics)
+                        .where(parent_id: nil)
+                        .where(id: namespaces_to_add.select(:namespace_id))
+          scope.find_each do |n|
+            next if indexed_namespaces_ids.include?(n.id)
+
+            sizes[n.id] = n.root_storage_statistics.repository_size if n.root_storage_statistics
+          end
+
+          sorted = sizes.to_a.sort_by { |_k, v| v }
+
+          count = 0
+          sorted.take(DOT_COM_ROLLOUT_LIMIT).each do |id, s|
+            size += s
+            break count if size > DOT_COM_ROLLOUT_TARGET_BYTES
+
+            Search::Zoekt::EnabledNamespace.create!(root_namespace_id: id, search: false)
+            count += 1
+          end
+
+          logger.info(build_structured_payload(
+            task: :dot_com_rollout,
+            message: 'Rollout has been completed',
+            namespace_count: count
+          ))
+
+          count
+        end
+      end
+      # rubocop:enable CodeReuse/ActiveRecord
+
       def remove_expired_subscriptions
         return false unless ::Gitlab::Saas.feature_available?(:exact_code_search)
 
diff --git a/ee/config/feature_flags/beta/zoekt_dot_com_rollout.yml b/ee/config/feature_flags/beta/zoekt_dot_com_rollout.yml
new file mode 100644
index 0000000000000..140275a581c5d
--- /dev/null
+++ b/ee/config/feature_flags/beta/zoekt_dot_com_rollout.yml
@@ -0,0 +1,9 @@
+---
+name: zoekt_dot_com_rollout
+feature_issue_url:
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/144988
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/442203
+milestone: '16.10'
+group: group::global search
+type: beta
+default_enabled: false
diff --git a/ee/spec/services/search/zoekt/scheduling_service_spec.rb b/ee/spec/services/search/zoekt/scheduling_service_spec.rb
index 402b746c4d6ae..b951a0c2a5695 100644
--- a/ee/spec/services/search/zoekt/scheduling_service_spec.rb
+++ b/ee/spec/services/search/zoekt/scheduling_service_spec.rb
@@ -42,6 +42,42 @@
     end
   end
 
+  describe '#dot_com_rollout' do
+    let(:task) { :dot_com_rollout }
+
+    it 'returns false unless saas' do
+      expect(execute_task).to eq(false)
+    end
+
+    it 'returns false if there are unassigned namespaces' do
+      create(:zoekt_enabled_namespace)
+
+      expect(execute_task).to eq(false)
+    end
+
+    context 'when on .com', :saas do
+      let_it_be(:group) { create(:group) }
+      let_it_be(:subscription) { create(:gitlab_subscription, namespace: group) }
+      let_it_be(:root_storage_statistics) { create(:namespace_root_storage_statistics, namespace: group) }
+
+      context 'when feature flag is disabled' do
+        before do
+          stub_feature_flags(zoekt_dot_com_rollout: false)
+        end
+
+        it 'returns false' do
+          expect(execute_task).to eq(false)
+        end
+      end
+
+      it 'assigns namespaces to a node' do
+        expect { execute_task }.to change { ::Search::Zoekt::EnabledNamespace.count }.by(1)
+
+        expect(::Search::Zoekt::EnabledNamespace.pluck(:root_namespace_id)).to contain_exactly(group.id)
+      end
+    end
+  end
+
   describe '#remove_expired_subscriptions' do
     let(:task) { :remove_expired_subscriptions }
 
-- 
GitLab