wip(generic-snmp)

This commit is contained in:
David Boucher 2025-04-29 14:36:18 +02:00
parent 6020f9973e
commit 9ff58afa42
6 changed files with 157 additions and 39 deletions

View File

@ -6,12 +6,13 @@ version = "1.0.0"
lalrpop = "0.22.1" lalrpop = "0.22.1"
[dependencies] [dependencies]
regex = "1.11.1" env_logger = "0.11.8"
rasn = "0.26.2" lalrpop-util = { version = "0.22.1", features = ["lexer"] }
rasn-snmp = "0.26.2"
rasn-smi = "0.26.2"
log = "0.4.27"
lexopt = "0.3.1" lexopt = "0.3.1"
log = "0.4.27"
rasn = "0.26.2"
rasn-smi = "0.26.2"
rasn-snmp = "0.26.2"
regex = "1.11.1"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140" serde_json = "1.0.140"
lalrpop-util = { version = "0.22.1", features = ["lexer"] }

View File

@ -1,3 +1,4 @@
use log::{debug, info, trace};
use snmp::SnmpResult; use snmp::SnmpResult;
use std::str; use std::str;
@ -232,20 +233,21 @@ impl<'input> Expr<'input> {
Expr::Number(n) => ExprResult::Scalar(*n), Expr::Number(n) => ExprResult::Scalar(*n),
Expr::Id(key) => { Expr::Id(key) => {
let k = str::from_utf8(key).unwrap(); let k = str::from_utf8(key).unwrap();
println!("Evaluation of Id '{}'", k);
for result in collect { for result in collect {
let item = &result.items[k]; match result.items.get(k) {
match item { Some(item) => match item {
ExprResult::Vector(n) => { ExprResult::Vector(n) => {
if n.len() == 1 { if n.len() == 1 {
println!("value {}", n[0]); info!("ID '{}' has value {}", k, n[0]);
return ExprResult::Scalar(n[0]); return ExprResult::Scalar(n[0]);
} else { } else {
println!("value {:?}", n); info!("ID '{}' has value {:?}", k, n);
return ExprResult::Vector(n.clone()); return ExprResult::Vector(n.clone());
} }
} }
_ => panic!("Should be a number"), _ => panic!("Should be a number"),
},
None => continue,
} }
} }
ExprResult::Scalar(0.0) ExprResult::Scalar(0.0)

View File

@ -1,4 +1,4 @@
use log::debug; use log::{debug, error, trace};
use std::str; use std::str;
pub type Spanned<Tok, Loc, Error> = Result<(Loc, Tok, Loc), Error>; pub type Spanned<Tok, Loc, Error> = Result<(Loc, Tok, Loc), Error>;
@ -54,7 +54,7 @@ impl<'input> Lexer<'input> {
end = chars.len(); end = chars.len();
} }
debug!( trace!(
"Token Number from {} to {} with value '{}'", "Token Number from {} to {} with value '{}'",
start, start,
end, end,
@ -83,7 +83,7 @@ impl<'input> Lexer<'input> {
if !done { if !done {
end = chars.len(); end = chars.len();
} }
debug!( trace!(
"Token Identifier from {} to {} with value '{}'", "Token Identifier from {} to {} with value '{}'",
start, start,
end, end,
@ -119,12 +119,12 @@ impl<'input> Iterator for Lexer<'input> {
} }
b'(' => { b'(' => {
self.offset = i + 1; self.offset = i + 1;
debug!("Token LParen at {}", i); trace!("Token LParen at {}", i);
return Some(Ok((i, Tok::LParen, i + 1))); return Some(Ok((i, Tok::LParen, i + 1)));
} }
b')' => { b')' => {
self.offset = i + 1; self.offset = i + 1;
debug!("Token RParen at {}", i); trace!("Token RParen at {}", i);
return Some(Ok((i, Tok::RParen, i + 1))); return Some(Ok((i, Tok::RParen, i + 1)));
} }
b'{' => { b'{' => {
@ -144,7 +144,7 @@ impl<'input> Iterator for Lexer<'input> {
} }
_ => { _ => {
// Unknown character // Unknown character
debug!("Unknown character at {}: '{}'", i, *c as char); error!("Unknown character at {}: '{}'", i, *c as char);
self.offset = i + 1; self.offset = i + 1;
return Some(Err(LexicalError::NotPossible)); return Some(Err(LexicalError::NotPossible));
} }
@ -158,8 +158,13 @@ impl<'input> Iterator for Lexer<'input> {
mod Test { mod Test {
use super::*; use super::*;
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[test] #[test]
fn test_lexer_num_id_num() { fn test_lexer_num_id_num() {
init();
let input = "123 abc 456"; let input = "123 abc 456";
let mut lexer = Lexer::new(input); let mut lexer = Lexer::new(input);
assert_eq!(lexer.next(), Some(Ok((0, Tok::Num(123_f64), 3)))); assert_eq!(lexer.next(), Some(Ok((0, Tok::Num(123_f64), 3))));
@ -170,6 +175,7 @@ mod Test {
#[test] #[test]
fn test_lexer_id_num_id() { fn test_lexer_id_num_id() {
init();
let input = "abc 123 def"; let input = "abc 123 def";
let mut lexer = Lexer::new(input); let mut lexer = Lexer::new(input);
assert_eq!(lexer.next(), Some(Ok((0, Tok::Id(b"abc"), 3)))); assert_eq!(lexer.next(), Some(Ok((0, Tok::Id(b"abc"), 3))));
@ -179,6 +185,7 @@ mod Test {
#[test] #[test]
fn test_lexer_num_op() { fn test_lexer_num_op() {
init();
let input = "1+2*3"; let input = "1+2*3";
let mut lexer = Lexer::new(input); let mut lexer = Lexer::new(input);
assert_eq!(lexer.next(), Some(Ok((0, Tok::Num(1_f64), 1)))); assert_eq!(lexer.next(), Some(Ok((0, Tok::Num(1_f64), 1))));

View File

@ -4,6 +4,8 @@ pub mod lexer;
use self::ast::ExprResult; use self::ast::ExprResult;
use self::lexer::{LexicalError, Tok}; use self::lexer::{LexicalError, Tok};
use lalrpop_util::{lalrpop_mod, ParseError}; use lalrpop_util::{lalrpop_mod, ParseError};
use log::{trace, debug};
use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
use snmp::SnmpResult; use snmp::SnmpResult;
@ -44,6 +46,7 @@ impl<'a> Parser<'a> {
&self, &self,
expr: &'a str, expr: &'a str,
) -> Result<ExprResult, ParseError<usize, Tok<'a>, LexicalError>> { ) -> Result<ExprResult, ParseError<usize, Tok<'a>, LexicalError>> {
debug!("Parsing expression: {}", expr);
let lexer = lexer::Lexer::new(expr); let lexer = lexer::Lexer::new(expr);
let res = self.parser.parse(lexer); let res = self.parser.parse(lexer);
match res { match res {
@ -54,14 +57,68 @@ impl<'a> Parser<'a> {
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
pub fn eval_str(
&self,
expr: &'a str,
) -> Result<ExprResult, ParseError<usize, Tok<'a>, LexicalError>> {
let re = Regex::new(r"\{[a-zA-Z_][a-zA-Z0-9_.]*\}").unwrap();
let mut suffix = expr;
let mut result: ExprResult = ExprResult::StrVector(vec![]);
loop {
let found = re.find(suffix);
if let Some(m) = found {
let start = m.start();
let end = m.end();
debug!(
"Identifier '{}' found in expr '{}'",
&expr[start + 1..end - 1],
expr
);
let prefix = &expr[0..start];
suffix = &expr[end..];
let mut result = vec![];
for snmp_result in self.collect {
if let Some(v) = snmp_result.items.get(&expr[start + 1..end - 1]) {
result = join_str_expr(prefix, v);
break;
}
}
trace!("Result string {:?}", result);
} else {
break;
}
}
Ok(result)
}
}
fn join_str_expr(prefix: &str, v: &ExprResult) -> Vec<String> {
match v {
ExprResult::StrVector(v) => {
let mut result = vec![];
for item in v {
result.push(format!("{}{}", prefix, item));
}
result
}
_ => panic!("Expected a string vector"),
}
} }
mod Test { mod Test {
use super::*; use super::*;
use log::info;
use std::collections::HashMap; use std::collections::HashMap;
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[test] #[test]
fn term() { fn term() {
init();
info!("test term");
let lexer = lexer::Lexer::new("123"); let lexer = lexer::Lexer::new("123");
let res = grammar::ExprParser::new().parse(lexer); let res = grammar::ExprParser::new().parse(lexer);
assert!(res.is_ok()); assert!(res.is_ok());
@ -80,6 +137,7 @@ mod Test {
#[test] #[test]
fn sum() { fn sum() {
init();
let lexer = lexer::Lexer::new("1 + 2"); let lexer = lexer::Lexer::new("1 + 2");
let res = grammar::ExprParser::new().parse(lexer); let res = grammar::ExprParser::new().parse(lexer);
assert!(res.is_ok()); assert!(res.is_ok());
@ -129,6 +187,7 @@ mod Test {
#[test] #[test]
fn product() { fn product() {
init();
let lexer = lexer::Lexer::new("2 * 3"); let lexer = lexer::Lexer::new("2 * 3");
let res = grammar::ExprParser::new().parse(lexer); let res = grammar::ExprParser::new().parse(lexer);
assert!(res.is_ok()); assert!(res.is_ok());
@ -180,6 +239,7 @@ mod Test {
#[test] #[test]
fn sum_product() { fn sum_product() {
init();
let lexer = lexer::Lexer::new("1 + (3 + 2 * 3) / 3"); let lexer = lexer::Lexer::new("1 + (3 + 2 * 3) / 3");
let res = grammar::ExprParser::new().parse(lexer); let res = grammar::ExprParser::new().parse(lexer);
assert!(res.is_ok()); assert!(res.is_ok());
@ -193,6 +253,7 @@ mod Test {
#[test] #[test]
fn identifier() { fn identifier() {
init();
let lexer = lexer::Lexer::new("{abc} + 1"); let lexer = lexer::Lexer::new("{abc} + 1");
let res = grammar::ExprParser::new().parse(lexer); let res = grammar::ExprParser::new().parse(lexer);
assert!(res.is_ok()); assert!(res.is_ok());
@ -212,6 +273,7 @@ mod Test {
#[test] #[test]
fn two_identifiers() { fn two_identifiers() {
init();
let lexer = lexer::Lexer::new("100 * (1 - {free}/{total})"); let lexer = lexer::Lexer::new("100 * (1 - {free}/{total})");
let res = grammar::ExprParser::new().parse(lexer); let res = grammar::ExprParser::new().parse(lexer);
assert!(res.is_ok()); assert!(res.is_ok());
@ -229,6 +291,7 @@ mod Test {
#[test] #[test]
fn function() { fn function() {
init();
let lexer = lexer::Lexer::new("Average({abc})"); let lexer = lexer::Lexer::new("Average({abc})");
let res = grammar::ExprParser::new().parse(lexer); let res = grammar::ExprParser::new().parse(lexer);
assert!(res.is_ok()); assert!(res.is_ok());
@ -243,4 +306,34 @@ mod Test {
_ => panic!("Expected a scalar value"), _ => panic!("Expected a scalar value"),
} }
} }
#[test]
fn identifier_str() {
init();
let items = HashMap::from([
(
"free".to_string(),
ExprResult::StrVector(vec!["free-one".to_string(), "free-two".to_string()]),
),
(
"total".to_string(),
ExprResult::StrVector(vec!["total-one".to_string(), "total-two".to_string()]),
),
]);
let collect = vec![SnmpResult::new(items)];
let parser = Parser::new(&collect);
let res = parser.eval_str("{free}foo{total}bar");
assert!(res.is_ok());
let res = res.unwrap();
match res {
ExprResult::StrVector(v) => {
assert_eq!(v.len(), 4);
assert_eq!(v[0], "free-onefoo");
assert_eq!(v[1], "free-twofoo");
assert_eq!(v[2], "total-onebar");
assert_eq!(v[3], "total-twobar");
}
_ => panic!("Expected a string vector"),
}
}
} }

View File

@ -2,9 +2,12 @@ extern crate serde;
extern crate serde_json; extern crate serde_json;
use compute::{ast::ExprResult, Compute, Parser}; use compute::{ast::ExprResult, Compute, Parser};
use log::{debug, trace};
use serde::Deserialize; use serde::Deserialize;
use snmp::{snmp_bulk_get, snmp_bulk_walk, snmp_bulk_walk_with_labels}; use snmp::{snmp_bulk_get, snmp_bulk_walk, snmp_bulk_walk_with_labels};
use std::collections::HashMap; use std::{collections::HashMap, ops::IndexMut};
use crate::snmp::SnmpResult;
#[derive(Debug)] #[derive(Debug)]
struct Perfdata { struct Perfdata {
@ -142,22 +145,23 @@ impl Command {
let r = snmp_bulk_get(target, version, community, 1, 1, &to_get, &get_name); let r = snmp_bulk_get(target, version, community, 1, 1, &to_get, &get_name);
collect.push(r); collect.push(r);
} }
println!("{:#?}", collect);
let mut idx: u32 = 0; let mut idx: u32 = 0;
let mut metrics = vec![]; let mut metrics = vec![];
let mut my_res = SnmpResult::new(HashMap::new());
for metric in self.compute.metrics.iter() { for metric in self.compute.metrics.iter() {
let value = &metric.value; let value = &metric.value;
let min = metric.min; let min = metric.min;
let max = metric.max; let max = metric.max;
let parser = Parser::new(&collect); let parser = Parser::new(&collect);
let value = parser.eval(value).unwrap(); let value = parser.eval(value).unwrap();
println!("ExprResult: {:?}", value);
match value { match &value {
ExprResult::Vector(v) => { ExprResult::Vector(v) => {
for item in v { for item in v {
let name = match &metric.prefix { let name = match &metric.prefix {
Some(prefix) => { Some(prefix) => {
let name_resolved = parser.eval_str(prefix).unwrap();
format!("{:?}#{}", prefix, metric.name) format!("{:?}#{}", prefix, metric.name)
} }
None => { None => {
@ -168,10 +172,11 @@ impl Command {
}; };
let m = Perfdata { let m = Perfdata {
name, name,
value: item, value: *item,
min, min,
max, max,
}; };
trace!("New metric '{}' with value {:?}", m.name, m.value);
metrics.push(m); metrics.push(m);
} }
} }
@ -188,16 +193,20 @@ impl Command {
}; };
let m = Perfdata { let m = Perfdata {
name, name,
value: s, value: *s,
min, min,
max, max,
}; };
trace!("New metric '{}' with value {:?}", m.name, m.value);
metrics.push(m); metrics.push(m);
} }
_ => panic!("Aggregation must be applied to a vector"), _ => panic!("Aggregation must be applied to a vector"),
} }
println!("perfdata: {:?}", metrics); let key = format!("metrics.{}", metric.name);
debug!("New ID '{}' with content: {:?}", key, value);
my_res.items.insert(key, value);
} }
collect.push(my_res);
if let Some(aggregations) = self.compute.aggregations.as_ref() { if let Some(aggregations) = self.compute.aggregations.as_ref() {
for metric in aggregations { for metric in aggregations {
let value = &metric.value; let value = &metric.value;
@ -252,6 +261,7 @@ impl Command {
min, min,
max, max,
}; };
trace!("New metric '{}' with value {:?}", m.name, m.value);
metrics.push(m); metrics.push(m);
} }
} }
@ -263,14 +273,16 @@ impl Command {
min, min,
max, max,
}; };
trace!("New metric '{}' with value {:?}", m.name, m.value);
metrics.push(m); metrics.push(m);
} }
_ => panic!("Aggregation must be applied to a vector"), _ => panic!("Aggregation must be applied to a vector"),
} }
println!("perfdata: {:?}", metrics);
} }
} }
trace!("collect: {:#?}", collect);
println!("metrics: {:#?}", metrics);
CmdResult { CmdResult {
status: Status::Unknown, status: Status::Unknown,
output: "No result".to_string(), output: "No result".to_string(),

View File

@ -1,3 +1,4 @@
extern crate env_logger;
extern crate lalrpop_util; extern crate lalrpop_util;
extern crate lexopt; extern crate lexopt;
extern crate log; extern crate log;
@ -14,8 +15,8 @@ mod snmp;
use generic::Command; use generic::Command;
use lalrpop_util::lalrpop_mod; use lalrpop_util::lalrpop_mod;
use log::{debug, info, trace};
use serde_json::Result; use serde_json::Result;
//use snmp::snmp_get;
use std::fs; use std::fs;
lalrpop_mod!(grammar); lalrpop_mod!(grammar);
@ -67,6 +68,8 @@ fn json_to_command(file_name: &str) -> Result<Command> {
} }
fn main() { fn main() {
env_logger::init();
use lexopt::prelude::*; use lexopt::prelude::*;
let mut parser = lexopt::Parser::from_env(); let mut parser = lexopt::Parser::from_env();
let mut hostname = "localhost".to_string(); let mut hostname = "localhost".to_string();
@ -78,30 +81,30 @@ fn main() {
let arg = parser.next(); let arg = parser.next();
match arg { match arg {
Ok(arg) => { Ok(arg) => {
println!("{:?} ok", arg);
match arg { match arg {
Some(arg) => match arg { Some(arg) => match arg {
Short('H') | Long("hostname") => { Short('H') | Long("hostname") => {
hostname = parser.value().unwrap().into_string().unwrap(); hostname = parser.value().unwrap().into_string().unwrap();
trace!("hostname: {:}", hostname);
} }
Short('p') | Long("port") => { Short('p') | Long("port") => {
port = parser.value().unwrap().parse::<u16>().unwrap(); port = parser.value().unwrap().parse::<u16>().unwrap();
println!("port: {}", port); trace!("port: {}", port);
} }
Short('j') | Long("json") => { Short('j') | Long("json") => {
json = Some(parser.value().unwrap().into_string().unwrap()); json = Some(parser.value().unwrap().into_string().unwrap());
println!("json: {:?}", json); trace!("json: {:?}", json);
} }
Short('v') | Long("snmp-version") => { Short('v') | Long("snmp-version") => {
snmp_version = parser.value().unwrap().into_string().unwrap(); snmp_version = parser.value().unwrap().into_string().unwrap();
println!("snmp_version: {}", snmp_version); trace!("snmp_version: {}", snmp_version);
} }
Short('c') | Long("snmp-community") => { Short('c') | Long("snmp-community") => {
snmp_community = parser.value().unwrap().into_string().unwrap(); snmp_community = parser.value().unwrap().into_string().unwrap();
println!("snmp_community: {}", snmp_community); trace!("snmp_community: {}", snmp_community);
} }
_ => { _ => {
println!("other"); debug!("other unknown argument");
} }
}, },
None => { None => {