Add more iLO data
All checks were successful
Build and push container image / build (push) Successful in 16m26s

This commit is contained in:
toast 2024-07-31 12:21:26 +10:00
parent 081ff781de
commit 56f5fd2bf9
4 changed files with 432 additions and 221 deletions

445
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "kon" name = "kon"
version = "0.3.6" version = "0.3.7"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View File

@ -66,50 +66,137 @@ struct Status {
state: String state: String
} }
#[derive(Serialize, Deserialize, Debug)]
struct Power {
#[serde(rename = "PowerCapacityWatts")]
power_capacity_watts: i32,
#[serde(rename = "PowerConsumedWatts")]
power_consumed_watts: i32,
#[serde(rename = "PowerMetrics")]
power_metrics: PowerMetrics
}
#[derive(Serialize, Deserialize, Debug)]
struct PowerMetrics {
#[serde(rename = "AverageConsumedWatts")]
average_consumed_watts: i32,
#[serde(rename = "MaxConsumedWatts")]
max_consumed_watts: i32,
#[serde(rename = "MinConsumedWatts")]
min_consumed_watts: i32
}
#[derive(Serialize, Deserialize)]
struct System {
#[serde(rename = "Memory")]
memory: Memory,
#[serde(rename = "Model")]
model: String,
#[serde(rename = "Oem")]
oem: Oem,
#[serde(rename = "PowerState")]
power_state: String,
#[serde(rename = "ProcessorSummary")]
processor_summary: ProcessorSummary
}
#[derive(Serialize, Deserialize)]
struct Memory {
#[serde(rename = "TotalSystemMemoryGB")]
total_system_memory: i32
}
#[derive(Serialize, Deserialize)]
struct ProcessorSummary {
#[serde(rename = "Count")]
count: i32,
#[serde(rename = "Model")]
cpu: String
}
#[derive(Serialize, Deserialize)]
struct Oem {
#[serde(rename = "Hp")]
hp: Hp
}
#[derive(Serialize, Deserialize)]
struct Hp {
#[serde(rename = "PostState")]
post_state: String
}
const ILO_HOSTNAME: &str = "POMNI"; const ILO_HOSTNAME: &str = "POMNI";
async fn ilo_data() -> Result<Chassis, ReqError> { enum RedfishEndpoint {
Thermal,
Power,
System
}
impl RedfishEndpoint {
fn url(&self) -> String {
match self {
RedfishEndpoint::Thermal => "Chassis/1/Thermal".to_string(),
RedfishEndpoint::Power => "Chassis/1/Power".to_string(),
RedfishEndpoint::System => "Systems/1".to_string()
}
}
}
async fn ilo_data(endpoint: RedfishEndpoint) -> Result<Box<dyn std::any::Any + Send>, ReqError> {
let client = ClientBuilder::new() let client = ClientBuilder::new()
.danger_accept_invalid_certs(true) .danger_accept_invalid_certs(true)
.build() .build()
.unwrap(); .unwrap();
let res = client let res = client
.get(format!("https://{}/redfish/v1/chassis/1/thermal", token_path().await.ilo_ip)) .get(format!("https://{}/redfish/v1/{}", token_path().await.ilo_ip, endpoint.url()))
.basic_auth(token_path().await.ilo_user, Some(token_path().await.ilo_pw)) .basic_auth(token_path().await.ilo_user, Some(token_path().await.ilo_pw))
.send() .send()
.await .await
.unwrap(); .unwrap();
let body = res.json().await.unwrap(); match endpoint {
Ok(body) RedfishEndpoint::Thermal => {
let body: Chassis = res.json().await.unwrap();
Ok(Box::new(body))
}
RedfishEndpoint::Power => {
let body: Power = res.json().await.unwrap();
Ok(Box::new(body))
}
RedfishEndpoint::System => {
let body: System = res.json().await.unwrap();
Ok(Box::new(body))
}
}
} }
/// Retrieve data from the HP iLO4 interface /// Retrieve data from the HP iLO4 interface
#[poise::command( #[poise::command(
slash_command, slash_command,
subcommands("temperature") subcommands("temperature", "power", "system")
)] )]
pub async fn ilo(_: poise::Context<'_, (), Error>) -> Result<(), Error> { pub async fn ilo(_: poise::Context<'_, (), Error>) -> Result<(), Error> {
Ok(()) Ok(())
} }
/// Retrieve data from the HP iLO4 interface /// Retrieve the server's temperature data
#[poise::command(slash_command)] #[poise::command(slash_command)]
pub async fn temperature(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> { pub async fn temperature(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
ctx.defer().await?; ctx.defer().await?;
let data = ilo_data().await.unwrap(); let ilo = ilo_data(RedfishEndpoint::Thermal).await.unwrap();
let data = ilo.downcast_ref::<Chassis>().unwrap();
let mut tempdata = String::new(); let mut tempdata = String::new();
let mut fandata = String::new(); let mut fandata = String::new();
let allowed_sensors = [ let allowed_sensors = [
"01-Inlet Ambient", "01-Inlet Ambient",
"04-P1 DIMM 1-6", "04-P1 DIMM 1-6",
"13-Chipset",
"14-Chipset Zone" "14-Chipset Zone"
]; ];
for temp in data.temperatures { for temp in &data.temperatures {
if temp.reading_celsius == 0 || !allowed_sensors.contains(&temp.name.as_str()) { if temp.reading_celsius == 0 || !allowed_sensors.contains(&temp.name.as_str()) {
continue; continue;
} }
@ -117,14 +204,13 @@ pub async fn temperature(ctx: poise::Context<'_, (), Error>) -> Result<(), Error
let name = match temp.name.as_str() { let name = match temp.name.as_str() {
"01-Inlet Ambient" => "Inlet Ambient", "01-Inlet Ambient" => "Inlet Ambient",
"04-P1 DIMM 1-6" => "P1 DIMM 1-6", "04-P1 DIMM 1-6" => "P1 DIMM 1-6",
"13-Chipset" => "Chipset",
"14-Chipset Zone" => "Chipset Zone", "14-Chipset Zone" => "Chipset Zone",
_ => "Unknown Sensor" _ => "Unknown Sensor"
}; };
tempdata.push_str(&format!("**{}:** `{}°C`\n", name, temp.reading_celsius)); tempdata.push_str(&format!("**{}:** `{}°C`\n", name, temp.reading_celsius));
} }
for fan in data.fans { for fan in &data.fans {
if fan.current_reading == 0 { if fan.current_reading == 0 {
continue; continue;
} }
@ -136,7 +222,7 @@ pub async fn temperature(ctx: poise::Context<'_, (), Error>) -> Result<(), Error
CreateEmbed::new() CreateEmbed::new()
.color(BINARY_PROPERTIES.embed_color) .color(BINARY_PROPERTIES.embed_color)
.timestamp(Timestamp::now()) .timestamp(Timestamp::now())
.title(format!("{} - HP iLO4 Temperatures", ILO_HOSTNAME)) .title(format!("{} - Temperatures", ILO_HOSTNAME))
.fields(vec![ .fields(vec![
("Temperatures", tempdata, false), ("Temperatures", tempdata, false),
("Fans", fandata, false) ("Fans", fandata, false)
@ -145,3 +231,65 @@ pub async fn temperature(ctx: poise::Context<'_, (), Error>) -> Result<(), Error
Ok(()) Ok(())
} }
/// Retrieve the server's power data
#[poise::command(slash_command)]
pub async fn power(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
ctx.defer().await?;
let ilo = ilo_data(RedfishEndpoint::Power).await.unwrap();
let data = ilo.downcast_ref::<Power>().unwrap();
let mut powerdata = String::new();
powerdata.push_str(&format!("**Power Capacity:** `{}w`\n", &data.power_capacity_watts));
powerdata.push_str(&format!("**Power Consumed:** `{}w`\n", &data.power_consumed_watts));
powerdata.push_str(&format!("**Average Power:** `{}w`\n", &data.power_metrics.average_consumed_watts));
powerdata.push_str(&format!("**Max Consumed:** `{}w`\n", &data.power_metrics.max_consumed_watts));
powerdata.push_str(&format!("**Min Consumed:** `{}w`", &data.power_metrics.min_consumed_watts));
ctx.send(CreateReply::default().embed(
CreateEmbed::new()
.color(BINARY_PROPERTIES.embed_color)
.timestamp(Timestamp::now())
.title(format!("{} - Power", ILO_HOSTNAME))
.description(powerdata)
)).await?;
Ok(())
}
/// Retrieve the server's system data
#[poise::command(slash_command)]
pub async fn system(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
ctx.defer().await?;
let ilo = ilo_data(RedfishEndpoint::System).await.unwrap();
let data = ilo.downcast_ref::<System>().unwrap();
let mut bios_data = String::new();
let post_state = match data.oem.hp.post_state.as_str() {
"FinishedPost" => "Finished POST",
_ => "???"
};
if data.oem.hp.post_state != "FinishedPost" {
println!("iLO:PostState = {}", data.oem.hp.post_state);
}
bios_data.push_str(&format!("**POST:** `{}`\n", post_state));
bios_data.push_str(&format!("**Power:** `{}`\n", &data.power_state));
bios_data.push_str(&format!("**Model:** `{}`", &data.model));
ctx.send(CreateReply::default().embed(
CreateEmbed::new()
.color(BINARY_PROPERTIES.embed_color)
.timestamp(Timestamp::now())
.title(format!("{} - System", ILO_HOSTNAME))
.description(bios_data)
.fields(vec![
(format!("CPU ({}x)", data.processor_summary.count), data.processor_summary.cpu.trim().to_string(), true),
("RAM".to_string(), format!("{} GB", data.memory.total_system_memory), true)
])
)).await?;
Ok(())
}

View File

@ -82,12 +82,40 @@ async fn on_ready(
async fn event_processor( async fn event_processor(
ctx: &Context, ctx: &Context,
event: &FullEvent, event: &FullEvent,
_framework: poise::FrameworkContext<'_, (), Error> framework: poise::FrameworkContext<'_, (), Error>
) -> Result<(), Error> { ) -> Result<(), Error> {
match event { match event {
FullEvent::Ratelimit { data } => { FullEvent::Ratelimit { data } => {
println!("Event[Ratelimit]: {:#?}", data); println!("Event[Ratelimit]: {:#?}", data);
} }
FullEvent::Message { new_message } => {
if new_message.author.bot || !new_message.guild_id.is_none() {
return Ok(());
}
if new_message.content.to_lowercase().starts_with("deploy") && new_message.author.id == BINARY_PROPERTIES.developers[0] {
let builder = poise::builtins::create_application_commands(&framework.options().commands);
let commands = Command::set_global_commands(&ctx.http, builder).await;
let mut commands_deployed = std::collections::HashSet::new();
match commands {
Ok(cmdmap) => for command in cmdmap.iter() {
commands_deployed.insert(command.name.clone());
},
Err(y) => {
eprintln!("Error registering commands: {:?}", y);
new_message.reply(&ctx.http, "Deployment failed, check console for more details!").await?;
}
}
if commands_deployed.len() > 0 {
new_message.reply(&ctx.http, format!(
"Deployed the commands globally:\n- {}",
commands_deployed.into_iter().collect::<Vec<_>>().join("\n- ")
)).await?;
}
}
}
FullEvent::Ready { .. } => { FullEvent::Ready { .. } => {
let thread_id = format!("{:?}", current().id()); let thread_id = format!("{:?}", current().id());
let thread_num: String = thread_id.chars().filter(|c| c.is_digit(10)).collect(); let thread_num: String = thread_id.chars().filter(|c| c.is_digit(10)).collect();
@ -162,6 +190,8 @@ async fn main() {
let mut client = ClientBuilder::new( let mut client = ClientBuilder::new(
token_path().await.main, token_path().await.main,
GatewayIntents::GUILDS GatewayIntents::GUILDS
| GatewayIntents::MESSAGE_CONTENT
| GatewayIntents::DIRECT_MESSAGES
) )
.framework(framework) .framework(framework)
.await.expect("Error creating client"); .await.expect("Error creating client");