mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
Merge with develop to resolve update conflicts and preserve building both the support tool and this branch (both modified build scripts on the same lines)
This commit is contained in:
commit
c9f9c51e7e
@ -22,7 +22,7 @@ ETC_DIR=$DPKG_DIR/etc
|
||||
MOTD_DIR=$DPKG_DIR/etc/update-motd.d
|
||||
LQOS_FILES="graphInfluxDB.py influxDBdashboardTemplate.json integrationCommon.py integrationRestHttp.py integrationSplynx.py integrationUISP.py integrationSonar.py ispConfig.example.py LibreQoS.py lqos.example lqTools.py mikrotikFindIPv6.py network.example.json pythonCheck.py README.md scheduler.py ShapedDevices.example.csv"
|
||||
LQOS_BIN_FILES="lqos_scheduler.service.example lqosd.service.example lqos_node_manager.service.example"
|
||||
RUSTPROGS="lqosd lqtop xdp_iphash_to_cpu_cmdline xdp_pping lqos_node_manager lqusers lqos_setup lqos_map_perf uisp_integration"
|
||||
RUSTPROGS="lqosd lqtop xdp_iphash_to_cpu_cmdline xdp_pping lqos_node_manager lqusers lqos_setup lqos_map_perf uisp_integration lqos_support_tool"
|
||||
|
||||
####################################################
|
||||
# Clean any previous dist build
|
||||
|
@ -52,7 +52,7 @@ rustup update
|
||||
|
||||
# Start building
|
||||
echo "Please wait while the system is compiled. Service will not be interrupted during this stage."
|
||||
PROGS="lqosd lqtop xdp_iphash_to_cpu_cmdline xdp_pping lqos_node_manager lqusers lqos_map_perf uisp_integration"
|
||||
PROGS="lqosd lqtop xdp_iphash_to_cpu_cmdline xdp_pping lqos_node_manager lqusers lqos_map_perf uisp_integration lqos_support_tool"
|
||||
mkdir -p bin/static
|
||||
pushd rust > /dev/null
|
||||
#cargo clean
|
||||
|
58
src/rust/Cargo.lock
generated
58
src/rust/Cargo.lock
generated
@ -131,9 +131,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.83"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
@ -437,6 +437,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.9.1"
|
||||
@ -513,9 +519,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.4"
|
||||
version = "4.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@ -523,9 +529,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.2"
|
||||
version = "4.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@ -535,9 +541,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.4"
|
||||
version = "4.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
|
||||
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@ -1675,9 +1681,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.154"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@ -1820,6 +1826,7 @@ dependencies = [
|
||||
"jemallocator",
|
||||
"lqos_bus",
|
||||
"lqos_config",
|
||||
"lqos_support_tool",
|
||||
"lqos_utils",
|
||||
"nix 0.28.0",
|
||||
"once_cell",
|
||||
@ -1880,6 +1887,23 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lqos_support_tool"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"colored",
|
||||
"libc",
|
||||
"log",
|
||||
"lqos_config",
|
||||
"miniz_oxide",
|
||||
"nix 0.29.0",
|
||||
"serde",
|
||||
"serde_cbor",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lqos_sys"
|
||||
version = "0.1.0"
|
||||
@ -2185,7 +2209,19 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"cfg_aliases 0.1.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.2.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
@ -32,4 +32,5 @@ members = [
|
||||
"lqos_map_perf", # A CLI tool for testing eBPF map performance
|
||||
"uisp", # REST support for the UISP API
|
||||
"uisp_integration", # UISP Integration in Rust
|
||||
"lqos_support_tool", # A Helper tool to make it easier to request/receive support
|
||||
]
|
||||
|
@ -22,6 +22,7 @@ once_cell = "1"
|
||||
dns-lookup = "1"
|
||||
dashmap = "5"
|
||||
reqwest = { version = "0.11.20", features = ["json"] }
|
||||
lqos_support_tool = { path = "../lqos_support_tool" }
|
||||
|
||||
# Support JemAlloc on supported platforms
|
||||
[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies]
|
||||
|
@ -13,6 +13,7 @@ mod network_tree;
|
||||
mod queue_info;
|
||||
mod toasts;
|
||||
mod flow_monitor;
|
||||
mod support;
|
||||
|
||||
// Use JemAllocator only on supported platforms
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
@ -45,6 +46,7 @@ fn rocket() -> _ {
|
||||
static_pages::unknown_devices_page,
|
||||
static_pages::circuit_queue,
|
||||
static_pages::pretty_map_graph,
|
||||
static_pages::help_page,
|
||||
config_control::config_page,
|
||||
network_tree::tree_page,
|
||||
static_pages::ip_dump,
|
||||
@ -124,6 +126,10 @@ fn rocket() -> _ {
|
||||
flow_monitor::flows_lat_lon,
|
||||
flow_monitor::flows_ether_protocol,
|
||||
flow_monitor::flows_ip_protocol,
|
||||
// Suport System
|
||||
support::run_sanity_check,
|
||||
support::gather_support_data,
|
||||
support::submit_support_data,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -83,6 +83,14 @@ pub async fn pretty_map_graph<'a>(
|
||||
NoCache::new(NamedFile::open("static/showoff.html").await.ok())
|
||||
}
|
||||
|
||||
// Help me obi-wan, you're our only hope
|
||||
#[get("/help")]
|
||||
pub async fn help_page<'a>(
|
||||
_auth: AuthGuard,
|
||||
) -> NoCache<Option<NamedFile>> {
|
||||
NoCache::new(NamedFile::open("static/help.html").await.ok())
|
||||
}
|
||||
|
||||
#[get("/vendor/bootstrap.min.css")]
|
||||
pub async fn bootsrap_css<'a>() -> LongCache<Option<NamedFile>> {
|
||||
LongCache::new(NamedFile::open("static/vendor/bootstrap.min.css").await.ok())
|
||||
|
62
src/rust/lqos_node_manager/src/support.rs
Normal file
62
src/rust/lqos_node_manager/src/support.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use rocket::fs::NamedFile;
|
||||
use rocket::serde::Deserialize;
|
||||
use rocket::serde::json::Json;
|
||||
use lqos_config::load_config;
|
||||
use lqos_support_tool::{run_sanity_checks, SanityChecks};
|
||||
use crate::auth_guard::AuthGuard;
|
||||
|
||||
#[get("/api/sanity")]
|
||||
pub async fn run_sanity_check(
|
||||
_auth: AuthGuard,
|
||||
) -> Json<SanityChecks> {
|
||||
let mut status = run_sanity_checks().unwrap();
|
||||
status.results.sort_by(|a,b| a.success.cmp(&b.success));
|
||||
Json(status)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct SupportMetadata {
|
||||
name: String,
|
||||
comment: String,
|
||||
}
|
||||
|
||||
#[post("/api/gatherSupport", data="<info>")]
|
||||
pub async fn gather_support_data(
|
||||
info: Json<SupportMetadata>
|
||||
) -> NamedFile {
|
||||
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||
let filename = format!("/tmp/libreqos_{}.support", timestamp);
|
||||
let path = Path::new(&filename);
|
||||
|
||||
let lts_key = if let Ok(cfg) = load_config() {
|
||||
cfg.long_term_stats.license_key.unwrap_or("None".to_string())
|
||||
} else {
|
||||
"None".to_string()
|
||||
};
|
||||
if let Ok(dump) = lqos_support_tool::gather_all_support_info(&info.name, &info.comment, <s_key) {
|
||||
std::fs::write(&path, dump.serialize_and_compress().unwrap()).unwrap();
|
||||
}
|
||||
|
||||
NamedFile::open(path).await.unwrap()
|
||||
}
|
||||
|
||||
#[post("/api/submitSupport", data="<info>")]
|
||||
pub async fn submit_support_data(
|
||||
info: Json<SupportMetadata>
|
||||
) -> String {
|
||||
let lts_key = if let Ok(cfg) = load_config() {
|
||||
cfg.long_term_stats.license_key.unwrap_or("None".to_string())
|
||||
} else {
|
||||
"None".to_string()
|
||||
};
|
||||
if let Ok(dump) = lqos_support_tool::gather_all_support_info(&info.name, &info.comment, <s_key) {
|
||||
lqos_support_tool::submit_to_network(dump);
|
||||
"Your support submission has been sent".to_string()
|
||||
} else {
|
||||
"Something went wrong".to_string()
|
||||
}
|
||||
|
||||
}
|
@ -50,6 +50,9 @@
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/help"><i class="fa fa-question-circle"></i> Help</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-link btn btn-small black-txt" href="#" id="btnReload"><i class="fa fa-refresh"></i>
|
||||
Reload LibreQoS</a>
|
||||
|
322
src/rust/lqos_node_manager/static/help.html
Normal file
322
src/rust/lqos_node_manager/static/help.html
Normal file
@ -0,0 +1,322 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="/vendor/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/vendor/solid.min.css">
|
||||
<link rel="stylesheet" href="/lqos.css">
|
||||
<link rel="icon" href="/favicon.png">
|
||||
<title>LibreQoS - Local Node Manager</title>
|
||||
<script src="/lqos.js"></script>
|
||||
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
||||
<script src="/vendor/jquery.min.js"></script><script src="/vendor/msgpack.min.js"></script>
|
||||
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-secondary">
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/"><img src="/vendor/tinylogo.svg" alt="LibreQoS SVG Logo" width="25"
|
||||
height="25" /> LibreQoS</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
|
||||
aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-tree"></i> Tree</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span
|
||||
id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/unknown"><i class="fa fa-address-card"></i> Unknown IPs <span
|
||||
id="unshapedCount" class="badge badge-warning orange-badge">?</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item" id="currentLogin"></li>
|
||||
<li class="nav-item" id="statsLink"></li>
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/help"><i class="fa fa-question-circle"></i> Help</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-link btn btn-small" href="#" id="btnReload"><i class="fa fa-refresh"></i> Reload
|
||||
LibreQoS</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="container" class="pad4">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fa fa-question-circle"></i> Help & Support</h5>
|
||||
|
||||
<p class="alert-warning">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
Priority support is given to Long-Term Stats subscribers and donors. Other support is
|
||||
best effort, with volunteers trying to help as best we can.
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://github.com/sponsors/LibreQoE/" class="btn btn-primary">
|
||||
<i class="fa fa-money"></i> Support LibreQoS Today
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fa fa-book"></i> Documentation</h5>
|
||||
<p>The documentation is a great place to start!</p>
|
||||
<a href="https://libreqos.readthedocs.io/en/latest/" class="btn btn-primary"><i class="fa fa-book"></i> Read The LibreQoS Documentation</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fa fa-user-circle"></i> Chat</h5>
|
||||
<p>
|
||||
Connect with other LibreQoS users, and the LibreQoS team on our Zulip Chat System. <br />
|
||||
<a class="btn btn-primary" href="https://chat.libreqos.io/"><i class="fa fa-user-circle"></i> Zulip chat system</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 10px;">
|
||||
<div class="col-4">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fa fa-wrench"></i> Tools</h5>
|
||||
<p style="border: 1px solid #eee">
|
||||
<strong>Sanity check</strong> reads your configuration and looks for common issues. This should be
|
||||
your first step when troubleshooting the system.<br />
|
||||
<button id="btnSanity" class="btn btn-primary" onclick="sanity()"><i class="fa fa-info"></i> Sanity Check Your Installation</button>
|
||||
</p>
|
||||
<p style="border: 1px solid #eee">
|
||||
<strong>Download</strong> support data to a local file. You can send this to LibreQoS via the
|
||||
chat or email (as part of an ongoing support discussion).<br />
|
||||
<button id="btnDownload" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#downloadDump"><i class="fa fa-download"></i> Download Support Dump</button>
|
||||
</p>
|
||||
<p style="border: 1px solid #eee">
|
||||
<strong>Submit</strong> support data directly to LibreQoS. Please contact us when you do this,
|
||||
with detailed information about the problems you are experiencing. Repeatedly hitting this
|
||||
button will get you slower - or no - service!<br />
|
||||
<button id="btnSubmit" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#submitDump"><i class="fa fa-send"></i> Send Support Dump</button>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<footer>© 2022-2023, LibreQoE LLC</footer>
|
||||
|
||||
<div class="modal" tabindex="-1" id="sanityModal">
|
||||
<div class="modal-dialog modal-fullscreen">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Configuration Check Results</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" style="overflow: auto;">
|
||||
<p id="configCheck"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" tabindex="-1" id="downloadDump">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Information To Add to the Support Dump</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" style="overflow: auto;">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label for="gatherName" class="form-label">Your Name</label>
|
||||
<input type="text" id="gatherName" class="form-control" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="gatherEmail" class="form-label">Your Email Address</label>
|
||||
<input type="text" id="gatherEmail" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label for="gatherComments" class="form-label">Comments</label>
|
||||
<input type="text" id="gatherComments" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" onclick="gather()"><i class="fa fa-download"></i> Generate and Download</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" tabindex="-1" id="submitDump">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Information To Add to the Support Dump</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" style="overflow: auto;">
|
||||
<p class="alert-warning"><i class="fa fa-warning"></i> Your support dump will contain unredated data about your customers, and lots
|
||||
of information about your server. By submitting, you are acknowledging that LibreQoS bear no liability for this data.
|
||||
LibreQoS will take all reasonable measures to protect your data, and will not share it.</p>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label for="submitName" class="form-label">Your Name</label>
|
||||
<input type="text" id="submitName" class="form-control" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="submitEmail" class="form-label">Your Email Address</label>
|
||||
<input type="text" id="submitEmail" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label for="submitComments" class="form-label">Comments</label>
|
||||
<input type="text" id="submitComments" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" onclick="submit()"><i class="fa fa-send"></i> Submit Support Data to LibreQoS</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function start() {
|
||||
setTitle();
|
||||
colorReloadButton();
|
||||
updateHostCounts();
|
||||
}
|
||||
|
||||
function gather() {
|
||||
let details = {
|
||||
name: $("#gatherName").val() + ", " + $("#gatherEmail").val(),
|
||||
comment: $("#gatherComments").val()
|
||||
};
|
||||
console.log(details);
|
||||
|
||||
if (details.name === ", " || details.name.indexOf("@")<1) {
|
||||
alert("Please enter a name and email address. If you share this, it'll be handy to know who sent it!");
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/gatherSupport",
|
||||
data: JSON.stringify(details),
|
||||
xhrFields: {
|
||||
responseType: 'blob' // to avoid binary data being mangled on charset conversion
|
||||
},
|
||||
success: function(blob, result, xhr) {
|
||||
var filename = "libreqos.support";
|
||||
|
||||
// use HTML5 a[download] attribute to specify filename
|
||||
var downloadUrl = URL.createObjectURL(blob);
|
||||
var a = document.createElement("a");
|
||||
// safari doesn't support this yet
|
||||
if (typeof a.download === 'undefined') {
|
||||
window.location.href = downloadUrl;
|
||||
} else {
|
||||
a.href = downloadUrl;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function submit() {
|
||||
let details = {
|
||||
name: $("#submitName").val() + ", " + $("#submitEmail").val(),
|
||||
comment: $("#submitComments").val()
|
||||
};
|
||||
console.log(details);
|
||||
|
||||
if (details.name === ", " || details.name.indexOf("@")<1) {
|
||||
alert("Please enter a name and email address. If you share this, it'll be handy to know who sent it!");
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/submitSupport",
|
||||
data: JSON.stringify(details),
|
||||
success: function(result) {
|
||||
console.log(result);
|
||||
alert(result);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function sanity() {
|
||||
$.get("/api/sanity", (data) => {
|
||||
console.log(data);
|
||||
let html = "<table class='table'><thead><th>Check</th><th>Success?</th><th>Comment</th></thead><tbody>";
|
||||
for (let i=0; i<data.results.length; i++) {
|
||||
let row = data.results[i];
|
||||
html += "<tr>";
|
||||
html += "<td>" + row.name + "</td>";
|
||||
if (row.success) {
|
||||
html += "<td style='color: green'><i class='fa fa-check'></i>";
|
||||
} else {
|
||||
html += "<td style='color: red'><i class='fa fa-warning'></i>";
|
||||
}
|
||||
html += "<td>" + row.comments + "</td>";
|
||||
html += "</tr>";
|
||||
}
|
||||
html += "</tbody>";
|
||||
$("#configCheck").html(html);
|
||||
|
||||
// Show the modal
|
||||
const myModal = new bootstrap.Modal(document.getElementById('sanityModal'), { focus: true });
|
||||
myModal.show();
|
||||
})
|
||||
}
|
||||
|
||||
$(document).ready(start);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -41,6 +41,9 @@
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/help"><i class="fa fa-question-circle"></i> Help</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-link btn btn-small black-txt" href="#" id="btnReload"><i class="fa fa-refresh"></i> Reload LibreQoS</a>
|
||||
</li>
|
||||
|
@ -48,6 +48,9 @@
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/help"><i class="fa fa-question-circle"></i> Help</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-link btn btn-small" href="#" id="btnReload"><i class="fa fa-refresh"></i> Reload
|
||||
LibreQoS</a>
|
||||
|
@ -41,6 +41,9 @@
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/help"><i class="fa fa-question-circle"></i> Help</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-link btn btn-small black-txt" href="#" id="btnReload"><i class="fa fa-refresh"></i> Reload LibreQoS</a>
|
||||
</li>
|
||||
|
@ -48,6 +48,9 @@
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/help"><i class="fa fa-question-circle"></i> Help</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-link btn btn-small" href="#" id="btnReload"><i class="fa fa-refresh"></i> Reload
|
||||
LibreQoS</a>
|
||||
|
@ -40,6 +40,9 @@
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="/help"><i class="fa fa-question-circle"></i> Help</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-link btn btn-small black-txt" href="#" id="btnReload"><i class="fa fa-refresh"></i> Reload LibreQoS</a>
|
||||
</li>
|
||||
|
17
src/rust/lqos_support_tool/Cargo.toml
Normal file
17
src/rust/lqos_support_tool/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "lqos_support_tool"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.86"
|
||||
clap = { version = "4.5.7", features = ["derive"] }
|
||||
colored = "2.1.0"
|
||||
lqos_config = { path = "../lqos_config" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_cbor = "0" # For RFC8949/7409 format C binary objects
|
||||
miniz_oxide = "0.7.1"
|
||||
log = "0.4.21" # For compression
|
||||
libc = "0.2.155"
|
||||
nix = "0.29.0"
|
||||
serde_json = "1.0.117"
|
13
src/rust/lqos_support_tool/src/console.rs
Normal file
13
src/rust/lqos_support_tool/src/console.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use colored::Colorize;
|
||||
|
||||
pub fn success(s: &str) {
|
||||
println!("{} - {s}", "OK".bright_green());
|
||||
}
|
||||
|
||||
pub fn warn(s: &str) {
|
||||
println!("{} - {s}", "WARN".bright_yellow());
|
||||
}
|
||||
|
||||
pub fn error(s: &str) {
|
||||
println!("{} - {s}", "ERROR".bright_red());
|
||||
}
|
36
src/rust/lqos_support_tool/src/lib.rs
Normal file
36
src/rust/lqos_support_tool/src/lib.rs
Normal file
@ -0,0 +1,36 @@
|
||||
//! Provides a support library for the support tool system.
|
||||
mod support_info;
|
||||
pub mod console;
|
||||
mod sanity_checks;
|
||||
|
||||
use std::io::Write;
|
||||
use std::net::TcpStream;
|
||||
pub use support_info::gather_all_support_info;
|
||||
pub use support_info::SupportDump;
|
||||
pub use sanity_checks::run_sanity_checks;
|
||||
use crate::console::{error, success};
|
||||
pub use sanity_checks::SanityChecks;
|
||||
|
||||
const REMOTE_SYSTEM: &str = "stats.libreqos.io:9200";
|
||||
|
||||
pub fn submit_to_network(dump: SupportDump) {
|
||||
// Build the payload
|
||||
let serialized = dump.serialize_and_compress().unwrap();
|
||||
let length = serialized.len() as u64;
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend(1212u32.to_be_bytes());
|
||||
bytes.extend(length.to_be_bytes());
|
||||
bytes.extend(&serialized);
|
||||
bytes.extend(1212u32.to_be_bytes());
|
||||
|
||||
// Do the actual connection
|
||||
let stream = TcpStream::connect(REMOTE_SYSTEM);
|
||||
if stream.is_err() {
|
||||
error(&format!("Unable to connect to {REMOTE_SYSTEM}"));
|
||||
println!("{stream:?}");
|
||||
return;
|
||||
}
|
||||
let mut stream = stream.unwrap();
|
||||
stream.write_all(&bytes).unwrap();
|
||||
success("Submitted to LibreQoS for Analysis. Thank you.");
|
||||
}
|
189
src/rust/lqos_support_tool/src/main.rs
Normal file
189
src/rust/lqos_support_tool/src/main.rs
Normal file
@ -0,0 +1,189 @@
|
||||
//! Provides the start of a text-mode support tool for LibreQoS. It will double as
|
||||
//! a library (see `lib.rs`) to provide similar functionality from the GUI.
|
||||
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use clap::{Parser, Subcommand};
|
||||
use colored::Colorize;
|
||||
use lqos_config::load_config;
|
||||
use lqos_support_tool::{gather_all_support_info, run_sanity_checks, SupportDump};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version = "1.0", about = "LibreQoS Support Tool", long_about = None, arg_required_else_help = true)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Sanity Checks your Configuration against your hardware
|
||||
Sanity,
|
||||
/// Gather Support Info and Save it to /tmp
|
||||
Gather,
|
||||
/// Gather Support Info and Send it to the LibreQoS Team. Note that LTS users and donors get priority, we don't guarantee that we'll help anyone else. Please make sure you've tried Zulip first ( https://chat.libreqos.io/ )
|
||||
Submit,
|
||||
/// Summarize the contents of a support dump
|
||||
Summarize {
|
||||
/// The filename to read
|
||||
filename: String
|
||||
},
|
||||
/// Expand all submitted data from a support dump into a given directory
|
||||
Expand {
|
||||
/// The filename to read from
|
||||
filename: String,
|
||||
/// The target directory
|
||||
target: String,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_line() -> String {
|
||||
use std::io::{stdin,stdout,Write};
|
||||
let mut s = String::new();
|
||||
stdin().read_line(&mut s).expect("Did not enter a correct string");
|
||||
s.trim().to_string()
|
||||
}
|
||||
|
||||
fn get_lts_key() -> String {
|
||||
if let Ok(cfg) = load_config() {
|
||||
if let Some(key) = cfg.long_term_stats.license_key {
|
||||
return key.clone();
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "No LTS Key Found!".bright_red());
|
||||
println!("We prioritize helping Long-Term Stats Subscribers and Donors.");
|
||||
println!("Please enter your LTS key (from your email), or ENTER for none:");
|
||||
return read_line();
|
||||
}
|
||||
|
||||
fn get_name() -> String {
|
||||
let mut candidate = String::new();
|
||||
while candidate.is_empty() {
|
||||
println!("Please enter your name, email address and Zulip handle in a single line (ENTER when done).");
|
||||
candidate = read_line();
|
||||
}
|
||||
candidate
|
||||
}
|
||||
|
||||
fn get_comments() -> String {
|
||||
println!("Anything you'd like to tell us about? (Comments)");
|
||||
read_line()
|
||||
}
|
||||
|
||||
fn gather_dump() {
|
||||
let name = get_name();
|
||||
let lts_key = get_lts_key();
|
||||
let comments = get_comments();
|
||||
|
||||
let dump = gather_all_support_info(&name, &comments, <s_key).unwrap();
|
||||
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||
let filename = format!("/tmp/libreqos_{}.support", timestamp);
|
||||
let path = Path::new(&filename);
|
||||
std::fs::write(&path, dump.serialize_and_compress().unwrap()).unwrap();
|
||||
lqos_support_tool::console::success(&format!("Dump written to {}", filename));
|
||||
}
|
||||
|
||||
fn summarize(filename: &str) {
|
||||
let path = Path::new(filename);
|
||||
if !path.exists() {
|
||||
println!("Dump not found at {filename}");
|
||||
} else {
|
||||
let bytes = std::fs::read(&path).unwrap();
|
||||
if let Ok(decoded) = SupportDump::from_bytes(&bytes) {
|
||||
println!("Sent by: {}", decoded.sender);
|
||||
println!("Comments: {}", decoded.comment);
|
||||
println!("LTS Key: {}", decoded.lts_key);
|
||||
|
||||
println!("{:50} {:10} {}", "Sanity Check", "Success?", "Comment");
|
||||
for entry in decoded.sanity_checks.results.iter() {
|
||||
println!("{:50} {:10} {}", entry.name, entry.success, entry.comments);
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("{:40} {:10} : {:40?}", "ENTRY", "SIZE", "FILENAME");
|
||||
for entry in decoded.entries.iter() {
|
||||
println!("{:40} {:10} : {:40?}", entry.name, entry.contents.len(), entry.filename);
|
||||
}
|
||||
} else {
|
||||
println!("Dump did not decode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sanity_checks() {
|
||||
if let Err(e) = run_sanity_checks() {
|
||||
println!("Sanity Check Failed: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn expand(filename: &str, target: &str) {
|
||||
// Check inputs
|
||||
let in_path = Path::new(filename);
|
||||
if !in_path.exists() {
|
||||
println!("{} not found", filename);
|
||||
return;
|
||||
}
|
||||
let out_path = Path::new(target);
|
||||
if !out_path.exists() {
|
||||
println!("{} not found", target);
|
||||
return;
|
||||
}
|
||||
if !out_path.is_dir() {
|
||||
println!("{} is not a directory", target);
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the data
|
||||
let bytes = std::fs::read(&in_path).unwrap();
|
||||
if let Ok(decoded) = SupportDump::from_bytes(&bytes) {
|
||||
// Save the header
|
||||
let header = format!("From: {}\nComment: {}\nLTS Key: {}\n", decoded.sender, decoded.comment, decoded.lts_key);
|
||||
let header_path = out_path.join("header.txt");
|
||||
std::fs::write(header_path, header.as_bytes()).unwrap();
|
||||
|
||||
// Save the sanity check results
|
||||
let mut sanity = String::new();
|
||||
for check in decoded.sanity_checks.results.iter() {
|
||||
sanity += &format!("{} - Success? {}: {}\n", check.name, check.success, check.comments);
|
||||
}
|
||||
let sanity_path = out_path.join("sanity_checks.txt");
|
||||
std::fs::write(sanity_path, sanity.as_bytes()).unwrap();
|
||||
|
||||
// Save the files
|
||||
for (idx, dump) in decoded.entries.iter().enumerate() {
|
||||
let trimmed = dump.name.trim().replace(' ', "").to_lowercase().replace('(', "").replace(')', "");
|
||||
let dump_path = out_path.join(&format!("{idx:0>2}_{}", trimmed));
|
||||
std::fs::write(dump_path, dump.contents.as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
println!("Expanded data written to {}", target);
|
||||
}
|
||||
|
||||
fn submit() {
|
||||
// Get header
|
||||
let name = get_name();
|
||||
let lts_key = get_lts_key();
|
||||
let comments = get_comments();
|
||||
|
||||
// Get the data
|
||||
let dump = gather_all_support_info(&name, &comments, <s_key).unwrap();
|
||||
|
||||
// Send it
|
||||
lqos_support_tool::submit_to_network(dump);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Some(Commands::Sanity) => sanity_checks(),
|
||||
Some(Commands::Gather) => gather_dump(),
|
||||
Some(Commands::Summarize { filename }) => summarize(&filename),
|
||||
Some(Commands::Expand { filename, target }) => expand(&filename, &target),
|
||||
Some(Commands::Submit) => submit(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
57
src/rust/lqos_support_tool/src/sanity_checks.rs
Normal file
57
src/rust/lqos_support_tool/src/sanity_checks.rs
Normal file
@ -0,0 +1,57 @@
|
||||
mod config_sane;
|
||||
mod interfaces;
|
||||
mod queues;
|
||||
mod bridge;
|
||||
mod net_json;
|
||||
mod shaped_devices;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::console::{error, success};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct SanityChecks {
|
||||
pub results: Vec<SanityCheck>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct SanityCheck {
|
||||
pub name: String,
|
||||
pub success: bool,
|
||||
pub comments: String,
|
||||
}
|
||||
|
||||
pub fn run_sanity_checks() -> anyhow::Result<SanityChecks> {
|
||||
println!("Running Sanity Checks");
|
||||
let mut results = Vec::new();
|
||||
|
||||
// Run the checks
|
||||
config_sane::config_exists(&mut results);
|
||||
config_sane::can_load_config(&mut results);
|
||||
interfaces::interfaces_exist(&mut results);
|
||||
queues::sanity_check_queues(&mut results);
|
||||
bridge::check_interface_status(&mut results);
|
||||
bridge::check_bridge(&mut results);
|
||||
net_json::check_net_json_exists(&mut results);
|
||||
net_json::can_we_load_net_json(&mut results);
|
||||
net_json::can_we_parse_net_json(&mut results);
|
||||
shaped_devices::shaped_devices_exists(&mut results);
|
||||
shaped_devices::can_we_read_shaped_devices(&mut results);
|
||||
shaped_devices::parent_check(&mut results);
|
||||
|
||||
// Did any fail?
|
||||
let mut any_errors = false;
|
||||
for s in results.iter() {
|
||||
if s.success {
|
||||
success(&format!("{} {}", s.name, s.comments));
|
||||
} else {
|
||||
error(&format!("{}: {}", s.name, s.comments));
|
||||
any_errors = true;
|
||||
}
|
||||
}
|
||||
|
||||
if any_errors {
|
||||
error("ERRORS FOUND DURING SANITY CHECK");
|
||||
}
|
||||
|
||||
Ok(SanityChecks { results })
|
||||
}
|
124
src/rust/lqos_support_tool/src/sanity_checks/bridge.rs
Normal file
124
src/rust/lqos_support_tool/src/sanity_checks/bridge.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use std::process::Command;
|
||||
use lqos_config::load_config;
|
||||
use crate::sanity_checks::SanityCheck;
|
||||
|
||||
pub fn check_bridge(results: &mut Vec<SanityCheck>) {
|
||||
if let Ok(cfg) = load_config() {
|
||||
if let Ok(interfaces) = get_interfaces_from_ip_link() {
|
||||
// On a stick mode is bridge-free
|
||||
if cfg.on_a_stick_mode() {
|
||||
results.push(SanityCheck{
|
||||
name: format!("Single Interface Mode: Bridges Ignored"),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Is the XDP bridge enabled?
|
||||
if let Some(bridge) = &cfg.bridge {
|
||||
if bridge.use_xdp_bridge {
|
||||
for bridge_if in interfaces
|
||||
.iter()
|
||||
.filter(|bridge_if| bridge_if.link_type == "ether" && bridge_if.operational_state == "UP")
|
||||
{
|
||||
// We found a bridge. Check member interfaces to check that it does NOT include any XDP
|
||||
// bridge members.
|
||||
let in_bridge: Vec<&IpLinkInterface> = interfaces
|
||||
.iter()
|
||||
.filter(|member_if| {
|
||||
if let Some(master) = &member_if.master {
|
||||
master == &bridge_if.name
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.filter(|member_if| member_if.name == cfg.internet_interface() || member_if.name == cfg.isp_interface())
|
||||
.collect();
|
||||
|
||||
if in_bridge.len() == 2 {
|
||||
results.push(SanityCheck{
|
||||
name: format!("Linux Bridge AND XDP Bridge At Once ({})", bridge_if.name),
|
||||
success: false,
|
||||
comments: format!("Bridge ({}) contains both the internet and ISP interfaces, and you have the xdp_bridge enabled. This is not supported.", bridge_if.name),
|
||||
});
|
||||
} else {
|
||||
results.push(SanityCheck{
|
||||
name: format!("Bridge Membership Check ({})", bridge_if.name),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_interface_status(results: &mut Vec<SanityCheck>) {
|
||||
if let Ok(cfg) = load_config() {
|
||||
if let Ok(interfaces) = get_interfaces_from_ip_link() {
|
||||
if let Some(stick) = &cfg.single_interface {
|
||||
if let Some(iface) = interfaces.iter().find(|i| i.name == stick.interface) {
|
||||
results.push(SanityCheck{
|
||||
name: format!("Interface {} in state {}", iface.name, iface.operational_state),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
}
|
||||
} else if let Some(bridge) = &cfg.bridge {
|
||||
if let Some(iface) = interfaces.iter().find(|i| i.name == bridge.to_internet) {
|
||||
results.push(SanityCheck{
|
||||
name: format!("Interface {} in state {}", iface.name, iface.operational_state),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
}
|
||||
if let Some(iface) = interfaces.iter().find(|i| i.name == bridge.to_network) {
|
||||
results.push(SanityCheck{
|
||||
name: format!("Interface {} in state {}", iface.name, iface.operational_state),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct IpLinkInterface {
|
||||
pub name: String,
|
||||
pub index: u32,
|
||||
pub operational_state: String,
|
||||
pub link_type: String,
|
||||
pub master: Option<String>,
|
||||
}
|
||||
|
||||
fn get_interfaces_from_ip_link() -> anyhow::Result<Vec<IpLinkInterface>> {
|
||||
let output = Command::new("/sbin/ip")
|
||||
.args(["-j", "link"])
|
||||
.output()?;
|
||||
let output = String::from_utf8(output.stdout)?;
|
||||
let output_json = serde_json::from_str::<serde_json::Value>(&output)?;
|
||||
|
||||
let mut interfaces = Vec::new();
|
||||
for interface in output_json.as_array().unwrap() {
|
||||
let name = interface["ifname"].as_str().unwrap().to_string();
|
||||
let index = interface["ifindex"].as_u64().unwrap() as u32;
|
||||
let operstate = interface["operstate"].as_str().unwrap().to_string();
|
||||
let link_type = interface["link_type"].as_str().unwrap().to_string();
|
||||
let master = interface["master"].as_str().map(|s| s.to_string());
|
||||
|
||||
interfaces.push(IpLinkInterface {
|
||||
name,
|
||||
index,
|
||||
operational_state: operstate,
|
||||
link_type,
|
||||
master,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(interfaces)
|
||||
}
|
34
src/rust/lqos_support_tool/src/sanity_checks/config_sane.rs
Normal file
34
src/rust/lqos_support_tool/src/sanity_checks/config_sane.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use std::path::Path;
|
||||
use lqos_config::load_config;
|
||||
use crate::sanity_checks::SanityCheck;
|
||||
|
||||
pub fn config_exists(results: &mut Vec<SanityCheck>) {
|
||||
let path = Path::new("/etc/lqos.conf");
|
||||
let mut result = SanityCheck {
|
||||
name: "Config File Exists".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
if path.exists() {
|
||||
result.success = true;
|
||||
} else {
|
||||
result.success = false;
|
||||
result.comments = "/etc/lqos.conf could not be opened".to_string();
|
||||
}
|
||||
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
pub fn can_load_config(results: &mut Vec<SanityCheck>) {
|
||||
let mut result = SanityCheck {
|
||||
name: "Config File Can Be Loaded".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let cfg = load_config();
|
||||
if cfg.is_ok() {
|
||||
result.success = true;
|
||||
} else {
|
||||
result.success = false;
|
||||
result.comments = "Configuration file could not be loaded".to_string();
|
||||
}
|
||||
results.push(result);
|
||||
}
|
63
src/rust/lqos_support_tool/src/sanity_checks/interfaces.rs
Normal file
63
src/rust/lqos_support_tool/src/sanity_checks/interfaces.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use std::ffi::CString;
|
||||
use anyhow::Error;
|
||||
use lqos_config::load_config;
|
||||
use crate::sanity_checks::SanityCheck;
|
||||
|
||||
fn interface_name_to_index(interface_name: &str) -> anyhow::Result<u32> {
|
||||
use nix::libc::if_nametoindex;
|
||||
let if_name = CString::new(interface_name)?;
|
||||
let index = unsafe { if_nametoindex(if_name.as_ptr()) };
|
||||
if index == 0 {
|
||||
Err(Error::msg(format!("Unknown interface: {interface_name}")))
|
||||
} else {
|
||||
Ok(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interfaces_exist(results: &mut Vec<SanityCheck>) {
|
||||
if let Ok(cfg) = load_config() {
|
||||
if cfg.on_a_stick_mode() {
|
||||
if interface_name_to_index(&cfg.internet_interface()).is_ok() {
|
||||
results.push(SanityCheck {
|
||||
name: "Single Interface Exists".to_string(),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
} else {
|
||||
results.push(SanityCheck {
|
||||
name: "Single Interface Exists".to_string(),
|
||||
success: false,
|
||||
comments: format!("Interface {} is listed in /etc/lqos.conf - but that interface does not appear to exist in the Linux interface map", cfg.internet_interface()),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if interface_name_to_index(&cfg.internet_interface()).is_ok() {
|
||||
results.push(SanityCheck {
|
||||
name: "Internet Interface Exists".to_string(),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
} else {
|
||||
results.push(SanityCheck {
|
||||
name: "Internet Interface Exists".to_string(),
|
||||
success: false,
|
||||
comments: format!("Interface {} is listed in /etc/lqos.conf - but that interface does not appear to exist in the Linux interface map", cfg.internet_interface()),
|
||||
});
|
||||
}
|
||||
|
||||
if interface_name_to_index(&cfg.isp_interface()).is_ok() {
|
||||
results.push(SanityCheck {
|
||||
name: "ISP Facing Interface Exists".to_string(),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
} else {
|
||||
results.push(SanityCheck {
|
||||
name: "ISP Facing Interface Exists".to_string(),
|
||||
success: false,
|
||||
comments: format!("Interface {} is listed in /etc/lqos.conf - but that interface does not appear to exist in the Linux interface map", cfg.isp_interface()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
68
src/rust/lqos_support_tool/src/sanity_checks/net_json.rs
Normal file
68
src/rust/lqos_support_tool/src/sanity_checks/net_json.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use std::path::Path;
|
||||
use serde_json::Value;
|
||||
use lqos_config::load_config;
|
||||
use crate::sanity_checks::SanityCheck;
|
||||
|
||||
pub fn check_net_json_exists(results: &mut Vec<SanityCheck>) {
|
||||
if let Ok(cfg) = load_config() {
|
||||
let path = Path::new(&cfg.lqos_directory).join("network.json");
|
||||
if path.exists() {
|
||||
results.push(SanityCheck{
|
||||
name: "network.json exists".to_string(),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
} else {
|
||||
results.push(SanityCheck{
|
||||
name: "network.json exists".to_string(),
|
||||
success: false,
|
||||
comments: format!("File not found at {:?}", path),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_we_load_net_json(results: &mut Vec<SanityCheck>) {
|
||||
if let Ok(cfg) = load_config() {
|
||||
let path = Path::new(&cfg.lqos_directory).join("network.json");
|
||||
if path.exists() {
|
||||
if let Ok(str) = std::fs::read_to_string(path) {
|
||||
match serde_json::from_str::<Value>(&str) {
|
||||
Ok(json) => {
|
||||
results.push(SanityCheck{
|
||||
name: "network.json is parseable JSON".to_string(),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
results.push(SanityCheck{
|
||||
name: "network.json is parseable JSON".to_string(),
|
||||
success: false,
|
||||
comments: format!("{e:?}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_we_parse_net_json(results: &mut Vec<SanityCheck>) {
|
||||
match lqos_config::NetworkJson::load() {
|
||||
Ok(json) => {
|
||||
results.push(SanityCheck{
|
||||
name: "network.json is valid JSON".to_string(),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
results.push(SanityCheck{
|
||||
name: "network.json is valid JSON".to_string(),
|
||||
success: false,
|
||||
comments: format!("{e:?}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
82
src/rust/lqos_support_tool/src/sanity_checks/queues.rs
Normal file
82
src/rust/lqos_support_tool/src/sanity_checks/queues.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use std::path::Path;
|
||||
use lqos_config::load_config;
|
||||
use crate::sanity_checks::SanityCheck;
|
||||
|
||||
fn check_queues(interface: &str) -> (i32, i32) {
|
||||
let path = format!("/sys/class/net/{interface}/queues/");
|
||||
let sys_path = Path::new(&path);
|
||||
if !sys_path.exists() {
|
||||
return (0,0);
|
||||
}
|
||||
|
||||
let mut counts = (0, 0);
|
||||
let paths = std::fs::read_dir(sys_path).unwrap();
|
||||
for path in paths {
|
||||
if let Ok(path) = &path {
|
||||
if path.path().is_dir() {
|
||||
if let Some(filename) = path.path().file_name() {
|
||||
if let Some(filename) = filename.to_str() {
|
||||
if filename.starts_with("rx-") {
|
||||
counts.0 += 1;
|
||||
} else if filename.starts_with("tx-") {
|
||||
counts.1 += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
counts
|
||||
}
|
||||
|
||||
pub fn sanity_check_queues(results: &mut Vec<SanityCheck>) {
|
||||
if let Ok(cfg) = load_config() {
|
||||
if cfg.on_a_stick_mode() {
|
||||
let counts = check_queues(&cfg.internet_interface());
|
||||
if counts.0 > 1 && counts.1 > 1 {
|
||||
results.push(SanityCheck{
|
||||
name: "Queue Check (Internet Interface)".to_string(),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
} else {
|
||||
results.push(SanityCheck{
|
||||
name: "Queue Check (Internet Interface)".to_string(),
|
||||
success: false,
|
||||
comments: format!("{} does not provide multiple RX and TX queues", cfg.internet_interface()),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let counts = check_queues(&cfg.internet_interface());
|
||||
if counts.0 > 1 && counts.1 > 1 {
|
||||
results.push(SanityCheck{
|
||||
name: "Queue Check (Internet Interface)".to_string(),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
} else {
|
||||
results.push(SanityCheck{
|
||||
name: "Queue Check (Internet Interface)".to_string(),
|
||||
success: false,
|
||||
comments: format!("{} does not provide multiple RX and TX queues", cfg.internet_interface()),
|
||||
});
|
||||
}
|
||||
|
||||
let counts = check_queues(&cfg.isp_interface());
|
||||
if counts.0 > 1 && counts.1 > 1 {
|
||||
results.push(SanityCheck{
|
||||
name: "Queue Check (ISP Facing Interface)".to_string(),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
} else {
|
||||
results.push(SanityCheck{
|
||||
name: "Queue Check (ISP Facing Interface)".to_string(),
|
||||
success: false,
|
||||
comments: format!("{} does not provide multiple RX and TX queues", cfg.isp_interface()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
use std::path::Path;
|
||||
use lqos_config::load_config;
|
||||
use crate::sanity_checks::SanityCheck;
|
||||
|
||||
pub fn shaped_devices_exists(results: &mut Vec<SanityCheck>) {
|
||||
if let Ok(cfg) = load_config() {
|
||||
let path = Path::new(&cfg.lqos_directory).join("ShapedDevices.csv");
|
||||
if path.exists() {
|
||||
results.push(SanityCheck{
|
||||
name: "ShapedDevices.csv exists".to_string(),
|
||||
success: true,
|
||||
comments: "".to_string(),
|
||||
});
|
||||
} else {
|
||||
results.push(SanityCheck{
|
||||
name: "ShapedDevices.csv exists".to_string(),
|
||||
success: false,
|
||||
comments: format!("File not found at {:?}", path),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_we_read_shaped_devices(results: &mut Vec<SanityCheck>) {
|
||||
match lqos_config::ConfigShapedDevices::load() {
|
||||
Ok(sd) => {
|
||||
results.push(SanityCheck{
|
||||
name: "ShapedDevices.csv Loads?".to_string(),
|
||||
success: true,
|
||||
comments: format!("{} Devices Found", sd.devices.len()),
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
results.push(SanityCheck{
|
||||
name: "ShapedDevices.csv Loads?".to_string(),
|
||||
success: false,
|
||||
comments: format!("{e:?}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent_check(results: &mut Vec<SanityCheck>) {
|
||||
if let Ok(net_json) = lqos_config::NetworkJson::load() {
|
||||
if net_json.nodes.len() < 2 {
|
||||
results.push(SanityCheck{
|
||||
name: "Flat Network - Skipping Parent Check".to_string(),
|
||||
success: true,
|
||||
comments: String::new(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(shaped_devices) = lqos_config::ConfigShapedDevices::load() {
|
||||
for sd in shaped_devices.devices.iter() {
|
||||
if !net_json.nodes.iter().any(|n| n.name == sd.parent_node) {
|
||||
results.push(SanityCheck{
|
||||
name: "Shaped Device Invalid Parent".to_string(),
|
||||
success: false,
|
||||
comments: format!("Device {}/{} is parented to {} - which does not exist", sd.device_name, sd.device_id, sd.parent_node),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
128
src/rust/lqos_support_tool/src/support_info.rs
Normal file
128
src/rust/lqos_support_tool/src/support_info.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use colored::Colorize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::console::error;
|
||||
use crate::sanity_checks::{run_sanity_checks, SanityChecks};
|
||||
|
||||
mod ip_link;
|
||||
mod systemctl_services;
|
||||
mod systemctl_service_single;
|
||||
mod lqos_config;
|
||||
mod task_journal;
|
||||
mod service_config;
|
||||
mod ip_addr;
|
||||
mod kernel_info;
|
||||
mod distro_name;
|
||||
|
||||
pub trait SupportInfo {
|
||||
fn get_string(&self) -> String;
|
||||
fn get_name(&self) -> String;
|
||||
fn get_filename(&self) -> Option<String>;
|
||||
fn gather(&mut self) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct SupportDump {
|
||||
pub sender: String,
|
||||
pub comment: String,
|
||||
pub lts_key: String,
|
||||
pub sanity_checks: SanityChecks,
|
||||
pub entries: Vec<DumpEntry>
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct DumpEntry {
|
||||
pub name: String,
|
||||
pub filename: Option<String>,
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
impl SupportDump {
|
||||
pub fn serialize_and_compress(&self) -> anyhow::Result<Vec<u8>> {
|
||||
let cbor_bytes = serde_cbor::to_vec(self)?;
|
||||
let compressed_bytes = miniz_oxide::deflate::compress_to_vec(&cbor_bytes, 10);
|
||||
Ok(compressed_bytes)
|
||||
}
|
||||
|
||||
pub fn from_bytes(raw_bytes: &[u8]) -> anyhow::Result<Self> {
|
||||
let decompressed_bytes = miniz_oxide::inflate::decompress_to_vec(raw_bytes).unwrap();
|
||||
let deserialized = serde_cbor::from_slice(&decompressed_bytes)?;
|
||||
Ok(deserialized)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gather_all_support_info(sender: &str, comments: &str, lts_key: &str) -> anyhow::Result<SupportDump> {
|
||||
let sanity_checks = run_sanity_checks()?;
|
||||
|
||||
let mut data_targets: Vec<Box<dyn SupportInfo>> = vec![
|
||||
lqos_config::LqosConfig::boxed(),
|
||||
ip_link::IpLink::boxed(),
|
||||
ip_addr::IpAddr::boxed(),
|
||||
kernel_info::KernelInfo::boxed(),
|
||||
distro_name::DistroName::boxed(),
|
||||
systemctl_services::SystemCtlServices::boxed(),
|
||||
systemctl_service_single::SystemCtlService::boxed("lqosd"),
|
||||
systemctl_service_single::SystemCtlService::boxed("lqos_node_manager"),
|
||||
systemctl_service_single::SystemCtlService::boxed("lqos_scheduler"),
|
||||
task_journal::TaskJournal::boxed("lqosd"),
|
||||
task_journal::TaskJournal::boxed("lqos_node_manager"),
|
||||
task_journal::TaskJournal::boxed("lqos_scheduler"),
|
||||
service_config::ServiceConfig::boxed("ShapedDevices.csv"),
|
||||
service_config::ServiceConfig::boxed("network.json"),
|
||||
];
|
||||
|
||||
for target in data_targets.iter_mut() {
|
||||
println!("{} : {}",
|
||||
"TASK-GATHER".cyan(),
|
||||
target.get_name().yellow()
|
||||
);
|
||||
if let Err(e) = target.gather() {
|
||||
error(&e.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let mut dump = SupportDump {
|
||||
sender: sender.to_string(),
|
||||
comment: comments.to_string(),
|
||||
lts_key: lts_key.to_string(),
|
||||
sanity_checks,
|
||||
entries: Vec::new(),
|
||||
};
|
||||
|
||||
for target in data_targets.iter() {
|
||||
let entry = DumpEntry {
|
||||
name: target.get_name(),
|
||||
filename: target.get_filename(),
|
||||
contents: target.get_string(),
|
||||
};
|
||||
dump.entries.push(entry);
|
||||
}
|
||||
//println!("{dump:#?}");
|
||||
|
||||
Ok(dump)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
let original = SupportDump {
|
||||
entries: vec![
|
||||
DumpEntry {
|
||||
name: "Test".to_string(),
|
||||
filename: None,
|
||||
contents: "BLAH".to_string(),
|
||||
}
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
let bytes = original.serialize_and_compress().unwrap();
|
||||
let restored = SupportDump::from_bytes(&bytes).unwrap();
|
||||
assert_eq!(original.entries.len(), restored.entries.len());
|
||||
assert_eq!(original.entries.len(), 1);
|
||||
assert_eq!(original.entries[0].name, "Test");
|
||||
assert!(original.entries[0].filename.is_none());
|
||||
assert_eq!(original.entries[0].contents, "BLAH");
|
||||
}
|
||||
}
|
38
src/rust/lqos_support_tool/src/support_info/distro_name.rs
Normal file
38
src/rust/lqos_support_tool/src/support_info/distro_name.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use std::process::Command;
|
||||
use crate::console::success;
|
||||
use crate::support_info::SupportInfo;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DistroName {
|
||||
output: String,
|
||||
}
|
||||
|
||||
impl SupportInfo for DistroName {
|
||||
fn get_string(&self) -> String {
|
||||
self.output.to_string()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
"LSB Distro Info".to_string()
|
||||
}
|
||||
|
||||
fn get_filename(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn gather(&mut self) -> anyhow::Result<()> {
|
||||
let output = Command::new("/bin/lsb_release")
|
||||
.arg("-a")
|
||||
.output()?;
|
||||
let out_str = String::from_utf8_lossy(output.stdout.as_slice());
|
||||
self.output = out_str.to_string();
|
||||
success("Gathered distro info");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DistroName {
|
||||
pub fn boxed() -> Box<Self> {
|
||||
Box::new(Self::default())
|
||||
}
|
||||
}
|
38
src/rust/lqos_support_tool/src/support_info/ip_addr.rs
Normal file
38
src/rust/lqos_support_tool/src/support_info/ip_addr.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use std::process::Command;
|
||||
use crate::console::success;
|
||||
use crate::support_info::SupportInfo;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IpAddr {
|
||||
output: String,
|
||||
}
|
||||
|
||||
impl SupportInfo for IpAddr {
|
||||
fn get_string(&self) -> String {
|
||||
self.output.to_string()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
"IP Address Information".to_string()
|
||||
}
|
||||
|
||||
fn get_filename(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn gather(&mut self) -> anyhow::Result<()> {
|
||||
let output = Command::new("/sbin/ip")
|
||||
.arg("addr")
|
||||
.output()?;
|
||||
let out_str = String::from_utf8_lossy(output.stdout.as_slice());
|
||||
self.output = out_str.to_string();
|
||||
success("Gathered `ip addr` data");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl IpAddr {
|
||||
pub fn boxed() -> Box<Self> {
|
||||
Box::new(Self::default())
|
||||
}
|
||||
}
|
38
src/rust/lqos_support_tool/src/support_info/ip_link.rs
Normal file
38
src/rust/lqos_support_tool/src/support_info/ip_link.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use std::process::Command;
|
||||
use crate::console::success;
|
||||
use crate::support_info::SupportInfo;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IpLink {
|
||||
output: String,
|
||||
}
|
||||
|
||||
impl SupportInfo for IpLink {
|
||||
fn get_string(&self) -> String {
|
||||
self.output.to_string()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
"IP Link Information".to_string()
|
||||
}
|
||||
|
||||
fn get_filename(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn gather(&mut self) -> anyhow::Result<()> {
|
||||
let output = Command::new("/sbin/ip")
|
||||
.arg("link")
|
||||
.output()?;
|
||||
let out_str = String::from_utf8_lossy(output.stdout.as_slice());
|
||||
self.output = out_str.to_string();
|
||||
success("Gathered `ip link` data");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl IpLink {
|
||||
pub fn boxed() -> Box<Self> {
|
||||
Box::new(Self::default())
|
||||
}
|
||||
}
|
38
src/rust/lqos_support_tool/src/support_info/kernel_info.rs
Normal file
38
src/rust/lqos_support_tool/src/support_info/kernel_info.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use std::process::Command;
|
||||
use crate::console::success;
|
||||
use crate::support_info::SupportInfo;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct KernelInfo {
|
||||
output: String,
|
||||
}
|
||||
|
||||
impl SupportInfo for KernelInfo {
|
||||
fn get_string(&self) -> String {
|
||||
self.output.to_string()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
"Uname Kernel Info".to_string()
|
||||
}
|
||||
|
||||
fn get_filename(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn gather(&mut self) -> anyhow::Result<()> {
|
||||
let output = Command::new("/bin/uname")
|
||||
.arg("-a")
|
||||
.output()?;
|
||||
let out_str = String::from_utf8_lossy(output.stdout.as_slice());
|
||||
self.output = out_str.to_string();
|
||||
success("Gathered kernel info");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl KernelInfo {
|
||||
pub fn boxed() -> Box<Self> {
|
||||
Box::new(Self::default())
|
||||
}
|
||||
}
|
38
src/rust/lqos_support_tool/src/support_info/lqos_config.rs
Normal file
38
src/rust/lqos_support_tool/src/support_info/lqos_config.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use std::path::Path;
|
||||
use crate::console::success;
|
||||
use crate::support_info::SupportInfo;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LqosConfig {
|
||||
output: String,
|
||||
}
|
||||
|
||||
impl SupportInfo for LqosConfig {
|
||||
fn get_string(&self) -> String {
|
||||
self.output.to_string()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
"LibreQoS Config File".to_string()
|
||||
}
|
||||
|
||||
fn get_filename(&self) -> Option<String> {
|
||||
Some("/etc/lqos.conf".to_string())
|
||||
}
|
||||
|
||||
fn gather(&mut self) -> anyhow::Result<()> {
|
||||
let path = Path::new("/etc/lqos.conf");
|
||||
if !path.exists() {
|
||||
anyhow::bail!("/etc/lqos.conf could not be opened");
|
||||
}
|
||||
self.output = std::fs::read_to_string(path)?;
|
||||
success("Gathered /etc/lqos.conf");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl LqosConfig {
|
||||
pub fn boxed() -> Box<Self> {
|
||||
Box::new(Self::default())
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
use std::path::Path;
|
||||
use lqos_config::load_config;
|
||||
use crate::console::{error, success};
|
||||
use crate::support_info::SupportInfo;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ServiceConfig {
|
||||
target: String,
|
||||
output: String,
|
||||
}
|
||||
|
||||
impl SupportInfo for ServiceConfig {
|
||||
fn get_string(&self) -> String {
|
||||
self.output.to_string()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
format!("Config File: {}", self.target)
|
||||
}
|
||||
|
||||
fn get_filename(&self) -> Option<String> {
|
||||
let cfg = load_config();
|
||||
if let Ok(cfg) = cfg {
|
||||
Some(format!("{}{}", cfg.lqos_directory, self.target))
|
||||
} else {
|
||||
error("Unable to read configuration!");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn gather(&mut self) -> anyhow::Result<()> {
|
||||
let cfg = load_config()?;
|
||||
let path = Path::new(&cfg.lqos_directory).join(&self.target);
|
||||
if !path.exists() {
|
||||
anyhow::bail!("Could not read from {:?}", path);
|
||||
}
|
||||
self.output = std::fs::read_to_string(path)?;
|
||||
success(&format!("Gathered {}", self.target));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceConfig {
|
||||
pub fn boxed<S: ToString>(target: S) -> Box<Self> {
|
||||
Box::new(Self {
|
||||
target: target.to_string(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
use std::process::Command;
|
||||
use crate::console::success;
|
||||
use crate::support_info::SupportInfo;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SystemCtlService {
|
||||
target: String,
|
||||
output: String,
|
||||
}
|
||||
|
||||
impl SupportInfo for SystemCtlService {
|
||||
fn get_string(&self) -> String {
|
||||
self.output.to_string()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
format!("SystemCtl Status ({})", self.target)
|
||||
}
|
||||
|
||||
fn get_filename(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn gather(&mut self) -> anyhow::Result<()> {
|
||||
let out = Command::new("/bin/systemctl")
|
||||
.args(&["--no-pager", "status", &self.target])
|
||||
.output()?;
|
||||
|
||||
self.output = String::from_utf8_lossy(&out.stdout).to_string();
|
||||
success(&format!("Gathered systemctl status for {}", self.target));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemCtlService {
|
||||
pub fn boxed<S: ToString>(target: S) -> Box<Self> {
|
||||
Box::new(Self {
|
||||
target: target.to_string(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
use std::process::Command;
|
||||
use crate::console::success;
|
||||
use crate::support_info::SupportInfo;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SystemCtlServices {
|
||||
output: String,
|
||||
}
|
||||
|
||||
impl SupportInfo for SystemCtlServices {
|
||||
fn get_string(&self) -> String {
|
||||
self.output.to_string()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
"SystemCtl Status".to_string()
|
||||
}
|
||||
|
||||
fn get_filename(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn gather(&mut self) -> anyhow::Result<()> {
|
||||
let out = Command::new("/bin/systemctl")
|
||||
.args(&["--no-pager", "status"])
|
||||
.output()?;
|
||||
|
||||
self.output = String::from_utf8_lossy(&out.stdout).to_string();
|
||||
success("Gathered global `systemctl status`");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemCtlServices {
|
||||
pub fn boxed() -> Box<Self> {
|
||||
Box::new(Self::default())
|
||||
}
|
||||
}
|
43
src/rust/lqos_support_tool/src/support_info/task_journal.rs
Normal file
43
src/rust/lqos_support_tool/src/support_info/task_journal.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use std::process::Command;
|
||||
use crate::console::success;
|
||||
use crate::support_info::SupportInfo;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TaskJournal {
|
||||
target: String,
|
||||
output: String,
|
||||
}
|
||||
|
||||
impl SupportInfo for TaskJournal {
|
||||
fn get_string(&self) -> String {
|
||||
self.output.to_string()
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
format!("Journal ({})", self.target)
|
||||
}
|
||||
|
||||
fn get_filename(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn gather(&mut self) -> anyhow::Result<()> {
|
||||
let out = Command::new("/bin/journalctl")
|
||||
.args(&["--no-pager", "-u", &self.target])
|
||||
.output()?;
|
||||
|
||||
self.output = String::from_utf8_lossy(&out.stdout).to_string();
|
||||
success(&format!("Gathered journalctl status for {}", self.target));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskJournal {
|
||||
pub fn boxed<S: ToString>(target: S) -> Box<Self> {
|
||||
Box::new(Self {
|
||||
target: target.to_string(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user