diff --git a/lib/ClientSai.cpp b/lib/ClientSai.cpp index 63bc2392e2..06829109b8 100644 --- a/lib/ClientSai.cpp +++ b/lib/ClientSai.cpp @@ -240,7 +240,8 @@ sai_status_t ClientSai::set( SWSS_LOG_ENTER(); REDIS_CHECK_API_INITIALIZED(); - if (RedisRemoteSaiInterface::isRedisAttribute(objectType, attr)) + if (RedisRemoteSaiInterface::isRedisAttribute(objectType, attr) || + RedisRemoteSaiInterface::isRedisPortAttribute(objectType, attr)) { SWSS_LOG_ERROR("sairedis extension attributes are not supported in CLIENT mode"); diff --git a/lib/RedisRemoteSaiInterface.cpp b/lib/RedisRemoteSaiInterface.cpp index dabd7ad0b0..be7c587d59 100644 --- a/lib/RedisRemoteSaiInterface.cpp +++ b/lib/RedisRemoteSaiInterface.cpp @@ -532,6 +532,83 @@ sai_status_t RedisRemoteSaiInterface::setRedisExtensionAttribute( return SAI_STATUS_FAILURE; } +sai_status_t RedisRemoteSaiInterface::setLinkEventDampingConfig( + _In_ sai_object_type_t objectType, + _In_ sai_object_id_t objectId, + _In_ const std::vector &values) +{ + SWSS_LOG_ENTER(); + + std::string key = sai_serialize_object_type(objectType) + ":" + sai_serialize_object_id(objectId); + + m_communicationChannel->set(key, values, REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET); + + if (m_syncMode) + { + swss::KeyOpFieldsValuesTuple kco; + auto status = m_communicationChannel->wait(REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET, kco); + + m_recorder->recordGenericSetResponse(status); + + return status; + } + + return SAI_STATUS_SUCCESS; +} + +sai_status_t RedisRemoteSaiInterface::setRedisPortExtensionAttribute( + _In_ sai_object_type_t objectType, + _In_ sai_object_id_t objectId, + _In_ const sai_attribute_t *attr) +{ + SWSS_LOG_ENTER(); + + if (attr == nullptr) + { + SWSS_LOG_ERROR("attr pointer is null"); + + return SAI_STATUS_INVALID_PARAMETER; + } + + std::string str_attr_id = sai_serialize_redis_port_attr_id( + static_cast(attr->id)); + + switch (attr->id) + { + case SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM: + { + std::string str_attr_value = sai_serialize_redis_link_event_damping_algorithm( + static_cast(attr->value.s32)); + + return setLinkEventDampingConfig( + objectType, objectId, {swss::FieldValueTuple(str_attr_id, str_attr_value)}); + } + case SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGO_AIED_CONFIG: + { + sai_redis_link_event_damping_algo_aied_config_t *config = + (sai_redis_link_event_damping_algo_aied_config_t *)attr->value.ptr; + + if (config == NULL) + { + SWSS_LOG_ERROR("invalid link damping config attr value NULL"); + + return SAI_STATUS_INVALID_PARAMETER; + } + + std::string str_attr_value = sai_serialize_redis_link_event_damping_aied_config(*config); + + return setLinkEventDampingConfig( + objectType, objectId, {swss::FieldValueTuple(str_attr_id, str_attr_value)}); + } + default: + break; + } + + SWSS_LOG_ERROR("unknown redis port extension attribute: %d", attr->id); + + return SAI_STATUS_INVALID_PARAMETER; +} + bool RedisRemoteSaiInterface::isSaiS8ListValidString( _In_ const sai_s8_list_t &s8list) { @@ -666,6 +743,11 @@ sai_status_t RedisRemoteSaiInterface::set( return setRedisExtensionAttribute(objectType, objectId, attr); } + if (RedisRemoteSaiInterface::isRedisPortAttribute(objectType, attr)) + { + return setRedisPortExtensionAttribute(objectType, objectId, attr); + } + auto status = set( objectType, sai_serialize_object_id(objectId), @@ -2201,6 +2283,20 @@ bool RedisRemoteSaiInterface::isRedisAttribute( return true; } +bool RedisRemoteSaiInterface::isRedisPortAttribute( + _In_ sai_object_type_t objectType, + _In_ const sai_attribute_t* attr) +{ + SWSS_LOG_ENTER(); + + if ((objectType != SAI_OBJECT_TYPE_PORT) || (attr == nullptr) || (attr->id < SAI_PORT_ATTR_CUSTOM_RANGE_START) || (attr->id >= SAI_PORT_ATTR_EXTENSIONS_RANGE_BASE)) + { + return false; + } + + return true; +} + void RedisRemoteSaiInterface::handleNotification( _In_ const std::string &name, _In_ const std::string &serializedNotification, diff --git a/lib/RedisRemoteSaiInterface.h b/lib/RedisRemoteSaiInterface.h index 74019eccf8..a6853713f9 100644 --- a/lib/RedisRemoteSaiInterface.h +++ b/lib/RedisRemoteSaiInterface.h @@ -231,6 +231,15 @@ namespace sairedis _In_ sai_object_id_t switchId, _In_ const sai_attribute_t* attr); + /** + * @brief Checks whether attribute is custom SAI_REDIS_PORT attribute. + * + * This function should only be used on port_api set function. + */ + static bool isRedisPortAttribute( + _In_ sai_object_type_t objectType, + _In_ const sai_attribute_t* attr); + void setMeta( _In_ std::weak_ptr meta); @@ -401,6 +410,11 @@ namespace sairedis _In_ sai_object_id_t objectId, _In_ const sai_attribute_t *attr); + sai_status_t setRedisPortExtensionAttribute( + _In_ sai_object_type_t objectType, + _In_ sai_object_id_t objectId, + _In_ const sai_attribute_t *attr); + bool isSaiS8ListValidString( _In_ const sai_s8_list_t &s8list); @@ -428,6 +442,11 @@ namespace sairedis _In_ sai_object_id_t switchId, _In_ const sai_attribute_t *attr); + sai_status_t setLinkEventDampingConfig( + _In_ sai_object_type_t objectType, + _In_ sai_object_id_t objectId, + _In_ const std::vector &values); + void clear_local_state(); sai_switch_notifications_t processNotification( diff --git a/lib/Sai.cpp b/lib/Sai.cpp index 22b33e4764..03f441e560 100644 --- a/lib/Sai.cpp +++ b/lib/Sai.cpp @@ -251,6 +251,12 @@ sai_status_t Sai::set( REDIS_CHECK_CONTEXT(objectId); + if (RedisRemoteSaiInterface::isRedisPortAttribute(objectType, attr)) + { + // skip metadata if attribute is redis extension port attribute. + return context->m_redisSai->set(objectType, objectId, attr); + } + return context->m_meta->set(objectType, objectId, attr); } diff --git a/lib/sairediscommon.h b/lib/sairediscommon.h index 4594cab5b0..c2da2a08e5 100644 --- a/lib/sairediscommon.h +++ b/lib/sairediscommon.h @@ -52,6 +52,8 @@ #define REDIS_ASIC_STATE_COMMAND_OBJECT_TYPE_GET_AVAILABILITY_QUERY "object_type_get_availability_query" #define REDIS_ASIC_STATE_COMMAND_OBJECT_TYPE_GET_AVAILABILITY_RESPONSE "object_type_get_availability_response" +#define REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET "link_event_damping_config_set" + #define REDIS_FLEX_COUNTER_COMMAND_START_POLL "start_poll" #define REDIS_FLEX_COUNTER_COMMAND_STOP_POLL "stop_poll" #define REDIS_FLEX_COUNTER_COMMAND_SET_GROUP "set_counter_group" diff --git a/syncd/NotificationProcessor.cpp b/syncd/NotificationProcessor.cpp index 1bd5ce0b97..20eb3937cb 100644 --- a/syncd/NotificationProcessor.cpp +++ b/syncd/NotificationProcessor.cpp @@ -18,8 +18,10 @@ using namespace saimeta; NotificationProcessor::NotificationProcessor( _In_ std::shared_ptr producer, _In_ std::shared_ptr client, - _In_ std::function synchronizer): + _In_ std::function synchronizer, + _In_ std::function linkEventDampingApplier): m_synchronizer(synchronizer), + m_linkEventDampingApplier(linkEventDampingApplier), m_client(client), m_notifications(producer) { @@ -494,6 +496,9 @@ void NotificationProcessor::process_on_port_state_change( SWSS_LOG_DEBUG("port notification count: %u", count); + // Vector to store filtered notifications (after damping applied) + std::vector filtered_notifications; + for (uint32_t i = 0; i < count; i++) { sai_port_oper_status_notification_t *oper_stat = &data[i]; @@ -520,14 +525,43 @@ void NotificationProcessor::process_on_port_state_change( * Port may be in process of removal. OA may receive notification for VID either * SAI_NULL_OBJECT_ID or non exist at time of processing */ + SWSS_LOG_INFO("Port VID %s state change notification: %s", + sai_serialize_object_id(oper_stat->port_id).c_str(), + sai_serialize_port_oper_status(oper_stat->port_state).c_str()); - SWSS_LOG_INFO("Port VID %s state change notification", - sai_serialize_object_id(oper_stat->port_id).c_str()); - } + // Apply link event damping if configured + bool should_suppress = false; + if (m_linkEventDampingApplier != nullptr && oper_stat->port_id != SAI_NULL_OBJECT_ID) + { + should_suppress = m_linkEventDampingApplier(oper_stat->port_id, oper_stat->port_state); + } - std::string s = sai_serialize_port_oper_status_ntf(count, data); + if (!should_suppress) + { + // Add to filtered notifications + filtered_notifications.push_back(*oper_stat); + SWSS_LOG_INFO("Port state change PROPAGATED: %s -> %s", + sai_serialize_object_id(oper_stat->port_id).c_str(), + sai_serialize_port_oper_status(oper_stat->port_state).c_str()); + } + else + { + SWSS_LOG_INFO("Port state change SUPPRESSED by damping: %s -> %s", + sai_serialize_object_id(oper_stat->port_id).c_str(), + sai_serialize_port_oper_status(oper_stat->port_state).c_str()); + } + } - sendNotification(SAI_SWITCH_NOTIFICATION_NAME_PORT_STATE_CHANGE, s); + // Send only non-suppressed (filtered) notifications + if (!filtered_notifications.empty()) + { + std::string s = sai_serialize_port_oper_status_ntf((uint32_t)filtered_notifications.size(), filtered_notifications.data()); + sendNotification(SAI_SWITCH_NOTIFICATION_NAME_PORT_STATE_CHANGE, s); + } + else + { + SWSS_LOG_DEBUG("All port state changes were suppressed by damping, no notification sent"); + } } void NotificationProcessor::process_on_bfd_session_state_change( diff --git a/syncd/NotificationProcessor.h b/syncd/NotificationProcessor.h index 3ee4a941cf..4d17748663 100644 --- a/syncd/NotificationProcessor.h +++ b/syncd/NotificationProcessor.h @@ -22,7 +22,8 @@ namespace syncd NotificationProcessor( _In_ std::shared_ptr producer, _In_ std::shared_ptr client, - _In_ std::function synchronizer); + _In_ std::function synchronizer, + _In_ std::function linkEventDampingApplier = nullptr); virtual ~NotificationProcessor(); @@ -211,6 +212,12 @@ namespace syncd std::function m_synchronizer; + /** + * @brief Callback function to apply link event damping to port state changes + * Returns true if notification should be suppressed, false if it should be propagated + */ + std::function m_linkEventDampingApplier; + std::shared_ptr m_client; std::shared_ptr m_notifications; diff --git a/syncd/Syncd.cpp b/syncd/Syncd.cpp index d2bc0ba056..64138af182 100644 --- a/syncd/Syncd.cpp +++ b/syncd/Syncd.cpp @@ -40,6 +40,7 @@ #include #include +#include #define DEF_SAI_WARM_BOOT_DATA_FILE "/var/warmboot/sai-warmboot.bin" #define SAI_FAILURE_DUMP_SCRIPT "/usr/bin/sai_failure_dump.sh" @@ -67,7 +68,8 @@ Syncd::Syncd( m_vendorSai(vendorSai), m_veryFirstRun(false), m_enableSyncMode(false), - m_timerWatchdog(cmd->m_watchdogWarnTimeSpan * WD_DELAY_FACTOR) + m_timerWatchdog(cmd->m_watchdogWarnTimeSpan * WD_DELAY_FACTOR), + m_runDampingTimerThread(false) { SWSS_LOG_ENTER(); @@ -134,6 +136,8 @@ Syncd::Syncd( // we need STATE_DB ASIC_DB and COUNTERS_DB m_dbAsic = std::make_shared(m_contextConfig->m_dbAsic, 0); + m_dbState = std::make_shared("STATE_DB", 0); + m_dampingCounterTable = std::make_shared(m_dbState.get(), "LINK_EVENT_DAMPING_STATS"); m_mdioIpcServer = std::make_shared(m_vendorSai, m_commandLineOptions->m_globalContext); if (m_contextConfig->m_zmqEnable) @@ -179,7 +183,11 @@ Syncd::Syncd( m_client = std::make_shared(m_dbAsic); } - m_processor = std::make_shared(m_notifications, m_client, std::bind(&Syncd::syncProcessNotification, this, _1)); + m_processor = std::make_shared( + m_notifications, + m_client, + std::bind(&Syncd::syncProcessNotification, this, _1), + std::bind(&Syncd::applyLinkEventDamping, this, _1, _2)); m_handler = std::make_shared(m_processor); m_sn.onFdbEvent = std::bind(&NotificationHandler::onFdbEvent, m_handler.get(), _1, _2); @@ -263,6 +271,9 @@ Syncd::Syncd( m_breakConfig = BreakConfigParser::parseBreakConfig(m_commandLineOptions->m_breakConfig); + // Start the damping timer thread for proactive timeout enforcement + startDampingTimerThread(); + SWSS_LOG_NOTICE("syncd started"); } @@ -270,7 +281,8 @@ Syncd::~Syncd() { SWSS_LOG_ENTER(); - // empty + // Stop the damping timer thread + stopDampingTimerThread(); } void Syncd::performStartupLogic() @@ -473,6 +485,9 @@ sai_status_t Syncd::processSingleEvent( if (op == REDIS_ASIC_STATE_COMMAND_OBJECT_TYPE_GET_AVAILABILITY_QUERY) return processObjectTypeGetAvailabilityQuery(kco); + if (op == REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET) + return processLinkEventDampingConfigSet(kco); + if (op == REDIS_FLEX_COUNTER_COMMAND_START_POLL) return processFlexCounterEvent(key, SET_COMMAND, kfvFieldsValues(kco)); @@ -842,6 +857,823 @@ sai_status_t Syncd::processStatsStCapabilityQuery( return status; } +sai_status_t Syncd::processLinkEventDampingConfigSet( + _In_ const swss::KeyOpFieldsValuesTuple &kco) +{ + SWSS_LOG_ENTER(); + + auto& key = kfvKey(kco); + auto& values = kfvFieldsValues(kco); + + // Parse the key format: "OBJECT_TYPE:OBJECT_ID" + size_t colon_pos = key.find(":"); + if (colon_pos == std::string::npos) + { + SWSS_LOG_ERROR("invalid key format: %s", key.c_str()); + sendLinkEventDampingConfigResponse(SAI_STATUS_INVALID_PARAMETER); + return SAI_STATUS_INVALID_PARAMETER; + } + + // Extract object type and object ID + std::string strObjectType = key.substr(0, colon_pos); + std::string strObjectId = key.substr(colon_pos + 1); + + sai_object_type_t objectType; + sai_deserialize_object_type(strObjectType, objectType); + + // Link event damping is a software-based feature - validate port exists + if (objectType != SAI_OBJECT_TYPE_PORT) + { + SWSS_LOG_ERROR("invalid object type for link event damping config: %s", + strObjectType.c_str()); + sendLinkEventDampingConfigResponse(SAI_STATUS_INVALID_PARAMETER); + return SAI_STATUS_INVALID_PARAMETER; + } + + sai_object_id_t portVid; + sai_object_id_t portRid; + + sai_deserialize_object_id(strObjectId, portVid); + + // Reject NULL object ID explicitly + if (portVid == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("invalid port VID: NULL object id"); + sendLinkEventDampingConfigResponse(SAI_STATUS_INVALID_PARAMETER); + return SAI_STATUS_INVALID_PARAMETER; + } + + // Validate that the port exists by translating VID to RID + if (!m_translator->tryTranslateVidToRid(portVid, portRid)) + { + SWSS_LOG_ERROR("failed to translate port VID to RID"); + sendLinkEventDampingConfigResponse(SAI_STATUS_INVALID_PARAMETER); + return SAI_STATUS_INVALID_PARAMETER; + } + + // Link event damping is a software-based feature implemented in syncd. + // Store the configuration parameters on the port object so that + // OnPortStateChange can apply the damping algorithm before forwarding notifications. + // The damping parameters will be used to decide whether to suppress link state changes. + sai_status_t status = SAI_STATUS_SUCCESS; + + // Acquire lock to protect damping state + std::lock_guard lock(m_linkEventDampingMutex); + + // Get or create damping state for this port + auto& dampingState = m_portLinkEventDampingStates[portVid]; + + // Process each attribute and apply it to the port + for (const auto& v : values) + { + std::string strAttrId = fvField(v); + std::string strAttrValue = fvValue(v); + + SWSS_LOG_DEBUG("processing link event damping attribute: %s = %s", + strAttrId.c_str(), strAttrValue.c_str()); + + // Deserialize attribute ID + sai_redis_port_attr_t attrId; + sai_deserialize_redis_port_attr_id(strAttrId, attrId); + + // Parse and set the attribute value based on the attribute ID + switch (attrId) + { + case SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM: + { + sai_redis_link_event_damping_algorithm_t algo; + sai_deserialize_redis_link_event_damping_algorithm(strAttrValue, algo); + + SWSS_LOG_INFO("setting link event damping algorithm on port %s: %d", + strObjectId.c_str(), algo); + + // Link event damping is a software-only feature as of now + // Store the configuration locally for use in notification + // processing. + dampingState.algorithm = algo; + + status = SAI_STATUS_SUCCESS; + break; + } + + case SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGO_AIED_CONFIG: + { + // Allocate temporary memory for the config structure + sai_redis_link_event_damping_algo_aied_config_t config{}; + + sai_deserialize_redis_link_event_damping_aied_config(strAttrValue, config); + + SWSS_LOG_INFO("setting link event damping AIED config on port %s: " + "max_suppress_time=%u, suppress_threshold=%u, " + "reuse_threshold=%u, decay_half_life=%u, flap_penalty=%u", + strObjectId.c_str(), config.max_suppress_time, + config.suppress_threshold, config.reuse_threshold, + config.decay_half_life, config.flap_penalty); + + // Link event damping is a software-only feature as of now + // Store the configuration locally for use in notification + // processing. + dampingState.aied_config = config; + + status = SAI_STATUS_SUCCESS; + break; + } + + default: + { + SWSS_LOG_WARN("unknown attribute ID: %d for link event damping", attrId); + status = SAI_STATUS_INVALID_PARAMETER; + break; + } + } + + if (status != SAI_STATUS_SUCCESS && status != SAI_STATUS_INVALID_PARAMETER) + { + // Log error but continue processing other attributes + SWSS_LOG_WARN("error processing link event damping attribute %s: %s", + strAttrId.c_str(), sai_serialize_status(status).c_str()); + break; + } + } + + sendLinkEventDampingConfigResponse(status); + + return status; +} + +void Syncd::sendLinkEventDampingConfigResponse( + _In_ sai_status_t status) +{ + SWSS_LOG_ENTER(); + + // If sync mode is not enabled, do not send response. + if (!m_enableSyncMode) + { + return; + } + + std::string strStatus = sai_serialize_status(status); + + std::vector entry; + + SWSS_LOG_INFO("sending link event damping config response: %s", strStatus.c_str()); + + m_selectableChannel->set(strStatus, entry, REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET); +} + +uint64_t Syncd::getCurrentTimeMs() +{ + SWSS_LOG_ENTER(); + + auto now = std::chrono::steady_clock::now(); + auto duration = now.time_since_epoch(); + return std::chrono::duration_cast(duration).count(); +} + +void Syncd::decayPenalty( + _In_ LinkEventDampingPortState& state, + _In_ uint64_t currentTimeMs) +{ + SWSS_LOG_ENTER(); + + if (state.current_penalty == 0) + { + return; // No penalty to decay + } + + if (state.aied_config.decay_half_life == 0) + { + return; // Invalid configuration, skip decay + } + + // Use last_decay_time to track decay independently from state transitions + // This ensures penalty decays even if no link state changes occur + uint64_t base_time = state.last_decay_time_ms; + if (base_time == 0) + { + // First time calculating decay - use last transition time as base + base_time = state.last_transition_time_ms; + } + + // Calculate elapsed time in milliseconds since last decay + uint64_t elapsed_ms = currentTimeMs - base_time; + + if (elapsed_ms <= 0) + { + return; // No time has elapsed + } + + // Penalty decay formula: P(t) = P0 * (0.5 ^ (t / half_life)) + // We use floating point for the calculation + double half_lives = (double)elapsed_ms / state.aied_config.decay_half_life; + double decay_factor = std::pow(0.5, half_lives); + uint32_t decayed_penalty = (uint32_t)(state.current_penalty * decay_factor); + + // Ensure penalty doesn't go below 0 + if (decayed_penalty < state.current_penalty) + { + state.current_penalty = decayed_penalty; + state.last_decay_time_ms = currentTimeMs; // Update last decay time + SWSS_LOG_DEBUG("Port penalty decayed: %u (half_life=%u ms, elapsed=%lu ms, decay_factor=%f)", + state.current_penalty, state.aied_config.decay_half_life, elapsed_ms, decay_factor); + } + else if (state.last_decay_time_ms == 0) + { + // Initialize decay time on first check + state.last_decay_time_ms = currentTimeMs; + } +} + +bool Syncd::applyAiedAlgorithm( + _In_ sai_object_id_t portVid, + _In_ LinkEventDampingPortState& state, + _In_ sai_port_oper_status_t newStatus, + _In_ uint64_t currentTimeMs) +{ + SWSS_LOG_ENTER(); + + std::string portVidStr = sai_serialize_object_id(portVid); + + // Validate configuration + if ((state.aied_config.decay_half_life == 0) || + (state.aied_config.max_suppress_time == 0) || + (state.aied_config.decay_half_life > state.aied_config.max_suppress_time)) + { + SWSS_LOG_WARN("Port VID %s invalid damping configuration: " + "decay_half_life (%u ms) max_suppress_time (%u ms). Damping disabled.", + portVidStr.c_str(), state.aied_config.decay_half_life, + state.aied_config.max_suppress_time); + return false; // Damping disabled for invalid config + } + + if ((state.aied_config.suppress_threshold == 0) || + (state.aied_config.reuse_threshold == 0) || + (state.aied_config.suppress_threshold <= state.aied_config.reuse_threshold)) + { + SWSS_LOG_WARN("Port VID %s invalid damping configuration: " + "suppress_threshold (%u) reuse_threshold (%u). Damping disabled.", + portVidStr.c_str(), state.aied_config.suppress_threshold, + state.aied_config.reuse_threshold); + return false; // Damping disabled for invalid config + } + + // First, apply penalty decay + decayPenalty(state, currentTimeMs); + + // Track if damping was active before this event + bool was_damping_active_before = state.is_damping_active; + + // Check if a link state change occurred + if (state.physical_status != newStatus) + { + // Link state transitioned + state.pre_damping_link_transitions++; + + if (newStatus == SAI_PORT_OPER_STATUS_UP) + { + state.pre_damping_up_events++; + } + else if (newStatus == SAI_PORT_OPER_STATUS_DOWN) + { + state.pre_damping_down_events++; + // Reset damping timer on DOWN event if damping is already active + if (state.is_damping_active) + { + state.damping_start_time_ms = currentTimeMs; + SWSS_LOG_DEBUG("Damping timer reset on DOWN event: new start time = %lu ms", + currentTimeMs); + } + } + + // Add penalty ONLY on DOWN events (UP -> DOWN) + if (state.physical_status == SAI_PORT_OPER_STATUS_UP && newStatus == SAI_PORT_OPER_STATUS_DOWN) + { + state.current_penalty += state.aied_config.flap_penalty; + + // Calculate penalty ceiling: 2^(max_suppress_time/decay_half_life) * reuse_threshold + double exponent = (double)state.aied_config.max_suppress_time / state.aied_config.decay_half_life; + uint32_t penalty_ceiling = (uint32_t)(std::pow(2.0, exponent) * state.aied_config.reuse_threshold); + + if (state.current_penalty > penalty_ceiling) + { + state.current_penalty = penalty_ceiling; + } + + SWSS_LOG_DEBUG("Port DOWN event: penalty accumulated to %u " + "(penalty_ceiling: %u, flap_penalty: %u)", + state.current_penalty, penalty_ceiling, state.aied_config.flap_penalty); + } + else + { + SWSS_LOG_DEBUG("Port UP event: no penalty added (penalty remains: %u)", + state.current_penalty); + } + + // Update physical status and timestamps + state.physical_status = newStatus; + state.last_transition_time_ms = currentTimeMs; + + // If this is the first state change after damping config was set, + // initialize decay time as well + if (state.last_decay_time_ms == 0) + { + state.last_decay_time_ms = currentTimeMs; + } + + // Check if we should enter damping state + if (state.current_penalty >= state.aied_config.suppress_threshold && + !state.is_damping_active) + { + SWSS_LOG_NOTICE("Port VID %s entering damped state: penalty (%u) >= " + "suppress_threshold (%u) at time %lu ms. Current event will be " + "PROPAGATED, future events will be suppressed.", + portVidStr.c_str(), state.current_penalty, + state.aied_config.suppress_threshold, currentTimeMs); + state.is_damping_active = true; + state.damping_start_time_ms = currentTimeMs; + } + + // Write updated pre-damping counters and physical status to STATE_DB + writeDampingCountersToStateDb(portVid, state); + } + + // Damping exits when EITHER: + // 1. Time-based: damping_duration_ms >= max_suppress_time + // 2. Penalty-based: current_penalty < reuse_threshold (decay-based recovery) + if (state.is_damping_active) + { + // Check timeout - never suppress longer than max_suppress_time + // This is a hard timestamp-based limit to prevent infinite suppression + uint64_t damping_duration_ms = currentTimeMs - state.damping_start_time_ms; + + if (damping_duration_ms >= state.aied_config.max_suppress_time) + { + // Store temporary strings to avoid dangling pointers + std::string physicalStatusStr = sai_serialize_port_oper_status(state.physical_status); + std::string advertisedStatusStr = sai_serialize_port_oper_status(state.advertised_status); + SWSS_LOG_NOTICE("Port VID %s exiting damped state: max suppress time (%u ms) " + "exceeded. Duration: %lu ms. Physical state: %s, Advertised state: %s", + portVidStr.c_str(), state.aied_config.max_suppress_time, + damping_duration_ms, physicalStatusStr.c_str(), + advertisedStatusStr.c_str()); + state.is_damping_active = false; + state.damping_start_time_ms = 0; // Reset timer when exiting damping + + // Propagate last link event when penalty decays below reuse threshold + if (state.advertised_status != state.physical_status) + { + state.pending_state_sync = true; + SWSS_LOG_NOTICE("Port VID %s state mismatch detected on damping " + "exit (timeout): physical=%s, advertised=%s. " + "Marking for state sync on next notification.", + portVidStr.c_str(), physicalStatusStr.c_str(), + advertisedStatusStr.c_str()); + } + state.advertised_status = state.physical_status; + + // Write updated state to STATE_DB after exiting damping + writeDampingCountersToStateDb(portVid, state); + } + // Check reuse threshold - exit if penalty decays below threshold + // Penalty decays based on last_decay_time tracking + else if (state.current_penalty < state.aied_config.reuse_threshold) + { + // Store temporary strings to avoid dangling pointers + std::string physicalStatusStr = sai_serialize_port_oper_status(state.physical_status); + std::string advertisedStatusStr = sai_serialize_port_oper_status(state.advertised_status); + SWSS_LOG_NOTICE("Port VID %s exiting damped state: penalty (%u) < " + "reuse_threshold (%u). Penalty decayed due to exponential decay " + "formula. Physical state: %s, Advertised state: %s", + portVidStr.c_str(), state.current_penalty, + state.aied_config.reuse_threshold, physicalStatusStr.c_str(), + advertisedStatusStr.c_str()); + state.is_damping_active = false; + state.damping_start_time_ms = 0; // Reset timer when exiting damping + + // Propagate last link event when penalty decays below reuse threshold + if (state.advertised_status != state.physical_status) + { + state.pending_state_sync = true; + SWSS_LOG_NOTICE("Port VID %s state mismatch detected on damping " + "exit (decay): physical=%s, advertised=%s. " + "Marking for state sync on next notification.", + portVidStr.c_str(), physicalStatusStr.c_str(), + advertisedStatusStr.c_str()); + } + state.advertised_status = state.physical_status; + + // Write updated state to STATE_DB after exiting damping + writeDampingCountersToStateDb(portVid, state); + } + } + + // Determine if notification should be suppressed + bool should_suppress = false; + + // Only suppress when damping is active AND this is NOT the threshold-crossing event + // The threshold-crossing event itself should be propagated + if (state.is_damping_active && was_damping_active_before) + { + // Calculate current suppression time based on damping algorithm + uint64_t damping_duration_ms = currentTimeMs - state.damping_start_time_ms; + + if (damping_duration_ms < state.aied_config.max_suppress_time) + { + // Calculate expected suppression time + // suppression_time = decay_half_life * log2(reuse_threshold / accumulated_penalty) + if (state.current_penalty > 0 && state.current_penalty >= state.aied_config.reuse_threshold) + { + double suppression_ratio = (double)state.aied_config.reuse_threshold / state.current_penalty; + double expected_suppress_time = state.aied_config.decay_half_life * std::log2(suppression_ratio); + + SWSS_LOG_DEBUG("Port damping active: suppression_time=%.0f ms, " + "max_suppress_time=%u ms, current_penalty=%u, reuse_threshold=%u", + expected_suppress_time, state.aied_config.max_suppress_time, + state.current_penalty, state.aied_config.reuse_threshold); + } + + // Suppress the notification + should_suppress = true; + state.last_suppressed_status = newStatus; // Track what was suppressed + // Store temporary strings to avoid dangling pointers + std::string newStatusStr = sai_serialize_port_oper_status(newStatus); + SWSS_LOG_NOTICE("Port VID %s suppressing port state change notification: " + "new_status=%s (damping active, penalty: %u, duration: %lu ms)", + portVidStr.c_str(), newStatusStr.c_str(), + state.current_penalty, damping_duration_ms); + } + else + { + // Should not happen due to exit check above, but handle gracefully + should_suppress = false; + + SWSS_LOG_WARN("Port VID %s unexpected state: damping_active=true but " + "duration >= max_suppress_time", portVidStr.c_str()); + } + } + else + { + // Damping is NOT active OR this is the threshold-crossing event - propagate the notification + should_suppress = false; + std::string physicalStatusStr = sai_serialize_port_oper_status(state.physical_status); + std::string advertisedStatusStr = sai_serialize_port_oper_status(state.advertised_status); + + // Track advertised transitions + if (newStatus == SAI_PORT_OPER_STATUS_UP) + { + state.post_damping_up_events++; + } + else if (newStatus == SAI_PORT_OPER_STATUS_DOWN) + { + state.post_damping_down_events++; + } + state.post_damping_link_transitions++; + + // SYNC STATE: Update advertised to match new state + state.advertised_status = newStatus; + + // Clear the pending sync flag since state is now synchronized + if (state.pending_state_sync && newStatus == state.physical_status) + { + state.pending_state_sync = false; + SWSS_LOG_INFO("Port state sync completed: physical=%s, advertised=%s", + physicalStatusStr.c_str(), advertisedStatusStr.c_str()); + } + + // Write updated counters to STATE_DB + writeDampingCountersToStateDb(portVid, state); + + // When damping changes state (enabled->disabled), propagate the event + if (was_damping_active_before && !state.is_damping_active) + { + SWSS_LOG_NOTICE("Port VID %s state change PROPAGATED (damping state changed " + "from active to inactive): physical=%s, advertised=%s", + portVidStr.c_str(), physicalStatusStr.c_str(), + advertisedStatusStr.c_str()); + } + else + { + SWSS_LOG_INFO("Port VID %s state change PROPAGATED: physical=%s, advertised=%s (damping inactive)", + portVidStr.c_str(), physicalStatusStr.c_str(), + advertisedStatusStr.c_str()); + } + + } + + return should_suppress; +} + +bool Syncd::applyLinkEventDamping( + _In_ sai_object_id_t portVid, + _In_ sai_port_oper_status_t newStatus) +{ + SWSS_LOG_ENTER(); + + std::lock_guard lock(m_linkEventDampingMutex); + + // Check if damping is configured for this port + auto it = m_portLinkEventDampingStates.find(portVid); + if (it == m_portLinkEventDampingStates.end()) + { + // No damping configured for this port + return false; // Don't suppress + } + + LinkEventDampingPortState& state = it->second; + + // Apply the appropriate damping algorithm + switch (state.algorithm) + { + case SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_AIED: + { + uint64_t currentTimeMs = getCurrentTimeMs(); + return applyAiedAlgorithm(portVid, state, newStatus, currentTimeMs); + } + + case SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_DISABLED: + SWSS_LOG_INFO("Damping algorithm disabled: %d", state.algorithm); + return false; + + default: + SWSS_LOG_WARN("Unknown damping algorithm: %d", state.algorithm); + return false; + } +} + +void Syncd::checkDampedPortsTimeout() +{ + SWSS_LOG_ENTER(); + + std::lock_guard lock(m_linkEventDampingMutex); + + uint64_t currentTimeMs = getCurrentTimeMs(); + + // Iterate through all ports with damping configured + for (auto& kv : m_portLinkEventDampingStates) + { + auto& portVid = kv.first; + auto& state = kv.second; + + // Only check ports that are currently in damped state + if (!state.is_damping_active) + { + continue; + } + + // Check if damping algorithm is enabled + if (state.algorithm != SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_AIED) + { + continue; + } + + std::string portVidStr = sai_serialize_object_id(portVid); + std::string physicalStatusStr = sai_serialize_port_oper_status(state.physical_status); + std::string advertisedStatusStr = sai_serialize_port_oper_status(state.advertised_status); + // Apply penalty decay first - penalty naturally decays over time + decayPenalty(state, currentTimeMs); + + // Check if penalty has decayed below reuse threshold + if (state.current_penalty < state.aied_config.reuse_threshold) + { + SWSS_LOG_NOTICE("Proactive timeout check: Port VID %s exiting damped state: " + "penalty (%u) < reuse_threshold (%u). Penalty decayed due to " + "exponential decay formula. Physical state: %s, Advertised state: %s", + portVidStr.c_str(), state.current_penalty, + state.aied_config.reuse_threshold, physicalStatusStr.c_str(), + advertisedStatusStr.c_str()); + + // Exit damping state + state.is_damping_active = false; + state.damping_start_time_ms = 0; // Reset timer when exiting damping + + // Check if there's a state mismatch that needs to be propagated + if (state.advertised_status != state.physical_status) + { + state.pending_state_sync = true; + + SWSS_LOG_NOTICE("Marked pending sync (decay) for Port VID %s: " + "physical=%s advertised=%s", + portVidStr.c_str(), physicalStatusStr.c_str(), + advertisedStatusStr.c_str()); + } + else + { + SWSS_LOG_INFO("Exited damping with no state mismatch for Port VID %s: ", + portVidStr.c_str()); + } + + // Write updated state to STATE_DB after exiting damping + writeDampingCountersToStateDb(portVid, state); + + // Skip to next port since we have already handled this one + continue; + } + + // Calculate how long the port has been damped + uint64_t damping_duration_ms = currentTimeMs - state.damping_start_time_ms; + + // Check if max_suppress_time has been exceeded + if (damping_duration_ms >= state.aied_config.max_suppress_time) + { + SWSS_LOG_NOTICE("Damping exit (timeout): Port VID %s " + "Duration=%lu >= max=%u, Physical %s, Advertised %s", + portVidStr.c_str(), damping_duration_ms, + state.aied_config.max_suppress_time, + physicalStatusStr.c_str(), advertisedStatusStr.c_str()); + + // Exit damping state + state.is_damping_active = false; + state.damping_start_time_ms = 0; // Reset timer when exiting damping + + // Check if there's a state mismatch that needs to be propagated + if (state.advertised_status != state.physical_status) + { + state.pending_state_sync = true; + + SWSS_LOG_NOTICE("Marked pending sync (timeout) for Port VID %s: " + "physical=%s, advertised=%s.", + portVidStr.c_str(), physicalStatusStr.c_str(), + advertisedStatusStr.c_str()); + } + else + { + SWSS_LOG_INFO("Exited damping with no state mismatch for Port VID %s", + portVidStr.c_str()); + } + + // Write updated state to STATE_DB after exiting damping + writeDampingCountersToStateDb(portVid, state); + } + else + { + // Port is still in damped state + // Updating penalty decay to STATE DB may not be useful. + SWSS_LOG_DEBUG("Proactive timeout check: Port VID %s still damped: " + "penalty=%u, duration=%lu ms", portVidStr.c_str(), + state.current_penalty, damping_duration_ms); + } + } +} + +void Syncd::processPendingDampingSync() +{ + SWSS_LOG_ENTER(); + + std::vector notifications; + + { + std::lock_guard lock(m_linkEventDampingMutex); + + for (auto &kv : m_portLinkEventDampingStates) + { + auto port = kv.first; + auto &state = kv.second; + + if (state.pending_state_sync && + state.advertised_status != state.physical_status) + { + state.pending_state_sync = false; + + state.advertised_status = state.physical_status; + + sai_port_oper_status_notification_t n; + n.port_id = port; + n.port_state = state.physical_status; + + notifications.push_back(n); + writeDampingCountersToStateDb(port, state); + } + } + } + + // ALWAYS send from main thread (safe) + if (!notifications.empty()) + { + std::string s = sai_serialize_port_oper_status_ntf( + (uint32_t)notifications.size(), + notifications.data()); + std::vector entry; + m_notifications->send(SAI_SWITCH_NOTIFICATION_NAME_PORT_STATE_CHANGE, s, entry); + } +} + +void Syncd::dampingTimerThreadFunc() +{ + SWSS_LOG_ENTER(); + SWSS_LOG_NOTICE("Damping timer thread started"); + + while (true) + { + { + std::unique_lock lock(m_dampingTimerMutex); + + // Wait for 1 second or until signaled to stop + if (m_dampingTimerCv.wait_for(lock, std::chrono::seconds(1), [this] { return !m_runDampingTimerThread; })) + { + // Signaled to stop + SWSS_LOG_NOTICE("Damping timer thread received stop signal"); + break; + } + + // Check if still running (in case of spurious wake up) + if (!m_runDampingTimerThread) + { + break; + } + } + + // Perform the proactive timeout check + try + { + checkDampedPortsTimeout(); + } + catch (const std::exception& e) + { + SWSS_LOG_ERROR("Exception in damping timer thread: %s", e.what()); + } + catch (...) + { + SWSS_LOG_ERROR("Unknown exception in damping timer thread"); + } + } + + SWSS_LOG_NOTICE("Damping timer thread stopped"); +} + +void Syncd::startDampingTimerThread() +{ + SWSS_LOG_ENTER(); + + if (m_runDampingTimerThread) + { + SWSS_LOG_WARN("Damping timer thread already running"); + return; + } + + m_runDampingTimerThread = true; + m_dampingTimerThread = std::make_shared(&Syncd::dampingTimerThreadFunc, this); + + SWSS_LOG_NOTICE("Started damping timer thread for proactive max_suppress_time enforcement"); +} + +void Syncd::stopDampingTimerThread() +{ + SWSS_LOG_ENTER(); + + if (!m_runDampingTimerThread) + { + SWSS_LOG_INFO("Damping timer thread not running"); + return; + } + + // Signal the thread to stop + { + std::lock_guard lock(m_dampingTimerMutex); + m_runDampingTimerThread = false; + } + m_dampingTimerCv.notify_one(); + + // Wait for the thread to finish + if (m_dampingTimerThread && m_dampingTimerThread->joinable()) + { + m_dampingTimerThread->join(); + SWSS_LOG_NOTICE("Damping timer thread stopped and joined"); + } + + m_dampingTimerThread.reset(); +} + +void Syncd::writeDampingCountersToStateDb( + _In_ sai_object_id_t portVid, + _In_ const LinkEventDampingPortState& state) +{ + SWSS_LOG_ENTER(); + + // Convert VID to string for STATE_DB key + std::string portVidStr = sai_serialize_object_id(portVid); + + // Prepare counter fields + std::vector fields; + fields.emplace_back("pre_damping_link_transitions", std::to_string(state.pre_damping_link_transitions)); + fields.emplace_back("pre_damping_up_events", std::to_string(state.pre_damping_up_events)); + fields.emplace_back("pre_damping_down_events", std::to_string(state.pre_damping_down_events)); + fields.emplace_back("post_damping_up_events", std::to_string(state.post_damping_up_events)); + fields.emplace_back("post_damping_down_events", std::to_string(state.post_damping_down_events)); + fields.emplace_back("post_damping_link_transitions", std::to_string(state.post_damping_link_transitions)); + + // Add damping state information + fields.emplace_back("is_damping_active", state.is_damping_active ? "true" : "false"); + fields.emplace_back("current_penalty", std::to_string(state.current_penalty)); + fields.emplace_back("damping_start_time_ms", std::to_string(state.damping_start_time_ms)); + fields.emplace_back("physical_status", sai_serialize_port_oper_status(state.physical_status)); + fields.emplace_back("advertised_status", sai_serialize_port_oper_status(state.advertised_status)); + + // Write to STATE_DB + m_dampingCounterTable->set(portVidStr, fields); + + SWSS_LOG_DEBUG("Wrote damping counters to STATE_DB for port %s", portVidStr.c_str()); +} + sai_status_t Syncd::processFdbFlush( _In_ const swss::KeyOpFieldsValuesTuple &kco) { @@ -5884,7 +6716,7 @@ void Syncd::run() { swss::Selectable *sel = NULL; - int result = s->select(&sel); + int result = s->select(&sel, 1000); if (sel == m_restartQuery.get()) { @@ -6000,6 +6832,9 @@ void Syncd::run() { SWSS_LOG_ERROR("select failed: %d", result); } + + // Process if any pending state sync due to link event damping + processPendingDampingSync(); } catch(const std::exception &e) { diff --git a/syncd/Syncd.h b/syncd/Syncd.h index d633c9196d..e802c3b76c 100644 --- a/syncd/Syncd.h +++ b/syncd/Syncd.h @@ -27,9 +27,67 @@ #include "swss/notificationconsumer.h" #include +#include +#include namespace syncd { + /** + * @brief Link event damping configuration and state per port + */ + struct LinkEventDampingPortState + { + // Configuration parameters + sai_redis_link_event_damping_algorithm_t algorithm; + sai_redis_link_event_damping_algo_aied_config_t aied_config; + + // Runtime state for AIED algorithm + uint32_t current_penalty; // Current penalty value + uint64_t last_transition_time_ms; // Timestamp of last transition (milliseconds) + uint64_t last_decay_time_ms; // Timestamp of last decay calculation (milliseconds) + uint64_t damping_start_time_ms; // When damping state started (milliseconds) + bool is_damping_active; // Whether link is currently in damped state + sai_port_oper_status_t physical_status; // Physical port status + sai_port_oper_status_t advertised_status; // Last advertised status (may differ due to damping) + sai_port_oper_status_t last_suppressed_status; // Last event suppressed while damping + bool pending_state_sync; // Flag to indicate state mismatch needs propagation + + // Counters for monitoring + uint64_t pre_damping_link_transitions; + uint64_t pre_damping_up_events; + uint64_t pre_damping_down_events; + uint64_t post_damping_up_events; + uint64_t post_damping_down_events; + uint64_t post_damping_link_transitions; + + // Constructor with defaults + LinkEventDampingPortState() + : algorithm(SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_DISABLED), + current_penalty(0), + last_transition_time_ms(0), + last_decay_time_ms(0), + damping_start_time_ms(0), + is_damping_active(false), + physical_status(SAI_PORT_OPER_STATUS_UNKNOWN), + advertised_status(SAI_PORT_OPER_STATUS_UNKNOWN), + last_suppressed_status(SAI_PORT_OPER_STATUS_UNKNOWN), + pending_state_sync(false), + pre_damping_link_transitions(0), + pre_damping_up_events(0), + pre_damping_down_events(0), + post_damping_up_events(0), + post_damping_down_events(0), + post_damping_link_transitions(0) + { + // Initialize AIED config with defaults + aied_config.max_suppress_time = 0; + aied_config.suppress_threshold = 0; + aied_config.reuse_threshold = 0; + aied_config.decay_half_life = 0; + aied_config.flap_penalty = 0; + } + }; + class Syncd { private: @@ -219,6 +277,86 @@ namespace syncd _In_ const std::vector &values, _In_ bool fromAsicChannel=true); + sai_status_t processLinkEventDampingConfigSet( + _In_ const swss::KeyOpFieldsValuesTuple &kco); + + private: // link event damping helpers + + /** + * @brief Apply link event damping algorithm to a port state change + * @param portVid Virtual object ID of the port + * @param newStatus New operational status of the port + * @return true if notification should be suppressed, false if it should be propagated + */ + bool applyLinkEventDamping( + _In_ sai_object_id_t portVid, + _In_ sai_port_oper_status_t newStatus); + + /** + * @brief Apply AIED damping algorithm + * @param state Port damping state + * @param newStatus New operational status + * @param currentTimeMs Current time in milliseconds + * @return true if should suppress, false if should propagate + */ + bool applyAiedAlgorithm( + _In_ sai_object_id_t portVid, + _In_ LinkEventDampingPortState& state, + _In_ sai_port_oper_status_t newStatus, + _In_ uint64_t currentTimeMs); + + /** + * @brief Decay penalty based on time elapsed + * @param state Port damping state + * @param currentTimeMs Current time in milliseconds + */ + void decayPenalty( + _In_ LinkEventDampingPortState& state, + _In_ uint64_t currentTimeMs); + + /** + * @brief Get current time in milliseconds + * @return Current time in milliseconds since epoch + */ + uint64_t getCurrentTimeMs(); + + /** + * @brief Proactively check all damped ports and enforce max_suppress_time + * Called periodically by timer thread to ensure ports don't exceed max_suppress_time + * even when no new port events arrive + */ + void checkDampedPortsTimeout(); + + /** + * @brief Timer thread function for proactive damping timeout enforcement + * Runs periodically to check if any damped ports have exceeded max_suppress_time + */ + void dampingTimerThreadFunc(); + + /** + * @brief Start the damping timer thread + */ + void startDampingTimerThread(); + + /** + * @brief Stop the damping timer thread + */ + void stopDampingTimerThread(); + + /** + * @brief link status sync notification after damping exit + */ + void processPendingDampingSync(); + + /** + * @brief Write damping counters to STATE_DB for a specific port + * @param portVid Virtual object ID of the port + * @param state Port damping state containing counters + */ + void writeDampingCountersToStateDb( + _In_ sai_object_id_t portVid, + _In_ const LinkEventDampingPortState& state); + private: // process quad oid sai_status_t processOidCreate( @@ -395,6 +533,9 @@ namespace syncd void sendNotifyResponse( _In_ sai_status_t status); + void sendLinkEventDampingConfigResponse( + _In_ sai_status_t status); + private: // snoop get response oids void snoopGetResponse( @@ -547,5 +688,46 @@ namespace syncd TimerWatchdog m_timerWatchdog; std::set m_createdInInitView; + + /** + * @brief Link event damping configuration per port + * Key: Port VID, Value: Damping state and configuration + */ + std::map m_portLinkEventDampingStates; + + /** + * @brief Mutex to protect link event damping state + */ + std::mutex m_linkEventDampingMutex; + + /** + * @brief STATE_DB connection for writing damping counters + */ + std::shared_ptr m_dbState; + + /** + * @brief STATE_DB table for damping counters + */ + std::shared_ptr m_dampingCounterTable; + + /** + * @brief Timer thread for proactive damping timeout enforcement + */ + std::shared_ptr m_dampingTimerThread; + + /** + * @brief Flag to control damping timer thread execution + */ + bool m_runDampingTimerThread; + + /** + * @brief Condition variable for damping timer thread + */ + std::condition_variable m_dampingTimerCv; + + /** + * @brief Mutex for damping timer thread synchronization + */ + std::mutex m_dampingTimerMutex; }; } diff --git a/syncd/tests/Makefile.am b/syncd/tests/Makefile.am index 2630eecdc9..f548d69c27 100644 --- a/syncd/tests/Makefile.am +++ b/syncd/tests/Makefile.am @@ -5,7 +5,7 @@ LDADD_GTEST = -L/usr/src/gtest -lgtest -lgtest_main bin_PROGRAMS = tests tests_SOURCES = \ - main.cpp TestSyncdBrcm.cpp TestSyncdMlnx.cpp TestSyncdNvdaBf.cpp TestSyncdLib.cpp TestDisabledRedisClient.cpp + main.cpp TestSyncdBrcm.cpp TestSyncdMlnx.cpp TestSyncdNvdaBf.cpp TestSyncdLib.cpp TestSyncdLinkEventDamping.cpp TestDisabledRedisClient.cpp tests_CXXFLAGS = \ $(DBGFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS_COMMON) tests_LDADD = \ diff --git a/syncd/tests/TestSyncdLinkEventDamping.cpp b/syncd/tests/TestSyncdLinkEventDamping.cpp new file mode 100644 index 0000000000..4e298cd749 --- /dev/null +++ b/syncd/tests/TestSyncdLinkEventDamping.cpp @@ -0,0 +1,419 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include "swss/select.h" + +#include "Sai.h" +#include "Syncd.h" +#include "MetadataLogger.h" + +#include "TestSyncdLib.h" + +#include "meta/sai_serialize.h" +#include "sairediscommon.h" +#include "meta/RedisSelectableChannel.h" + +using namespace syncd; + +static const char* profile_get_value( + _In_ sai_switch_profile_id_t profile_id, + _In_ const char* variable) +{ + SWSS_LOG_ENTER(); + + return NULL; +} + +static int profile_get_next_value( + _In_ sai_switch_profile_id_t profile_id, + _Out_ const char** variable, + _Out_ const char** value) +{ + SWSS_LOG_ENTER(); + + if (value == NULL) + { + SWSS_LOG_INFO("resetting profile map iterator"); + return 0; + } + + if (variable == NULL) + { + SWSS_LOG_WARN("variable is null"); + return -1; + } + + SWSS_LOG_INFO("iterator reached end"); + return -1; +} + +static sai_service_method_table_t test_services = { + profile_get_value, + profile_get_next_value +}; + +void syncdLinkEventDampingWorkerThread() +{ + SWSS_LOG_ENTER(); + + swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_NOTICE); + MetadataLogger::initialize(); + + auto vendorSai = std::make_shared(); + auto commandLineOptions = std::make_shared(); + auto isWarmStart = false; + + commandLineOptions->m_enableSyncMode= true; + commandLineOptions->m_enableTempView = true; + commandLineOptions->m_disableExitSleep = true; + commandLineOptions->m_enableUnittests = true; + commandLineOptions->m_enableSaiBulkSupport = true; + commandLineOptions->m_startType = SAI_START_TYPE_COLD_BOOT; + commandLineOptions->m_redisCommunicationMode = SAI_REDIS_COMMUNICATION_MODE_REDIS_SYNC; + commandLineOptions->m_profileMapFile = "./brcm/testprofile.ini"; + + auto syncd = std::make_shared(vendorSai, commandLineOptions, isWarmStart); + syncd->run(); + + SWSS_LOG_NOTICE("Started syncd worker."); +} + +class LinkEventDampingTest : public ::testing::Test +{ +public: + LinkEventDampingTest() + { + SWSS_LOG_ENTER(); + + auto dbAsic = std::make_shared("ASIC_DB", 0); + + m_selectableChannel = std::make_shared( + dbAsic, + REDIS_TABLE_GETRESPONSE, + ASIC_STATE_TABLE, + TEMP_PREFIX, + false); + } + + virtual ~LinkEventDampingTest() = default; + +public: + virtual void SetUp() override + { + SWSS_LOG_ENTER(); + + m_switchId = SAI_NULL_OBJECT_ID; + + // flush ASIC DB + flushAsicDb(); + + syncdStart(); + createSwitch(); + } + + void syncdStart() + { + SWSS_LOG_ENTER(); + + // start syncd worker + m_worker = std::make_shared(syncdLinkEventDampingWorkerThread); + + // initialize SAI redis + m_sairedis = std::make_shared(); + + auto status = m_sairedis->apiInitialize(0, &test_services); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + // set communication mode + sai_attribute_t attr; + + attr.id = SAI_REDIS_SWITCH_ATTR_REDIS_COMMUNICATION_MODE; + attr.value.s32 = SAI_REDIS_COMMUNICATION_MODE_REDIS_SYNC; + + status = m_sairedis->set(SAI_OBJECT_TYPE_SWITCH, SAI_NULL_OBJECT_ID, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + // enable recording + attr.id = SAI_REDIS_SWITCH_ATTR_RECORD; + attr.value.booldata = true; + + status = m_sairedis->set(SAI_OBJECT_TYPE_SWITCH, SAI_NULL_OBJECT_ID, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + } + + void createSwitch() + { + SWSS_LOG_ENTER(); + + sai_attribute_t attr; + + // init view + attr.id = SAI_REDIS_SWITCH_ATTR_NOTIFY_SYNCD; + attr.value.s32 = SAI_REDIS_NOTIFY_SYNCD_INIT_VIEW; + + auto status = m_sairedis->set(SAI_OBJECT_TYPE_SWITCH, SAI_NULL_OBJECT_ID, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + // apply view + attr.id = SAI_REDIS_SWITCH_ATTR_NOTIFY_SYNCD; + attr.value.s32 = SAI_REDIS_NOTIFY_SYNCD_APPLY_VIEW; + + status = m_sairedis->set(SAI_OBJECT_TYPE_SWITCH, SAI_NULL_OBJECT_ID, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + // create switch + attr.id = SAI_SWITCH_ATTR_INIT_SWITCH; + attr.value.booldata = true; + + status = m_sairedis->create(SAI_OBJECT_TYPE_SWITCH, &m_switchId, SAI_NULL_OBJECT_ID, 1, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + } + + virtual void TearDown() override + { + SWSS_LOG_ENTER(); + + // uninitialize SAI redis + + auto status = m_sairedis->apiUninitialize(); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + // stop syncd worker + sendSyncdShutdownNotification(); + m_worker->join(); + } + +protected: + std::shared_ptr m_worker; + std::shared_ptr m_sairedis; + sai_object_id_t m_switchId; + std::shared_ptr m_selectableChannel; +}; + +sai_status_t getResponseStatus( + _In_ const std::string& command, + _In_ sairedis::RedisSelectableChannel *selectable, + _In_ bool init_view_mode) +{ + SWSS_LOG_ENTER(); + + swss::Select s; + s.addSelectable(selectable); + + while (true) + { + swss::Selectable *sel; + int result = s.select(&sel, 1000); + + if (result == swss::Select::OBJECT) + { + swss::KeyOpFieldsValuesTuple kco; + selectable->pop(kco, init_view_mode); + + const std::string &op = kfvOp(kco); + const std::string &opkey = kfvKey(kco); + + if (op != command) + { + SWSS_LOG_WARN("got not expected response: %s:%s", opkey.c_str(), op.c_str()); + continue; + } + + sai_status_t status; + sai_deserialize_status(opkey, status); + + return status; + } + + SWSS_LOG_ERROR("SELECT operation result: %s on %s", swss::Select::resultToString(result).c_str(), command.c_str()); + break; + } + + return SAI_STATUS_FAILURE; +} + +TEST_F(LinkEventDampingTest, SetLinkEventDampingConfigInvalidPort) +{ + // SAI_NULL_OBJECT_ID is not a registered port VID, so VID→RID translation + // returns SAI_NULL_OBJECT_ID, which causes processLinkEventDampingConfigSet() + // to return SAI_STATUS_INVALID_PARAMETER + std::string key = sai_serialize_object_type(SAI_OBJECT_TYPE_PORT) + ":" + + sai_serialize_object_id(SAI_NULL_OBJECT_ID); + + std::string str_attr_id = sai_serialize_redis_port_attr_id( + SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM); + + std::string str_attr_value = sai_serialize_redis_link_event_damping_algorithm( + SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_AIED); + + m_selectableChannel->set(key, + {swss::FieldValueTuple(str_attr_id, str_attr_value)}, + REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET); + + EXPECT_EQ(getResponseStatus(REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET, + m_selectableChannel.get(), false), + SAI_STATUS_INVALID_PARAMETER); +} + +TEST_F(LinkEventDampingTest, SetLinkEventDampingConfigSuccess) +{ + //Retrieve the number of ports on the switch. + sai_attribute_t attr; + attr.id = SAI_SWITCH_ATTR_PORT_NUMBER; + + auto status = m_sairedis->get(SAI_OBJECT_TYPE_SWITCH, m_switchId, 1, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + ASSERT_GT(attr.value.u32, 0u); + + uint32_t portCount = attr.value.u32; + + //Retrieve the port list so we have a real, registered port VID. + std::vector portOids(portCount); + attr.id = SAI_SWITCH_ATTR_PORT_LIST; + attr.value.objlist.count = portCount; + attr.value.objlist.list = portOids.data(); + + status = m_sairedis->get(SAI_OBJECT_TYPE_SWITCH, m_switchId, 1, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + ASSERT_GT(attr.value.objlist.count, 0u); + + sai_object_id_t portVid = portOids[0]; + + //Build the damping config key using the real port VID. + std::string key = sai_serialize_object_type(SAI_OBJECT_TYPE_PORT) + ":" + + sai_serialize_object_id(portVid); + + //Set SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM → AIED. + std::string str_algo_id = sai_serialize_redis_port_attr_id( + SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM); + + std::string str_algo_value = sai_serialize_redis_link_event_damping_algorithm( + SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_AIED); + + m_selectableChannel->set(key, + {swss::FieldValueTuple(str_algo_id, str_algo_value)}, + REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET); + + EXPECT_EQ(getResponseStatus(REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET, + m_selectableChannel.get(), false), + SAI_STATUS_SUCCESS); + + //Also set SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGO_AIED_CONFIG. + sai_redis_link_event_damping_algo_aied_config_t aiedConfig; + aiedConfig.max_suppress_time = 30000; //30 seconds in ms + aiedConfig.suppress_threshold = 1600; + aiedConfig.reuse_threshold = 1200; + aiedConfig.decay_half_life = 15000; //15 seconds in ms + aiedConfig.flap_penalty = 1000; + + std::string str_aied_id = sai_serialize_redis_port_attr_id( + SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGO_AIED_CONFIG); + + std::string str_aied_value = sai_serialize_redis_link_event_damping_aied_config(aiedConfig); + + m_selectableChannel->set(key, + {swss::FieldValueTuple(str_aied_id, str_aied_value)}, + REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET); + + EXPECT_EQ(getResponseStatus(REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET, + m_selectableChannel.get(), false), + SAI_STATUS_SUCCESS); +} + +TEST_F(LinkEventDampingTest, SetLinkEventDampingConfigWrongObjectType) +{ + // Use SAI_OBJECT_TYPE_SWITCH instead of SAI_OBJECT_TYPE_PORT. + // processLinkEventDampingConfigSet() rejects any non-PORT object type + // at Syncd.cpp with SAI_STATUS_INVALID_PARAMETER. + std::string key = sai_serialize_object_type(SAI_OBJECT_TYPE_SWITCH) + ":" + + sai_serialize_object_id(m_switchId); + + std::string str_attr_id = sai_serialize_redis_port_attr_id( + SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM); + + std::string str_attr_value = sai_serialize_redis_link_event_damping_algorithm( + SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_AIED); + + m_selectableChannel->set(key, + {swss::FieldValueTuple(str_attr_id, str_attr_value)}, + REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET); + + EXPECT_EQ(getResponseStatus(REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET, + m_selectableChannel.get(), false), + SAI_STATUS_INVALID_PARAMETER); +} + +TEST_F(LinkEventDampingTest, SetLinkEventDampingConfigUnknownAttribute) +{ + //Get a real port VID so we pass the VID→RID validation. + sai_attribute_t attr; + attr.id = SAI_SWITCH_ATTR_PORT_NUMBER; + + auto status = m_sairedis->get(SAI_OBJECT_TYPE_SWITCH, m_switchId, 1, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + ASSERT_GT(attr.value.u32, 0u); + + uint32_t portCount = attr.value.u32; + + std::vector portOids(portCount); + attr.id = SAI_SWITCH_ATTR_PORT_LIST; + attr.value.objlist.count = portCount; + attr.value.objlist.list = portOids.data(); + + status = m_sairedis->get(SAI_OBJECT_TYPE_SWITCH, m_switchId, 1, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + ASSERT_GT(attr.value.objlist.count, 0u); + + sai_object_id_t portVid = portOids[0]; + + //Build the key for the valid port. + std::string key = sai_serialize_object_type(SAI_OBJECT_TYPE_PORT) + ":" + + sai_serialize_object_id(portVid); + + //Craft an unknown attribute ID — one past the last known enum value. + //This hits the default: branch in processLinkEventDampingConfigSet() + //at Syncd.cpp, which returns SAI_STATUS_INVALID_PARAMETER. + sai_redis_port_attr_t unknownAttrId = static_cast( + SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGO_AIED_CONFIG + 1); + + std::string str_unknown_id = sai_serialize_redis_port_attr_id(unknownAttrId); + + m_selectableChannel->set(key, + {swss::FieldValueTuple(str_unknown_id, "invalid_value")}, + REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET); + + EXPECT_EQ(getResponseStatus(REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET, + m_selectableChannel.get(), false), + SAI_STATUS_INVALID_PARAMETER); +} + +TEST_F(LinkEventDampingTest, SetDampingConfigMissingColonInKey) +{ + // Test invalid key format without colon separator + std::string invalidKey = "INVALID_KEY_NO_COLON"; + + std::string str_algo_id = sai_serialize_redis_port_attr_id( + SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM); + + std::string str_algo_value = sai_serialize_redis_link_event_damping_algorithm( + SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_AIED); + + m_selectableChannel->set(invalidKey, + {swss::FieldValueTuple(str_algo_id, str_algo_value)}, + REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET); + + EXPECT_EQ(getResponseStatus(REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET, + m_selectableChannel.get(), false), + SAI_STATUS_INVALID_PARAMETER); +} diff --git a/unittest/lib/TestClientServerSai.cpp b/unittest/lib/TestClientServerSai.cpp index 3eb65733a6..3fd724b2ac 100644 --- a/unittest/lib/TestClientServerSai.cpp +++ b/unittest/lib/TestClientServerSai.cpp @@ -146,6 +146,191 @@ TEST(ClientServerSai, logSet) EXPECT_EQ(SAI_STATUS_SUCCESS, css->logSet(SAI_API_PORT, SAI_LOG_LEVEL_NOTICE)); } +TEST(ClientServerSai, VerifySaiRedisPortAttrNotSupportedInClientMode) +{ + auto css = std::make_shared(); + + // Initialize as sairedis client. + EXPECT_EQ(SAI_STATUS_SUCCESS, css->apiInitialize(0, &test_client_services)); + + sai_attribute_t attr; + attr.id = SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM; + attr.value.s32 = SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_AIED; + + EXPECT_EQ(SAI_STATUS_FAILURE, css->set(SAI_OBJECT_TYPE_PORT, SAI_NULL_OBJECT_ID, &attr)); +} + +TEST(ClientServerSai, SetLinkEventDampingAlgorithm) +{ + auto css = std::make_shared(); + + // Initialize as sairedis server. + EXPECT_EQ(SAI_STATUS_SUCCESS, css->apiInitialize(0, &test_services)); + + sai_attribute_t attr; + attr.id = SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM; + attr.value.s32 = SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_AIED; + + EXPECT_EQ(SAI_STATUS_SUCCESS, css->set(SAI_OBJECT_TYPE_PORT, SAI_NULL_OBJECT_ID, &attr)); +} + +TEST(ClientServerSai, SetLinkEventDampingConfig) +{ + auto css = std::make_shared(); + + // Initialize as sairedis server. + EXPECT_EQ(SAI_STATUS_SUCCESS, css->apiInitialize(0, &test_services)); + + // Failure when config is NULL. + sai_attribute_t attr; + attr.id = SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGO_AIED_CONFIG; + attr.value.ptr = nullptr; + + EXPECT_EQ(SAI_STATUS_INVALID_PARAMETER, css->set(SAI_OBJECT_TYPE_PORT, SAI_NULL_OBJECT_ID, &attr)); + + sai_redis_link_event_damping_algo_aied_config_t config = { + .max_suppress_time = 5000, + .suppress_threshold = 1500, + .reuse_threshold = 1200, + .decay_half_life = 3000, + .flap_penalty = 1000}; + + attr.value.ptr = (void *) &config; + + EXPECT_EQ(SAI_STATUS_SUCCESS, css->set(SAI_OBJECT_TYPE_PORT, SAI_NULL_OBJECT_ID, &attr)); +} + +TEST(ClientServerSai, SetLinkEventDampingConfigVariousValues) +{ + auto css = std::make_shared(); + + // Initialize as sairedis server. + EXPECT_EQ(SAI_STATUS_SUCCESS, css->apiInitialize(0, &test_services)); + + sai_attribute_t attr; + attr.id = SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGO_AIED_CONFIG; + + // Test with small suppress time + sai_redis_link_event_damping_algo_aied_config_t config1 = { + .max_suppress_time = 1000, + .suppress_threshold = 100, + .reuse_threshold = 50, + .decay_half_life = 500, + .flap_penalty = 10}; + attr.value.ptr = (void *) &config1; + EXPECT_EQ(SAI_STATUS_SUCCESS, css->set(SAI_OBJECT_TYPE_PORT, SAI_NULL_OBJECT_ID, &attr)); + + // Test with large suppress time + sai_redis_link_event_damping_algo_aied_config_t config2 = { + .max_suppress_time = 60000, + .suppress_threshold = 2000, + .reuse_threshold = 1500, + .decay_half_life = 30000, + .flap_penalty = 2000}; + attr.value.ptr = (void *) &config2; + EXPECT_EQ(SAI_STATUS_SUCCESS, css->set(SAI_OBJECT_TYPE_PORT, SAI_NULL_OBJECT_ID, &attr)); + + // Test with zero penalties + sai_redis_link_event_damping_algo_aied_config_t config3 = { + .max_suppress_time = 10000, + .suppress_threshold = 0, + .reuse_threshold = 0, + .decay_half_life = 5000, + .flap_penalty = 0}; + attr.value.ptr = (void *) &config3; + EXPECT_EQ(SAI_STATUS_SUCCESS, css->set(SAI_OBJECT_TYPE_PORT, SAI_NULL_OBJECT_ID, &attr)); +} + +TEST(ClientServerSai, SetLinkEventDampingAlgorithmVariousTypes) +{ + auto css = std::make_shared(); + + // Initialize as sairedis server. + EXPECT_EQ(SAI_STATUS_SUCCESS, css->apiInitialize(0, &test_services)); + + sai_attribute_t attr; + attr.id = SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM; + + // Test disabled algorithm + attr.value.s32 = SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_DISABLED; + EXPECT_EQ(SAI_STATUS_SUCCESS, css->set(SAI_OBJECT_TYPE_PORT, SAI_NULL_OBJECT_ID, &attr)); + + // Test AIED algorithm + attr.value.s32 = SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_AIED; + EXPECT_EQ(SAI_STATUS_SUCCESS, css->set(SAI_OBJECT_TYPE_PORT, SAI_NULL_OBJECT_ID, &attr)); +} + +TEST(ClientServerSai, SetDampingConfigOnDifferentObjectTypes) +{ + auto css = std::make_shared(); + + // Initialize as sairedis server. + EXPECT_EQ(SAI_STATUS_SUCCESS, css->apiInitialize(0, &test_services)); + + sai_attribute_t attr; + + // Test on QUEUE object type (not PORT) + attr.id = SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM; + attr.value.s32 = SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_AIED; + EXPECT_EQ(SAI_STATUS_INVALID_PARAMETER, css->set(SAI_OBJECT_TYPE_QUEUE, SAI_NULL_OBJECT_ID, &attr)); + + // Test on VIRTUAL_ROUTER object type + EXPECT_EQ(SAI_STATUS_INVALID_PARAMETER, css->set(SAI_OBJECT_TYPE_VIRTUAL_ROUTER, SAI_NULL_OBJECT_ID, &attr)); + +} + +TEST(ClientServerSai, MultiplePortsDampingConfig) +{ + auto css = std::make_shared(); + + // Initialize as sairedis server. + EXPECT_EQ(SAI_STATUS_SUCCESS, css->apiInitialize(0, &test_services)); + + sai_attribute_t attr; + attr.id = SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM; + attr.value.s32 = SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_AIED; + + // Configure damping on multiple different object IDs + // (In real code, these would be different port VIDs) + sai_object_id_t port_ids[] = {0x1000000000000001, 0x1000000000000002, 0x1000000000000003}; + + for (size_t i = 0; i < sizeof(port_ids) / sizeof(port_ids[0]); ++i) + { + EXPECT_EQ(SAI_STATUS_SUCCESS, css->set(SAI_OBJECT_TYPE_PORT, port_ids[i], &attr)); + } + + // Verify all ports can be configured with different settings + attr.id = SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGO_AIED_CONFIG; + sai_redis_link_event_damping_algo_aied_config_t configs[] = { + {.max_suppress_time = 5000, .suppress_threshold = 1500, .reuse_threshold = 1200, + .decay_half_life = 2500, .flap_penalty = 500}, + {.max_suppress_time = 10000, .suppress_threshold = 2000, .reuse_threshold = 1500, + .decay_half_life = 5000, .flap_penalty = 1000}, + {.max_suppress_time = 15000, .suppress_threshold = 2500, .reuse_threshold = 2000, + .decay_half_life = 7500, .flap_penalty = 1500}, + }; + + for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++i) + { + attr.value.ptr = (void *) &configs[i]; + EXPECT_EQ(SAI_STATUS_SUCCESS, css->set(SAI_OBJECT_TYPE_PORT, port_ids[i], &attr)); + } +} + +TEST(ClientServerSai, SetInvalidSaiRedisPortAttribute) +{ + auto css = std::make_shared(); + + // Initialize as sairedis server. + EXPECT_EQ(SAI_STATUS_SUCCESS, css->apiInitialize(0, &test_services)); + + sai_attribute_t attr; + // Set an id that is not supported yet. + attr.id = SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGO_AIED_CONFIG + 100; + + EXPECT_EQ(SAI_STATUS_INVALID_PARAMETER, css->set(SAI_OBJECT_TYPE_PORT, SAI_NULL_OBJECT_ID, &attr)); +} + TEST(ClientServerSai, bulkGetClearStats) { auto css = std::make_shared(); diff --git a/unittest/syncd/TestSyncd.cpp b/unittest/syncd/TestSyncd.cpp index 3144cea4cf..c041e29bcc 100644 --- a/unittest/syncd/TestSyncd.cpp +++ b/unittest/syncd/TestSyncd.cpp @@ -21,6 +21,11 @@ #include #include +#include +#include + +#include "swss/table.h" + using namespace syncd; using namespace saivs; using namespace testing; @@ -772,4 +777,218 @@ TEST_F(SyncdTest, processEventInShutdownWaitMode_NonNotifyCommand) m_syncd->processEventInShutdownWaitMode(*channel); } + +// ---------------------------------------------------------------------------- +// Link event damping tests +// +// These tests exercise the real damping call chain: +// +// NotificationProcessor::syncProcessNotification +// -> handle_port_state_change +// -> process_on_port_state_change +// -> m_linkEventDampingApplier (bound to Syncd::applyLinkEventDamping) +// -> applyAiedAlgorithm / decayPenalty / writeDampingCountersToStateDb +// +// Notes: +// - Notifications must carry the port RID (the processor translates RID->VID). +// - Damping config is applied through Syncd::processEvent with op +// REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET, which covers +// processLinkEventDampingConfigSet as well. +// - Results are asserted via the LINK_EVENT_DAMPING_STATS table in STATE_DB, +// which is written by writeDampingCountersToStateDb. +// ---------------------------------------------------------------------------- + +class SyncdLinkEventDampingTest : public SyncdTest +{ +protected: + static constexpr sai_object_id_t PORT_VID = 0x10000000000002; + static constexpr sai_object_id_t PORT_RID = 0x11000000000002; + + void SetUp() override + { + SWSS_LOG_ENTER(); + + SyncdTest::SetUp(); + + m_syncd->m_translator->insertRidAndVid(PORT_RID, PORT_VID); + } + + // Apply damping config via the regular command path + // (covers Syncd::processLinkEventDampingConfigSet). + void setDampingConfig( + const sai_redis_link_event_damping_algo_aied_config_t& config) + { + SWSS_LOG_ENTER(); + + std::string key = sai_serialize_object_type(SAI_OBJECT_TYPE_PORT) + + ":" + sai_serialize_object_id(PORT_VID); + + std::vector values = { + { sai_serialize_redis_port_attr_id(SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGORITHM), + sai_serialize_redis_link_event_damping_algorithm(SAI_REDIS_LINK_EVENT_DAMPING_ALGORITHM_AIED) }, + { sai_serialize_redis_port_attr_id(SAI_REDIS_PORT_ATTR_LINK_EVENT_DAMPING_ALGO_AIED_CONFIG), + sai_serialize_redis_link_event_damping_aied_config(config) }, + }; + + swss::KeyOpFieldsValuesTuple kco(key, REDIS_ASIC_STATE_COMMAND_DAMPING_CONFIG_SET, values); + + MockSelectableChannel consumer; + EXPECT_CALL(consumer, empty()).WillOnce(testing::Return(true)); + EXPECT_CALL(consumer, pop(testing::_, testing::_)) + .WillOnce(testing::SetArgReferee<0>(kco)); + + m_syncd->processEvent(consumer); + } + + // Inject a port state change notification the same way the vendor SAI + // callback path would deliver it (using the port RID). + void sendPortStateChange( + sai_port_oper_status_t status) + { + SWSS_LOG_ENTER(); + + sai_port_oper_status_notification_t n; + + memset(&n, 0, sizeof(n)); + + n.port_id = PORT_RID; // notification carries RID, processor translates to VID + n.port_state = status; + + std::string data = sai_serialize_port_oper_status_ntf(1, &n); + + std::vector entry; + swss::KeyOpFieldsValuesTuple item(SAI_SWITCH_NOTIFICATION_NAME_PORT_STATE_CHANGE, data, entry); + + m_syncd->m_processor->syncProcessNotification(item); + } + + // Read a damping counter field from STATE_DB written by + // writeDampingCountersToStateDb. + std::string getDampingField( + const std::string& field) + { + SWSS_LOG_ENTER(); + + swss::DBConnector db("STATE_DB", 0); + swss::Table table(&db, "LINK_EVENT_DAMPING_STATS"); + + std::string value; + bool found = table.hget(sai_serialize_object_id(PORT_VID), field, value); + + return found ? value : ""; + } +}; + +TEST_F(SyncdLinkEventDampingTest, flapsEnterDampingAndSuppress) +{ + sai_redis_link_event_damping_algo_aied_config_t config; + config.max_suppress_time = 10000; + config.suppress_threshold = 1500; + config.reuse_threshold = 1000; + config.decay_half_life = 5000; + config.flap_penalty = 1000; + + setDampingConfig(config); + + // UNKNOWN -> DOWN: no penalty (penalty only on UP -> DOWN), propagated + sendPortStateChange(SAI_PORT_OPER_STATUS_DOWN); + // DOWN -> UP: propagated + sendPortStateChange(SAI_PORT_OPER_STATUS_UP); + // UP -> DOWN: penalty = 1000 < 1500, propagated + sendPortStateChange(SAI_PORT_OPER_STATUS_DOWN); + sendPortStateChange(SAI_PORT_OPER_STATUS_UP); + // UP -> DOWN: penalty ~2000 >= 1500 -> damping activated, + // threshold-crossing event itself is propagated + sendPortStateChange(SAI_PORT_OPER_STATUS_DOWN); + // damping active -> this UP must be suppressed + sendPortStateChange(SAI_PORT_OPER_STATUS_UP); + + EXPECT_EQ(getDampingField("is_damping_active"), "true"); + + // physical status followed the flaps, advertised stayed at last + // propagated state (DOWN), proving the UP was suppressed + EXPECT_EQ(getDampingField("physical_status"), "SAI_PORT_OPER_STATUS_UP"); + EXPECT_EQ(getDampingField("advertised_status"), "SAI_PORT_OPER_STATUS_DOWN"); + + // pre-damping counters see all transitions, post-damping counters + // miss the suppressed UP + EXPECT_EQ(getDampingField("pre_damping_down_events"), "3"); + EXPECT_EQ(getDampingField("pre_damping_up_events"), "3"); + EXPECT_EQ(getDampingField("post_damping_down_events"), "3"); + EXPECT_EQ(getDampingField("post_damping_up_events"), "2"); +} + +TEST_F(SyncdLinkEventDampingTest, maxSuppressTimeoutExitsDamping) +{ + sai_redis_link_event_damping_algo_aied_config_t config; + config.max_suppress_time = 200; // short timeout for the test + config.suppress_threshold = 100; + config.reuse_threshold = 50; + config.decay_half_life = 100; + config.flap_penalty = 1000; + + setDampingConfig(config); + + sendPortStateChange(SAI_PORT_OPER_STATUS_DOWN); + sendPortStateChange(SAI_PORT_OPER_STATUS_UP); + // UP -> DOWN: penalty 1000 >= 100 -> damping activated + sendPortStateChange(SAI_PORT_OPER_STATUS_DOWN); + // suppressed, physical becomes UP while advertised stays DOWN + sendPortStateChange(SAI_PORT_OPER_STATUS_UP); + + ASSERT_EQ(getDampingField("is_damping_active"), "true"); + + // wait past max_suppress_time + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + // same-state event (no DOWN, so damping timer is not reset): + // timeout branch in applyAiedAlgorithm exits damping and the event + // is propagated, synchronizing advertised with physical state + sendPortStateChange(SAI_PORT_OPER_STATUS_UP); + + EXPECT_EQ(getDampingField("is_damping_active"), "false"); + EXPECT_EQ(getDampingField("physical_status"), "SAI_PORT_OPER_STATUS_UP"); + EXPECT_EQ(getDampingField("advertised_status"), "SAI_PORT_OPER_STATUS_UP"); +} + +TEST_F(SyncdLinkEventDampingTest, penaltyDecayExitsDamping) +{ + sai_redis_link_event_damping_algo_aied_config_t config; + config.max_suppress_time = 10000; // large, so decay exit wins + config.suppress_threshold = 1000; + config.reuse_threshold = 600; + config.decay_half_life = 1000; // fast decay + config.flap_penalty = 1000; + + setDampingConfig(config); + + sendPortStateChange(SAI_PORT_OPER_STATUS_DOWN); + sendPortStateChange(SAI_PORT_OPER_STATUS_UP); + // UP -> DOWN: penalty 1000 >= 1000 -> damping activated + sendPortStateChange(SAI_PORT_OPER_STATUS_DOWN); + // suppressed + sendPortStateChange(SAI_PORT_OPER_STATUS_UP); + + EXPECT_EQ(getDampingField("is_damping_active"), "true"); + + // wait ~3 half lives: penalty 1000 -> ~125 < reuse_threshold (600) + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // same-state event triggers decayPenalty + reuse-threshold exit branch + sendPortStateChange(SAI_PORT_OPER_STATUS_UP); + + EXPECT_EQ(getDampingField("is_damping_active"), "false"); + EXPECT_EQ(getDampingField("advertised_status"), "SAI_PORT_OPER_STATUS_UP"); +} + +TEST_F(SyncdLinkEventDampingTest, noDampingConfiguredPropagates) +{ + // No damping config applied: applyLinkEventDamping returns early + // (port not in m_portLinkEventDampingStates) and events propagate + sendPortStateChange(SAI_PORT_OPER_STATUS_DOWN); + sendPortStateChange(SAI_PORT_OPER_STATUS_UP); + + // no STATE_DB entry is written for non-configured ports + EXPECT_EQ(getDampingField("is_damping_active"), ""); +} #endif