From 9b155571cd440337923c01c34fbe6e6b8106afd0 Mon Sep 17 00:00:00 2001 From: Ashish Singh Date: Thu, 14 Dec 2023 18:22:14 -0800 Subject: [PATCH] [Link Event Damping] Add per port link event damper class. - Adding per port link event damper class that manages the internal behavior and workings of link event damping logic on a port where link event damping config is enabled. - This class keeps track of damping timer, current port state, post damping advertised port state and relevant debug counters per port. HLD: sonic-net/SONiC#1071 --- syncd/Makefile.am | 1 + syncd/NotificationHandler.cpp | 11 + syncd/NotificationHandler.h | 6 + syncd/PortLinkEventDamper.cpp | 416 +++++++ syncd/PortLinkEventDamper.h | 148 +++ unittest/syncd/Makefile.am | 1 + unittest/syncd/MockNotificationHandler.h | 23 + unittest/syncd/TestPortLinkEventDamper.cpp | 1184 ++++++++++++++++++++ 8 files changed, 1790 insertions(+) create mode 100644 syncd/PortLinkEventDamper.cpp create mode 100644 syncd/PortLinkEventDamper.h create mode 100644 unittest/syncd/MockNotificationHandler.h create mode 100644 unittest/syncd/TestPortLinkEventDamper.cpp diff --git a/syncd/Makefile.am b/syncd/Makefile.am index 4982ea16c..d5d18cb69 100644 --- a/syncd/Makefile.am +++ b/syncd/Makefile.am @@ -32,6 +32,7 @@ libSyncd_a_SOURCES = \ NotificationHandler.cpp \ NotificationProcessor.cpp \ NotificationQueue.cpp \ + PortLinkEventDamper.cpp \ PortMap.cpp \ PortMapParser.cpp \ PortStateChangeHandler.cpp \ diff --git a/syncd/NotificationHandler.cpp b/syncd/NotificationHandler.cpp index ff2aeae59..00330d684 100644 --- a/syncd/NotificationHandler.cpp +++ b/syncd/NotificationHandler.cpp @@ -132,6 +132,17 @@ void NotificationHandler::updateNotificationsPointers( } } +void NotificationHandler::onPortStateChangePostLinkEventDamping( + _In_ uint32_t count, + _In_ const sai_port_oper_status_notification_t *data) +{ + SWSS_LOG_ENTER(); + + auto s = sai_serialize_port_oper_status_ntf(count, data); + + enqueueNotification(SAI_SWITCH_NOTIFICATION_NAME_PORT_STATE_CHANGE, s); +} + // TODO use same Notification class from sairedis lib // then this will handle deserialize free diff --git a/syncd/NotificationHandler.h b/syncd/NotificationHandler.h index 9e093a47e..b492b1886 100644 --- a/syncd/NotificationHandler.h +++ b/syncd/NotificationHandler.h @@ -35,6 +35,12 @@ namespace syncd _In_ uint32_t attr_count, _In_ sai_attribute_t *attr_list) const; + // Handling of port state change event when received after being + // processed in link event damping logic. + virtual void onPortStateChangePostLinkEventDamping( + _In_ uint32_t count, + _In_ const sai_port_oper_status_notification_t *data); + public: // members reflecting SAI callbacks void onFdbEvent( diff --git a/syncd/PortLinkEventDamper.cpp b/syncd/PortLinkEventDamper.cpp new file mode 100644 index 000000000..a2b0124cf --- /dev/null +++ b/syncd/PortLinkEventDamper.cpp @@ -0,0 +1,416 @@ +#include "PortLinkEventDamper.h" + +#include +#include + +#include "Utils.h" +#include "meta/sai_serialize.h" +#include "swss/logger.h" +#include "swss/timestamp.h" + +using namespace syncd; + +PortLinkEventDamper::PortLinkEventDamper( + _In_ std::shared_ptr notificationHandler, + _In_ std::shared_ptr sel, + _In_ sai_object_id_t vid, + _In_ sai_object_id_t rid, + _In_ const sai_redis_link_event_damping_algo_aied_config_t &config) + : m_notificationHandler(notificationHandler), + m_sel(sel), + m_vid(vid), + m_rid(rid), + m_maxSuppressTimeUsec(uint64_t(config.max_suppress_time) * + MICRO_SECS_PER_MILLI_SEC), + m_decayHalfLifeUsec(uint64_t(config.decay_half_life) * + MICRO_SECS_PER_MILLI_SEC), + m_suppressThreshold(config.suppress_threshold), + m_reuseThreshold(config.reuse_threshold), + m_flapPenalty(config.flap_penalty), + m_accumulatedPenalty(0), + m_penaltyCeiling(std::numeric_limits::max()), + m_dampingConfigEnabled(false), + m_dampingActive(false), + m_lastPenaltyUpdateTimestamp(0), + m_timer(/*interval=*/timespec{.tv_sec = 0, .tv_nsec = 0}), + m_timerAddedToSelect(false), + m_timerActive(false), + m_advertisedState(SAI_PORT_OPER_STATUS_UNKNOWN), + m_actualState(SAI_PORT_OPER_STATUS_UNKNOWN), + m_stats() +{ + SWSS_LOG_ENTER(); +} + +PortLinkEventDamper::~PortLinkEventDamper() +{ + SWSS_LOG_ENTER(); + + // Clean up the resources. + if (m_timerAddedToSelect) + { + cancelTimer(); + m_sel->removeSelectable(&m_timer); + + SWSS_LOG_DEBUG("Removed timer with fd %d for port vid: %s from select.", + m_timer.getFd(), sai_serialize_object_id(m_vid).c_str()); + } + + advertisePortState(m_actualState); +} + +void PortLinkEventDamper::setup() +{ + SWSS_LOG_ENTER(); + + if (configContainsZeroValueParam()) + { + SWSS_LOG_NOTICE( + "Damping config is disabled on port vid: %s due to one or more " + "received config params being 0.", + sai_serialize_object_id(m_vid).c_str()); + + m_penaltyCeiling = 0; + m_dampingConfigEnabled = false; + } + else + { + updatePenaltyCeiling(); + m_dampingConfigEnabled = true; + } + + m_sel->addSelectable(&m_timer); + m_timer.stop(); + + SWSS_LOG_DEBUG("Added timer with fd %d for port vid: %s to select.", + m_timer.getFd(), sai_serialize_object_id(m_vid).c_str()); + + m_timerAddedToSelect = true; +} + +void PortLinkEventDamper::updatePenaltyCeiling() +{ + SWSS_LOG_ENTER(); + + // If any of config param value is 0, damping config is treated as disabled + // and calculation of penalty ceiling is not valid so mark penalty ceiling 0. + if (configContainsZeroValueParam()) + { + m_penaltyCeiling = 0; + + return; + } + + double ceiling = pow(2.0, double(m_maxSuppressTimeUsec) / + double(m_decayHalfLifeUsec)) * + double(m_reuseThreshold); + + if (uint64_t(ceiling) > uint64_t(std::numeric_limits::max())) + { + SWSS_LOG_NOTICE("Calculated ceiling %ull greater than %u, truncating", + uint64_t(ceiling), std::numeric_limits::max()); + + m_penaltyCeiling = std::numeric_limits::max(); + } + else + { + m_penaltyCeiling = uint32_t(ceiling); + } + + SWSS_LOG_INFO("Penalty ceiling %u on port vid: %s.", m_penaltyCeiling, + sai_serialize_object_id(m_vid).c_str()); +} + +void PortLinkEventDamper::updatePenalty() +{ + SWSS_LOG_ENTER(); + + uint64_t currentTime = getCurrentTimeUsecs(); + + if (m_accumulatedPenalty > 0) + { + m_accumulatedPenalty = sairedis::Utils::valueAfterDecay( + currentTime - m_lastPenaltyUpdateTimestamp, m_decayHalfLifeUsec, + m_accumulatedPenalty); + } + + m_lastPenaltyUpdateTimestamp = currentTime; + + // Ensure updated accumulated penalty does not cross penalty ceiling. + // To avoid integer overflow, first check if enough gap is available. + if (m_penaltyCeiling - m_accumulatedPenalty > m_flapPenalty) + { + m_accumulatedPenalty += m_flapPenalty; + } + else + { + m_accumulatedPenalty = m_penaltyCeiling; + } +} + +void PortLinkEventDamper::resetTimer( + _In_ int64_t time_us) +{ + SWSS_LOG_ENTER(); + + m_timer.setInterval( + /*interval=*/{.tv_sec = (time_us / int64_t(MICRO_SECS_PER_SEC)), + .tv_nsec = (time_us % int64_t(MICRO_SECS_PER_SEC)) * + int64_t(NANO_SECS_PER_MICRO_SEC)}); + m_timer.reset(); + m_timerActive = true; +} + +void PortLinkEventDamper::cancelTimer() +{ + SWSS_LOG_ENTER(); + + m_timer.stop(); + m_timerActive = false; +} + +void PortLinkEventDamper::handleSelectableEvent() +{ + SWSS_LOG_ENTER(); + + cancelTimer(); + + // Calculate new penalty after decay. + uint64_t currentTime = getCurrentTimeUsecs(); + m_accumulatedPenalty = sairedis::Utils::valueAfterDecay( + currentTime - m_lastPenaltyUpdateTimestamp, m_decayHalfLifeUsec, + m_accumulatedPenalty); + m_lastPenaltyUpdateTimestamp = currentTime; + + // If accumulated penalty after the decay is below link reuse threshold, port + // no longer needs to be damped. Else, continue damping the port. + if (m_accumulatedPenalty <= m_reuseThreshold) + { + m_dampingActive = false; + + SWSS_LOG_INFO( + "Damping stops at time %llu on port vid: %s with accumulated penalty: " + "%u, link reuse threshold: %u.", + m_lastPenaltyUpdateTimestamp, sai_serialize_object_id(m_vid).c_str(), + m_accumulatedPenalty, m_reuseThreshold); + + // Advertise the actual state immediately. + advertisePortState(m_actualState); + } + else + { + uint64_t tv_usec = sairedis::Utils::timeToReachTargetValueUsingHalfLife( + m_decayHalfLifeUsec, m_accumulatedPenalty, m_reuseThreshold); + + SWSS_LOG_NOTICE( + "Expected current accumulated penalty %u to be <= link reuse threshold " + "%u " + "for port vid: %s, need another %d usecs.", + m_accumulatedPenalty, m_reuseThreshold, + sai_serialize_object_id(m_vid).c_str(), tv_usec); + + resetTimer(tv_usec); + } +} + +void PortLinkEventDamper::handlePortStateChange( + _In_ sai_port_oper_status_t newPortState) +{ + SWSS_LOG_ENTER(); + + m_actualState = newPortState; + + if (newPortState == SAI_PORT_OPER_STATUS_UP) + { + ++m_stats.pre_damping_up_events; + } + else + { + ++m_stats.pre_damping_down_events; + } + + // If link event damping config is not enabled, advertise the port event. + if (m_dampingConfigEnabled == false) + { + advertisePortState(newPortState); + + return; + } + + if (m_dampingActive == false) + { + advertisePortState(newPortState); + + // Penalty is updated only for DOWN events. + if (newPortState == SAI_PORT_OPER_STATUS_DOWN) + { + updatePenalty(); + + if (m_accumulatedPenalty >= m_suppressThreshold) + { + // Start the damping on the port. + resetTimer(sairedis::Utils::timeToReachTargetValueUsingHalfLife( + m_decayHalfLifeUsec, m_accumulatedPenalty, m_reuseThreshold)); + m_dampingActive = true; + + SWSS_LOG_INFO( + "Damping starts at time %llu on port vid: %s with accumulated " + "penalty: %u, reuse_threshold: %u", + m_lastPenaltyUpdateTimestamp, + sai_serialize_object_id(m_vid).c_str(), m_accumulatedPenalty, + m_reuseThreshold); + } + } + } + else // Damping is active on port. + { + // UP event on a damped link is no-op. + if (newPortState == SAI_PORT_OPER_STATUS_DOWN) + { + // Cancel the timer on the port. + cancelTimer(); + updatePenalty(); + + // Restart the timer. + resetTimer(sairedis::Utils::timeToReachTargetValueUsingHalfLife( + m_decayHalfLifeUsec, m_accumulatedPenalty, m_reuseThreshold)); + } + } +} + +void PortLinkEventDamper::advertisePortState( + _In_ sai_port_oper_status_t portState) +{ + SWSS_LOG_ENTER(); + + if (portState != m_advertisedState) + { + sai_port_oper_status_notification_t data = {.port_id = m_rid, + .port_state = portState}; + + SWSS_LOG_NOTICE("Advertising [port rid: %s, port oper status: %s]", + sai_serialize_object_id(data.port_id).c_str(), + sai_serialize_port_oper_status(data.port_state).c_str()); + + m_notificationHandler->onPortStateChangePostLinkEventDamping(/*count=*/1, &data); + m_advertisedState = portState; + + if (portState == SAI_PORT_OPER_STATUS_UP) + { + ++m_stats.post_damping_up_events; + m_stats.last_advertised_up_event_timestamp = swss::getTimestamp(); + } + else + { + ++m_stats.post_damping_down_events; + m_stats.last_advertised_down_event_timestamp = swss::getTimestamp(); + } + } +} + +void PortLinkEventDamper::updateLinkEventDampingConfig( + _In_ const sai_redis_link_event_damping_algo_aied_config_t &config) +{ + SWSS_LOG_ENTER(); + + if (isConfigSameAsRunningConfig(config)) + { + return; + } + + m_maxSuppressTimeUsec = + uint64_t(config.max_suppress_time) * MICRO_SECS_PER_MILLI_SEC; + m_decayHalfLifeUsec = + uint64_t(config.decay_half_life) * MICRO_SECS_PER_MILLI_SEC; + m_suppressThreshold = config.suppress_threshold; + m_reuseThreshold = config.reuse_threshold; + m_flapPenalty = config.flap_penalty; + m_accumulatedPenalty = 0; + m_dampingActive = false; + m_lastPenaltyUpdateTimestamp = getCurrentTimeUsecs(); + + if (configContainsZeroValueParam()) + { + if (m_dampingConfigEnabled) + { + SWSS_LOG_NOTICE("Disabling the damping config on port vid: %s", + sai_serialize_object_id(m_vid).c_str()); + } + + m_penaltyCeiling = 0; + m_dampingConfigEnabled = false; + } + else + { + updatePenaltyCeiling(); + + if (m_dampingConfigEnabled == false) + { + SWSS_LOG_NOTICE("Enabling the damping config on port vid: %s", + sai_serialize_object_id(m_vid).c_str()); + } + + m_dampingConfigEnabled = true; + } + + if (m_timerActive) + { + cancelTimer(); + } + + if (m_advertisedState != m_actualState) + { + // Advertise the current state. + advertisePortState(m_actualState); + } + + SWSS_LOG_DEBUG("Link event damping config on port vid: %s is updated.", + sai_serialize_object_id(m_vid).c_str()); +} + +bool PortLinkEventDamper::isConfigSameAsRunningConfig( + _In_ const sai_redis_link_event_damping_algo_aied_config_t &config) +{ + SWSS_LOG_ENTER(); + + if ((config.max_suppress_time != + uint32_t(m_maxSuppressTimeUsec / MICRO_SECS_PER_MILLI_SEC)) || + (config.decay_half_life != + uint32_t(m_decayHalfLifeUsec / MICRO_SECS_PER_MILLI_SEC)) || + (config.suppress_threshold != m_suppressThreshold) || + (config.reuse_threshold != m_reuseThreshold) || + (config.flap_penalty != m_flapPenalty)) + { + return false; + } + + return true; +} + +bool PortLinkEventDamper::configContainsZeroValueParam() const +{ + SWSS_LOG_ENTER(); + + if ((m_maxSuppressTimeUsec == 0) || (m_decayHalfLifeUsec == 0) || + (m_suppressThreshold == 0) || (m_reuseThreshold == 0) || + (m_flapPenalty == 0)) + { + return true; + } + + return false; +} + +struct DampingStats PortLinkEventDamper::getDampingStats() const +{ + SWSS_LOG_ENTER(); + + return m_stats; +} + +int PortLinkEventDamper::getSelectableTimerFd() +{ + SWSS_LOG_ENTER(); + + return m_timer.getFd(); +} diff --git a/syncd/PortLinkEventDamper.h b/syncd/PortLinkEventDamper.h new file mode 100644 index 000000000..46bdea342 --- /dev/null +++ b/syncd/PortLinkEventDamper.h @@ -0,0 +1,148 @@ +#pragma once + +#include + +#include "NotificationHandler.h" +#include "sairedis.h" +#include "swss/sal.h" +#include "swss/select.h" +#include "swss/selectabletimer.h" + +extern "C" { +#include "saimetadata.h" +} + +namespace syncd +{ + + constexpr uint64_t MICRO_SECS_PER_SEC = 1000000ULL; + constexpr uint64_t NANO_SECS_PER_MICRO_SEC = 1000ULL; + constexpr uint64_t MICRO_SECS_PER_MILLI_SEC = 1000ULL; + + inline uint64_t getCurrentTimeUsecs() + { + SWSS_LOG_ENTER(); + + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + + return (ts.tv_sec * MICRO_SECS_PER_SEC) + + (ts.tv_nsec / NANO_SECS_PER_MICRO_SEC); + } + + struct DampingStats + { + // Number of link UP events received. + uint64_t pre_damping_up_events; + // Number of link DOWN events received. + uint64_t pre_damping_down_events; + // Number of link UP events advertised post damping. + uint64_t post_damping_up_events; + // Number of link DOWN events advertised post damping. + uint64_t post_damping_down_events; + // Timestamp for last advertised link up event post damping. + std::string last_advertised_up_event_timestamp; + // Timestamp for last advertised link down event post damping. + std::string last_advertised_down_event_timestamp; + + DampingStats() + : pre_damping_up_events(0ULL), + pre_damping_down_events(0ULL), + post_damping_up_events(0ULL), + post_damping_down_events(0ULL), + last_advertised_up_event_timestamp("none"), + last_advertised_down_event_timestamp("none") {} + }; + + // Class to handle the link event damping on a port. + class PortLinkEventDamper + { + public: + PortLinkEventDamper( + _In_ std::shared_ptr notificationHandler, + _In_ std::shared_ptr sel, + _In_ sai_object_id_t vid, + _In_ sai_object_id_t rid, + _In_ const sai_redis_link_event_damping_algo_aied_config_t &config); + + virtual ~PortLinkEventDamper(); + + // Sets up the initial states for link event damping. + void setup(); + + // Handles and processes the port state change event. + void handlePortStateChange( + _In_ sai_port_oper_status_t newPortState); + + // Updates the link event damping configuration on the port. + void updateLinkEventDampingConfig( + _In_ const sai_redis_link_event_damping_algo_aied_config_t &config); + + // Handles for Selectable timer events. + void handleSelectableEvent(); + + struct DampingStats getDampingStats() const; + + int getSelectableTimerFd(); + + private: + // Resets the timer. + void resetTimer( + _In_ int64_t time_us); + + // Cancels the timer by stopping the timer. + void cancelTimer(); + + // Advertises the port state to consumers. + void advertisePortState( + _In_ sai_port_oper_status_t portState); + + void updatePenaltyCeiling(); + + void updatePenalty(); + + bool isConfigSameAsRunningConfig( + _In_ const sai_redis_link_event_damping_algo_aied_config_t &config); + + // Checks if any config parameter value is 0. + bool configContainsZeroValueParam() const; + + std::shared_ptr m_notificationHandler; + + // This is to add SelectableTimer. + std::shared_ptr m_sel; + + sai_object_id_t m_vid; + sai_object_id_t m_rid; + + uint64_t m_maxSuppressTimeUsec; + uint64_t m_decayHalfLifeUsec; + uint32_t m_suppressThreshold; + uint32_t m_reuseThreshold; + uint32_t m_flapPenalty; + uint32_t m_accumulatedPenalty; + uint32_t m_penaltyCeiling; + bool m_dampingConfigEnabled; + bool m_dampingActive; + uint64_t m_lastPenaltyUpdateTimestamp; + + // Timer to manage link event damping. + swss::SelectableTimer m_timer; + + // Flag to annotate if timer is added to select. + bool m_timerAddedToSelect; + bool m_timerActive; + + // Advertised port's oper status to consumers. + sai_port_oper_status_t m_advertisedState; + + // Actual port's oper status in the ASIC. + sai_port_oper_status_t m_actualState; + + // Damping related statistics. + struct DampingStats m_stats; + + friend class PortLinkEventDamperPeer; + friend class LinkEventDamperPeer; + }; +} // namespace syncd diff --git a/unittest/syncd/Makefile.am b/unittest/syncd/Makefile.am index 27c301e25..6fea46f50 100644 --- a/unittest/syncd/Makefile.am +++ b/unittest/syncd/Makefile.am @@ -16,6 +16,7 @@ tests_SOURCES = main.cpp \ TestNotificationProcessor.cpp \ TestNotificationHandler.cpp \ TestMdioIpcServer.cpp \ + TestPortLinkEventDamper.cpp \ TestPortStateChangeHandler.cpp \ TestVendorSai.cpp diff --git a/unittest/syncd/MockNotificationHandler.h b/unittest/syncd/MockNotificationHandler.h new file mode 100644 index 000000000..86af087bd --- /dev/null +++ b/unittest/syncd/MockNotificationHandler.h @@ -0,0 +1,23 @@ +#pragma once + +#include "NotificationHandler.h" + +#include "gmock/gmock.h" + +namespace syncd +{ + class MockNotificationHandler : public NotificationHandler + { + public: + + MockNotificationHandler( + _In_ std::shared_ptr notificationProcessor) + : NotificationHandler(notificationProcessor, nullptr) {} + + ~MockNotificationHandler() override {} + + MOCK_METHOD2(onPortStateChangePostLinkEventDamping, + void(_In_ uint32_t count, + _In_ const sai_port_oper_status_notification_t *data)); + }; +} // namespace syncd diff --git a/unittest/syncd/TestPortLinkEventDamper.cpp b/unittest/syncd/TestPortLinkEventDamper.cpp new file mode 100644 index 000000000..6f794defa --- /dev/null +++ b/unittest/syncd/TestPortLinkEventDamper.cpp @@ -0,0 +1,1184 @@ +#include + +#include +#include + +#include "PortLinkEventDamper.h" +#include "MockNotificationHandler.h" + +namespace syncd +{ + +// Peer class to access private state and APIs. +class PortLinkEventDamperPeer +{ + public: + explicit PortLinkEventDamperPeer( + _In_ PortLinkEventDamper *portLinkEventDamper) + : m_portLinkEventDamper(portLinkEventDamper) {} + + void setTimerActive( + _In_ bool state) + { + SWSS_LOG_ENTER(); + + m_portLinkEventDamper->m_timerActive = state; + } + + bool getTimerActive() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_timerActive; + } + + void setAdvertisedState( + _In_ sai_port_oper_status_t portState) + { + SWSS_LOG_ENTER(); + + m_portLinkEventDamper->m_advertisedState = portState; + } + + void setActualState( + _In_ sai_port_oper_status_t portState) + { + SWSS_LOG_ENTER(); + + m_portLinkEventDamper->m_actualState = portState; + } + + void setMaxSuppressTime( + _In_ uint64_t time) + { + SWSS_LOG_ENTER(); + + m_portLinkEventDamper->m_maxSuppressTimeUsec = time; + } + + void setDecayHalfLife( + _In_ uint64_t time) + { + SWSS_LOG_ENTER(); + + m_portLinkEventDamper->m_decayHalfLifeUsec = time; + } + + void setReuseThreshold( + _In_ uint32_t threshold) + { + SWSS_LOG_ENTER(); + + m_portLinkEventDamper->m_reuseThreshold = threshold; + } + + void setSuppressThreshold( + _In_ uint32_t threshold) + { + SWSS_LOG_ENTER(); + + m_portLinkEventDamper->m_suppressThreshold = threshold; + } + + void setFlapPenalty( + _In_ uint32_t val) + { + SWSS_LOG_ENTER(); + + m_portLinkEventDamper->m_flapPenalty = val; + } + + void setAccumulatedPenalty( + _In_ uint32_t penalty) + { + SWSS_LOG_ENTER(); + + m_portLinkEventDamper->m_accumulatedPenalty = penalty; + } + + void setLastPenaltyUpdateTimestamp( + _In_ uint64_t timestamp) + { + SWSS_LOG_ENTER(); + + m_portLinkEventDamper->m_lastPenaltyUpdateTimestamp = timestamp; + } + + void setDampingConfigEnabled( + _In_ bool state) + { + SWSS_LOG_ENTER(); + + m_portLinkEventDamper->m_dampingConfigEnabled = state; + } + + sai_port_oper_status_t getAdvertisedState() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_advertisedState; + } + + sai_port_oper_status_t getActualState() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_actualState; + } + + uint64_t getMaxSuppressTime() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_maxSuppressTimeUsec; + } + + uint64_t getDecayHalfLife() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_decayHalfLifeUsec; + } + + uint32_t getSuppressThreshold() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_suppressThreshold; + } + + uint32_t getReuseThreshold() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_reuseThreshold; + } + + uint32_t getFlapPenalty() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_flapPenalty; + } + + bool getDampingConfigEnabled() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_dampingConfigEnabled; + } + + bool getDampingActive() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_dampingActive; + } + + uint32_t getAccumulatedPenalty() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_accumulatedPenalty; + } + + uint32_t getPenaltyCeiling() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_penaltyCeiling; + } + + bool getTimerAddedToSelect() const + { + SWSS_LOG_ENTER(); + + return m_portLinkEventDamper->m_timerAddedToSelect; + } + + void updatePenaltyCeiling() + { + SWSS_LOG_ENTER(); + + m_portLinkEventDamper->updatePenaltyCeiling(); + } + + private: + PortLinkEventDamper *m_portLinkEventDamper; // Not owned. +}; + +namespace +{ + +using ::testing::_; +using ::testing::AllOf; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Field; +using ::testing::NotNull; +using ::testing::Pointee; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::StrictMock; + +constexpr sai_object_id_t kVid = 0x10002; +constexpr sai_object_id_t kRid = 0x20001; +constexpr uint32_t kDefaultMaxSuppressTimeMsec = 4500; +constexpr uint32_t kDefaultDecayHalfLifeMsec = 3000; +constexpr uint32_t kDefaultReuseThreshold = 1200; +constexpr uint32_t kDefaultSuppressThreshold = 1500; +constexpr uint32_t kDefaultFlapPenalty = 1000; + +constexpr uint32_t kExpectedPenaltyCeiling = 3394; + +class PortLinkEventDamperTest : public ::testing::Test +{ + protected: + PortLinkEventDamperTest() + : m_portLinkEventDamper(nullptr), + m_notificationHandler(nullptr) {} + + ~PortLinkEventDamperTest() override {} + + void SetUp() override; + + void TearDown() override + { + if (m_portLinkEventDamper != nullptr) + { + delete m_portLinkEventDamper; + } + } + + const sai_redis_link_event_damping_algo_aied_config_t kDefaultConfig = { + .max_suppress_time = kDefaultMaxSuppressTimeMsec, + .suppress_threshold = kDefaultSuppressThreshold, + .reuse_threshold = kDefaultReuseThreshold, + .decay_half_life = kDefaultDecayHalfLifeMsec, + .flap_penalty = kDefaultFlapPenalty}; + + PortLinkEventDamper *m_portLinkEventDamper; + std::shared_ptr> m_notificationHandler; +}; + +void PortLinkEventDamperTest::SetUp() +{ + SWSS_LOG_ENTER(); + + std::shared_ptr notificationProcessor = + std::make_shared(nullptr, nullptr, nullptr); + m_notificationHandler = std::make_shared>( + notificationProcessor); + + m_portLinkEventDamper = + new PortLinkEventDamper(m_notificationHandler, + std::make_shared(), + kVid, kRid, kDefaultConfig); +} + +void verifyStatesAfterPortDamperCreation( + _In_ const PortLinkEventDamperPeer &perPortDamperPeer) +{ + SWSS_LOG_ENTER(); + + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), + SAI_PORT_OPER_STATUS_UNKNOWN); + EXPECT_EQ(perPortDamperPeer.getActualState(), + SAI_PORT_OPER_STATUS_UNKNOWN); + EXPECT_EQ(perPortDamperPeer.getMaxSuppressTime(), + kDefaultMaxSuppressTimeMsec * MICRO_SECS_PER_MILLI_SEC); + EXPECT_EQ(perPortDamperPeer.getDecayHalfLife(), + kDefaultDecayHalfLifeMsec * MICRO_SECS_PER_MILLI_SEC); + EXPECT_EQ(perPortDamperPeer.getSuppressThreshold(), + kDefaultSuppressThreshold); + EXPECT_EQ(perPortDamperPeer.getReuseThreshold(), kDefaultReuseThreshold); + EXPECT_EQ(perPortDamperPeer.getFlapPenalty(), kDefaultFlapPenalty); + EXPECT_EQ(perPortDamperPeer.getAccumulatedPenalty(), 0); + EXPECT_EQ(perPortDamperPeer.getPenaltyCeiling(), + std::numeric_limits::max()); + EXPECT_FALSE(perPortDamperPeer.getDampingConfigEnabled()); + EXPECT_FALSE(perPortDamperPeer.getDampingActive()); + EXPECT_FALSE(perPortDamperPeer.getTimerActive()); + EXPECT_FALSE(perPortDamperPeer.getTimerAddedToSelect()); +} + +::testing::Matcher EqOperStatus( + _In_ const sai_port_oper_status_notification_t &expected) +{ + SWSS_LOG_ENTER(); + + return AllOf( + Field("port_id", &sai_port_oper_status_notification_t::port_id, + expected.port_id), + Field("port_state", &sai_port_oper_status_notification_t::port_state, + expected.port_state)); +} + +// Sets up the expectations for link event damper object destruction. +void setUpLinkEventDamperDestruction( + _In_ PortLinkEventDamperPeer &perPortDamperPeer, + _In_ std::shared_ptr> notificationHandler) +{ + SWSS_LOG_ENTER(); + + sai_port_oper_status_notification_t data = { + .port_id = kRid, .port_state = perPortDamperPeer.getActualState()}; + if (perPortDamperPeer.getAdvertisedState() != perPortDamperPeer.getActualState()) + { + // Set expectation to advertise the actual state. + EXPECT_CALL(*notificationHandler, + onPortStateChangePostLinkEventDamping(/*count=*/1, Pointee(EqOperStatus(data)))) + .WillOnce(Return()); + } + else + { + // No advertisement of actual state. + EXPECT_CALL(*notificationHandler, + onPortStateChangePostLinkEventDamping(/*count=*/1, Pointee(EqOperStatus(data)))) + .Times(0); + } +} + +void verifyStatesAfterLinkEventDampingConfigUpdate( + _In_ const PortLinkEventDamperPeer &perPortDamperPeer, + _In_ const sai_redis_link_event_damping_algo_aied_config_t &expectedConfig, + _In_ sai_port_oper_status_t expectedState, + _In_ bool dampingConfigEnabled) +{ + SWSS_LOG_ENTER(); + + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), expectedState); + EXPECT_EQ(perPortDamperPeer.getActualState(), expectedState); + EXPECT_EQ(perPortDamperPeer.getMaxSuppressTime(), + expectedConfig.max_suppress_time * MICRO_SECS_PER_MILLI_SEC); + EXPECT_EQ(perPortDamperPeer.getDecayHalfLife(), + expectedConfig.decay_half_life * MICRO_SECS_PER_MILLI_SEC); + EXPECT_EQ(perPortDamperPeer.getSuppressThreshold(), + expectedConfig.suppress_threshold); + EXPECT_EQ(perPortDamperPeer.getReuseThreshold(), + expectedConfig.reuse_threshold); + EXPECT_EQ(perPortDamperPeer.getFlapPenalty(), expectedConfig.flap_penalty); + EXPECT_EQ(perPortDamperPeer.getAccumulatedPenalty(), 0); + EXPECT_EQ(perPortDamperPeer.getDampingConfigEnabled(), dampingConfigEnabled); + EXPECT_FALSE(perPortDamperPeer.getDampingActive()); + EXPECT_FALSE(perPortDamperPeer.getTimerActive()); + EXPECT_FALSE(perPortDamperPeer.getTimerAddedToSelect()); +} + +void activateDampingOnPort( + _In_ PortLinkEventDamper *perPortDamper, + _In_ PortLinkEventDamperPeer &perPortDamperPeer, + _In_ std::shared_ptr> notificationHandler) +{ + SWSS_LOG_ENTER(); + + uint32_t suppressThreshold = perPortDamperPeer.getSuppressThreshold(); + constexpr sai_port_oper_status_t kPortStateUp = SAI_PORT_OPER_STATUS_UP; + constexpr sai_port_oper_status_t kPortStateDown = SAI_PORT_OPER_STATUS_DOWN; + struct DampingStats initialDampingStats = perPortDamper->getDampingStats(); + + uint32_t upLinkEvents = 0; + uint32_t downLinkEvents = 0; + + // Generate DOWN events till damping activates on the port. + while ((perPortDamperPeer.getAccumulatedPenalty() < suppressThreshold) && + (!perPortDamperPeer.getDampingActive())) + { + sai_port_oper_status_notification_t data = {.port_id = kRid, + .port_state = kPortStateUp}; + EXPECT_CALL(*notificationHandler, + onPortStateChangePostLinkEventDamping( + /*count=*/1, Pointee(EqOperStatus(data)))) + .WillOnce(Return()); + + perPortDamper->handlePortStateChange(kPortStateUp); + ++upLinkEvents; + + data.port_state = kPortStateDown; + EXPECT_CALL(*notificationHandler, + onPortStateChangePostLinkEventDamping( + /*count=*/1, Pointee(EqOperStatus(data)))) + .WillOnce(Return()); + + perPortDamper->handlePortStateChange(kPortStateDown); + ++downLinkEvents; + } + + EXPECT_TRUE(perPortDamperPeer.getDampingActive()); + EXPECT_TRUE(perPortDamperPeer.getTimerActive()); + EXPECT_GE(perPortDamperPeer.getAccumulatedPenalty(), suppressThreshold); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), + SAI_PORT_OPER_STATUS_DOWN); + EXPECT_EQ(perPortDamperPeer.getActualState(), SAI_PORT_OPER_STATUS_DOWN); + + struct DampingStats finalDampingStats = perPortDamper->getDampingStats(); + EXPECT_EQ(finalDampingStats.pre_damping_up_events, + initialDampingStats.pre_damping_up_events + upLinkEvents); + EXPECT_EQ(finalDampingStats.pre_damping_down_events, + initialDampingStats.pre_damping_down_events + downLinkEvents); + EXPECT_EQ(finalDampingStats.post_damping_up_events, + initialDampingStats.post_damping_up_events + upLinkEvents); + EXPECT_EQ(finalDampingStats.post_damping_down_events, + initialDampingStats.post_damping_down_events + downLinkEvents); +} + +TEST_F(PortLinkEventDamperTest, LinkEventDampingSetupSucceeds) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + + m_portLinkEventDamper->setup(); + + EXPECT_EQ(perPortDamperPeer.getPenaltyCeiling(), kExpectedPenaltyCeiling); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), + SAI_PORT_OPER_STATUS_UNKNOWN); + EXPECT_EQ(perPortDamperPeer.getActualState(), + SAI_PORT_OPER_STATUS_UNKNOWN); + EXPECT_TRUE(perPortDamperPeer.getTimerAddedToSelect()); + EXPECT_TRUE(perPortDamperPeer.getDampingConfigEnabled()); + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +TEST_F(PortLinkEventDamperTest, VerifyUpdatePenaltyCeiling) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + + struct Data { + uint64_t max_suppress_time; + uint64_t decay_half_life; + uint32_t reuse_threshold; + uint32_t expected_penalty_ceiling; + }; + + const std::vector kTestData = { + {64, 45, 1500, 4019}, + {64, 4, 50, 3276800}, + {64, 2, 1000, std::numeric_limits::max()}, + {35, 7, 132, 4224}, + {45, 9, 700, 22400}, + /* Calculated penalty is expected to be 0 if any of config param is 0. */ + {0, 10, 20, 0}, + {10, 0, 20, 0}, + {10, 4, 0, 0}}; + + for (const auto &data : kTestData) + { + SCOPED_TRACE(::testing::Message() + << "Max suppress time: " << data.max_suppress_time + << ", half life(usec): " << data.decay_half_life + << ", reuse threshold: " << data.reuse_threshold + << ", expected penalty ceiling: " + << data.expected_penalty_ceiling); + + perPortDamperPeer.setMaxSuppressTime(data.max_suppress_time); + perPortDamperPeer.setDecayHalfLife(data.decay_half_life); + perPortDamperPeer.setReuseThreshold(data.reuse_threshold); + + perPortDamperPeer.updatePenaltyCeiling(); + EXPECT_EQ(perPortDamperPeer.getPenaltyCeiling(), + data.expected_penalty_ceiling); + } + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +// Tests that when damping timer is expired and accumulated penalty after decay +// is above reuse threshold, no port state is advertised, damping on the port +// remains active and new damping timer is set. +TEST_F(PortLinkEventDamperTest, DampingTimerExpirationDoesNotDisableDamping) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + + perPortDamperPeer.setDampingConfigEnabled(/*state=*/true); + + activateDampingOnPort(m_portLinkEventDamper, perPortDamperPeer, + m_notificationHandler); + + struct DampingStats initialDampingStats = + m_portLinkEventDamper->getDampingStats(); + perPortDamperPeer.setAccumulatedPenalty( + perPortDamperPeer.getReuseThreshold() * 4); + + // Set decay interval to half time duration, so that accumulated penalty + // after decay still remains above reuse threshold. + perPortDamperPeer.setLastPenaltyUpdateTimestamp( + getCurrentTimeUsecs() - perPortDamperPeer.getDecayHalfLife()); + perPortDamperPeer.setAdvertisedState(SAI_PORT_OPER_STATUS_DOWN); + perPortDamperPeer.setActualState(SAI_PORT_OPER_STATUS_UP); + + EXPECT_CALL(*m_notificationHandler, onPortStateChangePostLinkEventDamping(/*count=*/1, _)) + .Times(0); + + // Simulate a timer event. + m_portLinkEventDamper->handleSelectableEvent(); + + EXPECT_TRUE(perPortDamperPeer.getTimerActive()); + EXPECT_TRUE(perPortDamperPeer.getDampingActive()); + EXPECT_TRUE(perPortDamperPeer.getDampingConfigEnabled()); + EXPECT_GT(perPortDamperPeer.getAccumulatedPenalty(), + perPortDamperPeer.getReuseThreshold()); + + struct DampingStats currDampingStats = + m_portLinkEventDamper->getDampingStats(); + EXPECT_EQ(currDampingStats.post_damping_up_events, + initialDampingStats.post_damping_up_events); + EXPECT_EQ(currDampingStats.post_damping_down_events, + initialDampingStats.post_damping_down_events); + EXPECT_EQ(currDampingStats.last_advertised_up_event_timestamp, + initialDampingStats.last_advertised_up_event_timestamp); + EXPECT_EQ(currDampingStats.last_advertised_down_event_timestamp, + initialDampingStats.last_advertised_down_event_timestamp); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), + SAI_PORT_OPER_STATUS_DOWN); + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +// Tests that when damping timer is expired and accumulated penalty after decay +// is below reuse threshold, damping is disabled on the port. If current port +// operational status is not same as advertised state, advertise it. +TEST_F(PortLinkEventDamperTest, + DampingTimerExpirationDisablesDampingOnPortAndAdvertiseState) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + perPortDamperPeer.setDampingConfigEnabled(/*state=*/true); + + activateDampingOnPort(m_portLinkEventDamper, perPortDamperPeer, + m_notificationHandler); + + struct DampingStats initialDampingStats = + m_portLinkEventDamper->getDampingStats(); + + // Set decay interval to half time duration and current accumulated penalty + // less than twice of reuse threshold so that accumulated penalty after decay + // goes below reuse threshold. + perPortDamperPeer.setAccumulatedPenalty( + (perPortDamperPeer.getReuseThreshold() * 3) / 2); + perPortDamperPeer.setLastPenaltyUpdateTimestamp( + getCurrentTimeUsecs() - perPortDamperPeer.getDecayHalfLife()); + perPortDamperPeer.setAdvertisedState(SAI_PORT_OPER_STATUS_DOWN); + perPortDamperPeer.setActualState(SAI_PORT_OPER_STATUS_UP); + + sai_port_oper_status_notification_t data = { + .port_id = kRid, .port_state = perPortDamperPeer.getActualState()}; + + EXPECT_CALL(*m_notificationHandler, + onPortStateChangePostLinkEventDamping( + /*count=*/1, Pointee(EqOperStatus(data)))) + .WillOnce(Return()); + + // Simulate a timer event, which should cause onDampedPortstateChange to be + // called and the actual state to be advertised. + m_portLinkEventDamper->handleSelectableEvent(); + + EXPECT_FALSE(perPortDamperPeer.getTimerActive()); + EXPECT_FALSE(perPortDamperPeer.getDampingActive()); + EXPECT_TRUE(perPortDamperPeer.getDampingConfigEnabled()); + EXPECT_LE(perPortDamperPeer.getAccumulatedPenalty(), + perPortDamperPeer.getReuseThreshold()); + EXPECT_EQ(perPortDamperPeer.getActualState(), SAI_PORT_OPER_STATUS_UP); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), SAI_PORT_OPER_STATUS_UP); + + struct DampingStats currDampingStats = + m_portLinkEventDamper->getDampingStats(); + EXPECT_EQ(currDampingStats.post_damping_up_events, + initialDampingStats.post_damping_up_events + 1); + EXPECT_EQ(currDampingStats.post_damping_down_events, + initialDampingStats.post_damping_down_events); + EXPECT_NE(currDampingStats.last_advertised_up_event_timestamp, + initialDampingStats.last_advertised_up_event_timestamp); + EXPECT_EQ(currDampingStats.last_advertised_down_event_timestamp, + initialDampingStats.last_advertised_down_event_timestamp); + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +// Tests that when damping timer is expired and accumulated penalty after decay +// is below reuse threshold, damping is disabled on the port. If current port +// operational status is same as advertised state, don't advertise it. +TEST_F(PortLinkEventDamperTest, + DampingTimerExpirationDisablesDampingOnPortAndDoesNotAdvertiseState) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + perPortDamperPeer.setDampingConfigEnabled(/*state=*/true); + + activateDampingOnPort(m_portLinkEventDamper, perPortDamperPeer, + m_notificationHandler); + + struct DampingStats initialDampingStats = + m_portLinkEventDamper->getDampingStats(); + + // Set decay interval to 1 half time duration and current accumulated penalty + // such that accumulated penalty after decay goes below reuse threshold. + perPortDamperPeer.setAccumulatedPenalty( + (perPortDamperPeer.getReuseThreshold() * 3) / 2); + perPortDamperPeer.setLastPenaltyUpdateTimestamp( + getCurrentTimeUsecs() - perPortDamperPeer.getDecayHalfLife()); + perPortDamperPeer.setAdvertisedState(SAI_PORT_OPER_STATUS_DOWN); + perPortDamperPeer.setActualState(SAI_PORT_OPER_STATUS_DOWN); + + EXPECT_CALL(*m_notificationHandler, onPortStateChangePostLinkEventDamping(/*count=*/1, _)) + .Times(0); + // Simulate a timer event. + m_portLinkEventDamper->handleSelectableEvent(); + + EXPECT_FALSE(perPortDamperPeer.getTimerActive()); + EXPECT_FALSE(perPortDamperPeer.getDampingActive()); + EXPECT_TRUE(perPortDamperPeer.getDampingConfigEnabled()); + EXPECT_LE(perPortDamperPeer.getAccumulatedPenalty(), + perPortDamperPeer.getReuseThreshold()); + EXPECT_EQ(perPortDamperPeer.getActualState(), SAI_PORT_OPER_STATUS_DOWN); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), + SAI_PORT_OPER_STATUS_DOWN); + + struct DampingStats currDampingStats = + m_portLinkEventDamper->getDampingStats(); + EXPECT_EQ(currDampingStats.post_damping_up_events, + initialDampingStats.post_damping_up_events); + EXPECT_EQ(currDampingStats.post_damping_down_events, + initialDampingStats.post_damping_down_events); + EXPECT_EQ(currDampingStats.last_advertised_up_event_timestamp, + initialDampingStats.last_advertised_up_event_timestamp); + EXPECT_EQ(currDampingStats.last_advertised_down_event_timestamp, + initialDampingStats.last_advertised_down_event_timestamp); + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +// Tests port events on damping config disabled port, get advertised +// immediately. +TEST_F(PortLinkEventDamperTest, + VerifyPortEventsAdvertisedImmediatelyOnConfigDisabledPort) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + + constexpr sai_port_oper_status_t kPortStateUp = SAI_PORT_OPER_STATUS_UP; + constexpr sai_port_oper_status_t kPortStateDown = SAI_PORT_OPER_STATUS_DOWN; + struct DampingStats initialDampingStats = + m_portLinkEventDamper->getDampingStats(); + + // Generate few port events. + constexpr int kPortFlapCount = 10; + for (int idx = 0; idx < kPortFlapCount; ++idx) + { + SCOPED_TRACE(::testing::Message() << "Testing iteration: " << idx); + sai_port_oper_status_notification_t data = {.port_id = kRid, + .port_state = kPortStateUp}; + EXPECT_CALL(*m_notificationHandler, + onPortStateChangePostLinkEventDamping( + /*count=*/1, Pointee(EqOperStatus(data)))) + .WillOnce(Return()); + + m_portLinkEventDamper->handlePortStateChange(kPortStateUp); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), kPortStateUp); + EXPECT_EQ(perPortDamperPeer.getActualState(), kPortStateUp); + + data.port_state = kPortStateDown; + EXPECT_CALL(*m_notificationHandler, + onPortStateChangePostLinkEventDamping( + /*count=*/1, Pointee(EqOperStatus(data)))) + .WillOnce(Return()); + + m_portLinkEventDamper->handlePortStateChange(kPortStateDown); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), kPortStateDown); + EXPECT_EQ(perPortDamperPeer.getActualState(), kPortStateDown); + } + + EXPECT_FALSE(perPortDamperPeer.getDampingActive()); + EXPECT_FALSE(perPortDamperPeer.getTimerActive()); + EXPECT_FALSE(perPortDamperPeer.getDampingConfigEnabled()); + EXPECT_EQ(perPortDamperPeer.getAccumulatedPenalty(), 0); + + struct DampingStats currDampingStats = + m_portLinkEventDamper->getDampingStats(); + EXPECT_EQ(currDampingStats.pre_damping_up_events, + initialDampingStats.pre_damping_up_events + kPortFlapCount); + EXPECT_EQ(currDampingStats.post_damping_up_events, + initialDampingStats.post_damping_up_events + kPortFlapCount); + EXPECT_EQ(currDampingStats.pre_damping_down_events, + initialDampingStats.pre_damping_down_events + kPortFlapCount); + EXPECT_EQ(currDampingStats.post_damping_down_events, + initialDampingStats.post_damping_down_events + kPortFlapCount); + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +// Tests when damping is not active on port and an UP event is observed, +// advertise the UP event immediately. +TEST_F(PortLinkEventDamperTest, UpEventWhileDampingNotActiveAdvertisesUpEvent) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + perPortDamperPeer.setDampingConfigEnabled(/*state=*/true); + + // Set the test scenarios. + perPortDamperPeer.setAdvertisedState(SAI_PORT_OPER_STATUS_DOWN); + perPortDamperPeer.setActualState(SAI_PORT_OPER_STATUS_DOWN); + + constexpr sai_port_oper_status_t kPortStateUp = SAI_PORT_OPER_STATUS_UP; + uint32_t prev_penalty = perPortDamperPeer.getAccumulatedPenalty(); + + sai_port_oper_status_notification_t data = {.port_id = kRid, + .port_state = kPortStateUp}; + EXPECT_CALL(*m_notificationHandler, + onPortStateChangePostLinkEventDamping( + /*count=*/1, Pointee(EqOperStatus(data)))) + .WillOnce(Return()); + + struct DampingStats initialDampingStats = + m_portLinkEventDamper->getDampingStats(); + m_portLinkEventDamper->handlePortStateChange(kPortStateUp); + + EXPECT_FALSE(perPortDamperPeer.getTimerActive()); + EXPECT_FALSE(perPortDamperPeer.getDampingActive()); + EXPECT_TRUE(perPortDamperPeer.getDampingConfigEnabled()); + EXPECT_EQ(perPortDamperPeer.getAccumulatedPenalty(), prev_penalty); + + struct DampingStats finalDampingStats = + m_portLinkEventDamper->getDampingStats(); + EXPECT_EQ(finalDampingStats.pre_damping_up_events, + initialDampingStats.pre_damping_up_events + 1); + EXPECT_EQ(finalDampingStats.post_damping_up_events, + initialDampingStats.post_damping_up_events + 1); + EXPECT_NE(finalDampingStats.last_advertised_up_event_timestamp, + initialDampingStats.last_advertised_up_event_timestamp); + EXPECT_EQ(finalDampingStats.pre_damping_down_events, + initialDampingStats.pre_damping_down_events); + EXPECT_EQ(finalDampingStats.post_damping_down_events, + initialDampingStats.post_damping_down_events); + EXPECT_EQ(finalDampingStats.last_advertised_down_event_timestamp, + initialDampingStats.last_advertised_down_event_timestamp); + EXPECT_EQ(perPortDamperPeer.getActualState(), kPortStateUp); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), kPortStateUp); + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +// Tests when damping is not active on port and next DOWN event doesn't activate +// damping on port, advertise the DOWN event immediately. +TEST_F(PortLinkEventDamperTest, + DownEventWhileDampingNotActiveDoesNotStartDamping) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + perPortDamperPeer.setDampingConfigEnabled(/*state=*/true); + + // Set the test scenarios. + perPortDamperPeer.setAdvertisedState(SAI_PORT_OPER_STATUS_UP); + perPortDamperPeer.setActualState(SAI_PORT_OPER_STATUS_UP); + perPortDamperPeer.setAccumulatedPenalty(0); + perPortDamperPeer.setLastPenaltyUpdateTimestamp( + getCurrentTimeUsecs() - perPortDamperPeer.getDecayHalfLife()); + + constexpr sai_port_oper_status_t kPortStateDown = SAI_PORT_OPER_STATUS_DOWN; + sai_port_oper_status_notification_t data = {.port_id = kRid, + .port_state = kPortStateDown}; + + EXPECT_CALL(*m_notificationHandler, + onPortStateChangePostLinkEventDamping( + /*count=*/1, Pointee(EqOperStatus(data)))) + .WillOnce(Return()); + + struct DampingStats initialDampingStats = + m_portLinkEventDamper->getDampingStats(); + m_portLinkEventDamper->handlePortStateChange(kPortStateDown); + + EXPECT_FALSE(perPortDamperPeer.getTimerActive()); + EXPECT_FALSE(perPortDamperPeer.getDampingActive()); + EXPECT_TRUE(perPortDamperPeer.getDampingConfigEnabled()); + EXPECT_LT(perPortDamperPeer.getAccumulatedPenalty(), + perPortDamperPeer.getSuppressThreshold()); + + struct DampingStats finalDampingStats = + m_portLinkEventDamper->getDampingStats(); + EXPECT_EQ(finalDampingStats.pre_damping_down_events, + initialDampingStats.pre_damping_down_events + 1); + EXPECT_EQ(finalDampingStats.post_damping_down_events, + initialDampingStats.post_damping_down_events + 1); + EXPECT_NE(finalDampingStats.last_advertised_down_event_timestamp, + initialDampingStats.last_advertised_down_event_timestamp); + EXPECT_EQ(finalDampingStats.pre_damping_up_events, + initialDampingStats.pre_damping_up_events); + EXPECT_EQ(finalDampingStats.post_damping_up_events, + initialDampingStats.post_damping_up_events); + EXPECT_EQ(finalDampingStats.last_advertised_up_event_timestamp, + initialDampingStats.last_advertised_up_event_timestamp); + EXPECT_EQ(perPortDamperPeer.getActualState(), kPortStateDown); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), kPortStateDown); + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +// Tests when damping is not active on port and next DOWN event activates +// damping on port, start damping and advertise the DOWN event immediately. +TEST_F(PortLinkEventDamperTest, DownEventWhileDampingNotActiveStartsDamping) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + perPortDamperPeer.setDampingConfigEnabled(/*state=*/true); + + // Set the test scenarios. + perPortDamperPeer.setAccumulatedPenalty( + perPortDamperPeer.getSuppressThreshold()); + // Set timestamp of last penalty read pretty close to current time so that + // decay does not happen a lot. + perPortDamperPeer.setLastPenaltyUpdateTimestamp(getCurrentTimeUsecs() - + 5000); + perPortDamperPeer.setAdvertisedState(SAI_PORT_OPER_STATUS_UP); + perPortDamperPeer.setActualState(SAI_PORT_OPER_STATUS_UP); + + constexpr sai_port_oper_status_t kPortStateDown = SAI_PORT_OPER_STATUS_DOWN; + sai_port_oper_status_notification_t data = {.port_id = kRid, + .port_state = kPortStateDown}; + + EXPECT_CALL(*m_notificationHandler, + onPortStateChangePostLinkEventDamping( + /*count=*/1, Pointee(EqOperStatus(data)))) + .WillOnce(Return()); + + struct DampingStats initialDampingStats = + m_portLinkEventDamper->getDampingStats(); + m_portLinkEventDamper->handlePortStateChange(kPortStateDown); + + EXPECT_TRUE(perPortDamperPeer.getTimerActive()); + EXPECT_TRUE(perPortDamperPeer.getDampingActive()); + EXPECT_TRUE(perPortDamperPeer.getDampingConfigEnabled()); + EXPECT_GE(perPortDamperPeer.getAccumulatedPenalty(), + perPortDamperPeer.getSuppressThreshold()); + + struct DampingStats finalDampingStats = + m_portLinkEventDamper->getDampingStats(); + EXPECT_EQ(finalDampingStats.pre_damping_down_events, + initialDampingStats.pre_damping_down_events + 1); + EXPECT_EQ(finalDampingStats.post_damping_down_events, + initialDampingStats.post_damping_down_events + 1); + EXPECT_NE(finalDampingStats.last_advertised_down_event_timestamp, + initialDampingStats.last_advertised_down_event_timestamp); + EXPECT_EQ(finalDampingStats.pre_damping_up_events, + initialDampingStats.pre_damping_up_events); + EXPECT_EQ(finalDampingStats.post_damping_up_events, + initialDampingStats.post_damping_up_events); + EXPECT_EQ(finalDampingStats.last_advertised_up_event_timestamp, + initialDampingStats.last_advertised_up_event_timestamp); + EXPECT_EQ(perPortDamperPeer.getActualState(), kPortStateDown); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), kPortStateDown); + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +// Tests when damping is active on port and an UP event is observed, UP event is +// damped. +TEST_F(PortLinkEventDamperTest, UpEventWhileDampingActiveIsDamped) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + perPortDamperPeer.setDampingConfigEnabled(/*state=*/true); + + activateDampingOnPort(m_portLinkEventDamper, perPortDamperPeer, + m_notificationHandler); + + struct DampingStats initialDampingStats = + m_portLinkEventDamper->getDampingStats(); + + uint32_t prev_penalty = perPortDamperPeer.getAccumulatedPenalty(); + constexpr sai_port_oper_status_t kPortStateUp = SAI_PORT_OPER_STATUS_UP; + + EXPECT_CALL(*m_notificationHandler, onPortStateChangePostLinkEventDamping(/*count=*/1, _)) + .Times(0); + + m_portLinkEventDamper->handlePortStateChange(kPortStateUp); + + EXPECT_TRUE(perPortDamperPeer.getTimerActive()); + EXPECT_TRUE(perPortDamperPeer.getDampingActive()); + EXPECT_TRUE(perPortDamperPeer.getDampingConfigEnabled()); + EXPECT_EQ(perPortDamperPeer.getAccumulatedPenalty(), prev_penalty); + + struct DampingStats finalDampingStats = + m_portLinkEventDamper->getDampingStats(); + EXPECT_EQ(finalDampingStats.pre_damping_up_events, + initialDampingStats.pre_damping_up_events + 1); + EXPECT_EQ(finalDampingStats.post_damping_up_events, + initialDampingStats.post_damping_up_events); + EXPECT_EQ(finalDampingStats.last_advertised_up_event_timestamp, + initialDampingStats.last_advertised_up_event_timestamp); + EXPECT_EQ(perPortDamperPeer.getActualState(), kPortStateUp); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), + SAI_PORT_OPER_STATUS_DOWN); + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +// Tests when damping is active on port and an DOWN event is observed, DOWN +// event is damped. +TEST_F(PortLinkEventDamperTest, DownEventWhileDampingActiveIsDamped) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + perPortDamperPeer.setDampingConfigEnabled(/*state=*/true); + + activateDampingOnPort(m_portLinkEventDamper, perPortDamperPeer, + m_notificationHandler); + + struct DampingStats initialDampingStats = + m_portLinkEventDamper->getDampingStats(); + + perPortDamperPeer.setAdvertisedState(SAI_PORT_OPER_STATUS_UP); + perPortDamperPeer.setActualState(SAI_PORT_OPER_STATUS_UP); + + constexpr sai_port_oper_status_t kPortStateDown = SAI_PORT_OPER_STATUS_DOWN; + + EXPECT_CALL(*m_notificationHandler, onPortStateChangePostLinkEventDamping(/*count=*/1, _)) + .Times(0); + + m_portLinkEventDamper->handlePortStateChange(kPortStateDown); + + EXPECT_TRUE(perPortDamperPeer.getTimerActive()); + EXPECT_TRUE(perPortDamperPeer.getDampingActive()); + EXPECT_TRUE(perPortDamperPeer.getDampingConfigEnabled()); + EXPECT_GT(perPortDamperPeer.getAccumulatedPenalty(), + perPortDamperPeer.getReuseThreshold()); + + struct DampingStats finalDampingStats = + m_portLinkEventDamper->getDampingStats(); + EXPECT_EQ(finalDampingStats.pre_damping_down_events, + initialDampingStats.pre_damping_down_events + 1); + EXPECT_EQ(finalDampingStats.post_damping_down_events, + initialDampingStats.post_damping_down_events); + EXPECT_EQ(finalDampingStats.last_advertised_down_event_timestamp, + initialDampingStats.last_advertised_down_event_timestamp); + EXPECT_EQ(perPortDamperPeer.getActualState(), kPortStateDown); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), SAI_PORT_OPER_STATUS_UP); + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +// Tests if config update is happening with same config that is already running, +// update operation will be no-op. +TEST_F(PortLinkEventDamperTest, UpdatingSameLinkEventDampingConfigIsNoOp) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + + m_portLinkEventDamper->updateLinkEventDampingConfig(kDefaultConfig); + + // State after updating same config should not change anything. + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +class PortLinkEventDamperDisabledConfigTest + : public PortLinkEventDamperTest, + public testing::WithParamInterface< + sai_redis_link_event_damping_algo_aied_config_t> {}; + +std::string PortLinkEventDamperDisabledConfigTestCaseName( + _In_ const testing::TestParamInfo< + sai_redis_link_event_damping_algo_aied_config_t> &info) +{ + SWSS_LOG_ENTER(); + + std::stringstream ss; + if (info.param.max_suppress_time == 0) { + ss << "MaxSuppresssTimeIsZero"; + } + + if (info.param.suppress_threshold == 0) { + ss << "SuppressThresholdIsZero"; + } + + if (info.param.reuse_threshold == 0) { + ss << "ReuseThresholdIsZero"; + } + + if (info.param.decay_half_life == 0) { + ss << "DecayHalfLifeIsZero"; + } + + if (info.param.flap_penalty == 0) { + ss << "FlapPenaltyIsZero"; + } + + return ss.str(); +} + +// Update link event damping config. +TEST_P(PortLinkEventDamperDisabledConfigTest, UpdateLinkEventDampingConfigFewTimes) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + + // Update the damping config. + sai_redis_link_event_damping_algo_aied_config_t config = kDefaultConfig; + config.reuse_threshold = 1100; + sai_port_oper_status_t currentActualState = perPortDamperPeer.getActualState(); + + m_portLinkEventDamper->updateLinkEventDampingConfig(config); + + { + SCOPED_TRACE(::testing::Message() + << "Max suppress time(msec): " << config.max_suppress_time + << ", suppress threshold: " << config.suppress_threshold + << ", reuse threshold: " << config.reuse_threshold + << ", half life(msec): " << config.decay_half_life + << ", flap penalty: " << config.flap_penalty); + + verifyStatesAfterLinkEventDampingConfigUpdate( + perPortDamperPeer, config, currentActualState, + /*damping_config_enabled=*/true); + + EXPECT_EQ(perPortDamperPeer.getPenaltyCeiling(), 3111); + } + + // Set actual state and advertise state different to trigger advertisement of + // state. + activateDampingOnPort(m_portLinkEventDamper, perPortDamperPeer, + m_notificationHandler); + + perPortDamperPeer.setAdvertisedState(SAI_PORT_OPER_STATUS_DOWN); + perPortDamperPeer.setActualState(SAI_PORT_OPER_STATUS_UP); + + currentActualState = SAI_PORT_OPER_STATUS_UP; + + sai_port_oper_status_notification_t data = { + .port_id = kRid, .port_state = currentActualState}; + + EXPECT_CALL(*m_notificationHandler, + onPortStateChangePostLinkEventDamping(/*count=*/1, Pointee(EqOperStatus(data)))) + .WillOnce(Return()); + + // Update the damping config. + config.reuse_threshold = 1300; + config.max_suppress_time = config.decay_half_life * 2; + m_portLinkEventDamper->updateLinkEventDampingConfig(config); + + { + SCOPED_TRACE(::testing::Message() + << "Max suppress time(msec): " << config.max_suppress_time + << ", suppress threshold: " << config.suppress_threshold + << ", reuse threshold: " << config.reuse_threshold + << ", half life(msec): " << config.decay_half_life + << ", flap penalty: " << config.flap_penalty); + + verifyStatesAfterLinkEventDampingConfigUpdate( + perPortDamperPeer, config, currentActualState, + /*damping_config_enabled=*/true); + + EXPECT_EQ(perPortDamperPeer.getPenaltyCeiling(), 5200); + } + + // Update with default damping with disabled config. + config = GetParam(); + m_portLinkEventDamper->updateLinkEventDampingConfig(config); + + { + SCOPED_TRACE(::testing::Message() + << "Max suppress time(msec): " << config.max_suppress_time + << ", suppress threshold: " << config.suppress_threshold + << ", reuse threshold: " << config.reuse_threshold + << ", half life(msec): " << config.decay_half_life + << ", flap penalty: " << config.flap_penalty); + + verifyStatesAfterLinkEventDampingConfigUpdate( + perPortDamperPeer, config, currentActualState, + /*damping_config_enabled=*/false); + + EXPECT_EQ(perPortDamperPeer.getPenaltyCeiling(), 0); + } + + // Update with default damping config and it will enable the config. + perPortDamperPeer.setTimerActive(true); + m_portLinkEventDamper->updateLinkEventDampingConfig(kDefaultConfig); + + { + SCOPED_TRACE(::testing::Message() + << "Max suppress time(msec): " << kDefaultConfig.max_suppress_time + << ", suppress threshold: " << kDefaultConfig.suppress_threshold + << ", reuse threshold: " << kDefaultConfig.reuse_threshold + << ", half life(msec): " << kDefaultConfig.decay_half_life + << ", flap penalty: " << kDefaultConfig.flap_penalty); + + verifyStatesAfterLinkEventDampingConfigUpdate( + perPortDamperPeer, kDefaultConfig, currentActualState, + /*damping_config_enabled=*/true); + + EXPECT_EQ(perPortDamperPeer.getPenaltyCeiling(), + kExpectedPenaltyCeiling); + } + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +TEST_P(PortLinkEventDamperDisabledConfigTest, + LinkEventDampingSetupSucceedsWithDisabledConfig) +{ + PortLinkEventDamperPeer perPortDamperPeer(m_portLinkEventDamper); + verifyStatesAfterPortDamperCreation(perPortDamperPeer); + + sai_redis_link_event_damping_algo_aied_config_t param = GetParam(); + perPortDamperPeer.setMaxSuppressTime(param.max_suppress_time); + perPortDamperPeer.setSuppressThreshold(param.suppress_threshold); + perPortDamperPeer.setReuseThreshold(param.reuse_threshold); + perPortDamperPeer.setDecayHalfLife(param.decay_half_life); + perPortDamperPeer.setFlapPenalty(param.flap_penalty); + + EXPECT_CALL(*m_notificationHandler, onPortStateChangePostLinkEventDamping(/*count=*/1, _)) + .Times(0); + + m_portLinkEventDamper->setup(); + + EXPECT_EQ(perPortDamperPeer.getPenaltyCeiling(), 0); + EXPECT_FALSE(perPortDamperPeer.getDampingConfigEnabled()); + EXPECT_EQ(perPortDamperPeer.getAdvertisedState(), + SAI_PORT_OPER_STATUS_UNKNOWN); + EXPECT_EQ(perPortDamperPeer.getActualState(), + SAI_PORT_OPER_STATUS_UNKNOWN); + EXPECT_TRUE(perPortDamperPeer.getTimerAddedToSelect()); + + setUpLinkEventDamperDestruction(perPortDamperPeer, m_notificationHandler); +} + +INSTANTIATE_TEST_SUITE_P(PortLinkEventDamperTestInstantiation, + PortLinkEventDamperDisabledConfigTest, + ::testing::Values( + (sai_redis_link_event_damping_algo_aied_config_t){ + .max_suppress_time = 0, + .suppress_threshold = 100, + .reuse_threshold = 100, + .decay_half_life = 100, + .flap_penalty = 100}, + (sai_redis_link_event_damping_algo_aied_config_t){ + .max_suppress_time = 100, + .suppress_threshold = 0, + .reuse_threshold = 100, + .decay_half_life = 100, + .flap_penalty = 100}, + (sai_redis_link_event_damping_algo_aied_config_t){ + .max_suppress_time = 100, + .suppress_threshold = 100, + .reuse_threshold = 0, + .decay_half_life = 100, + .flap_penalty = 100}, + (sai_redis_link_event_damping_algo_aied_config_t){ + .max_suppress_time = 100, + .suppress_threshold = 100, + .reuse_threshold = 100, + .decay_half_life = 0, + .flap_penalty = 100}, + (sai_redis_link_event_damping_algo_aied_config_t){ + .max_suppress_time = 100, + .suppress_threshold = 100, + .reuse_threshold = 100, + .decay_half_life = 100, + .flap_penalty = 0}), + PortLinkEventDamperDisabledConfigTestCaseName); + +} // namespace +} // namespace syncd