diff --git a/orchagent/flex_counter/flex_counter_manager.cpp b/orchagent/flex_counter/flex_counter_manager.cpp index b062650ff7..c86c4c703a 100644 --- a/orchagent/flex_counter/flex_counter_manager.cpp +++ b/orchagent/flex_counter/flex_counter_manager.cpp @@ -36,21 +36,22 @@ const unordered_map FlexCounterManager::status_lookup = const unordered_map FlexCounterManager::counter_id_field_lookup = { - { CounterType::PORT_DEBUG, PORT_DEBUG_COUNTER_ID_LIST }, - { CounterType::SWITCH_DEBUG, SWITCH_DEBUG_COUNTER_ID_LIST }, - { CounterType::PORT, PORT_COUNTER_ID_LIST }, - { CounterType::QUEUE, QUEUE_COUNTER_ID_LIST }, - { CounterType::QUEUE_ATTR, QUEUE_ATTR_ID_LIST }, - { CounterType::PRIORITY_GROUP, PG_COUNTER_ID_LIST }, - { CounterType::MACSEC_SA_ATTR, MACSEC_SA_ATTR_ID_LIST }, - { CounterType::MACSEC_SA, MACSEC_SA_COUNTER_ID_LIST }, - { CounterType::MACSEC_FLOW, MACSEC_FLOW_COUNTER_ID_LIST }, - { CounterType::ACL_COUNTER, ACL_COUNTER_ATTR_ID_LIST }, - { CounterType::TUNNEL, TUNNEL_COUNTER_ID_LIST }, - { 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 }, + { CounterType::PORT_DEBUG, PORT_DEBUG_COUNTER_ID_LIST }, + { CounterType::PORT_ATTR, PORT_ATTR_ID_LIST }, + { CounterType::SWITCH_DEBUG, SWITCH_DEBUG_COUNTER_ID_LIST }, + { CounterType::PORT, PORT_COUNTER_ID_LIST }, + { CounterType::QUEUE, QUEUE_COUNTER_ID_LIST }, + { CounterType::QUEUE_ATTR, QUEUE_ATTR_ID_LIST }, + { CounterType::PRIORITY_GROUP, PG_COUNTER_ID_LIST }, + { CounterType::MACSEC_SA_ATTR, MACSEC_SA_ATTR_ID_LIST }, + { CounterType::MACSEC_SA, MACSEC_SA_COUNTER_ID_LIST }, + { CounterType::MACSEC_FLOW, MACSEC_FLOW_COUNTER_ID_LIST }, + { CounterType::ACL_COUNTER, ACL_COUNTER_ATTR_ID_LIST }, + { CounterType::TUNNEL, TUNNEL_COUNTER_ID_LIST }, + { 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 ae58e024bb..4412bedba7 100644 --- a/orchagent/flex_counter/flex_counter_manager.h +++ b/orchagent/flex_counter/flex_counter_manager.h @@ -26,6 +26,7 @@ enum class StatsMode enum class CounterType { PORT, + PORT_ATTR, QUEUE, QUEUE_ATTR, PRIORITY_GROUP, diff --git a/orchagent/flexcounterorch.cpp b/orchagent/flexcounterorch.cpp index 7dc4033add..9c87da79a4 100644 --- a/orchagent/flexcounterorch.cpp +++ b/orchagent/flexcounterorch.cpp @@ -34,6 +34,7 @@ extern sai_object_id_t gSwitchId; #define BUFFER_POOL_WATERMARK_KEY "BUFFER_POOL_WATERMARK" #define PORT_KEY "PORT" +#define PORT_ATTR_KEY "PORT_ATTR" #define PORT_BUFFER_DROP_KEY "PORT_BUFFER_DROP" #define QUEUE_KEY "QUEUE" #define QUEUE_WATERMARK "QUEUE_WATERMARK" @@ -52,6 +53,7 @@ extern sai_object_id_t gSwitchId; unordered_map flexCounterGroupMap = { {"PORT", PORT_STAT_COUNTER_FLEX_COUNTER_GROUP}, + {"PORT_ATTR", PORT_ATTR_FLEX_COUNTER_GROUP}, {"PORT_RATES", PORT_RATE_COUNTER_FLEX_COUNTER_GROUP}, {"PORT_BUFFER_DROP", PORT_BUFFER_DROP_STAT_FLEX_COUNTER_GROUP}, {"QUEUE", QUEUE_STAT_COUNTER_FLEX_COUNTER_GROUP}, @@ -215,17 +217,17 @@ void FlexCounterOrch::doTask(Consumer &consumer) m_pg_watermark_enabled = true; gPortsOrch->addPriorityGroupWatermarkFlexCounters(getPgConfigurations()); } - else if(key == WRED_PORT_KEY) - { + else if(key == WRED_PORT_KEY) + { gPortsOrch->generateWredPortCounterMap(); m_wred_port_counter_enabled = true; - } - else if(key == WRED_QUEUE_KEY) - { + } + else if(key == WRED_QUEUE_KEY) + { gPortsOrch->generateQueueMap(getQueueConfigurations()); m_wred_queue_counter_enabled = true; gPortsOrch->addWredQueueFlexCounters(getQueueConfigurations()); - } + } } if(gIntfsOrch && (key == RIF_KEY) && (value == "enable")) { @@ -277,6 +279,19 @@ void FlexCounterOrch::doTask(Consumer &consumer) { gSrv6Orch->setCountersState((value == "enable")); } + if (gPortsOrch && (key == PORT_ATTR_KEY)) + { + if(value == "enable" && !m_port_attr_enabled) + { + m_port_attr_enabled = true; + gPortsOrch->generatePortAttrCounterMap(); + } + if (value == "disable" && m_port_attr_enabled) + { + gPortsOrch->clearPortAttrCounterMap(); + m_port_attr_enabled = false; + } + } if (gPortsOrch) { @@ -336,6 +351,11 @@ bool FlexCounterOrch::getPortCountersState() const return m_port_counter_enabled; } +bool FlexCounterOrch::getPortAttrCountersState() const +{ + return m_port_attr_enabled; +} + bool FlexCounterOrch::getPortBufferDropCountersState() const { return m_port_buffer_drop_counter_enabled; diff --git a/orchagent/flexcounterorch.h b/orchagent/flexcounterorch.h index 40f469a431..84d7251fe2 100644 --- a/orchagent/flexcounterorch.h +++ b/orchagent/flexcounterorch.h @@ -45,6 +45,7 @@ class FlexCounterOrch: public Orch FlexCounterOrch(swss::DBConnector *db, std::vector &tableNames); virtual ~FlexCounterOrch(void); bool getPortCountersState() const; + bool getPortAttrCountersState() const; bool getPortBufferDropCountersState() const; bool getQueueCountersState() const; bool getQueueWatermarkCountersState() const; @@ -60,6 +61,7 @@ class FlexCounterOrch: public Orch private: bool m_port_counter_enabled = false; + bool m_port_attr_enabled = false; bool m_port_buffer_drop_counter_enabled = false; bool m_queue_enabled = false; bool m_queue_watermark_enabled = false; diff --git a/orchagent/p4orch/tests/fake_portorch.cpp b/orchagent/p4orch/tests/fake_portorch.cpp index ac7de7219e..a19a81444a 100644 --- a/orchagent/p4orch/tests/fake_portorch.cpp +++ b/orchagent/p4orch/tests/fake_portorch.cpp @@ -24,6 +24,7 @@ PortsOrch::PortsOrch(DBConnector *db, DBConnector *stateDb, vector PortsOrch::generateCounterStats(const vector return {}; } +const std::vector& PortsOrch::getPortAttrIds() const +{ + static const std::vector empty_vector = {}; + return empty_vector; +} + void PortsOrch::doTask(swss::SelectableTimer &timer) { } diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index f793e8969e..dcd6c930c9 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -173,6 +173,13 @@ static map sai_queue_type_string_map = {SAI_QUEUE_TYPE_UNICAST_VOQ, "SAI_QUEUE_TYPE_UNICAST_VOQ"}, }; +const vector port_attr_ids = +{ + SAI_PORT_ATTR_RX_SIGNAL_DETECT, // RX signal detection per lane + SAI_PORT_ATTR_FEC_ALIGNMENT_LOCK, // FEC alignment lock status per lane + SAI_PORT_ATTR_RX_SNR // Receive Signal-to-Noise Ratio per lane +}; + const vector port_stat_ids = { SAI_PORT_STAT_IF_IN_OCTETS, @@ -562,6 +569,7 @@ PortsOrch::PortsOrch(DBConnector *db, DBConnector *stateDb, vectorgetPortAttrCountersState()) + { + if (!m_supported_phy_attrs.empty()) + { + if (p.m_type == Port::Type::PHY && supportsPortAttr(p.m_port_id, p.m_alias.c_str())) + { + auto port_attr_stats = generateCounterStats(m_supported_phy_attrs, sai_serialize_port_attr); + port_attr_manager.setCounterIdList(p.m_port_id, + CounterType::PORT_ATTR, port_attr_stats); + } + } + } if (flex_counters_orch->getPortBufferDropCountersState()) { auto port_buffer_drop_stats = generateCounterStats(port_buffer_drop_stat_ids, sai_serialize_port_stat); @@ -3885,6 +3906,13 @@ void PortsOrch::deInitPort(string alias, sai_object_id_t port_id) { wred_port_stat_manager.clearCounterIdList(p.m_port_id); } + if (!m_supported_phy_attrs.empty()) + { + if (p.m_type == Port::Type::PHY && supportsPortAttr(p.m_port_id, p.m_alias.c_str())) + { + port_attr_manager.clearCounterIdList(p.m_port_id); + } + } /* remove port name map from counter table */ m_counterTable->hdel("", alias); @@ -8361,6 +8389,158 @@ void PortsOrch::generatePortBufferDropCounterMap() m_isPortBufferDropCounterMapGenerated = true; } +void PortsOrch::queryPortAttrCapabilities() +{ + if (m_phy_attr_capability_checked) + { + return; + } + + for (const auto& attr_id : port_attr_ids) + { + sai_attr_capability_t capability; + + sai_status_t status = sai_query_attribute_capability( + gSwitchId, + SAI_OBJECT_TYPE_PORT, + attr_id, + &capability + ); + + auto meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_PORT, attr_id); + std::string attr_id_str = std::to_string(attr_id); + const char* attr_name = meta ? meta->attridname : attr_id_str.c_str(); + + if (status == SAI_STATUS_SUCCESS && capability.get_implemented) + { + m_supported_phy_attrs.push_back(attr_id); + SWSS_LOG_NOTICE("PORT_ATTR: Attribute %s is SUPPORTED for GET", + attr_name); + } + else + { + SWSS_LOG_NOTICE("PORT_ATTR: Attribute %s is NOT supported (status=%d, get_implemented=%d)", + attr_name, status, capability.get_implemented); + } + } + + m_phy_attr_capability_checked = true; +} + +bool PortsOrch::supportsPortAttr(sai_object_id_t port_id, const char* port_name) +{ + // Verify port supports ALL SERDES attributes + // Query with count=0 to check if attribute is supported (expect BUFFER_OVERFLOW) + + // Check RX_SIGNAL_DETECT + sai_attribute_t test_attr; + test_attr.id = SAI_PORT_ATTR_RX_SIGNAL_DETECT; + test_attr.value.portlanelatchstatuslist.count = 0; + test_attr.value.portlanelatchstatuslist.list = nullptr; + + sai_status_t status = sai_port_api->get_port_attribute(port_id, 1, &test_attr); + if (status != SAI_STATUS_BUFFER_OVERFLOW) + { + SWSS_LOG_ERROR("PORT_ATTR: Port %s does not support RX_SIGNAL_DETECT attribute (status=%d)", + port_name, status); + return false; + } + SWSS_LOG_DEBUG("PORT_ATTR: Port %s supports RX_SIGNAL_DETECT attribute (count=%d)", + port_name, test_attr.value.portlanelatchstatuslist.count); + + // Check FEC_ALIGNMENT_LOCK + test_attr.id = SAI_PORT_ATTR_FEC_ALIGNMENT_LOCK; + test_attr.value.portlanelatchstatuslist.count = 0; + test_attr.value.portlanelatchstatuslist.list = nullptr; + + status = sai_port_api->get_port_attribute(port_id, 1, &test_attr); + if (status != SAI_STATUS_BUFFER_OVERFLOW) + { + SWSS_LOG_ERROR("PORT_ATTR: Port %s does not support FEC_ALIGNMENT_LOCK attribute (status=%d)", + port_name, status); + return false; + } + SWSS_LOG_DEBUG("PORT_ATTR: Port %s supports FEC_ALIGNMENT_LOCK attribute (count=%d)", + port_name, test_attr.value.portlanelatchstatuslist.count); + + // Check RX_SNR + test_attr.id = SAI_PORT_ATTR_RX_SNR; + test_attr.value.portsnrlist.count = 0; + test_attr.value.portsnrlist.list = nullptr; + + status = sai_port_api->get_port_attribute(port_id, 1, &test_attr); + if (status != SAI_STATUS_BUFFER_OVERFLOW) + { + SWSS_LOG_ERROR("PORT_ATTR: Port %s does not support RX_SNR attribute (status=%d)", + port_name, status); + return false; + } + SWSS_LOG_DEBUG("PORT_ATTR: Port %s supports RX_SNR attribute (count=%d)", + port_name, test_attr.value.portsnrlist.count); + return true; +} + +void PortsOrch::generatePortAttrCounterMap() +{ + if (m_isPortAttrCounterMapGenerated) + { + return; + } + + queryPortAttrCapabilities(); + + if (m_supported_phy_attrs.empty()) + { + SWSS_LOG_WARN("PORT_ATTR: No PHY attributes supported on this platform"); + m_isPortAttrCounterMapGenerated = true; + return; + } + + auto port_attr_stats = generateCounterStats(m_supported_phy_attrs, sai_serialize_port_attr); + + for (const auto& it: m_portList) + { + if (it.second.m_type == Port::Type::PHY && supportsPortAttr(it.second.m_port_id, it.second.m_alias.c_str())) + { + SWSS_LOG_DEBUG("PORT_ATTR: Setting counter ID list for port %s", + it.second.m_alias.c_str()); + + port_attr_manager.setCounterIdList(it.second.m_port_id, + CounterType::PORT_ATTR, port_attr_stats); + } + } + + m_isPortAttrCounterMapGenerated = true; +} + +void PortsOrch::clearPortAttrCounterMap() +{ + if (!m_isPortAttrCounterMapGenerated) + { + return; + } + + for (const auto& it: m_portList) + { + // Clear counter stats only for PHY ports that were previously configured + if (it.second.m_type != Port::Type::PHY) + { + continue; + } + + SWSS_LOG_DEBUG("PORT_ATTR: Clearing counter ID list for port %s", it.second.m_alias.c_str()); + + port_attr_manager.clearCounterIdList(it.second.m_port_id); + } + + m_isPortAttrCounterMapGenerated = false; +} + +const std::vector& PortsOrch::getPortAttrIds() const +{ + return port_attr_ids; +} + /**** * Func Name : generateWredPortCounterMap * Parameters : None diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index 8dace4661a..9688d9d310 100644 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -27,6 +27,7 @@ #define PORT_STAT_COUNTER_FLEX_COUNTER_GROUP "PORT_STAT_COUNTER" #define PORT_RATE_COUNTER_FLEX_COUNTER_GROUP "PORT_RATE_COUNTER" #define PORT_BUFFER_DROP_STAT_FLEX_COUNTER_GROUP "PORT_BUFFER_DROP_STAT" +#define PORT_ATTR_FLEX_COUNTER_GROUP "PORT_ATTR" #define QUEUE_STAT_COUNTER_FLEX_COUNTER_GROUP "QUEUE_STAT_COUNTER" #define QUEUE_WATERMARK_STAT_COUNTER_FLEX_COUNTER_GROUP "QUEUE_WATERMARK_STAT_COUNTER" #define PG_WATERMARK_STAT_COUNTER_FLEX_COUNTER_GROUP "PG_WATERMARK_STAT_COUNTER" @@ -124,6 +125,14 @@ struct PortCapability typedef PortCapability PortFecModeCapability_t; +extern const std::vector port_attr_ids; + +// Forward declaration for unit test friend class +namespace portphyattr_test +{ +class PortAttrTest; +} // namespace portphyattr_test + class PortsOrch : public Orch, public Subject { public: @@ -204,6 +213,12 @@ class PortsOrch : public Orch, public Subject void generatePortCounterMap(); void generatePortBufferDropCounterMap(); + void generatePortAttrCounterMap(); + void clearPortAttrCounterMap(); + const std::vector& getPortAttrIds() const; + void queryPortAttrCapabilities(); + bool supportsPortAttr(sai_object_id_t port_id, const char* port_name); + void generateWredPortCounterMap(); void generateWredQueueCounterMap(); @@ -286,6 +301,7 @@ class PortsOrch : public Orch, public Subject shared_ptr m_notificationsDb; FlexCounterTaggedCachedManager port_stat_manager; + FlexCounterTaggedCachedManager port_attr_manager; FlexCounterTaggedCachedManager port_buffer_drop_stat_manager; FlexCounterTaggedCachedManager queue_stat_manager; FlexCounterTaggedCachedManager queue_watermark_manager; @@ -484,6 +500,10 @@ class PortsOrch : public Orch, public Subject bool m_isPortCounterMapGenerated = false; bool m_isPortBufferDropCounterMapGenerated = false; + bool m_isPortAttrCounterMapGenerated = false; + bool m_phy_attr_capability_checked = false; + std::vector m_supported_phy_attrs; + bool isAutoNegEnabled(sai_object_id_t id); task_process_status setPortAutoNeg(Port &port, bool autoneg); task_process_status setPortUnreliableLOS(Port &port, bool enabled); @@ -595,5 +615,8 @@ class PortsOrch : public Orch, public Subject // Port OA helper PortHelper m_portHlpr; bool m_isWarmRestoreStage = false; + + // Friend declaration for unit tests + friend class portphyattr_test::PortAttrTest; }; #endif /* SWSS_PORTSORCH_H */ diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 4450bbc118..1fcc5e54fe 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -73,6 +73,7 @@ tests_SOURCES = aclorch_ut.cpp \ twamporch_ut.cpp \ stporch_ut.cpp \ flexcounter_ut.cpp \ + portphyattr_ut.cpp \ mock_orch_test.cpp \ mock_dash_orch_test.cpp \ zmq_orch_ut.cpp \ diff --git a/tests/mock_tests/check.h b/tests/mock_tests/check.h index a13f2abd3c..d5fdc1309e 100644 --- a/tests/mock_tests/check.h +++ b/tests/mock_tests/check.h @@ -71,8 +71,8 @@ struct Check auto act_len = sai_serialize_attribute_value(act_buf.data(), meta, &act->value); auto exp_len = sai_serialize_attribute_value(exp_buf.data(), meta, &exp->value); - assert(act_len < act_str.size()); - assert(act_len < exp_str.size()); + assert(act_len < act_buf.size()); + assert(act_len < exp_buf.size()); act_buf.resize(act_len); exp_buf.resize(exp_len); diff --git a/tests/mock_tests/portphyattr_ut.cpp b/tests/mock_tests/portphyattr_ut.cpp new file mode 100644 index 0000000000..46f6d921b9 --- /dev/null +++ b/tests/mock_tests/portphyattr_ut.cpp @@ -0,0 +1,269 @@ +/** + * @file portphyattr_ut.cpp + * @brief Unit tests for PORT_ATTR flex counter orchestration + * + * Tests the end-to-end integration of PHY attribute collection from + * FlexCounterOrch through PortsOrch to the FlexCounter infrastructure. + */ + +#include "ut_helper.h" +#include "mock_orchagent_main.h" +#include "mock_orch_test.h" +#include "mock_table.h" + +#include +#include + +extern SwitchOrch *gSwitchOrch; +extern PortsOrch *gPortsOrch; +extern BufferOrch *gBufferOrch; + +namespace portphyattr_test +{ + using namespace std; + + struct PortAttrTest : public ::testing::Test + { + PortAttrTest() {} + + void SetUp() override + { + ::testing_db::reset(); + + // Initialize database connections + m_app_db = make_shared("APPL_DB", 0); + m_config_db = make_shared("CONFIG_DB", 0); + m_state_db = make_shared("STATE_DB", 0); + m_chassis_app_db = make_shared("CHASSIS_APP_DB", 0); + m_counters_db = make_shared("COUNTERS_DB", 0); + + // Create SwitchOrch dependencies + // Required for SAI switch initialization in the mock environment + TableConnector stateDbSwitchTable(m_state_db.get(), "SWITCH_CAPABILITY"); + TableConnector app_switch_table(m_app_db.get(), APP_SWITCH_TABLE_NAME); + TableConnector conf_asic_sensors(m_config_db.get(), CFG_ASIC_SENSORS_TABLE_NAME); + + vector switch_tables = { + conf_asic_sensors, + app_switch_table + }; + + ASSERT_EQ(gSwitchOrch, nullptr); + gSwitchOrch = new SwitchOrch(m_app_db.get(), switch_tables, stateDbSwitchTable); + + // Create PortsOrch with all required table dependencies + const int portsorch_base_pri = 40; + vector port_tables = { + { APP_PORT_TABLE_NAME, portsorch_base_pri + 5 }, // Physical port config (highest priority) + { APP_SEND_TO_INGRESS_PORT_TABLE_NAME, portsorch_base_pri + 5 }, // Ingress port forwarding + { APP_VLAN_TABLE_NAME, portsorch_base_pri + 2 }, // VLAN configuration + { APP_VLAN_MEMBER_TABLE_NAME, portsorch_base_pri }, // VLAN membership (lowest priority) + { APP_LAG_TABLE_NAME, portsorch_base_pri + 4 }, // Link aggregation groups + { APP_LAG_MEMBER_TABLE_NAME, portsorch_base_pri } // LAG membership + }; + + ASSERT_EQ(gPortsOrch, nullptr); + gPortsOrch = new PortsOrch(m_app_db.get(), m_state_db.get(), port_tables, m_chassis_app_db.get()); + + vector flex_counter_tables = {CFG_FLEX_COUNTER_TABLE_NAME}; + m_flexCounterOrch = new FlexCounterOrch(m_config_db.get(), flex_counter_tables); + + // Register FlexCounterOrch in gDirectory for PortsOrch to access via gDirectory.get() + gDirectory.set(m_flexCounterOrch); + + // Create BufferOrch - required by PortsOrch for port initialization + vector buffer_tables = { APP_BUFFER_POOL_TABLE_NAME, + APP_BUFFER_PROFILE_TABLE_NAME, + APP_BUFFER_QUEUE_TABLE_NAME, + APP_BUFFER_PG_TABLE_NAME, + APP_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME, + APP_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME }; + gBufferOrch = new BufferOrch(m_app_db.get(), m_config_db.get(), m_state_db.get(), buffer_tables); + + // Initialize ports using SAI default ports + Table portTable(m_app_db.get(), APP_PORT_TABLE_NAME); + auto ports = ut_helper::getInitialSaiPorts(); + for (const auto &it : ports) + { + portTable.set(it.first, it.second); + } + + // Set PortConfigDone + portTable.set("PortConfigDone", { { "count", to_string(ports.size()) } }); + gPortsOrch->addExistingData(&portTable); + static_cast(gPortsOrch)->doTask(); + + // Signal that port initialization is complete + portTable.set("PortInitDone", { { "lanes", "0" } }); + gPortsOrch->addExistingData(&portTable); + static_cast(gPortsOrch)->doTask(); + } + + void TearDown() override + { + ::testing_db::reset(); + + gDirectory.m_values.clear(); + + delete m_flexCounterOrch; + m_flexCounterOrch = nullptr; + + delete gBufferOrch; + gBufferOrch = nullptr; + + delete gPortsOrch; + gPortsOrch = nullptr; + + delete gSwitchOrch; + gSwitchOrch = nullptr; + } + + static void SetUpTestCase() + { + // Initialize the SAI virtual switch environment for unit testing + map profile = { + { "SAI_VS_SWITCH_TYPE", "SAI_VS_SWITCH_TYPE_BCM56850" }, // Simulate Broadcom switch + { "KV_DEVICE_MAC_ADDRESS", "20:03:04:05:06:00" } // Test MAC address + }; + + // Initialize the SAI API with virtual switch support + auto status = ut_helper::initSaiApi(profile); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + sai_attribute_t attr; + + // Create the virtual switch instance + attr.id = SAI_SWITCH_ATTR_INIT_SWITCH; + attr.value.booldata = true; + status = sai_switch_api->create_switch(&gSwitchId, 1, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + } + + static void TearDownTestCase() + { + auto status = sai_switch_api->remove_switch(gSwitchId); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + gSwitchId = 0; + + ut_helper::uninitSaiApi(); + } + + + shared_ptr m_app_db; + shared_ptr m_config_db; + shared_ptr m_state_db; + shared_ptr m_chassis_app_db; + shared_ptr m_counters_db; + shared_ptr m_flex_counter_db; + + FlexCounterOrch* m_flexCounterOrch = nullptr; + }; + + /** + * PORT_ATTR flex counter enable/disable via doTask + */ + TEST_F(PortAttrTest, EnablePortAttrFlexCounterDoTask) + { + ASSERT_NE(m_flexCounterOrch, nullptr); + ASSERT_NE(gPortsOrch, nullptr); + + bool initialState = m_flexCounterOrch->getPortAttrCountersState(); + EXPECT_FALSE(initialState); + + auto consumer = dynamic_cast(m_flexCounterOrch->getExecutor(CFG_FLEX_COUNTER_TABLE_NAME)); + ASSERT_NE(consumer, nullptr); + + Table flexCounterTable(m_config_db.get(), CFG_FLEX_COUNTER_TABLE_NAME); + vector fvs; + fvs.push_back(FieldValueTuple("FLEX_COUNTER_STATUS", "enable")); + fvs.push_back(FieldValueTuple("POLL_INTERVAL", "1000")); + flexCounterTable.set("PORT_ATTR", fvs); + std::cout << " CONFIG_DB configured: FLEX_COUNTER_STATUS=enable, POLL_INTERVAL=1000" << std::endl; + + std::deque entries; + entries.push_back({"PORT_ATTR", "SET", { + {"FLEX_COUNTER_STATUS", "enable"}, + {"POLL_INTERVAL", "1000"} + }}); + + consumer->addToSync(entries); + static_cast(m_flexCounterOrch)->doTask(*consumer); + + bool state = m_flexCounterOrch->getPortAttrCountersState(); + EXPECT_TRUE(state); + std::cout << " PORT_ATTR enablement verified: state = " << (state ? "ENABLED" : "DISABLED") << std::endl; + + entries.clear(); + entries.push_back({"PORT_ATTR", "SET", {{"FLEX_COUNTER_STATUS", "disable"}}}); + + consumer->addToSync(entries); + static_cast(m_flexCounterOrch)->doTask(*consumer); + + bool disabledState = m_flexCounterOrch->getPortAttrCountersState(); + EXPECT_FALSE(disabledState); + std::cout << " PORT_ATTR disablement verified: state = " << (disabledState ? "ENABLED" : "DISABLED") << std::endl; + } + + /** + * Validates that generatePortAttrCounterMap works with PORT_ATTR counter type + */ + TEST_F(PortAttrTest, generatePortAttrCounterMap) + { + // Directly set private members via friend access + gPortsOrch->m_supported_phy_attrs = { + SAI_PORT_ATTR_RX_SIGNAL_DETECT, + SAI_PORT_ATTR_FEC_ALIGNMENT_LOCK, + SAI_PORT_ATTR_RX_SNR + }; + gPortsOrch->m_phy_attr_capability_checked = true; + + // should complete without exceptions + try { + gPortsOrch->generatePortAttrCounterMap(); + EXPECT_TRUE(gPortsOrch->m_isPortAttrCounterMapGenerated); + std::cout << " generatePortAttrCounterMap() completed successfully" << std::endl; + } catch (const std::exception& e) { + FAIL() << "generatePortAttrCounterMap() threw exception: " << e.what(); + } catch (...) { + FAIL() << "generatePortAttrCounterMap() threw unknown exception"; + } + + // Test clear and regenerate + try { + gPortsOrch->clearPortAttrCounterMap(); + EXPECT_FALSE(gPortsOrch->m_isPortAttrCounterMapGenerated); + std::cout << " clearPortAttrCounterMap() completed successfully" << std::endl; + + gPortsOrch->generatePortAttrCounterMap(); + EXPECT_TRUE(gPortsOrch->m_isPortAttrCounterMapGenerated); + std::cout << " Regenerate after clear completed successfully" << std::endl; + } catch (const std::exception& e) { + FAIL() << "Clear/regenerate cycle threw exception: " << e.what(); + } catch (...) { + FAIL() << "Clear/regenerate cycle threw unknown exception"; + } + + // Verify the operations completed without crashes + SUCCEED() << "All PORT_ATTR counter map operations completed successfully"; + } + + TEST_F(PortAttrTest, QueryPortAttrCapabilitiesWithMockedSAI) + { + ASSERT_NE(gPortsOrch, nullptr); + + EXPECT_FALSE(gPortsOrch->m_phy_attr_capability_checked); + EXPECT_TRUE(gPortsOrch->m_supported_phy_attrs.empty()); + + gPortsOrch->queryPortAttrCapabilities(); + + EXPECT_TRUE(gPortsOrch->m_phy_attr_capability_checked); + + for (const auto& attr : gPortsOrch->m_supported_phy_attrs) + { + EXPECT_TRUE(attr == SAI_PORT_ATTR_RX_SIGNAL_DETECT || + attr == SAI_PORT_ATTR_FEC_ALIGNMENT_LOCK || + attr == SAI_PORT_ATTR_RX_SNR); + } + } +} // namespace portphyattr_test