diff --git a/holo-isis/src/instance.rs b/holo-isis/src/instance.rs index 7ea1bb00..51d12b5f 100644 --- a/holo-isis/src/instance.rs +++ b/holo-isis/src/instance.rs @@ -32,7 +32,7 @@ use crate::interface::CircuitIdAllocator; use crate::lsdb::{LspEntry, LspLogEntry}; use crate::northbound::configuration::InstanceCfg; use crate::packet::{LevelNumber, LevelType, Levels}; -use crate::spf::SpfScheduler; +use crate::spf::{SpfLogEntry, SpfScheduler}; use crate::tasks::messages::input::{ AdjHoldTimerMsg, DisElectionMsg, LspDeleteMsg, LspOriginateMsg, LspPurgeMsg, LspRefreshMsg, NetRxPduMsg, SendCsnpMsg, SendPsnpMsg, @@ -83,6 +83,9 @@ pub struct InstanceState { // Log of LSP updates. pub lsp_log: VecDeque, pub lsp_log_next_id: u32, + // Log of SPF runs. + pub spf_log: VecDeque, + pub spf_log_next_id: u32, } #[derive(Debug, Default)] @@ -378,6 +381,8 @@ impl InstanceState { discontinuity_time: Utc::now(), lsp_log: Default::default(), lsp_log_next_id: 0, + spf_log: Default::default(), + spf_log_next_id: 0, } } } diff --git a/holo-isis/src/lsdb.rs b/holo-isis/src/lsdb.rs index cf029fc4..70edfa63 100644 --- a/holo-isis/src/lsdb.rs +++ b/holo-isis/src/lsdb.rs @@ -480,6 +480,9 @@ pub(crate) fn install<'a>( // Schedule SPF run if necessary. if content_change { + let spf_sched = instance.state.spf_sched.get_mut(level); + spf_sched.trigger_lsps.push(lsp_log_id); + spf_sched.schedule_time.get_or_insert_with(Instant::now); instance .tx .protocol_input diff --git a/holo-isis/src/northbound/state.rs b/holo-isis/src/northbound/state.rs index 84cc4470..1a968b8a 100644 --- a/holo-isis/src/northbound/state.rs +++ b/holo-isis/src/northbound/state.rs @@ -25,11 +25,12 @@ use crate::adjacency::Adjacency; use crate::collections::Lsdb; use crate::instance::Instance; use crate::interface::Interface; -use crate::lsdb::{LspEntry, LspLogEntry}; +use crate::lsdb::{LspEntry, LspLogEntry, LspLogId}; use crate::packet::tlv::{ ExtIpv4Reach, ExtIsReach, Ipv4Reach, Ipv6Reach, IsReach, UnknownTlv, }; use crate::packet::{LanId, LevelNumber}; +use crate::spf::SpfLogEntry; pub static CALLBACKS: Lazy> = Lazy::new(load_callbacks); @@ -38,6 +39,8 @@ pub static CALLBACKS: Lazy> = Lazy::new(load_callbacks); pub enum ListEntry<'a> { #[default] None, + SpfLog(&'a SpfLogEntry), + SpfTriggerLsp(&'a LspLogId), LspLog(&'a LspLogEntry), Lsdb(LevelNumber, &'a Lsdb), LspEntry(&'a LspEntry), @@ -78,31 +81,35 @@ fn load_callbacks() -> Callbacks { }) }) .path(isis::spf_log::event::PATH) - .get_iterate(|_instance, _args| { - // TODO: implement me! - None + .get_iterate(|instance, _args| { + let Some(instance_state) = &instance.state else { return None }; + let iter = instance_state.spf_log.iter().map(ListEntry::SpfLog); + Some(Box::new(iter) as _).ignore_in_testing() }) - .get_object(|_instance, _args| { + .get_object(|_instance, args| { use isis::spf_log::event::Event; + let log = args.list_entry.as_spf_log().unwrap(); Box::new(Event { - id: todo!(), - spf_type: None, - level: None, - schedule_timestamp: None, - start_timestamp: None, - end_timestamp: None, + id: log.id, + spf_type: Some(log.spf_type.to_yang()), + level: Some(log.level as u8), + schedule_timestamp: Some(Cow::Borrowed(&log.schedule_time)), + start_timestamp: Some(Cow::Borrowed(&log.start_time)), + end_timestamp: Some(Cow::Borrowed(&log.end_time)), }) }) .path(isis::spf_log::event::trigger_lsp::PATH) - .get_iterate(|_instance, _args| { - // TODO: implement me! - None + .get_iterate(|_instance, args| { + let log = args.parent_list_entry.as_spf_log().unwrap(); + let iter = log.trigger_lsps.iter().map(ListEntry::SpfTriggerLsp); + Some(Box::new(iter)) }) - .get_object(|_instance, _args| { + .get_object(|_instance, args| { use isis::spf_log::event::trigger_lsp::TriggerLsp; + let lsp = args.list_entry.as_spf_trigger_lsp().unwrap(); Box::new(TriggerLsp { - lsp: todo!(), - sequence: None, + lsp: lsp.lsp_id.to_yang(), + sequence: Some(lsp.seqno), }) }) .path(isis::lsp_log::event::PATH) diff --git a/holo-isis/src/northbound/yang.rs b/holo-isis/src/northbound/yang.rs index 6786e5c2..8963f2ae 100644 --- a/holo-isis/src/northbound/yang.rs +++ b/holo-isis/src/northbound/yang.rs @@ -19,6 +19,7 @@ use crate::lsdb::LspLogReason; use crate::northbound::configuration::MetricType; use crate::packet::consts::LspFlags; use crate::packet::{AreaAddr, LanId, LevelType, LspId, SystemId}; +use crate::spf::SpfType; // ===== ToYang implementations ===== @@ -143,6 +144,15 @@ impl ToYang for LspLogReason { } } +impl ToYang for SpfType { + fn to_yang(&self) -> Cow<'static, str> { + match self { + SpfType::Full => "full".into(), + SpfType::RouteOnly => "route-only".into(), + } + } +} + // ===== TryFromYang implementations ===== impl TryFromYang for LevelType { diff --git a/holo-isis/src/spf.rs b/holo-isis/src/spf.rs index 71781c7a..0cf85b45 100644 --- a/holo-isis/src/spf.rs +++ b/holo-isis/src/spf.rs @@ -10,6 +10,7 @@ use std::time::{Duration, Instant}; use chrono::Utc; +use derive_new::new; use holo_utils::task::TimeoutTask; use crate::adjacency::Adjacency; @@ -17,10 +18,15 @@ use crate::collections::{Arena, Interfaces}; use crate::debug::Debug; use crate::error::Error; use crate::instance::{InstanceArenas, InstanceUpView}; -use crate::lsdb::LspEntry; +use crate::lsdb::{LspEntry, LspLogId}; use crate::packet::LevelNumber; use crate::tasks; +// Maximum size of the SPF log record. +const SPF_LOG_MAX_SIZE: usize = 32; +// Maximum number of trigger LSPs per entry in the SPF log record. +const SPF_LOG_TRIGGER_LSPS_MAX_SIZE: usize = 8; + #[derive(Debug, Default)] pub struct SpfScheduler { pub last_event_rcvd: Option, @@ -29,6 +35,25 @@ pub struct SpfScheduler { pub delay_timer: Option, pub hold_down_timer: Option, pub learn_timer: Option, + pub trigger_lsps: Vec, + pub schedule_time: Option, +} + +#[derive(Clone, Copy, Debug)] +pub enum SpfType { + Full, + RouteOnly, +} + +#[derive(Debug, new)] +pub struct SpfLogEntry { + pub id: u32, + pub spf_type: SpfType, + pub level: LevelNumber, + pub schedule_time: Instant, + pub start_time: Instant, + pub end_time: Instant, + pub trigger_lsps: Vec, } // SPF Delay State Machine. @@ -257,6 +282,16 @@ fn compute_spf( ) { let spf_sched = instance.state.spf_sched.get_mut(level); + // Get time the SPF was scheduled. + let schedule_time = + spf_sched.schedule_time.take().unwrap_or_else(Instant::now); + + // Record time the SPF computation was started. + let start_time = Instant::now(); + + // Get list of new or updated LSPs that triggered the SPF computation. + let trigger_lsps = std::mem::take(&mut spf_sched.trigger_lsps); + // TODO: Run SPF. // Update statistics. @@ -266,4 +301,48 @@ fn compute_spf( // Update time of last SPF computation. let end_time = Instant::now(); spf_sched.last_time = Some(end_time); + + // Add entry to SPF log. + log_spf_run( + level, + instance, + SpfType::Full, + schedule_time, + start_time, + end_time, + trigger_lsps, + ); +} + +// Adds log entry for the SPF run. +fn log_spf_run( + level: LevelNumber, + instance: &mut InstanceUpView<'_>, + spf_type: SpfType, + schedule_time: Instant, + start_time: Instant, + end_time: Instant, + mut trigger_lsps: Vec, +) { + // Get next log ID. + let log_id = &mut instance.state.spf_log_next_id; + *log_id += 1; + + // Get trigger LSPs in log format. + trigger_lsps.truncate(SPF_LOG_TRIGGER_LSPS_MAX_SIZE); + + // Add new log entry. + let log_entry = SpfLogEntry::new( + *log_id, + spf_type, + level, + schedule_time, + start_time, + end_time, + trigger_lsps, + ); + instance.state.spf_log.push_front(log_entry); + + // Remove old entries if necessary. + instance.state.spf_log.truncate(SPF_LOG_MAX_SIZE); }