use subfolder for frontend/backend
This commit is contained in:
1537
lib/Cargo.lock
generated
Normal file
1537
lib/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
lib/Cargo.toml
Normal file
11
lib/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "raid_manager"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rocket = {version = "0.5.0-rc.2", features = ["json"]}
|
||||
regex = "1"
|
||||
lazy_static = "1.4.0"
|
29
lib/src/main.rs
Normal file
29
lib/src/main.rs
Normal file
@ -0,0 +1,29 @@
|
||||
mod parser;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use crate::parser::lsblk_parser::{Disk, parse_lsblk};
|
||||
use crate::parser::mdstat_parser::{MdRaidSystem, parse_mdstat};
|
||||
|
||||
#[get("/raiddevices")]
|
||||
fn get_raid_devices() -> Json<MdRaidSystem> {
|
||||
Json(parse_mdstat())
|
||||
}
|
||||
|
||||
#[get("/disks")]
|
||||
fn get_disks() -> Json<Vec<Disk>> {
|
||||
Json(parse_lsblk())
|
||||
}
|
||||
|
||||
#[rocket::main]
|
||||
async fn main() -> Result<(), rocket::Error> {
|
||||
println!("init");
|
||||
let _rocket = rocket::build()
|
||||
.mount("/api", routes![get_raid_devices, get_disks])
|
||||
.launch()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
66
lib/src/parser/lsblk_parser.rs
Normal file
66
lib/src/parser/lsblk_parser.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use std::process::Command;
|
||||
use rocket::serde::json::{serde_json};
|
||||
use rocket::serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct Disk {
|
||||
name: String,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
struct ChildDevice {
|
||||
name: String,
|
||||
#[serde(rename = "maj:min")]
|
||||
majmin: String,
|
||||
rm: bool,
|
||||
size: u64,
|
||||
ro: bool,
|
||||
#[serde(rename = "type")]
|
||||
kind: String,
|
||||
mountpoints: Vec<Option<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
struct Blockdevice {
|
||||
name: String,
|
||||
#[serde(rename = "type")]
|
||||
kind: String,
|
||||
size: u64,
|
||||
#[serde(rename = "maj:min")]
|
||||
majmin: String,
|
||||
rm: bool,
|
||||
ro: bool,
|
||||
mountpoints: Vec<Option<String>>,
|
||||
children: Option<Vec<ChildDevice>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
struct LsblkT {
|
||||
blockdevices: Vec<Blockdevice>,
|
||||
}
|
||||
|
||||
pub fn parse_lsblk() -> Vec<Disk> {
|
||||
let output = Command::new("lsblk")
|
||||
.arg("-J")
|
||||
.arg("-b")// sizes in bytes
|
||||
.arg("-p") // complete device path
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
|
||||
let out = output.stdout;
|
||||
let out = String::from_utf8(out).unwrap();
|
||||
|
||||
let v: LsblkT = serde_json::from_str(out.as_str()).unwrap();
|
||||
|
||||
let mut disks: Vec<Disk> = Vec::new();
|
||||
for d in v.blockdevices {
|
||||
if d.kind != "disk" { continue; };
|
||||
disks.push(Disk { name: d.name, size: d.size });
|
||||
}
|
||||
return disks;
|
||||
}
|
165
lib/src/parser/mdstat_parser.rs
Normal file
165
lib/src/parser/mdstat_parser.rs
Normal file
@ -0,0 +1,165 @@
|
||||
use std::fs;
|
||||
use regex::{Regex};
|
||||
use rocket::serde::{Serialize};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct MdRaidSystem {
|
||||
raids: Vec<MdRaid>,
|
||||
supported_levels: Vec<String>
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct MdRaid {
|
||||
name: String,
|
||||
faulty: bool,
|
||||
level: String,
|
||||
devices: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn parse_mdstat() -> MdRaidSystem {
|
||||
let mdstat_path = "/proc/mdstat";
|
||||
return match fs::read_to_string(mdstat_path) {
|
||||
Ok(contents) => parse_mdstat_str(contents),
|
||||
Err(_) => MdRaidSystem{
|
||||
raids: vec![],
|
||||
supported_levels: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_mdstat_str(contents: String) -> MdRaidSystem {
|
||||
let mut md_raids = Vec::new();
|
||||
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r"([a-z_]{2,5}[0-9]{1,3})\s:\s(active|started|faulty)\s(raid[0-9])\s((?:\s?[a-z]{1,10}[0-9]{0,2}\[[0-9]])+)").unwrap();
|
||||
static ref RE_PERSONALITIES: Regex = Regex::new(r"Personalities\s:\s((?:\s?\[(?:raid[0-9]|linear|multipath|faulty)])+)").unwrap();
|
||||
}
|
||||
|
||||
for cap in RE.captures_iter(&contents) {
|
||||
if cap.len() == 5 {
|
||||
let devicesplit = cap[4].split(" ");
|
||||
let mut devices = Vec::new();
|
||||
for i in devicesplit {
|
||||
let i = format!("/dev/{i}");
|
||||
// remove brackets
|
||||
match i.split("[").next() {
|
||||
None => println!("no [ in device string!?"),
|
||||
Some(name) => devices.push(name.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
let raid = MdRaid {
|
||||
name: format!("/dev/{}", cap[1].to_string()),
|
||||
faulty: &cap[2] == "faulty",
|
||||
level: cap[3].to_string(),
|
||||
devices,
|
||||
};
|
||||
md_raids.push(raid);
|
||||
}
|
||||
}
|
||||
|
||||
let mut supp_levels: Vec<String> = Vec::new();
|
||||
match RE_PERSONALITIES.captures(&contents) {
|
||||
None => {}
|
||||
Some(res) => {
|
||||
if res.len() == 2 {
|
||||
let splits = res[1].split(" ");
|
||||
for i in splits {
|
||||
let mut chars = i.chars();
|
||||
// remove brackets
|
||||
chars.next();
|
||||
chars.next_back();
|
||||
supp_levels.push(chars.as_str().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MdRaidSystem{
|
||||
raids: md_raids,
|
||||
supported_levels: supp_levels
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rocket::serde::json::serde_json::json;
|
||||
// Note this useful idiom: importing names from outer (for mod tests) scope.
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
assert_eq!(json!(parse_mdstat_str(r#"
|
||||
Personalities :
|
||||
unused devices: <none>
|
||||
"#.to_string())).to_string(), r#"{"raids":[],"supported_levels":[]}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_special_name() {
|
||||
let exp = r#"{"raids":[{"devices":["/dev/sde1","/dev/sdf1","/dev/sdb1","/dev/sdd1","/dev/sdc1"],"faulty":false,"level":"raid5","name":"/dev/md_d0"}],"supported_levels":["raid1","raid6","raid5","raid4"]}"#;
|
||||
assert_eq!(json!(parse_mdstat_str(r#"
|
||||
Personalities : [raid1] [raid6] [raid5] [raid4]
|
||||
md_d0 : active raid5 sde1[0] sdf1[4] sdb1[5] sdd1[2] sdc1[1]
|
||||
1250241792 blocks super 1.2 level 5, 64k chunk, algorithm 2 [5/5] [UUUUU]
|
||||
bitmap: 0/10 pages [0KB], 16384KB chunk
|
||||
|
||||
unused devices: <none>
|
||||
"#.to_string())).to_string(), exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rebuilding_array() {
|
||||
let exp = r#"{"raids":[{"devices":["/dev/sdh1","/dev/sdg1","/dev/sdf1","/dev/sde1","/dev/sdd1","/dev/sdc1"],"faulty":false,"level":"raid5","name":"/dev/md127"}],"supported_levels":["raid1","raid6","raid5","raid4"]}"#;
|
||||
assert_eq!(json!(parse_mdstat_str(r#"
|
||||
Personalities : [raid1] [raid6] [raid5] [raid4]
|
||||
md127 : active raid5 sdh1[6] sdg1[4] sdf1[3] sde1[2] sdd1[1] sdc1[0]
|
||||
1464725760 blocks level 5, 64k chunk, algorithm 2 [6/5] [UUUUU_]
|
||||
[==>..................] recovery = 12.6% (37043392/292945152) finish=127.5min speed=33440K/sec
|
||||
|
||||
unused devices: <none>
|
||||
"#.to_string())).to_string(), exp);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test3() {
|
||||
let exp = r#"{"raids":[{"devices":["/dev/sda3","/dev/sdb3"],"faulty":false,"level":"raid0","name":"/dev/md2"},{"devices":["/dev/sda2","/dev/sdb2"],"faulty":false,"level":"raid1","name":"/dev/md1"},{"devices":["/dev/sda1","/dev/sdb1"],"faulty":false,"level":"raid1","name":"/dev/md0"}],"supported_levels":["linear","raid0","raid1"]}"#;
|
||||
assert_eq!(json!(parse_mdstat_str(r#"
|
||||
Personalities : [linear] [raid0] [raid1] [raid10] [raid6] [raid5] [raid4]
|
||||
md2 : active raid0 sda3[0] sdb3[1]
|
||||
4874137088 blocks super 1.2 64k chunks [2/2] [UU]
|
||||
|
||||
md1 : active raid1 sda2[0] sdb2[1]
|
||||
2097088 blocks [2/2] [UU]
|
||||
|
||||
md0 : active raid1 sda1[0] sdb1[1]
|
||||
2490176 blocks [2/2] [UU]
|
||||
"#.to_string())).to_string(), exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lots_of_devices() {
|
||||
let exp = r#"{"raids":[{"devices":["/dev/sdb2","/dev/sda2"],"faulty":false,"level":"raid1","name":"/dev/md1"},{"devices":["/dev/sdb3","/dev/sda3"],"faulty":false,"level":"raid1","name":"/dev/md2"},{"devices":["/dev/sdl1","/dev/sdk1","/dev/sdj1","/dev/sdi1","/dev/sdh1","/dev/sdg1","/dev/sdf1","/dev/sde1","/dev/sdd1","/dev/sdc1"],"faulty":false,"level":"raid5","name":"/dev/md3"},{"devices":["/dev/sdb1","/dev/sda1"],"faulty":false,"level":"raid1","name":"/dev/md0"}],"supported_levels":["raid1","raid6","raid5","raid4"]}"#;
|
||||
assert_eq!(json!(parse_mdstat_str(r#"
|
||||
Personalities : [raid1] [raid6] [raid5] [raid4]
|
||||
md1 : active raid1 sdb2[1] sda2[0]
|
||||
136448 blocks [2/2] [UU]
|
||||
|
||||
md2 : active raid1 sdb3[1] sda3[0]
|
||||
129596288 blocks [2/2] [UU]
|
||||
|
||||
md3 : active raid5 sdl1[9] sdk1[8] sdj1[7] sdi1[6] sdh1[5] sdg1[4] sdf1[3] sde1[2] sdd1[1] sdc1[0]
|
||||
1318680576 blocks level 5, 1024k chunk, algorithm 2 [10/10] [UUUUUUUUUU]
|
||||
|
||||
md0 : active raid1 sdb1[1] sda1[0]
|
||||
16787776 blocks [2/2] [UU]
|
||||
|
||||
unused devices: <none>
|
||||
"#.to_string())).to_string(), exp);
|
||||
}
|
||||
}
|
2
lib/src/parser/mod.rs
Normal file
2
lib/src/parser/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod mdstat_parser;
|
||||
pub mod lsblk_parser;
|
Reference in New Issue
Block a user