Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single node clickhouse init #6903

Draft
wants to merge 5 commits into
base: clickhouse-admin-smf-package-cleanup
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"clients/bootstrap-agent-client",
"clients/clickhouse-admin-keeper-client",
"clients/clickhouse-admin-server-client",
"clients/clickhouse-admin-single-client",
"clients/cockroach-admin-client",
"clients/ddm-admin-client",
"clients/dns-service-client",
Expand Down Expand Up @@ -131,6 +132,7 @@ default-members = [
"clients/bootstrap-agent-client",
"clients/clickhouse-admin-keeper-client",
"clients/clickhouse-admin-server-client",
"clients/clickhouse-admin-single-client",
"clients/cockroach-admin-client",
"clients/ddm-admin-client",
"clients/dns-service-client",
Expand Down Expand Up @@ -318,6 +320,7 @@ clap = { version = "4.5", features = ["cargo", "derive", "env", "wrap_help"] }
clickhouse-admin-api = { path = "clickhouse-admin/api" }
clickhouse-admin-keeper-client = { path = "clients/clickhouse-admin-keeper-client" }
clickhouse-admin-server-client = { path = "clients/clickhouse-admin-server-client" }
clickhouse-admin-single-client = { path = "clients/clickhouse-admin-single-client" }
clickhouse-admin-types = { path = "clickhouse-admin/types" }
clickward = { git = "https://github.com/oxidecomputer/clickward", rev = "ceec762e6a87d2a22bf56792a3025e145caa095e" }
cockroach-admin-api = { path = "cockroach-admin/api" }
Expand Down
2 changes: 1 addition & 1 deletion clickhouse-admin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ http.workspace = true
illumos-utils.workspace = true
omicron-common.workspace = true
omicron-uuid-kinds.workspace = true
oximeter-db.workspace = true
schemars.workspace = true
slog.workspace = true
slog-async.workspace = true
Expand All @@ -35,7 +36,6 @@ dropshot.workspace = true
expectorate.workspace = true
nexus-test-utils.workspace = true
omicron-test-utils.workspace = true
oximeter-db.workspace = true
oximeter-test-utils.workspace = true
openapi-lint.workspace = true
openapiv3.workspace = true
Expand Down
24 changes: 23 additions & 1 deletion clickhouse-admin/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use clickhouse_admin_types::{
ServerConfigurableSettings,
};
use dropshot::{
HttpError, HttpResponseCreated, HttpResponseOk, RequestContext, TypedBody,
HttpError, HttpResponseCreated, HttpResponseOk,
HttpResponseUpdatedNoContent, RequestContext, TypedBody,
};

/// API interface for our clickhouse-admin-keeper server
Expand Down Expand Up @@ -106,3 +107,24 @@ pub trait ClickhouseAdminServerApi {
body: TypedBody<ServerConfigurableSettings>,
) -> Result<HttpResponseCreated<ReplicaConfig>, HttpError>;
}

/// API interface for our clickhouse-admin-single server
///
/// The single-node server is distinct from the both the multi-node servers
/// and its keepers. The sole purpose of this API is to serialize database
/// initialization requests from reconfigurator execution. Multi-node clusters
/// must eventually implement a similar interface, but the implementation will
/// obviously be more complex.
#[dropshot::api_description]
pub trait ClickhouseAdminSingleApi {
type Context;

/// Idempotently initialize a single-node ClickHouse database.
#[endpoint {
method = PUT,
path = "/init"
}]
async fn init_db(
rqctx: RequestContext<Self::Context>,
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
}
72 changes: 72 additions & 0 deletions clickhouse-admin/src/bin/clickhouse-admin-single.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// 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/.

//! Single-node ClickHouse database admin binary.

use anyhow::anyhow;
use camino::Utf8PathBuf;
use clap::Parser;
use omicron_clickhouse_admin::{ClickhouseCli, Config};
use omicron_common::cmd::fatal;
use omicron_common::cmd::CmdError;
use std::net::{SocketAddr, SocketAddrV6};

#[derive(Debug, Parser)]
#[clap(
name = "clickhouse-admin-single",
about = "Single-node ClickHouse admin server"
)]
enum Args {
/// Start the single-node ClickHouse admin server
Run {
/// Address on which this server should run
#[clap(long, short = 'a', action)]
http_address: SocketAddrV6,

/// Path to the server configuration file
#[clap(long, short, action)]
config: Utf8PathBuf,

/// Address of the ClickHouse single-node database server
#[clap(long, short = 'l', action)]
listen_address: SocketAddrV6,

/// Path to the clickhouse binary
#[clap(long, short, action)]
binary_path: Utf8PathBuf,
},
}

#[tokio::main]
async fn main() {
if let Err(err) = main_impl().await {
fatal(err);
}
}

async fn main_impl() -> Result<(), CmdError> {
let args = Args::parse();

match args {
Args::Run { http_address, config, listen_address, binary_path } => {
let mut config = Config::from_file(&config)
.map_err(|err| CmdError::Failure(anyhow!(err)))?;
config.dropshot.bind_address = SocketAddr::V6(http_address);
let clickhouse_cli =
ClickhouseCli::new(binary_path, listen_address);

let server = omicron_clickhouse_admin::start_single_admin_server(
clickhouse_cli,
config,
)
.await
.map_err(|err| CmdError::Failure(anyhow!(err)))?;
server.await.map_err(|err| {
CmdError::Failure(anyhow!(
"server failed after starting: {err}"
))
})
}
}
}
21 changes: 21 additions & 0 deletions clickhouse-admin/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use crate::{ClickhouseCli, Clickward};
use slog::Logger;
use std::sync::Arc;
use tokio::sync::Mutex;

pub struct ServerContext {
clickward: Clickward,
Expand All @@ -28,3 +30,22 @@ impl ServerContext {
&self.clickhouse_cli
}
}

pub struct SingleServerContext {
clickhouse_cli: ClickhouseCli,
db_initialized: Arc<Mutex<bool>>,
}

impl SingleServerContext {
pub fn new(clickhouse_cli: ClickhouseCli) -> Self {
Self { clickhouse_cli, db_initialized: Arc::new(Mutex::new(false)) }
}

pub fn clickhouse_cli(&self) -> &ClickhouseCli {
&self.clickhouse_cli
}

pub fn db_initialized(&self) -> Arc<Mutex<bool>> {
self.db_initialized.clone()
}
}
54 changes: 48 additions & 6 deletions clickhouse-admin/src/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,38 @@
// 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::context::ServerContext;
use crate::context::{ServerContext, SingleServerContext};
use clickhouse_admin_api::*;
use clickhouse_admin_types::{
ClickhouseKeeperClusterMembership, KeeperConf, KeeperConfig,
KeeperConfigurableSettings, Lgif, RaftConfig, ReplicaConfig,
ServerConfigurableSettings,
};
use dropshot::{
HttpError, HttpResponseCreated, HttpResponseOk, RequestContext, TypedBody,
ApiDescription, HttpError, HttpResponseCreated, HttpResponseOk,
HttpResponseUpdatedNoContent, RequestContext, TypedBody,
};
use illumos_utils::svcadm::Svcadm;
use oximeter_db::{Client as OximeterClient, OXIMETER_VERSION};
use slog::debug;
use std::sync::Arc;

type ClickhouseApiDescription = dropshot::ApiDescription<Arc<ServerContext>>;

pub fn clickhouse_admin_server_api() -> ClickhouseApiDescription {
pub fn clickhouse_admin_server_api() -> ApiDescription<Arc<ServerContext>> {
clickhouse_admin_server_api_mod::api_description::<ClickhouseAdminServerImpl>()
.expect("registered entrypoints")
}

pub fn clickhouse_admin_keeper_api() -> ClickhouseApiDescription {
pub fn clickhouse_admin_keeper_api() -> ApiDescription<Arc<ServerContext>> {
clickhouse_admin_keeper_api_mod::api_description::<ClickhouseAdminKeeperImpl>()
.expect("registered entrypoints")
}

pub fn clickhouse_admin_single_api() -> ApiDescription<Arc<SingleServerContext>>
{
clickhouse_admin_single_api_mod::api_description::<ClickhouseAdminSingleImpl>()
.expect("registered entrypoints")
}

enum ClickhouseAdminServerImpl {}

impl ClickhouseAdminServerApi for ClickhouseAdminServerImpl {
Expand Down Expand Up @@ -102,3 +109,38 @@ impl ClickhouseAdminKeeperApi for ClickhouseAdminKeeperImpl {
Ok(HttpResponseOk(output))
}
}

enum ClickhouseAdminSingleImpl {}

impl ClickhouseAdminSingleApi for ClickhouseAdminSingleImpl {
type Context = Arc<SingleServerContext>;

async fn init_db(
rqctx: RequestContext<Self::Context>,
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
let log = &rqctx.log;
let ctx = rqctx.context();
let initialized = ctx.db_initialized();
let mut initialized = initialized.lock().await;
if !*initialized {
let address = ctx.clickhouse_cli().listen_address;
let client = OximeterClient::new(address.into(), log);
debug!(
log,
"initializing single-node ClickHouse \
at {address} to version {OXIMETER_VERSION}"
);
client
.initialize_db_with_version(false, OXIMETER_VERSION)
.await
.map_err(|e| {
HttpError::for_internal_error(format!(
"can't initialize single-node ClickHouse \
at {address} to version {OXIMETER_VERSION}: {e}",
))
})?;
*initialized = true;
}
Ok(HttpResponseUpdatedNoContent())
}
}
Loading
Loading