From 79f04e325a9042096a2ea548f0d0e3e4915382bf Mon Sep 17 00:00:00 2001 From: mssonicbld <79238446+mssonicbld@users.noreply.github.com> Date: Tue, 22 Apr 2025 16:02:09 +0800 Subject: [PATCH 1/8] Initialize the last fec ber computed values if not found (#3621) **What I did** `SAI_PORT_STAT_IF_FEC_CORRECTED_BITS_last` and `SAI_PORT_STAT_IF_FEC_NOT_CORRECTABLE_FARMES_last` counter values could be NOT present in redis, so initialize these values to ZERO **Why I did it** To prevent following error seen during warm boot ``` root@sonic:/# redis-cli --eval ./usr/share/swss/port_rates.lua oid:0x100000000000a , 2 COUNTERS 10 (error) ERR user_script:204: attempt to concatenate local 'fec_corr_bits_last' (a boolean value) script: b46cd0b446729aab6bbd20e0d4aa977e3da8f5f1, on @user_script:204. root@sonic:/# redis-cli --eval ./usr/share/swss/port_rates.lua oid:0x100000000000a , 2 COUNTERS 10 (error) ERR user_script:204: attempt to concatenate local 'fec_corr_bits_last' (a boolean value) script: b46cd0b446729aab6bbd20e0d4aa977e3da8f5f1, on @user_script:204. root@sonic:/# redis-cli --eval ./usr/share/swss/port_rates.lua oid:0x100000000000a , 2 COUNTERS 10 (error) ERR user_script:206: attempt to perform arithmetic on local 'fec_corr_bits_last' (a boolean value) script: cb98161ae9433f9bf8265cffd7d2eaaf6ac9282f, on @user_script:206. root@sonic:/# ``` **How I verified it** After fix the last computer FEC ber values are stored in redis DB ``` root@sonic:/# redis-cli --eval ./usr/share/swss/port_rates.lua oid:0x100000000000a , 2 COUNTERS 10 1) "0.18" 2) "0.82" 3) "10" 4) "DONE" root@str2-7050cx3-acs-14:/# redis-cli -n 2 hgetall "RATES:oid:0x100000000000a" 1) "SAI_PORT_STAT_IF_IN_UCAST_PKTS_last" 2) "0" 3) "SAI_PORT_STAT_IF_IN_NON_UCAST_PKTS_last" 4) "0" 5) "SAI_PORT_STAT_IF_OUT_UCAST_PKTS_last" 6) "0" 7) "SAI_PORT_STAT_IF_OUT_NON_UCAST_PKTS_last" 8) "0" 9) "SAI_PORT_STAT_IF_IN_OCTETS_last" 10) "0" 11) "SAI_PORT_STAT_IF_OUT_OCTETS_last" 12) "0" 13) "RX_BPS" 14) "0" 15) "RX_PPS" 16) "0" 17) "TX_BPS" 18) "0" 19) "TX_PPS" 20) "0" 21) "SAI_PORT_STAT_IF_FEC_CORRECTED_BITS_last" 22) "0" 23) "SAI_PORT_STAT_IF_FEC_NOT_CORRECTABLE_FARMES_last" 24) "0" 25) "FEC_PRE_BER" 26) "0" 27) "FEC_POST_BER" 28) "0" root@str2-7050cx3-acs-14:/# ``` **Details if related** --- orchagent/port_rates.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/orchagent/port_rates.lua b/orchagent/port_rates.lua index d7b8b8e4..917e7458 100644 --- a/orchagent/port_rates.lua +++ b/orchagent/port_rates.lua @@ -200,6 +200,10 @@ local function compute_rate(port) local fec_corr_bits_last = redis.call('HGET', rates_table_name .. ':' .. port, 'SAI_PORT_STAT_IF_FEC_CORRECTED_BITS_last') local fec_uncorr_frames_last = redis.call('HGET', rates_table_name .. ':' .. port, 'SAI_PORT_STAT_IF_FEC_NOT_CORRECTABLE_FARMES_last') + -- Initialize to 0 if last counter values does not exist (during first boot for eg) + fec_corr_bits_last = tonumber(fec_corr_bits_last) or 0 + fec_uncorr_frames_last = tonumber(fec_uncorr_frames_last) or 0 + local serdes_rate_total = lanes_count * serdes_speed * delta / 1000 fec_corr_bits_ber_new = (fec_corr_bits - fec_corr_bits_last) / serdes_rate_total @@ -207,7 +211,6 @@ local function compute_rate(port) else logit("FEC counters or lane info not found on " .. port) end - else redis.call('HSET', state_table, 'INIT_DONE', 'COUNTERS_LAST') end From 3b702926006024123b1037197a0d5afce53b4cac Mon Sep 17 00:00:00 2001 From: mssonicbld <79238446+mssonicbld@users.noreply.github.com> Date: Mon, 28 Apr 2025 13:01:45 +0800 Subject: [PATCH 2/8] Move timestamps out of counter table to avoid update too frequently (#75) **What I did** The timestamp of PFC watchdog polling is moved out of the COUNTER table. The PFC watchdog Lua plugin should be adjusted accordingly. **Why I did it** **How I verified it** **Details if related** --- orchagent/pfc_detect_mellanox.lua | 42 +++++++++++++------------------ 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/orchagent/pfc_detect_mellanox.lua b/orchagent/pfc_detect_mellanox.lua index e00243fa..c82662d6 100755 --- a/orchagent/pfc_detect_mellanox.lua +++ b/orchagent/pfc_detect_mellanox.lua @@ -25,9 +25,22 @@ if timestamp_last ~= false then redis.call('HSET', 'TIMESTAMP', 'effective_pfcwd_poll_time_last', global_effective_poll_time) end +-- Get timestamp from TIME_STAMP table for PFC_WD counters +-- Use a field name without spaces to avoid issues +local port_timestamp_current = tonumber(redis.call('HGET', 'COUNTERS:TIME_STAMP', 'PFC_WD_Port_Counter_time_stamp')) +local port_timestamp_last = tonumber(redis.call('HGET', 'COUNTERS:TIME_STAMP', 'PFC_WD_Port_Counter_time_stamp_last')) + +-- Update the last timestamp for all ports at once +if port_timestamp_current ~= nil then + redis.call('HSET', 'COUNTERS:TIME_STAMP', 'PFC_WD_Port_Counter_time_stamp_last', port_timestamp_current) +end + local effective_poll_time -local effective_poll_time_lasttime -local port_timestamp_last_cache = {} +if port_timestamp_current ~= nil and port_timestamp_last ~= nil then + effective_poll_time = (port_timestamp_current - port_timestamp_last) / 1000 +else + effective_poll_time = global_effective_poll_time +end local debug_storm_global = redis.call('HGET', 'DEBUG_STORM', 'enabled') == 'true' local debug_storm_threshold = tonumber(redis.call('HGET', 'DEBUG_STORM', 'threshold')) @@ -63,27 +76,6 @@ for i = n, 1, -1 do local pfc_rx_pkt_key = 'SAI_PORT_STAT_PFC_' .. queue_index .. '_RX_PKTS' local pfc_duration_key = 'SAI_PORT_STAT_PFC_' .. queue_index .. '_RX_PAUSE_DURATION_US' - -- Get port specific timestamp - local port_timestamp_current = tonumber(redis.call('HGET', counters_table_name .. ':' .. port_id, 'PFC_WD_time_stamp')) - if port_timestamp_current ~= nil then - local port_timestamp_lasttime = port_timestamp_last_cache[port_id] - if port_timestamp_lasttime == nil then - port_timestamp_lasttime = tonumber(redis.call('HGET', counters_table_name .. ':' .. port_id, 'PFC_WD_time_stamp_last')) - port_timestamp_last_cache[port_id] = port_timestamp_lasttime - redis.call('HSET', counters_table_name .. ':' .. port_id, 'PFC_WD_time_stamp_last', port_timestamp_current) - end - - if port_timestamp_lasttime ~= nil then - effective_poll_time = (port_timestamp_current - port_timestamp_lasttime) / 1000 - else - effective_poll_time = global_effective_poll_time - end - effective_poll_time_lasttime = false - else - effective_poll_time = global_effective_poll_time - effective_poll_time_lasttime = global_effective_poll_time_lasttime - end - -- Get all counters local occupancy_bytes = redis.call('HGET', counters_table_name .. ':' .. KEYS[i], 'SAI_QUEUE_STAT_CURR_OCCUPANCY_BYTES') local packets = redis.call('HGET', counters_table_name .. ':' .. KEYS[i], 'SAI_QUEUE_STAT_PACKETS') @@ -132,8 +124,8 @@ for i = n, 1, -1 do local pfc_rx_packets_string = '"pfc_rx_packets","' .. tostring(pfc_rx_packets) .. '","pfc_rx_packets_last","' .. tostring(pfc_rx_packets_last) .. '",' local storm_condition_string = '"pfc_duration","' .. tostring(pfc_duration) .. '","pfc_duration_last","' .. tostring(pfc_duration_last) .. '",' local timestamps = '"timestamp","' .. timestamp_string .. '","timestamp_last","' .. timestamp_last .. '","effective_poll_time","' .. effective_poll_time .. '"' - if effective_poll_time_lasttime ~= false then - timestamps = timestamps .. ',"effective_pfcwd_poll_time_last","' .. effective_poll_time_lasttime .. '"' + if global_effective_poll_time_lasttime ~= false then + timestamps = timestamps .. ',"effective_pfcwd_poll_time_last","' .. global_effective_poll_time_lasttime .. '"' end redis.call('PUBLISH', 'PFC_WD_ACTION', '["' .. KEYS[i] .. '","storm",' .. occupancy_string .. packets_string .. pfc_rx_packets_string .. storm_condition_string .. timestamps .. ']') is_deadlock = true From 2daf20793fe47f2e95e644a7760aff1df965e06e Mon Sep 17 00:00:00 2001 From: mssonicbld <79238446+mssonicbld@users.noreply.github.com> Date: Fri, 9 May 2025 16:02:53 +0800 Subject: [PATCH 3/8] Enable FDB learning event after all ports removed from default 1Q bridge (#79) **What I did** This PR is to fix an orchagent crash issue. Error logs are as below. ``` 2025 May 1 05:51:07.128331 str4-7060x6-64pe-7 ERR swss#orchagent: :- meta_generic_validation_remove: object 0x3a0000000000d7 reference count is 1, can't remove 2025 May 1 05:51:07.128331 str4-7060x6-64pe-7 ERR swss#orchagent: :- removeDefaultBridgePorts: Failed to remove bridge port, rv:-17 2025 May 1 05:51:07.128566 str4-7060x6-64pe-7 INFO swss#supervisord: orchagent terminate called after throwing an instance of 'std::runtime_error' 2025 May 1 05:51:07.128566 str4-7060x6-64pe-7 INFO swss#supervisord: orchagent what(): PortsOrch initialization failure 2025 May 1 05:51:07.815330 str4-7060x6-64pe-7 INFO swss#supervisord 2025-05-01 05:51:07,814 WARN exited: orchagent (terminated by SIGABRT (core dumped); not expected) ``` It's because FDB is learnt on the default bridge, which increased reference count for bridge port and caused port removal failure. The issue is addressed by **not** setting `SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY` when creating switch, and enable it after all ports being removed from default bridge. **Why I did it** This PR is to fix an orchagent crash issue. **How I verified it** 1. The change is verified on multiple platforms. FDB learning can be done normally after this change. **Broadcom** ``` admin@str4-7060x6-64pe-fan-4:~$ fdbshow No. Vlan MacAddress Port Type ----- ------ ----------------- ----------- ------- 1 1234 B6:2C:7E:FC:80:00 Ethernet496 Dynamic 2 1234 D6:5E:2C:C0:B8:0B Ethernet496 Dynamic 3 1235 CE:8F:2A:A1:00:01 Ethernet496 Dynamic ``` **Mellanox** ``` admin@str-msn2700-01:~$ fdbshow No. Vlan MacAddress Port Type ----- ------ ----------------- --------- ------- 1 1000 7C:FE:90:5E:60:01 Ethernet4 Dynamic Total number of entries 1 ``` **Cisco** ``` admin@str3-8101-03:~$ fdbshow No. Vlan MacAddress Port Type ----- ------ ----------------- ----------- ------- 1 1000 9C:09:8B:B6:E6:00 Ethernet240 Dynamic Total number of entries 1 ``` 2. The existing VS test `test_fdb.py` can cover the change. **Details if related** --- orchagent/main.cpp | 7 ------- orchagent/portsorch.cpp | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/orchagent/main.cpp b/orchagent/main.cpp index d50508f6..9ddd0672 100644 --- a/orchagent/main.cpp +++ b/orchagent/main.cpp @@ -512,13 +512,6 @@ int main(int argc, char **argv) attr.value.booldata = true; attrs.push_back(attr); - if (gMySwitchType != "dpu") - { - attr.id = SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY; - attr.value.ptr = (void *)on_fdb_event; - attrs.push_back(attr); - } - attr.id = SAI_SWITCH_ATTR_PORT_STATE_CHANGE_NOTIFY; attr.value.ptr = (void *)on_port_state_change; attrs.push_back(attr); diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index fbb39f25..7fbf65ec 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -821,6 +821,21 @@ PortsOrch::PortsOrch(DBConnector *db, DBConnector *stateDb, vectorset_switch_attribute(gSwitchId, &attr) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY attribute"); + throw runtime_error("PortsOrch initialization failure (failed to set fdb event notify)"); + } + } + /* Add port oper status notification support */ m_notificationsDb = make_shared("ASIC_DB", 0); m_portStatusNotificationConsumer = new swss::NotificationConsumer(m_notificationsDb.get(), "NOTIFICATIONS"); From 2a0856b03a1bf6e16eae05207ac4ad6f6c298a7b Mon Sep 17 00:00:00 2001 From: Nazarii Hnydyn Date: Wed, 14 May 2025 22:09:24 +0300 Subject: [PATCH 4/8] Merge pull request #78 from nazariig/202412-trim-azure [202412][trim]: Add Packet Trimming to OA --- 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 | 22 +- tests/mock_tests/Makefile.am | 2 + tests/mock_tests/aclorch_ut.cpp | 60 ++++ tests/mock_tests/flexcounter_ut.cpp | 1 + tests/test_trimming.py | 314 ++++++++++++++++++ 27 files changed, 1644 insertions(+), 10 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 adc97e57..3c35c2ce 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); @@ -2631,6 +2637,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 b50b0ced..b0b3e875 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 c0af006a..c81138db 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 570d4082..1defead3 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -102,6 +102,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 aclMirrorStageLookup = @@ -1948,6 +1949,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 9e27be2a..75c78aeb 100644 --- a/orchagent/aclorch.h +++ b/orchagent/aclorch.h @@ -61,6 +61,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" @@ -74,11 +75,12 @@ #define ACTION_META_DATA "META_DATA_ACTION" #define ACTION_DSCP "DSCP_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 00000000..0d4e6ffc --- /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 172f9457..d4a71092 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 ae4adae2..a5b53f5f 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -120,10 +120,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 74e3d7aa..0eb0b82a 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 7fbf65ec..d5e45d10 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -253,7 +253,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 = @@ -290,6 +291,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 00000000..c4c8efc7 --- /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 00000000..52ced127 --- /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 00000000..7a9ad1d6 --- /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 00000000..0c2f5b81 --- /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 00000000..f805b58d --- /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 00000000..3069b247 --- /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 64442c96..54441777 100644 --- a/orchagent/switchorch.cpp +++ b/orchagent/switchorch.cpp @@ -900,6 +900,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; @@ -1045,6 +1274,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 45ceb58e..e00a5a86 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" @@ -72,6 +74,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); @@ -87,6 +90,13 @@ class SwitchOrch : public Orch bool getSwitchHashOidSai(sai_object_id_t &oid, bool isEcmpHash) const; void querySwitchHashDefaults(); + // 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); @@ -151,7 +161,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 42b8766f..2b6bd27c 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 @@ -2004,7 +2006,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") @@ -2028,7 +2031,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): @@ -2038,6 +2042,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 00000000..61f4feaf --- /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 33024509..8adaae65 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 00000000..0300f5df --- /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 b57dc708..61ac3042 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, @@ -66,6 +78,14 @@ def verify_switch_generic( assert sai_qualifiers[k] == v elif k == "SAI_SWITCH_ATTR_LAG_DEFAULT_HASH_ALGORITHM": assert sai_qualifiers[k] == v + elif k == "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": + assert sai_qualifiers[k] == v + elif k == "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE": + assert sai_qualifiers[k] == v + elif k == "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": + assert sai_qualifiers[k] == v + elif k == "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": + assert sai_qualifiers[k] == v else: assert False, "Unknown SAI qualifier: key={}, value={}".format(k, v) diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 0bba827f..42a211f4 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -101,6 +101,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 aa6cc3b3..657174bf 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -1960,4 +1960,64 @@ namespace aclorch_test // Restore sai_switch_api. sai_switch_api = old_sai_switch_api; } + + 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 0ff39b2f..1210c386 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 00000000..3eb9b2c6 --- /dev/null +++ b/tests/test_trimming.py @@ -0,0 +1,314 @@ +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 TestTrimmingBasicFlows: + @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") + + @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("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 From a79b7e0f7a745cd551f183783545cae6afe557c2 Mon Sep 17 00:00:00 2001 From: Mahdi Ramezani Date: Thu, 15 May 2025 00:21:26 +0000 Subject: [PATCH 5/8] Set default nexthop weight to 1. Added unit tests for 'getNextHopWt'. Signed-off-by: Mahdi Ramezani --- fpmsyncd/routesync.cpp | 13 +++--- tests/mock_tests/fpmsyncd/test_routesync.cpp | 49 ++++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/fpmsyncd/routesync.cpp b/fpmsyncd/routesync.cpp index 4d6aa708..c851606d 100644 --- a/fpmsyncd/routesync.cpp +++ b/fpmsyncd/routesync.cpp @@ -2181,14 +2181,15 @@ string RouteSync::getNextHopWt(struct rtnl_route *route_obj) struct rtnl_nexthop *nexthop = rtnl_route_nexthop_n(route_obj, i); /* Get the weight of next hop */ uint8_t weight = rtnl_route_nh_get_weight(nexthop); - if (weight) + if (weight == 0) { - result += to_string(weight); - } - else - { - return ""; + nl_addr* nh_addr = rtnl_route_nh_get_gateway(nexthop); + char nh_addr_str[MAX_ADDR_SIZE + 1] = {0}; + nl_addr2str(nh_addr, nh_addr_str, MAX_ADDR_SIZE); + SWSS_LOG_INFO("Using default weight of 1 for nexthop %s", nh_addr_str); + weight = 1; // default weight is 1 } + result += to_string(weight); if (i + 1 < rtnl_route_get_nnexthops(route_obj)) { diff --git a/tests/mock_tests/fpmsyncd/test_routesync.cpp b/tests/mock_tests/fpmsyncd/test_routesync.cpp index a8de7885..1ae5113d 100644 --- a/tests/mock_tests/fpmsyncd/test_routesync.cpp +++ b/tests/mock_tests/fpmsyncd/test_routesync.cpp @@ -232,3 +232,52 @@ TEST_F(FpmSyncdResponseTest, testEvpn) ASSERT_EQ(value.get(), "0xc8"); } + +auto create_nl_addr(const char* addr_str) +{ + nl_addr* addr; + nl_addr_parse(addr_str, AF_INET, &addr); + return unique_ptr(addr, nl_addr_put); +} + +auto create_route(const char* dst_addr_str) +{ + rtnl_route* route = rtnl_route_alloc(); + auto dst_addr = create_nl_addr(dst_addr_str); + rtnl_route_set_dst(route, dst_addr.get()); + rtnl_route_set_type(route, RTN_UNICAST); + rtnl_route_set_protocol(route, RTPROT_STATIC); + rtnl_route_set_family(route, AF_INET); + rtnl_route_set_scope(route, RT_SCOPE_UNIVERSE); + rtnl_route_set_table(route, RT_TABLE_MAIN); + return unique_ptr(route, rtnl_route_put); +} + +rtnl_nexthop* create_nexthop(const char* gateway_str) +{ + static int idx = 1; // interface index + ++idx; + // Create a nexthop with 0 weight + rtnl_nexthop* nh = rtnl_route_nh_alloc(); + rtnl_route_nh_set_weight(nh, 0); + rtnl_route_nh_set_ifindex(nh, idx); + auto gateway_addr = create_nl_addr(gateway_str); + rtnl_route_nh_set_gateway(nh, gateway_addr.get()); + return nh; +} + +// Checks that when a nexthop is not assigned a weight, the default weight of 1 is used. +TEST_F(FpmSyncdResponseTest, TestGetNextHopWt) +{ + auto test_route = create_route("10.1.1.0"); + + // Create two nexthops with 0 weight + rtnl_nexthop* nh1 = create_nexthop(test_gateway); + rtnl_nexthop* nh2 = create_nexthop(test_gateway_); + + // Add new nexthops to the route + rtnl_route_add_nexthop(test_route.get(), nh1); + rtnl_route_add_nexthop(test_route.get(), nh2); + + EXPECT_EQ(m_mockRouteSync.getNextHopWt(test_route.get()), "1,1"); +} From 5cdc78e79d74c475a1b7b5b5fb599d2c6d3216bb Mon Sep 17 00:00:00 2001 From: Mahdi Ramezani Date: Thu, 15 May 2025 00:50:37 +0000 Subject: [PATCH 6/8] Fixed a compile error. Signed-off-by: Mahdi Ramezani --- tests/mock_tests/fpmsyncd/test_routesync.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mock_tests/fpmsyncd/test_routesync.cpp b/tests/mock_tests/fpmsyncd/test_routesync.cpp index 1ae5113d..136b1805 100644 --- a/tests/mock_tests/fpmsyncd/test_routesync.cpp +++ b/tests/mock_tests/fpmsyncd/test_routesync.cpp @@ -272,8 +272,8 @@ TEST_F(FpmSyncdResponseTest, TestGetNextHopWt) auto test_route = create_route("10.1.1.0"); // Create two nexthops with 0 weight - rtnl_nexthop* nh1 = create_nexthop(test_gateway); - rtnl_nexthop* nh2 = create_nexthop(test_gateway_); + rtnl_nexthop* nh1 = create_nexthop("192.168.1.1"); + rtnl_nexthop* nh2 = create_nexthop("192.168.1.2"); // Add new nexthops to the route rtnl_route_add_nexthop(test_route.get(), nh1); From a99088e003bcd97a8c77d06e098525e7bfdc279d Mon Sep 17 00:00:00 2001 From: Mahdi Ramezani Date: Thu, 15 May 2025 20:18:44 +0000 Subject: [PATCH 7/8] Removed logging code. Signed-off-by: Mahdi Ramezani --- fpmsyncd/routesync.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fpmsyncd/routesync.cpp b/fpmsyncd/routesync.cpp index c851606d..06b84285 100644 --- a/fpmsyncd/routesync.cpp +++ b/fpmsyncd/routesync.cpp @@ -2183,10 +2183,6 @@ string RouteSync::getNextHopWt(struct rtnl_route *route_obj) uint8_t weight = rtnl_route_nh_get_weight(nexthop); if (weight == 0) { - nl_addr* nh_addr = rtnl_route_nh_get_gateway(nexthop); - char nh_addr_str[MAX_ADDR_SIZE + 1] = {0}; - nl_addr2str(nh_addr, nh_addr_str, MAX_ADDR_SIZE); - SWSS_LOG_INFO("Using default weight of 1 for nexthop %s", nh_addr_str); weight = 1; // default weight is 1 } result += to_string(weight); From b801f2d9fc07fdce4bca822690bdb6c74052c882 Mon Sep 17 00:00:00 2001 From: Yakiv Huryk <62013282+Yakiv-Huryk@users.noreply.github.com> Date: Tue, 20 May 2025 03:11:07 +0300 Subject: [PATCH 8/8] [202412] [SRv6] add MySID counters support (#82) * SRv6: add MySID counters support * query SAI capability to check if SRv6 MySID counter is supported * support for SRv6 MySID counter utilizing flex counter infrastructure * extend vs test test_flex_counters.py * SRv6: increase log level for the counters state update Signed-off-by: Yakiv Huryk --------- Signed-off-by: Yakiv Huryk --- .../flex_counter/flex_counter_manager.cpp | 1 + orchagent/flex_counter/flex_counter_manager.h | 3 +- orchagent/flexcounterorch.cpp | 7 + orchagent/srv6orch.cpp | 247 +++++++++++++++++- orchagent/srv6orch.h | 45 ++-- tests/test_flex_counters.py | 32 +++ 6 files changed, 316 insertions(+), 19 deletions(-) diff --git a/orchagent/flex_counter/flex_counter_manager.cpp b/orchagent/flex_counter/flex_counter_manager.cpp index 36fba9c7..8172998e 100644 --- a/orchagent/flex_counter/flex_counter_manager.cpp +++ b/orchagent/flex_counter/flex_counter_manager.cpp @@ -50,6 +50,7 @@ const unordered_map FlexCounterManager::counter_id_field_lo { CounterType::HOSTIF_TRAP, FLOW_COUNTER_ID_LIST }, { CounterType::ROUTE, FLOW_COUNTER_ID_LIST }, { CounterType::ENI, ENI_COUNTER_ID_LIST }, + { CounterType::SRV6, SRV6_COUNTER_ID_LIST }, }; FlexManagerDirectory g_FlexManagerDirectory; diff --git a/orchagent/flex_counter/flex_counter_manager.h b/orchagent/flex_counter/flex_counter_manager.h index 1ad566ca..09862391 100644 --- a/orchagent/flex_counter/flex_counter_manager.h +++ b/orchagent/flex_counter/flex_counter_manager.h @@ -38,7 +38,8 @@ enum class CounterType TUNNEL, HOSTIF_TRAP, ROUTE, - ENI + ENI, + SRV6 }; extern bool gTraditionalFlexCounter; diff --git a/orchagent/flexcounterorch.cpp b/orchagent/flexcounterorch.cpp index 2ab009e7..0bae5ef6 100644 --- a/orchagent/flexcounterorch.cpp +++ b/orchagent/flexcounterorch.cpp @@ -27,6 +27,7 @@ extern BufferOrch *gBufferOrch; extern Directory gDirectory; extern CoppOrch *gCoppOrch; extern FlowCounterRouteOrch *gFlowCounterRouteOrch; +extern Srv6Orch *gSrv6Orch; extern sai_object_id_t gSwitchId; #define FLEX_COUNTER_DELAY_SEC 60 @@ -46,6 +47,7 @@ extern sai_object_id_t gSwitchId; #define ENI_KEY "ENI" #define WRED_QUEUE_KEY "WRED_ECN_QUEUE" #define WRED_PORT_KEY "WRED_ECN_PORT" +#define SRV6_KEY "SRV6" unordered_map flexCounterGroupMap = { @@ -71,6 +73,7 @@ unordered_map flexCounterGroupMap = {"ENI", ENI_STAT_COUNTER_FLEX_COUNTER_GROUP}, {"WRED_ECN_PORT", WRED_PORT_STAT_COUNTER_FLEX_COUNTER_GROUP}, {"WRED_ECN_QUEUE", WRED_QUEUE_STAT_COUNTER_FLEX_COUNTER_GROUP}, + {SRV6_KEY, SRV6_STAT_COUNTER_FLEX_COUNTER_GROUP}, }; @@ -270,6 +273,10 @@ void FlexCounterOrch::doTask(Consumer &consumer) m_route_flow_counter_enabled = false; } } + if (gSrv6Orch && (key == SRV6_KEY)) + { + gSrv6Orch->setCountersState((value == "enable")); + } gPortsOrch->flushCounters(); setFlexCounterGroupOperation(flexCounterGroupMap[key], value); diff --git a/orchagent/srv6orch.cpp b/orchagent/srv6orch.cpp index e429ebee..8bbce6d8 100644 --- a/orchagent/srv6orch.cpp +++ b/orchagent/srv6orch.cpp @@ -10,6 +10,8 @@ #include "crmorch.h" #include "subscriberstatetable.h" #include "redisutility.h" +#include "flex_counter_manager.h" +#include "flow_counter_handler.h" using namespace std; using namespace swss; @@ -21,6 +23,9 @@ using namespace swss; #define LOCATOR_DEFAULT_FUNC_LEN "16" #define LOCATOR_DEFAULT_ARG_LEN "0" +#define SRV6_FLEX_COUNTER_UPDATE_TIMER 1 +#define SRV6_STAT_COUNTER_POLLING_INTERVAL_MS 10000 + extern sai_object_id_t gSwitchId; extern sai_object_id_t gVirtualRouterId; extern sai_object_id_t gUnderlayIfId; @@ -31,6 +36,7 @@ extern sai_router_interface_api_t* sai_router_intfs_api; extern RouteOrch *gRouteOrch; extern CrmOrch *gCrmOrch; +extern bool gTraditionalFlexCounter; const map end_behavior_map = { @@ -89,6 +95,222 @@ static bool mySidDscpModeToSai(const string& mode, sai_tunnel_dscp_mode_t& sai_m return false; } +Srv6Orch::Srv6Orch(DBConnector *cfgDb, DBConnector *applDb, const vector& tables, SwitchOrch *switchOrch, VRFOrch *vrfOrch, NeighOrch *neighOrch): + Orch(tables), + m_vrfOrch(vrfOrch), + m_switchOrch(switchOrch), + m_neighOrch(neighOrch), + m_sidTable(applDb, APP_SRV6_SID_LIST_TABLE_NAME), + m_mysidTable(applDb, APP_SRV6_MY_SID_TABLE_NAME), + m_piccontextTable(applDb, APP_PIC_CONTEXT_TABLE_NAME), + m_mysidCfgTable(cfgDb, CFG_SRV6_MY_SID_TABLE_NAME), + m_locatorCfgTable(cfgDb, CFG_SRV6_MY_LOCATOR_TABLE_NAME), + m_counter_manager(SRV6_STAT_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, SRV6_STAT_COUNTER_POLLING_INTERVAL_MS, false) +{ + m_neighOrch->attach(this); + + initializeCounters(); +} + +Srv6Orch::~Srv6Orch() +{ + m_neighOrch->detach(this); +} + +void Srv6Orch::initializeCounters() +{ + m_mysid_counters_supported = queryMySidCountersCapability(); + if (!m_mysid_counters_supported) + { + SWSS_LOG_INFO("SRv6 counters are not supported on this platform"); + return; + } + + m_asic_db = make_shared("ASIC_DB", 0); + m_counter_db = make_shared("COUNTERS_DB", 0); + m_mysid_counters_table = make_unique(m_counter_db.get(), COUNTERS_SRV6_NAME_MAP); + + if (gTraditionalFlexCounter) + { + m_vid_to_rid_table = make_unique
(m_asic_db.get(), "VIDTORID"); + } + + m_counter_update_timer = new SelectableTimer(timespec { .tv_sec = SRV6_FLEX_COUNTER_UPDATE_TIMER , .tv_nsec = 0 }); + auto et = new ExecutableTimer(m_counter_update_timer, this, "SRV6_FLEX_COUNTER_UPDATE_TIMER"); + Orch::addExecutor(et); +} + +bool Srv6Orch::queryMySidCountersCapability() const +{ + sai_attr_capability_t capability; + sai_status_t status = sai_query_attribute_capability(gSwitchId, SAI_OBJECT_TYPE_MY_SID_ENTRY, SAI_MY_SID_ENTRY_ATTR_COUNTER_ID, &capability); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Could not query SRv6 MySID entry attribute SAI_MY_SID_ENTRY_ATTR_COUNTER_ID %d", status); + return false; + } + + return capability.set_implemented && capability.create_implemented; +} + + +bool Srv6Orch::getMySidCountersEnabled() const +{ + return m_mysid_counters_enabled; +} + +bool Srv6Orch::getMySidCountersSupported() const +{ + return m_mysid_counters_supported; +} + +IpAddress Srv6Orch::getMySidAddress(const sai_my_sid_entry_t& sai_entry) const +{ + ip_addr_t ip_addr = {}; + ip_addr.family = AF_INET6; + memcpy(&ip_addr.ip_addr.ipv6_addr, sai_entry.sid, sizeof(ip_addr.ip_addr.ipv6_addr)); + + return IpAddress(ip_addr); +} + +string Srv6Orch::getMySidCounterKey(const sai_my_sid_entry_t& sai_entry) const +{ + auto mysid_addr = getMySidAddress(sai_entry).to_string(); + auto locator_cfg = getMySidEntryLocatorCfg(sai_entry); + return getMySidPrefix(mysid_addr, locator_cfg); +} + +bool Srv6Orch::addMySidCounter(const sai_my_sid_entry_t& sai_entry, sai_object_id_t& counter_oid) +{ + SWSS_LOG_ENTER(); + + if (!FlowCounterHandler::createGenericCounter(counter_oid)) + { + SWSS_LOG_ERROR("Failed to create SAI counter for SRv6 MySID entry"); + return false; + } + + auto key = getMySidCounterKey(sai_entry); + vector fvs = { + {key, sai_serialize_object_id(counter_oid)} + }; + + m_mysid_counters_table->set("", fvs); + + auto was_empty = m_pending_counters.empty(); + m_pending_counters[counter_oid] = key; + + if (was_empty) + { + m_counter_update_timer->start(); + } + + return true; +} + +void Srv6Orch::removeMySidCounter(const sai_my_sid_entry_t& sai_entry, sai_object_id_t& counter_oid) +{ + SWSS_LOG_ENTER(); + + if (counter_oid == SAI_NULL_OBJECT_ID) + { + return; + } + + auto key = getMySidCounterKey(sai_entry); + + m_mysid_counters_table->hdel("", key); + + auto was_pending = m_pending_counters.erase(counter_oid) == 1; + if (!was_pending) + { + SWSS_LOG_INFO("Unregistering SRv6 counter for %s, oid %s", key.c_str(), sai_serialize_object_id(counter_oid).c_str()); + m_counter_manager.clearCounterIdList(counter_oid); + } + + FlowCounterHandler::removeGenericCounter(counter_oid); + counter_oid = SAI_NULL_OBJECT_ID; +} + +void Srv6Orch::setMySidEntryCounter(const sai_my_sid_entry_t& sai_entry, sai_object_id_t counter_oid) +{ + SWSS_LOG_ENTER(); + + sai_attribute_t attr; + attr.id = SAI_MY_SID_ENTRY_ATTR_COUNTER_ID; + attr.value.oid = counter_oid; + + auto status = sai_srv6_api->set_my_sid_entry_attribute(&sai_entry, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set my_sid entry counter oid to %s, rc: %s", sai_serialize_object_id(counter_oid).c_str(), sai_serialize_status(status).c_str()); + } +} + +void Srv6Orch::setCountersState(bool enable) +{ + SWSS_LOG_ENTER(); + + if (!getMySidCountersSupported()) + { + SWSS_LOG_WARN("Ignoring SRv6 counters state change as they are not supported on this platform"); + return; + } + + if (enable == m_mysid_counters_enabled) + { + return; + } + + SWSS_LOG_NOTICE("Setting SRv6 MySID counters state to %s", enable ? "enabled" : "disabled"); + + for (auto& mysid : srv6_my_sid_table_) + { + const auto& sai_entry = mysid.second.entry; + auto &counter_oid = mysid.second.counter; + + if (enable) + { + addMySidCounter(sai_entry, counter_oid); + setMySidEntryCounter(sai_entry, counter_oid); + } else { + setMySidEntryCounter(sai_entry, SAI_NULL_OBJECT_ID); + removeMySidCounter(sai_entry, counter_oid); + } + } + + m_mysid_counters_enabled = enable; +} + +void Srv6Orch::doTask(SelectableTimer &timer) +{ + SWSS_LOG_ENTER(); + + string value; + for (auto it = m_pending_counters.begin(); it != m_pending_counters.end();) + { + const auto oid = sai_serialize_object_id(it->first); + if (!gTraditionalFlexCounter || m_vid_to_rid_table->hget("", oid, value)) + { + SWSS_LOG_INFO("Registering SRv6 counter for %s, oid %s", it->second.c_str(), oid.c_str()); + + unordered_set counter_stats; + FlowCounterHandler::getGenericCounterStatIdList(counter_stats); + m_counter_manager.setCounterIdList(it->first, CounterType::SRV6, counter_stats); + it = m_pending_counters.erase(it); + } + else + { + ++it; + } + } + + if (m_pending_counters.empty()) + { + m_counter_update_timer->stop(); + } +} + MySidLocatorCfg Srv6Orch::getMySidEntryLocatorCfg(const sai_my_sid_entry_t& sai_entry) const { return { @@ -99,6 +321,12 @@ MySidLocatorCfg Srv6Orch::getMySidEntryLocatorCfg(const sai_my_sid_entry_t& sai_ }; } + +string Srv6Orch::getMySidPrefix(const string& my_sid_addr, const MySidLocatorCfg& locator_cfg) const +{ + return my_sid_addr + "/" + to_string(locator_cfg.block_len + locator_cfg.node_len + locator_cfg.func_len); +} + bool Srv6Orch::getLocatorCfgFromDb(const string& locator, MySidLocatorCfg& cfg) { vector fvs; @@ -201,7 +429,7 @@ void Srv6Orch::mySidCfgCacheRefresh() bool Srv6Orch::getMySidEntryDscpMode(const string& my_sid_addr, const MySidLocatorCfg& locator_cfg, sai_tunnel_dscp_mode_t& dscp_mode) { - auto my_sid_prefix = my_sid_addr + "/" + to_string(locator_cfg.block_len + locator_cfg.node_len + locator_cfg.func_len); + auto my_sid_prefix = getMySidPrefix(my_sid_addr, locator_cfg); auto cfg_cache = my_sid_dscp_cfg_cache_.equal_range(my_sid_prefix); if (cfg_cache.first == my_sid_dscp_cfg_cache_.end()) @@ -1356,6 +1584,20 @@ bool Srv6Orch::createUpdateMysidEntry(string my_sid_string, const string dt_vrf, sai_status_t status = SAI_STATUS_SUCCESS; if (!entry_exists) { + sai_object_id_t counter_oid = SAI_NULL_OBJECT_ID; + if (getMySidCountersSupported() && getMySidCountersEnabled()) + { + auto ok = addMySidCounter(my_sid_entry, counter_oid); + if (!ok) + { + return false; + } + + attr.id = SAI_MY_SID_ENTRY_ATTR_COUNTER_ID; + attr.value.oid = counter_oid; + attributes.push_back(attr); + } + status = sai_srv6_api->create_my_sid_entry(&my_sid_entry, (uint32_t) attributes.size(), attributes.data()); if (status != SAI_STATUS_SUCCESS) { @@ -1363,6 +1605,7 @@ bool Srv6Orch::createUpdateMysidEntry(string my_sid_string, const string dt_vrf, return false; } gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_SRV6_MY_SID_ENTRY); + srv6_my_sid_table_[key_string].counter = counter_oid; } else { @@ -1415,6 +1658,7 @@ bool Srv6Orch::deleteMysidEntry(const string my_sid_string) return false; } sai_my_sid_entry_t my_sid_entry = srv6_my_sid_table_[my_sid_string].entry; + sai_object_id_t& counter = srv6_my_sid_table_[my_sid_string].counter; SWSS_LOG_NOTICE("MySid Delete: sid %s", my_sid_string.c_str()); status = sai_srv6_api->remove_my_sid_entry(&my_sid_entry); @@ -1425,6 +1669,7 @@ bool Srv6Orch::deleteMysidEntry(const string my_sid_string) } gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_SRV6_MY_SID_ENTRY); + removeMySidCounter(my_sid_entry, counter); auto endBehavior = srv6_my_sid_table_[my_sid_string].endBehavior; /* Decrease VRF refcount */ diff --git a/orchagent/srv6orch.h b/orchagent/srv6orch.h index 9ba9e327..3a6afe03 100644 --- a/orchagent/srv6orch.h +++ b/orchagent/srv6orch.h @@ -26,6 +26,8 @@ using namespace std; using namespace swss; +#define SRV6_STAT_COUNTER_FLEX_COUNTER_GROUP "SRV6_STAT_COUNTER" + struct SidTableEntry { sai_object_id_t sid_object_id; // SRV6 SID list object id @@ -46,6 +48,7 @@ struct MySidEntry string endAdjString; // Used for END.X, END.DX4, END.DX6 sai_tunnel_dscp_mode_t dscp_mode; // Used for decapsulation configuration sai_object_id_t tunnel_term_entry; // Used for decapsulation configuration + sai_object_id_t counter; }; struct MySidIpInIpTunnel @@ -149,23 +152,8 @@ typedef std::unordered_multimap Srv6MySidDscpC class Srv6Orch : public Orch, public Observer { public: - Srv6Orch(DBConnector *cfgDb, DBConnector *applDb, const vector& tables, SwitchOrch *switchOrch, VRFOrch *vrfOrch, NeighOrch *neighOrch): - Orch(tables), - m_vrfOrch(vrfOrch), - m_switchOrch(switchOrch), - m_neighOrch(neighOrch), - m_sidTable(applDb, APP_SRV6_SID_LIST_TABLE_NAME), - m_mysidTable(applDb, APP_SRV6_MY_SID_TABLE_NAME), - m_piccontextTable(applDb, APP_PIC_CONTEXT_TABLE_NAME), - m_mysidCfgTable(cfgDb, CFG_SRV6_MY_SID_TABLE_NAME), - m_locatorCfgTable(cfgDb, CFG_SRV6_MY_LOCATOR_TABLE_NAME) - { - m_neighOrch->attach(this); - } - ~Srv6Orch() - { - m_neighOrch->detach(this); - } + Srv6Orch(DBConnector *cfgDb, DBConnector *applDb, const vector& tables, SwitchOrch *switchOrch, VRFOrch *vrfOrch, NeighOrch *neighOrch); + ~Srv6Orch(); void increasePicContextIdRefCount(const std::string&); void decreasePicContextIdRefCount(const std::string&); void increasePrefixAggIdRefCount(const NextHopGroupKey&); @@ -182,9 +170,11 @@ class Srv6Orch : public Orch, public Observer bool removeSrv6Nexthops(const std::vector &nhgv); void update(SubjectType, void *); bool contextIdExists(const std::string &context_id); + void setCountersState(bool enable); private: void doTask(Consumer &consumer); + void doTask(SelectableTimer &timer); task_process_status doTaskSidTable(const KeyOpFieldsValuesTuple &tuple); void doTaskMySidTable(const KeyOpFieldsValuesTuple &tuple); task_process_status doTaskPicContextTable(const KeyOpFieldsValuesTuple &tuple); @@ -200,6 +190,7 @@ class Srv6Orch : public Orch, public Observer bool sidEntryEndpointBehavior(const string action, sai_my_sid_entry_endpoint_behavior_t &end_behavior, sai_my_sid_entry_endpoint_behavior_flavor_t &end_flavor); MySidLocatorCfg getMySidEntryLocatorCfg(const sai_my_sid_entry_t& sai_entry) const; + string getMySidPrefix(const string& my_sid_addr, const MySidLocatorCfg& locator_cfg) const; bool getLocatorCfgFromDb(const string& locator, MySidLocatorCfg& cfg); bool reverseLookupLocator(const vector& candidates, const MySidLocatorCfg& locator_cfg, string& locator); void mySidCfgCacheRefresh(); @@ -233,6 +224,16 @@ class Srv6Orch : public Orch, public Observer bool deleteSrv6Vpns(const std::string &context_id); void updateNeighbor(const NeighborUpdate& update); + void initializeCounters(); + bool queryMySidCountersCapability() const; + bool getMySidCountersEnabled() const; + bool getMySidCountersSupported() const; + string getMySidCounterKey(const sai_my_sid_entry_t& sai_entry) const; + IpAddress getMySidAddress(const sai_my_sid_entry_t& sai_entry) const; + bool addMySidCounter(const sai_my_sid_entry_t& sai_entry, sai_object_id_t& counter_oid); + void removeMySidCounter(const sai_my_sid_entry_t& sai_entry, sai_object_id_t& counter_oid); + void setMySidEntryCounter(const sai_my_sid_entry_t& sai_entry, sai_object_id_t counter_oid); + ProducerStateTable m_sidTable; ProducerStateTable m_mysidTable; ProducerStateTable m_piccontextTable; @@ -255,6 +256,16 @@ class Srv6Orch : public Orch, public Observer SwitchOrch *m_switchOrch; NeighOrch *m_neighOrch; + FlexCounterManager m_counter_manager; + unique_ptr
m_mysid_counters_table; + unique_ptr
m_vid_to_rid_table; + shared_ptr m_counter_db; + shared_ptr m_asic_db; + map m_pending_counters; + SelectableTimer* m_counter_update_timer = nullptr; + bool m_mysid_counters_enabled = false; + bool m_mysid_counters_supported = false; + /* * Map to store the SRv6 MySID entries not yet configured in ASIC because associated to a non-ready nexthop * diff --git a/tests/test_flex_counters.py b/tests/test_flex_counters.py index 6d5e69a0..f6407342 100644 --- a/tests/test_flex_counters.py +++ b/tests/test_flex_counters.py @@ -88,6 +88,13 @@ 'key': 'WRED_ECN_PORT', 'group_name': 'WRED_ECN_PORT_STAT_COUNTER', 'name_map': 'COUNTERS_PORT_NAME_MAP', + }, + 'srv6_counter': { + 'key': 'SRV6', + 'group_name': 'SRV6_STAT_COUNTER', + 'name_map': 'COUNTERS_SRV6_NAME_MAP', + 'pre_test': 'pre_srv6_counter_test', + 'post_test': 'post_srv6_counter_test', } } @@ -194,6 +201,19 @@ def pre_route_flow_counter_test(self, meta_data): dvs.servers[1].runcmd("ping -6 -c 1 2001::1") dvs.runcmd("vtysh -c \"configure terminal\" -c \"ipv6 route 2000::/64 2001::2\"") + def pre_srv6_counter_test(self, meta_data): + dvs = meta_data['dvs'] + dvs.runcmd("ip link add sr0 type dummy") + dvs.runcmd("ip link set sr0 up") + + self.config_db.create_entry("SRV6_MY_LOCATORS", "loc1", {"prefix": "1000:0:1::", "block_len": "32", "node_len": "16", "func_len": "0", "arg_len": "0"}) + self.config_db.create_entry("SRV6_MY_SIDS", f'loc1|1000:0:1::/48', {"decap_dscp_mode": "pipe"}) + + loc_cmd = 'vtysh -c "configure terminal" -c "segment-routing" -c "srv6" -c "locators" -c "locator loc1" -c "prefix 1000:0:1::/48 block-len 32 node-len 16 func-bits 0" -c "behavior usid"' + sid_cmd = 'vtysh -c "configure terminal" -c "segment-routing" -c "srv6" -c "static-sids" -c "sid 1000:0:1::/48 locator loc1 behavior uN"' + dvs.runcmd(loc_cmd) + dvs.runcmd(sid_cmd) + def post_rif_counter_test(self, meta_data): self.config_db.db_connection.hdel('INTERFACE|Ethernet0|192.168.0.1/24', "NULL") @@ -267,6 +287,18 @@ def post_route_flow_counter_test(self, meta_data): dvs.servers[1].runcmd("ip -6 address del 2001::2/64 dev eth0") self.config_db.delete_entry('FLOW_COUNTER_ROUTE_PATTERN', '2000::/64') + def post_srv6_counter_test(self, meta_data): + dvs = meta_data['dvs'] + sid_cmd = 'vtysh -c "configure terminal" -c "segment-routing" -c "srv6" -c "static-sids" -c "no sid 1000:0:1::/48 locator loc1 behavior uN"' + loc_cmd = 'vtysh -c "configure terminal" -c "segment-routing" -c "srv6" -c "locators" -c "no locator loc1"' + dvs.runcmd(sid_cmd) + dvs.runcmd(loc_cmd) + + self.config_db.delete_entry("SRV6_MY_SIDS", f"loc1|1000:0:1::/48") + self.config_db.delete_entry("SRV6_MY_LOCATORS", "loc1") + + dvs.runcmd("ip link del sr0 type dummy") + def test_add_remove_trap(self, dvs): """Test steps: 1. Enable trap_flow_counter