admi api: remove info about local node from GetClusterStatus and add specific GetNodeInfo endpoint

This commit is contained in:
Alex Auvolat 2025-03-06 10:26:01 +01:00
parent 29ce490dd6
commit 2e03d90585
8 changed files with 85 additions and 85 deletions

View file

@ -84,34 +84,12 @@ paths:
application/json:
schema:
type: object
required: [ node, garageVersion, garageFeatures, rustVersion, dbEngine, knownNodes, layout ]
required: [ layoutVersion, nodes ]
properties:
node:
type: string
example: "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f"
garageVersion:
type: string
example: "v2.0.0"
garageFeatures:
type: array
items:
type: string
example:
- "k2v"
- "lmdb"
- "sqlite"
- "consul-discovery"
- "kubernetes-discovery"
- "metrics"
- "telemetry-otlp"
- "bundled-libs"
rustVersion:
type: string
example: "1.68.0"
dbEngine:
type: string
example: "LMDB (using Heed crate)"
knownNodes:
layoutVersion:
type: integer
example: 1
nodes:
type: array
example:
- id: "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f"
@ -131,8 +109,6 @@ paths:
hostname: neptune
items:
$ref: '#/components/schemas/NodeNetworkInfo'
layout:
$ref: '#/components/schemas/ClusterLayout'
/ConnectClusterNodes:
post:

View file

@ -68,26 +68,13 @@ Returns HTTP 200 Ok if yes, or HTTP 4xx if no website is available for this doma
Returns the cluster's current status in JSON, including:
- ID of the node being queried and its version of the Garage daemon
- Live nodes
- Currently configured cluster layout
- Staged changes to the cluster layout
Example response body:
```json
{
"node": "b10c110e4e854e5aa3f4637681befac755154b20059ec163254ddbfae86b09df",
"garageVersion": "v2.0.0",
"garageFeatures": [
"k2v",
"lmdb",
"sqlite",
"metrics",
"bundled-libs"
],
"rustVersion": "1.68.0",
"dbEngine": "LMDB (using Heed crate)",
"layoutVersion": 5,
"nodes": [
{
@ -362,19 +349,7 @@ layout, as well as the description of the layout as returned by GetClusterLayout
Clears all of the staged layout changes.
Request body format:
```json
{
"version": 13
}
```
Reverting the staged changes is done by incrementing the version number
and clearing the contents of the staged change list.
Similarly to the CLI, the body must include the incremented
version number, which MUST be 1 + the value of the currently
existing layout in the cluster.
This requests contains an empty body.
This returns the new cluster layout with all changes reverted,
as returned by GetClusterLayout.

View file

@ -10,10 +10,9 @@ use garage_rpc::*;
use garage_model::garage::Garage;
use garage_api_common::common_error::CommonErrorDerivative;
use garage_api_common::helpers::is_default;
use crate::api_server::{AdminRpc, AdminRpcResponse};
use crate::api_server::{find_matching_nodes, AdminRpc, AdminRpcResponse};
use crate::error::Error;
use crate::macros::*;
use crate::{Admin, RequestHandler};
@ -77,6 +76,7 @@ admin_endpoints![
RemoveBucketAlias,
// Node operations
GetNodeInfo,
CreateMetadataSnapshot,
GetNodeStatistics,
GetClusterStatistics,
@ -97,6 +97,7 @@ admin_endpoints![
local_admin_endpoints![
// Node operations
GetNodeInfo,
CreateMetadataSnapshot,
GetNodeStatistics,
LaunchRepairOperation,
@ -157,11 +158,6 @@ pub struct GetClusterStatusRequest;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetClusterStatusResponse {
pub node: String,
pub garage_version: String,
pub garage_features: Option<Vec<String>>,
pub rust_version: String,
pub db_engine: String,
pub layout_version: u64,
pub nodes: Vec<NodeResp>,
}
@ -636,6 +632,21 @@ pub struct RemoveBucketAliasResponse(pub GetBucketInfoResponse);
// Node operations
// **********************************************
// ---- GetNodeInfo ----
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LocalGetNodeInfoRequest;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct LocalGetNodeInfoResponse {
pub node_id: String,
pub garage_version: String,
pub garage_features: Option<Vec<String>>,
pub rust_version: String,
pub db_engine: String,
}
// ---- CreateMetadataSnapshot ----
#[derive(Debug, Clone, Serialize, Deserialize, Default)]

View file

@ -16,6 +16,7 @@ use opentelemetry_prometheus::PrometheusExporter;
use garage_model::garage::Garage;
use garage_rpc::{Endpoint as RpcEndpoint, *};
use garage_util::background::BackgroundRunner;
use garage_util::data::Uuid;
use garage_util::error::Error as GarageError;
use garage_util::socket_address::UnixOrTCPSocketAddress;
@ -265,3 +266,40 @@ fn verify_bearer_token(token: &hyper::http::HeaderValue, password_hash: &str) ->
Ok(())
}
pub(crate) fn find_matching_nodes(garage: &Garage, spec: &str) -> Result<Vec<Uuid>, Error> {
let mut res = vec![];
if spec == "*" {
res = garage.system.cluster_layout().all_nodes().to_vec();
for node in garage.system.get_known_nodes() {
if node.is_up && !res.contains(&node.id) {
res.push(node.id);
}
}
} else if spec == "self" {
res.push(garage.system.id);
} else {
let layout = garage.system.cluster_layout();
let known_nodes = garage.system.get_known_nodes();
let all_nodes = layout
.all_nodes()
.iter()
.copied()
.chain(known_nodes.iter().filter(|x| x.is_up).map(|x| x.id));
for node in all_nodes {
if !res.contains(&node) && hex::encode(node).starts_with(spec) {
res.push(node);
}
}
if res.is_empty() {
return Err(Error::bad_request(format!("No nodes matching {}", spec)));
}
if res.len() > 1 {
return Err(Error::bad_request(format!(
"Multiple nodes matching {}: {:?}",
spec, res
)));
}
}
Ok(res)
}

View file

@ -105,12 +105,6 @@ impl RequestHandler for GetClusterStatusRequest {
nodes.sort_by(|x, y| x.id.cmp(&y.id));
Ok(GetClusterStatusResponse {
node: hex::encode(garage.system.id),
garage_version: garage_util::version::garage_version().to_string(),
garage_features: garage_util::version::garage_features()
.map(|features| features.iter().map(ToString::to_string).collect()),
rust_version: garage_util::version::rust_version().to_string(),
db_engine: garage.db.engine(),
layout_version: layout.current().version,
nodes,
})

View file

@ -136,20 +136,7 @@ macro_rules! local_admin_endpoints {
type Response = [< $endpoint Response >];
async fn handle(self, garage: &Arc<Garage>, admin: &Admin) -> Result<Self::Response, Error> {
let to = match self.node.as_str() {
"*" => garage.system.cluster_layout().all_nodes().to_vec(),
id => {
let nodes = garage.system.cluster_layout().all_nodes()
.iter()
.filter(|x| hex::encode(x).starts_with(id))
.cloned()
.collect::<Vec<_>>();
if nodes.len() != 1 {
return Err(Error::bad_request(format!("Zero or multiple nodes matching {}: {:?}", id, nodes)));
}
nodes
}
};
let to = find_matching_nodes(garage, self.node.as_str())?;
let resps = garage.system.rpc_helper().call_many(&admin.endpoint,
&to,

View file

@ -18,6 +18,25 @@ use crate::api::*;
use crate::error::Error;
use crate::{Admin, RequestHandler};
impl RequestHandler for LocalGetNodeInfoRequest {
type Response = LocalGetNodeInfoResponse;
async fn handle(
self,
garage: &Arc<Garage>,
_admin: &Admin,
) -> Result<LocalGetNodeInfoResponse, Error> {
Ok(LocalGetNodeInfoResponse {
node_id: hex::encode(garage.system.id),
garage_version: garage_util::version::garage_version().to_string(),
garage_features: garage_util::version::garage_features()
.map(|features| features.iter().map(ToString::to_string).collect()),
rust_version: garage_util::version::rust_version().to_string(),
db_engine: garage.db.engine(),
})
}
}
impl RequestHandler for LocalCreateMetadataSnapshotRequest {
type Response = LocalCreateMetadataSnapshotResponse;

View file

@ -60,6 +60,7 @@ impl AdminApiRequest {
POST AddBucketAlias (body),
POST RemoveBucketAlias (body),
// Node APIs
GET GetNodeInfo (default::body, query::node),
POST CreateMetadataSnapshot (default::body, query::node),
GET GetNodeStatistics (default::body, query::node),
GET GetClusterStatistics (),
@ -93,9 +94,8 @@ impl AdminApiRequest {
use router_v1::Endpoint;
match v1_endpoint {
Endpoint::GetClusterStatus => {
Ok(AdminApiRequest::GetClusterStatus(GetClusterStatusRequest))
}
// GetClusterStatus semantics changed:
// info about local node is no longer returned
Endpoint::GetClusterHealth => {
Ok(AdminApiRequest::GetClusterHealth(GetClusterHealthRequest))
}