diff --git a/orchagent/port.h b/orchagent/port.h index f6b598edeb..e4a15c17ad 100644 --- a/orchagent/port.h +++ b/orchagent/port.h @@ -164,6 +164,7 @@ class Port uint32_t m_nat_zone_id = 0; uint32_t m_vnid = VNID_NONE; uint32_t m_fdb_count = 0; + uint64_t m_flap_count = 0; uint32_t m_up_member_count = 0; uint32_t m_maximum_headroom = 0; std::set m_adv_speeds; diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index ff333f1e83..23c1bc324e 100755 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -3107,6 +3107,30 @@ bool PortsOrch::removeVlanHostIntf(Port vl) return true; } +void PortsOrch::updateDbPortFlapCount(Port& port, sai_port_oper_status_t pstatus) +{ + SWSS_LOG_ENTER(); + + ++port.m_flap_count; + vector tuples; + FieldValueTuple tuple("flap_count", std::to_string(port.m_flap_count)); + tuples.push_back(tuple); + + auto now = std::chrono::system_clock::now(); + std::time_t now_c = std::chrono::system_clock::to_time_t(now); + if (pstatus == SAI_PORT_OPER_STATUS_DOWN) + { + FieldValueTuple tuple("last_down_time", std::ctime(&now_c)); + tuples.push_back(tuple); + } + else if (pstatus == SAI_PORT_OPER_STATUS_UP) + { + FieldValueTuple tuple("last_up_time", std::ctime(&now_c)); + tuples.push_back(tuple); + } + m_portTable->set(port.m_alias, tuples); +} + void PortsOrch::updateDbPortOperStatus(const Port& port, sai_port_oper_status_t status) const { SWSS_LOG_ENTER(); @@ -5315,7 +5339,7 @@ bool PortsOrch::initializePort(Port &port) /* Check warm start states */ vector tuples; bool exist = m_portTable->get(port.m_alias, tuples); - string operStatus; + string operStatus, flapCount = "0"; if (exist) { for (auto i : tuples) @@ -5324,9 +5348,14 @@ bool PortsOrch::initializePort(Port &port) { operStatus = fvValue(i); } + + if (fvField(i) == "flap_count") + { + flapCount = fvValue(i); + } } } - SWSS_LOG_DEBUG("initializePort %s with oper %s", port.m_alias.c_str(), operStatus.c_str()); + SWSS_LOG_INFO("Port %s with oper %s flap_count=%s", port.m_alias.c_str(), operStatus.c_str(), flapCount.c_str()); /** * Create database port oper status as DOWN if attr missing @@ -5347,6 +5376,20 @@ bool PortsOrch::initializePort(Port &port) port.m_oper_status = SAI_PORT_OPER_STATUS_DOWN; } + // initalize port flap count + if (!flapCount.empty()) + { + try + { + port.m_flap_count = stoull(flapCount); + m_portTable->hset(port.m_alias, "flap_count", flapCount); + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to get port (%s) flap_count: %s", port.m_alias.c_str(), e.what()); + } + } + /* initialize port admin status */ if (!getPortAdminStatus(port.m_port_id, port.m_admin_state_up)) { @@ -7580,6 +7623,7 @@ void PortsOrch::updatePortOperStatus(Port &port, sai_port_oper_status_t status) if (port.m_type == Port::PHY) { updateDbPortOperStatus(port, status); + updateDbPortFlapCount(port, status); updateGearboxPortOperStatus(port); /* Refresh the port states and reschedule the poller tasks */ diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index 1a37ceebdd..21ed299681 100755 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -147,6 +147,7 @@ class PortsOrch : public Orch, public Subject bool setHostIntfsOperStatus(const Port& port, bool up) const; void updateDbPortOperStatus(const Port& port, sai_port_oper_status_t status) const; + void updateDbPortFlapCount(Port& port, sai_port_oper_status_t pstatus); bool createVlanHostIntf(Port& vl, string hostif_name); bool removeVlanHostIntf(Port vl); diff --git a/tests/mock_tests/portsorch_ut.cpp b/tests/mock_tests/portsorch_ut.cpp index fca4f34beb..db50c63e66 100644 --- a/tests/mock_tests/portsorch_ut.cpp +++ b/tests/mock_tests/portsorch_ut.cpp @@ -499,6 +499,73 @@ namespace portsorch_test } }; + + /* + * Test port flap count + */ + TEST_F(PortsOrchTest, PortFlapCount) + { + Table portTable = Table(m_app_db.get(), APP_PORT_TABLE_NAME); + + // Get SAI default ports to populate DB + auto ports = ut_helper::getInitialSaiPorts(); + + // Populate port table with SAI ports + for (const auto &it : ports) + { + portTable.set(it.first, it.second); + } + + // Set PortConfigDone, PortInitDone + portTable.set("PortConfigDone", { { "count", to_string(ports.size()) } }); + portTable.set("PortInitDone", { { "lanes", "0" } }); + + // refill consumer + gPortsOrch->addExistingData(&portTable); + // Apply configuration : create ports + static_cast(gPortsOrch)->doTask(); + + // Get first port, expect the oper status is not UP + Port port; + gPortsOrch->getPort("Ethernet0", port); + ASSERT_TRUE(port.m_oper_status != SAI_PORT_OPER_STATUS_UP); + ASSERT_TRUE(port.m_flap_count == 0); + + auto exec = static_cast(gPortsOrch->getExecutor("PORT_STATUS_NOTIFICATIONS")); + auto consumer = exec->getNotificationConsumer(); + + // mock a redis reply for notification, it notifies that Ehernet0 is going to up + for (uint32_t count=0; count < 5; count++) { + sai_port_oper_status_t oper_status = (count % 2 == 0) ? SAI_PORT_OPER_STATUS_UP : SAI_PORT_OPER_STATUS_DOWN; + mockReply = (redisReply *)calloc(sizeof(redisReply), 1); + mockReply->type = REDIS_REPLY_ARRAY; + mockReply->elements = 3; // REDIS_PUBLISH_MESSAGE_ELEMNTS + mockReply->element = (redisReply **)calloc(sizeof(redisReply *), mockReply->elements); + mockReply->element[2] = (redisReply *)calloc(sizeof(redisReply), 1); + mockReply->element[2]->type = REDIS_REPLY_STRING; + sai_port_oper_status_notification_t port_oper_status; + port_oper_status.port_state = oper_status; + port_oper_status.port_id = port.m_port_id; + std::string data = sai_serialize_port_oper_status_ntf(1, &port_oper_status); + std::vector notifyValues; + FieldValueTuple opdata("port_state_change", data); + notifyValues.push_back(opdata); + std::string msg = swss::JSon::buildJson(notifyValues); + mockReply->element[2]->str = (char*)calloc(1, msg.length() + 1); + memcpy(mockReply->element[2]->str, msg.c_str(), msg.length()); + + // trigger the notification + consumer->readData(); + gPortsOrch->doTask(*consumer); + mockReply = nullptr; + + gPortsOrch->getPort("Ethernet0", port); + ASSERT_TRUE(port.m_oper_status == oper_status); + ASSERT_TRUE(port.m_flap_count == count+1); + } + + cleanupPorts(gPortsOrch); + } TEST_F(PortsOrchTest, PortBulkCreateRemove) { @@ -1956,6 +2023,7 @@ namespace portsorch_test gPortsOrch->getPort("Ethernet0", port); ASSERT_TRUE(port.m_oper_status == SAI_PORT_OPER_STATUS_UP); + ASSERT_TRUE(port.m_flap_count == 1); std::vector values; portTable.get("Ethernet0", values); diff --git a/tests/test_warm_reboot.py b/tests/test_warm_reboot.py index 718aac9bb5..c0e4117f4b 100644 --- a/tests/test_warm_reboot.py +++ b/tests/test_warm_reboot.py @@ -1090,7 +1090,7 @@ def test_swss_port_state_syncup(self, dvs, testlog): orchStateCount += 1; # Only WARM_RESTART_TABLE|orchagent state=reconciled operation may exist after port oper status change. - assert orchStateCount == 1 + assert orchStateCount == 2 #clean up arp dvs.runcmd("arp -d 10.0.0.1")