2022-02-08 09:25:59 -08:00
|
|
|
use serde_derive::Deserialize;
|
|
|
|
|
use tokio::fs::File;
|
|
|
|
|
use tokio::io::AsyncReadExt;
|
|
|
|
|
use toml;
|
|
|
|
|
|
2022-02-09 21:19:14 -08:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2022-02-08 17:08:17 -08:00
|
|
|
|
2022-02-08 09:25:59 -08:00
|
|
|
use crate::errors::Error;
|
|
|
|
|
|
2022-02-09 20:02:20 -08:00
|
|
|
#[derive(Clone, PartialEq, Deserialize, Hash, std::cmp::Eq, Debug, Copy)]
|
|
|
|
|
pub enum Role {
|
|
|
|
|
Primary,
|
|
|
|
|
Replica,
|
|
|
|
|
}
|
|
|
|
|
|
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 {
|
|
|
|
|
pub host: String,
|
|
|
|
|
pub port: String,
|
2022-02-15 08:18:01 -08:00
|
|
|
pub shard: usize,
|
2022-02-09 20:02:20 -08:00
|
|
|
pub role: Role,
|
2022-02-05 10:02:13 -08:00
|
|
|
}
|
|
|
|
|
|
2022-02-08 09:25:59 -08:00
|
|
|
#[derive(Clone, PartialEq, Hash, std::cmp::Eq, Deserialize, Debug)]
|
2022-02-05 10:02:13 -08:00
|
|
|
pub struct User {
|
|
|
|
|
pub name: String,
|
|
|
|
|
pub password: String,
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-08 09:25:59 -08:00
|
|
|
#[derive(Deserialize, Debug, Clone)]
|
|
|
|
|
pub struct General {
|
|
|
|
|
pub host: String,
|
|
|
|
|
pub port: i16,
|
|
|
|
|
pub pool_size: u32,
|
|
|
|
|
pub pool_mode: String,
|
|
|
|
|
pub connect_timeout: u64,
|
|
|
|
|
pub healthcheck_timeout: u64,
|
|
|
|
|
pub ban_time: i64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug, Clone)]
|
|
|
|
|
pub struct Shard {
|
2022-02-09 20:02:20 -08:00
|
|
|
pub servers: Vec<(String, u16, String)>,
|
2022-02-08 09:25:59 -08:00
|
|
|
pub database: String,
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-11 11:19:40 -08:00
|
|
|
#[derive(Deserialize, Debug, Clone)]
|
|
|
|
|
pub struct QueryRouter {
|
|
|
|
|
pub default_role: String,
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-08 09:25:59 -08:00
|
|
|
#[derive(Deserialize, Debug, Clone)]
|
|
|
|
|
pub struct Config {
|
|
|
|
|
pub general: General,
|
|
|
|
|
pub user: User,
|
|
|
|
|
pub shards: HashMap<String, Shard>,
|
2022-02-11 11:19:40 -08:00
|
|
|
pub query_router: QueryRouter,
|
2022-02-08 09:25:59 -08:00
|
|
|
}
|
|
|
|
|
|
2022-02-09 06:51:31 -08:00
|
|
|
/// Parse the config.
|
2022-02-08 09:25:59 -08:00
|
|
|
pub async fn parse(path: &str) -> Result<Config, Error> {
|
|
|
|
|
let mut contents = String::new();
|
|
|
|
|
let mut file = match File::open(path).await {
|
|
|
|
|
Ok(file) => file,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
println!("> Config error: {:?}", err);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match file.read_to_string(&mut contents).await {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
println!("> Config error: {:?}", err);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let config: Config = match toml::from_str(&contents) {
|
|
|
|
|
Ok(config) => config,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
println!("> Config error: {:?}", err);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-02-10 09:07:10 -08:00
|
|
|
// Quick config sanity check.
|
2022-02-09 21:19:14 -08:00
|
|
|
for shard in &config.shards {
|
2022-02-10 09:07:10 -08:00
|
|
|
// We use addresses as unique identifiers,
|
|
|
|
|
// let's make sure they are unique in the config as well.
|
2022-02-09 21:19:14 -08:00
|
|
|
let mut dup_check = HashSet::new();
|
2022-02-10 09:07:10 -08:00
|
|
|
let mut primary_count = 0;
|
2022-02-09 21:19:14 -08:00
|
|
|
|
2022-02-11 22:19:49 -08:00
|
|
|
if shard.1.servers.len() == 0 {
|
|
|
|
|
println!("> Shard {} has no servers configured", shard.0);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-09 21:19:14 -08:00
|
|
|
for server in &shard.1.servers {
|
|
|
|
|
dup_check.insert(server);
|
2022-02-10 09:07:10 -08:00
|
|
|
|
|
|
|
|
// 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" => (),
|
|
|
|
|
_ => {
|
|
|
|
|
println!(
|
|
|
|
|
"> Shard {} server role must be either 'primary' or 'replica', got: '{}'",
|
|
|
|
|
shard.0, server.2
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if primary_count > 1 {
|
|
|
|
|
println!("> Shard {} has more than on primary configured.", &shard.0);
|
|
|
|
|
return Err(Error::BadConfig);
|
2022-02-09 21:19:14 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dup_check.len() != shard.1.servers.len() {
|
|
|
|
|
println!("> Shard {} contains duplicate server configs.", &shard.0);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-11 11:19:40 -08:00
|
|
|
match config.query_router.default_role.as_ref() {
|
|
|
|
|
"any" => (),
|
|
|
|
|
"primary" => (),
|
|
|
|
|
"replica" => (),
|
|
|
|
|
other => {
|
|
|
|
|
println!(
|
|
|
|
|
"> Query router default_role must be 'primary', 'replica', or 'any', got: '{}'",
|
|
|
|
|
other
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-02-08 09:25:59 -08:00
|
|
|
Ok(config)
|
|
|
|
|
}
|
2022-02-08 17:08:17 -08:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod test {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_config() {
|
|
|
|
|
let config = parse("pgcat.toml").await.unwrap();
|
|
|
|
|
assert_eq!(config.general.pool_size, 15);
|
|
|
|
|
assert_eq!(config.shards.len(), 3);
|
|
|
|
|
assert_eq!(config.shards["1"].servers[0].0, "127.0.0.1");
|
2022-02-09 20:02:20 -08:00
|
|
|
assert_eq!(config.shards["0"].servers[0].2, "primary");
|
2022-02-11 11:19:40 -08:00
|
|
|
assert_eq!(config.query_router.default_role, "any");
|
2022-02-08 17:08:17 -08:00
|
|
|
}
|
|
|
|
|
}
|