Skip to content

Commit

Permalink
add detailed node api
Browse files Browse the repository at this point in the history
  • Loading branch information
archeoss committed Nov 16, 2023
1 parent 02c5872 commit 14d7edf
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ Bob Management GUI changelog
- Home page, backend (#18)
- Node list page, backend (#19)
- VDisk list page, backend (#20)
- Detailed node information page, backend (#21)
4 changes: 4 additions & 0 deletions backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ impl Modify for SecurityAddon {
services::api::get_nodes,
services::api::raw_metrics_by_node,
services::api::raw_configuration_by_node,
services::api::get_nodes,
services::api::get_detailed_node_info,
services::api::get_vdisks,
),
components(
Expand All @@ -59,6 +61,8 @@ impl Modify for SecurityAddon {
models::api::SpaceInfo,
models::api::VDisk,
models::api::VDiskStatus,
models::api::DetailedNode,
models::api::DetailedNodeMetrics,
models::api::Operation,
models::api::RPS,
models::api::RawMetricEntry,
Expand Down
36 changes: 36 additions & 0 deletions backend/src/models/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,42 @@ pub enum VDiskStatus {
Offline,
}

#[derive(ToSchema, Debug, Clone, Serialize)]
pub struct DetailedNode {
pub name: String,

pub hostname: String,

pub vdisks: Vec<VDisk>,

#[serde(flatten)]
pub status: NodeStatus,

pub metrics: DetailedNodeMetrics,

pub disks: Vec<Disk>,
}

#[derive(ToSchema, Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DetailedNodeMetrics {
pub rps: RPS,

pub alien_count: u64,

pub corrupted_count: u64,

pub space: SpaceInfo,

pub cpu_load: u64,

pub total_ram: u64,

pub used_ram: u64,

pub descr_amount: u64,
}

#[derive(
ToSchema, Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, EnumIter,
)]
Expand Down
183 changes: 182 additions & 1 deletion backend/src/services/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use crate::{

use super::{
auth::HttpClient,
methods::{request_configuration, request_metrics, request_nodes, request_vdisks},
methods::{
request_configuration, request_disks, request_metrics, request_nodes, request_space,
request_status, request_vdisks,
},
prelude::*,
};

Expand Down Expand Up @@ -558,6 +561,53 @@ fn disk_status_from_space(space: &dto::SpaceInfo, occupied_space: u64) -> DiskSt
}
}

// Bad function, 6 args :(
async fn process_vdisks_for_node(
client: HttpBobClient,
virt_disks: &[dto::VDisk],
node_name: NodeName,
all_disks: &HashMap<DiskName, IsActive>,
nodes: &HashMap<&NodeName, &dto::Node>,
partitions_count_on_vdisk: &HashMap<i32, usize>,
) -> Vec<VDisk> {
let mut res_replicas = vec![];
let mut res_vdisks = vec![];
for (vdisk, replicas) in virt_disks
.iter()
.filter_map(|vdisk| vdisk.replicas.as_ref().map(|repl| (vdisk, repl)))
.filter(|(_, replicas)| replicas.iter().any(|replica| replica.node == node_name))
{
for replica in replicas {
res_replicas.push((
vdisk.id,
process_replica(&client, replica.clone(), all_disks, nodes).await,
));
}
res_vdisks.push(VDisk {
id: vdisk.id as u64,
status: if res_replicas
.iter()
.any(|(_, replica)| matches!(replica.status, ReplicaStatus::Offline(_)))
{
VDiskStatus::Bad
} else {
VDiskStatus::Good
},
partition_count: partitions_count_on_vdisk
.get(&vdisk.id)
.copied()
.unwrap_or_default() as u64,
replicas: res_replicas
.iter()
.filter(|(id, _)| id == &vdisk.id)
.map(|(_, replica)| replica.clone())
.collect(),
});
}

res_vdisks
}

/// Get Raw Metrics from Node
///
/// # Errors
Expand Down Expand Up @@ -608,6 +658,137 @@ pub async fn raw_configuration_by_node(
Ok(Json(request_configuration(client.as_ref()).await?))
}

/// Get Detailed Information on Node
///
/// # Errors
///
/// This function will return an error if the server was unable to get node'a client
/// or one of the requests to get information from the node fails
#[cfg_attr(feature = "swagger", utoipa::path(
get,
context_path = ApiV1::to_path(),
path = "/nodes/{node_name}",
params (
("id", description = "Node's ID")
),
responses(
(status = 200, body = DetailedNode, content_type = "application/json", description = "Detailed Node information"),
(status = 401, description = "Unauthorized"),
(status = 404, description = "Node Not Found")
),
security(("api_key" = []))
))]
pub async fn get_detailed_node_info(
Extension(client): Extension<HttpBobClient>,
Path(node_name): Path<NodeName>,
) -> AxumResult<Json<DetailedNode>> {
let mut all_disks: FuturesUnordered<_> = client
.cluster()
.map(move |node| {
let handle = node.clone();
tokio::spawn(async move { handle.get_disks().await })
})
.collect();

let node_client = get_client_by_node(&client, node_name.clone()).await?;

let virt_disks = request_vdisks(node_client.as_ref()).await?;

let mut all_partitions: FuturesUnordered<_> = virt_disks
.iter()
.map(|vdisk| {
let id = vdisk.id;
let handle = client.api_main().clone();
tokio::spawn(async move { (id, handle.get_partitions(id).await) })
})
.collect();

let metrics = request_metrics(node_client.as_ref()).await?;
let typed_metrics: TypedMetrics = metrics.clone().into();

let space = request_space(node_client.as_ref()).await?;
let node_status = request_status(node_client.as_ref()).await?;
let disks = request_disks(node_client.as_ref()).await?;

let res_disks = proccess_disks(&disks, &space, &metrics);
let nodes = request_nodes(node_client.as_ref()).await?;

let nodes = nodes
.iter()
.map(|node| (&node.name, node))
.collect::<HashMap<&NodeName, &dto::Node>>();

let mut proc_disks = HashMap::new();
while let Some(disks) = all_disks.next().await {
let Ok(Ok(GetDisksResponse::AJSONArrayWithDisksAndTheirStates(disks))) = disks else {
tracing::error!("couldn't get disk inforamtion from node");
continue;
};
for disk in disks {
proc_disks.insert(disk.name, disk.is_active);
}
}

let mut res_partitions = HashMap::new();
while let Some(partitions) = all_partitions.next().await {
let Ok((id, Ok(GetPartitionsResponse::NodeInfoAndJSONArrayWithPartitionsInfo(partitions)))) =
partitions
else {
// tracing::error!("couldn't get Partition inforamtion from node"); // Too noisy
continue;
};
if let Some(partitions) = partitions.partitions {
res_partitions.insert(id, partitions.len());
}
}

let vdisks = process_vdisks_for_node(
client,
&virt_disks,
node_name,
&proc_disks,
&nodes,
&res_partitions,
)
.await;

let mut rps = RPS::new();

let status = NodeStatus::from_problems(NodeProblem::default_from_metrics(&typed_metrics));

rps[Operation::Get] = typed_metrics[RawMetricEntry::PearlGetCountRate].value;
rps[Operation::Put] = typed_metrics[RawMetricEntry::PearlPutCountRate].value;
rps[Operation::Exist] = typed_metrics[RawMetricEntry::PearlExistCountRate].value;
rps[Operation::Delete] = typed_metrics[RawMetricEntry::PearlDeleteCountRate].value;

let res = Json(DetailedNode {
name: node_status.name,
hostname: node_status.address,
vdisks,
status,
metrics: DetailedNodeMetrics {
rps,
alien_count: typed_metrics[RawMetricEntry::BackendAlienCount].value,
corrupted_count: typed_metrics[RawMetricEntry::BackendCorruptedBlobCount].value,
space: SpaceInfo {
total_disk: space.total_disk_space_bytes,
free_disk: space.total_disk_space_bytes - space.used_disk_space_bytes,
used_disk: space.used_disk_space_bytes,
occupied_disk: space.occupied_disk_space_bytes,
},
cpu_load: typed_metrics[RawMetricEntry::HardwareBobCpuLoad].value,
total_ram: typed_metrics[RawMetricEntry::HardwareTotalRam].value,
used_ram: typed_metrics[RawMetricEntry::HardwareUsedRam].value,
descr_amount: typed_metrics[RawMetricEntry::HardwareDescrAmount].value,
},
disks: res_disks,
});
tracing::trace!("send response: {res:?}");

Ok(res)
}

async fn get_client_by_node(
client: &HttpBobClient,
node_name: NodeName,
Expand Down
5 changes: 4 additions & 1 deletion backend/src/services/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ use api::{get_disks_count, get_nodes_count, get_rps, get_space};
use auth::{login, logout, require_auth, AuthState, BobUser, HttpBobClient, InMemorySessionStore};
use prelude::*;

use self::api::{get_nodes, raw_configuration_by_node, raw_metrics_by_node, get_vdisks};
use self::api::{
get_detailed_node_info, get_nodes, get_vdisks, raw_configuration_by_node, raw_metrics_by_node,
};

type BobAuthState = AuthState<
BobUser,
Expand All @@ -52,6 +54,7 @@ pub fn api_router_v1(auth_state: BobAuthState) -> Result<Router<BobAuthState>, R
.api_route("/nodes/rps", &Method::GET, get_rps)
.api_route("/nodes/space", &Method::GET, get_space)
.api_route("/nodes", &Method::GET, get_nodes)
.api_route("/nodes/:node_name", &Method::GET, get_detailed_node_info)
.api_route(
"/nodes/:node_name/metrics",
&Method::GET,
Expand Down

0 comments on commit 14d7edf

Please sign in to comment.