From 0fd3c1f2a5da333eaffd0978dee5f078fe9438b9 Mon Sep 17 00:00:00 2001 From: junchao Date: Thu, 26 Aug 2021 11:27:45 +0800 Subject: [PATCH] [orchagent] Add flow counter support --- orchagent/Makefile.am | 5 +- orchagent/copporch.cpp | 210 +++++++++++-- orchagent/copporch.h | 30 ++ .../flex_counter/flex_counter_manager.cpp | 1 + orchagent/flex_counter/flex_counter_manager.h | 1 + orchagent/flexcounterorch.cpp | 20 +- orchagent/flexcounterorch.h | 2 + .../flow_counter/flow_counter_handler.cpp | 49 +++ orchagent/flow_counter/flow_counter_handler.h | 16 + orchagent/orchdaemon.cpp | 5 +- orchagent/saihelper.cpp | 3 + orchagent/trap_rates.lua | 67 ++++ tests/mock_tests/Makefile.am | 4 +- tests/test_flex_counters.py | 290 +++++++++++++++--- 14 files changed, 620 insertions(+), 83 deletions(-) create mode 100644 orchagent/flow_counter/flow_counter_handler.cpp create mode 100644 orchagent/flow_counter/flow_counter_handler.h create mode 100644 orchagent/trap_rates.lua diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index d4f3627203..fdd6ed6838 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -3,6 +3,7 @@ INCLUDES = -I $(top_srcdir)/lib \ -I $(top_srcdir)/warmrestart \ -I flex_counter \ -I debug_counter \ + -I flow_counter \ -I pbh CFLAGS_SAI = -I /usr/include/sai @@ -21,7 +22,8 @@ dist_swss_DATA = \ watermark_queue.lua \ watermark_pg.lua \ watermark_bufferpool.lua \ - lagids.lua + lagids.lua \ + trap_rates.lua bin_PROGRAMS = orchagent routeresync orchagent_restart_check @@ -81,6 +83,7 @@ orchagent_SOURCES = \ orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp orchagent_SOURCES += debug_counter/debug_counter.cpp debug_counter/drop_counter.cpp +orchagent_SOURCES += flow_counter/flow_counter_handler.cpp orchagent_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) orchagent_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) diff --git a/orchagent/copporch.cpp b/orchagent/copporch.cpp index 34d83dd274..4d735f3d3d 100644 --- a/orchagent/copporch.cpp +++ b/orchagent/copporch.cpp @@ -1,8 +1,13 @@ #include "sai.h" #include "copporch.h" #include "portsorch.h" +#include "flexcounterorch.h" #include "tokenize.h" #include "logger.h" +#include "sai_serialize.h" +#include "schema.h" +#include "directory.h" +#include "flow_counter_handler.h" #include #include @@ -18,6 +23,7 @@ extern sai_switch_api_t* sai_switch_api; extern sai_object_id_t gSwitchId; extern PortsOrch* gPortsOrch; +extern Directory gDirectory; extern bool gIsNatSupported; static map policer_meter_map = { @@ -78,6 +84,21 @@ static map trap_id_map = { {"dest_nat_miss", SAI_HOSTIF_TRAP_TYPE_DNAT_MISS} }; + +std::string get_trap_name_by_type(sai_hostif_trap_type_t trap_type) +{ + static map trap_name_to_id_map; + if (trap_name_to_id_map.empty()) + { + for (const auto &kv : trap_id_map) + { + trap_name_to_id_map.emplace(kv.second, kv.first); + } + } + + return trap_name_to_id_map.at(trap_type); +} + static map packet_action_map = { {"drop", SAI_PACKET_ACTION_DROP}, {"forward", SAI_PACKET_ACTION_FORWARD}, @@ -93,12 +114,17 @@ const string default_trap_group = "default"; const vector default_trap_ids = { SAI_HOSTIF_TRAP_TYPE_TTL_ERROR }; +const uint HOSTIF_TRAP_COUNTER_POLLING_INTERVAL_MS = 1000; CoppOrch::CoppOrch(DBConnector* db, string tableName) : - Orch(db, tableName) + Orch(db, tableName), + m_counter_db(std::shared_ptr(new DBConnector("COUNTERS_DB", 0))), + m_flex_db(std::shared_ptr(new DBConnector("FLEX_COUNTER_DB", 0))), + m_counter_table(std::unique_ptr(new Table(m_counter_db.get(), COUNTERS_TRAP_NAME_MAP))), + m_flex_counter_group_table(std::unique_ptr(new ProducerTable(m_flex_db.get(), FLEX_COUNTER_GROUP_TABLE))), + m_trap_counter_manager(HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, HOSTIF_TRAP_COUNTER_POLLING_INTERVAL_MS, false) { SWSS_LOG_ENTER(); - initDefaultHostIntfTable(); initDefaultTrapGroup(); initDefaultTrapIds(); @@ -317,6 +343,8 @@ bool CoppOrch::applyAttributesToTrapIds(sai_object_id_t trap_group_id, } m_syncdTrapIds[trap_id].trap_group_obj = trap_group_id; m_syncdTrapIds[trap_id].trap_obj = hostif_trap_id; + m_syncdTrapIds[trap_id].trap_type = trap_id; + bindTrapCounter(hostif_trap_id, trap_id); } return true; } @@ -773,17 +801,9 @@ bool CoppOrch::trapGroupProcessTrapIdChange (string trap_group_name, { if (m_syncdTrapIds.find(i)!= m_syncdTrapIds.end()) { - sai_status_t sai_status = sai_hostif_api->remove_hostif_trap( - m_syncdTrapIds[i].trap_obj); - if (sai_status != SAI_STATUS_SUCCESS) + if (!removeTrap(m_syncdTrapIds[i].trap_obj)) { - SWSS_LOG_ERROR("Failed to remove trap object %" PRId64 "", - m_syncdTrapIds[i].trap_obj); - task_process_status handle_status = handleSaiRemoveStatus(SAI_API_HOSTIF, sai_status); - if (handle_status != task_success) - { - return parseHandleSaiStatusFailure(handle_status); - } + return false; } } } @@ -826,17 +846,9 @@ bool CoppOrch::trapGroupProcessTrapIdChange (string trap_group_name, */ if (m_syncdTrapIds[i].trap_group_obj == m_trap_group_map[trap_group_name]) { - sai_status_t sai_status = sai_hostif_api->remove_hostif_trap( - m_syncdTrapIds[i].trap_obj); - if (sai_status != SAI_STATUS_SUCCESS) + if (!removeTrap(m_syncdTrapIds[i].trap_obj)) { - SWSS_LOG_ERROR("Failed to remove trap object %" PRId64 "", - m_syncdTrapIds[i].trap_obj); - task_process_status handle_status = handleSaiRemoveStatus(SAI_API_HOSTIF, sai_status); - if (handle_status != task_success) - { - return parseHandleSaiStatusFailure(handle_status); - } + return false; } m_syncdTrapIds.erase(i); } @@ -878,15 +890,9 @@ bool CoppOrch::processTrapGroupDel (string trap_group_name) if (it.second.trap_group_obj == m_trap_group_map[trap_group_name]) { trap_ids_to_reset.push_back(it.first); - sai_status_t sai_status = sai_hostif_api->remove_hostif_trap(it.second.trap_obj); - if (sai_status != SAI_STATUS_SUCCESS) + if (!removeTrap(it.second.trap_obj)) { - SWSS_LOG_ERROR("Failed to remove trap object %" PRId64 "", it.second.trap_obj); - task_process_status handle_status = handleSaiRemoveStatus(SAI_API_HOSTIF, sai_status); - if (handle_status != task_success) - { - return parseHandleSaiStatusFailure(handle_status); - } + return false; } } } @@ -1093,3 +1099,147 @@ bool CoppOrch::trapGroupUpdatePolicer (string trap_group_name, return true; } +void CoppOrch::initTrapRatePlugin() +{ + if (m_trap_rate_plugin_loaded) + { + return; + } + + std::string trapRatePluginName = "trap_rates.lua"; + try + { + std::string trapLuaScript = swss::loadLuaScript(trapRatePluginName); + std::string trapSha = swss::loadRedisScript(m_counter_db.get(), trapLuaScript); + + vector fieldValues; + fieldValues.emplace_back(FLOW_COUNTER_PLUGIN_FIELD, trapSha); + fieldValues.emplace_back(STATS_MODE_FIELD, STATS_MODE_READ); + m_flex_counter_group_table->set(HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP, fieldValues); + } + catch (const runtime_error &e) + { + SWSS_LOG_ERROR("Trap flex counter groups were not set successfully: %s", e.what()); + } + m_trap_rate_plugin_loaded = true; +} + +bool CoppOrch::removeTrap(sai_object_id_t hostif_trap_id) +{ + unbindTrapCounter(hostif_trap_id); + + sai_status_t sai_status = sai_hostif_api->remove_hostif_trap(hostif_trap_id); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove trap object %" PRId64 "", + hostif_trap_id); + task_process_status handle_status = handleSaiRemoveStatus(SAI_API_HOSTIF, sai_status); + if (handle_status != task_success) + { + return parseHandleSaiStatusFailure(handle_status); + } + } + + return true; +} + +bool CoppOrch::bindTrapCounter(sai_object_id_t hostif_trap_id, sai_hostif_trap_type_t trap_type) +{ + auto flex_counters_orch = gDirectory.get(); + + if (!flex_counters_orch || !flex_counters_orch->getHostIfTrapCounterState()) + { + return false; + } + + if (m_trap_id_name_map.count(hostif_trap_id) > 0) + { + return true; + } + + initTrapRatePlugin(); + + // Create generic counter + sai_object_id_t counter_id; + if (!FlowCounterHandler::createGenericCounter(counter_id)) + { + return false; + } + + // Bind generic counter to trap + sai_attribute_t trap_attr; + trap_attr.id = SAI_HOSTIF_TRAP_ATTR_COUNTER_ID; + trap_attr.value.oid = counter_id; + sai_status_t sai_status = sai_hostif_api->set_hostif_trap_attribute(hostif_trap_id, &trap_attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Failed to bind trap %" PRId64 " to counter %" PRId64 "", hostif_trap_id, counter_id); + return false; + } + + // Update COUNTERS_TRAP_NAME_MAP + auto trap_name = get_trap_name_by_type(trap_type); + FieldValueTuple tuple(trap_name, sai_serialize_object_id(counter_id)); + vector fields; + fields.push_back(tuple); + m_counter_table->set("", fields); + + // Update FLEX_COUNTER table + std::unordered_set counter_stats; + FlowCounterHandler::getGenericCounterIdList(counter_stats); + m_trap_counter_manager.setCounterIdList(counter_id, CounterType::HOSTIF_TRAP, counter_stats); + m_trap_id_name_map.emplace(hostif_trap_id, trap_name); + return true; +} + +void CoppOrch::unbindTrapCounter(sai_object_id_t hostif_trap_id) +{ + auto iter = m_trap_id_name_map.find(hostif_trap_id); + if (iter == m_trap_id_name_map.end()) + { + return; + } + + std::string counter_oid_str; + m_counter_table->hget("", iter->second, counter_oid_str); + + // Clear FLEX_COUNTER table + sai_object_id_t counter_id; + sai_deserialize_object_id(counter_oid_str, counter_id); + m_trap_counter_manager.clearCounterIdList(counter_id); + + // Remmove trap from COUNTERS_TRAP_NAME_MAP + m_counter_table->hdel("", iter->second); + + // Unbind generic counter to trap + sai_attribute_t trap_attr; + trap_attr.id = SAI_HOSTIF_TRAP_ATTR_COUNTER_ID; + trap_attr.value.oid = SAI_NULL_OBJECT_ID; + sai_status_t sai_status = sai_hostif_api->set_hostif_trap_attribute(hostif_trap_id, &trap_attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to unbind trap %" PRId64 " to counter %" PRId64 "", hostif_trap_id, counter_id); + } + + // Remove generic counter + FlowCounterHandler::removeGenericCounter(counter_id); + + m_trap_id_name_map.erase(iter); +} + +void CoppOrch::generateHostIfTrapCounterIdList() +{ + for (const auto &kv : m_syncdTrapIds) + { + bindTrapCounter(kv.second.trap_obj, kv.second.trap_type); + } +} + +void CoppOrch::clearHostIfTrapCounterIdList() +{ + for (const auto &kv : m_syncdTrapIds) + { + unbindTrapCounter(kv.second.trap_obj); + } +} + diff --git a/orchagent/copporch.h b/orchagent/copporch.h index 4794cfd2a6..e1a17b61d3 100644 --- a/orchagent/copporch.h +++ b/orchagent/copporch.h @@ -3,7 +3,16 @@ #include #include +#include +#include "dbconnector.h" #include "orch.h" +#include "flex_counter_manager.h" +#include "producertable.h" +#include "table.h" + +using namespace swss; + +#define HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP "HOSTIF_TRAP_FLOW_COUNTER" // trap fields const std::string copp_trap_id_list = "trap_ids"; @@ -33,6 +42,7 @@ struct copp_trap_objects { sai_object_id_t trap_obj; sai_object_id_t trap_group_obj; + sai_hostif_trap_type_t trap_type; }; /* TrapGroupPolicerTable: trap group ID, policer ID */ @@ -45,11 +55,15 @@ typedef std::map TrapGroupHostIfMap; typedef std::map TrapIdHostIfTableMap; /* Trap group to trap ID attributes */ typedef std::map TrapGroupTrapIdAttribs; +/* Trap OID to trap name*/ +typedef std::map TrapIdNameMap; class CoppOrch : public Orch { public: CoppOrch(swss::DBConnector* db, std::string tableName); + void generateHostIfTrapCounterIdList(); + void clearHostIfTrapCounterIdList(); protected: object_map m_trap_group_map; @@ -59,10 +73,21 @@ class CoppOrch : public Orch TrapGroupHostIfMap m_trap_group_hostif_map; TrapIdHostIfTableMap m_trapid_hostif_table_map; TrapGroupTrapIdAttribs m_trap_group_trap_id_attrs; + TrapIdNameMap m_trap_id_name_map; + + std::shared_ptr m_counter_db; + std::shared_ptr m_flex_db; + std::unique_ptr
m_counter_table; + std::unique_ptr m_flex_counter_group_table; + + FlexCounterManager m_trap_counter_manager; + + bool m_trap_rate_plugin_loaded = false; void initDefaultHostIntfTable(); void initDefaultTrapGroup(); void initDefaultTrapIds(); + void initTrapRatePlugin(); task_process_status processCoppRule(Consumer& consumer); bool isValidList(std::vector &trap_id_list, std::vector &all_items) const; @@ -99,6 +124,11 @@ class CoppOrch : public Orch bool trapGroupUpdatePolicer (std::string trap_group_name, std::vector &policer_attribs); + bool removeTrap(sai_object_id_t hostif_trap_id); + + bool bindTrapCounter(sai_object_id_t hostif_trap_id, sai_hostif_trap_type_t trap_type); + void unbindTrapCounter(sai_object_id_t hostif_trap_id); + virtual void doTask(Consumer& consumer); }; #endif /* SWSS_COPPORCH_H */ diff --git a/orchagent/flex_counter/flex_counter_manager.cpp b/orchagent/flex_counter/flex_counter_manager.cpp index 299e238d37..7f195bebb1 100644 --- a/orchagent/flex_counter/flex_counter_manager.cpp +++ b/orchagent/flex_counter/flex_counter_manager.cpp @@ -39,6 +39,7 @@ const unordered_map FlexCounterManager::counter_id_field_lo { CounterType::PORT, PORT_COUNTER_ID_LIST }, { CounterType::QUEUE, QUEUE_COUNTER_ID_LIST }, { CounterType::MACSEC_SA_ATTR, MACSEC_SA_ATTR_ID_LIST }, + { CounterType::HOSTIF_TRAP, FLOW_COUNTER_ID_LIST }, }; FlexCounterManager::FlexCounterManager( diff --git a/orchagent/flex_counter/flex_counter_manager.h b/orchagent/flex_counter/flex_counter_manager.h index 4df99c90bd..57a398113b 100644 --- a/orchagent/flex_counter/flex_counter_manager.h +++ b/orchagent/flex_counter/flex_counter_manager.h @@ -24,6 +24,7 @@ enum class CounterType PORT_DEBUG, SWITCH_DEBUG, MACSEC_SA_ATTR, + HOSTIF_TRAP, }; // FlexCounterManager allows users to manage a group of flex counters. diff --git a/orchagent/flexcounterorch.cpp b/orchagent/flexcounterorch.cpp index 78368508a5..1f51af9309 100644 --- a/orchagent/flexcounterorch.cpp +++ b/orchagent/flexcounterorch.cpp @@ -9,6 +9,7 @@ #include "bufferorch.h" #include "flexcounterorch.h" #include "debugcounterorch.h" +#include "copporch.h" extern sai_port_api_t *sai_port_api; @@ -16,6 +17,7 @@ extern PortsOrch *gPortsOrch; extern FabricPortsOrch *gFabricPortsOrch; extern IntfsOrch *gIntfsOrch; extern BufferOrch *gBufferOrch; +extern CoppOrch *gCoppOrch; #define BUFFER_POOL_WATERMARK_KEY "BUFFER_POOL_WATERMARK" #define PORT_KEY "PORT" @@ -23,12 +25,13 @@ extern BufferOrch *gBufferOrch; #define QUEUE_KEY "QUEUE" #define PG_WATERMARK_KEY "PG_WATERMARK" #define RIF_KEY "RIF" +#define FLOW_CNT_TRAP_KEY "FLOW_CNT_TRAP" unordered_map flexCounterGroupMap = { {"PORT", PORT_STAT_COUNTER_FLEX_COUNTER_GROUP}, {"PORT_RATES", PORT_RATE_COUNTER_FLEX_COUNTER_GROUP}, - {"PORT_BUFFER_DROP", PORT_STAT_COUNTER_FLEX_COUNTER_GROUP}, + {"PORT_BUFFER_DROP", PORT_BUFFER_DROP_STAT_FLEX_COUNTER_GROUP}, {"QUEUE", QUEUE_STAT_COUNTER_FLEX_COUNTER_GROUP}, {"PFCWD", PFC_WD_FLEX_COUNTER_GROUP}, {"QUEUE_WATERMARK", QUEUE_WATERMARK_STAT_COUNTER_FLEX_COUNTER_GROUP}, @@ -38,6 +41,7 @@ unordered_map flexCounterGroupMap = {"RIF", RIF_STAT_COUNTER_FLEX_COUNTER_GROUP}, {"RIF_RATES", RIF_RATE_COUNTER_FLEX_COUNTER_GROUP}, {"DEBUG_COUNTER", DEBUG_COUNTER_FLEX_COUNTER_GROUP}, + {FLOW_CNT_TRAP_KEY, HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP}, }; @@ -147,6 +151,20 @@ void FlexCounterOrch::doTask(Consumer &consumer) { gFabricPortsOrch->generateQueueStats(); } + if (gCoppOrch && (key == FLOW_CNT_TRAP_KEY)) + { + if (value == "enable") + { + m_hostif_trap_counter_enabled = true; + gCoppOrch->generateHostIfTrapCounterIdList(); + } + else if (value == "disable") + { + gCoppOrch->clearHostIfTrapCounterIdList(); + m_hostif_trap_counter_enabled = false; + } + + } vector fieldValues; fieldValues.emplace_back(FLEX_COUNTER_STATUS_FIELD, value); m_flexCounterGroupTable->set(flexCounterGroupMap[key], fieldValues); diff --git a/orchagent/flexcounterorch.h b/orchagent/flexcounterorch.h index 0fb9f70e4b..765c272f2b 100644 --- a/orchagent/flexcounterorch.h +++ b/orchagent/flexcounterorch.h @@ -17,12 +17,14 @@ class FlexCounterOrch: public Orch virtual ~FlexCounterOrch(void); bool getPortCountersState() const; bool getPortBufferDropCountersState() const; + bool getHostIfTrapCounterState() const {return m_hostif_trap_counter_enabled;} private: std::shared_ptr m_flexCounterDb = nullptr; std::shared_ptr m_flexCounterGroupTable = nullptr; bool m_port_counter_enabled = false; bool m_port_buffer_drop_counter_enabled = false; + bool m_hostif_trap_counter_enabled = false; }; #endif diff --git a/orchagent/flow_counter/flow_counter_handler.cpp b/orchagent/flow_counter/flow_counter_handler.cpp new file mode 100644 index 0000000000..01f08dfce7 --- /dev/null +++ b/orchagent/flow_counter/flow_counter_handler.cpp @@ -0,0 +1,49 @@ +#include +#include +#include "flow_counter_handler.h" +#include "logger.h" +#include "sai_serialize.h" + +extern sai_object_id_t gSwitchId; +extern sai_counter_api_t* sai_counter_api; + +const std::vector generic_counter_stat_ids = +{ + SAI_COUNTER_STAT_PACKETS, + SAI_COUNTER_STAT_BYTES, +}; + +bool FlowCounterHandler::createGenericCounter(sai_object_id_t &counter_id) +{ + sai_attribute_t counter_attr; + counter_attr.id = SAI_COUNTER_ATTR_TYPE; + counter_attr.value.s32 = SAI_COUNTER_TYPE_REGULAR; + sai_status_t sai_status = sai_counter_api->create_counter(&counter_id, gSwitchId, 1, &counter_attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Failed to create generic counter"); + return false; + } + + return true; +} + +bool FlowCounterHandler::removeGenericCounter(sai_object_id_t counter_id) +{ + sai_status_t sai_status = sai_counter_api->remove_counter(counter_id); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove generic counter: %" PRId64 "", counter_id); + return false; + } + + return true; +} + +void FlowCounterHandler::getGenericCounterIdList(std::unordered_set& counter_stats) +{ + for (const auto& it: generic_counter_stat_ids) + { + counter_stats.emplace(sai_serialize_counter_stat(it)); + } +} diff --git a/orchagent/flow_counter/flow_counter_handler.h b/orchagent/flow_counter/flow_counter_handler.h new file mode 100644 index 0000000000..58b3eb8a30 --- /dev/null +++ b/orchagent/flow_counter/flow_counter_handler.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +extern "C" { +#include "sai.h" +} + +class FlowCounterHandler +{ +public: + static bool createGenericCounter(sai_object_id_t &counter_id); + static bool removeGenericCounter(sai_object_id_t counter_id); + static void getGenericCounterIdList(std::unordered_set& counter_stats); +}; diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 814729ee0c..ebe8c41735 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -43,6 +43,7 @@ NatOrch *gNatOrch; MlagOrch *gMlagOrch; IsoGrpOrch *gIsoGrpOrch; MACsecOrch *gMacsecOrch; +CoppOrch *gCoppOrch; bool gIsNatSupported = false; @@ -165,7 +166,7 @@ bool OrchDaemon::init() }; gRouteOrch = new RouteOrch(m_applDb, route_tables, gSwitchOrch, gNeighOrch, gIntfsOrch, vrf_orch, gFgNhgOrch); - CoppOrch *copp_orch = new CoppOrch(m_applDb, APP_COPP_TABLE_NAME); + gCoppOrch = new CoppOrch(m_applDb, APP_COPP_TABLE_NAME); TunnelDecapOrch *tunnel_decap_orch = new TunnelDecapOrch(m_applDb, APP_TUNNEL_DECAP_TABLE_NAME); VxlanTunnelOrch *vxlan_tunnel_orch = new VxlanTunnelOrch(m_stateDb, m_applDb, APP_VXLAN_TUNNEL_TABLE_NAME); @@ -297,7 +298,7 @@ bool OrchDaemon::init() * when iterating ConsumerMap. This is ensured implicitly by the order of keys in ordered map. * For cases when Orch has to process tables in specific order, like PortsOrch during warm start, it has to override Orch::doTask() */ - m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, gIntfsOrch, gNeighOrch, gRouteOrch, copp_orch, tunnel_decap_orch, qos_orch, wm_orch, policer_orch, sflow_orch, debug_counter_orch, gMacsecOrch}; + m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, gIntfsOrch, gNeighOrch, gRouteOrch, gCoppOrch, tunnel_decap_orch, qos_orch, wm_orch, policer_orch, sflow_orch, debug_counter_orch, gMacsecOrch}; bool initialize_dtel = false; if (platform == BFN_PLATFORM_SUBSTRING || platform == VS_PLATFORM_SUBSTRING) diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 490efb90b3..5bab706e29 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -66,6 +66,7 @@ sai_nat_api_t* sai_nat_api; sai_isolation_group_api_t* sai_isolation_group_api; sai_system_port_api_t* sai_system_port_api; sai_macsec_api_t* sai_macsec_api; +sai_counter_api_t* sai_counter_api; extern sai_object_id_t gSwitchId; extern bool gSairedisRecord; @@ -189,6 +190,7 @@ void initSaiApi() sai_api_query(SAI_API_ISOLATION_GROUP, (void **)&sai_isolation_group_api); sai_api_query(SAI_API_SYSTEM_PORT, (void **)&sai_system_port_api); sai_api_query(SAI_API_MACSEC, (void **)&sai_macsec_api); + sai_api_query(SAI_API_COUNTER, (void **)&sai_counter_api); sai_log_set(SAI_API_SWITCH, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BRIDGE, SAI_LOG_LEVEL_NOTICE); @@ -221,6 +223,7 @@ void initSaiApi() sai_log_set((sai_api_t)SAI_API_NAT, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_SYSTEM_PORT, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_MACSEC, SAI_LOG_LEVEL_NOTICE); + sai_log_set(SAI_API_COUNTER, SAI_LOG_LEVEL_NOTICE); } void initSaiRedis(const string &record_location, const std::string &record_filename) diff --git a/orchagent/trap_rates.lua b/orchagent/trap_rates.lua new file mode 100644 index 0000000000..69b9c5cd3f --- /dev/null +++ b/orchagent/trap_rates.lua @@ -0,0 +1,67 @@ +-- KEYS - generic counter IDs +-- ARGV[1] - counters db index +-- ARGV[2] - counters table name +-- ARGV[3] - poll time interval +-- return log + +local logtable = {} + +local function logit(msg) + logtable[#logtable+1] = tostring(msg) +end + +local counters_db = ARGV[1] +local counters_table_name = ARGV[2] +local rates_table_name = "RATES" + +-- Get configuration +redis.call('SELECT', counters_db) +local smooth_interval = redis.call('HGET', rates_table_name .. ':' .. 'TRAP', 'TRAP_SMOOTH_INTERVAL') +local alpha = redis.call('HGET', rates_table_name .. ':' .. 'TRAP', 'TRAP_ALPHA') +if not alpha then + logit("Alpha is not defined") + return logtable +end +local one_minus_alpha = 1.0 - alpha +local delta = tonumber(ARGV[3]) + +logit(alpha) +logit(one_minus_alpha) +logit(delta) + +local n = table.getn(KEYS) +for i = 1, n do + local state_table = rates_table_name .. ':' .. KEYS[i] .. ':' .. 'TRAP' + local initialized = redis.call('HGET', state_table, 'INIT_DONE') + logit(initialized) + + -- Get new COUNTERS values + local in_pkts = redis.call('HGET', counters_table_name .. ':' .. KEYS[i], 'SAI_COUNTER_STAT_PACKETS') + + if initialized == 'DONE' or initialized == 'COUNTERS_LAST' then + -- Get old COUNTERS values + local in_pkts_last = redis.call('HGET', rates_table_name .. ':' .. KEYS[i], 'SAI_COUNTER_STAT_PACKETS_last') + + -- Calculate new rates values + local rx_pps_new = (in_pkts - in_pkts_last) / delta * 1000 + + if initialized == "DONE" then + -- Get old rates values + local rx_pps_old = redis.call('HGET', rates_table_name .. ':' .. KEYS[i], 'RX_PPS') + + -- Smooth the rates values and store them in DB + redis.call('HSET', rates_table_name .. ':' .. KEYS[i], 'RX_PPS', alpha*rx_pps_new + one_minus_alpha*rx_pps_old) + else + -- Store unsmoothed initial rates values in DB + redis.call('HSET', rates_table_name .. ':' .. KEYS[i], 'RX_PPS', rx_pps_new) + redis.call('HSET', state_table, 'INIT_DONE', 'DONE') + end + else + redis.call('HSET', state_table, 'INIT_DONE', 'COUNTERS_LAST') + end + + -- Set old COUNTERS values + redis.call('HSET', rates_table_name .. ':' .. KEYS[i], 'SAI_COUNTER_STAT_PACKETS_last', in_pkts) +end + +return logtable diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 5fed8001a4..86f306b02a 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -1,7 +1,8 @@ FLEX_CTR_DIR = $(top_srcdir)/orchagent/flex_counter DEBUG_CTR_DIR = $(top_srcdir)/orchagent/debug_counter +FLOW_CTR_DIR = $(top_srcdir)/orchagent/flow_counter -INCLUDES = -I $(FLEX_CTR_DIR) -I $(DEBUG_CTR_DIR) -I $(top_srcdir)/lib +INCLUDES = -I $(FLEX_CTR_DIR) -I $(DEBUG_CTR_DIR) -I $(FLOW_CTR_DIR) -I $(top_srcdir)/lib CFLAGS_SAI = -I /usr/include/sai @@ -80,6 +81,7 @@ tests_SOURCES = aclorch_ut.cpp \ tests_SOURCES += $(FLEX_CTR_DIR)/flex_counter_manager.cpp $(FLEX_CTR_DIR)/flex_counter_stat_manager.cpp tests_SOURCES += $(DEBUG_CTR_DIR)/debug_counter.cpp $(DEBUG_CTR_DIR)/drop_counter.cpp +tests_SOURCES += $(FLOW_CTR_DIR)/flow_counter_handler.cpp tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) tests_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) -I$(top_srcdir)/orchagent diff --git a/tests/test_flex_counters.py b/tests/test_flex_counters.py index ecdc844572..9880627900 100644 --- a/tests/test_flex_counters.py +++ b/tests/test_flex_counters.py @@ -1,39 +1,53 @@ import time import pytest -# Counter keys on ConfigDB -PORT_KEY = "PORT" -QUEUE_KEY = "QUEUE" -RIF_KEY = "RIF" -BUFFER_POOL_WATERMARK_KEY = "BUFFER_POOL_WATERMARK" -PORT_BUFFER_DROP_KEY = "PORT_BUFFER_DROP" -PG_WATERMARK_KEY = "PG_WATERMARK" - -# Counter stats on FlexCountersDB -PORT_STAT = "PORT_STAT_COUNTER" -QUEUE_STAT = "QUEUE_STAT_COUNTER" -RIF_STAT = "RIF_STAT_COUNTER" -BUFFER_POOL_WATERMARK_STAT = "BUFFER_POOL_WATERMARK_STAT_COUNTER" -PORT_BUFFER_DROP_STAT = "PORT_BUFFER_DROP_STAT" -PG_WATERMARK_STAT = "PG_WATERMARK_STAT_COUNTER" - -# Counter maps on CountersDB -PORT_MAP = "COUNTERS_PORT_NAME_MAP" -QUEUE_MAP = "COUNTERS_QUEUE_NAME_MAP" -RIF_MAP = "COUNTERS_RIF_NAME_MAP" -BUFFER_POOL_WATERMARK_MAP = "COUNTERS_BUFFER_POOL_NAME_MAP" -PORT_BUFFER_DROP_MAP = "COUNTERS_PORT_NAME_MAP" -PG_WATERMARK_MAP = "COUNTERS_PG_NAME_MAP" +from swsscommon import swsscommon NUMBER_OF_RETRIES = 10 CPU_PORT_OID = "0x0" -counter_type_dict = {"port_counter":[PORT_KEY, PORT_STAT, PORT_MAP], - "queue_counter":[QUEUE_KEY, QUEUE_STAT, QUEUE_MAP], - "rif_counter":[RIF_KEY, RIF_STAT, RIF_MAP], - "buffer_pool_watermark_counter":[BUFFER_POOL_WATERMARK_KEY, BUFFER_POOL_WATERMARK_STAT, BUFFER_POOL_WATERMARK_MAP], - "port_buffer_drop_counter":[PORT_BUFFER_DROP_KEY, PORT_BUFFER_DROP_STAT, PORT_BUFFER_DROP_MAP], - "pg_watermark_counter":[PG_WATERMARK_KEY, PG_WATERMARK_STAT, PG_WATERMARK_MAP]} +counter_group_meta = { + 'port_counter': { + 'key': 'PORT', + 'group_name': 'PORT_STAT_COUNTER', + 'name_map': 'COUNTERS_PORT_NAME_MAP', + 'post_test': 'post_port_counter_test', + }, + 'queue_counter': { + 'key': 'QUEUE', + 'group_name': 'QUEUE_STAT_COUNTER', + 'name_map': 'COUNTERS_QUEUE_NAME_MAP', + }, + 'rif_counter': { + 'key': 'RIF', + 'group_name': 'RIF_STAT_COUNTER', + 'name_map': 'COUNTERS_RIF_NAME_MAP', + 'pre_test': 'pre_rif_counter_test', + 'post_test': 'post_rif_counter_test', + }, + 'buffer_pool_watermark_counter': { + 'key': 'BUFFER_POOL_WATERMARK', + 'group_name': 'BUFFER_POOL_WATERMARK_STAT_COUNTER', + 'name_map': 'COUNTERS_BUFFER_POOL_NAME_MAP', + }, + 'port_buffer_drop_counter': { + 'key': 'PORT_BUFFER_DROP', + 'group_name': 'PORT_BUFFER_DROP_STAT', + 'name_map': 'COUNTERS_PORT_NAME_MAP', + }, + 'pg_watermark_counter': { + 'key': 'PG_WATERMARK', + 'group_name': 'PG_WATERMARK_STAT_COUNTER', + 'name_map': 'COUNTERS_PG_NAME_MAP', + }, + 'trap_flow_counter': { + 'key': 'FLOW_CNT_TRAP', + 'group_name': 'HOSTIF_TRAP_FLOW_COUNTER', + 'name_map': 'COUNTERS_TRAP_NAME_MAP', + 'post_test': 'post_trap_flow_counter_test', + }, +} + class TestFlexCounters(object): @@ -41,6 +55,7 @@ def setup_dbs(self, dvs): self.config_db = dvs.get_config_db() self.flex_db = dvs.get_flex_db() self.counters_db = dvs.get_counters_db() + self.app_db = dvs.get_app_db() def wait_for_table(self, table): for retry in range(NUMBER_OF_RETRIES): @@ -52,6 +67,16 @@ def wait_for_table(self, table): assert False, str(table) + " not created in Counters DB" + def wait_for_table_empty(self, table): + for retry in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(table) + if len(counters_keys) == 0: + return + else: + time.sleep(1) + + assert False, str(table) + " is still in Counters DB" + def wait_for_id_list(self, stat, name, oid): for retry in range(NUMBER_OF_RETRIES): id_list = self.flex_db.db_connection.hgetall("FLEX_COUNTER_TABLE:" + stat + ":" + oid).items() @@ -62,6 +87,27 @@ def wait_for_id_list(self, stat, name, oid): assert False, "No ID list for counter " + str(name) + def wait_for_id_list_remove(self, stat, name, oid): + for retry in range(NUMBER_OF_RETRIES): + id_list = self.flex_db.db_connection.hgetall("FLEX_COUNTER_TABLE:" + stat + ":" + oid).items() + if len(id_list) == 0: + return + else: + time.sleep(1) + + assert False, "ID list for counter " + str(name) + " is still there" + + def wait_for_interval_set(self, group, interval): + interval_value = None + for retry in range(NUMBER_OF_RETRIES): + interval_value = self.flex_db.db_connection.hget("FLEX_COUNTER_GROUP_TABLE:" + group, 'POLL_INTERVAL') + if interval_value == interval: + return + else: + time.sleep(1) + + assert False, "Polling interval is not applied to FLEX_COUNTER_GROUP_TABLE for group {}, expect={}, actual={}".format(group, interval, interval_value) + def verify_no_flex_counters_tables(self, counter_stat): counters_stat_keys = self.flex_db.get_keys("FLEX_COUNTER_TABLE:" + counter_stat) assert len(counters_stat_keys) == 0, "FLEX_COUNTER_TABLE:" + str(counter_stat) + " tables exist before enabling the flex counter group" @@ -73,18 +119,26 @@ def verify_flex_counters_populated(self, map, stat): oid = counter_entry[1] self.wait_for_id_list(stat, name, oid) - def verify_only_phy_ports_created(self): - port_counters_keys = self.counters_db.db_connection.hgetall(PORT_MAP) - port_counters_stat_keys = self.flex_db.get_keys("FLEX_COUNTER_TABLE:" + PORT_STAT) + def verify_only_phy_ports_created(self, meta_data): + port_counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + port_counters_stat_keys = self.flex_db.get_keys("FLEX_COUNTER_TABLE:" + meta_data['group_name']) for port_stat in port_counters_stat_keys: assert port_stat in dict(port_counters_keys.items()).values(), "Non PHY port created on PORT_STAT_COUNTER group: {}".format(port_stat) - def enable_flex_counter_group(self, group, map): - group_stats_entry = {"FLEX_COUNTER_STATUS": "enable"} + def set_flex_counter_group_status(self, group, map, status='enable'): + group_stats_entry = {"FLEX_COUNTER_STATUS": status} self.config_db.create_entry("FLEX_COUNTER_TABLE", group, group_stats_entry) - self.wait_for_table(map) + if status == 'enable': + self.wait_for_table(map) + else: + self.wait_for_table_empty(map) + + def set_flex_counter_group_interval(self, key, group, interval): + group_stats_entry = {"POLL_INTERVAL": interval} + self.config_db.create_entry("FLEX_COUNTER_TABLE", key, group_stats_entry) + self.wait_for_interval_set(group, interval) - @pytest.mark.parametrize("counter_type", counter_type_dict.keys()) + @pytest.mark.parametrize("counter_type", counter_group_meta.keys()) def test_flex_counters(self, dvs, counter_type): """ The test will check there are no flex counters tables on FlexCounter DB when the counters are disabled. @@ -92,21 +146,161 @@ def test_flex_counters(self, dvs, counter_type): For some counter types the MAPS on COUNTERS DB will be created as well after enabling the counter group, this will be also verified on this test. """ self.setup_dbs(dvs) - counter_key = counter_type_dict[counter_type][0] - counter_stat = counter_type_dict[counter_type][1] - counter_map = counter_type_dict[counter_type][2] + meta_data = counter_group_meta[counter_type] + counter_key = meta_data['key'] + counter_stat = meta_data['group_name'] + counter_map = meta_data['name_map'] + pre_test = meta_data.get('pre_test') + post_test = meta_data.get('post_test') self.verify_no_flex_counters_tables(counter_stat) - if counter_type == "rif_counter": - self.config_db.db_connection.hset('INTERFACE|Ethernet0', "NULL", "NULL") - self.config_db.db_connection.hset('INTERFACE|Ethernet0|192.168.0.1/24', "NULL", "NULL") - - self.enable_flex_counter_group(counter_key, counter_map) + if pre_test: + cb = getattr(self, pre_test) + cb(meta_data) + + self.set_flex_counter_group_status(counter_key, counter_map) self.verify_flex_counters_populated(counter_map, counter_stat) + self.set_flex_counter_group_interval(counter_key, counter_stat, '2500') + + if post_test: + cb = getattr(self, post_test) + cb(meta_data) + + def pre_rif_counter_test(self, meta_data): + self.config_db.db_connection.hset('INTERFACE|Ethernet0', "NULL", "NULL") + self.config_db.db_connection.hset('INTERFACE|Ethernet0|192.168.0.1/24', "NULL", "NULL") + + def post_rif_counter_test(self, meta_data): + self.config_db.db_connection.hdel('INTERFACE|Ethernet0|192.168.0.1/24', "NULL") + + def post_port_counter_test(self, meta_data): + self.verify_only_phy_ports_created(meta_data) + + def post_trap_flow_counter_test(self, meta_data): + """Post verification for test_flex_counters for trap_flow_counter. Steps: + 1. Disable test_flex_counters + 2. Verify name map and counter ID list are cleared + 3. Clear trap ids to avoid affecting further test cases + + Args: + meta_data (object): flex counter meta data + """ + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + self.set_flex_counter_group_status(meta_data['key'], meta_data['group_name'], 'disable') + + for counter_entry in counters_keys.items(): + self.wait_for_id_list_remove(meta_data['group_name'], counter_entry[0], counter_entry[1]) + self.wait_for_table_empty(meta_data['name_map']) + + def test_add_remove_trap(self, dvs): + """Test steps: + 1. Enable trap_flow_counter + 2. Remove a COPP trap + 3. Verify counter is automatically unbind + 4. Add the COPP trap back + 5. Verify counter is added back + + Args: + dvs (object): virtual switch object + """ + self.setup_dbs(dvs) + meta_data = counter_group_meta['trap_flow_counter'] + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map']) + + removed_trap = None + changed_group = None + trap_ids = None + copp_groups = self.app_db.db_connection.keys('COPP_TABLE:*') + for copp_group in copp_groups: + trap_ids = self.app_db.db_connection.hget(copp_group, 'trap_ids') + if trap_ids and ',' in trap_ids: + trap_ids = [x.strip() for x in trap_ids.split(',')] + removed_trap = trap_ids.pop() + changed_group = copp_group.split(':')[1] + break + + if not removed_trap: + pytest.skip('There is not copp group with more than 1 traps, skip rest of the test') + + oid = None + for _ in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + if removed_trap in counters_keys: + oid = counters_keys[removed_trap] + break + else: + time.sleep(1) + + assert oid, 'trap counter is not created for {}'.format(removed_trap) + + app_copp_table = swsscommon.ProducerStateTable(self.app_db.db_connection, 'COPP_TABLE') + app_copp_table.set(changed_group, [('trap_ids', ','.join(trap_ids))]) + self.wait_for_id_list_remove(meta_data['group_name'], removed_trap, oid) + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + assert removed_trap not in counters_keys + + trap_ids.append(removed_trap) + app_copp_table.set(changed_group, [('trap_ids', ','.join(trap_ids))]) + + oid = None + for _ in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + if removed_trap in counters_keys: + oid = counters_keys[removed_trap] + break + else: + time.sleep(1) + + assert oid, 'Add trap {}, but trap counter is not created'.format(removed_trap) + self.wait_for_id_list(meta_data['group_name'], removed_trap, oid) + self.set_flex_counter_group_status(meta_data['key'], meta_data['group_name'], 'disable') + + def test_remove_trap_group(self, dvs): + """Remove trap group and verify that all related trap counters are removed + + Args: + dvs (object): virtual switch object + """ + self.setup_dbs(dvs) + meta_data = counter_group_meta['trap_flow_counter'] + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map']) + + removed_group = None + trap_ids = None + copp_groups = self.app_db.db_connection.keys('COPP_TABLE:*') + for copp_group in copp_groups: + trap_ids = self.app_db.db_connection.hget(copp_group, 'trap_ids') + if trap_ids and trap_ids.strip(): + removed_group = copp_group.split(':')[1] + break + + if not removed_group: + pytest.skip('There is not copp group with at least 1 traps, skip rest of the test') + + trap_ids = [x.strip() for x in trap_ids.split(',')] + for _ in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + found = True + for trap_id in trap_ids: + if trap_id not in counters_keys: + found = False + break + if found: + break + else: + time.sleep(1) + + assert found, 'Not all trap id found in name map' + + app_copp_table = swsscommon.ProducerStateTable(self.app_db.db_connection, 'COPP_TABLE') + app_copp_table._del(removed_group) + + for trap_id in trap_ids: + self.wait_for_id_list_remove(meta_data['group_name'], trap_id, counters_keys[trap_id]) - if counter_type == "port_counter": - self.verify_only_phy_ports_created() + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + for trap_id in trap_ids: + assert trap_id not in counters_keys - if counter_type == "rif_counter": - self.config_db.db_connection.hdel('INTERFACE|Ethernet0|192.168.0.1/24', "NULL") + self.set_flex_counter_group_status(meta_data['key'], meta_data['group_name'], 'disable')