admin_spec.rb 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. # frozen_string_literal: true
  2. require 'uri'
  3. require_relative 'spec_helper'
  4. describe "Admin" do
  5. let(:processes) { Helpers::Pgcat.single_instance_setup("sharded_db", 10) }
  6. let(:pgcat_conn_str) { processes.pgcat.connection_string("sharded_db", "sharding_user") }
  7. after do
  8. processes.all_databases.map(&:reset)
  9. processes.pgcat.shutdown
  10. end
  11. describe "SHOW STATS" do
  12. context "clients connect and make one query" do
  13. it "updates *_query_time and *_wait_time" do
  14. connections = Array.new(3) { PG::connect("#{pgcat_conn_str}?application_name=one_query") }
  15. connections.each do |c|
  16. Thread.new { c.async_exec("SELECT pg_sleep(0.25)") }
  17. end
  18. sleep(1)
  19. connections.map(&:close)
  20. # wait for averages to be calculated, we shouldn't do this too often
  21. sleep(15.5)
  22. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  23. results = admin_conn.async_exec("SHOW STATS")[0]
  24. admin_conn.close
  25. expect(results["total_query_time"].to_i).to be_within(200).of(750)
  26. expect(results["avg_query_time"].to_i).to be_within(20).of(50)
  27. expect(results["total_wait_time"].to_i).to_not eq(0)
  28. expect(results["avg_wait_time"].to_i).to_not eq(0)
  29. end
  30. end
  31. end
  32. describe "SHOW POOLS" do
  33. context "bad credentials" do
  34. it "does not change any stats" do
  35. bad_password_url = URI(pgcat_conn_str)
  36. bad_password_url.password = "wrong"
  37. expect { PG::connect("#{bad_password_url.to_s}?application_name=bad_password") }.to raise_error(PG::ConnectionBad)
  38. sleep(1)
  39. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  40. results = admin_conn.async_exec("SHOW POOLS")[0]
  41. %w[cl_idle cl_active cl_waiting cl_cancel_req sv_active sv_used sv_tested sv_login maxwait].each do |s|
  42. raise StandardError, "Field #{s} was expected to be 0 but found to be #{results[s]}" if results[s] != "0"
  43. end
  44. expect(results["sv_idle"]).to eq("1")
  45. end
  46. end
  47. context "bad database name" do
  48. it "does not change any stats" do
  49. bad_db_url = URI(pgcat_conn_str)
  50. bad_db_url.path = "/wrong_db"
  51. expect { PG::connect("#{bad_db_url.to_s}?application_name=bad_db") }.to raise_error(PG::ConnectionBad)
  52. sleep(1)
  53. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  54. results = admin_conn.async_exec("SHOW POOLS")[0]
  55. %w[cl_idle cl_active cl_waiting cl_cancel_req sv_active sv_used sv_tested sv_login maxwait].each do |s|
  56. raise StandardError, "Field #{s} was expected to be 0 but found to be #{results[s]}" if results[s] != "0"
  57. end
  58. expect(results["sv_idle"]).to eq("1")
  59. end
  60. end
  61. context "client connects but issues no queries" do
  62. it "only affects cl_idle stats" do
  63. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  64. before_test = admin_conn.async_exec("SHOW POOLS")[0]["sv_idle"]
  65. connections = Array.new(20) { PG::connect(pgcat_conn_str) }
  66. sleep(1)
  67. results = admin_conn.async_exec("SHOW POOLS")[0]
  68. %w[cl_active cl_waiting cl_cancel_req sv_active sv_used sv_tested sv_login maxwait].each do |s|
  69. raise StandardError, "Field #{s} was expected to be 0 but found to be #{results[s]}" if results[s] != "0"
  70. end
  71. expect(results["cl_idle"]).to eq("20")
  72. expect(results["sv_idle"]).to eq(before_test)
  73. connections.map(&:close)
  74. sleep(1.1)
  75. results = admin_conn.async_exec("SHOW POOLS")[0]
  76. %w[cl_active cl_idle cl_waiting cl_cancel_req sv_active sv_used sv_tested sv_login maxwait].each do |s|
  77. raise StandardError, "Field #{s} was expected to be 0 but found to be #{results[s]}" if results[s] != "0"
  78. end
  79. expect(results["sv_idle"]).to eq(before_test)
  80. end
  81. end
  82. context "clients connect and make one query" do
  83. it "only affects cl_idle, sv_idle stats" do
  84. connections = Array.new(5) { PG::connect("#{pgcat_conn_str}?application_name=one_query") }
  85. connections.each do |c|
  86. Thread.new { c.async_exec("SELECT pg_sleep(2.5)") }
  87. end
  88. sleep(1.1)
  89. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  90. results = admin_conn.async_exec("SHOW POOLS")[0]
  91. %w[cl_idle cl_waiting cl_cancel_req sv_idle sv_used sv_tested sv_login maxwait].each do |s|
  92. raise StandardError, "Field #{s} was expected to be 0 but found to be #{results[s]}" if results[s] != "0"
  93. end
  94. expect(results["cl_active"]).to eq("5")
  95. expect(results["sv_active"]).to eq("5")
  96. sleep(3)
  97. results = admin_conn.async_exec("SHOW POOLS")[0]
  98. %w[cl_active cl_waiting cl_cancel_req sv_active sv_used sv_tested sv_login maxwait].each do |s|
  99. raise StandardError, "Field #{s} was expected to be 0 but found to be #{results[s]}" if results[s] != "0"
  100. end
  101. expect(results["cl_idle"]).to eq("5")
  102. expect(results["sv_idle"]).to eq("5")
  103. connections.map(&:close)
  104. sleep(1)
  105. results = admin_conn.async_exec("SHOW POOLS")[0]
  106. %w[cl_idle cl_active cl_waiting cl_cancel_req sv_active sv_used sv_tested sv_login maxwait].each do |s|
  107. raise StandardError, "Field #{s} was expected to be 0 but found to be #{results[s]}" if results[s] != "0"
  108. end
  109. expect(results["sv_idle"]).to eq("5")
  110. end
  111. end
  112. context "client connects and opens a transaction and closes connection uncleanly" do
  113. it "produces correct statistics" do
  114. connections = Array.new(5) { PG::connect("#{pgcat_conn_str}?application_name=one_query") }
  115. connections.each do |c|
  116. Thread.new do
  117. c.async_exec("BEGIN")
  118. c.async_exec("SELECT pg_sleep(0.01)")
  119. c.close
  120. end
  121. end
  122. sleep(1.1)
  123. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  124. results = admin_conn.async_exec("SHOW POOLS")[0]
  125. %w[cl_idle cl_active cl_waiting cl_cancel_req sv_active sv_used sv_tested sv_login maxwait].each do |s|
  126. raise StandardError, "Field #{s} was expected to be 0 but found to be #{results[s]}" if results[s] != "0"
  127. end
  128. expect(results["sv_idle"]).to eq("5")
  129. end
  130. end
  131. context "client fail to checkout connection from the pool" do
  132. it "counts clients as idle" do
  133. new_configs = processes.pgcat.current_config
  134. new_configs["general"]["connect_timeout"] = 500
  135. new_configs["general"]["ban_time"] = 1
  136. new_configs["general"]["shutdown_timeout"] = 1
  137. new_configs["pools"]["sharded_db"]["users"]["0"]["pool_size"] = 1
  138. processes.pgcat.update_config(new_configs)
  139. processes.pgcat.reload_config
  140. threads = []
  141. connections = Array.new(5) { PG::connect("#{pgcat_conn_str}?application_name=one_query") }
  142. connections.each do |c|
  143. threads << Thread.new { c.async_exec("SELECT pg_sleep(1)") rescue PG::SystemError }
  144. end
  145. sleep(2)
  146. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  147. results = admin_conn.async_exec("SHOW POOLS")[0]
  148. %w[cl_active cl_waiting cl_cancel_req sv_active sv_used sv_tested sv_login maxwait].each do |s|
  149. raise StandardError, "Field #{s} was expected to be 0 but found to be #{results[s]}" if results[s] != "0"
  150. end
  151. expect(results["cl_idle"]).to eq("5")
  152. expect(results["sv_idle"]).to eq("1")
  153. threads.map(&:join)
  154. connections.map(&:close)
  155. end
  156. end
  157. context "clients connects and disconnect normally" do
  158. let(:processes) { Helpers::Pgcat.single_instance_setup("sharded_db", 2) }
  159. it 'shows the same number of clients before and after' do
  160. clients_before = clients_connected_to_pool(processes: processes)
  161. threads = []
  162. connections = Array.new(4) { PG::connect("#{pgcat_conn_str}?application_name=one_query") }
  163. connections.each do |c|
  164. threads << Thread.new { c.async_exec("SELECT 1") }
  165. end
  166. clients_between = clients_connected_to_pool(processes: processes)
  167. expect(clients_before).not_to eq(clients_between)
  168. connections.each(&:close)
  169. clients_after = clients_connected_to_pool(processes: processes)
  170. expect(clients_before).to eq(clients_after)
  171. end
  172. end
  173. context "clients connects and disconnect abruptly" do
  174. let(:processes) { Helpers::Pgcat.single_instance_setup("sharded_db", 10) }
  175. it 'shows the same number of clients before and after' do
  176. threads = []
  177. connections = Array.new(2) { PG::connect("#{pgcat_conn_str}?application_name=one_query") }
  178. connections.each do |c|
  179. threads << Thread.new { c.async_exec("SELECT 1") }
  180. end
  181. clients_before = clients_connected_to_pool(processes: processes)
  182. random_string = (0...8).map { (65 + rand(26)).chr }.join
  183. connection_string = "#{pgcat_conn_str}?application_name=#{random_string}"
  184. faulty_client = Process.spawn("psql -Atx #{connection_string} >/dev/null")
  185. sleep(1)
  186. # psql starts two processes, we only know the pid of the parent, this
  187. # ensure both are killed
  188. `pkill -9 -f '#{random_string}'`
  189. Process.wait(faulty_client)
  190. clients_after = clients_connected_to_pool(processes: processes)
  191. expect(clients_before).to eq(clients_after)
  192. end
  193. end
  194. context "clients overwhelm server pools" do
  195. let(:processes) { Helpers::Pgcat.single_instance_setup("sharded_db", 2) }
  196. it "cl_waiting is updated to show it" do
  197. threads = []
  198. connections = Array.new(4) { PG::connect("#{pgcat_conn_str}?application_name=one_query") }
  199. connections.each do |c|
  200. threads << Thread.new { c.async_exec("SELECT pg_sleep(1.5)") }
  201. end
  202. sleep(1.1) # Allow time for stats to update
  203. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  204. results = admin_conn.async_exec("SHOW POOLS")[0]
  205. %w[cl_idle cl_cancel_req sv_idle sv_used sv_tested sv_login maxwait].each do |s|
  206. raise StandardError, "Field #{s} was expected to be 0 but found to be #{results[s]}" if results[s] != "0"
  207. end
  208. expect(results["cl_waiting"]).to eq("2")
  209. expect(results["cl_active"]).to eq("2")
  210. expect(results["sv_active"]).to eq("2")
  211. sleep(2.5) # Allow time for stats to update
  212. results = admin_conn.async_exec("SHOW POOLS")[0]
  213. %w[cl_active cl_waiting cl_cancel_req sv_active sv_used sv_tested sv_login].each do |s|
  214. raise StandardError, "Field #{s} was expected to be 0 but found to be #{results[s]}" if results[s] != "0"
  215. end
  216. expect(results["cl_idle"]).to eq("4")
  217. expect(results["sv_idle"]).to eq("2")
  218. threads.map(&:join)
  219. connections.map(&:close)
  220. end
  221. it "show correct max_wait" do
  222. threads = []
  223. connections = Array.new(4) { PG::connect("#{pgcat_conn_str}?application_name=one_query") }
  224. connections.each do |c|
  225. threads << Thread.new { c.async_exec("SELECT pg_sleep(1.5)") }
  226. end
  227. sleep(2.5) # Allow time for stats to update
  228. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  229. results = admin_conn.async_exec("SHOW POOLS")[0]
  230. expect(results["maxwait"]).to eq("1")
  231. expect(results["maxwait_us"].to_i).to be_within(200_000).of(500_000)
  232. sleep(4.5) # Allow time for stats to update
  233. results = admin_conn.async_exec("SHOW POOLS")[0]
  234. expect(results["maxwait"]).to eq("0")
  235. threads.map(&:join)
  236. connections.map(&:close)
  237. end
  238. end
  239. end
  240. describe "SHOW CLIENTS" do
  241. it "reports correct number and application names" do
  242. conn_str = processes.pgcat.connection_string("sharded_db", "sharding_user")
  243. connections = Array.new(20) { |i| PG::connect("#{conn_str}?application_name=app#{i % 5}") }
  244. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  245. sleep(1) # Wait for stats to be updated
  246. results = admin_conn.async_exec("SHOW CLIENTS")
  247. expect(results.count).to eq(21) # count admin clients
  248. expect(results.select { |c| c["application_name"] == "app3" || c["application_name"] == "app4" }.count).to eq(8)
  249. expect(results.select { |c| c["database"] == "pgcat" }.count).to eq(1)
  250. connections[0..5].map(&:close)
  251. sleep(1) # Wait for stats to be updated
  252. results = admin_conn.async_exec("SHOW CLIENTS")
  253. expect(results.count).to eq(15)
  254. connections[6..].map(&:close)
  255. sleep(1) # Wait for stats to be updated
  256. expect(admin_conn.async_exec("SHOW CLIENTS").count).to eq(1)
  257. admin_conn.close
  258. end
  259. it "reports correct number of queries and transactions" do
  260. conn_str = processes.pgcat.connection_string("sharded_db", "sharding_user")
  261. connections = Array.new(2) { |i| PG::connect("#{conn_str}?application_name=app#{i}") }
  262. connections.each do |c|
  263. c.async_exec("SELECT 1")
  264. c.async_exec("SELECT 2")
  265. c.async_exec("SELECT 3")
  266. c.async_exec("BEGIN")
  267. c.async_exec("SELECT 4")
  268. c.async_exec("SELECT 5")
  269. c.async_exec("COMMIT")
  270. end
  271. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  272. sleep(1) # Wait for stats to be updated
  273. results = admin_conn.async_exec("SHOW CLIENTS")
  274. expect(results.count).to eq(3)
  275. normal_client_results = results.reject { |r| r["database"] == "pgcat" }
  276. expect(normal_client_results[0]["transaction_count"]).to eq("4")
  277. expect(normal_client_results[1]["transaction_count"]).to eq("4")
  278. expect(normal_client_results[0]["query_count"]).to eq("7")
  279. expect(normal_client_results[1]["query_count"]).to eq("7")
  280. admin_conn.close
  281. connections.map(&:close)
  282. end
  283. end
  284. describe "Manual Banning" do
  285. let(:processes) { Helpers::Pgcat.single_shard_setup("sharded_db", 10) }
  286. before do
  287. new_configs = processes.pgcat.current_config
  288. # Prevent immediate unbanning when we ban localhost
  289. new_configs["pools"]["sharded_db"]["shards"]["0"]["servers"][0][0] = "127.0.0.1"
  290. new_configs["pools"]["sharded_db"]["shards"]["0"]["servers"][1][0] = "127.0.0.1"
  291. processes.pgcat.update_config(new_configs)
  292. processes.pgcat.reload_config
  293. end
  294. describe "BAN/UNBAN and SHOW BANS" do
  295. it "bans/unbans hosts" do
  296. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  297. # Returns a list of the banned addresses
  298. results = admin_conn.async_exec("BAN localhost 10").to_a
  299. expect(results.count).to eq(2)
  300. expect(results.map{ |r| r["host"] }.uniq).to eq(["localhost"])
  301. # Subsequent calls should yield no results
  302. results = admin_conn.async_exec("BAN localhost 10").to_a
  303. expect(results.count).to eq(0)
  304. results = admin_conn.async_exec("SHOW BANS").to_a
  305. expect(results.count).to eq(2)
  306. expect(results.map{ |r| r["host"] }.uniq).to eq(["localhost"])
  307. # Returns a list of the unbanned addresses
  308. results = admin_conn.async_exec("UNBAN localhost").to_a
  309. expect(results.count).to eq(2)
  310. expect(results.map{ |r| r["host"] }.uniq).to eq(["localhost"])
  311. # Subsequent calls should yield no results
  312. results = admin_conn.async_exec("UNBAN localhost").to_a
  313. expect(results.count).to eq(0)
  314. results = admin_conn.async_exec("SHOW BANS").to_a
  315. expect(results.count).to eq(0)
  316. end
  317. it "honors ban duration" do
  318. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  319. # Returns a list of the banned addresses
  320. results = admin_conn.async_exec("BAN localhost 1").to_a
  321. expect(results.count).to eq(2)
  322. expect(results.map{ |r| r["host"] }.uniq).to eq(["localhost"])
  323. sleep(2)
  324. # After 2 seconds the ban should be lifted
  325. results = admin_conn.async_exec("SHOW BANS").to_a
  326. expect(results.count).to eq(0)
  327. end
  328. it "can handle bad input" do
  329. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  330. expect { admin_conn.async_exec("BAN").to_a }.to raise_error(PG::SystemError)
  331. expect { admin_conn.async_exec("BAN a").to_a }.to raise_error(PG::SystemError)
  332. expect { admin_conn.async_exec("BAN a a").to_a }.to raise_error(PG::SystemError)
  333. expect { admin_conn.async_exec("BAN a -5").to_a }.to raise_error(PG::SystemError)
  334. expect { admin_conn.async_exec("BAN a 0").to_a }.to raise_error(PG::SystemError)
  335. expect { admin_conn.async_exec("BAN a a a").to_a }.to raise_error(PG::SystemError)
  336. expect { admin_conn.async_exec("UNBAN").to_a }.to raise_error(PG::SystemError)
  337. end
  338. end
  339. end
  340. describe "SHOW users" do
  341. it "returns the right users" do
  342. admin_conn = PG::connect(processes.pgcat.admin_connection_string)
  343. results = admin_conn.async_exec("SHOW USERS")[0]
  344. admin_conn.close
  345. expect(results["name"]).to eq("sharding_user")
  346. expect(results["pool_mode"]).to eq("transaction")
  347. end
  348. end
  349. end