From c251772ed47d8e1a729cf5e51e5a9ae4ae5b5170 Mon Sep 17 00:00:00 2001 From: Sonic Build Admin Date: Sat, 28 Jun 2025 09:06:32 +0000 Subject: [PATCH] [trim]: Add Packet Trimming to OA Signed-off-by: Nazarii Hnydyn **DEPENDS:** 1. https://github.com/sonic-net/sonic-swss-common/pull/1001 2. https://github.com/sonic-net/sonic-sairedis/pull/1575 3. https://github.com/sonic-net/sonic-sairedis/pull/1598 **HLD:** https://github.com/sonic-net/SONiC/pull/1898 **What I did** * Implemented Packet Trimming feature **Why I did it** * Implementation is done according to the Packet Trimming HLD **How I verified it** 1. Run Packet Trimming VS UTs **Details if related** * N/A #### A picture of a cute animal (not mandatory but encouraged) ``` .---. .----------- / \ __ / ------ / / \( )/ ----- ////// ' \/ ` --- //// / // : : --- // / / /` '-- // //..\\ ====UU====UU==== '//||\\` ''`` ``` --- cfgmgr/buffermgrdyn.cpp | 10 + cfgmgr/buffermgrdyn.h | 2 + orchagent/Makefile.am | 2 + orchagent/aclorch.cpp | 7 + orchagent/aclorch.h | 12 +- orchagent/buffer/bufferschema.h | 8 + orchagent/bufferorch.cpp | 24 + orchagent/orchdaemon.cpp | 2 + orchagent/p4orch/tests/Makefile.am | 2 + orchagent/portsorch.cpp | 4 +- orchagent/switch/trimming/capabilities.cpp | 356 ++++++++++++++ orchagent/switch/trimming/capabilities.h | 58 +++ orchagent/switch/trimming/container.h | 38 ++ orchagent/switch/trimming/helper.cpp | 200 ++++++++ orchagent/switch/trimming/helper.h | 29 ++ orchagent/switch/trimming/schema.h | 12 + orchagent/switchorch.cpp | 233 ++++++++++ orchagent/switchorch.h | 12 + tests/conftest.py | 20 +- tests/dvslib/dvs_buffer.py | 52 +++ tests/dvslib/dvs_port.py | 53 ++- tests/dvslib/dvs_queue.py | 119 +++++ tests/dvslib/dvs_switch.py | 55 +-- tests/mock_tests/Makefile.am | 2 + tests/mock_tests/aclorch_ut.cpp | 63 ++- tests/mock_tests/flexcounter_ut.cpp | 1 + tests/test_trimming.py | 516 +++++++++++++++++++++ 27 files changed, 1841 insertions(+), 51 deletions(-) create mode 100644 orchagent/buffer/bufferschema.h create mode 100644 orchagent/switch/trimming/capabilities.cpp create mode 100644 orchagent/switch/trimming/capabilities.h create mode 100644 orchagent/switch/trimming/container.h create mode 100644 orchagent/switch/trimming/helper.cpp create mode 100644 orchagent/switch/trimming/helper.h create mode 100644 orchagent/switch/trimming/schema.h create mode 100644 tests/dvslib/dvs_buffer.py create mode 100644 tests/dvslib/dvs_queue.py create mode 100644 tests/test_trimming.py diff --git a/cfgmgr/buffermgrdyn.cpp b/cfgmgr/buffermgrdyn.cpp index a0764ab46c4..ea2b94bb540 100644 --- a/cfgmgr/buffermgrdyn.cpp +++ b/cfgmgr/buffermgrdyn.cpp @@ -14,6 +14,8 @@ #include "schema.h" #include "warm_restart.h" +#include "buffer/bufferschema.h" + /* * Some Tips * 1. All keys in this file are in format of APPL_DB key. @@ -896,6 +898,10 @@ void BufferMgrDynamic::updateBufferProfileToDb(const string &name, const buffer_ } fvVector.emplace_back("xoff", profile.xoff); } + if (!profile.packet_discard_action.empty()) + { + fvVector.emplace_back(BUFFER_PROFILE_PACKET_DISCARD_ACTION, profile.packet_discard_action); + } fvVector.emplace_back("size", profile.size); fvVector.emplace_back("pool", profile.pool_name); fvVector.emplace_back(mode, profile.threshold); @@ -2651,6 +2657,10 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues profileApp.direction = BUFFER_INGRESS; } } + else if (field == BUFFER_PROFILE_PACKET_DISCARD_ACTION) + { + profileApp.packet_discard_action = value; + } SWSS_LOG_INFO("Inserting BUFFER_PROFILE table field %s value %s", field.c_str(), value.c_str()); } diff --git a/cfgmgr/buffermgrdyn.h b/cfgmgr/buffermgrdyn.h index b50b0ced694..b0b3e875d64 100644 --- a/cfgmgr/buffermgrdyn.h +++ b/cfgmgr/buffermgrdyn.h @@ -76,6 +76,8 @@ typedef struct { // port_pgs - stores pgs referencing this profile // An element will be added or removed when a PG added or removed port_pg_set_t port_pgs; + // packet trimming control + std::string packet_discard_action; } buffer_profile_t; typedef struct { diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index adb3ef54196..61beba680c8 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -80,6 +80,8 @@ orchagent_SOURCES = \ saiattr.cpp \ switch/switch_capabilities.cpp \ switch/switch_helper.cpp \ + switch/trimming/capabilities.cpp \ + switch/trimming/helper.cpp \ switchorch.cpp \ pfcwdorch.cpp \ pfcactionhandler.cpp \ diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index db2f0e5d4dc..830a28682e7 100755 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -109,6 +109,7 @@ static acl_rule_attr_lookup_t aclL3ActionLookup = { ACTION_PACKET_ACTION, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION }, { ACTION_REDIRECT_ACTION, SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT }, { ACTION_DO_NOT_NAT_ACTION, SAI_ACL_ENTRY_ATTR_ACTION_NO_NAT }, + { ACTION_DISABLE_TRIM, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_TRIM_DISABLE } }; static acl_rule_attr_lookup_t aclInnerActionLookup = @@ -2016,6 +2017,12 @@ bool AclRulePacket::validateAddAction(string attr_name, string _attr_value) actionData.parameter.booldata = true; action_str = ACTION_DO_NOT_NAT_ACTION; } + // handle PACKET_ACTION_DISABLE_TRIM in ACTION_PACKET_ACTION + else if (attr_value == PACKET_ACTION_DISABLE_TRIM) + { + actionData.parameter.booldata = true; + action_str = ACTION_DISABLE_TRIM; + } else { return false; diff --git a/orchagent/aclorch.h b/orchagent/aclorch.h index 3031cdcf4a6..32dc172eda1 100755 --- a/orchagent/aclorch.h +++ b/orchagent/aclorch.h @@ -65,6 +65,7 @@ #define ACTION_PACKET_ACTION "PACKET_ACTION" #define ACTION_REDIRECT_ACTION "REDIRECT_ACTION" #define ACTION_DO_NOT_NAT_ACTION "DO_NOT_NAT_ACTION" +#define ACTION_DISABLE_TRIM "DISABLE_TRIM_ACTION" #define ACTION_MIRROR_ACTION "MIRROR_ACTION" #define ACTION_MIRROR_INGRESS_ACTION "MIRROR_INGRESS_ACTION" #define ACTION_MIRROR_EGRESS_ACTION "MIRROR_EGRESS_ACTION" @@ -79,11 +80,12 @@ #define ACTION_DSCP "DSCP_ACTION" #define ACTION_INNER_SRC_MAC_REWRITE_ACTION "INNER_SRC_MAC_REWRITE_ACTION" -#define PACKET_ACTION_FORWARD "FORWARD" -#define PACKET_ACTION_DROP "DROP" -#define PACKET_ACTION_COPY "COPY" -#define PACKET_ACTION_REDIRECT "REDIRECT" -#define PACKET_ACTION_DO_NOT_NAT "DO_NOT_NAT" +#define PACKET_ACTION_FORWARD "FORWARD" +#define PACKET_ACTION_DROP "DROP" +#define PACKET_ACTION_COPY "COPY" +#define PACKET_ACTION_REDIRECT "REDIRECT" +#define PACKET_ACTION_DO_NOT_NAT "DO_NOT_NAT" +#define PACKET_ACTION_DISABLE_TRIM "DISABLE_TRIM" #define DTEL_FLOW_OP_NOP "NOP" #define DTEL_FLOW_OP_POSTCARD "POSTCARD" diff --git a/orchagent/buffer/bufferschema.h b/orchagent/buffer/bufferschema.h new file mode 100644 index 00000000000..0d4e6ffcf10 --- /dev/null +++ b/orchagent/buffer/bufferschema.h @@ -0,0 +1,8 @@ +#pragma once + +// defines ------------------------------------------------------------------------------------------------------------ + +#define BUFFER_PROFILE_PACKET_DISCARD_ACTION_DROP "drop" +#define BUFFER_PROFILE_PACKET_DISCARD_ACTION_TRIM "trim" + +#define BUFFER_PROFILE_PACKET_DISCARD_ACTION "packet_discard_action" diff --git a/orchagent/bufferorch.cpp b/orchagent/bufferorch.cpp index 172f94570de..d4a7109212a 100644 --- a/orchagent/bufferorch.cpp +++ b/orchagent/bufferorch.cpp @@ -9,6 +9,8 @@ #include #include +#include "buffer/bufferschema.h" + using namespace std; extern sai_port_api_t *sai_port_api; @@ -710,6 +712,28 @@ task_process_status BufferOrch::processBufferProfile(KeyOpFieldsValuesTuple &tup attr.value.u64 = (uint64_t)stoul(value); attribs.push_back(attr); } + else if (field == BUFFER_PROFILE_PACKET_DISCARD_ACTION) + { + attr.id = SAI_BUFFER_PROFILE_ATTR_PACKET_ADMISSION_FAIL_ACTION; + + if (value == BUFFER_PROFILE_PACKET_DISCARD_ACTION_DROP) + { + attr.value.s32 = SAI_BUFFER_PROFILE_PACKET_ADMISSION_FAIL_ACTION_DROP; + } + else if (value == BUFFER_PROFILE_PACKET_DISCARD_ACTION_TRIM) + { + attr.value.s32 = SAI_BUFFER_PROFILE_PACKET_ADMISSION_FAIL_ACTION_DROP_AND_TRIM; + } + else + { + SWSS_LOG_ERROR("Failed to parse buffer profile(%s) field(%s): invalid value(%s)", + object_name.c_str(), field.c_str(), value.c_str() + ); + return task_process_status::task_failed; + } + + attribs.push_back(attr); + } else { SWSS_LOG_ERROR("Unknown buffer profile field specified:%s, ignoring", field.c_str()); diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 50f6cde21fe..69fc0839df0 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -173,10 +173,12 @@ bool OrchDaemon::init() TableConnector app_switch_table(m_applDb, APP_SWITCH_TABLE_NAME); TableConnector conf_asic_sensors(m_configDb, CFG_ASIC_SENSORS_TABLE_NAME); TableConnector conf_switch_hash(m_configDb, CFG_SWITCH_HASH_TABLE_NAME); + TableConnector conf_switch_trim(m_configDb, CFG_SWITCH_TRIMMING_TABLE_NAME); TableConnector conf_suppress_asic_sdk_health_categories(m_configDb, CFG_SUPPRESS_ASIC_SDK_HEALTH_EVENT_NAME); vector switch_tables = { conf_switch_hash, + conf_switch_trim, conf_asic_sensors, conf_suppress_asic_sdk_health_categories, app_switch_table diff --git a/orchagent/p4orch/tests/Makefile.am b/orchagent/p4orch/tests/Makefile.am index 74e3d7aa5cc..0eb0b82ad7f 100644 --- a/orchagent/p4orch/tests/Makefile.am +++ b/orchagent/p4orch/tests/Makefile.am @@ -23,6 +23,8 @@ p4orch_tests_SOURCES = $(ORCHAGENT_DIR)/orch.cpp \ $(ORCHAGENT_DIR)/copporch.cpp \ $(ORCHAGENT_DIR)/switch/switch_capabilities.cpp \ $(ORCHAGENT_DIR)/switch/switch_helper.cpp \ + $(ORCHAGENT_DIR)/switch/trimming/capabilities.cpp \ + $(ORCHAGENT_DIR)/switch/trimming/helper.cpp \ $(ORCHAGENT_DIR)/switchorch.cpp \ $(ORCHAGENT_DIR)/request_parser.cpp \ $(top_srcdir)/lib/recorder.cpp \ diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index 5cc52bd857f..dec83eb5f73 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -255,7 +255,8 @@ const vector port_stat_ids = SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S13, SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S14, SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S15, - SAI_PORT_STAT_IF_IN_FEC_CORRECTED_BITS + SAI_PORT_STAT_IF_IN_FEC_CORRECTED_BITS, + SAI_PORT_STAT_TRIM_PACKETS }; const vector gbport_stat_ids = @@ -292,6 +293,7 @@ static const vector queue_stat_ids = SAI_QUEUE_STAT_BYTES, SAI_QUEUE_STAT_DROPPED_PACKETS, SAI_QUEUE_STAT_DROPPED_BYTES, + SAI_QUEUE_STAT_TRIM_PACKETS }; static const vector voq_stat_ids = { diff --git a/orchagent/switch/trimming/capabilities.cpp b/orchagent/switch/trimming/capabilities.cpp new file mode 100644 index 00000000000..c4c8efc70e9 --- /dev/null +++ b/orchagent/switch/trimming/capabilities.cpp @@ -0,0 +1,356 @@ +// includes ----------------------------------------------------------------------------------------------------------- + +extern "C" { +#include +#include +#include +#include +} + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "schema.h" +#include "capabilities.h" + +using namespace swss; + +// defines ------------------------------------------------------------------------------------------------------------ + +#define CAPABILITY_SWITCH_QUEUE_RESOLUTION_MODE_FIELD "SWITCH|PACKET_TRIMMING_QUEUE_RESOLUTION_MODE" + +#define CAPABILITY_SWITCH_TRIMMING_CAPABLE_FIELD "SWITCH_TRIMMING_CAPABLE" + +#define CAPABILITY_KEY "switch" + +#define SWITCH_STATE_DB_NAME "STATE_DB" +#define SWITCH_STATE_DB_TIMEOUT 0 + +// constants ---------------------------------------------------------------------------------------------------------- + +static const std::unordered_map modeMap = +{ + { SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_STATIC, SWITCH_TRIMMING_QUEUE_MODE_STATIC }, + { SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_DYNAMIC, SWITCH_TRIMMING_QUEUE_MODE_DYNAMIC } +}; + +// variables ---------------------------------------------------------------------------------------------------------- + +extern sai_object_id_t gSwitchId; + +// functions ---------------------------------------------------------------------------------------------------------- + +static std::string toStr(sai_object_type_t objType, sai_attr_id_t attrId) +{ + const auto *meta = sai_metadata_get_attr_metadata(objType, attrId); + + return meta != nullptr ? meta->attridname : "UNKNOWN"; +} + +static std::string toStr(sai_packet_trim_queue_resolution_mode_t value) +{ + const auto *name = sai_metadata_get_packet_trim_queue_resolution_mode_name(value); + + return name != nullptr ? name : "UNKNOWN"; +} + +static std::string toStr(const std::set &value) +{ + std::vector strList; + + for (const auto &cit1 : value) + { + const auto &cit2 = modeMap.find(cit1); + if (cit2 != modeMap.cend()) + { + strList.push_back(cit2->second); + } + } + + return join(",", strList.cbegin(), strList.cend()); +} + +static std::string toStr(bool value) +{ + return value ? "true" : "false"; +} + +// capabilities ------------------------------------------------------------------------------------------------------- + +SwitchTrimmingCapabilities::SwitchTrimmingCapabilities() +{ + queryCapabilities(); + writeCapabilitiesToDb(); +} + +bool SwitchTrimmingCapabilities::isSwitchTrimmingSupported() const +{ + auto size = capabilities.size.isAttrSupported; + auto dscp = capabilities.dscp.isAttrSupported; + auto mode = capabilities.mode.isAttrSupported; + auto queue = true; + + // Do not care of queue index configuration capabilities, + // if static queue resolution mode is not supported + if (capabilities.mode.isStaticModeSupported) + { + queue = capabilities.queue.isAttrSupported; + } + + return size && dscp && mode && queue; +} + +bool SwitchTrimmingCapabilities::validateQueueModeCap(sai_packet_trim_queue_resolution_mode_t value) const +{ + SWSS_LOG_ENTER(); + + if (!capabilities.mode.isEnumSupported) + { + return true; + } + + if (capabilities.mode.mSet.empty()) + { + SWSS_LOG_ERROR("Failed to validate queue resolution mode: no capabilities"); + return false; + } + + if (capabilities.mode.mSet.count(value) == 0) + { + SWSS_LOG_ERROR("Failed to validate queue resolution mode: value(%s) is not supported", toStr(value).c_str()); + return false; + } + + return true; +} + +sai_status_t SwitchTrimmingCapabilities::queryEnumCapabilitiesSai(std::vector &capList, sai_object_type_t objType, sai_attr_id_t attrId) const +{ + sai_s32_list_t enumList = { .count = 0, .list = nullptr }; + + auto status = sai_query_attribute_enum_values_capability(gSwitchId, objType, attrId, &enumList); + if ((status != SAI_STATUS_SUCCESS) && (status != SAI_STATUS_BUFFER_OVERFLOW)) + { + return status; + } + + capList.resize(enumList.count); + enumList.list = capList.data(); + + return sai_query_attribute_enum_values_capability(gSwitchId, objType, attrId, &enumList); +} + +sai_status_t SwitchTrimmingCapabilities::queryAttrCapabilitiesSai(sai_attr_capability_t &attrCap, sai_object_type_t objType, sai_attr_id_t attrId) const +{ + return sai_query_attribute_capability(gSwitchId, objType, attrId, &attrCap); +} + +void SwitchTrimmingCapabilities::queryTrimSizeAttrCapabilities() +{ + SWSS_LOG_ENTER(); + + sai_attr_capability_t attrCap; + + auto status = queryAttrCapabilitiesSai( + attrCap, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_SIZE + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) capabilities", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_SIZE).c_str() + ); + return; + } + + if (!attrCap.set_implemented) + { + SWSS_LOG_WARN( + "Attribute(%s) SET is not implemented in SAI", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_SIZE).c_str() + ); + return; + } + + capabilities.size.isAttrSupported = true; +} + +void SwitchTrimmingCapabilities::queryTrimDscpAttrCapabilities() +{ + SWSS_LOG_ENTER(); + + sai_attr_capability_t attrCap; + + auto status = queryAttrCapabilitiesSai( + attrCap, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) capabilities", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE).c_str() + ); + return; + } + + if (!attrCap.set_implemented) + { + SWSS_LOG_WARN( + "Attribute(%s) SET is not implemented in SAI", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE).c_str() + ); + return; + } + + capabilities.dscp.isAttrSupported = true; +} + +void SwitchTrimmingCapabilities::queryTrimModeEnumCapabilities() +{ + SWSS_LOG_ENTER(); + + std::vector mList; + auto status = queryEnumCapabilitiesSai( + mList, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) enum value capabilities", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE).c_str() + ); + return; + } + + auto &mSet = capabilities.mode.mSet; + std::transform( + mList.cbegin(), mList.cend(), std::inserter(mSet, mSet.begin()), + [](sai_int32_t value) { return static_cast(value); } + ); + + if (mSet.empty() || (mSet.count(SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_STATIC) == 0)) + { + capabilities.mode.isStaticModeSupported = false; + } + + capabilities.mode.isEnumSupported = true; +} + +void SwitchTrimmingCapabilities::queryTrimModeAttrCapabilities() +{ + SWSS_LOG_ENTER(); + + sai_attr_capability_t attrCap; + + auto status = queryAttrCapabilitiesSai( + attrCap, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) capabilities", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE).c_str() + ); + return; + } + + if (!attrCap.set_implemented) + { + SWSS_LOG_WARN( + "Attribute(%s) SET is not implemented in SAI", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE).c_str() + ); + return; + } + + capabilities.mode.isAttrSupported = true; +} + +void SwitchTrimmingCapabilities::queryTrimQueueAttrCapabilities() +{ + SWSS_LOG_ENTER(); + + sai_attr_capability_t attrCap; + + auto status = queryAttrCapabilitiesSai( + attrCap, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) capabilities", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX).c_str() + ); + return; + } + + if (!attrCap.set_implemented) + { + SWSS_LOG_WARN( + "Attribute(%s) SET is not implemented in SAI", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX).c_str() + ); + return; + } + + capabilities.queue.isAttrSupported = true; +} + +void SwitchTrimmingCapabilities::queryCapabilities() +{ + queryTrimSizeAttrCapabilities(); + queryTrimDscpAttrCapabilities(); + + queryTrimModeEnumCapabilities(); + queryTrimModeAttrCapabilities(); + + queryTrimQueueAttrCapabilities(); +} + +FieldValueTuple SwitchTrimmingCapabilities::makeSwitchTrimmingCapDbEntry() const +{ + auto field = CAPABILITY_SWITCH_TRIMMING_CAPABLE_FIELD; + auto value = toStr(isSwitchTrimmingSupported()); + + return FieldValueTuple(field, value); +} + +FieldValueTuple SwitchTrimmingCapabilities::makeQueueModeCapDbEntry() const +{ + auto field = CAPABILITY_SWITCH_QUEUE_RESOLUTION_MODE_FIELD; + auto value = capabilities.mode.isEnumSupported ? toStr(capabilities.mode.mSet) : "N/A"; + + return FieldValueTuple(field, value); +} + +void SwitchTrimmingCapabilities::writeCapabilitiesToDb() +{ + SWSS_LOG_ENTER(); + + DBConnector stateDb(SWITCH_STATE_DB_NAME, SWITCH_STATE_DB_TIMEOUT); + Table capTable(&stateDb, STATE_SWITCH_CAPABILITY_TABLE_NAME); + + std::vector fvList = { + makeSwitchTrimmingCapDbEntry(), + makeQueueModeCapDbEntry() + }; + + capTable.set(CAPABILITY_KEY, fvList); + + SWSS_LOG_NOTICE( + "Wrote switch trimming capabilities to State DB: %s key", + capTable.getKeyName(CAPABILITY_KEY).c_str() + ); +} diff --git a/orchagent/switch/trimming/capabilities.h b/orchagent/switch/trimming/capabilities.h new file mode 100644 index 00000000000..52ced127786 --- /dev/null +++ b/orchagent/switch/trimming/capabilities.h @@ -0,0 +1,58 @@ +#pragma once + +extern "C" { +#include +#include +#include +} + +#include +#include + +class SwitchTrimmingCapabilities final +{ +public: + SwitchTrimmingCapabilities(); + ~SwitchTrimmingCapabilities() = default; + + bool isSwitchTrimmingSupported() const; + + bool validateQueueModeCap(sai_packet_trim_queue_resolution_mode_t value) const; + +private: + swss::FieldValueTuple makeSwitchTrimmingCapDbEntry() const; + swss::FieldValueTuple makeQueueModeCapDbEntry() const; + + sai_status_t queryEnumCapabilitiesSai(std::vector &capList, sai_object_type_t objType, sai_attr_id_t attrId) const; + sai_status_t queryAttrCapabilitiesSai(sai_attr_capability_t &attrCap, sai_object_type_t objType, sai_attr_id_t attrId) const; + + void queryTrimSizeAttrCapabilities(); + void queryTrimDscpAttrCapabilities(); + void queryTrimModeEnumCapabilities(); + void queryTrimModeAttrCapabilities(); + void queryTrimQueueAttrCapabilities(); + + void queryCapabilities(); + void writeCapabilitiesToDb(); + + struct { + struct { + bool isAttrSupported = false; + } size; // SAI_SWITCH_ATTR_PACKET_TRIM_SIZE + + struct { + bool isAttrSupported = false; + } dscp; // SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE + + struct { + std::set mSet; + bool isStaticModeSupported = true; + bool isEnumSupported = false; + bool isAttrSupported = false; + } mode; // SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE + + struct { + bool isAttrSupported = false; + } queue; // SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX + } capabilities; +}; diff --git a/orchagent/switch/trimming/container.h b/orchagent/switch/trimming/container.h new file mode 100644 index 00000000000..7a9ad1d65fc --- /dev/null +++ b/orchagent/switch/trimming/container.h @@ -0,0 +1,38 @@ +#pragma once + +extern "C" { +#include +} + +#include +#include + +class SwitchTrimming final +{ +public: + SwitchTrimming() = default; + ~SwitchTrimming() = default; + + struct { + sai_uint32_t value; + bool is_set = false; + } size; // Trim packets to this size to reduce bandwidth + + struct { + sai_uint8_t value; + bool is_set = false; + } dscp; // New packet trimming DSCP value + + struct { + struct { + sai_packet_trim_queue_resolution_mode_t value; + bool is_set = false; + } mode; + struct { + sai_uint8_t value; + bool is_set = false; + } index; + } queue; // New packet trimming queue index + + std::unordered_map fieldValueMap; +}; diff --git a/orchagent/switch/trimming/helper.cpp b/orchagent/switch/trimming/helper.cpp new file mode 100644 index 00000000000..0c2f5b81a7d --- /dev/null +++ b/orchagent/switch/trimming/helper.cpp @@ -0,0 +1,200 @@ +// includes ----------------------------------------------------------------------------------------------------------- + +extern "C" { +#include +} + +#include +#include + +#include +#include + +#include + +#include +#include + +#include "schema.h" +#include "helper.h" + +using namespace swss; + +// constants ---------------------------------------------------------------------------------------------------------- + +static const std::uint8_t minDscp = 0; +static const std::uint8_t maxDscp = 63; + +// functions ---------------------------------------------------------------------------------------------------------- + +static inline std::uint8_t toUInt8(const std::string &str) +{ + return to_uint(str); +} + +static inline std::uint32_t toUInt32(const std::string &str) +{ + return to_uint(str); +} + +// helper ------------------------------------------------------------------------------------------------------------- + +bool SwitchTrimmingHelper::isStaticQueueMode(const SwitchTrimming &cfg) const +{ + return cfg.queue.mode.is_set && (cfg.queue.mode.value == SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_STATIC); +} + +const SwitchTrimming& SwitchTrimmingHelper::getConfig() const +{ + return cfg; +} + +void SwitchTrimmingHelper::setConfig(const SwitchTrimming &value) +{ + cfg = value; +} + +bool SwitchTrimmingHelper::parseSize(SwitchTrimming &cfg, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty value is prohibited", field.c_str()); + return false; + } + + try + { + cfg.size.value = toUInt32(value); + cfg.size.is_set = true; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to parse field(%s): %s", field.c_str(), e.what()); + return false; + } + + return true; +} + +bool SwitchTrimmingHelper::parseDscp(SwitchTrimming &cfg, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty value is prohibited", field.c_str()); + return false; + } + + try + { + cfg.dscp.value = toUInt8(value); + cfg.dscp.is_set = true; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to parse field(%s): %s", field.c_str(), e.what()); + return false; + } + + if (!((minDscp <= cfg.dscp.value) && (cfg.dscp.value <= maxDscp))) + { + SWSS_LOG_ERROR( + "Failed to parse field(%s): value(%s) is out of range: %u <= speed <= %u", + field.c_str(), value.c_str(), minDscp, maxDscp + ); + return false; + } + + return true; +} + +bool SwitchTrimmingHelper::parseQueue(SwitchTrimming &cfg, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty value is prohibited", field.c_str()); + return false; + } + + if (boost::algorithm::to_lower_copy(value) == SWITCH_TRIMMING_QUEUE_INDEX_DYNAMIC) + { + cfg.queue.mode.value = SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_DYNAMIC; + cfg.queue.mode.is_set = true; + return true; + } + + try + { + cfg.queue.index.value = toUInt8(value); + cfg.queue.index.is_set = true; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to parse field(%s): %s", field.c_str(), e.what()); + return false; + } + + cfg.queue.mode.value = SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_STATIC; + cfg.queue.mode.is_set = true; + + return true; +} + +bool SwitchTrimmingHelper::parseConfig(SwitchTrimming &cfg) const +{ + SWSS_LOG_ENTER(); + + for (const auto &cit : cfg.fieldValueMap) + { + const auto &field = cit.first; + const auto &value = cit.second; + + if (field == SWITCH_TRIMMING_SIZE) + { + if (!parseSize(cfg, field, value)) + { + return false; + } + } + else if (field == SWITCH_TRIMMING_DSCP_VALUE) + { + if (!parseDscp(cfg, field, value)) + { + return false; + } + } + else if (field == SWITCH_TRIMMING_QUEUE_INDEX) + { + if (!parseQueue(cfg, field, value)) + { + return false; + } + } + else + { + SWSS_LOG_WARN("Unknown field(%s): skipping ...", field.c_str()); + } + } + + return validateConfig(cfg); +} + +bool SwitchTrimmingHelper::validateConfig(SwitchTrimming &cfg) const +{ + SWSS_LOG_ENTER(); + + auto cond = cfg.size.is_set || cfg.dscp.is_set || cfg.queue.mode.is_set; + + if (!cond) + { + SWSS_LOG_ERROR("Validation error: missing valid fields"); + return false; + } + + return true; +} diff --git a/orchagent/switch/trimming/helper.h b/orchagent/switch/trimming/helper.h new file mode 100644 index 00000000000..f805b58d652 --- /dev/null +++ b/orchagent/switch/trimming/helper.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "container.h" + +class SwitchTrimmingHelper final +{ +public: + SwitchTrimmingHelper() = default; + ~SwitchTrimmingHelper() = default; + + bool isStaticQueueMode(const SwitchTrimming &cfg) const; + + const SwitchTrimming& getConfig() const; + void setConfig(const SwitchTrimming &cfg); + + bool parseConfig(SwitchTrimming &cfg) const; + +private: + bool parseSize(SwitchTrimming &cfg, const std::string &field, const std::string &value) const; + bool parseDscp(SwitchTrimming &cfg, const std::string &field, const std::string &value) const; + bool parseQueue(SwitchTrimming &cfg, const std::string &field, const std::string &value) const; + + bool validateConfig(SwitchTrimming &cfg) const; + +private: + SwitchTrimming cfg; +}; diff --git a/orchagent/switch/trimming/schema.h b/orchagent/switch/trimming/schema.h new file mode 100644 index 00000000000..3069b24771b --- /dev/null +++ b/orchagent/switch/trimming/schema.h @@ -0,0 +1,12 @@ +#pragma once + +// defines ------------------------------------------------------------------------------------------------------------ + +#define SWITCH_TRIMMING_QUEUE_INDEX_DYNAMIC "dynamic" + +#define SWITCH_TRIMMING_QUEUE_MODE_STATIC "STATIC" +#define SWITCH_TRIMMING_QUEUE_MODE_DYNAMIC "DYNAMIC" + +#define SWITCH_TRIMMING_SIZE "size" +#define SWITCH_TRIMMING_DSCP_VALUE "dscp_value" +#define SWITCH_TRIMMING_QUEUE_INDEX "queue_index" diff --git a/orchagent/switchorch.cpp b/orchagent/switchorch.cpp index 2f103f35647..6f5be293dee 100644 --- a/orchagent/switchorch.cpp +++ b/orchagent/switchorch.cpp @@ -901,6 +901,235 @@ void SwitchOrch::doCfgSwitchHashTableTask(Consumer &consumer) } } +bool SwitchOrch::setSwitchTrimmingSizeSai(const SwitchTrimming &trim) const +{ + sai_attribute_t attr; + + attr.id = SAI_SWITCH_ATTR_PACKET_TRIM_SIZE; + attr.value.u32 = trim.size.value; + + auto status = sai_switch_api->set_switch_attribute(gSwitchId, &attr); + return status == SAI_STATUS_SUCCESS; +} + +bool SwitchOrch::setSwitchTrimmingDscpSai(const SwitchTrimming &trim) const +{ + sai_attribute_t attr; + + attr.id = SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE; + attr.value.u8 = trim.dscp.value; + + auto status = sai_switch_api->set_switch_attribute(gSwitchId, &attr); + return status == SAI_STATUS_SUCCESS; +} + +bool SwitchOrch::setSwitchTrimmingQueueModeSai(const SwitchTrimming &trim) const +{ + sai_attribute_t attr; + + attr.id = SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE; + attr.value.s32 = trim.queue.mode.value; + + auto status = sai_switch_api->set_switch_attribute(gSwitchId, &attr); + return status == SAI_STATUS_SUCCESS; +} + +bool SwitchOrch::setSwitchTrimmingQueueIndexSai(const SwitchTrimming &trim) const +{ + sai_attribute_t attr; + + attr.id = SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX; + attr.value.u8 = trim.queue.index.value; + + auto status = sai_switch_api->set_switch_attribute(gSwitchId, &attr); + return status == SAI_STATUS_SUCCESS; +} + +bool SwitchOrch::setSwitchTrimming(const SwitchTrimming &trim) +{ + SWSS_LOG_ENTER(); + + auto tObj = trimHlpr.getConfig(); + auto cfgUpd = false; + auto qIdxBak = false; + + if (!trimCap.isSwitchTrimmingSupported()) + { + SWSS_LOG_WARN("Switch trimming configuration is not supported: skipping ..."); + return true; + } + + if (trim.size.is_set) + { + if (!tObj.size.is_set || (tObj.size.value != trim.size.value)) + { + if (!setSwitchTrimmingSizeSai(trim)) + { + SWSS_LOG_ERROR("Failed to set switch trimming size in SAI"); + return false; + } + + cfgUpd = true; + } + } + else + { + if (tObj.size.is_set) + { + SWSS_LOG_ERROR("Failed to remove switch trimming size configuration: operation is not supported"); + return false; + } + } + + if (trim.dscp.is_set) + { + if (!tObj.dscp.is_set || (tObj.dscp.value != trim.dscp.value)) + { + if (!setSwitchTrimmingDscpSai(trim)) + { + SWSS_LOG_ERROR("Failed to set switch trimming DSCP in SAI"); + return false; + } + + cfgUpd = true; + } + } + else + { + if (tObj.dscp.is_set) + { + SWSS_LOG_ERROR("Failed to remove switch trimming DSCP configuration: operation is not supported"); + return false; + } + } + + if (trim.queue.mode.is_set) + { + if (!tObj.queue.mode.is_set || (tObj.queue.mode.value != trim.queue.mode.value)) + { + if (!trimCap.validateQueueModeCap(trim.queue.mode.value)) + { + SWSS_LOG_ERROR("Failed to validate switch trimming queue mode: capability is not supported"); + return false; + } + + if (!setSwitchTrimmingQueueModeSai(trim)) + { + SWSS_LOG_ERROR("Failed to set switch trimming queue mode in SAI"); + return false; + } + + if (trimHlpr.isStaticQueueMode(tObj)) + { + qIdxBak = true; + } + + cfgUpd = true; + } + } + else + { + if (tObj.queue.mode.is_set) + { + SWSS_LOG_ERROR("Failed to remove switch trimming queue configuration: operation is not supported"); + return false; + } + } + + if (trim.queue.index.is_set) + { + if (!tObj.queue.index.is_set || (tObj.queue.index.value != trim.queue.index.value)) + { + if (!setSwitchTrimmingQueueIndexSai(trim)) + { + SWSS_LOG_ERROR("Failed to set switch trimming queue index in SAI"); + return false; + } + + cfgUpd = true; + } + } + + // Don't update internal cache when config remains unchanged + if (!cfgUpd) + { + SWSS_LOG_NOTICE("Switch trimming in SAI is up-to-date"); + return true; + } + + if (qIdxBak) // Override queue index configuration during transition from static -> dynamic + { + auto cfg = trim; + cfg.queue.index = tObj.queue.index; + trimHlpr.setConfig(cfg); + } + else // Regular configuration update + { + trimHlpr.setConfig(trim); + } + + SWSS_LOG_NOTICE("Set switch trimming in SAI"); + + return true; +} + +void SwitchOrch::doCfgSwitchTrimmingTableTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto &map = consumer.m_toSync; + auto it = map.begin(); + + while (it != map.end()) + { + auto keyOpFieldsValues = it->second; + auto key = kfvKey(keyOpFieldsValues); + auto op = kfvOp(keyOpFieldsValues); + + SWSS_LOG_INFO("KEY: %s, OP: %s", key.c_str(), op.c_str()); + + if (key.empty()) + { + SWSS_LOG_ERROR("Failed to parse switch trimming key: empty string"); + it = map.erase(it); + continue; + } + + SwitchTrimming trim; + + if (op == SET_COMMAND) + { + for (const auto &cit : kfvFieldsValues(keyOpFieldsValues)) + { + auto fieldName = fvField(cit); + auto fieldValue = fvValue(cit); + + SWSS_LOG_INFO("FIELD: %s, VALUE: %s", fieldName.c_str(), fieldValue.c_str()); + + trim.fieldValueMap[fieldName] = fieldValue; + } + + if (trimHlpr.parseConfig(trim)) + { + if (!setSwitchTrimming(trim)) + { + SWSS_LOG_ERROR("Failed to set switch trimming: ASIC and CONFIG DB are diverged"); + } + } + } + else if (op == DEL_COMMAND) + { + SWSS_LOG_ERROR("Failed to remove switch trimming: operation is not supported: ASIC and CONFIG DB are diverged"); + } + else + { + SWSS_LOG_ERROR("Unknown operation(%s)", op.c_str()); + } + + it = map.erase(it); + } +} + void SwitchOrch::registerAsicSdkHealthEventCategories(sai_switch_attr_t saiSeverity, const string &severityString, const string &suppressed_category_list, bool isInitializing) { sai_status_t status; @@ -1046,6 +1275,10 @@ void SwitchOrch::doTask(Consumer &consumer) { doCfgSwitchHashTableTask(consumer); } + else if (tableName == CFG_SWITCH_TRIMMING_TABLE_NAME) + { + doCfgSwitchTrimmingTableTask(consumer); + } else if (tableName == CFG_SUPPRESS_ASIC_SDK_HEALTH_EVENT_NAME) { doCfgSuppressAsicSdkHealthEventTableTask(consumer); diff --git a/orchagent/switchorch.h b/orchagent/switchorch.h index 5d78660300f..8d77979c705 100644 --- a/orchagent/switchorch.h +++ b/orchagent/switchorch.h @@ -5,6 +5,8 @@ #include "timer.h" #include "switch/switch_capabilities.h" #include "switch/switch_helper.h" +#include "switch/trimming/capabilities.h" +#include "switch/trimming/helper.h" #define DEFAULT_ASIC_SENSORS_POLLER_INTERVAL 60 #define ASIC_SENSORS_POLLER_STATUS "ASIC_SENSORS_POLLER_STATUS" @@ -74,6 +76,7 @@ class SwitchOrch : public Orch void doTask(Consumer &consumer); void doTask(swss::SelectableTimer &timer); void doCfgSwitchHashTableTask(Consumer &consumer); + void doCfgSwitchTrimmingTableTask(Consumer &consumer); void doCfgSensorsTableTask(Consumer &consumer); void doCfgSuppressAsicSdkHealthEventTableTask(Consumer &consumer); void doAppSwitchTableTask(Consumer &consumer); @@ -90,6 +93,13 @@ class SwitchOrch : public Orch void querySwitchHashDefaults(); void setSwitchIcmpOffloadCapability(); + // Switch trimming + bool setSwitchTrimmingSizeSai(const SwitchTrimming &trim) const; + bool setSwitchTrimmingDscpSai(const SwitchTrimming &trim) const; + bool setSwitchTrimmingQueueModeSai(const SwitchTrimming &trim) const; + bool setSwitchTrimmingQueueIndexSai(const SwitchTrimming &trim) const; + bool setSwitchTrimming(const SwitchTrimming &trim); + sai_status_t setSwitchTunnelVxlanParams(swss::FieldValueTuple &val); void setSwitchNonSaiAttributes(swss::FieldValueTuple &val); @@ -154,7 +164,9 @@ class SwitchOrch : public Orch // Switch OA capabilities SwitchCapabilities swCap; + SwitchTrimmingCapabilities trimCap; // Switch OA helper SwitchHelper swHlpr; + SwitchTrimmingHelper trimHlpr; }; diff --git a/tests/conftest.py b/tests/conftest.py index cc55e42bd7a..d67044799bc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,6 +30,8 @@ from dvslib import dvs_hash from dvslib import dvs_switch from dvslib import dvs_twamp +from dvslib import dvs_buffer +from dvslib import dvs_queue from buffer_model import enable_dynamic_buffer @@ -2014,7 +2016,8 @@ def dvs_vlan_manager(request, dvs): def dvs_port_manager(request, dvs): request.cls.dvs_port = dvs_port.DVSPort(dvs.get_asic_db(), dvs.get_app_db(), - dvs.get_config_db()) + dvs.get_config_db(), + dvs.get_counters_db()) @pytest.fixture(scope="class") @@ -2038,7 +2041,8 @@ def dvs_hash_manager(request, dvs): @pytest.fixture(scope="class") def dvs_switch_manager(request, dvs): - request.cls.dvs_switch = dvs_switch.DVSSwitch(dvs.get_asic_db()) + request.cls.dvs_switch = dvs_switch.DVSSwitch(dvs.get_asic_db(), + dvs.get_config_db()) @pytest.fixture(scope="class") def dvs_twamp_manager(request, dvs): @@ -2048,6 +2052,18 @@ def dvs_twamp_manager(request, dvs): dvs.get_counters_db(), dvs.get_app_db()) +@pytest.fixture(scope="class") +def dvs_buffer_manager(request, dvs): + request.cls.dvs_buffer = dvs_buffer.DVSBuffer(dvs.get_asic_db(), + dvs.get_config_db(), + dvs.get_state_db()) + +@pytest.fixture(scope="class") +def dvs_queue_manager(request, dvs): + request.cls.dvs_queue = dvs_queue.DVSQueue(dvs.get_asic_db(), + dvs.get_config_db(), + dvs.get_counters_db()) + ##################### DPB fixtures ########################################### def create_dpb_config_file(dvs): cmd = "sonic-cfggen -j /etc/sonic/init_cfg.json -j /tmp/ports.json --print-data > /tmp/dpb_config_db.json" diff --git a/tests/dvslib/dvs_buffer.py b/tests/dvslib/dvs_buffer.py new file mode 100644 index 00000000000..61f4feaf568 --- /dev/null +++ b/tests/dvslib/dvs_buffer.py @@ -0,0 +1,52 @@ +"""Utilities for interacting with BUFFER objects when writing VS tests.""" + +from typing import Dict + + +class DVSBuffer: + """Manage buffer objects on the virtual switch.""" + + ASIC_BUFFER_PROFILE = "ASIC_STATE:SAI_OBJECT_TYPE_BUFFER_PROFILE" + + CONFIG_BUFFER_PROFILE = "BUFFER_PROFILE" + + STATE_BUFFER_MAX_PARAM = "BUFFER_MAX_PARAM_TABLE" + KEY_BUFFER_MAX_PARAM_GLOBAL = "global" + + def __init__(self, asic_db, config_db, state_db): + """Create a new DVS buffer manager.""" + self.asic_db = asic_db + self.config_db = config_db + self.state_db = state_db + + def update_buffer_profile( + self, + buffer_profile_name: str, + qualifiers: Dict[str, str] + ) -> None: + """Update buffer profile in CONFIG DB.""" + self.config_db.update_entry(self.CONFIG_BUFFER_PROFILE, buffer_profile_name, qualifiers) + + def update_buffer_mmu( + self, + mmu_size: str + ) -> None: + """Update buffer MMU size in STATE DB.""" + attr_dict = { + "mmu_size": mmu_size + } + self.state_db.update_entry(self.STATE_BUFFER_MAX_PARAM, self.KEY_BUFFER_MAX_PARAM_GLOBAL, attr_dict) + + def remove_buffer_mmu( + self + ) -> None: + """Remove buffer MMU size from STATE DB.""" + self.state_db.delete_entry(self.STATE_BUFFER_MAX_PARAM, self.KEY_BUFFER_MAX_PARAM_GLOBAL) + + def verify_buffer_profile( + self, + sai_buffer_profile_id: str, + sai_qualifiers: Dict[str, str] + ) -> None: + """Verify that buffer profile object has correct ASIC DB representation.""" + self.asic_db.wait_for_field_match(self.ASIC_BUFFER_PROFILE, sai_buffer_profile_id, sai_qualifiers) diff --git a/tests/dvslib/dvs_port.py b/tests/dvslib/dvs_port.py index 330245099c0..8adaae6550f 100644 --- a/tests/dvslib/dvs_port.py +++ b/tests/dvslib/dvs_port.py @@ -1,4 +1,5 @@ """Utilities for interacting with PORT objects when writing VS tests.""" + from typing import Dict, List from swsscommon import swsscommon @@ -8,14 +9,22 @@ class DVSPort(object): ASIC_DB = swsscommon.ASIC_DB APPL_DB = swsscommon.APPL_DB + CHANNEL_UNITTEST = "SAI_VS_UNITTEST_CHANNEL" + + ASIC_VIDTORID = "VIDTORID" + CFGDB_PORT = "PORT" APPDB_PORT = "PORT_TABLE" ASICDB_PORT = "ASIC_STATE:SAI_OBJECT_TYPE_PORT" - def __init__(self, asicdb, appdb, cfgdb): + COUNTERS_COUNTERS = "COUNTERS" + COUNTERS_PORT_NAME_MAP = "COUNTERS_PORT_NAME_MAP" + + def __init__(self, asicdb, appdb, cfgdb, counters_db): self.asic_db = asicdb self.app_db = appdb self.config_db = cfgdb + self.counters_db = counters_db def create_port_generic( self, @@ -63,6 +72,16 @@ def update_port( """Update PORT in Config DB.""" self.config_db.update_entry(self.CFGDB_PORT, port_name, attr_dict) + def get_port_id( + self, + port_name: str + ) -> str: + """Get port id from COUNTERS DB.""" + attr_list = [ port_name ] + fvs = self.counters_db.wait_for_fields(self.COUNTERS_PORT_NAME_MAP, "", attr_list) + + return fvs[port_name] + def get_port_ids( self, expected: int = None, @@ -86,6 +105,38 @@ def get_port_ids( return conn.wait_for_n_keys(table, expected) + def set_port_counter( + self, + sai_port_id: str, + sai_qualifiers: Dict[str, str] + ) -> None: + """Set port counter value in ASIC DB.""" + attr_list = [ sai_port_id ] + fvs = self.asic_db.wait_for_fields(self.ASIC_VIDTORID, "", attr_list) + + ntf = swsscommon.NotificationProducer(self.asic_db.db_connection, self.CHANNEL_UNITTEST) + + # Enable test mode + fvp = swsscommon.FieldValuePairs() + ntf.send("enable_unittests", "true", fvp) + + # Set queue stats + key = fvs[sai_port_id] + fvp = swsscommon.FieldValuePairs(list(sai_qualifiers.items())) + ntf.send("set_stats", str(key), fvp) + + # Disable test mode + fvp = swsscommon.FieldValuePairs() + ntf.send("enable_unittests", "false", fvp) + + def verify_port_counter( + self, + sai_port_id: str, + sai_qualifiers: Dict[str, str] + ) -> None: + """Verify that port counter object has correct COUNTERS DB representation.""" + self.counters_db.wait_for_field_match(self.COUNTERS_COUNTERS, sai_port_id, sai_qualifiers) + def verify_port_count( self, expected: int, diff --git a/tests/dvslib/dvs_queue.py b/tests/dvslib/dvs_queue.py new file mode 100644 index 00000000000..0300f5df15e --- /dev/null +++ b/tests/dvslib/dvs_queue.py @@ -0,0 +1,119 @@ +"""Utilities for interacting with QUEUE objects when writing VS tests.""" + +from typing import Dict, Union +from swsscommon import swsscommon + + +class DVSQueue: + """Manage queue objects on the virtual switch.""" + + CHANNEL_UNITTEST = "SAI_VS_UNITTEST_CHANNEL" + + ASIC_VIDTORID = "VIDTORID" + ASIC_QUEUE = "ASIC_STATE:SAI_OBJECT_TYPE_QUEUE" + + CONFIG_BUFFER_QUEUE = "BUFFER_QUEUE" + + COUNTERS_COUNTERS = "COUNTERS" + COUNTERS_QUEUE_NAME_MAP = "COUNTERS_QUEUE_NAME_MAP" + + def __init__(self, asic_db, config_db, counters_db): + """Create a new DVS queue manager.""" + self.asic_db = asic_db + self.config_db = config_db + self.counters_db = counters_db + + def get_queue_id( + self, + port_name: str, + queue_index: str + ) -> str: + """Get queue id from COUNTERS DB.""" + field = "{}:{}".format(port_name, queue_index) + + attr_list = [ field ] + fvs = self.counters_db.wait_for_fields(self.COUNTERS_QUEUE_NAME_MAP, "", attr_list) + + return fvs[field] + + def get_queue_buffer_profile_id( + self, + port_name: str, + queue_index: str + ) -> str: + """Get queue buffer profile id from ASIC DB.""" + field = "SAI_QUEUE_ATTR_BUFFER_PROFILE_ID" + + sai_queue_id = self.get_queue_id(port_name, queue_index) + attr_list = [ field ] + fvs = self.asic_db.wait_for_fields(self.ASIC_QUEUE, sai_queue_id, attr_list) + + return fvs[field] + + def get_queue_buffer_profile_name( + self, + port_name: str, + queue_index: str + ) -> str: + """Get queue buffer profile name from CONFIG DB.""" + def get_buffer_queue_key(port: str, idx: str) -> Union[str, None]: + keys = self.config_db.get_keys(self.CONFIG_BUFFER_QUEUE) + + for key in keys: + if port in key: + assert "|" in key, \ + "Malformed queue buffer entry: key={}".format(key) + _, queue = key.split("|") + + if "-" in queue: + idx1, idx2 = queue.split("-") + if int(idx1) <= int(idx) and int(idx) <= int(idx2): + return key + else: + if int(idx) == int(queue): + return key + + return None + + key = get_buffer_queue_key(port_name, queue_index) + assert key is not None, \ + "Queue buffer profile name doesn't exist: port={}, queue={}".format(port_name, queue_index) + + field = "profile" + + attr_list = [ field ] + fvs = self.config_db.wait_for_fields(self.CONFIG_BUFFER_QUEUE, key, attr_list) + + return fvs[field] + + def set_queue_counter( + self, + sai_queue_id: str, + sai_qualifiers: Dict[str, str] + ) -> None: + """Set queue counter value in ASIC DB.""" + attr_list = [ sai_queue_id ] + fvs = self.asic_db.wait_for_fields(self.ASIC_VIDTORID, "", attr_list) + + ntf = swsscommon.NotificationProducer(self.asic_db.db_connection, self.CHANNEL_UNITTEST) + + # Enable test mode + fvp = swsscommon.FieldValuePairs() + ntf.send("enable_unittests", "true", fvp) + + # Set queue stats + key = fvs[sai_queue_id] + fvp = swsscommon.FieldValuePairs(list(sai_qualifiers.items())) + ntf.send("set_stats", str(key), fvp) + + # Disable test mode + fvp = swsscommon.FieldValuePairs() + ntf.send("enable_unittests", "false", fvp) + + def verify_queue_counter( + self, + sai_queue_id: str, + sai_qualifiers: Dict[str, str] + ) -> None: + """Verify that queue counter object has correct COUNTERS DB representation.""" + self.counters_db.wait_for_field_match(self.COUNTERS_COUNTERS, sai_queue_id, sai_qualifiers) diff --git a/tests/dvslib/dvs_switch.py b/tests/dvslib/dvs_switch.py index b57dc7082fa..f858e6adb1c 100644 --- a/tests/dvslib/dvs_switch.py +++ b/tests/dvslib/dvs_switch.py @@ -1,4 +1,5 @@ """Utilities for interacting with SWITCH objects when writing VS tests.""" + from typing import Dict, List @@ -7,9 +8,20 @@ class DVSSwitch: ADB_SWITCH = "ASIC_STATE:SAI_OBJECT_TYPE_SWITCH" - def __init__(self, asic_db): + CONFIG_SWITCH_TRIMMING = "SWITCH_TRIMMING" + KEY_SWITCH_TRIMMING_GLOBAL = "GLOBAL" + + def __init__(self, asic_db, config_db): """Create a new DVS switch manager.""" self.asic_db = asic_db + self.config_db = config_db + + def update_switch_trimming( + self, + qualifiers: Dict[str, str] + ) -> None: + """Update switch trimming global in CONFIG DB.""" + self.config_db.update_entry(self.CONFIG_SWITCH_TRIMMING, self.KEY_SWITCH_TRIMMING_GLOBAL, qualifiers) def get_switch_ids( self, @@ -45,52 +57,15 @@ def verify_switch_count( """ self.get_switch_ids(expected) - def verify_switch_generic( - self, - sai_switch_id: str, - sai_qualifiers: Dict[str, str] - ) -> None: - """Verify that switch object has correct ASIC DB representation. - - Args: - sai_switch_id: The specific switch id to check in ASIC DB. - sai_qualifiers: The expected set of SAI qualifiers to be found in ASIC DB. - """ - entry = self.asic_db.wait_for_entry(self.ADB_SWITCH, sai_switch_id) - - for k, v in entry.items(): - if k == "NULL": - continue - elif k in sai_qualifiers: - if k == "SAI_SWITCH_ATTR_ECMP_DEFAULT_HASH_ALGORITHM": - assert sai_qualifiers[k] == v - elif k == "SAI_SWITCH_ATTR_LAG_DEFAULT_HASH_ALGORITHM": - assert sai_qualifiers[k] == v - else: - assert False, "Unknown SAI qualifier: key={}, value={}".format(k, v) - def verify_switch( self, sai_switch_id: str, - sai_qualifiers: Dict[str, str], - strict: bool = False + sai_qualifiers: Dict[str, str] ) -> None: """Verify that switch object has correct ASIC DB representation. Args: sai_switch_id: The specific switch id to check in ASIC DB. sai_qualifiers: The expected set of SAI qualifiers to be found in ASIC DB. - strict: Specifies whether verification should be strict """ - if strict: - self.verify_switch_generic(sai_switch_id, sai_qualifiers) - return - - entry = self.asic_db.wait_for_entry(self.ADB_SWITCH, sai_switch_id) - - attr_dict = { - **entry, - **sai_qualifiers - } - - self.verify_switch_generic(sai_switch_id, attr_dict) + self.asic_db.wait_for_field_match(self.ADB_SWITCH, sai_switch_id, sai_qualifiers) diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index bf685c47349..327f8727a3e 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -110,6 +110,8 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/saiattr.cpp \ $(top_srcdir)/orchagent/switch/switch_capabilities.cpp \ $(top_srcdir)/orchagent/switch/switch_helper.cpp \ + $(top_srcdir)/orchagent/switch/trimming/capabilities.cpp \ + $(top_srcdir)/orchagent/switch/trimming/helper.cpp \ $(top_srcdir)/orchagent/switchorch.cpp \ $(top_srcdir)/orchagent/pfcwdorch.cpp \ $(top_srcdir)/orchagent/pfcactionhandler.cpp \ diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index 018b8a5c64c..9827d0a7f03 100755 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -2054,9 +2054,9 @@ namespace aclorch_test auto it_rule = aclTableObject.rules.find(aclRuleName); ASSERT_NE(it_rule, aclTableObject.rules.end()); ASSERT_TRUE(validateAclRuleByConfOp(*it_rule->second, kfvFieldsValues(kvfAclRule.front()))); -} + } -TEST_F(AclOrchTest, AclInnerSourceMacRewriteTableValidation) + TEST_F(AclOrchTest, AclInnerSourceMacRewriteTableValidation) { const string aclTableTypeName = "INNER_SRC_MAC_REWRITE_TABLE_TYPE"; const string aclTableName = "INNER_SRC_MAC_REWRITE_TABLE"; @@ -2305,4 +2305,63 @@ TEST_F(AclOrchTest, AclInnerSourceMacRewriteTableValidation) } + TEST_F(AclOrchTest, AclRule_TrimDisableAction) + { + const std::string aclTableTypeName = "TRIM_TYPE"; + const std::string aclTableName = "TRIM_TABLE"; + const std::string aclRuleName = "TRIM_RULE"; + + // Create ACL OA + + auto orch = createAclOrch(); + + // Create ACL table type + + auto tableTypeKofvt = std::deque({ + { + aclTableTypeName, + SET_COMMAND, + { + { ACL_TABLE_TYPE_MATCHES, MATCH_SRC_IP }, + { ACL_TABLE_TYPE_ACTIONS, ACTION_DISABLE_TRIM }, + { ACL_TABLE_TYPE_BPOINT_TYPES, BIND_POINT_TYPE_PORT }, + } + } + }); + orch->doAclTableTypeTask(tableTypeKofvt); + ASSERT_NE(orch->getAclTableType(aclTableTypeName), nullptr); + + // Create ACL table + + auto tableKofvt = std::deque({ + { + aclTableName, + SET_COMMAND, + { + { ACL_TABLE_DESCRIPTION, "Test trim table" }, + { ACL_TABLE_TYPE, aclTableTypeName }, + { ACL_TABLE_STAGE, STAGE_INGRESS }, + { ACL_TABLE_PORTS, "1,2" }, + } + } + }); + orch->doAclTableTask(tableKofvt); + ASSERT_NE(orch->getAclTable(aclTableName), nullptr); + + // Create ACL rule + + auto ruleKofvt = std::deque({ + { + aclTableName + "|" + aclRuleName, + SET_COMMAND, + { + { RULE_PRIORITY, "999" }, + { MATCH_SRC_IP, "1.1.1.1/32" }, + { ACTION_PACKET_ACTION, PACKET_ACTION_DISABLE_TRIM }, + } + } + }); + orch->doAclRuleTask(ruleKofvt); + ASSERT_NE(orch->getAclRule(aclTableName, aclRuleName), nullptr); + } } // namespace nsAclOrchTest diff --git a/tests/mock_tests/flexcounter_ut.cpp b/tests/mock_tests/flexcounter_ut.cpp index c508a9a3660..cc88206c1aa 100644 --- a/tests/mock_tests/flexcounter_ut.cpp +++ b/tests/mock_tests/flexcounter_ut.cpp @@ -718,6 +718,7 @@ namespace flexcounter_test ASSERT_TRUE(checkFlexCounter(QUEUE_STAT_COUNTER_FLEX_COUNTER_GROUP, queueOid, { {QUEUE_COUNTER_ID_LIST, + "SAI_QUEUE_STAT_TRIM_PACKETS," "SAI_QUEUE_STAT_DROPPED_BYTES,SAI_QUEUE_STAT_DROPPED_PACKETS," "SAI_QUEUE_STAT_BYTES,SAI_QUEUE_STAT_PACKETS" } diff --git a/tests/test_trimming.py b/tests/test_trimming.py new file mode 100644 index 00000000000..960a2f86684 --- /dev/null +++ b/tests/test_trimming.py @@ -0,0 +1,516 @@ +import pytest +import logging + +from typing import NamedTuple + +import buffer_model + + +logging.basicConfig(level=logging.INFO) +trimlogger = logging.getLogger(__name__) + + +SAI_QUEUE_MODE_DICT = { + "static": "SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_STATIC", + "dynamic": "SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_DYNAMIC" +} +SAI_BUFFER_PROFILE_MODE_DICT = { + "drop": "SAI_BUFFER_PROFILE_PACKET_ADMISSION_FAIL_ACTION_DROP", + "trim": "SAI_BUFFER_PROFILE_PACKET_ADMISSION_FAIL_ACTION_DROP_AND_TRIM" +} + + +class TrimmingTuple(NamedTuple): + """Config DB trimming attribute container""" + size: str + dscp: str + queue: str + + +class TrimmingTupleSai(NamedTuple): + """ASIC DB trimming attribute container""" + size: str + dscp: str + mode: str + queue: str + + +@pytest.fixture(scope="class") +def dynamicModel(dvs): + trimlogger.info("Enable dynamic buffer model") + buffer_model.enable_dynamic_buffer(dvs.get_config_db(), dvs.runcmd) + yield + buffer_model.disable_dynamic_buffer(dvs.get_config_db(), dvs.runcmd) + trimlogger.info("Disable dynamic buffer model") + + +@pytest.fixture(scope="class") +def portCounters(dvs): + trimlogger.info("Initialize port counters") + dvs.runcmd("counterpoll port enable") + yield + dvs.runcmd("counterpoll port disable") + trimlogger.info("Deinitialize port counters") + + +@pytest.fixture(scope="class") +def queueCounters(dvs): + trimlogger.info("Initialize queue counters") + dvs.runcmd("counterpoll queue enable") + yield + dvs.runcmd("counterpoll queue disable") + trimlogger.info("Deinitialize queue counters") + + +@pytest.mark.usefixtures("dvs_switch_manager") +@pytest.mark.usefixtures("testlog") +class TestTrimmingFlows: + @pytest.fixture(scope="class") + def switchData(self): + trimlogger.info("Initialize switch data") + + trimlogger.info("Verify switch count") + self.dvs_switch.verify_switch_count(0) + + trimlogger.info("Get switch id") + switchIdList = self.dvs_switch.get_switch_ids() + + # Assumption: VS has only one switch object + meta_dict = { + "id": switchIdList[0] + } + + yield meta_dict + + trimlogger.info("Deinitialize switch data") + + +class TestTrimmingBasicFlows(TestTrimmingFlows): + @pytest.mark.parametrize( + "attrDict,saiAttrDict", [ + pytest.param( + TrimmingTuple(size="100", dscp="10", queue="1"), + TrimmingTupleSai(size="100", dscp="10", mode=SAI_QUEUE_MODE_DICT["static"], queue="1"), + id="static-queue-index" + ), + pytest.param( + TrimmingTuple(size="200", dscp="20", queue="dynamic"), + TrimmingTupleSai(size="200", dscp="20", mode=SAI_QUEUE_MODE_DICT["dynamic"], queue="1"), + id="dynamic-queue-index" + ) + ] + ) + def test_TrimSwitchGlobalConfiguration(self, switchData, attrDict, saiAttrDict): + attr_dict = { + "size": attrDict.size, + "dscp_value": attrDict.dscp, + "queue_index": attrDict.queue + } + + trimlogger.info("Update trimming global") + self.dvs_switch.update_switch_trimming( + qualifiers=attr_dict + ) + + switchId = switchData["id"] + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": saiAttrDict.size, + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE": saiAttrDict.dscp, + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": saiAttrDict.mode, + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": saiAttrDict.queue + } + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + +@pytest.mark.usefixtures("genericConfig") +@pytest.mark.usefixtures("restoreConfig") +class TestTrimmingNegativeFlows(TestTrimmingFlows): + @pytest.fixture(scope="class") + def genericConfig(self, switchData): + trimlogger.info("Add generic configuration") + + switchId = switchData["id"] + + attr_dict = { + "size": "100", + "dscp_value": "10", + "queue_index": "1" + } + + trimlogger.info("Update trimming global") + self.dvs_switch.update_switch_trimming( + qualifiers=attr_dict + ) + + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": "100", + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE": "10", + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": SAI_QUEUE_MODE_DICT["static"], + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": "1" + } + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + yield + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + trimlogger.info("Verify generic configuration") + + @pytest.fixture(scope="function") + def restoreConfig(self, switchData, request): + switchId = switchData["id"] + + attrDict = request.getfixturevalue("attrDict") + saiAttrDict = request.getfixturevalue("saiAttrDict") + + yield + + attr_dict = {} + + if attrDict.size is not None: + attr_dict = { + "size": "100" + } + + if attrDict.dscp is not None: + attr_dict = { + "dscp_value": "10" + } + + if attrDict.queue is not None: + attr_dict = { + "queue_index": "1" + } + + trimlogger.info("Update trimming global") + self.dvs_switch.update_switch_trimming( + qualifiers=attr_dict + ) + + sai_attr_dict = {} + + if saiAttrDict.size is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": "100" + } + + if saiAttrDict.dscp is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE": "10" + } + + if saiAttrDict.queue is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": SAI_QUEUE_MODE_DICT["static"], + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": "1" + } + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + trimlogger.info("Verify configuration rollback: {}".format(str(attrDict))) + + @pytest.mark.parametrize( + "attrDict,saiAttrDict", [ + pytest.param( + TrimmingTuple(size="", dscp=None, queue=None), + TrimmingTupleSai(size="100", dscp=None, mode=None, queue=None), + id="size-empty" + ), + pytest.param( + TrimmingTuple(size="-1", dscp=None, queue=None), + TrimmingTupleSai(size="100", dscp=None, mode=None, queue=None), + id="size-min-1" + ), + pytest.param( + TrimmingTuple(size="4294967296", dscp=None, queue=None), + TrimmingTupleSai(size="100", dscp=None, mode=None, queue=None), + id="size-max+1" + ), + pytest.param( + TrimmingTuple(size=None, dscp="", queue=None), + TrimmingTupleSai(size=None, dscp="10", mode=None, queue=None), + id="dscp-empty" + ), + pytest.param( + TrimmingTuple(size=None, dscp="-1", queue=None), + TrimmingTupleSai(size=None, dscp="10", mode=None, queue=None), + id="dscp-min-1" + ), + pytest.param( + TrimmingTuple(size=None, dscp="64", queue=None), + TrimmingTupleSai(size=None, dscp="10", mode=None, queue=None), + id="dscp-max+1" + ), + pytest.param( + TrimmingTuple(size=None, dscp=None, queue=""), + TrimmingTupleSai(size=None, dscp=None, mode=SAI_QUEUE_MODE_DICT["static"], queue="1"), + id="queue-empty" + ), + pytest.param( + TrimmingTuple(size=None, dscp=None, queue="-1"), + TrimmingTupleSai(size=None, dscp=None, mode=SAI_QUEUE_MODE_DICT["static"], queue="1"), + id="queue-min-1" + ), + pytest.param( + TrimmingTuple(size=None, dscp=None, queue="256"), + TrimmingTupleSai(size=None, dscp=None, mode=SAI_QUEUE_MODE_DICT["static"], queue="1"), + id="queue-max+1" + ) + ] + ) + def test_TrimNegValueOutOfBound(self, switchData, attrDict, saiAttrDict): + switchId = switchData["id"] + + attr_dict = {} + + if attrDict.size is not None: + attr_dict = { + "size": attrDict.size + } + + if attrDict.dscp is not None: + attr_dict = { + "dscp_value": attrDict.dscp + } + + if attrDict.queue is not None: + attr_dict = { + "queue_index": attrDict.queue + } + + trimlogger.info("Update trimming global") + self.dvs_switch.update_switch_trimming( + qualifiers=attr_dict + ) + + sai_attr_dict = {} + + if saiAttrDict.size is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": saiAttrDict.size + } + + if saiAttrDict.dscp is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE": saiAttrDict.dscp + } + + if saiAttrDict.queue is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": saiAttrDict.mode, + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": saiAttrDict.queue + } + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + +@pytest.mark.usefixtures("dvs_buffer_manager") +@pytest.mark.usefixtures("dvs_queue_manager") +@pytest.mark.usefixtures("testlog") +class TrimmingBufferModel: + PORT = "Ethernet0" + QUEUE = "0" + MMU = "12766208" + + @pytest.fixture(scope="class") + def dynamicBuffer(self, dvs, dynamicModel): + trimlogger.info("Add dynamic buffer configuration") + + # W/A: Enable dynamic buffer model on VS platform + trimlogger.info("Configure buffer MMU: size={}".format(self.MMU)) + self.dvs_buffer.update_buffer_mmu(self.MMU) + + trimlogger.info("Set interface admin state to UP: port={}".format(self.PORT)) + dvs.port_admin_set(self.PORT, "up") + + yield + + trimlogger.info("Set interface admin state to DOWN: port={}".format(self.PORT)) + dvs.port_admin_set(self.PORT, "down") + + trimlogger.info("Remove buffer MMU") + self.dvs_buffer.remove_buffer_mmu() + + trimlogger.info("Remove dynamic buffer configuration") + + @pytest.fixture(scope="class") + def bufferData(self, queueCounters): + trimlogger.info("Initialize buffer data") + + trimlogger.info("Get buffer profile name: port={}, queue={}".format(self.PORT, self.QUEUE)) + bufferProfileName = self.dvs_queue.get_queue_buffer_profile_name(self.PORT, self.QUEUE) + + trimlogger.info("Get buffer profile id: port={}, queue={}".format(self.PORT, self.QUEUE)) + bufferProfileId = self.dvs_queue.get_queue_buffer_profile_id(self.PORT, self.QUEUE) + + meta_dict = { + "name": bufferProfileName, + "id": bufferProfileId + } + + yield meta_dict + + attr_dict = { + "packet_discard_action": "drop" + } + + trimlogger.info("Reset buffer profile trimming configuration: {}".format(bufferProfileName)) + self.dvs_buffer.update_buffer_profile(bufferProfileName, attr_dict) + + trimlogger.info("Deinitialize buffer data") + + def verifyBufferProfileConfiguration(self, bufferData, action): + attr_dict = { + "packet_discard_action": action + } + + trimlogger.info("Update buffer profile: {}".format(bufferData["name"])) + self.dvs_buffer.update_buffer_profile( + buffer_profile_name=bufferData["name"], + qualifiers=attr_dict + ) + + bufferProfileId = bufferData["id"] + sai_attr_dict = { + "SAI_BUFFER_PROFILE_ATTR_PACKET_ADMISSION_FAIL_ACTION": SAI_BUFFER_PROFILE_MODE_DICT[action] + } + + trimlogger.info("Validate buffer profile: {}".format(bufferData["name"])) + self.dvs_buffer.verify_buffer_profile( + sai_buffer_profile_id=bufferProfileId, + sai_qualifiers=sai_attr_dict + ) + +class TestTrimmingTraditionalBufferModel(TrimmingBufferModel): + @pytest.mark.parametrize( + "action", [ + pytest.param("drop", id="drop-packet"), + pytest.param("trim", id="trim-packet") + ] + ) + def test_TrimStaticBufferProfileConfiguration(self, bufferData, action): + self.verifyBufferProfileConfiguration(bufferData, action) + +class TestTrimmingDynamicBufferModel(TrimmingBufferModel): + @pytest.mark.parametrize( + "action", [ + pytest.param("drop", id="drop-packet"), + pytest.param("trim", id="trim-packet") + ] + ) + def test_TrimDynamicBufferProfileConfiguration(self, dynamicBuffer, bufferData, action): + self.verifyBufferProfileConfiguration(bufferData, action) + + +@pytest.mark.usefixtures("dvs_port_manager") +@pytest.mark.usefixtures("dvs_queue_manager") +@pytest.mark.usefixtures("testlog") +class TestTrimmingStats: + PORT = "Ethernet4" + QUEUE = "1" + + @pytest.fixture(scope="class") + def portData(self, portCounters): + trimlogger.info("Initialize port data") + + trimlogger.info("Get port id: port={}".format(self.PORT)) + portId = self.dvs_port.get_port_id(self.PORT) + + meta_dict = { + "id": portId + } + + yield meta_dict + + sai_attr_dict = { + "SAI_PORT_STAT_TRIM_PACKETS": "0" + } + + trimlogger.info("Reset port trimming counters: port={}".format(self.PORT)) + self.dvs_port.set_port_counter(portId, sai_attr_dict) + + trimlogger.info("Deinitialize port data") + + @pytest.fixture(scope="class") + def queueData(self, queueCounters): + trimlogger.info("Initialize queue data") + + trimlogger.info("Get queue id: port={}, queue={}".format(self.PORT, self.QUEUE)) + queueId = self.dvs_queue.get_queue_id(self.PORT, self.QUEUE) + + meta_dict = { + "id": queueId + } + + yield meta_dict + + sai_attr_dict = { + "SAI_QUEUE_STAT_TRIM_PACKETS": "0" + } + + trimlogger.info("Reset queue trimming counters: port={}, queue={}".format(self.PORT, self.QUEUE)) + self.dvs_queue.set_queue_counter(queueId, sai_attr_dict) + + trimlogger.info("Deinitialize queue data") + + def test_TrimPortStats(self, portData): + sai_attr_dict = { + "SAI_PORT_STAT_TRIM_PACKETS": "1000" + } + + trimlogger.info("Update port counters: port={}".format(self.PORT)) + self.dvs_port.set_port_counter( + sai_port_id=portData["id"], + sai_qualifiers=sai_attr_dict + ) + + trimlogger.info("Validate port counters: port={}".format(self.PORT)) + self.dvs_port.verify_port_counter( + sai_port_id=portData["id"], + sai_qualifiers=sai_attr_dict + ) + + def test_TrimQueueStats(self, queueData): + sai_attr_dict = { + "SAI_QUEUE_STAT_TRIM_PACKETS": "1000" + } + + trimlogger.info("Update queue counters: port={}, queue={}".format(self.PORT, self.QUEUE)) + self.dvs_queue.set_queue_counter( + sai_queue_id=queueData["id"], + sai_qualifiers=sai_attr_dict + ) + + trimlogger.info("Validate queue counters: port={}, queue={}".format(self.PORT, self.QUEUE)) + self.dvs_queue.verify_queue_counter( + sai_queue_id=queueData["id"], + sai_qualifiers=sai_attr_dict + ) + + +# Add Dummy always-pass test at end as workaroud +# for issue when Flaky fail on final test it invokes module tear-down before retrying +def test_nonflaky_dummy(): + pass