Skip to content
Open
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
6 changes: 6 additions & 0 deletions metrics-cache/src/piggyback/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::Arc;

use crate::host_settings::HostSettings;
use crate::piggyback::{AggregationHost, Meta, PiggybackHost};
use crate::section::node_kubelet::KubeNodeKubeletV1;
use crate::section::writeable::{SectionError, WriteableSection};
use crate::snapshot::Snapshot;

Expand Down Expand Up @@ -52,6 +53,11 @@ impl PiggybackHost for Node<'_> {
fn emit(&self) -> Vec<Result<WriteableSection, SectionError>> {
let me = self.meta.piggyback_hostname(&self.settings.cluster_name);
let mut out = Vec::new();

if let Some(kube_node_kubelet_v1) = KubeNodeKubeletV1::from_node(self.api) {
out.push(WriteableSection::of(&me, &kube_node_kubelet_v1));
}

out.extend(self.aggregation_sections(&me));
out
}
Expand Down
1 change: 1 addition & 0 deletions metrics-cache/src/section/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod common;
pub mod cronjob;
pub mod namespace;
pub mod node_kubelet;
pub mod performance;
pub mod pod;
pub mod pvc;
Expand Down
90 changes: 90 additions & 0 deletions metrics-cache/src/section/node_kubelet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use k8s_openapi::api::core::v1::Node;
use serde::Serialize;

use crate::section::Section;

#[derive(Serialize)]
pub(crate) struct HealthZ<'a> {
pub status_code: u16,
pub response: &'a str,
}

/// Kubelet version and health. (`kube_node_kubelet_v1`)
#[derive(Serialize)]
pub(crate) struct KubeNodeKubeletV1<'a> {
pub version: &'a str,
pub health: HealthZ<'a>,
}

impl<'a> KubeNodeKubeletV1<'a> {
pub fn from_node(node: &'a Node) -> Option<KubeNodeKubeletV1<'a>> {
let status = node.status.as_ref()?;
let node_info = status.node_info.as_ref()?;
let ready = status
.conditions
.as_deref()
.unwrap_or_default()
.iter()
.find(|c| c.type_ == "Ready");

let health = match ready {
Some(c) if c.status == "True" => HealthZ {
status_code: 200,
response: c.message.as_deref().unwrap_or(""),
},
Some(c) => HealthZ {
// "False" or "Unknown" -- deliberately not distinguished.
status_code: 503,
response: c.message.as_deref().unwrap_or(""),
},
None => return None,
};

Some(KubeNodeKubeletV1 {
version: &node_info.kubelet_version,
health,
})
}
}

impl Section for KubeNodeKubeletV1<'_> {
const NAME: &'static str = "kube_node_kubelet_v1";
}

#[cfg(test)]
mod tests {
use super::*;

use crate::test_support::*;

#[test]
fn kube_node_kubelet_v1_ready() {
let node = node_prefilled("node01");
insta::assert_json_snapshot!(KubeNodeKubeletV1::from_node(&node));
}

#[test]
fn kube_node_kubelet_v1_not_ready() {
let mut node = node_prefilled("node01");
let condition = &mut node
.status
.as_mut()
.expect("node_prefilled sets status")
.conditions
.as_mut()
.expect("node_prefilled sets a Ready condition")[0];
condition.status = s("False");
condition.message = Some(s("kubelet is not posting ready status"));
insta::assert_json_snapshot!(KubeNodeKubeletV1::from_node(&node));
}

#[test]
fn kube_node_kubelet_v1_no_ready_condition() {
let mut node = node_prefilled("node01");
node.status
.as_mut()
.expect("node_prefilled sets status")
.conditions = None;
assert!(KubeNodeKubeletV1::from_node(&node).is_none());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: metrics-cache/src/section/node_kubelet.rs
expression: "KubeNodeKubeletV1::from_node(&node)"
---
{
"version": "v1.34.0",
"health": {
"status_code": 503,
"response": "kubelet is not posting ready status"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: metrics-cache/src/section/node_kubelet.rs
expression: "KubeNodeKubeletV1::from_node(&node)"
---
{
"version": "v1.34.0",
"health": {
"status_code": 200,
"response": "kubelet is posting ready status"
}
}
48 changes: 46 additions & 2 deletions metrics-cache/src/test_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
use k8s_openapi::api::apps::v1::ReplicaSet;
use k8s_openapi::api::batch::v1::{CronJob, CronJobSpec, Job};
use k8s_openapi::api::core::v1::{
Container, Namespace, Node, PersistentVolumeClaim, PersistentVolumeClaimSpec,
PersistentVolumeClaimStatus, Pod, PodSpec, VolumeResourceRequirements,
Container, Namespace, Node, NodeAddress, NodeCondition, NodeStatus, NodeSystemInfo,
PersistentVolumeClaim, PersistentVolumeClaimSpec, PersistentVolumeClaimStatus, Pod, PodSpec,
VolumeResourceRequirements,
};
use k8s_openapi::apimachinery::pkg::api::resource::Quantity;
use k8s_openapi::apimachinery::pkg::apis::meta::v1::{ObjectMeta, OwnerReference, Time};
Expand Down Expand Up @@ -103,6 +104,49 @@ pub fn node(name: &str) -> Node {
}
}

pub fn node_prefilled(name: &str) -> Node {
let timestamp: Timestamp = "2026-08-07 15:22:45-04".parse().unwrap();
let mut node = node(name);
node.metadata.creation_timestamp = Some(Time(timestamp));
node.metadata.labels = Some(BTreeMap::from([
(s("kubernetes.io/hostname"), name.to_string()),
(s("kubernetes.io/arch"), s("amd64")),
]));
node.metadata.annotations = Some(BTreeMap::from([
(s("example.com/cool-animal"), s("monkeys")),
(s("checkmk.com/promote-to-host"), s("true")),
]));
node.status = Some(NodeStatus {
addresses: Some(vec![
NodeAddress {
address: s("10.0.0.5"),
type_: s("InternalIP"),
},
NodeAddress {
address: name.to_string(),
type_: s("Hostname"),
},
]),
conditions: Some(vec![NodeCondition {
type_: s("Ready"),
status: s("True"),
message: Some(s("kubelet is posting ready status")),
..Default::default()
}]),
node_info: Some(NodeSystemInfo {
architecture: s("amd64"),
container_runtime_version: s("containerd://1.7.24"),
kernel_version: s("6.8.0-51-generic"),
kubelet_version: s("v1.34.0"),
operating_system: s("linux"),
os_image: s("Ubuntu 22.04.5 LTS"),
..Default::default()
}),
..Default::default()
});
node
}

pub fn owner_ref(kind: &str, name: &str, uid: &str) -> OwnerReference {
OwnerReference {
kind: kind.into(),
Expand Down
Loading