pgcat_process.rb 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. require 'pg'
  2. require 'toml'
  3. require 'fileutils'
  4. require 'securerandom'
  5. class PgcatProcess
  6. attr_reader :port
  7. attr_reader :pid
  8. def self.finalize(pid, log_filename, config_filename)
  9. if pid
  10. Process.kill("TERM", pid)
  11. Process.wait(pid)
  12. end
  13. File.delete(config_filename) if File.exist?(config_filename)
  14. File.delete(log_filename) if File.exist?(log_filename)
  15. end
  16. def initialize(log_level)
  17. @env = {"RUST_LOG" => log_level}
  18. @port = rand(20000..32760)
  19. @log_level = log_level
  20. @log_filename = "/tmp/pgcat_log_#{SecureRandom.urlsafe_base64}.log"
  21. @config_filename = "/tmp/pgcat_cfg_#{SecureRandom.urlsafe_base64}.toml"
  22. command_path = if ENV['CARGO_TARGET_DIR'] then
  23. "#{ENV['CARGO_TARGET_DIR']}/debug/pgcat"
  24. else
  25. '../../target/debug/pgcat'
  26. end
  27. @command = "#{command_path} #{@config_filename}"
  28. FileUtils.cp("../../pgcat.toml", @config_filename)
  29. cfg = current_config
  30. cfg["general"]["port"] = @port.to_i
  31. cfg["general"]["enable_prometheus_exporter"] = false
  32. update_config(cfg)
  33. end
  34. def logs
  35. File.read(@log_filename)
  36. end
  37. def update_config(config_hash)
  38. @original_config = current_config
  39. output_to_write = TOML::Generator.new(config_hash).body
  40. output_to_write = output_to_write.gsub(/,\s*["|'](\d+)["|']\s*,/, ',\1,')
  41. output_to_write = output_to_write.gsub(/,\s*["|'](\d+)["|']\s*\]/, ',\1]')
  42. File.write(@config_filename, output_to_write)
  43. end
  44. def current_config
  45. loadable_string = File.read(@config_filename)
  46. loadable_string = loadable_string.gsub(/,\s*(\d+)\s*,/, ', "\1",')
  47. loadable_string = loadable_string.gsub(/,\s*(\d+)\s*\]/, ', "\1"]')
  48. TOML.load(loadable_string)
  49. end
  50. def reload_config
  51. `kill -s HUP #{@pid}`
  52. sleep 0.5
  53. end
  54. def start
  55. raise StandardError, "Process is already started" unless @pid.nil?
  56. @pid = Process.spawn(@env, @command, err: @log_filename, out: @log_filename)
  57. Process.detach(@pid)
  58. ObjectSpace.define_finalizer(@log_filename, proc { PgcatProcess.finalize(@pid, @log_filename, @config_filename) })
  59. return self
  60. end
  61. def wait_until_ready(connection_string = nil)
  62. exc = nil
  63. 10.times do
  64. Process.kill 0, @pid
  65. PG::connect(connection_string || example_connection_string).close
  66. return self
  67. rescue Errno::ESRCH
  68. raise StandardError, "Process #{@pid} died. #{logs}"
  69. rescue => e
  70. exc = e
  71. sleep(0.5)
  72. end
  73. puts exc
  74. raise StandardError, "Process #{@pid} never became ready. Logs #{logs}"
  75. end
  76. def stop
  77. return unless @pid
  78. Process.kill("TERM", @pid)
  79. Process.wait(@pid)
  80. @pid = nil
  81. end
  82. def shutdown
  83. stop
  84. File.delete(@config_filename) if File.exist?(@config_filename)
  85. File.delete(@log_filename) if File.exist?(@log_filename)
  86. end
  87. def admin_connection_string
  88. cfg = current_config
  89. username = cfg["general"]["admin_username"]
  90. password = cfg["general"]["admin_password"]
  91. "postgresql://#{username}:#{password}@0.0.0.0:#{@port}/pgcat"
  92. end
  93. def connection_string(pool_name, username, password = nil)
  94. cfg = current_config
  95. user_idx, user_obj = cfg["pools"][pool_name]["users"].detect { |k, user| user["username"] == username }
  96. "postgresql://#{username}:#{password || user_obj["password"]}@0.0.0.0:#{@port}/#{pool_name}"
  97. end
  98. def example_connection_string
  99. cfg = current_config
  100. first_pool_name = cfg["pools"].keys[0]
  101. db_name = first_pool_name
  102. username = cfg["pools"][first_pool_name]["users"]["0"]["username"]
  103. password = cfg["pools"][first_pool_name]["users"]["0"]["password"]
  104. "postgresql://#{username}:#{password}@0.0.0.0:#{@port}/#{db_name}?application_name=example_app"
  105. end
  106. end