2022-03-10 01:33:29 -08:00
|
|
|
/// Parse the configuration file.
|
2022-06-24 14:52:38 -07:00
|
|
|
use arc_swap::ArcSwap;
|
2022-02-20 22:47:08 -08:00
|
|
|
use log::{error, info};
|
2022-02-19 13:57:35 -08:00
|
|
|
use once_cell::sync::Lazy;
|
2023-02-15 15:19:16 -06:00
|
|
|
use regex::Regex;
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
use serde::{Deserializer, Serializer};
|
2022-07-28 17:42:04 -05:00
|
|
|
use serde_derive::{Deserialize, Serialize};
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
|
2023-02-21 21:53:10 -06:00
|
|
|
use std::collections::hash_map::DefaultHasher;
|
2022-09-23 12:06:07 -07:00
|
|
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
2023-02-21 21:53:10 -06:00
|
|
|
use std::hash::{Hash, Hasher};
|
2022-06-27 17:01:40 -07:00
|
|
|
use std::path::Path;
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
2022-03-10 01:33:29 -08:00
|
|
|
use std::sync::Arc;
|
2022-02-08 09:25:59 -08:00
|
|
|
use tokio::fs::File;
|
|
|
|
|
use tokio::io::AsyncReadExt;
|
|
|
|
|
|
2023-05-02 10:26:40 +02:00
|
|
|
use crate::dns_cache::CachedResolver;
|
2022-02-08 09:25:59 -08:00
|
|
|
use crate::errors::Error;
|
2022-09-20 21:47:32 -04:00
|
|
|
use crate::pool::{ClientServerMap, ConnectionPool};
|
2022-09-28 09:50:14 -04:00
|
|
|
use crate::sharding::ShardingFunction;
|
2023-03-28 17:19:37 +02:00
|
|
|
use crate::stats::AddressStats;
|
2022-06-27 17:01:14 -07:00
|
|
|
use crate::tls::{load_certs, load_keys};
|
2022-02-08 09:25:59 -08:00
|
|
|
|
2022-07-27 21:47:55 -05:00
|
|
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Globally available configuration.
|
2022-02-19 13:57:35 -08:00
|
|
|
static CONFIG: Lazy<ArcSwap<Config>> = Lazy::new(|| ArcSwap::from_pointee(Config::default()));
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Server role: primary or replica.
|
2022-07-28 17:42:04 -05:00
|
|
|
#[derive(Clone, PartialEq, Serialize, Deserialize, Hash, std::cmp::Eq, Debug, Copy)]
|
2022-02-09 20:02:20 -08:00
|
|
|
pub enum Role {
|
2022-09-22 13:07:02 -04:00
|
|
|
#[serde(alias = "primary", alias = "Primary")]
|
2022-02-09 20:02:20 -08:00
|
|
|
Primary,
|
2022-09-22 13:07:02 -04:00
|
|
|
#[serde(alias = "replica", alias = "Replica")]
|
2022-02-09 20:02:20 -08:00
|
|
|
Replica,
|
2023-03-10 06:23:51 -06:00
|
|
|
#[serde(alias = "mirror", alias = "Mirror")]
|
|
|
|
|
Mirror,
|
2022-02-09 20:02:20 -08:00
|
|
|
}
|
|
|
|
|
|
2024-07-15 22:30:26 -05:00
|
|
|
impl std::fmt::Display for Role {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
Role::Primary => write!(f, "primary"),
|
|
|
|
|
Role::Replica => write!(f, "replica"),
|
|
|
|
|
Role::Mirror => write!(f, "mirror"),
|
2022-02-28 17:22:28 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-18 07:10:18 -08:00
|
|
|
impl PartialEq<Option<Role>> for Role {
|
|
|
|
|
fn eq(&self, other: &Option<Role>) -> bool {
|
|
|
|
|
match other {
|
|
|
|
|
None => true,
|
|
|
|
|
Some(role) => *self == *role,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialEq<Role> for Option<Role> {
|
|
|
|
|
fn eq(&self, other: &Role) -> bool {
|
|
|
|
|
match *self {
|
|
|
|
|
None => true,
|
|
|
|
|
Some(role) => role == *other,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Address identifying a PostgreSQL server uniquely.
|
2023-03-28 17:19:37 +02:00
|
|
|
#[derive(Clone, Debug)]
|
2022-02-05 10:02:13 -08:00
|
|
|
pub struct Address {
|
2022-08-25 06:40:56 -07:00
|
|
|
/// Unique ID per addressable Postgres server.
|
2022-03-04 17:04:27 -08:00
|
|
|
pub id: usize,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// Server host.
|
2022-02-05 10:02:13 -08:00
|
|
|
pub host: String,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// Server port.
|
|
|
|
|
pub port: u16,
|
|
|
|
|
|
|
|
|
|
/// Shard number of this Postgres server.
|
2022-02-15 08:18:01 -08:00
|
|
|
pub shard: usize,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// The name of the Postgres database.
|
2022-07-27 21:47:55 -05:00
|
|
|
pub database: String,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// Server role: replica, primary.
|
2022-02-09 20:02:20 -08:00
|
|
|
pub role: Role,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// If it's a replica, number it for reference and failover.
|
2022-08-21 22:40:49 -07:00
|
|
|
pub replica_number: usize,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// Position of the server in the pool for failover.
|
2022-08-21 22:40:49 -07:00
|
|
|
pub address_index: usize,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// The name of the user configured to use this pool.
|
2022-08-17 10:40:47 -05:00
|
|
|
pub username: String,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// The name of this pool (i.e. database name visible to the client).
|
2022-08-21 22:40:49 -07:00
|
|
|
pub pool_name: String,
|
2023-03-10 06:23:51 -06:00
|
|
|
|
|
|
|
|
/// List of addresses to receive mirrored traffic.
|
|
|
|
|
pub mirrors: Vec<Address>,
|
2023-03-28 17:19:37 +02:00
|
|
|
|
|
|
|
|
/// Address stats
|
|
|
|
|
pub stats: Arc<AddressStats>,
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
|
|
|
|
|
/// Number of errors encountered since last successful checkout
|
|
|
|
|
pub error_count: Arc<AtomicU64>,
|
2022-02-05 10:02:13 -08:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Default for Address {
|
|
|
|
|
fn default() -> Address {
|
|
|
|
|
Address {
|
2022-03-04 17:04:27 -08:00
|
|
|
id: 0,
|
2022-02-19 13:57:35 -08:00
|
|
|
host: String::from("127.0.0.1"),
|
2022-08-25 06:40:56 -07:00
|
|
|
port: 5432,
|
2022-02-19 13:57:35 -08:00
|
|
|
shard: 0,
|
2022-07-27 21:47:55 -05:00
|
|
|
database: String::from("database"),
|
2022-02-19 13:57:35 -08:00
|
|
|
role: Role::Replica,
|
2023-10-25 18:11:57 -04:00
|
|
|
replica_number: 0,
|
|
|
|
|
address_index: 0,
|
2022-08-17 10:40:47 -05:00
|
|
|
username: String::from("username"),
|
2022-08-21 22:40:49 -07:00
|
|
|
pool_name: String::from("pool_name"),
|
2023-03-10 06:23:51 -06:00
|
|
|
mirrors: Vec::new(),
|
2023-03-28 17:19:37 +02:00
|
|
|
stats: Arc::new(AddressStats::default()),
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
error_count: Arc::new(AtomicU64::new(0)),
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 09:50:52 -07:00
|
|
|
impl std::fmt::Display for Address {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
|
write!(
|
|
|
|
|
f,
|
|
|
|
|
"[address: {}:{}][database: {}][user: {}]",
|
|
|
|
|
self.host, self.port, self.database, self.username
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-28 17:19:37 +02:00
|
|
|
// We need to implement PartialEq by ourselves so we skip stats in the comparison
|
|
|
|
|
impl PartialEq for Address {
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
self.id == other.id
|
|
|
|
|
&& self.host == other.host
|
|
|
|
|
&& self.port == other.port
|
|
|
|
|
&& self.shard == other.shard
|
|
|
|
|
&& self.address_index == other.address_index
|
|
|
|
|
&& self.replica_number == other.replica_number
|
|
|
|
|
&& self.database == other.database
|
|
|
|
|
&& self.role == other.role
|
|
|
|
|
&& self.username == other.username
|
|
|
|
|
&& self.pool_name == other.pool_name
|
|
|
|
|
&& self.mirrors == other.mirrors
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl Eq for Address {}
|
|
|
|
|
|
|
|
|
|
// We need to implement Hash by ourselves so we skip stats in the comparison
|
|
|
|
|
impl Hash for Address {
|
|
|
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
|
|
|
self.id.hash(state);
|
|
|
|
|
self.host.hash(state);
|
|
|
|
|
self.port.hash(state);
|
|
|
|
|
self.shard.hash(state);
|
|
|
|
|
self.address_index.hash(state);
|
|
|
|
|
self.replica_number.hash(state);
|
|
|
|
|
self.database.hash(state);
|
|
|
|
|
self.role.hash(state);
|
|
|
|
|
self.username.hash(state);
|
|
|
|
|
self.pool_name.hash(state);
|
|
|
|
|
self.mirrors.hash(state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-01 22:49:43 -08:00
|
|
|
impl Address {
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Address name (aka database) used in `SHOW STATS`, `SHOW DATABASES`, and `SHOW POOLS`.
|
2022-03-01 22:49:43 -08:00
|
|
|
pub fn name(&self) -> String {
|
|
|
|
|
match self.role {
|
2022-08-21 22:40:49 -07:00
|
|
|
Role::Primary => format!("{}_shard_{}_primary", self.pool_name, self.shard),
|
2022-07-27 21:47:55 -05:00
|
|
|
Role::Replica => format!(
|
|
|
|
|
"{}_shard_{}_replica_{}",
|
2022-08-21 22:40:49 -07:00
|
|
|
self.pool_name, self.shard, self.replica_number
|
2022-07-27 21:47:55 -05:00
|
|
|
),
|
2023-03-10 06:23:51 -06:00
|
|
|
Role::Mirror => format!(
|
|
|
|
|
"{}_shard_{}_mirror_{}",
|
|
|
|
|
self.pool_name, self.shard, self.replica_number
|
|
|
|
|
),
|
2022-03-01 22:49:43 -08:00
|
|
|
}
|
|
|
|
|
}
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
|
|
|
|
|
pub fn error_count(&self) -> u64 {
|
|
|
|
|
self.error_count.load(Ordering::Relaxed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn increment_error_count(&self) {
|
|
|
|
|
self.error_count.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn reset_error_count(&self) {
|
|
|
|
|
self.error_count.store(0, Ordering::Relaxed);
|
|
|
|
|
}
|
2022-03-01 22:49:43 -08:00
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// PostgreSQL user.
|
2022-09-23 11:32:05 -07:00
|
|
|
#[derive(Clone, PartialEq, Hash, Eq, Serialize, Deserialize, Debug)]
|
2022-02-05 10:02:13 -08:00
|
|
|
pub struct User {
|
2022-07-27 21:47:55 -05:00
|
|
|
pub username: String,
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
pub password: Option<String>,
|
2024-09-10 09:29:45 -05:00
|
|
|
|
|
|
|
|
#[serde(default = "User::default_auth_type")]
|
|
|
|
|
pub auth_type: AuthType,
|
2023-04-18 09:57:17 -07:00
|
|
|
pub server_username: Option<String>,
|
|
|
|
|
pub server_password: Option<String>,
|
2022-07-27 21:47:55 -05:00
|
|
|
pub pool_size: u32,
|
2023-04-26 16:33:26 -07:00
|
|
|
pub min_pool_size: Option<u32>,
|
2023-04-05 15:06:19 -07:00
|
|
|
pub pool_mode: Option<PoolMode>,
|
2023-04-26 16:33:26 -07:00
|
|
|
pub server_lifetime: Option<u64>,
|
2022-09-23 02:00:46 -04:00
|
|
|
#[serde(default)] // 0
|
2022-08-13 13:45:58 -07:00
|
|
|
pub statement_timeout: u64,
|
2023-11-06 12:18:52 -08:00
|
|
|
pub connect_timeout: Option<u64>,
|
|
|
|
|
pub idle_timeout: Option<u64>,
|
2022-02-05 10:02:13 -08:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Default for User {
|
|
|
|
|
fn default() -> User {
|
|
|
|
|
User {
|
2022-07-27 21:47:55 -05:00
|
|
|
username: String::from("postgres"),
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
password: None,
|
2024-09-10 09:29:45 -05:00
|
|
|
auth_type: AuthType::MD5,
|
2023-04-18 09:57:17 -07:00
|
|
|
server_username: None,
|
|
|
|
|
server_password: None,
|
2022-07-27 21:47:55 -05:00
|
|
|
pool_size: 15,
|
2023-04-26 16:33:26 -07:00
|
|
|
min_pool_size: None,
|
2022-08-13 13:45:58 -07:00
|
|
|
statement_timeout: 0,
|
2023-04-05 15:06:19 -07:00
|
|
|
pool_mode: None,
|
2023-04-26 16:33:26 -07:00
|
|
|
server_lifetime: None,
|
2023-11-06 12:18:52 -08:00
|
|
|
connect_timeout: None,
|
|
|
|
|
idle_timeout: None,
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-26 16:33:26 -07:00
|
|
|
impl User {
|
2024-09-10 09:29:45 -05:00
|
|
|
pub fn default_auth_type() -> AuthType {
|
|
|
|
|
AuthType::MD5
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-26 16:33:26 -07:00
|
|
|
fn validate(&self) -> Result<(), Error> {
|
2023-10-10 09:18:21 -07:00
|
|
|
if let Some(min_pool_size) = self.min_pool_size {
|
|
|
|
|
if min_pool_size > self.pool_size {
|
|
|
|
|
error!(
|
|
|
|
|
"min_pool_size of {} cannot be larger than pool_size of {}",
|
|
|
|
|
min_pool_size, self.pool_size
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
2023-04-26 16:33:26 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// General configuration.
|
2022-07-28 17:42:04 -05:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub struct General {
|
2022-09-23 02:00:46 -04:00
|
|
|
#[serde(default = "General::default_host")]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub host: String,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
#[serde(default = "General::default_port")]
|
2023-04-05 15:06:19 -07:00
|
|
|
pub port: u16,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
2022-08-09 15:19:11 -04:00
|
|
|
pub enable_prometheus_exporter: Option<bool>,
|
2023-05-12 09:50:52 -07:00
|
|
|
|
|
|
|
|
#[serde(default = "General::default_prometheus_exporter_port")]
|
2022-08-14 01:25:14 +08:00
|
|
|
pub prometheus_exporter_port: i16,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
#[serde(default = "General::default_connect_timeout")]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub connect_timeout: u64,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
#[serde(default = "General::default_idle_timeout")]
|
|
|
|
|
pub idle_timeout: u64,
|
|
|
|
|
|
2023-02-08 11:35:38 -06:00
|
|
|
#[serde(default = "General::default_tcp_keepalives_idle")]
|
|
|
|
|
pub tcp_keepalives_idle: u64,
|
|
|
|
|
#[serde(default = "General::default_tcp_keepalives_count")]
|
|
|
|
|
pub tcp_keepalives_count: u32,
|
|
|
|
|
#[serde(default = "General::default_tcp_keepalives_interval")]
|
|
|
|
|
pub tcp_keepalives_interval: u64,
|
2023-07-12 13:24:30 -05:00
|
|
|
#[serde(default = "General::default_tcp_user_timeout")]
|
|
|
|
|
pub tcp_user_timeout: u64,
|
2023-02-08 11:35:38 -06:00
|
|
|
|
2022-11-16 22:15:47 -08:00
|
|
|
#[serde(default)] // False
|
|
|
|
|
pub log_client_connections: bool,
|
|
|
|
|
|
|
|
|
|
#[serde(default)] // False
|
|
|
|
|
pub log_client_disconnections: bool,
|
|
|
|
|
|
2023-05-02 10:26:40 +02:00
|
|
|
#[serde(default)] // False
|
|
|
|
|
pub dns_cache_enabled: bool,
|
|
|
|
|
|
|
|
|
|
#[serde(default = "General::default_dns_max_ttl")]
|
|
|
|
|
pub dns_max_ttl: u64,
|
|
|
|
|
|
2022-09-23 02:00:46 -04:00
|
|
|
#[serde(default = "General::default_shutdown_timeout")]
|
2022-08-08 19:01:24 -04:00
|
|
|
pub shutdown_timeout: u64,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
#[serde(default = "General::default_healthcheck_timeout")]
|
|
|
|
|
pub healthcheck_timeout: u64,
|
|
|
|
|
|
|
|
|
|
#[serde(default = "General::default_healthcheck_delay")]
|
2022-08-11 17:42:40 -04:00
|
|
|
pub healthcheck_delay: u64,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
#[serde(default = "General::default_ban_time")]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub ban_time: i64,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
2023-03-24 11:20:30 -04:00
|
|
|
#[serde(default = "General::default_idle_client_in_transaction_timeout")]
|
|
|
|
|
pub idle_client_in_transaction_timeout: u64,
|
|
|
|
|
|
2023-04-26 16:33:26 -07:00
|
|
|
#[serde(default = "General::default_server_lifetime")]
|
|
|
|
|
pub server_lifetime: u64,
|
|
|
|
|
|
2023-06-09 14:35:20 -04:00
|
|
|
#[serde(default = "General::default_server_round_robin")] // False
|
|
|
|
|
pub server_round_robin: bool,
|
|
|
|
|
|
2022-12-16 20:13:13 +01:00
|
|
|
#[serde(default = "General::default_worker_threads")]
|
|
|
|
|
pub worker_threads: usize,
|
|
|
|
|
|
2023-04-10 14:51:01 -07:00
|
|
|
#[serde(default)] // None
|
|
|
|
|
pub autoreload: Option<u64>,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
2022-06-27 09:46:33 -07:00
|
|
|
pub tls_certificate: Option<String>,
|
|
|
|
|
pub tls_private_key: Option<String>,
|
2023-04-30 09:41:46 -07:00
|
|
|
|
|
|
|
|
#[serde(default)] // false
|
|
|
|
|
pub server_tls: bool,
|
|
|
|
|
|
|
|
|
|
#[serde(default)] // false
|
|
|
|
|
pub verify_server_certificate: bool,
|
|
|
|
|
|
2022-07-27 21:47:55 -05:00
|
|
|
pub admin_username: String,
|
|
|
|
|
pub admin_password: String,
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
|
2024-09-10 09:29:45 -05:00
|
|
|
#[serde(default = "General::default_admin_auth_type")]
|
|
|
|
|
pub admin_auth_type: AuthType,
|
|
|
|
|
|
2023-05-03 17:07:23 -07:00
|
|
|
#[serde(default = "General::default_validate_config")]
|
|
|
|
|
pub validate_config: bool,
|
|
|
|
|
|
2023-05-03 09:13:05 -07:00
|
|
|
// Support for auth query
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
pub auth_query: Option<String>,
|
|
|
|
|
pub auth_query_user: Option<String>,
|
|
|
|
|
pub auth_query_password: Option<String>,
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
|
2022-09-23 02:00:46 -04:00
|
|
|
impl General {
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_host() -> String {
|
2022-09-23 02:00:46 -04:00
|
|
|
"0.0.0.0".into()
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-10 09:29:45 -05:00
|
|
|
pub fn default_admin_auth_type() -> AuthType {
|
|
|
|
|
AuthType::MD5
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-05 15:06:19 -07:00
|
|
|
pub fn default_port() -> u16 {
|
2022-09-23 02:00:46 -04:00
|
|
|
5432
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-26 16:33:26 -07:00
|
|
|
pub fn default_server_lifetime() -> u64 {
|
2023-05-31 11:25:42 -04:00
|
|
|
1000 * 60 * 60 // 1 hour
|
2023-04-26 16:33:26 -07:00
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_connect_timeout() -> u64 {
|
2022-09-23 02:00:46 -04:00
|
|
|
1000
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-08 11:35:38 -06:00
|
|
|
// These keepalive defaults should detect a dead connection within 30 seconds.
|
|
|
|
|
// Tokio defaults to disabling keepalives which keeps dead connections around indefinitely.
|
2023-04-11 09:37:16 +08:00
|
|
|
// This can lead to permanent server pool exhaustion
|
2023-02-08 11:35:38 -06:00
|
|
|
pub fn default_tcp_keepalives_idle() -> u64 {
|
|
|
|
|
5 // 5 seconds
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn default_tcp_keepalives_count() -> u32 {
|
|
|
|
|
5 // 5 time
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn default_tcp_keepalives_interval() -> u64 {
|
|
|
|
|
5 // 5 seconds
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 13:24:30 -05:00
|
|
|
pub fn default_tcp_user_timeout() -> u64 {
|
|
|
|
|
10000 // 10000 milliseconds
|
|
|
|
|
}
|
|
|
|
|
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
pub fn default_idle_timeout() -> u64 {
|
2023-06-09 14:35:20 -04:00
|
|
|
600000 // 10 minutes
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_shutdown_timeout() -> u64 {
|
2022-09-23 02:00:46 -04:00
|
|
|
60000
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-02 10:26:40 +02:00
|
|
|
pub fn default_dns_max_ttl() -> u64 {
|
|
|
|
|
30
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_healthcheck_timeout() -> u64 {
|
2022-09-23 02:00:46 -04:00
|
|
|
1000
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_healthcheck_delay() -> u64 {
|
2022-09-23 02:00:46 -04:00
|
|
|
30000
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_ban_time() -> i64 {
|
2022-09-23 02:00:46 -04:00
|
|
|
60
|
|
|
|
|
}
|
2022-12-16 20:13:13 +01:00
|
|
|
|
|
|
|
|
pub fn default_worker_threads() -> usize {
|
|
|
|
|
4
|
|
|
|
|
}
|
2023-03-24 11:20:30 -04:00
|
|
|
|
|
|
|
|
pub fn default_idle_client_in_transaction_timeout() -> u64 {
|
|
|
|
|
0
|
|
|
|
|
}
|
2023-05-03 17:07:23 -07:00
|
|
|
|
|
|
|
|
pub fn default_validate_config() -> bool {
|
|
|
|
|
true
|
|
|
|
|
}
|
2023-05-12 09:50:52 -07:00
|
|
|
|
|
|
|
|
pub fn default_prometheus_exporter_port() -> i16 {
|
|
|
|
|
9930
|
|
|
|
|
}
|
2023-06-09 14:35:20 -04:00
|
|
|
|
|
|
|
|
pub fn default_server_round_robin() -> bool {
|
|
|
|
|
true
|
|
|
|
|
}
|
2022-09-23 02:00:46 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Default for General {
|
|
|
|
|
fn default() -> General {
|
|
|
|
|
General {
|
2022-09-28 09:50:14 -04:00
|
|
|
host: Self::default_host(),
|
|
|
|
|
port: Self::default_port(),
|
2022-08-09 15:19:11 -04:00
|
|
|
enable_prometheus_exporter: Some(false),
|
2022-08-14 01:25:14 +08:00
|
|
|
prometheus_exporter_port: 9930,
|
2022-09-23 02:00:46 -04:00
|
|
|
connect_timeout: General::default_connect_timeout(),
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
idle_timeout: General::default_idle_timeout(),
|
2023-02-08 11:35:38 -06:00
|
|
|
tcp_keepalives_idle: Self::default_tcp_keepalives_idle(),
|
|
|
|
|
tcp_keepalives_count: Self::default_tcp_keepalives_count(),
|
|
|
|
|
tcp_keepalives_interval: Self::default_tcp_keepalives_interval(),
|
2023-07-12 13:24:30 -05:00
|
|
|
tcp_user_timeout: Self::default_tcp_user_timeout(),
|
2022-11-16 22:15:47 -08:00
|
|
|
log_client_connections: false,
|
|
|
|
|
log_client_disconnections: false,
|
2023-05-02 10:26:40 +02:00
|
|
|
dns_cache_enabled: false,
|
|
|
|
|
dns_max_ttl: Self::default_dns_max_ttl(),
|
2023-10-25 18:11:57 -04:00
|
|
|
shutdown_timeout: Self::default_shutdown_timeout(),
|
|
|
|
|
healthcheck_timeout: Self::default_healthcheck_timeout(),
|
|
|
|
|
healthcheck_delay: Self::default_healthcheck_delay(),
|
|
|
|
|
ban_time: Self::default_ban_time(),
|
|
|
|
|
idle_client_in_transaction_timeout: Self::default_idle_client_in_transaction_timeout(),
|
|
|
|
|
server_lifetime: Self::default_server_lifetime(),
|
|
|
|
|
server_round_robin: Self::default_server_round_robin(),
|
|
|
|
|
worker_threads: Self::default_worker_threads(),
|
|
|
|
|
autoreload: None,
|
2022-06-27 09:46:33 -07:00
|
|
|
tls_certificate: None,
|
|
|
|
|
tls_private_key: None,
|
2023-04-30 09:41:46 -07:00
|
|
|
server_tls: false,
|
|
|
|
|
verify_server_certificate: false,
|
2022-07-27 21:47:55 -05:00
|
|
|
admin_username: String::from("admin"),
|
|
|
|
|
admin_password: String::from("admin"),
|
2024-09-10 09:29:45 -05:00
|
|
|
admin_auth_type: AuthType::MD5,
|
2023-10-25 18:11:57 -04:00
|
|
|
validate_config: true,
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
auth_query: None,
|
|
|
|
|
auth_query_user: None,
|
|
|
|
|
auth_query_password: None,
|
2022-07-27 21:47:55 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
/// Pool mode:
|
|
|
|
|
/// - transaction: server serves one transaction,
|
|
|
|
|
/// - session: server is attached to the client.
|
2022-09-23 11:32:05 -07:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy, Hash)]
|
2022-09-23 02:00:46 -04:00
|
|
|
pub enum PoolMode {
|
|
|
|
|
#[serde(alias = "transaction", alias = "Transaction")]
|
|
|
|
|
Transaction,
|
|
|
|
|
|
|
|
|
|
#[serde(alias = "session", alias = "Session")]
|
|
|
|
|
Session,
|
|
|
|
|
}
|
2023-04-05 15:06:19 -07:00
|
|
|
|
2024-09-10 09:29:45 -05:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy, Hash)]
|
|
|
|
|
pub enum AuthType {
|
|
|
|
|
#[serde(alias = "trust", alias = "Trust")]
|
|
|
|
|
Trust,
|
|
|
|
|
|
|
|
|
|
#[serde(alias = "md5", alias = "MD5")]
|
|
|
|
|
MD5,
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-15 22:30:26 -05:00
|
|
|
impl std::fmt::Display for PoolMode {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
PoolMode::Transaction => write!(f, "transaction"),
|
|
|
|
|
PoolMode::Session => write!(f, "session"),
|
2022-09-23 02:00:46 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-17 06:52:18 -06:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy, Hash)]
|
|
|
|
|
pub enum LoadBalancingMode {
|
|
|
|
|
#[serde(alias = "random", alias = "Random")]
|
|
|
|
|
Random,
|
|
|
|
|
|
|
|
|
|
#[serde(alias = "loc", alias = "LOC", alias = "least_outstanding_connections")]
|
|
|
|
|
LeastOutstandingConnections,
|
|
|
|
|
}
|
2024-07-15 22:30:26 -05:00
|
|
|
|
|
|
|
|
impl std::fmt::Display for LoadBalancingMode {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
LoadBalancingMode::Random => write!(f, "random"),
|
2023-01-17 06:52:18 -06:00
|
|
|
LoadBalancingMode::LeastOutstandingConnections => {
|
2024-07-15 22:30:26 -05:00
|
|
|
write!(f, "least_outstanding_connections")
|
2023-01-17 06:52:18 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-23 12:06:07 -07:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
2022-07-27 21:47:55 -05:00
|
|
|
pub struct Pool {
|
2022-09-23 02:00:46 -04:00
|
|
|
#[serde(default = "Pool::default_pool_mode")]
|
|
|
|
|
pub pool_mode: PoolMode,
|
|
|
|
|
|
2023-01-17 06:52:18 -06:00
|
|
|
#[serde(default = "Pool::default_load_balancing_mode")]
|
|
|
|
|
pub load_balancing_mode: LoadBalancingMode,
|
|
|
|
|
|
2023-05-12 09:50:52 -07:00
|
|
|
#[serde(default = "Pool::default_default_role")]
|
2022-07-27 21:47:55 -05:00
|
|
|
pub default_role: String,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
#[serde(default)] // False
|
2022-07-27 21:47:55 -05:00
|
|
|
pub query_parser_enabled: bool,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
2023-08-08 16:10:03 -04:00
|
|
|
pub query_parser_max_length: Option<usize>,
|
|
|
|
|
|
2023-08-08 17:45:48 -07:00
|
|
|
#[serde(default)] // False
|
2023-08-08 16:10:03 -04:00
|
|
|
pub query_parser_read_write_splitting: bool,
|
|
|
|
|
|
2022-09-23 02:00:46 -04:00
|
|
|
#[serde(default)] // False
|
2022-07-27 21:47:55 -05:00
|
|
|
pub primary_reads_enabled: bool,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
2023-05-18 10:46:55 -07:00
|
|
|
/// Maximum time to allow for establishing a new server connection.
|
2022-09-28 09:50:14 -04:00
|
|
|
pub connect_timeout: Option<u64>,
|
2022-09-23 11:32:05 -07:00
|
|
|
|
2023-05-18 10:46:55 -07:00
|
|
|
/// Close idle connections that have been opened for longer than this.
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
pub idle_timeout: Option<u64>,
|
|
|
|
|
|
2023-05-18 10:46:55 -07:00
|
|
|
/// Close server connections that have been opened for longer than this.
|
|
|
|
|
/// Only applied to idle connections. If the connection is actively used for
|
|
|
|
|
/// longer than this period, the pool will not interrupt it.
|
2023-04-26 16:33:26 -07:00
|
|
|
pub server_lifetime: Option<u64>,
|
|
|
|
|
|
2023-05-12 09:50:52 -07:00
|
|
|
#[serde(default = "Pool::default_sharding_function")]
|
2022-09-28 09:50:14 -04:00
|
|
|
pub sharding_function: ShardingFunction,
|
2022-10-25 11:47:41 -07:00
|
|
|
|
|
|
|
|
#[serde(default = "Pool::default_automatic_sharding_key")]
|
|
|
|
|
pub automatic_sharding_key: Option<String>,
|
|
|
|
|
|
2023-02-15 15:19:16 -06:00
|
|
|
pub sharding_key_regex: Option<String>,
|
|
|
|
|
pub shard_id_regex: Option<String>,
|
|
|
|
|
pub regex_search_limit: Option<usize>,
|
|
|
|
|
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
#[serde(default = "Pool::default_default_shard")]
|
|
|
|
|
pub default_shard: DefaultShard,
|
|
|
|
|
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
pub auth_query: Option<String>,
|
|
|
|
|
pub auth_query_user: Option<String>,
|
|
|
|
|
pub auth_query_password: Option<String>,
|
|
|
|
|
|
2023-05-18 10:46:55 -07:00
|
|
|
#[serde(default = "Pool::default_cleanup_server_connections")]
|
|
|
|
|
pub cleanup_server_connections: bool,
|
|
|
|
|
|
2023-08-16 14:01:21 -04:00
|
|
|
#[serde(default)] // False
|
|
|
|
|
pub log_client_parameter_status_changes: bool,
|
|
|
|
|
|
2023-10-25 18:11:57 -04:00
|
|
|
#[serde(default = "Pool::default_prepared_statements_cache_size")]
|
|
|
|
|
pub prepared_statements_cache_size: usize,
|
|
|
|
|
|
2023-05-12 09:50:52 -07:00
|
|
|
pub plugins: Option<Plugins>,
|
2022-09-23 12:06:07 -07:00
|
|
|
pub shards: BTreeMap<String, Shard>,
|
|
|
|
|
pub users: BTreeMap<String, User>,
|
2023-04-11 09:37:16 +08:00
|
|
|
// Note, don't put simple fields below these configs. There's a compatibility issue with TOML that makes it
|
2023-02-15 15:19:16 -06:00
|
|
|
// incompatible to have simple fields in TOML after complex objects. See
|
|
|
|
|
// https://users.rust-lang.org/t/why-toml-to-string-get-error-valueaftertable/85903
|
2022-09-23 11:32:05 -07:00
|
|
|
}
|
|
|
|
|
|
2022-09-23 02:00:46 -04:00
|
|
|
impl Pool {
|
2023-02-21 21:53:10 -06:00
|
|
|
pub fn hash_value(&self) -> u64 {
|
|
|
|
|
let mut s = DefaultHasher::new();
|
|
|
|
|
self.hash(&mut s);
|
|
|
|
|
s.finish()
|
|
|
|
|
}
|
|
|
|
|
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
pub fn is_auth_query_configured(&self) -> bool {
|
|
|
|
|
self.auth_query_password.is_some()
|
|
|
|
|
&& self.auth_query_user.is_some()
|
|
|
|
|
&& self.auth_query_password.is_some()
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_pool_mode() -> PoolMode {
|
2022-09-23 02:00:46 -04:00
|
|
|
PoolMode::Transaction
|
|
|
|
|
}
|
2022-09-28 09:50:14 -04:00
|
|
|
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
pub fn default_default_shard() -> DefaultShard {
|
|
|
|
|
DefaultShard::default()
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-17 06:52:18 -06:00
|
|
|
pub fn default_load_balancing_mode() -> LoadBalancingMode {
|
|
|
|
|
LoadBalancingMode::Random
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-25 11:47:41 -07:00
|
|
|
pub fn default_automatic_sharding_key() -> Option<String> {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 09:50:52 -07:00
|
|
|
pub fn default_default_role() -> String {
|
|
|
|
|
"any".into()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn default_sharding_function() -> ShardingFunction {
|
|
|
|
|
ShardingFunction::PgBigintHash
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 10:46:55 -07:00
|
|
|
pub fn default_cleanup_server_connections() -> bool {
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-25 18:11:57 -04:00
|
|
|
pub fn default_prepared_statements_cache_size() -> usize {
|
|
|
|
|
0
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-02 00:53:31 -05:00
|
|
|
pub fn validate(&mut self) -> Result<(), Error> {
|
2022-09-28 09:50:14 -04:00
|
|
|
match self.default_role.as_ref() {
|
|
|
|
|
"any" => (),
|
|
|
|
|
"primary" => (),
|
|
|
|
|
"replica" => (),
|
|
|
|
|
other => {
|
|
|
|
|
error!(
|
|
|
|
|
"Query router default_role must be 'primary', 'replica', or 'any', got: '{}'",
|
|
|
|
|
other
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (shard_idx, shard) in &self.shards {
|
|
|
|
|
match shard_idx.parse::<usize>() {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
Err(_) => {
|
|
|
|
|
error!(
|
|
|
|
|
"Shard '{}' is not a valid number, shards must be numbered starting at 0",
|
|
|
|
|
shard_idx
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
shard.validate()?;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-15 15:19:16 -06:00
|
|
|
for (option, name) in [
|
|
|
|
|
(&self.shard_id_regex, "shard_id_regex"),
|
|
|
|
|
(&self.sharding_key_regex, "sharding_key_regex"),
|
|
|
|
|
] {
|
|
|
|
|
if let Some(regex) = option {
|
|
|
|
|
if let Err(parse_err) = Regex::new(regex.as_str()) {
|
|
|
|
|
error!("{} is not a valid Regex: {}", name, parse_err);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-08 16:10:03 -04:00
|
|
|
if self.query_parser_read_write_splitting && !self.query_parser_enabled {
|
|
|
|
|
error!(
|
|
|
|
|
"query_parser_read_write_splitting is only valid when query_parser_enabled is true"
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.plugins.is_some() && !self.query_parser_enabled {
|
|
|
|
|
error!("plugins are only valid when query_parser_enabled is true");
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-02 00:53:31 -05:00
|
|
|
self.automatic_sharding_key = match &self.automatic_sharding_key {
|
|
|
|
|
Some(key) => {
|
|
|
|
|
// No quotes in the key so we don't have to compare quoted
|
|
|
|
|
// to unquoted idents.
|
2023-10-10 09:18:21 -07:00
|
|
|
let key = key.replace('\"', "");
|
2023-03-02 00:53:31 -05:00
|
|
|
|
2023-10-10 09:18:21 -07:00
|
|
|
if key.split('.').count() != 2 {
|
2023-03-02 00:53:31 -05:00
|
|
|
error!(
|
|
|
|
|
"automatic_sharding_key '{}' must be fully qualified, e.g. t.{}`",
|
|
|
|
|
key, key
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Some(key)
|
|
|
|
|
}
|
|
|
|
|
None => None,
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-10 09:18:21 -07:00
|
|
|
if let DefaultShard::Shard(shard_number) = self.default_shard {
|
|
|
|
|
if shard_number >= self.shards.len() {
|
|
|
|
|
error!("Invalid shard {:?}", shard_number);
|
|
|
|
|
return Err(Error::BadConfig);
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-10 09:18:21 -07:00
|
|
|
for user in self.users.values() {
|
2023-04-26 16:33:26 -07:00
|
|
|
user.validate()?;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2022-09-23 02:00:46 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-27 21:47:55 -05:00
|
|
|
impl Default for Pool {
|
|
|
|
|
fn default() -> Pool {
|
|
|
|
|
Pool {
|
2022-09-28 09:50:14 -04:00
|
|
|
pool_mode: Self::default_pool_mode(),
|
2023-01-17 06:52:18 -06:00
|
|
|
load_balancing_mode: Self::default_load_balancing_mode(),
|
2022-07-27 21:47:55 -05:00
|
|
|
default_role: String::from("any"),
|
|
|
|
|
query_parser_enabled: false,
|
2023-08-08 16:10:03 -04:00
|
|
|
query_parser_max_length: None,
|
|
|
|
|
query_parser_read_write_splitting: false,
|
2022-09-23 02:00:46 -04:00
|
|
|
primary_reads_enabled: false,
|
2022-09-28 09:50:14 -04:00
|
|
|
connect_timeout: None,
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
idle_timeout: None,
|
2023-10-25 18:11:57 -04:00
|
|
|
server_lifetime: None,
|
|
|
|
|
sharding_function: ShardingFunction::PgBigintHash,
|
|
|
|
|
automatic_sharding_key: None,
|
2023-02-15 15:19:16 -06:00
|
|
|
sharding_key_regex: None,
|
|
|
|
|
shard_id_regex: None,
|
|
|
|
|
regex_search_limit: Some(1000),
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
default_shard: Self::default_default_shard(),
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
auth_query: None,
|
|
|
|
|
auth_query_user: None,
|
|
|
|
|
auth_query_password: None,
|
2023-05-18 10:46:55 -07:00
|
|
|
cleanup_server_connections: true,
|
2023-08-16 14:01:21 -04:00
|
|
|
log_client_parameter_status_changes: false,
|
2023-10-25 18:11:57 -04:00
|
|
|
prepared_statements_cache_size: Self::default_prepared_statements_cache_size(),
|
|
|
|
|
plugins: None,
|
|
|
|
|
shards: BTreeMap::from([(String::from("1"), Shard::default())]),
|
|
|
|
|
users: BTreeMap::default(),
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
}
|
2022-02-08 09:25:59 -08:00
|
|
|
}
|
|
|
|
|
|
2022-09-22 13:07:02 -04:00
|
|
|
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, Hash, Eq)]
|
|
|
|
|
pub struct ServerConfig {
|
|
|
|
|
pub host: String,
|
|
|
|
|
pub port: u16,
|
|
|
|
|
pub role: Role,
|
|
|
|
|
}
|
|
|
|
|
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
// No Shard Specified handling.
|
|
|
|
|
#[derive(Debug, PartialEq, Clone, Eq, Hash, Copy)]
|
|
|
|
|
pub enum DefaultShard {
|
|
|
|
|
Shard(usize),
|
|
|
|
|
Random,
|
|
|
|
|
RandomHealthy,
|
|
|
|
|
}
|
|
|
|
|
impl Default for DefaultShard {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
DefaultShard::Shard(0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl serde::Serialize for DefaultShard {
|
|
|
|
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
|
|
|
match self {
|
|
|
|
|
DefaultShard::Shard(shard) => {
|
|
|
|
|
serializer.serialize_str(&format!("shard_{}", &shard.to_string()))
|
|
|
|
|
}
|
|
|
|
|
DefaultShard::Random => serializer.serialize_str("random"),
|
|
|
|
|
DefaultShard::RandomHealthy => serializer.serialize_str("random_healthy"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl<'de> serde::Deserialize<'de> for DefaultShard {
|
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
|
where
|
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
|
{
|
|
|
|
|
let s = String::deserialize(deserializer)?;
|
2023-10-10 09:18:21 -07:00
|
|
|
if let Some(s) = s.strip_prefix("shard_") {
|
|
|
|
|
let shard = s.parse::<usize>().map_err(serde::de::Error::custom)?;
|
Allow configuring routing decision when no shard is selected (#578)
The TL;DR for the change is that we allow QueryRouter to set the active shard to None. This signals to the Pool::get method that we have no shard selected. The get method follows a no_shard_specified_behavior config to know how to route the query.
Original PR description
Ruby-pg library makes a startup query to SET client_encoding to ... if Encoding.default_internal value is set (Code). This query is troublesome because we cannot possibly attach a routing comment to it. PgCat, by default, will route that query to the default shard.
Everything is fine until shard 0 has issues, Clients will all be attempting to send this query to shard0 which increases the connection latency significantly for all clients, even those not interested in shard0
This PR introduces no_shard_specified_behavior that defines the behavior in case we have routing-by-comment enabled but we get a query without a comment. The allowed behaviors are
random: Picks a shard at random
random_healthy: Picks a shard at random favoring shards with the least number of recent connection/checkout errors
shard_<number>: e.g. shard_0, shard_4, etc. picks a specific shard, everytime
In order to achieve this, this PR introduces an error_count on the Address Object that tracks the number of errors since the last checkout and uses that metric to sort shards by error count before making a routing decision.
I didn't want to use address stats to avoid introducing a routing dependency on internal stats (We might do that in the future but I prefer to avoid this for the time being.
I also made changes to the test environment to replace Ruby's TOML reader library, It appears to be abandoned and does not support mixed arrays (which we use in the config toml), and it also does not play nicely with single-quoted regular expressions. I opted for using yj which is a CLI tool that can convert from toml to JSON and back. So I refactor the tests to use that library.
2023-09-11 13:47:28 -05:00
|
|
|
return Ok(DefaultShard::Shard(shard));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match s.as_str() {
|
|
|
|
|
"random" => Ok(DefaultShard::Random),
|
|
|
|
|
"random_healthy" => Ok(DefaultShard::RandomHealthy),
|
|
|
|
|
_ => Err(serde::de::Error::custom(
|
|
|
|
|
"invalid value for no_shard_specified_behavior",
|
|
|
|
|
)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-10 06:23:51 -06:00
|
|
|
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, Hash, Eq)]
|
|
|
|
|
pub struct MirrorServerConfig {
|
|
|
|
|
pub host: String,
|
|
|
|
|
pub port: u16,
|
|
|
|
|
pub mirroring_target_index: usize,
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Shard configuration.
|
2022-09-23 11:32:05 -07:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Hash, Eq)]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub struct Shard {
|
|
|
|
|
pub database: String,
|
2023-03-10 06:23:51 -06:00
|
|
|
pub mirrors: Option<Vec<MirrorServerConfig>>,
|
2023-03-10 07:39:42 -06:00
|
|
|
pub servers: Vec<ServerConfig>,
|
2022-02-08 09:25:59 -08:00
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
impl Shard {
|
|
|
|
|
pub fn validate(&self) -> Result<(), Error> {
|
|
|
|
|
// We use addresses as unique identifiers,
|
|
|
|
|
// let's make sure they are unique in the config as well.
|
|
|
|
|
let mut dup_check = HashSet::new();
|
|
|
|
|
let mut primary_count = 0;
|
|
|
|
|
|
2022-11-10 02:04:31 +08:00
|
|
|
if self.servers.is_empty() {
|
2022-09-28 09:50:14 -04:00
|
|
|
error!("Shard {} has no servers configured", self.database);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for server in &self.servers {
|
|
|
|
|
dup_check.insert(server);
|
|
|
|
|
|
|
|
|
|
// Check that we define only zero or one primary.
|
2022-11-10 02:04:31 +08:00
|
|
|
if server.role == Role::Primary {
|
|
|
|
|
primary_count += 1
|
|
|
|
|
}
|
2022-09-28 09:50:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if primary_count > 1 {
|
|
|
|
|
error!(
|
2023-04-26 11:28:54 -04:00
|
|
|
"Shard {} has more than one primary configured",
|
2022-09-28 09:50:14 -04:00
|
|
|
self.database
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dup_check.len() != self.servers.len() {
|
|
|
|
|
error!("Shard {} contains duplicate server configs", self.database);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Default for Shard {
|
|
|
|
|
fn default() -> Shard {
|
|
|
|
|
Shard {
|
2023-10-25 18:11:57 -04:00
|
|
|
database: String::from("postgres"),
|
|
|
|
|
mirrors: None,
|
2022-09-22 13:07:02 -04:00
|
|
|
servers: vec![ServerConfig {
|
|
|
|
|
host: String::from("localhost"),
|
|
|
|
|
port: 5432,
|
|
|
|
|
role: Role::Primary,
|
|
|
|
|
}],
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 09:50:52 -07:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, Hash, Eq)]
|
2023-05-03 16:13:45 -07:00
|
|
|
pub struct Plugins {
|
|
|
|
|
pub intercept: Option<Intercept>,
|
|
|
|
|
pub table_access: Option<TableAccess>,
|
|
|
|
|
pub query_logger: Option<QueryLogger>,
|
2023-05-12 09:50:52 -07:00
|
|
|
pub prewarmer: Option<Prewarmer>,
|
2023-05-03 16:13:45 -07:00
|
|
|
}
|
|
|
|
|
|
2023-10-03 13:13:21 -07:00
|
|
|
pub trait Plugin {
|
|
|
|
|
fn is_enabled(&self) -> bool;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 09:50:52 -07:00
|
|
|
impl std::fmt::Display for Plugins {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
2023-10-03 13:13:21 -07:00
|
|
|
fn is_enabled<T: Plugin>(arg: Option<&T>) -> bool {
|
2023-10-10 09:18:21 -07:00
|
|
|
if let Some(arg) = arg {
|
2023-10-03 13:13:21 -07:00
|
|
|
arg.is_enabled()
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-12 09:50:52 -07:00
|
|
|
write!(
|
|
|
|
|
f,
|
|
|
|
|
"interceptor: {}, table_access: {}, query_logger: {}, prewarmer: {}",
|
2023-10-03 13:13:21 -07:00
|
|
|
is_enabled(self.intercept.as_ref()),
|
|
|
|
|
is_enabled(self.table_access.as_ref()),
|
|
|
|
|
is_enabled(self.query_logger.as_ref()),
|
|
|
|
|
is_enabled(self.prewarmer.as_ref()),
|
2023-05-12 09:50:52 -07:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, Hash, Eq)]
|
2023-05-03 16:13:45 -07:00
|
|
|
pub struct Intercept {
|
|
|
|
|
pub enabled: bool,
|
|
|
|
|
pub queries: BTreeMap<String, Query>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 13:13:21 -07:00
|
|
|
impl Plugin for Intercept {
|
|
|
|
|
fn is_enabled(&self) -> bool {
|
|
|
|
|
self.enabled
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 09:50:52 -07:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, Hash, Eq)]
|
2023-05-03 16:13:45 -07:00
|
|
|
pub struct TableAccess {
|
|
|
|
|
pub enabled: bool,
|
|
|
|
|
pub tables: Vec<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 13:13:21 -07:00
|
|
|
impl Plugin for TableAccess {
|
|
|
|
|
fn is_enabled(&self) -> bool {
|
|
|
|
|
self.enabled
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 09:50:52 -07:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, Hash, Eq)]
|
2023-05-03 16:13:45 -07:00
|
|
|
pub struct QueryLogger {
|
|
|
|
|
pub enabled: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 13:13:21 -07:00
|
|
|
impl Plugin for QueryLogger {
|
|
|
|
|
fn is_enabled(&self) -> bool {
|
|
|
|
|
self.enabled
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 09:50:52 -07:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, Hash, Eq)]
|
|
|
|
|
pub struct Prewarmer {
|
|
|
|
|
pub enabled: bool,
|
|
|
|
|
pub queries: Vec<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 13:13:21 -07:00
|
|
|
impl Plugin for Prewarmer {
|
|
|
|
|
fn is_enabled(&self) -> bool {
|
|
|
|
|
self.enabled
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-03 16:13:45 -07:00
|
|
|
impl Intercept {
|
|
|
|
|
pub fn substitute(&mut self, db: &str, user: &str) {
|
|
|
|
|
for (_, query) in self.queries.iter_mut() {
|
|
|
|
|
query.substitute(db, user);
|
2023-05-03 16:47:20 -07:00
|
|
|
query.query = query.query.to_ascii_lowercase();
|
2023-05-03 16:13:45 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 09:50:52 -07:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, Hash, Eq)]
|
2023-05-03 16:13:45 -07:00
|
|
|
pub struct Query {
|
|
|
|
|
pub query: String,
|
|
|
|
|
pub schema: Vec<Vec<String>>,
|
|
|
|
|
pub result: Vec<Vec<String>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Query {
|
2023-10-10 09:18:21 -07:00
|
|
|
#[allow(clippy::needless_range_loop)]
|
2023-05-03 16:13:45 -07:00
|
|
|
pub fn substitute(&mut self, db: &str, user: &str) {
|
|
|
|
|
for col in self.result.iter_mut() {
|
|
|
|
|
for i in 0..col.len() {
|
|
|
|
|
col[i] = col[i].replace("${USER}", user).replace("${DATABASE}", db);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Configuration wrapper.
|
2022-07-28 17:42:04 -05:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub struct Config {
|
2022-07-30 18:12:02 -05:00
|
|
|
// Serializer maintains the order of fields in the struct
|
|
|
|
|
// so we should always put simple fields before nested fields
|
|
|
|
|
// in all serializable structs to avoid ValueAfterTable errors
|
|
|
|
|
// These errors occur when the toml serializer is about to produce
|
2022-09-28 09:50:14 -04:00
|
|
|
// ambiguous toml structure like the one below
|
2022-07-30 18:12:02 -05:00
|
|
|
// [main]
|
|
|
|
|
// field1_under_main = 1
|
|
|
|
|
// field2_under_main = 2
|
|
|
|
|
// [main.subconf]
|
|
|
|
|
// field1_under_subconf = 1
|
|
|
|
|
// field3_under_main = 3 # This field will be interpreted as being under subconf and not under main
|
2022-09-23 02:00:46 -04:00
|
|
|
#[serde(default = "Config::default_path")]
|
2022-06-24 14:52:38 -07:00
|
|
|
pub path: String,
|
|
|
|
|
|
2023-05-12 09:50:52 -07:00
|
|
|
// General and global settings.
|
2022-02-08 09:25:59 -08:00
|
|
|
pub general: General,
|
2023-05-12 09:50:52 -07:00
|
|
|
|
|
|
|
|
// Plugins that should run in all pools.
|
2023-05-03 16:13:45 -07:00
|
|
|
pub plugins: Option<Plugins>,
|
2023-05-12 09:50:52 -07:00
|
|
|
|
|
|
|
|
// Connection pools.
|
2022-07-27 21:47:55 -05:00
|
|
|
pub pools: HashMap<String, Pool>,
|
2022-02-08 09:25:59 -08:00
|
|
|
}
|
|
|
|
|
|
2022-09-23 02:00:46 -04:00
|
|
|
impl Config {
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
pub fn is_auth_query_configured(&self) -> bool {
|
|
|
|
|
self.pools
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|(_name, pool)| pool.is_auth_query_configured())
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_path() -> String {
|
2022-09-23 02:00:46 -04:00
|
|
|
String::from("pgcat.toml")
|
|
|
|
|
}
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
|
|
|
|
|
pub fn fill_up_auth_query_config(&mut self) {
|
|
|
|
|
for (_name, pool) in self.pools.iter_mut() {
|
|
|
|
|
if pool.auth_query.is_none() {
|
2024-07-15 22:30:26 -05:00
|
|
|
pool.auth_query.clone_from(&self.general.auth_query);
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if pool.auth_query_user.is_none() {
|
2024-07-15 22:30:26 -05:00
|
|
|
pool.auth_query_user
|
|
|
|
|
.clone_from(&self.general.auth_query_user);
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if pool.auth_query_password.is_none() {
|
2024-07-15 22:30:26 -05:00
|
|
|
pool.auth_query_password
|
|
|
|
|
.clone_from(&self.general.auth_query_password);
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-09-23 02:00:46 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Default for Config {
|
|
|
|
|
fn default() -> Config {
|
|
|
|
|
Config {
|
2022-09-28 09:50:14 -04:00
|
|
|
path: Self::default_path(),
|
2022-02-19 13:57:35 -08:00
|
|
|
general: General::default(),
|
2023-05-03 16:13:45 -07:00
|
|
|
plugins: None,
|
2023-10-25 18:11:57 -04:00
|
|
|
pools: HashMap::default(),
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-28 08:14:39 -08:00
|
|
|
impl From<&Config> for std::collections::HashMap<String, String> {
|
|
|
|
|
fn from(config: &Config) -> HashMap<String, String> {
|
2022-07-27 21:47:55 -05:00
|
|
|
let mut r: Vec<(String, String)> = config
|
|
|
|
|
.pools
|
|
|
|
|
.iter()
|
|
|
|
|
.flat_map(|(pool_name, pool)| {
|
|
|
|
|
[
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{}.pool_mode", pool_name),
|
2022-09-23 02:00:46 -04:00
|
|
|
pool.pool_mode.to_string(),
|
2022-07-27 21:47:55 -05:00
|
|
|
),
|
2023-01-17 06:52:18 -06:00
|
|
|
(
|
|
|
|
|
format!("pools.{}.load_balancing_mode", pool_name),
|
|
|
|
|
pool.load_balancing_mode.to_string(),
|
|
|
|
|
),
|
2022-07-27 21:47:55 -05:00
|
|
|
(
|
|
|
|
|
format!("pools.{}.primary_reads_enabled", pool_name),
|
|
|
|
|
pool.primary_reads_enabled.to_string(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{}.query_parser_enabled", pool_name),
|
|
|
|
|
pool.query_parser_enabled.to_string(),
|
|
|
|
|
),
|
2023-08-08 16:10:03 -04:00
|
|
|
(
|
|
|
|
|
format!("pools.{}.query_parser_max_length", pool_name),
|
|
|
|
|
match pool.query_parser_max_length {
|
|
|
|
|
Some(max_length) => max_length.to_string(),
|
|
|
|
|
None => String::from("unlimited"),
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{}.query_parser_read_write_splitting", pool_name),
|
|
|
|
|
pool.query_parser_read_write_splitting.to_string(),
|
|
|
|
|
),
|
2022-07-27 21:47:55 -05:00
|
|
|
(
|
|
|
|
|
format!("pools.{}.default_role", pool_name),
|
|
|
|
|
pool.default_role.clone(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{}.sharding_function", pool_name),
|
2022-09-28 09:50:14 -04:00
|
|
|
pool.sharding_function.to_string(),
|
2022-07-27 21:47:55 -05:00
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{:?}.shard_count", pool_name),
|
|
|
|
|
pool.shards.len().to_string(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{:?}.users", pool_name),
|
|
|
|
|
pool.users
|
2023-10-10 09:18:21 -07:00
|
|
|
.values()
|
|
|
|
|
.map(|user| &user.username)
|
2022-07-27 21:47:55 -05:00
|
|
|
.cloned()
|
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
|
.join(", "),
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let mut static_settings = vec![
|
2022-02-28 08:14:39 -08:00
|
|
|
("host".to_string(), config.general.host.to_string()),
|
|
|
|
|
("port".to_string(), config.general.port.to_string()),
|
2022-08-14 01:25:14 +08:00
|
|
|
(
|
|
|
|
|
"prometheus_exporter_port".to_string(),
|
|
|
|
|
config.general.prometheus_exporter_port.to_string(),
|
|
|
|
|
),
|
2022-02-28 08:14:39 -08:00
|
|
|
(
|
|
|
|
|
"connect_timeout".to_string(),
|
|
|
|
|
config.general.connect_timeout.to_string(),
|
|
|
|
|
),
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
(
|
|
|
|
|
"idle_timeout".to_string(),
|
|
|
|
|
config.general.idle_timeout.to_string(),
|
|
|
|
|
),
|
2022-02-28 08:14:39 -08:00
|
|
|
(
|
|
|
|
|
"healthcheck_timeout".to_string(),
|
|
|
|
|
config.general.healthcheck_timeout.to_string(),
|
|
|
|
|
),
|
2022-08-08 19:01:24 -04:00
|
|
|
(
|
|
|
|
|
"shutdown_timeout".to_string(),
|
|
|
|
|
config.general.shutdown_timeout.to_string(),
|
|
|
|
|
),
|
2022-08-11 17:42:40 -04:00
|
|
|
(
|
|
|
|
|
"healthcheck_delay".to_string(),
|
|
|
|
|
config.general.healthcheck_delay.to_string(),
|
|
|
|
|
),
|
2022-02-28 08:14:39 -08:00
|
|
|
("ban_time".to_string(), config.general.ban_time.to_string()),
|
2023-03-24 11:20:30 -04:00
|
|
|
(
|
|
|
|
|
"idle_client_in_transaction_timeout".to_string(),
|
|
|
|
|
config
|
|
|
|
|
.general
|
|
|
|
|
.idle_client_in_transaction_timeout
|
|
|
|
|
.to_string(),
|
|
|
|
|
),
|
2022-07-27 21:47:55 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
r.append(&mut static_settings);
|
|
|
|
|
return r.iter().cloned().collect();
|
2022-02-28 08:14:39 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Config {
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Print current configuration.
|
2022-02-19 13:57:35 -08:00
|
|
|
pub fn show(&self) {
|
2023-10-25 18:11:57 -04:00
|
|
|
info!("Config path: {}", self.path);
|
2022-02-20 22:47:08 -08:00
|
|
|
info!("Ban time: {}s", self.general.ban_time);
|
2023-03-24 11:20:30 -04:00
|
|
|
info!(
|
|
|
|
|
"Idle client in transaction timeout: {}ms",
|
|
|
|
|
self.general.idle_client_in_transaction_timeout
|
|
|
|
|
);
|
2023-02-16 17:51:38 -05:00
|
|
|
info!("Worker threads: {}", self.general.worker_threads);
|
2022-02-20 22:47:08 -08:00
|
|
|
info!(
|
|
|
|
|
"Healthcheck timeout: {}ms",
|
2022-02-19 13:57:35 -08:00
|
|
|
self.general.healthcheck_timeout
|
|
|
|
|
);
|
2022-02-20 22:47:08 -08:00
|
|
|
info!("Connection timeout: {}ms", self.general.connect_timeout);
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
info!("Idle timeout: {}ms", self.general.idle_timeout);
|
2022-11-16 22:15:47 -08:00
|
|
|
info!(
|
|
|
|
|
"Log client connections: {}",
|
|
|
|
|
self.general.log_client_connections
|
|
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"Log client disconnections: {}",
|
|
|
|
|
self.general.log_client_disconnections
|
|
|
|
|
);
|
2022-08-08 19:01:24 -04:00
|
|
|
info!("Shutdown timeout: {}ms", self.general.shutdown_timeout);
|
2022-08-11 17:42:40 -04:00
|
|
|
info!("Healthcheck delay: {}ms", self.general.healthcheck_delay);
|
2023-04-26 16:33:26 -07:00
|
|
|
info!(
|
|
|
|
|
"Default max server lifetime: {}ms",
|
|
|
|
|
self.general.server_lifetime
|
|
|
|
|
);
|
2024-07-15 22:30:26 -05:00
|
|
|
info!("Server round robin: {}", self.general.server_round_robin);
|
2022-06-27 17:01:14 -07:00
|
|
|
match self.general.tls_certificate.clone() {
|
|
|
|
|
Some(tls_certificate) => {
|
|
|
|
|
info!("TLS certificate: {}", tls_certificate);
|
|
|
|
|
|
2023-10-10 09:18:21 -07:00
|
|
|
if let Some(tls_private_key) = self.general.tls_private_key.clone() {
|
|
|
|
|
info!("TLS private key: {}", tls_private_key);
|
|
|
|
|
info!("TLS support is enabled");
|
2022-06-27 17:01:14 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None => {
|
|
|
|
|
info!("TLS support is disabled");
|
2022-06-27 17:01:40 -07:00
|
|
|
}
|
2022-06-27 17:01:14 -07:00
|
|
|
};
|
2023-04-30 09:41:46 -07:00
|
|
|
info!("Server TLS enabled: {}", self.general.server_tls);
|
|
|
|
|
info!(
|
|
|
|
|
"Server TLS certificate verification: {}",
|
|
|
|
|
self.general.verify_server_certificate
|
|
|
|
|
);
|
2023-05-12 09:50:52 -07:00
|
|
|
info!(
|
|
|
|
|
"Plugins: {}",
|
|
|
|
|
match self.plugins {
|
|
|
|
|
Some(ref plugins) => plugins.to_string(),
|
|
|
|
|
None => "not configured".into(),
|
|
|
|
|
}
|
|
|
|
|
);
|
2022-07-27 21:47:55 -05:00
|
|
|
|
|
|
|
|
for (pool_name, pool_config) in &self.pools {
|
2022-08-13 13:45:58 -07:00
|
|
|
// TODO: Make this output prettier (maybe a table?)
|
2022-07-27 21:47:55 -05:00
|
|
|
info!(
|
2022-08-25 06:40:56 -07:00
|
|
|
"[pool: {}] Maximum user connections: {}",
|
|
|
|
|
pool_name,
|
2022-07-27 21:47:55 -05:00
|
|
|
pool_config
|
|
|
|
|
.users
|
2023-10-10 09:18:21 -07:00
|
|
|
.values()
|
|
|
|
|
.map(|user_cfg| user_cfg.pool_size)
|
2022-07-27 21:47:55 -05:00
|
|
|
.sum::<u32>()
|
|
|
|
|
.to_string()
|
|
|
|
|
);
|
2022-09-23 02:00:46 -04:00
|
|
|
info!(
|
2023-04-05 15:06:19 -07:00
|
|
|
"[pool: {}] Default pool mode: {}",
|
|
|
|
|
pool_name,
|
|
|
|
|
pool_config.pool_mode.to_string()
|
2022-09-23 02:00:46 -04:00
|
|
|
);
|
2023-01-17 06:52:18 -06:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Load Balancing mode: {:?}",
|
|
|
|
|
pool_name, pool_config.load_balancing_mode
|
|
|
|
|
);
|
2022-09-28 09:50:14 -04:00
|
|
|
let connect_timeout = match pool_config.connect_timeout {
|
|
|
|
|
Some(connect_timeout) => connect_timeout,
|
|
|
|
|
None => self.general.connect_timeout,
|
|
|
|
|
};
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Connection timeout: {}ms",
|
|
|
|
|
pool_name, connect_timeout
|
|
|
|
|
);
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
let idle_timeout = match pool_config.idle_timeout {
|
|
|
|
|
Some(idle_timeout) => idle_timeout,
|
|
|
|
|
None => self.general.idle_timeout,
|
|
|
|
|
};
|
|
|
|
|
info!("[pool: {}] Idle timeout: {}ms", pool_name, idle_timeout);
|
2022-08-25 06:40:56 -07:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Sharding function: {}",
|
2022-09-28 09:50:14 -04:00
|
|
|
pool_name,
|
|
|
|
|
pool_config.sharding_function.to_string()
|
2022-08-25 06:40:56 -07:00
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Primary reads: {}",
|
|
|
|
|
pool_name, pool_config.primary_reads_enabled
|
|
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Query router: {}",
|
|
|
|
|
pool_name, pool_config.query_parser_enabled
|
|
|
|
|
);
|
2023-08-08 16:10:03 -04:00
|
|
|
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Query parser max length: {:?}",
|
|
|
|
|
pool_name, pool_config.query_parser_max_length
|
|
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Infer role from query: {}",
|
|
|
|
|
pool_name, pool_config.query_parser_read_write_splitting
|
|
|
|
|
);
|
2022-08-25 06:40:56 -07:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Number of shards: {}",
|
|
|
|
|
pool_name,
|
|
|
|
|
pool_config.shards.len()
|
|
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Number of users: {}",
|
|
|
|
|
pool_name,
|
|
|
|
|
pool_config.users.len()
|
|
|
|
|
);
|
2023-04-26 16:33:26 -07:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Max server lifetime: {}",
|
|
|
|
|
pool_name,
|
|
|
|
|
match pool_config.server_lifetime {
|
|
|
|
|
Some(server_lifetime) => format!("{}ms", server_lifetime),
|
|
|
|
|
None => "default".to_string(),
|
|
|
|
|
}
|
|
|
|
|
);
|
2023-05-18 10:46:55 -07:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Cleanup server connections: {}",
|
|
|
|
|
pool_name, pool_config.cleanup_server_connections
|
|
|
|
|
);
|
2023-08-16 14:01:21 -04:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Log client parameter status changes: {}",
|
|
|
|
|
pool_name, pool_config.log_client_parameter_status_changes
|
|
|
|
|
);
|
2023-10-25 18:11:57 -04:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Prepared statements server cache size: {}",
|
|
|
|
|
pool_name, pool_config.prepared_statements_cache_size
|
|
|
|
|
);
|
2023-05-12 09:50:52 -07:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Plugins: {}",
|
|
|
|
|
pool_name,
|
|
|
|
|
match pool_config.plugins {
|
|
|
|
|
Some(ref plugins) => plugins.to_string(),
|
|
|
|
|
None => "not configured".into(),
|
|
|
|
|
}
|
|
|
|
|
);
|
2022-08-13 13:45:58 -07:00
|
|
|
|
|
|
|
|
for user in &pool_config.users {
|
|
|
|
|
info!(
|
2022-08-25 06:40:56 -07:00
|
|
|
"[pool: {}][user: {}] Pool size: {}",
|
|
|
|
|
pool_name, user.1.username, user.1.pool_size,
|
2022-08-13 13:45:58 -07:00
|
|
|
);
|
2023-04-26 16:33:26 -07:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}][user: {}] Minimum pool size: {}",
|
|
|
|
|
pool_name,
|
|
|
|
|
user.1.username,
|
|
|
|
|
user.1.min_pool_size.unwrap_or(0)
|
|
|
|
|
);
|
2022-08-25 06:40:56 -07:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}][user: {}] Statement timeout: {}",
|
|
|
|
|
pool_name, user.1.username, user.1.statement_timeout
|
2023-04-05 15:06:19 -07:00
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}][user: {}] Pool mode: {}",
|
|
|
|
|
pool_name,
|
|
|
|
|
user.1.username,
|
|
|
|
|
match user.1.pool_mode {
|
|
|
|
|
Some(pool_mode) => pool_mode.to_string(),
|
|
|
|
|
None => pool_config.pool_mode.to_string(),
|
|
|
|
|
}
|
|
|
|
|
);
|
2023-04-26 16:33:26 -07:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}][user: {}] Max server lifetime: {}",
|
|
|
|
|
pool_name,
|
|
|
|
|
user.1.username,
|
|
|
|
|
match user.1.server_lifetime {
|
|
|
|
|
Some(server_lifetime) => format!("{}ms", server_lifetime),
|
|
|
|
|
None => "default".to_string(),
|
|
|
|
|
}
|
|
|
|
|
);
|
2023-11-06 12:18:52 -08:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}][user: {}] Connection timeout: {}",
|
|
|
|
|
pool_name,
|
|
|
|
|
user.1.username,
|
|
|
|
|
match user.1.connect_timeout {
|
|
|
|
|
Some(connect_timeout) => format!("{}ms", connect_timeout),
|
|
|
|
|
None => "not set".to_string(),
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}][user: {}] Idle timeout: {}",
|
|
|
|
|
pool_name,
|
|
|
|
|
user.1.username,
|
|
|
|
|
match user.1.idle_timeout {
|
|
|
|
|
Some(idle_timeout) => format!("{}ms", idle_timeout),
|
|
|
|
|
None => "not set".to_string(),
|
|
|
|
|
}
|
|
|
|
|
);
|
2022-08-13 13:45:58 -07:00
|
|
|
}
|
2022-07-27 21:47:55 -05:00
|
|
|
}
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
2022-09-28 09:50:14 -04:00
|
|
|
|
|
|
|
|
pub fn validate(&mut self) -> Result<(), Error> {
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
// Validation for auth_query feature
|
|
|
|
|
if self.general.auth_query.is_some()
|
|
|
|
|
&& (self.general.auth_query_user.is_none()
|
|
|
|
|
|| self.general.auth_query_password.is_none())
|
|
|
|
|
{
|
2023-04-26 16:33:26 -07:00
|
|
|
error!(
|
|
|
|
|
"If auth_query is specified, \
|
|
|
|
|
you need to provide a value \
|
|
|
|
|
for `auth_query_user`, \
|
|
|
|
|
`auth_query_password`"
|
|
|
|
|
);
|
|
|
|
|
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (name, pool) in self.pools.iter() {
|
|
|
|
|
if pool.auth_query.is_some()
|
|
|
|
|
&& (pool.auth_query_user.is_none() || pool.auth_query_password.is_none())
|
|
|
|
|
{
|
2023-04-26 16:33:26 -07:00
|
|
|
error!(
|
|
|
|
|
"Error in pool {{ {} }}. \
|
|
|
|
|
If auth_query is specified, you need \
|
|
|
|
|
to provide a value for `auth_query_user`, \
|
|
|
|
|
`auth_query_password`",
|
|
|
|
|
name
|
|
|
|
|
);
|
|
|
|
|
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (_name, user_data) in pool.users.iter() {
|
|
|
|
|
if (pool.auth_query.is_none()
|
|
|
|
|
|| pool.auth_query_password.is_none()
|
|
|
|
|
|| pool.auth_query_user.is_none())
|
|
|
|
|
&& user_data.password.is_none()
|
|
|
|
|
{
|
2023-04-26 16:33:26 -07:00
|
|
|
error!(
|
|
|
|
|
"Error in pool {{ {} }}. \
|
|
|
|
|
You have to specify a user password \
|
|
|
|
|
for every pool if auth_query is not specified",
|
|
|
|
|
name
|
|
|
|
|
);
|
|
|
|
|
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
// Validate TLS!
|
2023-10-10 09:18:21 -07:00
|
|
|
if let Some(tls_certificate) = self.general.tls_certificate.clone() {
|
|
|
|
|
match load_certs(Path::new(&tls_certificate)) {
|
|
|
|
|
Ok(_) => {
|
|
|
|
|
// Cert is okay, but what about the private key?
|
|
|
|
|
match self.general.tls_private_key.clone() {
|
|
|
|
|
Some(tls_private_key) => match load_keys(Path::new(&tls_private_key)) {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
error!("tls_private_key is incorrectly configured: {:?}", err);
|
2022-09-28 09:50:14 -04:00
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
2023-10-10 09:18:21 -07:00
|
|
|
},
|
2022-09-28 09:50:14 -04:00
|
|
|
|
2023-10-10 09:18:21 -07:00
|
|
|
None => {
|
|
|
|
|
error!("tls_certificate is set, but the tls_private_key is not");
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Err(err) => {
|
|
|
|
|
error!("tls_certificate is incorrectly configured: {:?}", err);
|
|
|
|
|
return Err(Error::BadConfig);
|
2022-09-28 09:50:14 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-10 02:04:31 +08:00
|
|
|
for pool in self.pools.values_mut() {
|
2022-09-28 09:50:14 -04:00
|
|
|
pool.validate()?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Get a read-only instance of the configuration
|
|
|
|
|
/// from anywhere in the app.
|
|
|
|
|
/// ArcSwap makes this cheap and quick.
|
2022-06-24 14:52:38 -07:00
|
|
|
pub fn get_config() -> Config {
|
|
|
|
|
(*(*CONFIG.load())).clone()
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
|
2023-03-24 11:20:30 -04:00
|
|
|
pub fn get_idle_client_in_transaction_timeout() -> u64 {
|
2023-06-18 23:02:34 -07:00
|
|
|
CONFIG.load().general.idle_client_in_transaction_timeout
|
2023-03-24 11:20:30 -04:00
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Parse the configuration file located at the path.
|
2022-02-19 13:57:35 -08:00
|
|
|
pub async fn parse(path: &str) -> Result<(), Error> {
|
2022-02-08 09:25:59 -08:00
|
|
|
let mut contents = String::new();
|
|
|
|
|
let mut file = match File::open(path).await {
|
|
|
|
|
Ok(file) => file,
|
|
|
|
|
Err(err) => {
|
2022-02-21 20:41:32 -08:00
|
|
|
error!("Could not open '{}': {}", path, err.to_string());
|
2022-02-08 09:25:59 -08:00
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match file.read_to_string(&mut contents).await {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
Err(err) => {
|
2022-02-21 20:41:32 -08:00
|
|
|
error!("Could not read config file: {}", err.to_string());
|
2022-02-08 09:25:59 -08:00
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-02-27 10:21:24 -08:00
|
|
|
let mut config: Config = match toml::from_str(&contents) {
|
2022-02-08 09:25:59 -08:00
|
|
|
Ok(config) => config,
|
|
|
|
|
Err(err) => {
|
2022-02-21 20:41:32 -08:00
|
|
|
error!("Could not parse config file: {}", err.to_string());
|
2022-02-08 09:25:59 -08:00
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
config.fill_up_auth_query_config();
|
2022-09-28 09:50:14 -04:00
|
|
|
config.validate()?;
|
2022-07-27 21:47:55 -05:00
|
|
|
|
2022-06-24 14:52:38 -07:00
|
|
|
config.path = path.to_string();
|
2022-02-27 10:21:24 -08:00
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
// Update the configuration globally.
|
2022-02-19 13:57:35 -08:00
|
|
|
CONFIG.store(Arc::new(config.clone()));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
2022-02-08 09:25:59 -08:00
|
|
|
}
|
2022-02-08 17:08:17 -08:00
|
|
|
|
2022-06-25 11:46:20 -07:00
|
|
|
pub async fn reload_config(client_server_map: ClientServerMap) -> Result<bool, Error> {
|
2022-06-24 14:52:38 -07:00
|
|
|
let old_config = get_config();
|
2023-05-03 16:13:45 -07:00
|
|
|
|
2022-06-24 14:52:38 -07:00
|
|
|
match parse(&old_config.path).await {
|
|
|
|
|
Ok(()) => (),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
error!("Config reload error: {:?}", err);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
2023-05-03 16:13:45 -07:00
|
|
|
|
2022-06-24 14:52:38 -07:00
|
|
|
let new_config = get_config();
|
2023-05-03 16:13:45 -07:00
|
|
|
|
2023-05-02 10:26:40 +02:00
|
|
|
match CachedResolver::from_config().await {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
Err(err) => error!("DNS cache reinitialization error: {:?}", err),
|
|
|
|
|
};
|
2022-06-24 14:52:38 -07:00
|
|
|
|
2023-05-03 16:13:45 -07:00
|
|
|
if old_config != new_config {
|
|
|
|
|
info!("Config changed, reloading");
|
2022-06-25 11:46:20 -07:00
|
|
|
ConnectionPool::from_config(client_server_map).await?;
|
|
|
|
|
Ok(true)
|
2022-06-24 14:52:38 -07:00
|
|
|
} else {
|
2022-06-25 11:46:20 -07:00
|
|
|
Ok(false)
|
2022-06-24 14:52:38 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-08 17:08:17 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod test {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_config() {
|
2022-02-19 13:57:35 -08:00
|
|
|
parse("pgcat.toml").await.unwrap();
|
2022-07-27 21:47:55 -05:00
|
|
|
|
2022-06-24 14:52:38 -07:00
|
|
|
assert_eq!(get_config().path, "pgcat.toml".to_string());
|
2022-07-27 21:47:55 -05:00
|
|
|
|
|
|
|
|
assert_eq!(get_config().general.ban_time, 60);
|
2023-03-24 11:20:30 -04:00
|
|
|
assert_eq!(get_config().general.idle_client_in_transaction_timeout, 0);
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
assert_eq!(get_config().general.idle_timeout, 30000);
|
2022-07-27 21:47:55 -05:00
|
|
|
assert_eq!(get_config().pools.len(), 2);
|
2022-08-08 15:15:48 -05:00
|
|
|
assert_eq!(get_config().pools["sharded_db"].shards.len(), 3);
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
assert_eq!(get_config().pools["sharded_db"].idle_timeout, Some(40000));
|
2022-07-27 21:47:55 -05:00
|
|
|
assert_eq!(get_config().pools["simple_db"].shards.len(), 1);
|
2022-08-08 15:15:48 -05:00
|
|
|
assert_eq!(get_config().pools["sharded_db"].users.len(), 2);
|
2022-07-27 21:47:55 -05:00
|
|
|
assert_eq!(get_config().pools["simple_db"].users.len(), 1);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
2022-09-22 13:07:02 -04:00
|
|
|
get_config().pools["sharded_db"].shards["0"].servers[0].host,
|
2022-07-27 21:47:55 -05:00
|
|
|
"127.0.0.1"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
2022-09-22 13:07:02 -04:00
|
|
|
get_config().pools["sharded_db"].shards["1"].servers[0].role,
|
|
|
|
|
Role::Primary
|
2022-07-27 21:47:55 -05:00
|
|
|
);
|
|
|
|
|
assert_eq!(
|
2022-08-08 15:15:48 -05:00
|
|
|
get_config().pools["sharded_db"].shards["1"].database,
|
|
|
|
|
"shard1"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
get_config().pools["sharded_db"].users["0"].username,
|
2022-07-27 21:47:55 -05:00
|
|
|
"sharding_user"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
get_config().pools["sharded_db"].users["1"]
|
|
|
|
|
.password
|
|
|
|
|
.as_ref()
|
|
|
|
|
.unwrap(),
|
2022-07-27 21:47:55 -05:00
|
|
|
"other_user"
|
|
|
|
|
);
|
2022-08-08 15:15:48 -05:00
|
|
|
assert_eq!(get_config().pools["sharded_db"].users["1"].pool_size, 21);
|
|
|
|
|
assert_eq!(get_config().pools["sharded_db"].default_role, "any");
|
2022-07-27 21:47:55 -05:00
|
|
|
|
|
|
|
|
assert_eq!(
|
2022-09-22 13:07:02 -04:00
|
|
|
get_config().pools["simple_db"].shards["0"].servers[0].host,
|
2022-07-27 21:47:55 -05:00
|
|
|
"127.0.0.1"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
2022-09-22 13:07:02 -04:00
|
|
|
get_config().pools["simple_db"].shards["0"].servers[0].port,
|
2022-07-27 21:47:55 -05:00
|
|
|
5432
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
get_config().pools["simple_db"].shards["0"].database,
|
|
|
|
|
"some_db"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(get_config().pools["simple_db"].default_role, "primary");
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
get_config().pools["simple_db"].users["0"].username,
|
|
|
|
|
"simple_user"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
get_config().pools["simple_db"].users["0"]
|
|
|
|
|
.password
|
|
|
|
|
.as_ref()
|
|
|
|
|
.unwrap(),
|
2022-07-27 21:47:55 -05:00
|
|
|
"simple_user"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(get_config().pools["simple_db"].users["0"].pool_size, 5);
|
Auth passthrough (auth_query) (#266)
* Add a new exec_simple_query method
This adds a new `exec_simple_query` method so we can make 'out of band'
queries to servers that don't interfere with pools at all.
In order to reuse startup code for making these simple queries,
we need to set the stats (`Reporter`) optional, so using these
simple queries wont interfere with stats.
* Add auth passthough (auth_query)
Adds a feature that allows setting auth passthrough for md5 auth.
It adds 3 new (general and pool) config parameters:
- `auth_query`: An string containing a query that will be executed on boot
to obtain the hash of a given user. This query have to use a placeholder `$1`,
so pgcat can replace it with the user its trying to fetch the hash from.
- `auth_query_user`: The user to use for connecting to the server and executing the
auth_query.
- `auth_query_password`: The password to use for connecting to the server and executing the
auth_query.
The configuration can be done either on the general config (so pools share them) or in a per-pool basis.
The behavior is, at boot time, when validating server connections, a hash is fetched per server
and stored in the pool. When new server connections are created, and no cleartext password is specified,
the obtained hash is used for creating them, if the hash could not be obtained for whatever reason, it retries
it.
When client authentication is tried, it uses cleartext passwords if specified, it not, it checks whether
we have query_auth set up, if so, it tries to use the obtained hash for making client auth. If there is no
hash (we could not obtain one when validating the connection), a new fetch is tried.
Once we have a hash, we authenticate using it against whathever the client has sent us, if there is a failure
we refetch the hash and retry auth (so password changes can be done).
The idea with this 'retrial' mechanism is to make it fault tolerant, so if for whatever reason hash could not be
obtained during connection validation, or the password has change, we can still connect later.
* Add documentation for Auth passthrough
2023-03-30 22:29:23 +02:00
|
|
|
assert_eq!(get_config().general.auth_query, None);
|
|
|
|
|
assert_eq!(get_config().general.auth_query_user, None);
|
|
|
|
|
assert_eq!(get_config().general.auth_query_password, None);
|
2022-02-08 17:08:17 -08:00
|
|
|
}
|
2022-07-30 18:28:25 -05:00
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_serialize_configs() {
|
|
|
|
|
parse("pgcat.toml").await.unwrap();
|
|
|
|
|
print!("{}", toml::to_string(&get_config()).unwrap());
|
|
|
|
|
}
|
2022-02-08 17:08:17 -08:00
|
|
|
}
|