Skip to content
Snippets Groups Projects
adapters.rs 18.4 KiB
Newer Older
use std::fs;
use std::collections::{HashMap, hash_map};
use std::io::Error as IOError;
use tera::{Context, Tera};
use std::process::Command;
Francesco's avatar
Francesco committed
use crate::constants::*;
#[cfg(not(tarpaulin_include))]
fn split_path(path: &str) -> String {
    let split = path.split('/');
Francesco WITZ's avatar
Francesco WITZ committed
    let split_vec = split.collect::<Vec<&str>>();
    split_vec[split_vec.len()-1].to_string()
}

#[cfg(not(tarpaulin_include))]
pub fn open_files(dir_path: &Path) -> Result<Vec<String>, IOError> {
    let files = fs::read_dir(dir_path).unwrap();
    let mut conf_files = Vec::new();
    for file in files {
        let conf_file = match fs::read_to_string(file.unwrap().path()) {
            Ok(file) => file,
            Err(err) => return Err(err)
        };
        conf_files.push(conf_file);
#[cfg(not(tarpaulin_include))]
pub fn parse_yaml(conf_file: String) -> Result<JobConfCommand, serde_yaml::Error> {
    let conf: JobConfCommand = match serde_yaml::from_str(&conf_file) {
        Ok(file) => file,
        Err(err) => return Err(err)
Francesco WITZ's avatar
Francesco WITZ committed
pub fn check_syntax(conf: &JobConfCommand) -> Result<&JobConfCommand, JobConfError> {
    for param in &conf.parameters {
        match &param.default_value {
            Some(val) => {
                match param.value_type {
                    JobConfValueType::String => (),
                    JobConfValueType::Boolean => {
                        if val.parse::<bool>().is_err() {
                            eprintln!("Unable to cast default-value to string");
                            return Err(JobConfError::ParseBoolError);
                        }
                    }
                    JobConfValueType::Integer => {
                        if val.parse::<i32>().is_err() {
                            eprintln!("Unable to cast default-value to string");
                            return Err(JobConfError::ParseIntError);
                        }
                    }
                    JobConfValueType::Float => {
                        if val.parse::<f64>().is_err() {
                            eprintln!("Unable to cast default-value to string");
                            return Err(JobConfError::ParseFloatError);
                    JobConfValueType::None => ()
        if param.named && param.parameter_name.is_none(){
            eprintln!("Missing expected parameter name (named parameter is set to true)");
            return Err(JobConfError::MissingValue);
#[cfg(not(tarpaulin_include))]
pub fn load_conf_files(root_dir: &Path) -> Result<Configurations, AdapterError> {
Francesco WITZ's avatar
Francesco WITZ committed
    let mut parsed_configurations = Configurations::new();
    let softwares_dir = match fs::read_dir(root_dir) {
        Ok(result) => result,
        Err(err) => return Err(AdapterError {kind: AdapterErrorKind::IOError, error:Some(Box::new(err))}),
    };
    for dir in softwares_dir {
        let dirname = dir.unwrap();
        let version_dir = match fs::read_dir(&dirname.path()) {
Francesco WITZ's avatar
Francesco WITZ committed
            Ok(result) => result,
            Err(err) => return Err(AdapterError {kind: AdapterErrorKind::IOError, error:Some(Box::new(err))}),
        };
        for vdir in version_dir {
            let vdirname = vdir.unwrap();
            let job_conf = match fs::read_dir(&vdirname.path()) {
Francesco WITZ's avatar
Francesco WITZ committed
                Ok(result) => result,
                Err(err) => return Err(AdapterError {kind: AdapterErrorKind::IOError, error:Some(Box::new(err))}),
            };
            for conf in job_conf {
                let filename = conf.unwrap();
                let content = match fs::read_to_string(&filename.path()) {
Francesco WITZ's avatar
Francesco WITZ committed
                    Ok(file) => file,
                    Err(err) => return Err(AdapterError {kind: AdapterErrorKind::IOError, error:Some(Box::new(err))}),
                };
                let parsed_content = match parse_yaml(content) {
                    Ok(parsed) => parsed,
                    Err(err) => return Err(AdapterError {kind: AdapterErrorKind::ParseError, error:Some(Box::new(err))}),
                };
                match check_syntax(&parsed_content) {
Francesco WITZ's avatar
Francesco WITZ committed
                    Ok(result) => {
                        let software_name = match &dirname.path().to_str() {
                            Some(val) => split_path(val),
Francesco WITZ's avatar
Francesco WITZ committed
                            None => String::from(""),
                        };
                        let version_name = match &vdirname.path().to_str() {
                            Some(val) => split_path(val),
                            None => String::from(""),
                        };
                        let job_name = match &filename.path().to_str() {
                            Some(val) => {
                                let splited = split_path(val);
                                splited.replace(".yaml", "")
                            },
                            None => String::from(""),
                        }; 
                        if let hash_map::Entry::Vacant(e) = parsed_configurations.entry(software_name.clone()) {
                            let mut job_map = HashMap::new();
                            job_map.insert(job_name, result.clone());
                            let mut version_map = HashMap::new();
                            version_map.insert(version_name, job_map);
                            e.insert(version_map);
                            let version_map = parsed_configurations.get_mut(&software_name.to_string()).unwrap();
                            if version_map.contains_key(&version_name.to_string()) {
                                let job_map = version_map.get_mut(&version_name.to_string()).unwrap();
                                job_map.insert(job_name, result.clone());
                                let mut job_map = HashMap::new();
                                job_map.insert(job_name, result.clone());
                                version_map.insert(version_name, job_map);
Francesco WITZ's avatar
Francesco WITZ committed
                    },
                    Err(err) => return Err(AdapterError {kind: AdapterErrorKind::ParseError, error:Some(Box::new(err))}),
                };
            }
        }
    }
    Ok(parsed_configurations)
pub fn get_conf_by_uuid(id: Uuid, configurations: Configurations) -> Option<JobConfCommand> {
    for (_, version_map) in configurations.into_iter() {
        for (_, job_map) in version_map.into_iter() {
            for (_, conf) in job_map.into_iter() {
                if conf.uuid == id {
                    return Some(conf);
                }
            }
        }
    }
Francesco WITZ's avatar
Francesco WITZ committed
    None
pub fn generate_command(cmd_params: HashMap<String, String>, conf: JobConfCommand) -> Result<String, CommandError> {
    let mut cmd = String::new();
    cmd.push_str(&(conf.bin));
    for param in &conf.parameters {
        if param.required && !cmd_params.contains_key(&param.name) {
            return Err(CommandError::MissingRequiredParam);
        } else if let Some(val) = cmd_params.get(&param.name) {
            if param.named {
                cmd.push_str(format!(" {} {}", &param.parameter_name.clone().unwrap(), &val).as_str());
            } else {
                cmd.push_str(format!(" {}", &val).as_str());
            }
#[cfg(not(tarpaulin_include))]
Francesco WITZ's avatar
Francesco WITZ committed
pub fn generate_sbatch(job_params: HashMap<String, String>, cmd: String) -> Result<String, tera::Error>{
    let tera = match Tera::new("template/*") {
Francesco's avatar
Francesco committed
        Ok(t) => t,
        Err(err) => return Err(err)
    };
    let mut context = Context::new();
Francesco's avatar
Francesco committed
    for (param, value) in job_params {
        context.insert(param, &value);
Francesco's avatar
Francesco committed
    }
    context.insert(String::from("command"), &cmd.as_str());
Francesco WITZ's avatar
Francesco WITZ committed
    let content = match tera.render("template.sbatch", &context) {
        Ok(result) => result,
        Err(err) => {
            return Err(err)
        }

#[cfg(not(tarpaulin_include))]
pub fn run_sbatch(sbatch_path: &Path) -> Result<String, IOError> {
    let job_id = match Command::new("sbatch").arg(sbatch_path).output() {
        Ok(result) => {
Francesco WITZ's avatar
Francesco WITZ committed
            let stdout_result = String::from_utf8_lossy(&result.stdout);
            let split = stdout_result.split(' ');
            let split_vec = split.collect::<Vec<&str>>();
            let job_id = split_vec[split_vec.len()-1];
        },
        Err(err) => return Err(err)
    };
#[cfg(not(tarpaulin_include))]
pub fn get_slurm_job_infos(slurm_job_id: String) -> Result<SlurmJobInfos, IOError> {
    let job_infos = match Command::new("scontrol").arg("show").arg("job").arg(slurm_job_id).output() {
        Ok(result) => {
            let stdout_result = String::from_utf8_lossy(&result.stdout);
            let split = stdout_result.split(' ');
            let split_vec = split.collect::<Vec<&str>>();
            let keys = &["JobState", "RunTime", "SubmitTime", "StartTime", "EndTime"];
Francesco WITZ's avatar
Francesco WITZ committed
            let trim_split_vec = split_vec.clone().into_iter().filter(|e| e.to_string().ne("")).filter(|e| keys.iter().any(|k| k.eq(e))).collect::<Vec<&str>>();
            SlurmJobInfos {
                run_time: String::from(trim_split_vec[1].split('=').collect::<Vec<&str>>()[1]),
                submit_time: String::from(trim_split_vec[2].split('=').collect::<Vec<&str>>()[1]),
                start_time: String::from(trim_split_vec[3].split('=').collect::<Vec<&str>>()[1]),
                end_time: String::from(trim_split_vec[4].split('=').collect::<Vec<&str>>()[1]),
                status: String::from(trim_split_vec[0].split('=').collect::<Vec<&str>>()[1])
            }
        },
        Err(err) => return Err(err)
    };
    Ok(job_infos)
}


    use std::collections::HashMap;
Francesco's avatar
Francesco committed
    use super::check_syntax;
    use super::get_conf_by_uuid;
Francesco's avatar
Francesco committed
    use super::generate_command;

    use crate::constants::*;

    #[test]
    fn test_correct_conf() {
        let conf_expected = JobConfCommand {
            uuid: Uuid::parse_str("c5e9d770-73ea-43f5-84bb-cd612c0c7b1d").unwrap(),
            parameters: vec![JobConfCommandParam {
                name: String::from("to_print"),
                value_type: JobConfValueType::String,
                default_value: Some(String::from("")),
                comment: Some(String::from("Value to print on the cli")),
                required: true,
                named: false,
                parameter_name: None
            }]
        };
Francesco WITZ's avatar
Francesco WITZ committed
        let conf = check_syntax(&conf_expected);
        assert_eq!(conf.unwrap(), &conf_expected);
    }

    #[test]
    fn test_missing_name() {
        let conf_to_test = JobConfCommand {
            uuid: Uuid::parse_str("c5e9d770-73ea-43f5-84bb-cd612c0c7b1d").unwrap(),
            bin: String::from("echo"), 
            parameters: vec![JobConfCommandParam {
                name: String::from("to_print"),
                value_type: JobConfValueType::String,
                default_value: Some(String::from("")),
                comment: Some(String::from("Value to print on the cli")),
                required: true,
                named: true,
                parameter_name: None
            }]
        };
Francesco WITZ's avatar
Francesco WITZ committed
        let conf = check_syntax(&conf_to_test);
        assert_eq!(conf.unwrap_err(), JobConfError::MissingValue);
    fn test_wrong_integer_cast() {
        let conf_to_test = JobConfCommand {
            uuid: Uuid::parse_str("c5e9d770-73ea-43f5-84bb-cd612c0c7b1d").unwrap(),
            bin: String::from("echo"), 
            parameters: vec![JobConfCommandParam {
                name: String::from("to_print"),
                value_type: JobConfValueType::Integer,
                default_value: Some(String::from("test")),
                comment: Some(String::from("Value to print on the cli")),
                required: true,
                named: true,
                parameter_name: None
            }]
        };
Francesco WITZ's avatar
Francesco WITZ committed
        let conf = check_syntax(&conf_to_test);
        assert_eq!(conf.unwrap_err(), JobConfError::ParseIntError);
    #[test]
    fn test_wrong_boolean_cast() {
        let conf_to_test = JobConfCommand {
            uuid: Uuid::parse_str("c5e9d770-73ea-43f5-84bb-cd612c0c7b1d").unwrap(),
            bin: String::from("echo"), 
            parameters: vec![JobConfCommandParam {
                name: String::from("to_print"),
                value_type: JobConfValueType::Boolean,
                default_value: Some(String::from("test")),
                comment: Some(String::from("Value to print on the cli")),
                required: true,
                named: true,
                parameter_name: None
            }]
        };
        let conf = check_syntax(&conf_to_test);
        assert_eq!(conf.unwrap_err(), JobConfError::ParseBoolError);
    }

    #[test]
    fn test_wrong_float_cast() {
        let conf_to_test = JobConfCommand {
            uuid: Uuid::parse_str("c5e9d770-73ea-43f5-84bb-cd612c0c7b1d").unwrap(),
            bin: String::from("echo"), 
            parameters: vec![JobConfCommandParam {
                name: String::from("to_print"),
                value_type: JobConfValueType::Float,
                default_value: Some(String::from("test")),
                comment: Some(String::from("Value to print on the cli")),
                required: true,
                named: true,
                parameter_name: None
            }]
        };
        let conf = check_syntax(&conf_to_test);
        assert_eq!(conf.unwrap_err(), JobConfError::ParseFloatError);
    }

    #[test]
    fn test_missing_required_param() {
        let conf = JobConfCommand {
            uuid: Uuid::parse_str("c5e9d770-73ea-43f5-84bb-cd612c0c7b1d").unwrap(),
            bin: String::from("echo"), 
            parameters: vec![JobConfCommandParam {
                name: String::from("to_print"),
                value_type: JobConfValueType::String,
                default_value: Some(String::from("")),
                comment: Some(String::from("Value to print on the cli")),
                required: true,
                named: false,
                parameter_name: None
            }]
        };
        let cmd_params = HashMap::new();
        let cmd = generate_command(cmd_params, conf);
        assert_eq!(cmd.unwrap_err(), CommandError::MissingRequiredParam);
    #[test]
    fn test_correct_conf_retrieving() {
        let conf = JobConfCommand {
            uuid: Uuid::parse_str("c5e9d770-73ea-43f5-84bb-cd612c0c7b1d").unwrap(),
            bin: String::from("echo"), 
            parameters: vec![JobConfCommandParam {
                name: String::from("to_print"),
                value_type: JobConfValueType::String,
                default_value: Some(String::from("")),
                comment: Some(String::from("Value to print on the cli")),
                required: true,
                named: false,
                parameter_name: None
            }]
        };
        let mut jobs: HashMap<String, JobConfCommand> = HashMap::new();
        jobs.insert("test".to_string(), conf.clone());
        let mut versions: HashMap<String, HashMap<String, JobConfCommand>> = HashMap::new();
        versions.insert("v0.4.0".to_string(), jobs);
        let mut configurations: Configurations = HashMap::new();
        configurations.insert("cryosparc".to_string(), versions);
        let found_conf = get_conf_by_uuid(Uuid::parse_str("c5e9d770-73ea-43f5-84bb-cd612c0c7b1d").unwrap(), configurations);
        assert_eq!(found_conf, Some(conf));
    }

    #[test]
    fn test_conf_not_found() {
        let conf = JobConfCommand {
            uuid: Uuid::parse_str("c5e9d770-73ea-43f5-84bb-cd612c0c7b1d").unwrap(),
            bin: String::from("echo"), 
            parameters: vec![JobConfCommandParam {
                name: String::from("to_print"),
                value_type: JobConfValueType::String,
                default_value: Some(String::from("")),
                comment: Some(String::from("Value to print on the cli")),
                required: true,
                named: false,
                parameter_name: None
            }]
        };
        let mut jobs: HashMap<String, JobConfCommand> = HashMap::new();
        jobs.insert("test".to_string(), conf.clone());
        let mut versions: HashMap<String, HashMap<String, JobConfCommand>> = HashMap::new();
        versions.insert("v0.4.0".to_string(), jobs);
        let mut configurations: Configurations = HashMap::new();
        configurations.insert("cryosparc".to_string(), versions);
        let found_conf = get_conf_by_uuid(Uuid::parse_str("c5e9d770-73ea-43f5-84bb-cd612c0c7b1e").unwrap(), configurations);
        assert_eq!(found_conf, None);
    }

    #[test]
    fn test_correct_command_generation() {
        let conf = JobConfCommand {
            uuid: Uuid::parse_str("c5e9d770-73ea-43f5-84bb-cd612c0c7b1d").unwrap(),
            bin: String::from("echo"), 
            parameters: vec![JobConfCommandParam {
                name: String::from("to_print"),
                value_type: JobConfValueType::String,
                default_value: Some(String::from("")),
                comment: Some(String::from("Value to print on the cli")),
                required: true,
                named: false,
                parameter_name: None
            }]
        };
        let mut cmd_params = HashMap::new();
        cmd_params.insert("to_print".to_string(), "Hello World !".to_string());
        let cmd = generate_command(cmd_params, conf);
        assert_eq!(cmd.unwrap(), String::from("echo Hello World !"));

    #[test]
    fn test_correct_named_command_generation() {
        let conf = JobConfCommand {
            uuid: Uuid::parse_str("c5e9d770-73ea-43f5-84bb-cd612c0c7b1d").unwrap(),
            bin: String::from("echo"), 
            parameters: vec![JobConfCommandParam {
                name: String::from("to_print"),
                value_type: JobConfValueType::String,
                default_value: Some(String::from("")),
                comment: Some(String::from("Value to print on the cli")),
                required: true,
                named: true,
                parameter_name: Some(String::from("-n"))
            }]
        };
        let mut cmd_params = HashMap::new();
        cmd_params.insert("to_print".to_string(), "Hello World !".to_string());
        let cmd = generate_command(cmd_params, conf);
        assert_eq!(cmd.unwrap(), String::from("echo -n Hello World !"));
    }