Add a new "licman" command.

This is intended to manage the license management server, once it
is running. It starts with a command "licman add host <ip>" that
adds a data-collector to the available stats hosts pool.
This commit is contained in:
Herbert Wolverson 2023-04-20 17:51:27 +00:00
parent dca44f294a
commit 07dc9c0745
8 changed files with 134 additions and 2 deletions

12
src/rust/Cargo.lock generated
View File

@ -1820,6 +1820,18 @@ dependencies = [
"tokio",
]
[[package]]
name = "licman"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 4.2.1",
"env_logger",
"log",
"pgdb",
"tokio",
]
[[package]]
name = "link-cplusplus"
version = "1.0.8"

View File

@ -30,5 +30,6 @@ members = [
"long_term_stats/license_server", # Licensing Server for LibreQoS Long-term stats
"long_term_stats/lts_node", # Long-term stats cluster node
"long_term_stats/pgdb", # PostgreSQL interface for the LTS system
"long_term_stats/licman", # A CLI tool for managing the licensing server
"lqos_map_perf", # A CLI tool for testing eBPF map performance
]

View File

@ -20,4 +20,4 @@ stop you.
* Setup `/etc/lqdb`.
* Copy `lts_keys.bin` from the license server to the `lts_node` directory.
* Run the process.
* Add the node to the license server.
* Login to the licensing server, and run `licman host add <ip of the new host>`

View File

@ -0,0 +1,12 @@
[package]
name = "licman"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4", features = ["derive"] }
anyhow = "1"
pgdb = { path = "../pgdb" }
tokio = { version = "1", features = [ "rt", "macros", "net", "io-util", "time" ] }
env_logger = "0"
log = "0"

View File

@ -0,0 +1,64 @@
use anyhow::Result;
use clap::{Parser, Subcommand};
use std::process::exit;
#[derive(Parser)]
#[command()]
struct Args {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// Manage stats hosts
Hosts {
#[command(subcommand)]
command: Option<HostsCommands>,
},
}
#[derive(Subcommand)]
enum HostsCommands {
Add { hostname: String },
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
env_logger::init_from_env(
env_logger::Env::default()
.filter_or(env_logger::DEFAULT_FILTER_ENV, "warn"),
);
// Get the database connection pool
let pool = pgdb::get_connection_pool(5).await;
if pool.is_err() {
log::error!("Unable to connect to the database");
log::error!("{pool:?}");
return Err(anyhow::Error::msg("Unable to connect to the database"));
}
let pool = pool.unwrap();
let cli = Args::parse();
match cli.command {
Some(Commands::Hosts {
command: Some(HostsCommands::Add { hostname }),
}) => {
match pgdb::add_stats_host(pool, hostname).await {
Err(e) => {
log::error!("Unable to add stats host: {e:?}");
exit(1);
}
Ok(new_id) => {
println!("Added stats host with id {}", new_id);
}
}
}
Some(Commands::Hosts { command: None }) | None => {
println!("Run with --help to see instructions");
exit(0);
}
}
Ok(())
}

View File

@ -0,0 +1,39 @@
use sqlx::{Pool, Postgres, Row};
use crate::license::StatsHostError;
pub async fn add_stats_host(cnn: Pool<Postgres>, hostname: String) -> Result<i64, StatsHostError> {
// Does the stats host already exist? We don't want duplicates
let row = sqlx::query("SELECT COUNT(*) AS count FROM stats_hosts WHERE ip_address=$1")
.bind(&hostname)
.fetch_one(&cnn)
.await
.map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
let count: i64 = row.try_get("count").map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
if count != 0 {
return Err(StatsHostError::HostAlreadyExists);
}
// Get the new primary key
log::info!("Getting new primary key for stats host");
let row = sqlx::query("SELECT NEXTVAL('stats_hosts_id_seq') AS id")
.fetch_one(&cnn)
.await
.map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
let new_id: i64 = row.try_get("id").map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
// Insert the stats host
log::info!("Inserting new stats host: {} ({})", hostname, new_id);
sqlx::query("INSERT INTO stats_hosts (id, ip_address, can_accept_new_clients) VALUES ($1, $2, $3)")
.bind(new_id)
.bind(&hostname)
.bind(true)
.execute(&cnn)
.await
.map_err(|e| StatsHostError::DatabaseError(e.to_string()))?;
Ok(new_id)
}

View File

@ -1,6 +1,7 @@
mod connection;
mod license;
mod organization;
mod hosts;
pub mod sqlx {
pub use sqlx::*;
@ -8,4 +9,5 @@ pub mod sqlx {
pub use connection::get_connection_pool;
pub use license::{get_stats_host_for_key, insert_or_update_node_public_key, fetch_public_key};
pub use organization::{OrganizationDetails, get_organization};
pub use organization::{OrganizationDetails, get_organization};
pub use hosts::add_stats_host;

View File

@ -72,4 +72,6 @@ pub async fn fetch_public_key(cnn: Pool<Postgres>, license_key: &str, node_id: &
pub enum StatsHostError {
#[error("Database error occurred")]
DatabaseError(String),
#[error("Host already exists")]
HostAlreadyExists,
}