Add new tables, add automatic data cleanup with configurable duration, move company business logic into seperate folder
This commit is contained in:
@@ -9,16 +9,18 @@ CREATE TABLE IF NOT EXISTS antennas (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS client_readings (
|
CREATE TABLE IF NOT EXISTS client_readings (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
ip VARCHAR(15) NOT NULL,
|
|
||||||
|
ip VARCHAR(15),
|
||||||
mac VARCHAR(17) NOT NULL,
|
mac VARCHAR(17) NOT NULL,
|
||||||
antenna_ip VARCHAR(15) NOT NULL,
|
antenna_ip VARCHAR(15) NOT NULL,
|
||||||
db_reading INT NOT NULL,
|
|
||||||
read_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
read_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
db_reading INT NOT NULL,
|
||||||
tx_ccq INT,
|
tx_ccq INT,
|
||||||
rx_ccq INT,
|
rx_ccq INT,
|
||||||
radio_name VARCHAR(255),
|
radio_name VARCHAR(255),
|
||||||
tx_rate VARCHAR(50),
|
tx_rate VARCHAR(50),
|
||||||
rx_rate VARCHAR(50),
|
rx_rate VARCHAR(50),
|
||||||
|
|
||||||
CONSTRAINT fk_client_readings_antenna FOREIGN KEY (antenna_ip) REFERENCES antennas(ip)
|
CONSTRAINT fk_client_readings_antenna FOREIGN KEY (antenna_ip) REFERENCES antennas(ip)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS client_statistics;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS client_statistics (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
|
||||||
|
ip VARCHAR(15),
|
||||||
|
mac VARCHAR(17) NOT NULL,
|
||||||
|
antenna_ip VARCHAR(15) NOT NULL,
|
||||||
|
read_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
db_reading INT NOT NULL,
|
||||||
|
tx_ccq INT,
|
||||||
|
rx_ccq INT,
|
||||||
|
radio_name VARCHAR(255),
|
||||||
|
tx_rate VARCHAR(50),
|
||||||
|
rx_rate VARCHAR(50),
|
||||||
|
|
||||||
|
CONSTRAINT uq_client_statistics_mac UNIQUE (mac),
|
||||||
|
CONSTRAINT fk_client_statistics_antenna FOREIGN KEY (antenna_ip) REFERENCES antennas(ip)
|
||||||
|
);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS wara;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS wara (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
|
||||||
|
last_client_reading_cleanup TIMESTAMP
|
||||||
|
);
|
||||||
261
src/bes_wifi/mod.rs
Normal file
261
src/bes_wifi/mod.rs
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use chrono::{Duration, NaiveDateTime, Utc};
|
||||||
|
use diesel::query_builder::AsQuery;
|
||||||
|
use diesel::query_dsl::methods::{FilterDsl, SelectDsl};
|
||||||
|
use diesel::{ExpressionMethods, OptionalExtension, QueryResult, RunQueryDsl, sql_query};
|
||||||
|
|
||||||
|
use crate::config::{Config, ConfigHost};
|
||||||
|
use crate::db::DbConnection;
|
||||||
|
use crate::db::models::*;
|
||||||
|
|
||||||
|
pub async fn run_bes_wifi_collection(
|
||||||
|
interval: &tokio::time::Duration,
|
||||||
|
cleanup_interval: &tokio::time::Duration,
|
||||||
|
cfg: &Config,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut conn = crate::db::init_db(&cfg.db_url);
|
||||||
|
|
||||||
|
for host in &cfg.hosts {
|
||||||
|
use crate::db::schema::antennas;
|
||||||
|
diesel::insert_into(antennas::table)
|
||||||
|
.values(&NewAntenna {
|
||||||
|
name: host.name.clone(),
|
||||||
|
ip: host.ip.to_string(),
|
||||||
|
error: None,
|
||||||
|
})
|
||||||
|
.on_conflict(diesel::dsl::DuplicatedKeys)
|
||||||
|
.do_update()
|
||||||
|
.set((
|
||||||
|
antennas::name.eq(host.name.clone()),
|
||||||
|
antennas::error.eq(&None as &Option<String>),
|
||||||
|
))
|
||||||
|
.execute(&mut conn)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let v6_hosts = cfg
|
||||||
|
.hosts
|
||||||
|
.iter()
|
||||||
|
.filter(|v| v.tags.contains(&"v6".to_string()))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let v7_hosts = cfg
|
||||||
|
.hosts
|
||||||
|
.iter()
|
||||||
|
.filter(|v| v.tags.contains(&"v7".to_string()))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let v6_cmd = "/interface/wireless/registration-table/print";
|
||||||
|
let v7_cmd = "/interface/wifi/registration-table/print";
|
||||||
|
|
||||||
|
let mut interval = tokio::time::interval(*interval);
|
||||||
|
|
||||||
|
let mut last_cleanup: Option<NaiveDateTime> = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
interval.tick().await;
|
||||||
|
|
||||||
|
{
|
||||||
|
if let Some(last) = last_cleanup {
|
||||||
|
let now = Utc::now().naive_utc();
|
||||||
|
if now - last > chrono::Duration::from_std(cleanup_interval.clone())? {
|
||||||
|
cleanup_old_client_readings(&mut conn)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
use crate::db::schema::wara;
|
||||||
|
|
||||||
|
let row = wara::table.first::<Wara>(&mut conn).optional()?;
|
||||||
|
if let Some(row) = row {
|
||||||
|
last_cleanup = row.last_client_reading_cleanup;
|
||||||
|
} else {
|
||||||
|
diesel::insert_into(wara::table)
|
||||||
|
.values(NewWara {
|
||||||
|
last_client_reading_cleanup: None,
|
||||||
|
})
|
||||||
|
.execute(&mut conn)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (v6_res, v7_res) = futures::join!(
|
||||||
|
crate::mt_commander::MtCommander::run_command_on_hosts(&v6_hosts, v6_cmd),
|
||||||
|
crate::mt_commander::MtCommander::run_command_on_hosts(&v7_hosts, v7_cmd),
|
||||||
|
);
|
||||||
|
let mut records = Vec::new();
|
||||||
|
|
||||||
|
for (antenna, res) in v6_res?.iter().chain(v7_res?.iter()) {
|
||||||
|
match res {
|
||||||
|
Ok(vals) if antenna.tags.contains(&"v7".to_string()) => {
|
||||||
|
for val in vals {
|
||||||
|
records.push(NewClientReadingTimed {
|
||||||
|
ip: Default::default(),
|
||||||
|
mac: get_val_from_cmd_output(antenna, val, "mac-address")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
antenna_ip: antenna.ip.to_string(),
|
||||||
|
db_reading: get_val_from_cmd_output(antenna, val, "signal")
|
||||||
|
.map(|db| parse_int::parse::<i32>(db).expect("Unparseble int"))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
radio_name: Default::default(),
|
||||||
|
rx_ccq: Default::default(),
|
||||||
|
tx_ccq: Default::default(),
|
||||||
|
tx_rate: get_val_from_cmd_output(antenna, val, "tx-rate")
|
||||||
|
.cloned()
|
||||||
|
.ok(),
|
||||||
|
rx_rate: get_val_from_cmd_output(antenna, val, "rx-rate")
|
||||||
|
.cloned()
|
||||||
|
.ok(),
|
||||||
|
read_at: Utc::now().naive_utc(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(vals) if antenna.tags.contains(&"v6".to_string()) => {
|
||||||
|
for val in vals {
|
||||||
|
records.push(NewClientReadingTimed {
|
||||||
|
ip: get_val_from_cmd_output(antenna, val, "last-ip")
|
||||||
|
.cloned()
|
||||||
|
.ok(),
|
||||||
|
mac: get_val_from_cmd_output(antenna, val, "mac-address")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
antenna_ip: antenna.ip.to_string(),
|
||||||
|
db_reading: get_val_from_cmd_output(antenna, val, "signal-strength")
|
||||||
|
.cloned()
|
||||||
|
.map(|mut db| {
|
||||||
|
if db.contains('@') {
|
||||||
|
db = db.split('@').nth(0).unwrap().to_string();
|
||||||
|
}
|
||||||
|
parse_int::parse::<i32>(&db).expect("Unparseble int")
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
radio_name: get_val_from_cmd_output(antenna, val, "radio-name")
|
||||||
|
.cloned()
|
||||||
|
.ok(),
|
||||||
|
rx_ccq: get_val_from_cmd_output(antenna, val, "rx-ccq")
|
||||||
|
.map(|db| parse_int::parse::<i32>(db).expect("Unparseble int"))
|
||||||
|
.ok(),
|
||||||
|
tx_ccq: get_val_from_cmd_output(antenna, val, "tx-ccq")
|
||||||
|
.map(|db| parse_int::parse::<i32>(db).expect("Unparseble int"))
|
||||||
|
.ok(),
|
||||||
|
tx_rate: get_val_from_cmd_output(antenna, val, "tx-rate")
|
||||||
|
.cloned()
|
||||||
|
.ok(),
|
||||||
|
rx_rate: get_val_from_cmd_output(antenna, val, "rx-rate")
|
||||||
|
.cloned()
|
||||||
|
.ok(),
|
||||||
|
read_at: Utc::now().naive_utc(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(_) => unreachable!(),
|
||||||
|
Err(e) => {
|
||||||
|
use crate::db::schema::antennas;
|
||||||
|
diesel::update(antennas::table.filter(antennas::ip.eq(antenna.ip.to_string())))
|
||||||
|
.set(antennas::error.eq(e.to_string()))
|
||||||
|
.execute(&mut conn)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!("inserting: {records:?}");
|
||||||
|
diesel::insert_into(crate::db::schema::client_readings::table)
|
||||||
|
.values(&records)
|
||||||
|
.execute(&mut conn)
|
||||||
|
.unwrap();
|
||||||
|
upsert_client_statistics(&mut conn, &records)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_val_from_cmd_output<'a, T>(
|
||||||
|
antenna: &ConfigHost,
|
||||||
|
command_output: &'a HashMap<String, Option<T>>,
|
||||||
|
key: &str,
|
||||||
|
) -> anyhow::Result<&'a T> {
|
||||||
|
match command_output.get(key) {
|
||||||
|
Some(Some(val)) => Ok(val),
|
||||||
|
Some(None) => {
|
||||||
|
log::warn!(
|
||||||
|
"[{} | {}] Value of '{key}' was none",
|
||||||
|
antenna.ip,
|
||||||
|
antenna.name
|
||||||
|
);
|
||||||
|
anyhow::bail!(
|
||||||
|
"[{} | {}] Value of '{key}' was none",
|
||||||
|
antenna.ip,
|
||||||
|
antenna.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::warn!(
|
||||||
|
"[{} | {}] Unable to find '{key}' in command output",
|
||||||
|
antenna.ip,
|
||||||
|
antenna.name
|
||||||
|
);
|
||||||
|
anyhow::bail!(
|
||||||
|
"[{} | {}] Unable to find '{key}' in command output",
|
||||||
|
antenna.ip,
|
||||||
|
antenna.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upsert_client_statistics(
|
||||||
|
conn: &mut crate::db::DbConnection,
|
||||||
|
readings: &Vec<NewClientReadingTimed>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
use crate::db::schema::client_statistics;
|
||||||
|
for reading in readings.into_iter() {
|
||||||
|
let existing_id = client_statistics::table
|
||||||
|
.filter(client_statistics::mac.eq(&reading.mac))
|
||||||
|
.select(client_statistics::id)
|
||||||
|
.first::<i64>(conn)
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
match existing_id {
|
||||||
|
Some(id) => {
|
||||||
|
diesel::update(client_statistics::table.filter(client_statistics::id.eq(id)))
|
||||||
|
.set((
|
||||||
|
client_statistics::ip.eq(&reading.ip),
|
||||||
|
client_statistics::antenna_ip.eq(&reading.antenna_ip),
|
||||||
|
client_statistics::db_reading.eq(reading.db_reading),
|
||||||
|
client_statistics::read_at.eq(reading.read_at),
|
||||||
|
client_statistics::tx_ccq.eq(reading.tx_ccq),
|
||||||
|
client_statistics::rx_ccq.eq(reading.rx_ccq),
|
||||||
|
client_statistics::radio_name.eq(&reading.radio_name),
|
||||||
|
client_statistics::tx_rate.eq(&reading.tx_rate),
|
||||||
|
client_statistics::rx_rate.eq(&reading.rx_rate),
|
||||||
|
))
|
||||||
|
.execute(conn)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
let val: NewClientStatisticWithTime = reading.clone().into();
|
||||||
|
diesel::insert_into(client_statistics::table)
|
||||||
|
.values(val)
|
||||||
|
.execute(conn)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cleanup_old_client_readings(conn: &mut DbConnection) -> anyhow::Result<()> {
|
||||||
|
use crate::db::schema::{client_readings, wara};
|
||||||
|
|
||||||
|
let cutoff = Utc::now().naive_utc() - Duration::days(7);
|
||||||
|
|
||||||
|
let count = diesel::delete(client_readings::table.filter(client_readings::read_at.lt(cutoff)))
|
||||||
|
.execute(conn)?;
|
||||||
|
log::info!("Deleted {count} old 'client_readings' entries");
|
||||||
|
|
||||||
|
diesel::update(wara::table.filter(wara::id.eq(1)))
|
||||||
|
.set(wara::last_client_reading_cleanup.eq(Some(Utc::now().naive_utc())))
|
||||||
|
.execute(conn)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -94,6 +94,9 @@ pub enum CliCommand {
|
|||||||
BesWifi {
|
BesWifi {
|
||||||
#[arg(long, short = 'i', value_parser = humantime::parse_duration, default_value="5m")]
|
#[arg(long, short = 'i', value_parser = humantime::parse_duration, default_value="5m")]
|
||||||
interval: std::time::Duration,
|
interval: std::time::Duration,
|
||||||
|
|
||||||
|
#[arg(long, short = 'C', value_parser = humantime::parse_duration, default_value="1w")]
|
||||||
|
cleanup_interval: std::time::Duration,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[default]
|
#[default]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use diesel::MysqlConnection as DbConnection;
|
pub use diesel::MysqlConnection as DbConnection;
|
||||||
|
pub use diesel::mysql::Mysql as DbBackend;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
use chrono::NaiveDateTime;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Queryable, Selectable)]
|
|
||||||
#[diesel(table_name = crate::db::schema::antennas)]
|
|
||||||
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
|
||||||
pub struct Antenna {
|
|
||||||
pub id: i64,
|
|
||||||
pub name: String,
|
|
||||||
pub ip: String,
|
|
||||||
pub error: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Insertable)]
|
|
||||||
#[diesel(table_name = crate::db::schema::antennas)]
|
|
||||||
pub struct NewAntenna {
|
|
||||||
pub name: String,
|
|
||||||
pub ip: String,
|
|
||||||
pub error: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Queryable, Selectable)]
|
|
||||||
#[diesel(table_name = crate::db::schema::client_readings)]
|
|
||||||
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
|
||||||
pub struct ClientReading {
|
|
||||||
pub id: i64,
|
|
||||||
pub ip: String,
|
|
||||||
pub mac: String,
|
|
||||||
pub antenna_ip: String,
|
|
||||||
pub db_reading: i32,
|
|
||||||
pub read_at: NaiveDateTime,
|
|
||||||
pub tx_ccq: i32,
|
|
||||||
pub rx_ccq: i32,
|
|
||||||
pub radio_name: String,
|
|
||||||
pub tx_rate: String,
|
|
||||||
pub rx_rate: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Insertable)]
|
|
||||||
#[diesel(table_name = crate::db::schema::client_readings)]
|
|
||||||
pub struct NewClientReading {
|
|
||||||
pub ip: String,
|
|
||||||
pub mac: String,
|
|
||||||
pub antenna_ip: String,
|
|
||||||
pub db_reading: i32,
|
|
||||||
pub tx_ccq: i32,
|
|
||||||
pub rx_ccq: i32,
|
|
||||||
pub radio_name: String,
|
|
||||||
pub tx_rate: String,
|
|
||||||
pub rx_rate: String,
|
|
||||||
}
|
|
||||||
20
src/db/models/antenna.rs
Normal file
20
src/db/models/antenna.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Queryable, Selectable)]
|
||||||
|
#[diesel(table_name = crate::db::schema::antennas)]
|
||||||
|
#[diesel(check_for_backend(crate::db::DbBackend))]
|
||||||
|
pub struct Antenna {
|
||||||
|
pub id: i64,
|
||||||
|
pub name: String,
|
||||||
|
pub ip: String,
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Insertable)]
|
||||||
|
#[diesel(table_name = crate::db::schema::antennas)]
|
||||||
|
pub struct NewAntenna {
|
||||||
|
pub name: String,
|
||||||
|
pub ip: String,
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
83
src/db/models/client_readings.rs
Normal file
83
src/db/models/client_readings.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
use crate::db::models::NewClientStatisticWithTime;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Queryable, Selectable)]
|
||||||
|
#[diesel(table_name = crate::db::schema::client_readings)]
|
||||||
|
#[diesel(check_for_backend(crate::db::DbBackend))]
|
||||||
|
pub struct ClientReading {
|
||||||
|
pub id: i64,
|
||||||
|
pub ip: Option<String>,
|
||||||
|
pub mac: String,
|
||||||
|
pub antenna_ip: String,
|
||||||
|
pub db_reading: i32,
|
||||||
|
pub read_at: NaiveDateTime,
|
||||||
|
pub tx_ccq: Option<i32>,
|
||||||
|
pub rx_ccq: Option<i32>,
|
||||||
|
pub radio_name: Option<String>,
|
||||||
|
pub tx_rate: Option<String>,
|
||||||
|
pub rx_rate: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Insertable)]
|
||||||
|
#[diesel(table_name = crate::db::schema::client_readings)]
|
||||||
|
pub struct NewClientReading {
|
||||||
|
pub ip: Option<String>,
|
||||||
|
pub mac: String,
|
||||||
|
pub antenna_ip: String,
|
||||||
|
pub db_reading: i32,
|
||||||
|
pub tx_ccq: Option<i32>,
|
||||||
|
pub rx_ccq: Option<i32>,
|
||||||
|
pub radio_name: Option<String>,
|
||||||
|
pub tx_rate: Option<String>,
|
||||||
|
pub rx_rate: Option<String>,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Insertable)]
|
||||||
|
#[diesel(table_name = crate::db::schema::client_readings)]
|
||||||
|
pub struct NewClientReadingTimed {
|
||||||
|
pub ip: Option<String>,
|
||||||
|
pub mac: String,
|
||||||
|
pub antenna_ip: String,
|
||||||
|
pub db_reading: i32,
|
||||||
|
pub read_at: NaiveDateTime,
|
||||||
|
pub tx_ccq: Option<i32>,
|
||||||
|
pub rx_ccq: Option<i32>,
|
||||||
|
pub radio_name: Option<String>,
|
||||||
|
pub tx_rate: Option<String>,
|
||||||
|
pub rx_rate: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<NewClientReading> for NewClientReadingTimed {
|
||||||
|
fn into(self) -> NewClientReading {
|
||||||
|
NewClientReading {
|
||||||
|
ip: self.ip,
|
||||||
|
mac: self.mac,
|
||||||
|
antenna_ip: self.antenna_ip,
|
||||||
|
db_reading: self.db_reading,
|
||||||
|
tx_ccq: self.tx_ccq,
|
||||||
|
rx_ccq: self.rx_ccq,
|
||||||
|
radio_name: self.radio_name,
|
||||||
|
tx_rate: self.tx_rate,
|
||||||
|
rx_rate: self.rx_rate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<NewClientStatisticWithTime> for NewClientReadingTimed {
|
||||||
|
fn into(self) -> NewClientStatisticWithTime {
|
||||||
|
NewClientStatisticWithTime {
|
||||||
|
ip: self.ip,
|
||||||
|
mac: self.mac,
|
||||||
|
antenna_ip: self.antenna_ip,
|
||||||
|
db_reading: self.db_reading,
|
||||||
|
read_at: self.read_at,
|
||||||
|
tx_ccq: self.tx_ccq,
|
||||||
|
rx_ccq: self.rx_ccq,
|
||||||
|
radio_name: self.radio_name,
|
||||||
|
tx_rate: self.tx_rate,
|
||||||
|
rx_rate: self.rx_rate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/db/models/client_statistics.rs
Normal file
49
src/db/models/client_statistics.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Queryable, Selectable)]
|
||||||
|
#[diesel(table_name = crate::db::schema::client_statistics)]
|
||||||
|
#[diesel(check_for_backend(crate::db::DbBackend))]
|
||||||
|
pub struct ClientStatistics {
|
||||||
|
pub id: i64,
|
||||||
|
pub ip: Option<String>,
|
||||||
|
pub mac: String,
|
||||||
|
pub antenna_ip: String,
|
||||||
|
pub db_reading: i32,
|
||||||
|
pub read_at: NaiveDateTime,
|
||||||
|
pub tx_ccq: Option<i32>,
|
||||||
|
pub rx_ccq: Option<i32>,
|
||||||
|
pub radio_name: Option<String>,
|
||||||
|
pub tx_rate: Option<String>,
|
||||||
|
pub rx_rate: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Insertable)]
|
||||||
|
#[diesel(table_name = crate::db::schema::client_statistics)]
|
||||||
|
pub struct NewClientStatistic {
|
||||||
|
pub ip: Option<String>,
|
||||||
|
pub mac: String,
|
||||||
|
pub antenna_ip: String,
|
||||||
|
pub db_reading: i32,
|
||||||
|
pub tx_ccq: Option<i32>,
|
||||||
|
pub rx_ccq: Option<i32>,
|
||||||
|
pub radio_name: Option<String>,
|
||||||
|
pub tx_rate: Option<String>,
|
||||||
|
pub rx_rate: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Insertable)]
|
||||||
|
#[diesel(table_name = crate::db::schema::client_statistics)]
|
||||||
|
pub struct NewClientStatisticWithTime {
|
||||||
|
pub ip: Option<String>,
|
||||||
|
pub mac: String,
|
||||||
|
pub antenna_ip: String,
|
||||||
|
pub db_reading: i32,
|
||||||
|
pub read_at: NaiveDateTime,
|
||||||
|
pub tx_ccq: Option<i32>,
|
||||||
|
pub rx_ccq: Option<i32>,
|
||||||
|
pub radio_name: Option<String>,
|
||||||
|
pub tx_rate: Option<String>,
|
||||||
|
pub rx_rate: Option<String>,
|
||||||
|
}
|
||||||
9
src/db/models/mod.rs
Normal file
9
src/db/models/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
mod antenna;
|
||||||
|
mod client_readings;
|
||||||
|
mod client_statistics;
|
||||||
|
mod wara;
|
||||||
|
|
||||||
|
pub use antenna::*;
|
||||||
|
pub use client_readings::*;
|
||||||
|
pub use client_statistics::*;
|
||||||
|
pub use wara::*;
|
||||||
18
src/db/models/wara.rs
Normal file
18
src/db/models/wara.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Queryable, Selectable)]
|
||||||
|
#[diesel(table_name = crate::db::schema::wara)]
|
||||||
|
#[diesel(check_for_backend(crate::db::DbBackend))]
|
||||||
|
pub struct Wara {
|
||||||
|
pub id: i64,
|
||||||
|
|
||||||
|
pub last_client_reading_cleanup: Option<NaiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Insertable)]
|
||||||
|
#[diesel(table_name = crate::db::schema::wara)]
|
||||||
|
pub struct NewWara {
|
||||||
|
pub last_client_reading_cleanup: Option<NaiveDateTime>,
|
||||||
|
}
|
||||||
@@ -1,28 +1,65 @@
|
|||||||
// Written manually because diesel cant fucking read the db properly for some reason
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
antennas (id) {
|
antennas (id) {
|
||||||
id -> Int8,
|
id -> Bigint,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
ip -> Text,
|
#[max_length = 15]
|
||||||
|
ip -> Varchar,
|
||||||
error -> Nullable<Text>,
|
error -> Nullable<Text>,
|
||||||
|
last_error_t -> Nullable<Timestamp>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
client_readings (id) {
|
client_readings (id) {
|
||||||
id -> Int8,
|
id -> Bigint,
|
||||||
ip -> Text,
|
#[max_length = 15]
|
||||||
mac -> Text,
|
ip -> Nullable<Varchar>,
|
||||||
antenna_ip -> Text,
|
#[max_length = 17]
|
||||||
db_reading -> Int4,
|
mac -> Varchar,
|
||||||
|
#[max_length = 15]
|
||||||
|
antenna_ip -> Varchar,
|
||||||
read_at -> Timestamp,
|
read_at -> Timestamp,
|
||||||
tx_ccq -> Int4,
|
db_reading -> Integer,
|
||||||
rx_ccq -> Int4,
|
tx_ccq -> Nullable<Integer>,
|
||||||
radio_name -> Varchar,
|
rx_ccq -> Nullable<Integer>,
|
||||||
tx_rate -> Varchar,
|
#[max_length = 255]
|
||||||
rx_rate -> Varchar,
|
radio_name -> Nullable<Varchar>,
|
||||||
|
#[max_length = 50]
|
||||||
|
tx_rate -> Nullable<Varchar>,
|
||||||
|
#[max_length = 50]
|
||||||
|
rx_rate -> Nullable<Varchar>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::allow_tables_to_appear_in_same_query!(antennas, client_readings,);
|
diesel::table! {
|
||||||
|
client_statistics (id) {
|
||||||
|
id -> Bigint,
|
||||||
|
#[max_length = 15]
|
||||||
|
ip -> Nullable<Varchar>,
|
||||||
|
#[max_length = 17]
|
||||||
|
mac -> Varchar,
|
||||||
|
#[max_length = 15]
|
||||||
|
antenna_ip -> Varchar,
|
||||||
|
read_at -> Timestamp,
|
||||||
|
db_reading -> Integer,
|
||||||
|
tx_ccq -> Nullable<Integer>,
|
||||||
|
rx_ccq -> Nullable<Integer>,
|
||||||
|
#[max_length = 255]
|
||||||
|
radio_name -> Nullable<Varchar>,
|
||||||
|
#[max_length = 50]
|
||||||
|
tx_rate -> Nullable<Varchar>,
|
||||||
|
#[max_length = 50]
|
||||||
|
rx_rate -> Nullable<Varchar>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
wara (id) {
|
||||||
|
id -> Bigint,
|
||||||
|
last_client_reading_cleanup -> Nullable<Timestamp>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::allow_tables_to_appear_in_same_query!(antennas, client_readings, client_statistics, wara,);
|
||||||
|
|||||||
193
src/main.rs
193
src/main.rs
@@ -13,22 +13,16 @@ use std::{
|
|||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use diesel::{ExpressionMethods, RunQueryDsl, query_dsl::methods::FilterDsl};
|
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::time::{self, Duration};
|
use tokio::time::Duration;
|
||||||
|
|
||||||
use crate::{
|
use crate::config::{
|
||||||
config::{
|
Config, ConfigHost,
|
||||||
Config, ConfigHost,
|
cli::{CliCommand, CliDeviceCommand, OutputFormat},
|
||||||
cli::{CliCommand, CliDeviceCommand, OutputFormat},
|
|
||||||
},
|
|
||||||
db::{
|
|
||||||
models::{NewAntenna, NewClientReading},
|
|
||||||
schema::antennas,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod bes_wifi;
|
||||||
mod config;
|
mod config;
|
||||||
mod db;
|
mod db;
|
||||||
mod mt_commander;
|
mod mt_commander;
|
||||||
@@ -55,179 +49,10 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
mt_commander::MtCommander::init(cfg.clone())?;
|
mt_commander::MtCommander::init(cfg.clone())?;
|
||||||
|
|
||||||
match &cfg.cli.command {
|
match &cfg.cli.command {
|
||||||
CliCommand::BesWifi { interval } => {
|
CliCommand::BesWifi {
|
||||||
let mut conn = db::init_db(&cfg.db_url);
|
interval,
|
||||||
|
cleanup_interval,
|
||||||
for host in &cfg.hosts {
|
} => bes_wifi::run_bes_wifi_collection(interval, cleanup_interval, &cfg).await?,
|
||||||
use crate::db::schema::antennas;
|
|
||||||
diesel::insert_into(antennas::table)
|
|
||||||
.values(&NewAntenna {
|
|
||||||
name: host.name.clone(),
|
|
||||||
ip: host.ip.to_string(),
|
|
||||||
error: None,
|
|
||||||
})
|
|
||||||
.on_conflict(diesel::dsl::DuplicatedKeys)
|
|
||||||
.do_update()
|
|
||||||
.set((
|
|
||||||
antennas::name.eq(host.name.clone()),
|
|
||||||
antennas::error.eq(&None as &Option<String>),
|
|
||||||
))
|
|
||||||
.execute(&mut conn)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let v6_hosts = cfg
|
|
||||||
.hosts
|
|
||||||
.iter()
|
|
||||||
.filter(|v| v.tags.contains(&"v6".to_string()))
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let v7_hosts = cfg
|
|
||||||
.hosts
|
|
||||||
.iter()
|
|
||||||
.filter(|v| v.tags.contains(&"v7".to_string()))
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let v6_cmd = "/interface/wireless/registration-table/print";
|
|
||||||
let v7_cmd = "/interface/wifi/registration-table/print";
|
|
||||||
|
|
||||||
let mut interval = time::interval(*interval);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
interval.tick().await;
|
|
||||||
|
|
||||||
let (v6_res, v7_res) = futures::join!(
|
|
||||||
mt_commander::MtCommander::run_command_on_hosts(&v6_hosts, v6_cmd),
|
|
||||||
mt_commander::MtCommander::run_command_on_hosts(&v7_hosts, v7_cmd),
|
|
||||||
);
|
|
||||||
let mut records = Vec::new();
|
|
||||||
|
|
||||||
fn get_val_from_cmd_output<'a, T>(
|
|
||||||
antenna: &ConfigHost,
|
|
||||||
command_output: &'a HashMap<String, Option<T>>,
|
|
||||||
key: &str,
|
|
||||||
) -> anyhow::Result<&'a T> {
|
|
||||||
match command_output.get(key) {
|
|
||||||
Some(Some(val)) => Ok(val),
|
|
||||||
Some(None) => {
|
|
||||||
log::warn!(
|
|
||||||
"[{} | {}] Value of '{key}' was none",
|
|
||||||
antenna.ip,
|
|
||||||
antenna.name
|
|
||||||
);
|
|
||||||
anyhow::bail!(
|
|
||||||
"[{} | {}] Value of '{key}' was none",
|
|
||||||
antenna.ip,
|
|
||||||
antenna.name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
log::warn!(
|
|
||||||
"[{} | {}] Unable to find '{key}' in command output",
|
|
||||||
antenna.ip,
|
|
||||||
antenna.name
|
|
||||||
);
|
|
||||||
anyhow::bail!(
|
|
||||||
"[{} | {}] Unable to find '{key}' in command output",
|
|
||||||
antenna.ip,
|
|
||||||
antenna.name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (antenna, res) in v6_res?.iter().chain(v7_res?.iter()) {
|
|
||||||
match res {
|
|
||||||
Ok(vals) if antenna.tags.contains(&"v7".to_string()) => {
|
|
||||||
for val in vals {
|
|
||||||
records.push(NewClientReading {
|
|
||||||
ip: Default::default(),
|
|
||||||
mac: get_val_from_cmd_output(antenna, val, "mac-address")
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
antenna_ip: antenna.ip.to_string(),
|
|
||||||
db_reading: get_val_from_cmd_output(antenna, val, "signal")
|
|
||||||
.map(|db| {
|
|
||||||
parse_int::parse::<i32>(db).expect("Unparseble int")
|
|
||||||
})
|
|
||||||
.unwrap_or_default(),
|
|
||||||
radio_name: Default::default(),
|
|
||||||
rx_ccq: Default::default(),
|
|
||||||
tx_ccq: Default::default(),
|
|
||||||
tx_rate: get_val_from_cmd_output(antenna, val, "tx-rate")
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
rx_rate: get_val_from_cmd_output(antenna, val, "rx-rate")
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(vals) if antenna.tags.contains(&"v6".to_string()) => {
|
|
||||||
for val in vals {
|
|
||||||
records.push(NewClientReading {
|
|
||||||
ip: get_val_from_cmd_output(antenna, val, "last-ip")
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
mac: get_val_from_cmd_output(antenna, val, "mac-address")
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
antenna_ip: antenna.ip.to_string(),
|
|
||||||
db_reading: get_val_from_cmd_output(
|
|
||||||
antenna,
|
|
||||||
val,
|
|
||||||
"signal-strength",
|
|
||||||
)
|
|
||||||
.cloned()
|
|
||||||
.map(|mut db| {
|
|
||||||
if db.contains('@') {
|
|
||||||
db = db.split('@').nth(0).unwrap().to_string();
|
|
||||||
}
|
|
||||||
parse_int::parse::<i32>(&db).expect("Unparseble int")
|
|
||||||
})
|
|
||||||
.unwrap_or_default(),
|
|
||||||
radio_name: get_val_from_cmd_output(antenna, val, "radio-name")
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
rx_ccq: get_val_from_cmd_output(antenna, val, "rx-ccq")
|
|
||||||
.map(|db| {
|
|
||||||
parse_int::parse::<i32>(db).expect("Unparseble int")
|
|
||||||
})
|
|
||||||
.unwrap_or_default(),
|
|
||||||
tx_ccq: get_val_from_cmd_output(antenna, val, "tx-ccq")
|
|
||||||
.map(|db| {
|
|
||||||
parse_int::parse::<i32>(db).expect("Unparseble int")
|
|
||||||
})
|
|
||||||
.unwrap_or_default(),
|
|
||||||
tx_rate: get_val_from_cmd_output(antenna, val, "tx-rate")
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
rx_rate: get_val_from_cmd_output(antenna, val, "rx-rate")
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(_) => unreachable!(),
|
|
||||||
Err(e) => {
|
|
||||||
diesel::update(
|
|
||||||
antennas::table.filter(antennas::ip.eq(antenna.ip.to_string())),
|
|
||||||
)
|
|
||||||
.set(antennas::error.eq(e.to_string()))
|
|
||||||
.execute(&mut conn)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::debug!("inserting: {records:?}");
|
|
||||||
diesel::insert_into(crate::db::schema::client_readings::table)
|
|
||||||
.values(&records)
|
|
||||||
.execute(&mut conn)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CliCommand::Run {
|
CliCommand::Run {
|
||||||
all,
|
all,
|
||||||
devices,
|
devices,
|
||||||
|
|||||||
Reference in New Issue
Block a user