use crate::{
consolidation_function::ConsolidationFunction,
errors::RRDCachedClientError,
sanitisation::{check_data_source_name, check_rrd_path},
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CreateDataSourceType {
Gauge,
Counter,
DCounter,
Derive,
DDerive,
Absolute,
}
impl CreateDataSourceType {
pub fn to_str(self) -> &'static str {
match self {
CreateDataSourceType::Gauge => "GAUGE",
CreateDataSourceType::Counter => "COUNTER",
CreateDataSourceType::DCounter => "DCOUNTER",
CreateDataSourceType::Derive => "DERIVE",
CreateDataSourceType::DDerive => "DDERIVE",
CreateDataSourceType::Absolute => "ABSOLUTE",
}
}
}
#[derive(Debug)]
pub struct CreateDataSource {
pub name: String,
pub minimum: Option<f64>,
pub maximum: Option<f64>,
pub heartbeat: i64,
pub serie_type: CreateDataSourceType,
}
impl CreateDataSource {
pub fn validate(&self) -> Result<(), RRDCachedClientError> {
if self.heartbeat <= 0 {
return Err(RRDCachedClientError::InvalidCreateDataSerie(
"heartbeat must be greater than 0".to_string(),
));
}
if let Some(minimum) = self.minimum {
if let Some(maximum) = self.maximum {
if maximum <= minimum {
return Err(RRDCachedClientError::InvalidCreateDataSerie(
"maximum must be greater than to minimum".to_string(),
));
}
}
}
check_data_source_name(&self.name)?;
Ok(())
}
pub fn to_str(&self) -> String {
format!(
"DS:{}:{}:{}:{}:{}",
self.name,
self.serie_type.to_str(),
self.heartbeat,
match self.minimum {
Some(minimum) => minimum.to_string(),
None => "U".to_string(),
},
match self.maximum {
Some(maximum) => maximum.to_string(),
None => "U".to_string(),
}
)
}
}
#[derive(Debug)]
pub struct CreateRoundRobinArchive {
pub consolidation_function: ConsolidationFunction,
pub xfiles_factor: f64,
pub steps: i64,
pub rows: i64,
}
impl CreateRoundRobinArchive {
pub fn validate(&self) -> Result<(), RRDCachedClientError> {
if self.xfiles_factor < 0.0 || self.xfiles_factor > 1.0 {
return Err(RRDCachedClientError::InvalidCreateDataSerie(
"xfiles_factor must be between 0 and 1".to_string(),
));
}
if self.steps <= 0 {
return Err(RRDCachedClientError::InvalidCreateDataSerie(
"steps must be greater than 0".to_string(),
));
}
if self.rows <= 0 {
return Err(RRDCachedClientError::InvalidCreateDataSerie(
"rows must be greater than 0".to_string(),
));
}
Ok(())
}
pub fn to_str(&self) -> String {
format!(
"RRA:{}:{}:{}:{}",
self.consolidation_function.to_str(),
self.xfiles_factor,
self.steps,
self.rows
)
}
}
#[derive(Debug)]
pub struct CreateArguments {
pub path: String,
pub data_sources: Vec<CreateDataSource>,
pub round_robin_archives: Vec<CreateRoundRobinArchive>,
pub start_timestamp: u64,
pub step_seconds: u64,
}
impl CreateArguments {
pub fn validate(&self) -> Result<(), RRDCachedClientError> {
if self.data_sources.is_empty() {
return Err(RRDCachedClientError::InvalidCreateDataSerie(
"at least one data serie is required".to_string(),
));
}
if self.round_robin_archives.is_empty() {
return Err(RRDCachedClientError::InvalidCreateDataSerie(
"at least one round robin archive is required".to_string(),
));
}
for data_serie in &self.data_sources {
data_serie.validate()?;
}
for rr_archive in &self.round_robin_archives {
rr_archive.validate()?;
}
check_rrd_path(&self.path)?;
Ok(())
}
pub fn to_str(&self) -> String {
let mut result = format!(
"{}.rrd -s {} -b {}",
self.path, self.step_seconds, self.start_timestamp
);
for data_serie in &self.data_sources {
result.push(' ');
result.push_str(&data_serie.to_str());
}
for rr_archive in &self.round_robin_archives {
result.push(' ');
result.push_str(&rr_archive.to_str());
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_data_source_type_to_str() {
assert_eq!(CreateDataSourceType::Gauge.to_str(), "GAUGE");
assert_eq!(CreateDataSourceType::Counter.to_str(), "COUNTER");
assert_eq!(CreateDataSourceType::DCounter.to_str(), "DCOUNTER");
assert_eq!(CreateDataSourceType::Derive.to_str(), "DERIVE");
assert_eq!(CreateDataSourceType::DDerive.to_str(), "DDERIVE");
assert_eq!(CreateDataSourceType::Absolute.to_str(), "ABSOLUTE");
}
#[test]
fn test_create_data_source_validate() {
let valid_ds = CreateDataSource {
name: "valid_name_1".to_string(),
minimum: Some(0.0),
maximum: Some(100.0),
heartbeat: 300,
serie_type: CreateDataSourceType::Gauge,
};
assert!(valid_ds.validate().is_ok());
let invalid_ds_name = CreateDataSource {
name: "Invalid Name!".to_string(), ..valid_ds
};
assert!(invalid_ds_name.validate().is_err());
let invalid_ds_heartbeat = CreateDataSource {
heartbeat: -1, name: "valid_name_2".to_string(),
..valid_ds
};
assert!(invalid_ds_heartbeat.validate().is_err());
let invalid_ds_min_max = CreateDataSource {
minimum: Some(100.0),
maximum: Some(50.0), name: "valid_name_3".to_string(),
..valid_ds
};
assert!(invalid_ds_min_max.validate().is_err());
let invalid_ds_max = CreateDataSource {
minimum: Some(100.0),
maximum: Some(0.0),
name: "valid_name_5".to_string(),
..valid_ds
};
assert!(invalid_ds_max.validate().is_err());
let valid_ds_max = CreateDataSource {
maximum: Some(100.0),
name: "valid_name_6".to_string(),
..valid_ds
};
assert!(valid_ds_max.validate().is_ok());
let valid_ds_min = CreateDataSource {
minimum: Some(-100.0),
name: "valid_name_7".to_string(),
..valid_ds
};
assert!(valid_ds_min.validate().is_ok());
}
#[test]
fn test_create_data_source_to_str() {
let ds = CreateDataSource {
name: "test_ds".to_string(),
minimum: Some(10.0),
maximum: Some(100.0),
heartbeat: 600,
serie_type: CreateDataSourceType::Gauge,
};
assert_eq!(ds.to_str(), "DS:test_ds:GAUGE:600:10:100");
let ds = CreateDataSource {
name: "test_ds".to_string(),
minimum: None,
maximum: None,
heartbeat: 600,
serie_type: CreateDataSourceType::Gauge,
};
assert_eq!(ds.to_str(), "DS:test_ds:GAUGE:600:U:U");
}
#[test]
fn test_create_round_robin_archive_validate() {
let valid_rra = CreateRoundRobinArchive {
consolidation_function: ConsolidationFunction::Average,
xfiles_factor: 0.5,
steps: 1,
rows: 100,
};
assert!(valid_rra.validate().is_ok());
let invalid_rra_xff = CreateRoundRobinArchive {
xfiles_factor: -0.1, ..valid_rra
};
assert!(invalid_rra_xff.validate().is_err());
let invalid_rra_steps = CreateRoundRobinArchive {
steps: 0, ..valid_rra
};
assert!(invalid_rra_steps.validate().is_err());
let invalid_rra_rows = CreateRoundRobinArchive {
rows: -100, ..valid_rra
};
assert!(invalid_rra_rows.validate().is_err());
}
#[test]
fn test_create_round_robin_archive_to_str() {
let rra = CreateRoundRobinArchive {
consolidation_function: ConsolidationFunction::Max,
xfiles_factor: 0.5,
steps: 1,
rows: 100,
};
assert_eq!(rra.to_str(), "RRA:MAX:0.5:1:100");
}
#[test]
fn test_create_arguments_validate() {
let valid_args = CreateArguments {
path: "valid_path".to_string(),
data_sources: vec![CreateDataSource {
name: "ds1".to_string(),
minimum: Some(0.0),
maximum: Some(100.0),
heartbeat: 300,
serie_type: CreateDataSourceType::Gauge,
}],
round_robin_archives: vec![CreateRoundRobinArchive {
consolidation_function: ConsolidationFunction::Average,
xfiles_factor: 0.5,
steps: 1,
rows: 100,
}],
start_timestamp: 1609459200,
step_seconds: 300,
};
assert!(valid_args.validate().is_ok());
let invalid_args_no_ds = CreateArguments {
data_sources: vec![],
path: "valid_path".to_string(),
..valid_args
};
assert!(invalid_args_no_ds.validate().is_err());
let invalid_args_no_rra = CreateArguments {
round_robin_archives: vec![],
path: "valid_path".to_string(),
..valid_args
};
assert!(invalid_args_no_rra.validate().is_err());
}
#[test]
fn test_create_arguments_to_str() {
let args = CreateArguments {
path: "test_path".to_string(),
data_sources: vec![CreateDataSource {
name: "ds1".to_string(),
minimum: Some(0.0),
maximum: Some(100.0),
heartbeat: 300,
serie_type: CreateDataSourceType::Gauge,
}],
round_robin_archives: vec![CreateRoundRobinArchive {
consolidation_function: ConsolidationFunction::Average,
xfiles_factor: 0.5,
steps: 1,
rows: 100,
}],
start_timestamp: 1609459200,
step_seconds: 300,
};
let expected_str =
"test_path.rrd -s 300 -b 1609459200 DS:ds1:GAUGE:300:0:100 RRA:AVERAGE:0.5:1:100";
assert_eq!(args.to_str(), expected_str);
}
}