From eb1473060e80c49128f717d79e5ef9e30584261b Mon Sep 17 00:00:00 2001 From: Lev Kokotov Date: Mon, 28 Feb 2022 08:14:39 -0800 Subject: [PATCH] admin: SHOW CONFIG (#50) * admin: SHOW CONFIG * test --- .circleci/run_tests.sh | 5 +-- src/admin.rs | 91 ++++++++++++++++++++++++++++++++++++++++++ src/config.rs | 46 +++++++++++++++++++++ 3 files changed, 139 insertions(+), 3 deletions(-) diff --git a/.circleci/run_tests.sh b/.circleci/run_tests.sh index abc1a7f..e311904 100644 --- a/.circleci/run_tests.sh +++ b/.circleci/run_tests.sh @@ -59,6 +59,8 @@ cd ../.. # Admin tests psql -e -h 127.0.0.1 -p 6432 -d pgbouncer -c 'SHOW STATS' > /dev/null +psql -h 127.0.0.1 -p 6432 -d pgbouncer -c 'RELOAD' > /dev/null +psql -h 127.0.0.1 -p 6432 -d pgbouncer -c 'SHOW CONFIG' > /dev/null (! psql -e -h 127.0.0.1 -p 6432 -d random_db -c 'SHOW STATS' > /dev/null) # Start PgCat in debug to demonstrate failover better @@ -86,9 +88,6 @@ sed -i 's/pool_mode = "transaction"/pool_mode = "session"/' pgcat.toml # Reload config test kill -SIGHUP $(pgrep pgcat) -# Reload again with the admin database -psql -h 127.0.0.1 -p 6432 -d pgbouncer -c 'RELOAD' > /dev/null - # Prepared statements that will only work in session mode pgbench -h 127.0.0.1 -p 6432 -t 500 -c 2 --protocol prepared diff --git a/src/admin.rs b/src/admin.rs index 97346ed..43c46af 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -2,6 +2,8 @@ use bytes::{Buf, BufMut, BytesMut}; use log::{info, trace}; use tokio::net::tcp::OwnedWriteHalf; +use std::collections::HashMap; + use crate::config::{get_config, parse}; use crate::constants::{OID_NUMERIC, OID_TEXT}; use crate::errors::Error; @@ -27,6 +29,9 @@ pub async fn handle_admin(stream: &mut OwnedWriteHalf, mut query: BytesMut) -> R } else if query.starts_with("RELOAD") { trace!("RELOAD"); reload(stream).await + } else if query.starts_with("SHOW CONFIG") { + trace!("SHOW CONFIG"); + show_config(stream).await } else { Err(Error::ProtocolSyncError) } @@ -61,6 +66,92 @@ pub async fn reload(stream: &mut OwnedWriteHalf) -> Result<(), Error> { write_all_half(stream, res).await } +pub async fn show_config(stream: &mut OwnedWriteHalf) -> Result<(), Error> { + let guard = get_config(); + let config = &*guard.clone(); + let config: HashMap = config.into(); + drop(guard); + + // Configs that cannot be changed dynamically. + let immutables = ["host", "port", "connect_timeout"]; + + // Columns + let columns = ["key", "value", "default", "changeable"]; + + // RowDescription + let mut row_desc = BytesMut::new(); + row_desc.put_i16(4 as i16); // key, value, default, changeable + + for column in columns { + row_desc.put_slice(&format!("{}\0", column).as_bytes()); + + // Doesn't belong to any table + row_desc.put_i32(0); + + // Doesn't belong to any table + row_desc.put_i16(0); + + // Data type + row_desc.put_i32(OID_TEXT); + + // text size = variable (-1) + row_desc.put_i16(-1); + + // Type modifier: none that I know + row_desc.put_i32(-1); + + // Format being used: text (0), binary (1) + row_desc.put_i16(0); + } + + // Response data + let mut res = BytesMut::new(); + res.put_u8(b'T'); + res.put_i32(row_desc.len() as i32 + 4); + res.put(row_desc); + + // DataRow rows + for (key, value) in config { + let mut data_row = BytesMut::new(); + + data_row.put_i16(4 as i16); // key, value, default, changeable + + let key_bytes = key.as_bytes(); + let value = value.as_bytes(); + + data_row.put_i32(key_bytes.len() as i32); + data_row.put_slice(&key_bytes); + + data_row.put_i32(value.len() as i32); + data_row.put_slice(&value); + + data_row.put_i32(1 as i32); + data_row.put_slice(&"-".as_bytes()); + + let changeable = if immutables.iter().filter(|col| *col == &key).count() == 1 { + "no".as_bytes() + } else { + "yes".as_bytes() + }; + data_row.put_i32(changeable.len() as i32); + data_row.put_slice(&changeable); + + res.put_u8(b'D'); + res.put_i32(data_row.len() as i32 + 4); + res.put(data_row); + } + + res.put_u8(b'C'); + res.put_i32("SHOW CONFIG\0".as_bytes().len() as i32 + 4); + res.put_slice(&"SHOW CONFIG\0".as_bytes()); + + res.put_u8(b'Z'); + res.put_i32(5); + res.put_u8(b'I'); + + write_all_half(stream, res).await +} + /// SHOW STATS pub async fn show_stats(stream: &mut OwnedWriteHalf) -> Result<(), Error> { let columns = [ diff --git a/src/config.rs b/src/config.rs index 3cb413a..203f410 100644 --- a/src/config.rs +++ b/src/config.rs @@ -153,6 +153,52 @@ impl Default for Config { } } +impl From<&Config> for std::collections::HashMap { + fn from(config: &Config) -> HashMap { + HashMap::from([ + ("host".to_string(), config.general.host.to_string()), + ("port".to_string(), config.general.port.to_string()), + ( + "pool_size".to_string(), + config.general.pool_size.to_string(), + ), + ( + "pool_mode".to_string(), + config.general.pool_mode.to_string(), + ), + ( + "connect_timeout".to_string(), + config.general.connect_timeout.to_string(), + ), + ( + "healthcheck_timeout".to_string(), + config.general.healthcheck_timeout.to_string(), + ), + ("ban_time".to_string(), config.general.ban_time.to_string()), + ( + "statsd_address".to_string(), + config.general.statsd_address.to_string(), + ), + ( + "default_role".to_string(), + config.query_router.default_role.to_string(), + ), + ( + "query_parser_enabled".to_string(), + config.query_router.query_parser_enabled.to_string(), + ), + ( + "primary_reads_enabled".to_string(), + config.query_router.primary_reads_enabled.to_string(), + ), + ( + "sharding_function".to_string(), + config.query_router.sharding_function.to_string(), + ), + ]) + } +} + impl Config { pub fn show(&self) { info!("Pool size: {}", self.general.pool_size);