use crate::dpdk::CoreId;
use crate::net::{Ipv4Cidr, Ipv6Cidr, MacAddr};
use anyhow::Result;
use clap::{clap_app, crate_version};
use regex::Regex;
use serde::{de, Deserialize, Deserializer};
use std::fmt;
use std::fs;
use std::str::FromStr;
use std::time::Duration;
impl<'de> Deserialize<'de> for CoreId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let i = usize::deserialize(deserializer)?;
Ok(CoreId::new(i))
}
}
impl<'de> Deserialize<'de> for MacAddr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
MacAddr::from_str(&s).map_err(de::Error::custom)
}
}
impl<'de> Deserialize<'de> for Ipv4Cidr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ipv4Cidr::from_str(&s).map_err(de::Error::custom)
}
}
impl<'de> Deserialize<'de> for Ipv6Cidr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ipv6Cidr::from_str(&s).map_err(de::Error::custom)
}
}
pub fn duration_from_secs<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let secs = u64::deserialize(deserializer)?;
Ok(Duration::from_secs(secs))
}
pub fn duration_option_from_secs<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Wrapper(#[serde(deserialize_with = "duration_from_secs")] Duration);
let option = Option::deserialize(deserializer)?.and_then(|Wrapper(dur)| {
if dur.as_secs() > 0 {
Some(dur)
} else {
None
}
});
Ok(option)
}
#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeConfig {
pub app_name: String,
#[serde(default)]
pub secondary: bool,
#[serde(default)]
pub app_group: Option<String>,
pub master_core: CoreId,
#[serde(default)]
pub cores: Vec<CoreId>,
#[serde(default)]
pub mempool: MempoolConfig,
pub ports: Vec<PortConfig>,
#[serde(default)]
pub dpdk_args: Option<String>,
#[serde(default, deserialize_with = "duration_option_from_secs")]
pub duration: Option<Duration>,
}
impl RuntimeConfig {
pub(crate) fn all_cores(&self) -> Vec<CoreId> {
let mut cores = vec![];
cores.push(self.master_core);
cores.extend(self.cores.iter());
self.ports.iter().for_each(|port| {
cores.extend(port.cores.iter());
});
cores.sort();
cores.dedup();
cores
}
pub(crate) fn to_eal_args(&self) -> Vec<String> {
let mut eal_args = vec![];
eal_args.push(self.app_name.clone());
let proc_type = if self.secondary {
"secondary".to_owned()
} else {
"primary".to_owned()
};
eal_args.push("--proc-type".to_owned());
eal_args.push(proc_type);
let prefix = self.app_group.as_ref().unwrap_or(&self.app_name);
eal_args.push("--file-prefix".to_owned());
eal_args.push(prefix.clone());
let pcie = Regex::new(r"^\d{4}:\d{2}:\d{2}\.\d$").unwrap();
self.ports.iter().for_each(|port| {
if pcie.is_match(port.device.as_str()) {
eal_args.push("--pci-whitelist".to_owned());
eal_args.push(port.device.clone());
} else {
let vdev = if let Some(args) = &port.args {
format!("{},{}", port.device, args)
} else {
port.device.clone()
};
eal_args.push("--vdev".to_owned());
eal_args.push(vdev);
}
});
eal_args.push("--master-lcore".to_owned());
eal_args.push(self.master_core.raw().to_string());
eal_args.push("-l".to_owned());
eal_args.push(self.master_core.raw().to_string());
if let Some(args) = &self.dpdk_args {
eal_args.extend(args.split_ascii_whitespace().map(str::to_owned));
}
eal_args
}
pub(crate) fn num_knis(&self) -> usize {
self.ports.iter().filter(|p| p.kni).count()
}
}
impl fmt::Debug for RuntimeConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("runtime");
d.field("app_name", &self.app_name)
.field("secondary", &self.secondary)
.field(
"app_group",
self.app_group.as_ref().unwrap_or(&self.app_name),
)
.field("master_core", &self.master_core)
.field("cores", &self.cores)
.field("mempool", &self.mempool)
.field("ports", &self.ports);
if let Some(dpdk_args) = &self.dpdk_args {
d.field("dpdk_args", dpdk_args);
}
if let Some(duration) = &self.duration {
d.field("duration", duration);
}
d.finish()
}
}
#[derive(Clone, Deserialize)]
pub struct MempoolConfig {
#[serde(default = "default_capacity")]
pub capacity: usize,
#[serde(default = "default_cache_size")]
pub cache_size: usize,
}
fn default_capacity() -> usize {
65535
}
fn default_cache_size() -> usize {
0
}
impl Default for MempoolConfig {
fn default() -> Self {
MempoolConfig {
capacity: default_capacity(),
cache_size: default_cache_size(),
}
}
}
impl fmt::Debug for MempoolConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("mempool")
.field("capacity", &self.capacity)
.field("cache_size", &self.cache_size)
.finish()
}
}
#[derive(Clone, Deserialize)]
pub struct PortConfig {
pub name: String,
pub device: String,
#[serde(default)]
pub args: Option<String>,
pub cores: Vec<CoreId>,
#[serde(default = "default_port_rxd")]
pub rxd: usize,
#[serde(default = "default_port_txd")]
pub txd: usize,
#[serde(default)]
pub promiscuous: bool,
#[serde(default = "default_multicast_mode")]
pub multicast: bool,
#[serde(default)]
pub kni: bool,
}
fn default_port_rxd() -> usize {
128
}
fn default_port_txd() -> usize {
128
}
fn default_multicast_mode() -> bool {
true
}
impl fmt::Debug for PortConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("port");
d.field("name", &self.name);
d.field("device", &self.device);
if let Some(args) = &self.args {
d.field("args", args);
}
d.field("cores", &self.cores)
.field("rxd", &self.rxd)
.field("txd", &self.txd)
.field("promiscuous", &self.promiscuous)
.field("multicast", &self.multicast)
.field("kni", &self.kni)
.finish()
}
}
pub fn load_config() -> Result<RuntimeConfig> {
let matches = clap_app!(capsule =>
(version: crate_version!())
(@arg file: -f --file +required +takes_value "configuration file")
)
.get_matches();
let path = matches.value_of("file").unwrap();
let content = fs::read_to_string(path)?;
toml::from_str(&content).map_err(|err| err.into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_defaults() {
const CONFIG: &str = r#"
app_name = "myapp"
master_core = 0
[[ports]]
name = "eth0"
device = "0000:00:01.0"
cores = [2, 3]
"#;
let config: RuntimeConfig = toml::from_str(CONFIG).unwrap();
assert_eq!(false, config.secondary);
assert_eq!(None, config.app_group);
assert!(config.cores.is_empty());
assert_eq!(None, config.dpdk_args);
assert_eq!(default_capacity(), config.mempool.capacity);
assert_eq!(default_cache_size(), config.mempool.cache_size);
assert_eq!(None, config.ports[0].args);
assert_eq!(default_port_rxd(), config.ports[0].rxd);
assert_eq!(default_port_txd(), config.ports[0].txd);
assert_eq!(false, config.ports[0].promiscuous);
assert_eq!(default_multicast_mode(), config.ports[0].multicast);
assert_eq!(false, config.ports[0].kni);
}
#[test]
fn config_to_eal_args() {
const CONFIG: &str = r#"
app_name = "myapp"
secondary = false
app_group = "mygroup"
master_core = 0
cores = [1]
dpdk_args = "-v --log-level eal:8"
[mempool]
capacity = 255
cache_size = 16
[[ports]]
name = "eth0"
device = "0000:00:01.0"
cores = [2, 3]
rxd = 32
txd = 32
[[ports]]
name = "eth1"
device = "net_pcap0"
args = "rx=lo,tx=lo"
cores = [0, 4]
rxd = 32
txd = 32
"#;
let config: RuntimeConfig = toml::from_str(CONFIG).unwrap();
assert_eq!(
&[
"myapp",
"--proc-type",
"primary",
"--file-prefix",
"mygroup",
"--pci-whitelist",
"0000:00:01.0",
"--vdev",
"net_pcap0,rx=lo,tx=lo",
"--master-lcore",
"0",
"-l",
"0",
"-v",
"--log-level",
"eal:8"
],
config.to_eal_args().as_slice(),
)
}
}