diff --git a/Cargo.lock b/Cargo.lock index 837015f3bc..4565997213 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1125,6 +1125,19 @@ dependencies = [ "serde", ] +[[package]] +name = "clickhouse-admin-types" +version = "0.1.0" +dependencies = [ + "anyhow", + "camino", + "derive_more", + "omicron-workspace-hack", + "schemars", + "serde", + "serde_json", +] + [[package]] name = "clickward" version = "0.1.0" @@ -5776,6 +5789,7 @@ dependencies = [ "chrono", "clap", "clickhouse-admin-api", + "clickhouse-admin-types", "dropshot", "expectorate", "http 0.2.12", diff --git a/Cargo.toml b/Cargo.toml index cfb097ef3c..451fe23b0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -307,6 +307,7 @@ chrono = { version = "0.4", features = [ "serde" ] } ciborium = "0.2.2" clap = { version = "4.5", features = ["cargo", "derive", "env", "wrap_help"] } clickhouse-admin-api = { path = "clickhouse-admin/api" } +clickhouse-admin-types = { path = "clickhouse-admin/types" } clickward = { git = "https://github.com/oxidecomputer/clickward", rev = "ceec762e6a87d2a22bf56792a3025e145caa095e" } cockroach-admin-api = { path = "cockroach-admin/api" } cockroach-admin-client = { path = "clients/cockroach-admin-client" } diff --git a/clickhouse-admin/Cargo.toml b/clickhouse-admin/Cargo.toml index 033836dfe0..270f779d7e 100644 --- a/clickhouse-admin/Cargo.toml +++ b/clickhouse-admin/Cargo.toml @@ -10,6 +10,7 @@ camino.workspace = true chrono.workspace = true clap.workspace = true clickhouse-admin-api.workspace = true +clickhouse-admin-types.workspace = true dropshot.workspace = true http.workspace = true illumos-utils.workspace = true diff --git a/clickhouse-admin/types/Cargo.toml b/clickhouse-admin/types/Cargo.toml new file mode 100644 index 0000000000..9a5211a129 --- /dev/null +++ b/clickhouse-admin/types/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "clickhouse-admin-types" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +[lints] +workspace = true + +[dependencies] +anyhow.workspace = true +camino.workspace = true +derive_more.workspace = true +omicron-workspace-hack.workspace = true +schemars.workspace = true +serde.workspace = true +serde_json.workspace = true diff --git a/clickhouse-admin/types/src/config.rs b/clickhouse-admin/types/src/config.rs new file mode 100644 index 0000000000..063b79d14e --- /dev/null +++ b/clickhouse-admin/types/src/config.rs @@ -0,0 +1,369 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use crate::{KeeperId, ServerId}; +use camino::Utf8PathBuf; +use schemars::{ + gen::SchemaGenerator, + schema::{Schema, SchemaObject}, + JsonSchema, +}; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; + +// Used for schemars to be able to be used with camino: +// See https://github.com/camino-rs/camino/issues/91#issuecomment-2027908513 +fn path_schema(gen: &mut SchemaGenerator) -> Schema { + let mut schema: SchemaObject = ::json_schema(gen).into(); + schema.format = Some("Utf8PathBuf".to_owned()); + schema.into() +} + +/// Config for an individual Clickhouse Replica +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub struct ReplicaConfig { + pub logger: LogConfig, + pub macros: Macros, + pub listen_host: String, + pub http_port: u16, + pub tcp_port: u16, + pub interserver_http_port: u16, + pub remote_servers: RemoteServers, + pub keepers: KeeperConfigsForReplica, + #[schemars(schema_with = "path_schema")] + pub data_path: Utf8PathBuf, +} + +impl ReplicaConfig { + pub fn to_xml(&self) -> String { + let ReplicaConfig { + logger, + macros, + listen_host, + http_port, + tcp_port, + interserver_http_port, + remote_servers, + keepers, + data_path, + } = self; + let logger = logger.to_xml(); + let cluster = macros.cluster.clone(); + let id = macros.replica; + let macros = macros.to_xml(); + let keepers = keepers.to_xml(); + let remote_servers = remote_servers.to_xml(); + let user_files_path = data_path.clone().join("user_files"); + //let access_path = data_path.clone().join("access"); + let format_schema_path = data_path.clone().join("format_schemas"); + format!( + " + +{logger} + {data_path} + + + + random + + + + + + + + + ::/0 + + default + default + + + + + + + 3600 + 0 + 0 + 0 + 0 + 0 + + + + + {user_files_path} + default + {format_schema_path} + {cluster}-{id} + {listen_host} + {http_port} + {tcp_port} + {interserver_http_port} + ::1 + + + + + 604800 + + + 60 + + + 1000 + +{macros} +{remote_servers} +{keepers} + + +" + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub struct Macros { + pub shard: u64, + pub replica: ServerId, + pub cluster: String, +} + +impl Macros { + pub fn to_xml(&self) -> String { + let Macros { shard, replica, cluster } = self; + format!( + " + + {shard} + {replica} + {cluster} + " + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub struct RemoteServers { + pub cluster: String, + pub secret: String, + pub replicas: Vec, +} + +impl RemoteServers { + pub fn to_xml(&self) -> String { + let RemoteServers { cluster, secret, replicas } = self; + + let mut s = format!( + " + + <{cluster}> + {secret} + + true" + ); + + for r in replicas { + let ServerConfig { host, port } = r; + s.push_str(&format!( + " + + {host} + {port} + " + )); + } + + s.push_str(&format!( + " + + + + " + )); + + s + } +} + +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub struct KeeperConfigsForReplica { + pub nodes: Vec, +} + +impl KeeperConfigsForReplica { + pub fn to_xml(&self) -> String { + let mut s = String::from(" "); + for node in &self.nodes { + let ServerConfig { host, port } = node; + s.push_str(&format!( + " + + {host} + {port} + ", + )); + } + s.push_str("\n "); + s + } +} + +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub struct ServerConfig { + pub host: String, + pub port: u16, +} + +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub struct LogConfig { + pub level: LogLevel, + #[schemars(schema_with = "path_schema")] + pub log: Utf8PathBuf, + #[schemars(schema_with = "path_schema")] + pub errorlog: Utf8PathBuf, + // TODO: stronger type? + pub size: String, + pub count: usize, +} + +impl LogConfig { + pub fn to_xml(&self) -> String { + let LogConfig { level, log, errorlog, size, count } = &self; + format!( + " + + {level} + {log} + {errorlog} + {size} + {count} + +" + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub struct KeeperCoordinationSettings { + pub operation_timeout_ms: u32, + pub session_timeout_ms: u32, + pub raft_logs_level: LogLevel, +} + +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub struct RaftServers { + pub servers: Vec, +} + +impl RaftServers { + pub fn to_xml(&self) -> String { + let mut s = String::new(); + for server in &self.servers { + let RaftServerConfig { id, hostname, port } = server; + s.push_str(&format!( + " + + {id} + {hostname} + {port} + + " + )); + } + + s + } +} + +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub struct RaftServerConfig { + pub id: KeeperId, + pub hostname: String, + pub port: u16, +} + +/// Config for an individual Clickhouse Keeper +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub struct KeeperConfig { + pub logger: LogConfig, + pub listen_host: String, + pub tcp_port: u16, + pub server_id: KeeperId, + #[schemars(schema_with = "path_schema")] + pub log_storage_path: Utf8PathBuf, + #[schemars(schema_with = "path_schema")] + pub snapshot_storage_path: Utf8PathBuf, + pub coordination_settings: KeeperCoordinationSettings, + pub raft_config: RaftServers, +} + +impl KeeperConfig { + pub fn to_xml(&self) -> String { + let KeeperConfig { + logger, + listen_host, + tcp_port, + server_id, + log_storage_path, + snapshot_storage_path, + coordination_settings, + raft_config, + } = self; + let logger = logger.to_xml(); + let KeeperCoordinationSettings { + operation_timeout_ms, + session_timeout_ms, + raft_logs_level, + } = coordination_settings; + let raft_servers = raft_config.to_xml(); + format!( + " + +{logger} + {listen_host} + + false + {tcp_port} + {server_id} + {log_storage_path} + {snapshot_storage_path} + + {operation_timeout_ms} + {session_timeout_ms} + {raft_logs_level} + + +{raft_servers} + + + + +" + ) + } +} + +#[allow(unused)] +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub enum LogLevel { + Trace, + Debug, +} + +impl Display for LogLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + LogLevel::Trace => "trace", + LogLevel::Debug => "debug", + }; + write!(f, "{s}") + } +} diff --git a/clickhouse-admin/types/src/lib.rs b/clickhouse-admin/types/src/lib.rs new file mode 100644 index 0000000000..2a90788572 --- /dev/null +++ b/clickhouse-admin/types/src/lib.rs @@ -0,0 +1,47 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use derive_more::{Add, AddAssign, Display, From}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +pub mod config; + +/// A unique ID for a clickhouse keeper +#[derive( + Debug, + Clone, + Copy, + Eq, + PartialEq, + Ord, + PartialOrd, + From, + Add, + AddAssign, + Display, + JsonSchema, + Serialize, + Deserialize, +)] +pub struct KeeperId(pub u64); + +/// A unique ID for a clickhouse server +#[derive( + Debug, + Clone, + Copy, + Eq, + PartialEq, + Ord, + PartialOrd, + From, + Add, + AddAssign, + Display, + JsonSchema, + Serialize, + Deserialize, +)] +pub struct ServerId(pub u64);