From 50e61707cc53d7a649ccef2bf08b4e8921d0036a Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 27 Aug 2024 18:38:49 +0200 Subject: [PATCH] Check for loops in relays --- central_backend/src/devices/device.rs | 119 +++++++++++++++++- central_backend/src/server/servers.rs | 4 + .../src/server/web_api/relays_controller.rs | 15 ++- 3 files changed, 133 insertions(+), 5 deletions(-) diff --git a/central_backend/src/devices/device.rs b/central_backend/src/devices/device.rs index e92870d..150a3e4 100644 --- a/central_backend/src/devices/device.rs +++ b/central_backend/src/devices/device.rs @@ -1,7 +1,7 @@ //! # Devices entities definition use crate::constants::StaticConstraints; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; /// 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::>(); + let mut relays_map = list.iter().map(|r| (r.id, r)).collect::>(); if self.depends_on.iter().any(|d| !relays_map.contains_key(d)) { return Some("A specified dependent relay does not exists!"); @@ -179,10 +179,73 @@ impl DeviceRelay { 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 } + + fn check_for_loop_in_dependencies( + &self, + visited: &HashSet, + list: &HashMap, + ) -> 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, + conflicts_out: &mut HashSet, + list: &HashMap, + ) { + 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 @@ -213,7 +276,7 @@ impl DeviceGeneralInfo { #[cfg(test)] mod tests { - use crate::devices::device::DeviceRelay; + use crate::devices::device::{DeviceRelay, DeviceRelayID}; #[test] fn check_device_relay_error() { @@ -238,5 +301,53 @@ mod tests { assert!(bad_name.error(&[]).is_some()); assert_eq!(dep_on_unitary.error(&[unitary.clone()]), None); 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()); } } diff --git a/central_backend/src/server/servers.rs b/central_backend/src/server/servers.rs index 985d412..7e717ec 100644 --- a/central_backend/src/server/servers.rs +++ b/central_backend/src/server/servers.rs @@ -165,6 +165,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> "/web_api/relays/list", web::get().to(relays_controller::get_list), ) + .route( + "/web_api/relays/create", + web::post().to(relays_controller::create), + ) // Devices API .route( "/devices_api/utils/time", diff --git a/central_backend/src/server/web_api/relays_controller.rs b/central_backend/src/server/web_api/relays_controller.rs index 45256f3..8f62978 100644 --- a/central_backend/src/server/web_api/relays_controller.rs +++ b/central_backend/src/server/web_api/relays_controller.rs @@ -1,10 +1,23 @@ +use crate::devices::device::{DeviceId, DeviceRelay}; use crate::energy::energy_actor; use crate::server::custom_error::HttpResult; use crate::server::WebEnergyActor; -use actix_web::HttpResponse; +use actix_web::{web, HttpResponse}; /// Get the full list of relays pub async fn get_list(actor: WebEnergyActor) -> HttpResult { let list = actor.send(energy_actor::GetRelaysList).await?; 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) -> HttpResult { + todo!() +}