diff --git a/src/meta-srv/src/keys.rs b/src/meta-srv/src/keys.rs index 7a8f73d65a..9ee1100c65 100644 --- a/src/meta-srv/src/keys.rs +++ b/src/meta-srv/src/keys.rs @@ -36,7 +36,8 @@ lazy_static! { static ref DATANODE_STAT_KEY_PATTERN: Regex = Regex::new(&format!("^{DN_STAT_PREFIX}-([0-9]+)-([0-9]+)$")).unwrap(); } -#[derive(Debug, Clone, Eq, Hash, PartialEq)] + +#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct LeaseKey { pub cluster_id: u64, pub node_id: u64, diff --git a/src/meta-srv/src/service/admin.rs b/src/meta-srv/src/service/admin.rs index 43adfd0fe7..c9efebb798 100644 --- a/src/meta-srv/src/service/admin.rs +++ b/src/meta-srv/src/service/admin.rs @@ -16,6 +16,7 @@ mod health; mod heartbeat; mod leader; mod meta; +mod node_lease; mod route; use std::collections::HashMap; @@ -32,6 +33,13 @@ use crate::metasrv::MetaSrv; pub fn make_admin_service(meta_srv: MetaSrv) -> Admin { let router = Router::new().route("/health", health::HealthHandler); + let router = router.route( + "/node-lease", + node_lease::NodeLeaseHandler { + meta_peer_client: meta_srv.meta_peer_client(), + }, + ); + let router = router.route( "/heartbeat", heartbeat::HeartBeatHandler { @@ -119,7 +127,7 @@ impl Service> for Admin where T: Send, { - type Response = http::Response; + type Response = http::Response; type Error = Infallible; type Future = BoxFuture; diff --git a/src/meta-srv/src/service/admin/node_lease.rs b/src/meta-srv/src/service/admin/node_lease.rs new file mode 100644 index 0000000000..3b9b63f31d --- /dev/null +++ b/src/meta-srv/src/service/admin/node_lease.rs @@ -0,0 +1,88 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use snafu::{OptionExt, ResultExt}; +use tonic::codegen::http; + +use crate::cluster::MetaPeerClientRef; +use crate::error::{self, Result}; +use crate::keys::{LeaseKey, LeaseValue}; +use crate::lease; +use crate::service::admin::HttpHandler; + +pub struct NodeLeaseHandler { + pub meta_peer_client: MetaPeerClientRef, +} + +#[async_trait::async_trait] +impl HttpHandler for NodeLeaseHandler { + async fn handle( + &self, + _: &str, + params: &HashMap, + ) -> Result> { + let cluster_id = params + .get("cluster_id") + .map(|id| id.parse::()) + .context(error::MissingRequiredParameterSnafu { + param: "cluster_id", + })? + .context(error::ParseNumSnafu { + err_msg: "`cluster_id` is not a valid number", + })?; + + let leases = + lease::filter_datanodes(cluster_id, &self.meta_peer_client, |_, _| true).await?; + let leases = leases + .into_iter() + .map(|(k, v)| HumanLease { + name: k, + human_time: common_time::DateTime::new(v.timestamp_millis / 1000).to_string(), + lease: v, + }) + .collect::>(); + let result = LeaseValues { leases }.try_into()?; + + http::Response::builder() + .status(http::StatusCode::OK) + .body(result) + .context(error::InvalidHttpBodySnafu) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct HumanLease { + pub name: LeaseKey, + pub human_time: String, + pub lease: LeaseValue, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(transparent)] +pub struct LeaseValues { + pub leases: Vec, +} + +impl TryFrom for String { + type Error = error::Error; + + fn try_from(vals: LeaseValues) -> Result { + serde_json::to_string(&vals).context(error::SerializeToJsonSnafu { + input: format!("{vals:?}"), + }) + } +}