admi api: remove info about local node from GetClusterStatus and add specific GetNodeInfo endpoint
Some checks are pending
ci/woodpecker/push/debug Pipeline is running
ci/woodpecker/pr/debug Pipeline was successful

This commit is contained in:
Alex 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: application/json:
schema: schema:
type: object type: object
required: [ node, garageVersion, garageFeatures, rustVersion, dbEngine, knownNodes, layout ] required: [ layoutVersion, nodes ]
properties: properties:
node: layoutVersion:
type: string type: integer
example: "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f" example: 1
garageVersion: nodes:
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:
type: array type: array
example: example:
- id: "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f" - id: "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f"
@ -131,8 +109,6 @@ paths:
hostname: neptune hostname: neptune
items: items:
$ref: '#/components/schemas/NodeNetworkInfo' $ref: '#/components/schemas/NodeNetworkInfo'
layout:
$ref: '#/components/schemas/ClusterLayout'
/ConnectClusterNodes: /ConnectClusterNodes:
post: 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: Returns the cluster's current status in JSON, including:
- ID of the node being queried and its version of the Garage daemon
- Live nodes - Live nodes
- Currently configured cluster layout - Currently configured cluster layout
- Staged changes to the cluster layout
Example response body: Example response body:
```json ```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, "layoutVersion": 5,
"nodes": [ "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. Clears all of the staged layout changes.
Request body format: This requests contains an empty body.
```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 returns the new cluster layout with all changes reverted, This returns the new cluster layout with all changes reverted,
as returned by GetClusterLayout. as returned by GetClusterLayout.

View file

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

View file

@ -16,6 +16,7 @@ use opentelemetry_prometheus::PrometheusExporter;
use garage_model::garage::Garage; use garage_model::garage::Garage;
use garage_rpc::{Endpoint as RpcEndpoint, *}; use garage_rpc::{Endpoint as RpcEndpoint, *};
use garage_util::background::BackgroundRunner; use garage_util::background::BackgroundRunner;
use garage_util::data::Uuid;
use garage_util::error::Error as GarageError; use garage_util::error::Error as GarageError;
use garage_util::socket_address::UnixOrTCPSocketAddress; use garage_util::socket_address::UnixOrTCPSocketAddress;
@ -265,3 +266,40 @@ fn verify_bearer_token(token: &hyper::http::HeaderValue, password_hash: &str) ->
Ok(()) 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)); nodes.sort_by(|x, y| x.id.cmp(&y.id));
Ok(GetClusterStatusResponse { 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, layout_version: layout.current().version,
nodes, nodes,
}) })

View file

@ -136,20 +136,7 @@ macro_rules! local_admin_endpoints {
type Response = [< $endpoint Response >]; type Response = [< $endpoint Response >];
async fn handle(self, garage: &Arc<Garage>, admin: &Admin) -> Result<Self::Response, Error> { async fn handle(self, garage: &Arc<Garage>, admin: &Admin) -> Result<Self::Response, Error> {
let to = match self.node.as_str() { let to = find_matching_nodes(garage, 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 resps = garage.system.rpc_helper().call_many(&admin.endpoint, let resps = garage.system.rpc_helper().call_many(&admin.endpoint,
&to, &to,

View file

@ -18,6 +18,25 @@ use crate::api::*;
use crate::error::Error; use crate::error::Error;
use crate::{Admin, RequestHandler}; 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 { impl RequestHandler for LocalCreateMetadataSnapshotRequest {
type Response = LocalCreateMetadataSnapshotResponse; type Response = LocalCreateMetadataSnapshotResponse;

View file

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