ee/spec/support/search/zoekt_process_manager.rb (119 lines of code) (raw):

# frozen_string_literal: true require 'socket' require 'fileutils' require 'timeout' require 'uri' module Search module Zoekt # This class is responsible for starting and stopping Zoekt processes during test runs # # By default, this class will start local Zoekt indexer and webserver processes using # the compiled binary in tmp/tests/gitlab-zoekt/bin/gitlab-zoekt. # # If you want to use external Zoekt services (e.g., running in Docker or on another machine), # you can set the following environment variables: # # - ZOEKT_INDEX_BASE_URL: URL of the Zoekt indexer service (default: http://127.0.0.1:6060) # - ZOEKT_SEARCH_BASE_URL: URL of the Zoekt webserver service (default: http://127.0.0.1:6070) # # When these environment variables are set, the process manager will not start local # processes and will use the provided URLs instead. class ZoektProcessManager ZOEKT_INDEX_PORT = 6060 ZOEKT_SEARCH_PORT = 6070 INDEX_DIR = Rails.root.join("tmp/tests/zoekt-index").to_s LOG_DIR = Rails.root.join("tmp/tests/zoekt-logs").to_s def self.instance @instance ||= new end def self.custom_urls_provided? ENV.key?('ZOEKT_INDEX_BASE_URL') || ENV.key?('ZOEKT_SEARCH_BASE_URL') end def initialize @indexer_pid = nil @webserver_pid = nil @index_base_url = ENV.fetch('ZOEKT_INDEX_BASE_URL', "http://127.0.0.1:#{ZOEKT_INDEX_PORT}") @search_base_url = ENV.fetch('ZOEKT_SEARCH_BASE_URL', "http://127.0.0.1:#{ZOEKT_SEARCH_PORT}") @using_custom_urls = self.class.custom_urls_provided? # Create index directory if it doesn't exist FileUtils.mkdir_p(INDEX_DIR) # Create log directory if it doesn't exist FileUtils.mkdir_p(LOG_DIR) end def start # If custom URLs are provided, don't start local processes return if @using_custom_urls # Return if processes are already running return if @indexer_pid && @webserver_pid && process_running?(@indexer_pid) && process_running?(@webserver_pid) stop if @indexer_pid || @webserver_pid zoekt_binary = Search::Zoekt.bin_path # Create a test secret if it doesn't exist secret_dir = Rails.root.join("tmp/tests").to_s secret_path = File.join(secret_dir, '.gitlab_shell_secret') unless File.exist?(secret_path) FileUtils.mkdir_p(secret_dir) File.write(secret_path, SecureRandom.hex(32)) end # Create log files for stdout and stderr indexer_log = File.join(LOG_DIR, "indexer.log") webserver_log = File.join(LOG_DIR, "webserver.log") # Start indexer process @indexer_pid = spawn( zoekt_binary, 'indexer', "-index_dir", INDEX_DIR, "-listen", ":#{ZOEKT_INDEX_PORT}", "-secret_path", secret_path, out: indexer_log, err: indexer_log ) # Start webserver process @webserver_pid = spawn( zoekt_binary, 'webserver', "-index_dir", INDEX_DIR, "-rpc", "-listen", ":#{ZOEKT_SEARCH_PORT}", "-secret_path", secret_path, out: webserver_log, err: webserver_log ) # Wait for processes to start wait_for_services # Register process termination on exit at_exit { stop } end def stop # If custom URLs are provided, don't stop any processes return if @using_custom_urls # Kill processes if they exist Process.kill("TERM", @indexer_pid) if @indexer_pid && process_running?(@indexer_pid) Process.kill("TERM", @webserver_pid) if @webserver_pid && process_running?(@webserver_pid) # Reset PIDs @indexer_pid = nil @webserver_pid = nil rescue Errno::ESRCH # Process not found, which is fine end def process_running?(pid) Process.getpgid(pid) true rescue Errno::ESRCH false end def wait_for_services if @using_custom_urls # If using custom URLs, verify they are reachable verify_custom_urls_connectivity else # Wait for indexer to start accepting connections wait_for_port(ZOEKT_INDEX_PORT) # Wait for webserver to start accepting connections wait_for_port(ZOEKT_SEARCH_PORT) end end def verify_custom_urls_connectivity # Extract host and port from URLs index_uri = URI.parse(@index_base_url) search_uri = URI.parse(@search_base_url) # Check if the services are reachable begin Timeout.timeout(5) do index_socket = TCPSocket.new(index_uri.host, index_uri.port) index_socket.close search_socket = TCPSocket.new(search_uri.host, search_uri.port) search_socket.close end rescue StandardError => e Rails.logger.warn "Warning: Unable to connect to custom Zoekt services: #{e.message}" Rails.logger.warn "Ensure your ZOEKT_INDEX_BASE_URL (#{@index_base_url}) and " \ "ZOEKT_SEARCH_BASE_URL (#{@search_base_url}) are correct" end end def wait_for_port(port, max_retries = 100, retry_delay = 0.2) retries = 0 begin socket = TCPSocket.new('127.0.0.1', port) socket.close true rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH retries += 1 if retries <= max_retries sleep(retry_delay) retry else false end end end attr_reader :index_base_url, :search_base_url end end end