This commit is contained in:
lukas-heiligenbrunner 2022-12-04 15:02:07 +01:00
commit 3b06a130b2
6 changed files with 1748 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

17
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,17 @@
stages:
- build
rust-latest:
stage: build
image: rust:latest
script:
- cargo build --verbose
- cargo test --verbose
rust-nightly:
stage: build
image: rustlang/rust:nightly
script:
- cargo build --verbose
- cargo test --verbose
allow_failure: true

1536
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[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"

22
src/main.rs Normal file
View File

@ -0,0 +1,22 @@
mod mdstat_parser;
#[macro_use]
extern crate rocket;
use rocket::serde::json::Json;
use crate::mdstat_parser::{MdRaidSystem, parse_mdstat};
#[get("/raiddevices")]
fn get_raid_devices() -> Json<MdRaidSystem> {
Json(parse_mdstat())
}
#[rocket::main]
async fn main() -> Result<(), rocket::Error> {
let _rocket = rocket::build()
.mount("/api", routes![get_raid_devices])
.launch()
.await?;
Ok(())
}

162
src/mdstat_parser.rs Normal file
View File

@ -0,0 +1,162 @@
use std::fs;
use regex::{Regex};
use rocket::serde::{Serialize};
#[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";
let mdstat_path = "testmdstat.txt";
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();
// todo avoid recompiling regex every time
let re = 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();
let re_personalities = 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 {
// todo error handling
continue
} else {
let devicesplit = cap[4].split(" ");
let mut devices = Vec::new();
for i in devicesplit {
// remove brackets
match i.split("[").next() {
None => println!("no [ in device string!?"),
Some(name) => devices.push(name.to_string())
}
}
let raid = MdRaid {
name: 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() {
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(), r#"{"raids":[{"devices":["sde1","sdf1","sdb1","sdd1","sdc1"],"faulty":false,"level":"raid5","name":"md_d0"}],"supported_levels":["raid1","raid6","raid5","raid4"]}"#);
}
#[test]
fn rebuilding_array() {
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(), r#"{"raids":[{"devices":["sdh1","sdg1","sdf1","sde1","sdd1","sdc1"],"faulty":false,"level":"raid5","name":"md127"}],"supported_levels":["raid1","raid6","raid5","raid4"]}"#);
}
#[test]
fn test3() {
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(), r#"{"raids":[{"devices":["sda3","sdb3"],"faulty":false,"level":"raid0","name":"md2"},{"devices":["sda2","sdb2"],"faulty":false,"level":"raid1","name":"md1"},{"devices":["sda1","sdb1"],"faulty":false,"level":"raid1","name":"md0"}],"supported_levels":["linear","raid0","raid1"]}"#);
}
#[test]
fn lots_of_devices() {
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(), r#"{"raids":[{"devices":["sdb2","sda2"],"faulty":false,"level":"raid1","name":"md1"},{"devices":["sdb3","sda3"],"faulty":false,"level":"raid1","name":"md2"},{"devices":["sdl1","sdk1","sdj1","sdi1","sdh1","sdg1","sdf1","sde1","sdd1","sdc1"],"faulty":false,"level":"raid5","name":"md3"},{"devices":["sdb1","sda1"],"faulty":false,"level":"raid1","name":"md0"}],"supported_levels":["raid1","raid6","raid5","raid4"]}"#);
}
}