Skip to content

Commit

Permalink
implement extending config by pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
Tagir Asadullin committed Jan 29, 2024
1 parent f972a59 commit 15df29f
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 92 deletions.
90 changes: 51 additions & 39 deletions bob-apps/bin/ccg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,14 @@ mod config_cluster_generator;
extern crate log;

use anyhow::{anyhow, Result as AnyResult};
use bob::{ClusterConfig};
use bob::ClusterConfig;
use bob_common::configs::{cluster::DistributionFunc};
use clap::{App, Arg, ArgMatches, SubCommand};
use config_cluster_generator::{
center::{check_expand_configs, get_new_disks, get_new_racks, Center},
utils::{
init_logger, ceil, read_config_from_file, write_to_file,
generate_range_samples, substitute_node, parse_address_pattern
},
pattern::{pattern_to_nodes},
utils::{ceil, init_logger, read_config_from_file, write_to_file},
};
use itertools::Itertools;

use bob_common::configs::cluster::{DistributionFunc, Node};
use bob_common::core_types::{DiskName, DiskPath};

#[tokio::main]
async fn main() {
Expand All @@ -36,6 +31,7 @@ fn try_main() -> AnyResult<()> {
("new", Some(matches)) => subcommand_new(matches),
("expand", Some(matches)) => subcommand_expand(matches),
("new-hw", Some(matches)) => subcommand_new_hw(matches),
("expand-hw", Some(matches)) => subcommand_expand_hw(matches),
_ => Err(anyhow!("incorrect arguments: ERR")),
}
}
Expand Down Expand Up @@ -91,6 +87,23 @@ fn subcommand_new_hw(matches: &ArgMatches) -> AnyResult<()> {
Ok(())
}

fn subcommand_expand_hw(matches: &ArgMatches) -> AnyResult<()> {
debug!("start config extending with new nodes by range pattern");
debug!("arguments: {:?}", matches);
let config = read_config_from_file(&get_input_config_name(matches))?;
let output = expand_hw_config(matches, config)?;
let output = serde_yaml::to_string(&output).expect("config serialization error");
debug!("config cluster extending: OK");
if let Some(name) = matches.value_of("output") {
write_to_file(output, name.to_owned());
debug!("output to file: OK");
} else {
println!("{}", output);
debug!("no file provided, stdout print: OK");
}
Ok(())
}

fn generate_config(matches: &ArgMatches, input: ClusterConfig) -> AnyResult<ClusterConfig> {
let replicas_count = get_replicas_count(matches)?;
let (total_vdisks, vdisks_per_disk) = get_vdisks_total_and_per_disk(matches)?;
Expand Down Expand Up @@ -126,6 +139,14 @@ fn generate_hw_config(matches: &ArgMatches) -> AnyResult<ClusterConfig> {
Ok(res)
}

fn expand_hw_config(matches: &ArgMatches, config: ClusterConfig) -> AnyResult<ClusterConfig> {
let pattern = get_pattern(matches)?;
let node_pattern = get_nodename(matches);
let res = pattern_expand(config, pattern, node_pattern)?;
debug!("expand hw config: OK");
Ok(res)
}

fn simple_expand(
config: ClusterConfig,
mut hardware_config: ClusterConfig,
Expand Down Expand Up @@ -187,38 +208,22 @@ fn simple_gen(
}

fn pattern_gen(pattern: String, node_pattern: String) -> AnyResult<ClusterConfig> {
let nodes: Vec<Node> = generate_range_samples(&pattern)
.iter()
.map(|key| {
let (ip, port, path) = parse_address_pattern(&key).unwrap();
((ip, port), path)
})
.group_by(|key| key.0)
.into_iter()
.enumerate()
.map(|(node_count, (ip_port, addresses))| {
let disks: Vec<DiskPath> = addresses
.enumerate()
.map(|(disk_count, (_, disk))| {
DiskPath::new(
DiskName::new(format!("disk{}", disk_count + 1).as_str()),
disk.as_str(),
)
})
.collect();

Node::new(
substitute_node(&node_pattern, ip_port.0, ip_port.1, node_count).clone(),
format!("{}:{}", ip_port.0, ip_port.1),
disks,
)
})
.collect();
let nodes = pattern_to_nodes(pattern, node_pattern)?;
let config = ClusterConfig::new(nodes, vec![], vec![], DistributionFunc::default());
debug!("pattern gen: OK [\n{:#?}\n]", config);
Ok(config)
}

fn pattern_expand(
mut config: ClusterConfig,
pattern: String,
node_pattern: String,
) -> AnyResult<ClusterConfig> {
let nodes = pattern_to_nodes(pattern, node_pattern)?;
config.disjoint_union_nodes(nodes);
Ok(config)
}

fn get_input_config_name(matches: &ArgMatches) -> String {
let name = matches
.value_of("input")
Expand Down Expand Up @@ -269,7 +274,7 @@ fn get_pattern(matches: &ArgMatches) -> AnyResult<String> {
Ok(name.to_owned())
} else {
debug!("get_pattern: No value");
Err(anyhow!("Failed no pattern present"))
Err(anyhow!("Failed: no pattern present"))
}
}

Expand Down Expand Up @@ -321,7 +326,7 @@ fn get_matches() -> ArgMatches<'static> {
.takes_value(true);
let nodename_config = Arg::with_name("nodename")
.short("n")
.default_value("Node_{ip}:{port}_{id}")
.default_value("Node_{ip}_{port}_{id}")
.help("Node name pattern for pattern generation")
.takes_value(true);
debug!("input arg: OK");
Expand All @@ -340,13 +345,20 @@ fn get_matches() -> ArgMatches<'static> {
.arg(replicas);

let subcommand_new_hw = SubCommand::with_name("new-hw")
.arg(output)
.arg(output.clone())
.arg(pattern_config.clone())
.arg(nodename_config.clone());

let subcommand_expand_hw = SubCommand::with_name("expand-hw")
.arg(input.clone())
.arg(output.clone())
.arg(pattern_config)
.arg(nodename_config);

App::new("Config Cluster Generator")
.subcommand(subcommand_expand)
.subcommand(subcommand_new)
.subcommand(subcommand_new_hw)
.subcommand(subcommand_expand_hw)
.get_matches()
}
1 change: 1 addition & 0 deletions bob-apps/bin/config_cluster_generator/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod center;
pub mod pattern;
pub mod utils;
134 changes: 134 additions & 0 deletions bob-apps/bin/config_cluster_generator/pattern.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use anyhow::{anyhow, Result as AnyResult};
use itertools::Itertools;
use bob_common::configs::cluster::Node;
use bob_common::core_types::{DiskName, DiskPath};

fn parse_address_pattern(pattern: &String) -> AnyResult<((String, u16), String)> {
debug!("Pattern to parse: {}", pattern);
let re: regex::Regex = regex::Regex::new(r"^([\w.]+):(\d+)(/[\w/]+)$").unwrap();

if let Some(captures) = re.captures(pattern) {
let ip = captures.get(1).unwrap().as_str().to_owned();
let port = captures.get(2).unwrap().as_str();
let path = captures.get(3).unwrap().as_str().to_owned();

let port: u16 = port
.parse()
.map_err(|_| anyhow!("Failed to parse port {}", port))?;
Ok(((ip, port), path))
} else {
Err(anyhow!("Failed to parse the pattern {}", pattern))
}
}

fn substitute_node(node_pattern: &str, ip: &str, port: u16, id: usize) -> String {
node_pattern
.replace("{ip}", &ip)
.replace("{port}", &port.to_string())
.replace("{id}", &id.to_string())
}

fn generate_range_samples(pattern: &str) -> impl Iterator<Item = String> {
let re = regex::Regex::new(r"\[(\d+)-(\d+)]").unwrap();

let ranges = re.captures_iter(pattern).map(|captures| {
let start: usize = captures[1].parse().unwrap();
let end: usize = captures[2].parse().unwrap();
start..=end
});

re.split(pattern)
.zip_longest(ranges)
.map(|x| {
if let itertools::EitherOrBoth::Both(part, range) = x {
range.map(|i| part.to_string() + &i.to_string()).collect()
} else {
vec![x.left().unwrap().to_string()]
}
})
.multi_cartesian_product()
.map(|x| x.concat())
.into_iter()
}

pub fn pattern_to_nodes(pattern: String, node_pattern: String) -> AnyResult<Vec<Node>> {
let mut err = Ok(());
let nodes = generate_range_samples(&pattern)
.map_while(|key| {
let result = parse_address_pattern(&key);
match result {
Ok(((ip, port), path)) => Some(((ip, port), path)),
Err(e) => { err = Err(e); None },
}
})
.group_by(|key| key.0.clone())
.into_iter()
.enumerate()
.map(|(node_count, (ip_port, addresses))| {
let disks: Vec<DiskPath> = addresses
.enumerate()
.map(|(disk_count, disk)| {
DiskPath::new(
DiskName::new(format!("disk{}", disk_count + 1).as_str()),
disk.1.as_str(),
)
})
.collect();

Node::new(
substitute_node(node_pattern.as_str(), ip_port.0.as_str(), ip_port.1, node_count + 1),
format!("{}:{}", ip_port.0, ip_port.1),
disks,
)
})
.collect();
err?;
Ok(nodes)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_generate_range_samples() {
let pattern = "abc[1-3]def";
let samples: Vec<String> = generate_range_samples(pattern).collect();
assert_eq!(samples, vec!["abc1def", "abc2def", "abc3def"]);

let pattern = "[0-1]a[1-2]b[2-3]";
let samples: Vec<String> = generate_range_samples(pattern).collect();
assert_eq!(
samples,
vec!["0a1b2", "0a1b3", "0a2b2", "0a2b3", "1a1b2", "1a1b3", "1a2b2", "1a2b3"]
);

let pattern = "a[5-6]b[2-3]c";
let samples: Vec<String> = generate_range_samples(pattern).collect();
assert_eq!(samples, vec!["a5b2c", "a5b3c", "a6b2c", "a6b3c"]);

let pattern = "[5-5]a[0-0]";
let samples: Vec<String> = generate_range_samples(pattern).collect();
assert_eq!(samples, vec!["5a0"]);
}

#[test]
fn test_parse_address_pattern() {
let pattern = String::from("127.0.0.1:8080/disk/path");
let result = parse_address_pattern(&pattern);
assert!(result.is_ok());

let ((ip, port), path) = result.unwrap();
assert_eq!(ip, "127.0.0.1");
assert_eq!(port, 8080);
assert_eq!(path, "/disk/path");

let pattern = String::from("127.0.0.1:65536/disk/path");
let result = parse_address_pattern(&pattern);
assert!(result.is_err());

let pattern = String::from("a,a:8080/disk/path");
let result = parse_address_pattern(&pattern);
assert!(result.is_err());
}
}
47 changes: 0 additions & 47 deletions bob-apps/bin/config_cluster_generator/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use env_logger::fmt::Color;
use log::{Level, LevelFilter};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::net::Ipv4Addr;

pub fn init_logger() {
env_logger::builder()
Expand Down Expand Up @@ -91,49 +90,3 @@ pub fn ceil(a: usize, b: usize) -> usize {
a / b
}
}

pub fn parse_address_pattern(pattern: &String) -> AnyResult<(Ipv4Addr, u16, String)> {
let re: regex::Regex = regex::Regex::new(r"^(\d+\.\d+\.\d+\.\d+):(\d+)(/.+)$").unwrap();

if let Some(captures) = re.captures(pattern) {
let ip = captures.get(1).unwrap().as_str();
let port = captures.get(2).unwrap().as_str();
let path = captures.get(3).unwrap().as_str().to_owned();

let ip: Ipv4Addr = ip.parse().map_err(|_| anyhow!("Failed to parse ip"))?;
let port: u16 = port.parse().map_err(|_| anyhow!("Failed to parse port"))?;
Ok((ip, port, path))
} else {
Err(anyhow!("Failed to match the pattern"))
}
}
pub fn substitute_node(node_pattern: &String, ip: Ipv4Addr, port: u16, id: usize) -> String {
let substituted = node_pattern
.replace("{ip}", &ip.to_string())
.replace("{port}", &port.to_string())
.replace("{id}", &id.to_string());
substituted
}

pub fn generate_range_samples(pattern: &String) -> Vec<String> {
let re = regex::Regex::new(r"\[(\d+)-(\d+)]").unwrap();
let ranges = re.captures_iter(pattern).map(|captures| {
let start: usize = captures[1].parse().unwrap();
let end: usize = captures[2].parse().unwrap();
(start, end)
});

let mut samples: Vec<String> = vec![pattern.to_string()];

for (start, end) in ranges {
samples = samples
.iter()
.flat_map(|template| {
(start..=end).map(move |i| {
template.replacen(&format!("[{}-{}]", start, end), &i.to_string(), 1)
})
})
.collect();
}
samples
}
Loading

0 comments on commit 15df29f

Please sign in to comment.