Skip to content

Commit 6b8f95c

Browse files
committed
feat(config): support multiple config format
1 parent e92e96e commit 6b8f95c

File tree

6 files changed

+209
-53
lines changed

6 files changed

+209
-53
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ serde = { version = "1.0", features = ["derive"] }
2020
futures = "0.3"
2121
pbr = "1.0.4"
2222
num_cpus = "1.13"
23+
ron = "0.8"
24+
serde_json = "1.0"
25+
serde_yaml = "0.9"

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Examples see `examples/simple.rs`.
1717

1818
To run the example, you can first execute the script `setup-ns3.sh` then execute `cargo run --example simple` in the root directory.
1919

20+
Currently support 4 config file formats: toml, ron, json, yaml. Example config files can see `config.toml` and `config.ron` under root. **Welcome contributions for any new config format**.
21+
2022
## Maintainer
2123

2224
[@BobAnkh](https://github.com/BobAnkh)

config.ron

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"exp-1": (
3+
policy: [
4+
1, 2
5+
],
6+
app_name: "ns3-tcp-bbr",
7+
sim_time: 5,
8+
),
9+
"exp-2": (
10+
policy: [
11+
1, 2, 3, 4
12+
],
13+
app_name: "ns3-tcp-cubic",
14+
sim_time: 2,
15+
),
16+
"exp-3": (
17+
policy: [
18+
1, 2, 3, 4
19+
],
20+
app_name: "ns3-tcp-copa",
21+
),
22+
"exp-4": (
23+
app_name: "ns3-tcp-creno",
24+
sim_time: 2,
25+
)
26+
}

examples/simple.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use ns3_parallel::{BuildCmd, BuildParam, Executor, ExecutorBuilder};
1+
use ns3_parallel::{executor::ConfigFormat, BuildCmd, BuildParam, Executor, ExecutorBuilder};
22
use serde::{Deserialize, Serialize};
33

44
// This is what you want to read from your configuration file.
@@ -62,9 +62,11 @@ impl BuildCmd for MyParam {
6262

6363
#[tokio::main]
6464
async fn main() {
65+
// ========== Use toml format as the config file ==========
6566
// Use ExecutorBuilder to build your executor.
6667
let mut exe: Executor<MyConfig, MyParam> = ExecutorBuilder::new()
6768
.config_path("config.toml")
69+
.config_format(ConfigFormat::Toml)
6870
.ns3_path("ns-allinone-3.33/ns-3.33/")
6971
.build()
7072
.unwrap();
@@ -81,4 +83,28 @@ async fn main() {
8183
println!("{}", task.stderr);
8284
}
8385
}
86+
87+
// ========== Use ron format as the config file ==========
88+
// Use ExecutorBuilder to build your executor.
89+
let mut exe: Executor<MyConfig, MyParam> = ExecutorBuilder::new()
90+
.config_path("config.ron")
91+
.config_format(ConfigFormat::Ron)
92+
.ns3_path("ns-allinone-3.33/ns-3.33/")
93+
.task_concurrent(4)
94+
.retry_limit(2)
95+
.build()
96+
.unwrap();
97+
98+
// Run your executor.
99+
let _ = exe.execute().await.unwrap();
100+
101+
// Collect your results.
102+
let outputs = exe.get_outputs().to_owned();
103+
104+
// Here I just print all the results, you can do whatever you want with them here.
105+
for (_, output) in outputs {
106+
for task in output {
107+
println!("{}", task.stderr);
108+
}
109+
}
84110
}

src/error.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,51 @@
11
//! Error
22
3+
use ron::error::SpannedError;
4+
use std::io;
5+
use tokio::task::JoinError;
6+
37
/// # Error
48
///
59
/// Error type for NS3 executor.
610
#[derive(Debug, Clone)]
711
pub enum Error {
812
InvalidConfig(String),
913
FileNotFound(String),
10-
InvalidTomlFormat(String),
14+
InvalidConfigFormat(String),
1115
ExecuteFail(String),
1216
BuildFail(String),
17+
IoError(String),
18+
JoinError(String),
19+
NotImplement(String),
1320
RetryLimitExceed,
1421
}
22+
23+
impl From<io::Error> for Error {
24+
fn from(e: io::Error) -> Self {
25+
Error::IoError(format!("{:?}", e))
26+
}
27+
}
28+
29+
impl From<JoinError> for Error {
30+
fn from(e: JoinError) -> Self {
31+
Error::JoinError(format!("{:?}", e))
32+
}
33+
}
34+
35+
impl From<SpannedError> for Error {
36+
fn from(e: SpannedError) -> Self {
37+
Error::InvalidConfigFormat(format!("{:?}", e))
38+
}
39+
}
40+
41+
impl From<serde_json::Error> for Error {
42+
fn from(e: serde_json::Error) -> Self {
43+
Error::InvalidConfigFormat(format!("{:?}", e))
44+
}
45+
}
46+
47+
impl From<serde_yaml::Error> for Error {
48+
fn from(e: serde_yaml::Error) -> Self {
49+
Error::InvalidConfigFormat(format!("{:?}", e))
50+
}
51+
}

src/executor.rs

Lines changed: 113 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use futures::stream::FuturesUnordered;
22
use futures::StreamExt;
33
use pbr::MultiBar;
4+
use serde::{Deserialize, Serialize};
45
use std::collections::HashMap;
5-
use std::path::Path;
6+
use std::path::{Path, PathBuf};
67
use std::process::Output;
78
use tokio::process::Command;
89
use tokio::task::spawn_blocking;
@@ -12,9 +13,21 @@ use crate::error::Error;
1213

1314
const DEFAULT_RETRY_LIMIT: i32 = 5;
1415

16+
/// Used for ExecutorBuilder.
17+
///
18+
/// Specify the format of your config file. Default to `ConfigFormat::Toml`
19+
#[derive(Debug, Serialize, Deserialize, Clone)]
20+
pub enum ConfigFormat {
21+
Ron,
22+
Json,
23+
Toml,
24+
Yaml,
25+
}
26+
1527
#[derive(Debug, Clone)]
1628
pub struct Executor<T: Default + BuildParam<P>, P: BuildCmd> {
1729
config_path: String,
30+
config_format: ConfigFormat,
1831
ns3_path: String,
1932
task_concurrent: usize,
2033
retry_limit: u32,
@@ -25,6 +38,7 @@ pub struct Executor<T: Default + BuildParam<P>, P: BuildCmd> {
2538
#[derive(Debug, Clone)]
2639
pub struct ExecutorBuilder {
2740
pub config_path: Option<String>,
41+
pub config_format: Option<ConfigFormat>,
2842
pub ns3_path: Option<String>,
2943
pub task_concurrent: Option<usize>,
3044
pub retry_limit: Option<u32>,
@@ -43,6 +57,10 @@ impl<T: Default + BuildParam<P>, P: BuildCmd> Executor<T, P> {
4357
&self.config_path
4458
}
4559

60+
pub fn get_config_format(&self) -> &ConfigFormat {
61+
&self.config_format
62+
}
63+
4664
pub fn get_ns3_path(&self) -> &str {
4765
&self.ns3_path
4866
}
@@ -121,6 +139,56 @@ impl<T: Default + BuildParam<P>, P: BuildCmd> Executor<T, P> {
121139
}
122140
}
123141

142+
fn check_config_file(config_path: &String, ext: &ConfigFormat) -> Result<PathBuf, Error> {
143+
let config_file_path = match Path::new(&config_path).canonicalize() {
144+
Ok(path) => path,
145+
Err(e) => {
146+
return Err(Error::FileNotFound(format!(
147+
"Can not locate config file: {:?}.",
148+
e
149+
)));
150+
}
151+
};
152+
match config_file_path.extension() {
153+
Some(t) => match ext {
154+
ConfigFormat::Ron => {
155+
if t != "ron" {
156+
return Err(Error::InvalidConfig(
157+
"Config file must be a ron file.".to_string(),
158+
));
159+
}
160+
}
161+
ConfigFormat::Json => {
162+
if t != "json" {
163+
return Err(Error::InvalidConfig(
164+
"Config file must be a json file.".to_string(),
165+
));
166+
}
167+
}
168+
ConfigFormat::Toml => {
169+
if t != "toml" {
170+
return Err(Error::InvalidConfig(
171+
"Config file must be a toml file.".to_string(),
172+
));
173+
}
174+
}
175+
ConfigFormat::Yaml => {
176+
if t != "yaml" {
177+
return Err(Error::InvalidConfig(
178+
"Config file must be a yaml file.".to_string(),
179+
));
180+
}
181+
}
182+
},
183+
None => {
184+
return Err(Error::InvalidConfig(
185+
"Config file must have a valid file extension.".to_string(),
186+
));
187+
}
188+
}
189+
Ok(config_file_path)
190+
}
191+
124192
impl Default for ExecutorBuilder {
125193
fn default() -> Self {
126194
Self::new()
@@ -131,6 +199,7 @@ impl ExecutorBuilder {
131199
pub fn new() -> Self {
132200
Self {
133201
config_path: None,
202+
config_format: None,
134203
ns3_path: None,
135204
task_concurrent: None,
136205
retry_limit: None,
@@ -142,6 +211,11 @@ impl ExecutorBuilder {
142211
self
143212
}
144213

214+
pub fn config_format(mut self, config_format: ConfigFormat) -> Self {
215+
self.config_format = Some(config_format);
216+
self
217+
}
218+
145219
pub fn ns3_path(mut self, ns3_path: &str) -> Self {
146220
self.ns3_path = Some(ns3_path.to_string());
147221
self
@@ -157,41 +231,21 @@ impl ExecutorBuilder {
157231
self
158232
}
159233

160-
pub fn build<'de, T: Default + BuildParam<P> + serde::de::Deserialize<'de>, P: BuildCmd>(
234+
pub fn build<T: Default + BuildParam<P> + serde::de::DeserializeOwned, P: BuildCmd>(
161235
self,
162236
) -> Result<Executor<T, P>, Error> {
163-
let mut config_path = self
164-
.config_path
165-
.unwrap_or_else(|| "config.toml".to_string());
237+
let config_format = self.config_format.unwrap_or(ConfigFormat::Toml);
238+
let mut config_path = self.config_path.unwrap_or_else(|| match &config_format {
239+
ConfigFormat::Ron => "config.ron".to_string(),
240+
ConfigFormat::Toml => "config.toml".to_string(),
241+
ConfigFormat::Json => "config.json".to_string(),
242+
ConfigFormat::Yaml => "config.yaml".to_string(),
243+
});
166244
let mut ns3_path = self.ns3_path.unwrap_or_else(|| "/".to_string());
167245
let task_concurrent = self.task_concurrent.unwrap_or_else(num_cpus::get);
168-
let retry_limit = self
169-
.retry_limit
170-
.unwrap_or_else(|| DEFAULT_RETRY_LIMIT as u32);
246+
let retry_limit = self.retry_limit.unwrap_or(DEFAULT_RETRY_LIMIT as u32);
171247
// Check config file
172-
let config_file_path = match Path::new(&config_path).canonicalize() {
173-
Ok(path) => path,
174-
Err(e) => {
175-
return Err(Error::FileNotFound(format!(
176-
"Can not locate config file: {:?}.",
177-
e
178-
)));
179-
}
180-
};
181-
match config_file_path.extension() {
182-
Some(t) => {
183-
if t != "toml" {
184-
return Err(Error::InvalidConfig(
185-
"Config file must be a toml file.".to_string(),
186-
));
187-
}
188-
}
189-
None => {
190-
return Err(Error::InvalidConfig(
191-
"Config file must have a valid file extension.".to_string(),
192-
));
193-
}
194-
}
248+
let config_file_path = check_config_file(&config_path, &config_format)?;
195249
config_path = config_file_path.display().to_string();
196250
// check ns3 directory
197251
let ns3_dir_path = match Path::new(&ns3_path).join("waf").canonicalize() {
@@ -204,36 +258,44 @@ impl ExecutorBuilder {
204258
}
205259
};
206260
ns3_path = ns3_dir_path.parent().unwrap().display().to_string();
207-
let configuration = match std::fs::read_to_string(config_file_path) {
208-
Ok(c) => c,
209-
Err(e) => {
210-
return Err(Error::InvalidConfig(format!(
211-
"Config file cannot be opened at {}. Err: {:?}.",
212-
&config_path, e
213-
)))
261+
let configs: HashMap<String, T> = match config_format {
262+
ConfigFormat::Ron => {
263+
let f = std::fs::File::open(config_file_path)?;
264+
ron::de::from_reader(f)?
214265
}
215-
};
216-
let configs: toml::value::Table = match toml::from_str(&configuration) {
217-
Ok(t) => t,
218-
Err(e) => {
219-
return Err(Error::InvalidTomlFormat(format!(
220-
"Config file is not a valid toml file. Err: {:?}.",
221-
e
222-
)));
266+
ConfigFormat::Json => {
267+
let f = std::fs::File::open(config_file_path)?;
268+
serde_json::from_reader(f)?
269+
}
270+
ConfigFormat::Yaml => {
271+
let f = std::fs::File::open(config_file_path)?;
272+
serde_yaml::from_reader(f)?
273+
}
274+
ConfigFormat::Toml => {
275+
let configuration = std::fs::read_to_string(config_file_path)?;
276+
let configs: toml::value::Table = match toml::from_str(&configuration) {
277+
Ok(t) => t,
278+
Err(e) => {
279+
return Err(Error::InvalidConfigFormat(format!(
280+
"Config file is not a valid toml file. Err: {:?}.",
281+
e
282+
)));
283+
}
284+
};
285+
configs
286+
.iter()
287+
.map(|(k, v)| (k.to_owned(), v.to_owned().try_into().unwrap()))
288+
.collect()
223289
}
224290
};
225-
226-
let configs: HashMap<String, T> = configs
227-
.iter()
228-
.map(|(k, v)| (k.to_owned(), v.to_owned().try_into().unwrap()))
229-
.collect();
230291
let outputs: HashMap<String, Vec<Task<P>>> = configs
231292
.iter()
232293
.map(|(k, _)| (k.to_owned(), vec![]))
233294
.collect();
234295

235296
Ok(Executor {
236297
config_path,
298+
config_format,
237299
ns3_path,
238300
task_concurrent,
239301
retry_limit,

0 commit comments

Comments
 (0)