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;
|
2022-07-28 17:42:04 -05:00
|
|
|
use serde_derive::{Deserialize, Serialize};
|
2022-03-10 01:33:29 -08:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2022-07-27 21:47:55 -05:00
|
|
|
use std::hash::Hash;
|
2022-06-27 17:01:40 -07:00
|
|
|
use std::path::Path;
|
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;
|
|
|
|
|
use toml;
|
|
|
|
|
|
|
|
|
|
use crate::errors::Error;
|
2022-06-27 17:01:14 -07:00
|
|
|
use crate::tls::{load_certs, load_keys};
|
2022-06-27 17:01:40 -07:00
|
|
|
use crate::{ClientServerMap, ConnectionPool};
|
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 {
|
|
|
|
|
Primary,
|
|
|
|
|
Replica,
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-28 17:22:28 -08:00
|
|
|
impl ToString for Role {
|
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
|
match *self {
|
|
|
|
|
Role::Primary => "primary".to_string(),
|
|
|
|
|
Role::Replica => "replica".to_string(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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.
|
2022-02-05 13:15:53 -08:00
|
|
|
#[derive(Clone, PartialEq, Hash, std::cmp::Eq, Debug)]
|
2022-02-05 10:02:13 -08:00
|
|
|
pub struct Address {
|
2022-03-04 17:04:27 -08:00
|
|
|
pub id: usize,
|
2022-02-05 10:02:13 -08:00
|
|
|
pub host: String,
|
|
|
|
|
pub port: String,
|
2022-02-15 08:18:01 -08:00
|
|
|
pub shard: usize,
|
2022-07-27 21:47:55 -05:00
|
|
|
pub database: String,
|
2022-02-09 20:02:20 -08:00
|
|
|
pub role: Role,
|
2022-03-01 22:49:43 -08:00
|
|
|
pub replica_number: usize,
|
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"),
|
|
|
|
|
port: String::from("5432"),
|
|
|
|
|
shard: 0,
|
2022-03-01 22:49:43 -08:00
|
|
|
replica_number: 0,
|
2022-07-27 21:47:55 -05:00
|
|
|
database: String::from("database"),
|
2022-02-19 13:57:35 -08:00
|
|
|
role: Role::Replica,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-07-27 21:47:55 -05:00
|
|
|
Role::Primary => format!("{}_shard_{}_primary", self.database, self.shard),
|
2022-03-01 22:49:43 -08:00
|
|
|
|
2022-07-27 21:47:55 -05:00
|
|
|
Role::Replica => format!(
|
|
|
|
|
"{}_shard_{}_replica_{}",
|
|
|
|
|
self.database, self.shard, self.replica_number
|
|
|
|
|
),
|
2022-03-01 22:49:43 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// PostgreSQL user.
|
2022-07-28 17:42:04 -05:00
|
|
|
#[derive(Clone, PartialEq, Hash, std::cmp::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,
|
2022-02-05 10:02:13 -08:00
|
|
|
pub password: String,
|
2022-07-27 21:47:55 -05:00
|
|
|
pub pool_size: u32,
|
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"),
|
2022-02-19 13:57:35 -08:00
|
|
|
password: String::new(),
|
2022-07-27 21:47:55 -05:00
|
|
|
pool_size: 15,
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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 {
|
|
|
|
|
pub host: String,
|
|
|
|
|
pub port: i16,
|
|
|
|
|
pub connect_timeout: u64,
|
|
|
|
|
pub healthcheck_timeout: u64,
|
|
|
|
|
pub ban_time: i64,
|
2022-06-25 11:46:20 -07:00
|
|
|
pub autoreload: bool,
|
2022-06-27 09:46:33 -07:00
|
|
|
pub tls_certificate: Option<String>,
|
|
|
|
|
pub tls_private_key: Option<String>,
|
2022-07-27 21:47:55 -05:00
|
|
|
pub admin_username: String,
|
|
|
|
|
pub admin_password: String,
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for General {
|
|
|
|
|
fn default() -> General {
|
|
|
|
|
General {
|
|
|
|
|
host: String::from("localhost"),
|
|
|
|
|
port: 5432,
|
|
|
|
|
connect_timeout: 5000,
|
|
|
|
|
healthcheck_timeout: 1000,
|
|
|
|
|
ban_time: 60,
|
2022-06-25 11:46:20 -07:00
|
|
|
autoreload: false,
|
2022-06-27 09:46:33 -07:00
|
|
|
tls_certificate: None,
|
|
|
|
|
tls_private_key: None,
|
2022-07-27 21:47:55 -05:00
|
|
|
admin_username: String::from("admin"),
|
|
|
|
|
admin_password: String::from("admin"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-28 17:42:04 -05:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
2022-07-27 21:47:55 -05:00
|
|
|
pub struct Pool {
|
|
|
|
|
pub pool_mode: String,
|
|
|
|
|
pub default_role: String,
|
|
|
|
|
pub query_parser_enabled: bool,
|
|
|
|
|
pub primary_reads_enabled: bool,
|
|
|
|
|
pub sharding_function: String,
|
2022-07-30 18:12:02 -05:00
|
|
|
pub shards: HashMap<String, Shard>,
|
|
|
|
|
pub users: HashMap<String, User>,
|
2022-07-27 21:47:55 -05:00
|
|
|
}
|
|
|
|
|
impl Default for Pool {
|
|
|
|
|
fn default() -> Pool {
|
|
|
|
|
Pool {
|
|
|
|
|
pool_mode: String::from("transaction"),
|
|
|
|
|
shards: HashMap::from([(String::from("1"), Shard::default())]),
|
|
|
|
|
users: HashMap::default(),
|
|
|
|
|
default_role: String::from("any"),
|
|
|
|
|
query_parser_enabled: false,
|
|
|
|
|
primary_reads_enabled: true,
|
|
|
|
|
sharding_function: "pg_bigint_hash".to_string(),
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
}
|
2022-02-08 09:25:59 -08:00
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Shard 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 Shard {
|
|
|
|
|
pub database: String,
|
2022-07-27 21:47:55 -05:00
|
|
|
pub servers: Vec<(String, u16, String)>,
|
2022-02-08 09:25:59 -08:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Default for Shard {
|
|
|
|
|
fn default() -> Shard {
|
|
|
|
|
Shard {
|
|
|
|
|
servers: vec![(String::from("localhost"), 5432, String::from("primary"))],
|
|
|
|
|
database: String::from("postgres"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 14:52:38 -07:00
|
|
|
fn default_path() -> String {
|
|
|
|
|
String::from("pgcat.toml")
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
// ambigous toml structure like the one below
|
|
|
|
|
// [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-06-24 14:52:38 -07:00
|
|
|
#[serde(default = "default_path")]
|
|
|
|
|
pub path: String,
|
|
|
|
|
|
2022-02-08 09:25:59 -08:00
|
|
|
pub general: General,
|
2022-07-27 21:47:55 -05:00
|
|
|
pub pools: HashMap<String, Pool>,
|
2022-02-08 09:25:59 -08:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Default for Config {
|
|
|
|
|
fn default() -> Config {
|
|
|
|
|
Config {
|
2022-06-24 14:52:38 -07:00
|
|
|
path: String::from("pgcat.toml"),
|
2022-02-19 13:57:35 -08:00
|
|
|
general: General::default(),
|
2022-07-27 21:47:55 -05: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),
|
|
|
|
|
pool.pool_mode.clone(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
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(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{}.default_role", pool_name),
|
|
|
|
|
pool.default_role.clone(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{}.sharding_function", pool_name),
|
|
|
|
|
pool.sharding_function.clone(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{:?}.shard_count", pool_name),
|
|
|
|
|
pool.shards.len().to_string(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{:?}.users", pool_name),
|
|
|
|
|
pool.users
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(_username, user)| &user.username)
|
|
|
|
|
.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()),
|
|
|
|
|
(
|
|
|
|
|
"connect_timeout".to_string(),
|
|
|
|
|
config.general.connect_timeout.to_string(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"healthcheck_timeout".to_string(),
|
|
|
|
|
config.general.healthcheck_timeout.to_string(),
|
|
|
|
|
),
|
|
|
|
|
("ban_time".to_string(), config.general.ban_time.to_string()),
|
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) {
|
2022-02-20 22:47:08 -08:00
|
|
|
info!("Ban time: {}s", self.general.ban_time);
|
|
|
|
|
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);
|
2022-06-27 17:01:14 -07:00
|
|
|
match self.general.tls_certificate.clone() {
|
|
|
|
|
Some(tls_certificate) => {
|
|
|
|
|
info!("TLS certificate: {}", tls_certificate);
|
|
|
|
|
|
|
|
|
|
match self.general.tls_private_key.clone() {
|
|
|
|
|
Some(tls_private_key) => {
|
|
|
|
|
info!("TLS private key: {}", tls_private_key);
|
|
|
|
|
info!("TLS support is enabled");
|
2022-06-27 17:01:40 -07:00
|
|
|
}
|
2022-06-27 17:01:14 -07:00
|
|
|
|
|
|
|
|
None => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None => {
|
|
|
|
|
info!("TLS support is disabled");
|
2022-06-27 17:01:40 -07:00
|
|
|
}
|
2022-06-27 17:01:14 -07:00
|
|
|
};
|
2022-07-27 21:47:55 -05:00
|
|
|
|
|
|
|
|
for (pool_name, pool_config) in &self.pools {
|
|
|
|
|
info!("--- Settings for pool {} ---", pool_name);
|
|
|
|
|
info!(
|
|
|
|
|
"Pool size from all users: {}",
|
|
|
|
|
pool_config
|
|
|
|
|
.users
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(_, user_cfg)| user_cfg.pool_size)
|
|
|
|
|
.sum::<u32>()
|
|
|
|
|
.to_string()
|
|
|
|
|
);
|
|
|
|
|
info!("Pool mode: {}", pool_config.pool_mode);
|
|
|
|
|
info!("Sharding function: {}", pool_config.sharding_function);
|
|
|
|
|
info!("Primary reads: {}", pool_config.primary_reads_enabled);
|
|
|
|
|
info!("Query router: {}", pool_config.query_parser_enabled);
|
|
|
|
|
info!("Number of shards: {}", pool_config.shards.len());
|
|
|
|
|
info!("Number of users: {}", pool_config.users.len());
|
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-27 17:01:14 -07:00
|
|
|
// Validate TLS!
|
|
|
|
|
match config.general.tls_certificate.clone() {
|
|
|
|
|
Some(tls_certificate) => {
|
|
|
|
|
match load_certs(&Path::new(&tls_certificate)) {
|
|
|
|
|
Ok(_) => {
|
|
|
|
|
// Cert is okay, but what about the private key?
|
|
|
|
|
match config.general.tls_private_key.clone() {
|
2022-06-27 17:01:40 -07:00
|
|
|
Some(tls_private_key) => match load_keys(&Path::new(&tls_private_key)) {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
error!("tls_private_key is incorrectly configured: {:?}", err);
|
|
|
|
|
return Err(Error::BadConfig);
|
2022-06-27 17:01:14 -07:00
|
|
|
}
|
2022-06-27 17:01:40 -07:00
|
|
|
},
|
2022-06-27 17:01:14 -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-06-27 17:01:40 -07:00
|
|
|
}
|
2022-06-27 17:01:14 -07:00
|
|
|
None => (),
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-27 21:47:55 -05:00
|
|
|
for (pool_name, pool) in &config.pools {
|
|
|
|
|
match pool.sharding_function.as_ref() {
|
|
|
|
|
"pg_bigint_hash" => (),
|
|
|
|
|
"sha1" => (),
|
|
|
|
|
_ => {
|
|
|
|
|
error!(
|
|
|
|
|
"Supported sharding functions are: 'pg_bigint_hash', 'sha1', got: '{}' in pool {} settings",
|
|
|
|
|
pool.sharding_function,
|
|
|
|
|
pool_name
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match pool.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 in &pool.shards {
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
match shard.0.parse::<usize>() {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
Err(_) => {
|
|
|
|
|
error!(
|
|
|
|
|
"Shard '{}' is not a valid number, shards must be numbered starting at 0",
|
|
|
|
|
shard.0
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if shard.1.servers.len() == 0 {
|
|
|
|
|
error!("Shard {} has no servers configured", shard.0);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for server in &shard.1.servers {
|
|
|
|
|
dup_check.insert(server);
|
|
|
|
|
|
|
|
|
|
// Check that we define only zero or one primary.
|
|
|
|
|
match server.2.as_ref() {
|
|
|
|
|
"primary" => primary_count += 1,
|
|
|
|
|
_ => (),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Check role spelling.
|
|
|
|
|
match server.2.as_ref() {
|
|
|
|
|
"primary" => (),
|
|
|
|
|
"replica" => (),
|
|
|
|
|
_ => {
|
|
|
|
|
error!(
|
|
|
|
|
"Shard {} server role must be either 'primary' or 'replica', got: '{}'",
|
|
|
|
|
shard.0, server.2
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if primary_count > 1 {
|
|
|
|
|
error!("Shard {} has more than on primary configured", &shard.0);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dup_check.len() != shard.1.servers.len() {
|
|
|
|
|
error!("Shard {} contains duplicate server configs", &shard.0);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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();
|
|
|
|
|
match parse(&old_config.path).await {
|
|
|
|
|
Ok(()) => (),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
error!("Config reload error: {:?}", err);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let new_config = get_config();
|
|
|
|
|
|
2022-07-27 21:47:55 -05:00
|
|
|
if old_config.pools != new_config.pools {
|
|
|
|
|
info!("Pool configuration changed, re-creating server pools");
|
2022-06-25 11:46:20 -07:00
|
|
|
ConnectionPool::from_config(client_server_map).await?;
|
|
|
|
|
Ok(true)
|
|
|
|
|
} else if old_config != new_config {
|
|
|
|
|
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);
|
|
|
|
|
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);
|
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-08-08 15:15:48 -05:00
|
|
|
get_config().pools["sharded_db"].shards["0"].servers[0].0,
|
2022-07-27 21:47:55 -05:00
|
|
|
"127.0.0.1"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
2022-08-08 15:15:48 -05:00
|
|
|
get_config().pools["sharded_db"].shards["1"].servers[0].2,
|
2022-07-27 21:47:55 -05:00
|
|
|
"primary"
|
|
|
|
|
);
|
|
|
|
|
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!(
|
2022-08-08 15:15:48 -05:00
|
|
|
get_config().pools["sharded_db"].users["1"].password,
|
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!(
|
|
|
|
|
get_config().pools["simple_db"].shards["0"].servers[0].0,
|
|
|
|
|
"127.0.0.1"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
get_config().pools["simple_db"].shards["0"].servers[0].1,
|
|
|
|
|
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!(
|
|
|
|
|
get_config().pools["simple_db"].users["0"].password,
|
|
|
|
|
"simple_user"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(get_config().pools["simple_db"].users["0"].pool_size, 5);
|
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
|
|
|
}
|