Check for loops in relays

This commit is contained in:
Pierre HUBERT 2024-08-27 18:38:49 +02:00
parent d890b23670
commit 50e61707cc
3 changed files with 133 additions and 5 deletions

View File

@ -1,7 +1,7 @@
//! # Devices entities definition //! # Devices entities definition
use crate::constants::StaticConstraints; use crate::constants::StaticConstraints;
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
/// Device information provided directly by the device during syncrhonisation. /// Device information provided directly by the device during syncrhonisation.
/// ///
@ -165,7 +165,7 @@ impl DeviceRelay {
} }
} }
let relays_map = list.iter().map(|r| (r.id, r)).collect::<HashMap<_, _>>(); let mut relays_map = list.iter().map(|r| (r.id, r)).collect::<HashMap<_, _>>();
if self.depends_on.iter().any(|d| !relays_map.contains_key(d)) { if self.depends_on.iter().any(|d| !relays_map.contains_key(d)) {
return Some("A specified dependent relay does not exists!"); return Some("A specified dependent relay does not exists!");
@ -179,10 +179,73 @@ impl DeviceRelay {
return Some("A specified conflicting relay does not exists!"); return Some("A specified conflicting relay does not exists!");
} }
// TODO : check for loops // Check for loops in dependencies
if self.check_for_loop_in_dependencies(&HashSet::new(), &relays_map) {
return Some("A loop was detected in relay dependencies!");
}
// Check if relay is in conflicts with one of its dependencies
let mut all_dependencies = HashSet::new();
let mut all_conflicts = HashSet::new();
self.get_list_of_dependencies_and_conflicts(
&mut all_dependencies,
&mut all_conflicts,
&relays_map,
);
for conf_id in all_conflicts {
if all_dependencies.contains(&conf_id) {
return Some(
"The relay or one of its dependencies is in conflict with a dependency!",
);
}
}
None None
} }
fn check_for_loop_in_dependencies(
&self,
visited: &HashSet<DeviceRelayID>,
list: &HashMap<DeviceRelayID, &Self>,
) -> bool {
let mut clone = visited.clone();
clone.insert(self.id);
for d in &self.depends_on {
if visited.contains(&d) {
return true;
}
if list
.get(&d)
.expect("Missing a relay!")
.check_for_loop_in_dependencies(&clone, list)
{
return true;
}
}
false
}
fn get_list_of_dependencies_and_conflicts(
&self,
deps_out: &mut HashSet<DeviceRelayID>,
conflicts_out: &mut HashSet<DeviceRelayID>,
list: &HashMap<DeviceRelayID, &Self>,
) {
for d in &self.depends_on {
let dependency = list.get(&d).expect("Missing a relay!");
deps_out.insert(dependency.id);
dependency.get_list_of_dependencies_and_conflicts(deps_out, conflicts_out, list);
}
for d in &self.conflicts_with {
conflicts_out.insert(*d);
}
}
} }
/// Device general information /// Device general information
@ -213,7 +276,7 @@ impl DeviceGeneralInfo {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::devices::device::DeviceRelay; use crate::devices::device::{DeviceRelay, DeviceRelayID};
#[test] #[test]
fn check_device_relay_error() { fn check_device_relay_error() {
@ -238,5 +301,53 @@ mod tests {
assert!(bad_name.error(&[]).is_some()); assert!(bad_name.error(&[]).is_some());
assert_eq!(dep_on_unitary.error(&[unitary.clone()]), None); assert_eq!(dep_on_unitary.error(&[unitary.clone()]), None);
assert!(dep_on_unitary.error(&[]).is_some()); assert!(dep_on_unitary.error(&[]).is_some());
// Dependency loop
let mut dep_cycle_1 = DeviceRelay {
id: DeviceRelayID::default(),
name: "dep_cycle_1".to_string(),
..Default::default()
};
let dep_cycle_2 = DeviceRelay {
id: DeviceRelayID::default(),
name: "dep_cycle_2".to_string(),
depends_on: vec![dep_cycle_1.id],
..Default::default()
};
let dep_cycle_3 = DeviceRelay {
id: DeviceRelayID::default(),
name: "dep_cycle_3".to_string(),
depends_on: vec![dep_cycle_2.id],
..Default::default()
};
dep_cycle_1.depends_on = vec![dep_cycle_3.id];
assert!(dep_cycle_1.error(&[dep_cycle_2, dep_cycle_3]).is_some());
// Impossible conflict
let other_dep = DeviceRelay {
id: DeviceRelayID::default(),
name: "other_dep".to_string(),
..Default::default()
};
let second_dep = DeviceRelay {
id: DeviceRelayID::default(),
name: "second_dep".to_string(),
conflicts_with: vec![other_dep.id],
..Default::default()
};
let target_relay = DeviceRelay {
id: DeviceRelayID::default(),
name: "target_relay".to_string(),
depends_on: vec![other_dep.id, second_dep.id],
..Default::default()
};
assert!(target_relay
.error(&[other_dep.clone(), second_dep.clone()])
.is_some());
assert!(target_relay
.error(&[other_dep, second_dep, target_relay.clone()])
.is_some());
} }
} }

View File

@ -165,6 +165,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
"/web_api/relays/list", "/web_api/relays/list",
web::get().to(relays_controller::get_list), web::get().to(relays_controller::get_list),
) )
.route(
"/web_api/relays/create",
web::post().to(relays_controller::create),
)
// Devices API // Devices API
.route( .route(
"/devices_api/utils/time", "/devices_api/utils/time",

View File

@ -1,10 +1,23 @@
use crate::devices::device::{DeviceId, DeviceRelay};
use crate::energy::energy_actor; use crate::energy::energy_actor;
use crate::server::custom_error::HttpResult; use crate::server::custom_error::HttpResult;
use crate::server::WebEnergyActor; use crate::server::WebEnergyActor;
use actix_web::HttpResponse; use actix_web::{web, HttpResponse};
/// Get the full list of relays /// Get the full list of relays
pub async fn get_list(actor: WebEnergyActor) -> HttpResult { pub async fn get_list(actor: WebEnergyActor) -> HttpResult {
let list = actor.send(energy_actor::GetRelaysList).await?; let list = actor.send(energy_actor::GetRelaysList).await?;
Ok(HttpResponse::Ok().json(list)) Ok(HttpResponse::Ok().json(list))
} }
#[derive(serde::Deserialize)]
pub struct CreateDeviceRelayRequest {
device_id: DeviceId,
#[serde(flatten)]
relay: DeviceRelay,
}
/// Create a new relay
pub async fn create(actor: WebEnergyActor, req: web::Json<CreateDeviceRelayRequest>) -> HttpResult {
todo!()
}