From 1fd1dbfe7eaaa52f1d7edbdd2bb88483f031fd5e Mon Sep 17 00:00:00 2001 From: Junchao-Mellanox <57339448+Junchao-Mellanox@users.noreply.github.com> Date: Wed, 20 Apr 2022 00:07:48 +0800 Subject: [PATCH] Add support for route flow counter (#2094) * Add support for route flow counter --- orchagent/Makefile.am | 2 +- .../flex_counter/flex_counter_manager.cpp | 1 + orchagent/flex_counter/flex_counter_manager.h | 1 + .../flex_counter/flow_counter_handler.cpp | 13 + orchagent/flex_counter/flow_counter_handler.h | 1 + .../flex_counter/flowcounterrouteorch.cpp | 991 ++++++++++++++++++ orchagent/flex_counter/flowcounterrouteorch.h | 178 ++++ orchagent/flexcounterorch.cpp | 18 + orchagent/flexcounterorch.h | 3 +- orchagent/intfsorch.cpp | 7 +- orchagent/orchdaemon.cpp | 9 +- orchagent/orchdaemon.h | 1 + orchagent/routeorch.cpp | 13 +- orchagent/routeorch.h | 3 +- orchagent/swssnet.h | 48 + orchagent/vnetorch.cpp | 64 +- orchagent/vnetorch.h | 6 + orchagent/vrforch.cpp | 10 +- tests/mock_tests/Makefile.am | 3 +- tests/mock_tests/aclorch_ut.cpp | 7 + tests/mock_tests/mock_orchagent_main.h | 2 + tests/mock_tests/routeorch_ut.cpp | 6 + tests/mock_tests/swssnet_ut.cpp | 39 + tests/test_flex_counters.py | 336 +++++- 24 files changed, 1733 insertions(+), 29 deletions(-) create mode 100644 orchagent/flex_counter/flowcounterrouteorch.cpp create mode 100644 orchagent/flex_counter/flowcounterrouteorch.h create mode 100644 tests/mock_tests/swssnet_ut.cpp diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index fedd7ef4aefc..6c7bbeb2520f 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -96,7 +96,7 @@ orchagent_SOURCES = \ response_publisher.cpp \ nvgreorch.cpp -orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp flex_counter/flow_counter_handler.cpp +orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp flex_counter/flow_counter_handler.cpp flex_counter/flowcounterrouteorch.cpp orchagent_SOURCES += debug_counter/debug_counter.cpp debug_counter/drop_counter.cpp orchagent_SOURCES += p4orch/p4orch.cpp \ p4orch/p4orch_util.cpp \ diff --git a/orchagent/flex_counter/flex_counter_manager.cpp b/orchagent/flex_counter/flex_counter_manager.cpp index 71731e84d3fb..3e61289acdda 100644 --- a/orchagent/flex_counter/flex_counter_manager.cpp +++ b/orchagent/flex_counter/flex_counter_manager.cpp @@ -44,6 +44,7 @@ const unordered_map FlexCounterManager::counter_id_field_lo { 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 }, }; FlexManagerDirectory g_FlexManagerDirectory; diff --git a/orchagent/flex_counter/flex_counter_manager.h b/orchagent/flex_counter/flex_counter_manager.h index 6e80feb8fba8..6a997f28f7df 100644 --- a/orchagent/flex_counter/flex_counter_manager.h +++ b/orchagent/flex_counter/flex_counter_manager.h @@ -31,6 +31,7 @@ enum class CounterType ACL_COUNTER, TUNNEL, HOSTIF_TRAP, + ROUTE, }; // FlexCounterManager allows users to manage a group of flex counters. diff --git a/orchagent/flex_counter/flow_counter_handler.cpp b/orchagent/flex_counter/flow_counter_handler.cpp index 89f621fe7ba7..27ba357a8ed0 100644 --- a/orchagent/flex_counter/flow_counter_handler.cpp +++ b/orchagent/flex_counter/flow_counter_handler.cpp @@ -47,3 +47,16 @@ void FlowCounterHandler::getGenericCounterStatIdList(std::unordered_set& counter_stats); + static bool queryRouteFlowCounterCapability(); }; diff --git a/orchagent/flex_counter/flowcounterrouteorch.cpp b/orchagent/flex_counter/flowcounterrouteorch.cpp new file mode 100644 index 000000000000..b82d66e27aa7 --- /dev/null +++ b/orchagent/flex_counter/flowcounterrouteorch.cpp @@ -0,0 +1,991 @@ +#include "dbconnector.h" +#include "directory.h" +#include "flow_counter_handler.h" +#include "logger.h" +#include "routeorch.h" +#include "flowcounterrouteorch.h" +#include "schema.h" +#include "swssnet.h" +#include "table.h" +#include "vnetorch.h" + +#include + +extern Directory gDirectory; +extern RouteOrch* gRouteOrch; +extern size_t gMaxBulkSize; +extern sai_route_api_t* sai_route_api; +extern sai_object_id_t gVirtualRouterId; +extern sai_object_id_t gSwitchId; + +#define FLEX_COUNTER_UPD_INTERVAL 1 +#define FLOW_COUNTER_ROUTE_KEY "route" +#define FLOW_COUNTER_SUPPORT_FIELD "support" +#define ROUTE_PATTERN_MAX_MATCH_COUNT_FIELD "max_match_count" +#define ROUTE_PATTERN_DEFAULT_MAX_MATCH_COUNT 30 +#define ROUTE_FLOW_COUNTER_POLLING_INTERVAL_MS 10000 + +FlowCounterRouteOrch::FlowCounterRouteOrch(swss::DBConnector *db, const std::vector &tableNames): +Orch(db, tableNames), +mAsicDb(std::shared_ptr(new DBConnector("ASIC_DB", 0))), +mCounterDb(std::shared_ptr(new DBConnector("COUNTERS_DB", 0))), +mVidToRidTable(std::unique_ptr(new Table(mAsicDb.get(), "VIDTORID"))), +mPrefixToCounterTable(std::unique_ptr
(new Table(mCounterDb.get(), COUNTERS_ROUTE_NAME_MAP))), +mPrefixToPatternTable(std::unique_ptr
(new Table(mCounterDb.get(), COUNTERS_ROUTE_TO_PATTERN_MAP))), +mRouteFlowCounterMgr(ROUTE_FLOW_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, ROUTE_FLOW_COUNTER_POLLING_INTERVAL_MS, false), +gRouteBulker(sai_route_api, gMaxBulkSize) +{ + SWSS_LOG_ENTER(); + initRouteFlowCounterCapability(); + + if (mRouteFlowCounterSupported) + { + auto intervT = timespec { .tv_sec = FLEX_COUNTER_UPD_INTERVAL , .tv_nsec = 0 }; + mFlexCounterUpdTimer = new SelectableTimer(intervT); + auto executorT = new ExecutableTimer(mFlexCounterUpdTimer, this, "FLEX_COUNTER_UPD_TIMER"); + Orch::addExecutor(executorT); + } +} + +FlowCounterRouteOrch::~FlowCounterRouteOrch() +{ + SWSS_LOG_ENTER(); +} + +void FlowCounterRouteOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + if (!gRouteOrch || !mRouteFlowCounterSupported) + { + return; + } + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + + const auto &key = kfvKey(t); + const auto &op = kfvOp(t); + const auto &data = kfvFieldsValues(t); + if (op == SET_COMMAND) + { + size_t maxMatchCount = ROUTE_PATTERN_DEFAULT_MAX_MATCH_COUNT; + for (auto valuePair : data) + { + const auto &field = fvField(valuePair); + const auto &value = fvValue(valuePair); + if (field == ROUTE_PATTERN_MAX_MATCH_COUNT_FIELD) + { + maxMatchCount = (size_t)std::stoul(value); + if (maxMatchCount == 0) + { + SWSS_LOG_WARN("Max match count for route pattern cannot be 0, set it to default value 30"); + maxMatchCount = ROUTE_PATTERN_DEFAULT_MAX_MATCH_COUNT; + } + } + } + + addRoutePattern(key, maxMatchCount); + } + else if (op == DEL_COMMAND) + { + removeRoutePattern(key); + } + consumer.m_toSync.erase(it++); + } +} + +void FlowCounterRouteOrch::doTask(SelectableTimer &timer) +{ + SWSS_LOG_ENTER(); + SWSS_LOG_NOTICE("Add flex counters, pending in queue: %zu", mPendingAddToFlexCntr.size()); + string value; + std::string nameMapKey; + std::string pattern; + vector prefixToCounterMap; + vector prefixToPatternMap; + for (auto it = mPendingAddToFlexCntr.begin(); it != mPendingAddToFlexCntr.end(); ) + { + const auto& route_pattern = it->first; + auto vrf_id = route_pattern.vrf_id; + + for(auto inner_iter = it->second.begin(); inner_iter != it->second.end(); ) + { + const auto id = sai_serialize_object_id(inner_iter->second); + if (mVidToRidTable->hget("", id, value)) + { + auto ip_prefix = inner_iter->first; + SWSS_LOG_INFO("Registering %s, id %s", ip_prefix.to_string().c_str(), id.c_str()); + + std::unordered_set counter_stats; + FlowCounterHandler::getGenericCounterStatIdList(counter_stats); + mRouteFlowCounterMgr.setCounterIdList(inner_iter->second, CounterType::ROUTE, counter_stats); + + getRouteFlowCounterNameMapKey(vrf_id, ip_prefix, nameMapKey); + prefixToCounterMap.emplace_back(nameMapKey, id); + + getRouteFlowCounterNameMapKey(vrf_id, route_pattern.ip_prefix, pattern); + prefixToPatternMap.emplace_back(nameMapKey, pattern); + + updateRouterFlowCounterCache(route_pattern, ip_prefix, inner_iter->second, mBoundRouteCounters); + inner_iter = it->second.erase(inner_iter); + } + else + { + ++inner_iter; + } + } + + if (it->second.empty()) + { + it = mPendingAddToFlexCntr.erase(it); + } + else + { + ++it; + } + } + + if (!prefixToCounterMap.empty()) + { + mPrefixToCounterTable->set("", prefixToCounterMap); + } + + if (!prefixToPatternMap.empty()) + { + mPrefixToPatternTable->set("", prefixToPatternMap); + } + + if (mPendingAddToFlexCntr.empty()) + { + mFlexCounterUpdTimer->stop(); + } +} + +void FlowCounterRouteOrch::initRouteFlowCounterCapability() +{ + SWSS_LOG_ENTER(); + mRouteFlowCounterSupported = FlowCounterHandler::queryRouteFlowCounterCapability(); + if (!mRouteFlowCounterSupported) + { + SWSS_LOG_NOTICE("Route flow counter is not supported on this platform"); + } + swss::DBConnector state_db("STATE_DB", 0); + swss::Table capability_table(&state_db, STATE_FLOW_COUNTER_CAPABILITY_TABLE_NAME); + std::vector fvs; + fvs.emplace_back(FLOW_COUNTER_SUPPORT_FIELD, mRouteFlowCounterSupported ? "true" : "false"); + capability_table.set(FLOW_COUNTER_ROUTE_KEY, fvs); +} + +void FlowCounterRouteOrch::generateRouteFlowStats() +{ + SWSS_LOG_ENTER(); + if (!mRouteFlowCounterSupported) + { + return; + } + + for (const auto &route_pattern : mRoutePatternSet) + { + createRouteFlowCounterByPattern(route_pattern, 0); + } +} + +void FlowCounterRouteOrch::clearRouteFlowStats() +{ + SWSS_LOG_ENTER(); + if (!mBoundRouteCounters.empty() || !mPendingAddToFlexCntr.empty()) + { + for (auto &entry : mBoundRouteCounters) + { + const auto& route_pattern = entry.first; + for (auto &inner_entry : entry.second) + { + removeRouteFlowCounterFromDB(route_pattern.vrf_id, inner_entry.first, inner_entry.second); + unbindFlowCounter(route_pattern, route_pattern.vrf_id, inner_entry.first, inner_entry.second); + } + } + + for (auto &entry : mPendingAddToFlexCntr) + { + const auto& route_pattern = entry.first; + for (auto &inner_entry : entry.second) + { + unbindFlowCounter(route_pattern, route_pattern.vrf_id, inner_entry.first, inner_entry.second); + } + } + + mBoundRouteCounters.clear(); + mPendingAddToFlexCntr.clear(); + } +} + +void FlowCounterRouteOrch::addRoutePattern(const std::string &pattern, size_t max_match_count) +{ + SWSS_LOG_ENTER(); + sai_object_id_t vrf_id; + IpPrefix ip_prefix; + std::string vrf_name; + if (!parseRouteKeyForRoutePattern(pattern, '|', vrf_id, ip_prefix, vrf_name)) + { + vrf_id = SAI_NULL_OBJECT_ID; + } + + auto insert_result = mRoutePatternSet.emplace(vrf_name, vrf_id, ip_prefix, max_match_count); + if (insert_result.second) + { + SWSS_LOG_NOTICE("Inserting route pattern %s, max match count is %zu", pattern.c_str(), max_match_count); + if (!validateRoutePattern(*insert_result.first)) + { + mRoutePatternSet.erase(insert_result.first); + return; + } + + createRouteFlowCounterByPattern(*insert_result.first, 0); + } + else + { + SWSS_LOG_NOTICE("Updating route pattern %s max match count to %zu", pattern.c_str(), max_match_count); + RoutePattern &existing = const_cast(*insert_result.first); + onRoutePatternMaxMatchCountChange(existing, max_match_count); + } +} + +void FlowCounterRouteOrch::removeRoutePattern(const std::string& pattern) +{ + SWSS_LOG_ENTER(); + sai_object_id_t vrf_id; + IpPrefix ip_prefix; + std::string vrf_name; + if (!parseRouteKeyForRoutePattern(pattern, '|', vrf_id, ip_prefix, vrf_name)) + { + vrf_id = SAI_NULL_OBJECT_ID; + } + + SWSS_LOG_NOTICE("Removing route pattern %s", pattern.c_str()); + RoutePattern route_pattern(vrf_name, vrf_id, ip_prefix, 0); + auto iter = mRoutePatternSet.find(route_pattern); + if (iter == mRoutePatternSet.end()) + { + // Should not go to this branch, just in case + SWSS_LOG_ERROR("Trying to remove route pattern %s, but it does not exist", pattern.c_str()); + return; + } + mRoutePatternSet.erase(iter); + + removeRoutePattern(route_pattern); +} + +void FlowCounterRouteOrch::removeRoutePattern(const RoutePattern &route_pattern) +{ + SWSS_LOG_ENTER(); + auto cache_iter = mBoundRouteCounters.find(route_pattern); + if (cache_iter != mBoundRouteCounters.end()) + { + for (auto &entry : cache_iter->second) + { + removeRouteFlowCounterFromDB(route_pattern.vrf_id, entry.first, entry.second); + unbindFlowCounter(route_pattern, route_pattern.vrf_id, entry.first, entry.second); + } + mBoundRouteCounters.erase(cache_iter); + } + + auto pending_iter = mPendingAddToFlexCntr.find(route_pattern); + if (pending_iter != mPendingAddToFlexCntr.end()) + { + for (auto &entry : pending_iter->second) + { + unbindFlowCounter(route_pattern, route_pattern.vrf_id, entry.first, entry.second); + } + mPendingAddToFlexCntr.erase(pending_iter); + } +} + +void FlowCounterRouteOrch::onAddMiscRouteEntry(sai_object_id_t vrf_id, const sai_ip_prefix_t& ip_pfx, bool add_to_cache) +{ + SWSS_LOG_ENTER(); + if (!mRouteFlowCounterSupported) + { + return; + } + + IpPrefix ip_prefix = getIpPrefixFromSaiPrefix(ip_pfx); + onAddMiscRouteEntry(vrf_id, ip_prefix, add_to_cache); +} + +void FlowCounterRouteOrch::onAddMiscRouteEntry(sai_object_id_t vrf_id, const IpPrefix& ip_prefix, bool add_to_cache) +{ + SWSS_LOG_ENTER(); + if (!mRouteFlowCounterSupported) + { + return; + } + + if (add_to_cache) + { + auto iter = mMiscRoutes.find(vrf_id); + if (iter == mMiscRoutes.end()) + { + mMiscRoutes.emplace(vrf_id, std::set({ip_prefix})); + } + else + { + iter->second.insert(ip_prefix); + } + } + + if (!isRouteFlowCounterEnabled()) + { + return; + } + + if (mRoutePatternSet.empty()) + { + return; + } + + handleRouteAdd(vrf_id, ip_prefix); +} + +void FlowCounterRouteOrch::onRemoveMiscRouteEntry(sai_object_id_t vrf_id, const sai_ip_prefix_t& ip_pfx, bool remove_from_cache) +{ + SWSS_LOG_ENTER(); + if (!mRouteFlowCounterSupported) + { + return; + } + + IpPrefix ip_prefix = getIpPrefixFromSaiPrefix(ip_pfx); + onRemoveMiscRouteEntry(vrf_id, ip_prefix, remove_from_cache); +} + +void FlowCounterRouteOrch::onRemoveMiscRouteEntry(sai_object_id_t vrf_id, const IpPrefix& ip_prefix, bool remove_from_cache) +{ + SWSS_LOG_ENTER(); + if (!mRouteFlowCounterSupported) + { + return; + } + + if (remove_from_cache) + { + auto iter = mMiscRoutes.find(vrf_id); + if (iter != mMiscRoutes.end()) + { + auto prefix_iter = iter->second.find(ip_prefix); + if (prefix_iter != iter->second.end()) + { + iter->second.erase(prefix_iter); + if (iter->second.empty()) + { + mMiscRoutes.erase(iter); + } + } + } + } + + if (!isRouteFlowCounterEnabled()) + { + return; + } + + if (mRoutePatternSet.empty()) + { + return; + } + + handleRouteRemove(vrf_id, ip_prefix); +} + +void FlowCounterRouteOrch::onAddVR(sai_object_id_t vrf_id) +{ + SWSS_LOG_ENTER(); + if (!mRouteFlowCounterSupported) + { + return; + } + + assert(vrf_id != gVirtualRouterId); + auto *vrf_orch = gDirectory.get(); + std::string vrf_name = vrf_orch->getVRFname(vrf_id); + if (vrf_name == "") + { + getVnetNameByVrfId(vrf_id, vrf_name); + } + + if (vrf_name == "") + { + SWSS_LOG_WARN("Failed to get VRF name for vrf id %s", sai_serialize_object_id(vrf_id).c_str()); + } + + for (auto &route_pattern : mRoutePatternSet) + { + if (route_pattern.vrf_name == vrf_name) + { + RoutePattern &existing = const_cast(route_pattern); + existing.vrf_id = vrf_id; + createRouteFlowCounterByPattern(existing, 0); + break; + } + } +} + +void FlowCounterRouteOrch::onRemoveVR(sai_object_id_t vrf_id) +{ + SWSS_LOG_ENTER(); + if (!mRouteFlowCounterSupported) + { + return; + } + + for (auto &route_pattern : mRoutePatternSet) + { + if (route_pattern.vrf_id == vrf_id) + { + SWSS_LOG_NOTICE("Removing route pattern %s and all related counters due to VRF %s has been removed", route_pattern.to_string().c_str(), route_pattern.vrf_name.c_str()); + removeRoutePattern(route_pattern); + RoutePattern &existing = const_cast(route_pattern); + existing.vrf_id = SAI_NULL_OBJECT_ID; + } + } +} + +bool FlowCounterRouteOrch::bindFlowCounter(const RoutePattern &route_pattern, sai_object_id_t vrf_id, const IpPrefix& ip_prefix) +{ + SWSS_LOG_ENTER(); + + SWSS_LOG_NOTICE("Binding route entry vrf=%s prefix=%s to flow counter", route_pattern.vrf_name.c_str(), ip_prefix.to_string().c_str()); + + sai_object_id_t counter_oid; + if (!FlowCounterHandler::createGenericCounter(counter_oid)) + { + SWSS_LOG_ERROR("Failed to create generic counter"); + return false; + } + + sai_route_entry_t route_entry; + route_entry.switch_id = gSwitchId; + route_entry.vr_id = route_pattern.vrf_id; + copy(route_entry.destination, ip_prefix); + sai_attribute_t attr; + attr.id = SAI_ROUTE_ENTRY_ATTR_COUNTER_ID; + attr.value.oid = counter_oid; + + auto status = sai_route_api->set_route_entry_attribute(&route_entry, &attr); + if (status != SAI_STATUS_SUCCESS) + { + FlowCounterHandler::removeGenericCounter(counter_oid); + SWSS_LOG_WARN("Failed to bind route entry vrf=%s prefix=%s to flow counter", route_pattern.vrf_name.c_str(), ip_prefix.to_string().c_str()); + return false; + } + + pendingUpdateFlexDb(route_pattern, ip_prefix, counter_oid); + return true; +} + +void FlowCounterRouteOrch::unbindFlowCounter(const RoutePattern &route_pattern, sai_object_id_t vrf_id, const IpPrefix& ip_prefix, sai_object_id_t counter_oid) +{ + SWSS_LOG_ENTER(); + + SWSS_LOG_NOTICE("Unbinding route entry vrf=%s prefix=%s to flow counter", route_pattern.vrf_name.c_str(), ip_prefix.to_string().c_str()); + + sai_route_entry_t route_entry; + route_entry.switch_id = gSwitchId; + route_entry.vr_id = route_pattern.vrf_id; + copy(route_entry.destination, ip_prefix); + sai_attribute_t attr; + attr.id = SAI_ROUTE_ENTRY_ATTR_COUNTER_ID; + attr.value.oid = SAI_NULL_OBJECT_ID; + + auto status = sai_route_api->set_route_entry_attribute(&route_entry, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Failed to unbind route entry vrf=%s prefix=%s from flow counter", route_pattern.vrf_name.c_str(), ip_prefix.to_string().c_str()); + } + + FlowCounterHandler::removeGenericCounter(counter_oid); +} + +bool FlowCounterRouteOrch::removeRouteFlowCounter(const RoutePattern &route_pattern, sai_object_id_t vrf_id, const IpPrefix& ip_prefix) +{ + SWSS_LOG_ENTER(); + + SWSS_LOG_NOTICE("Removing route entry vrf=%s prefix=%s from flow counter", route_pattern.vrf_name.c_str(), ip_prefix.to_string().c_str()); + + // Check if the entry is in mPendingAddToFlexCntr + sai_object_id_t counter_oid = SAI_NULL_OBJECT_ID; + auto pending_iter = mPendingAddToFlexCntr.find(route_pattern); + if (pending_iter != mPendingAddToFlexCntr.end()) + { + auto iter_prefix = pending_iter->second.find(ip_prefix); + if (iter_prefix != pending_iter->second.end()) + { + counter_oid = iter_prefix->second; + pending_iter->second.erase(iter_prefix); + if (pending_iter->second.empty()) + { + mPendingAddToFlexCntr.erase(pending_iter); + } + } + } + + if (counter_oid == SAI_NULL_OBJECT_ID) + { + // Check if the entry is in mBoundRouteCounters + auto cache_iter = mBoundRouteCounters.find(route_pattern); + if (cache_iter != mBoundRouteCounters.end()) + { + auto iter_prefix = cache_iter->second.find(ip_prefix); + if (iter_prefix != cache_iter->second.end()) + { + counter_oid = iter_prefix->second; + removeRouteFlowCounterFromDB(vrf_id, ip_prefix, counter_oid); + cache_iter->second.erase(iter_prefix); + if (cache_iter->second.empty()) + { + mBoundRouteCounters.erase(cache_iter); + } + } + } + } + + // No need unbind because the route entry has been removed, just remove the generic counter + if (counter_oid != SAI_NULL_OBJECT_ID) + { + FlowCounterHandler::removeGenericCounter(counter_oid); + return true; + } + + return false; +} + +void FlowCounterRouteOrch::pendingUpdateFlexDb(const RoutePattern &route_pattern, const IpPrefix& ip_prefix, sai_object_id_t counter_oid) +{ + SWSS_LOG_ENTER(); + bool was_empty = mPendingAddToFlexCntr.empty(); + updateRouterFlowCounterCache(route_pattern, ip_prefix, counter_oid, mPendingAddToFlexCntr); + if (was_empty) + { + mFlexCounterUpdTimer->start(); + } +} + +bool FlowCounterRouteOrch::validateRoutePattern(const RoutePattern &route_pattern) const +{ + SWSS_LOG_ENTER(); + + for (const auto& existing : mRoutePatternSet) + { + if (existing.is_overlap_with(route_pattern)) + { + SWSS_LOG_ERROR("Configured route pattern %s is conflict with existing one %s", route_pattern.to_string().c_str(), existing.to_string().c_str()); + return false; + } + } + + return true; +} + +size_t FlowCounterRouteOrch::getRouteFlowCounterSizeByPattern(const RoutePattern &route_pattern) const +{ + SWSS_LOG_ENTER(); + + auto cache_iter = mBoundRouteCounters.find(route_pattern); + auto cache_count = cache_iter == mBoundRouteCounters.end() ? 0 : cache_iter->second.size(); + auto pending_iter = mPendingAddToFlexCntr.find(route_pattern); + auto pending_count = pending_iter == mPendingAddToFlexCntr.end() ? 0 : pending_iter->second.size(); + return cache_count + pending_count; +} + +bool FlowCounterRouteOrch::isRouteAlreadyBound(const RoutePattern &route_pattern, const IpPrefix &ip_prefix) const +{ + SWSS_LOG_ENTER(); + + auto iter = mBoundRouteCounters.find(route_pattern); + if (iter == mBoundRouteCounters.end()) + { + auto pending_iter = mPendingAddToFlexCntr.find(route_pattern); + if (pending_iter != mPendingAddToFlexCntr.end()) + { + return pending_iter->second.find(ip_prefix) != pending_iter->second.end(); + } + return false; + } + + return iter->second.find(ip_prefix) != iter->second.end(); +} + +void FlowCounterRouteOrch::createRouteFlowCounterByPattern(const RoutePattern &route_pattern, size_t current_bound_count) +{ + SWSS_LOG_ENTER(); + if (!isRouteFlowCounterEnabled()) + { + return; + } + + auto &syncdRoutes = gRouteOrch->getSyncdRoutes(); + auto iter = syncdRoutes.find(route_pattern.vrf_id); + if (iter != syncdRoutes.end()) + { + SWSS_LOG_NOTICE("Creating route flow counter for pattern %s", route_pattern.to_string().c_str()); + + for (auto &entry : iter->second) + { + if (current_bound_count == route_pattern.max_match_count) + { + return; + } + + if (route_pattern.is_match(route_pattern.vrf_id, entry.first)) + { + if (isRouteAlreadyBound(route_pattern, entry.first)) + { + continue; + } + + if (bindFlowCounter(route_pattern, route_pattern.vrf_id, entry.first)) + { + ++current_bound_count; + } + } + } + } + + createRouteFlowCounterFromVnetRoutes(route_pattern, current_bound_count); + + auto misc_iter = mMiscRoutes.find(route_pattern.vrf_id); + if (misc_iter != mMiscRoutes.end()) + { + SWSS_LOG_NOTICE("Creating route flow counter for pattern %s for other type route entries", route_pattern.to_string().c_str()); + + for (auto ip_prefix : misc_iter->second) + { + if (current_bound_count == route_pattern.max_match_count) + { + return; + } + + if (route_pattern.is_match(route_pattern.vrf_id, ip_prefix)) + { + if (isRouteAlreadyBound(route_pattern, ip_prefix)) + { + continue; + } + + if (bindFlowCounter(route_pattern, route_pattern.vrf_id, ip_prefix)) + { + ++current_bound_count; + } + } + } + } +} + +void FlowCounterRouteOrch::createRouteFlowCounterFromVnetRoutes(const RoutePattern &route_pattern, size_t& current_bound_count) +{ + SWSS_LOG_ENTER(); + + auto *vnet_orch = gDirectory.get(); + assert(vnet_orch); // VnetOrch instance is created before RouteOrch + + if (!vnet_orch->isVnetExists(route_pattern.vrf_name)) + { + return; + } + + SWSS_LOG_NOTICE("Creating route flow counter for pattern %s for VNET route entries", route_pattern.to_string().c_str()); + + auto *vrf_obj = vnet_orch->getTypePtr(route_pattern.vrf_name); + const auto &route_map = vrf_obj->getRouteMap(); + for (const auto &entry : route_map) + { + if (current_bound_count == route_pattern.max_match_count) + { + return; + } + + if (route_pattern.is_match(route_pattern.vrf_id, entry.first)) + { + if (isRouteAlreadyBound(route_pattern, entry.first)) + { + continue; + } + + if (bindFlowCounter(route_pattern, route_pattern.vrf_id, entry.first)) + { + ++current_bound_count; + } + } + } + + const auto &tunnel_routes = vrf_obj->getTunnelRoutes(); + for (const auto &entry : tunnel_routes) + { + if (current_bound_count == route_pattern.max_match_count) + { + return; + } + if (route_pattern.is_match(route_pattern.vrf_id, entry.first)) + { + if (isRouteAlreadyBound(route_pattern, entry.first)) + { + continue; + } + + if (bindFlowCounter(route_pattern, route_pattern.vrf_id, entry.first)) + { + ++current_bound_count; + } + } + } +} + +void FlowCounterRouteOrch::reapRouteFlowCounterByPattern(const RoutePattern &route_pattern, size_t current_bound_count) +{ + SWSS_LOG_ENTER(); + + auto pending_iter = mPendingAddToFlexCntr.find(route_pattern); + auto iter = mBoundRouteCounters.find(route_pattern); + if (iter == mBoundRouteCounters.end() && pending_iter == mPendingAddToFlexCntr.end()) + { + return; + } + + // Remove from pending cache first + if (pending_iter != mPendingAddToFlexCntr.end()) + { + while(current_bound_count > route_pattern.max_match_count) + { + auto bound_iter = pending_iter->second.begin(); + if (bound_iter == pending_iter->second.end()) + { + break; + } + unbindFlowCounter(route_pattern, route_pattern.vrf_id, bound_iter->first, bound_iter->second); + pending_iter->second.erase(bound_iter); + --current_bound_count; + } + } + + // Remove from bound cache + if (iter != mBoundRouteCounters.end()) + { + while(current_bound_count > route_pattern.max_match_count) + { + auto bound_iter = iter->second.begin(); + if (bound_iter == iter->second.end()) + { + break; + } + + removeRouteFlowCounterFromDB(route_pattern.vrf_id, bound_iter->first, bound_iter->second); + unbindFlowCounter(route_pattern, route_pattern.vrf_id, bound_iter->first, bound_iter->second); + iter->second.erase(bound_iter); + --current_bound_count; + } + } +} + +void FlowCounterRouteOrch::onRoutePatternMaxMatchCountChange(RoutePattern &route_pattern, size_t new_max_match_count) +{ + SWSS_LOG_ENTER(); + + if (route_pattern.max_match_count != new_max_match_count) + { + auto old_max_match_count = route_pattern.max_match_count; + route_pattern.max_match_count = new_max_match_count; + + if (!isRouteFlowCounterEnabled()) + { + return; + } + + auto current_bound_count = getRouteFlowCounterSizeByPattern(route_pattern); + SWSS_LOG_NOTICE("Current bound route flow counter count is %zu, new limit is %zu, old limit is %zu", current_bound_count, new_max_match_count, old_max_match_count); + if (new_max_match_count > old_max_match_count) + { + if (current_bound_count == old_max_match_count) + { + createRouteFlowCounterByPattern(route_pattern, current_bound_count); + } + } + else + { + if (current_bound_count > new_max_match_count) + { + reapRouteFlowCounterByPattern(route_pattern, current_bound_count); + } + } + } +} + +void FlowCounterRouteOrch::getRouteFlowCounterNameMapKey(sai_object_id_t vrf_id, const IpPrefix& ip_prefix, std::string &key) +{ + SWSS_LOG_ENTER(); + std::ostringstream oss; + if (gVirtualRouterId != vrf_id) + { + auto *vrf_orch = gDirectory.get(); + auto vrf_name = vrf_orch->getVRFname(vrf_id); + if (vrf_name == "") + { + getVnetNameByVrfId(vrf_id, vrf_name); + } + + if (vrf_name != "") + { + oss << vrf_name; + oss << "|"; + } + else + { + // Should not happen, just in case + SWSS_LOG_ERROR("Failed to get VRF/VNET name for vrf id %s", sai_serialize_object_id(vrf_id).c_str()); + } + } + oss << ip_prefix.to_string(); + key = oss.str(); +} + +void FlowCounterRouteOrch::handleRouteAdd(sai_object_id_t vrf_id, const IpPrefix& ip_prefix) +{ + if (!mRouteFlowCounterSupported) + { + return; + } + + if (!isRouteFlowCounterEnabled()) + { + return; + } + + for (const auto &route_pattern : mRoutePatternSet) + { + if (route_pattern.is_match(vrf_id, ip_prefix)) + { + auto current_bound_count = getRouteFlowCounterSizeByPattern(route_pattern); + if (current_bound_count < route_pattern.max_match_count) + { + bindFlowCounter(route_pattern, vrf_id, ip_prefix); + } + break; + } + } +} + +void FlowCounterRouteOrch::handleRouteRemove(sai_object_id_t vrf_id, const IpPrefix& ip_prefix) +{ + if (!mRouteFlowCounterSupported) + { + return; + } + + if (!isRouteFlowCounterEnabled()) + { + return; + } + + for (const auto &route_pattern : mRoutePatternSet) + { + if (route_pattern.is_match(vrf_id, ip_prefix)) + { + if (isRouteAlreadyBound(route_pattern, ip_prefix)) + { + if (removeRouteFlowCounter(route_pattern, vrf_id, ip_prefix)) + { + auto current_bound_count = getRouteFlowCounterSizeByPattern(route_pattern); + if (current_bound_count == route_pattern.max_match_count - 1) + { + createRouteFlowCounterByPattern(route_pattern, current_bound_count); + } + } + } + break; + } + } +} + +void FlowCounterRouteOrch::removeRouteFlowCounterFromDB(sai_object_id_t vrf_id, const IpPrefix& ip_prefix, sai_object_id_t counter_oid) +{ + SWSS_LOG_ENTER(); + std::string nameMapKey; + getRouteFlowCounterNameMapKey(vrf_id, ip_prefix, nameMapKey); + mPrefixToPatternTable->hdel("", nameMapKey); + mPrefixToCounterTable->hdel("", nameMapKey); + mRouteFlowCounterMgr.clearCounterIdList(counter_oid); +} + +void FlowCounterRouteOrch::updateRouterFlowCounterCache( + const RoutePattern &route_pattern, + const IpPrefix &ip_prefix, + sai_object_id_t counter_oid, + RouterFlowCounterCache &cache) +{ + SWSS_LOG_ENTER(); + auto iter = cache.find(route_pattern); + if (iter == cache.end()) + { + cache.emplace(route_pattern, std::map({{ip_prefix, counter_oid}})); + } + else + { + iter->second.emplace(ip_prefix, counter_oid); + } +} + +bool FlowCounterRouteOrch::isRouteFlowCounterEnabled() const +{ + SWSS_LOG_ENTER(); + FlexCounterOrch *flexCounterOrch = gDirectory.get(); + return flexCounterOrch && flexCounterOrch->getRouteFlowCountersState(); +} + +bool FlowCounterRouteOrch::parseRouteKeyForRoutePattern(const std::string &key, char sep, sai_object_id_t &vrf_id, IpPrefix &ip_prefix, std::string &vrf_name) +{ + size_t found = key.find(sep); + if (found == std::string::npos) + { + vrf_id = gVirtualRouterId; + ip_prefix = IpPrefix(key); + vrf_name = ""; + } + else + { + vrf_name = key.substr(0, found); + auto *vrf_orch = gDirectory.get(); + if (!key.compare(0, strlen(VRF_PREFIX), VRF_PREFIX) && vrf_orch->isVRFexists(vrf_name)) + { + vrf_id = vrf_orch->getVRFid(vrf_name); + } + else + { + if (!getVrfIdByVnetName(vrf_name, vrf_id)) + { + SWSS_LOG_NOTICE("VRF/VNET name %s is not resolved", vrf_name.c_str()); + return false; + } + } + + ip_prefix = IpPrefix(key.substr(found+1)); + } + + return true; +} + +bool FlowCounterRouteOrch::getVrfIdByVnetName(const std::string& vnet_name, sai_object_id_t &vrf_id) +{ + auto *vnet_orch = gDirectory.get(); + assert(vnet_orch); // VnetOrch instance is created before RouteOrch + + return vnet_orch->getVrfIdByVnetName(vnet_name, vrf_id); +} + +bool FlowCounterRouteOrch::getVnetNameByVrfId(sai_object_id_t vrf_id, std::string& vnet_name) +{ + auto *vnet_orch = gDirectory.get(); + assert(vnet_orch); // VnetOrch instance is created before RouteOrch + + return vnet_orch->getVnetNameByVrfId(vrf_id, vnet_name); +} + diff --git a/orchagent/flex_counter/flowcounterrouteorch.h b/orchagent/flex_counter/flowcounterrouteorch.h new file mode 100644 index 000000000000..1ef0452d4a3c --- /dev/null +++ b/orchagent/flex_counter/flowcounterrouteorch.h @@ -0,0 +1,178 @@ +#pragma once + +#include "bulker.h" +#include "dbconnector.h" +#include "ipprefix.h" +#include "orch.h" +#include +#include +#include +#include +#include + +#define ROUTE_FLOW_COUNTER_FLEX_COUNTER_GROUP "ROUTE_FLOW_COUNTER" + +struct RoutePattern +{ + RoutePattern(const std::string& input_vrf_name, sai_object_id_t vrf, IpPrefix prefix, size_t max_match_count) + :vrf_name(input_vrf_name), vrf_id(vrf), ip_prefix(prefix), max_match_count(max_match_count), exact_match(prefix.isDefaultRoute()) + { + } + + std::string vrf_name; + sai_object_id_t vrf_id; + IpPrefix ip_prefix; + size_t max_match_count; + bool exact_match; + + bool operator < (const RoutePattern &other) const + { + // We don't compare the vrf id here because: + // 1. vrf id could be SAI_NULL_OBJECT_ID if the VRF name is not resolved, two pattern with different VRF name and vrf_id=SAI_NULL_OBJECT_ID + // and same prefix will be treat as same route pattern, which is not expected + // 2. vrf name must be different + auto vrf_name_compare = vrf_name.compare(other.vrf_name); + if (vrf_name_compare < 0) + { + return true; + } + else if (vrf_name_compare == 0 && ip_prefix < other.ip_prefix) + { + return true; + } + else + { + return false; + } + } + + bool is_match(sai_object_id_t vrf, IpPrefix prefix) const + { + // No need compare VRF name here because: + // 1. If the VRF is not resolved, the vrf_id shall be SAI_NULL_OBJECT_ID, it cannot match any input vrf_id + // 2. If the VRF is resolved, different vrf must have different vrf id + if (vrf_id != vrf) + { + return false; + } + + if (!exact_match) + { + return (ip_prefix.getMaskLength() <= prefix.getMaskLength() && ip_prefix.isAddressInSubnet(prefix.getIp())); + } + else + { + return prefix == ip_prefix; + } + } + + bool is_overlap_with(const RoutePattern &other) const + { + if (this == &other) + { + return false; + } + + if (vrf_name != other.vrf_name) + { + return false; + } + + if (vrf_name != other.vrf_name) + { + return false; + } + + return is_match(other.vrf_id, other.ip_prefix) || other.is_match(vrf_id, ip_prefix); + } + + std::string to_string() const + { + std::ostringstream oss; + oss << "RoutePattern(vrf_id=" << vrf_id << ",ip_prefix=" << ip_prefix.to_string() << ")"; + return oss.str(); + } +}; + + + +typedef std::set RoutePatternSet; +/* RoutePattern to */ +typedef std::map> RouterFlowCounterCache; +/* IP2ME, MUX, VNET route entries */ +typedef std::map> MiscRouteEntryMap; + +class FlowCounterRouteOrch : public Orch +{ +public: + FlowCounterRouteOrch(swss::DBConnector *db, const std::vector &tableNames); + virtual ~FlowCounterRouteOrch(void); + + bool getRouteFlowCounterSupported() const { return mRouteFlowCounterSupported; } + void generateRouteFlowStats(); + void clearRouteFlowStats(); + void addRoutePattern(const std::string &pattern, size_t); + void removeRoutePattern(const std::string &pattern); + void onAddMiscRouteEntry(sai_object_id_t vrf_id, const IpPrefix& ip_prefix, bool add_to_cache = true); + void onAddMiscRouteEntry(sai_object_id_t vrf_id, const sai_ip_prefix_t& ip_pfx, bool add_to_cache = true); + void onRemoveMiscRouteEntry(sai_object_id_t vrf_id, const IpPrefix& ip_prefix, bool remove_from_cache = true); + void onRemoveMiscRouteEntry(sai_object_id_t vrf_id, const sai_ip_prefix_t& ip_pfx, bool remove_from_cache = true); + void onAddVR(sai_object_id_t vrf_id); + void onRemoveVR(sai_object_id_t vrf_id); + void handleRouteAdd(sai_object_id_t vrf_id, const IpPrefix& ip_prefix); + void handleRouteRemove(sai_object_id_t vrf_id, const IpPrefix& ip_prefix); + void processRouteFlowCounterBinding(); + +protected: + void doTask(Consumer &consumer) override; + void doTask(SelectableTimer &timer) override; + +private: + std::shared_ptr mAsicDb; + std::shared_ptr mCounterDb; + std::unique_ptr
mVidToRidTable; + std::unique_ptr
mPrefixToCounterTable; + std::unique_ptr
mPrefixToPatternTable; + + bool mRouteFlowCounterSupported = false; + /* Route pattern set, store configured route patterns */ + RoutePatternSet mRoutePatternSet; + /* Cache for those bound route flow counters*/ + RouterFlowCounterCache mBoundRouteCounters; + /* Cache for those route flow counters pending update to FLEX DB */ + RouterFlowCounterCache mPendingAddToFlexCntr; + /* IP2ME, MUX */ // TODO: remove MUX support + MiscRouteEntryMap mMiscRoutes; // Save here for route flow counter + /* Flex counter manager for route flow counter */ + FlexCounterManager mRouteFlowCounterMgr; + /* Timer to create flex counter and update counters DB */ + SelectableTimer *mFlexCounterUpdTimer = nullptr; + + EntityBulker gRouteBulker; + + void initRouteFlowCounterCapability(); + void removeRoutePattern(const RoutePattern &route_pattern); + void removeRouteFlowCounterFromDB(sai_object_id_t vrf_id, const IpPrefix& ip_prefix, sai_object_id_t counter_oid); + bool bindFlowCounter(const RoutePattern &route_pattern, sai_object_id_t vrf_id, const IpPrefix& ip_prefix); + void unbindFlowCounter(const RoutePattern &route_pattern, sai_object_id_t vrf_id, const IpPrefix& ip_prefix, sai_object_id_t counter_oid); + void pendingUpdateFlexDb(const RoutePattern &route_pattern, const IpPrefix &ip_prefix, sai_object_id_t counter_oid); + void updateRouterFlowCounterCache( + const RoutePattern &route_pattern, + const IpPrefix& ip_prefix, + sai_object_id_t counter_oid, + RouterFlowCounterCache &cache); + bool validateRoutePattern(const RoutePattern &route_pattern) const; + void onRoutePatternMaxMatchCountChange(RoutePattern &route_pattern, size_t new_max_match_count); + bool isRouteAlreadyBound(const RoutePattern &route_pattern, const IpPrefix &ip_prefix) const; + void createRouteFlowCounterByPattern(const RoutePattern &route_pattern, size_t currentBoundCount); + /* Return true if it actaully removed a counter so that caller need to fill the hole if possible*/ + bool removeRouteFlowCounter(const RoutePattern &route_pattern, sai_object_id_t vrf_id, const IpPrefix& ip_prefix); + void createRouteFlowCounterFromVnetRoutes(const RoutePattern &route_pattern, size_t& current_bound_count); + void reapRouteFlowCounterByPattern(const RoutePattern &route_pattern, size_t currentBoundCount); + bool isRouteFlowCounterEnabled() const; + void getRouteFlowCounterNameMapKey(sai_object_id_t vrf_id, const IpPrefix &ip_prefix, std::string &key); + size_t getRouteFlowCounterSizeByPattern(const RoutePattern &route_pattern) const; + bool parseRouteKeyForRoutePattern(const std::string &key, char sep, sai_object_id_t &vrf_id, IpPrefix &ip_prefix, std::string& vrf_name); + bool getVrfIdByVnetName(const std::string& vnet_name, sai_object_id_t &vrf_id); + bool getVnetNameByVrfId(sai_object_id_t vrf_id, std::string& vnet_name); +}; diff --git a/orchagent/flexcounterorch.cpp b/orchagent/flexcounterorch.cpp index dc14998774ff..a3770b76cb8a 100644 --- a/orchagent/flexcounterorch.cpp +++ b/orchagent/flexcounterorch.cpp @@ -10,6 +10,8 @@ #include "debugcounterorch.h" #include "directory.h" #include "copporch.h" +#include "routeorch.h" +#include "flowcounterrouteorch.h" extern sai_port_api_t *sai_port_api; @@ -19,6 +21,7 @@ extern IntfsOrch *gIntfsOrch; extern BufferOrch *gBufferOrch; extern Directory gDirectory; extern CoppOrch *gCoppOrch; +extern FlowCounterRouteOrch *gFlowCounterRouteOrch; #define BUFFER_POOL_WATERMARK_KEY "BUFFER_POOL_WATERMARK" #define PORT_KEY "PORT" @@ -29,6 +32,7 @@ extern CoppOrch *gCoppOrch; #define ACL_KEY "ACL" #define TUNNEL_KEY "TUNNEL" #define FLOW_CNT_TRAP_KEY "FLOW_CNT_TRAP" +#define FLOW_CNT_ROUTE_KEY "FLOW_CNT_ROUTE" unordered_map flexCounterGroupMap = { @@ -47,6 +51,7 @@ unordered_map flexCounterGroupMap = {"ACL", ACL_COUNTER_FLEX_COUNTER_GROUP}, {"TUNNEL", TUNNEL_STAT_COUNTER_FLEX_COUNTER_GROUP}, {FLOW_CNT_TRAP_KEY, HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP}, + {FLOW_CNT_ROUTE_KEY, ROUTE_FLOW_COUNTER_FLEX_COUNTER_GROUP}, }; @@ -175,6 +180,19 @@ void FlexCounterOrch::doTask(Consumer &consumer) m_hostif_trap_counter_enabled = false; } } + if (gFlowCounterRouteOrch && gFlowCounterRouteOrch->getRouteFlowCounterSupported() && key == FLOW_CNT_ROUTE_KEY) + { + if (value == "enable" && !m_route_flow_counter_enabled) + { + m_route_flow_counter_enabled = true; + gFlowCounterRouteOrch->generateRouteFlowStats(); + } + else if (value == "disable" && m_route_flow_counter_enabled) + { + gFlowCounterRouteOrch->clearRouteFlowStats(); + m_route_flow_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 ceb8187506f9..4f9734c0e2d4 100644 --- a/orchagent/flexcounterorch.h +++ b/orchagent/flexcounterorch.h @@ -19,15 +19,16 @@ class FlexCounterOrch: public Orch bool getPortCountersState() const; bool getPortBufferDropCountersState() const; bool getHostIfTrapCounterState() const {return m_hostif_trap_counter_enabled;} + bool getRouteFlowCountersState() const {return m_route_flow_counter_enabled;} bool bake() override; - 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; + bool m_route_flow_counter_enabled = false; Table m_flexCounterConfigTable; }; diff --git a/orchagent/intfsorch.cpp b/orchagent/intfsorch.cpp index 1feebb4d753e..587e04e140f9 100644 --- a/orchagent/intfsorch.cpp +++ b/orchagent/intfsorch.cpp @@ -12,6 +12,7 @@ #include "swssnet.h" #include "tokenize.h" #include "routeorch.h" +#include "flowcounterrouteorch.h" #include "crmorch.h" #include "bufferorch.h" #include "directory.h" @@ -29,7 +30,7 @@ extern sai_vlan_api_t* sai_vlan_api; extern sai_object_id_t gSwitchId; extern PortsOrch *gPortsOrch; -extern RouteOrch *gRouteOrch; +extern FlowCounterRouteOrch *gFlowCounterRouteOrch; extern CrmOrch *gCrmOrch; extern BufferOrch *gBufferOrch; extern bool gIsNatSupported; @@ -1272,6 +1273,8 @@ void IntfsOrch::addIp2MeRoute(sai_object_id_t vrf_id, const IpPrefix &ip_prefix) { gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); } + + gFlowCounterRouteOrch->onAddMiscRouteEntry(vrf_id, IpPrefix(ip_prefix.getIp().to_string())); } void IntfsOrch::removeIp2MeRoute(sai_object_id_t vrf_id, const IpPrefix &ip_prefix) @@ -1301,6 +1304,8 @@ void IntfsOrch::removeIp2MeRoute(sai_object_id_t vrf_id, const IpPrefix &ip_pref { gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); } + + gFlowCounterRouteOrch->onRemoveMiscRouteEntry(vrf_id, IpPrefix(ip_prefix.getIp().to_string())); } void IntfsOrch::addDirectedBroadcast(const Port &port, const IpPrefix &ip_prefix) diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index a6563d45caea..1b9d1c10879b 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -52,6 +52,7 @@ CoppOrch *gCoppOrch; P4Orch *gP4Orch; BfdOrch *gBfdOrch; Srv6Orch *gSrv6Orch; +FlowCounterRouteOrch *gFlowCounterRouteOrch; DebugCounterOrch *gDebugCounterOrch; bool gIsNatSupported = false; @@ -131,6 +132,12 @@ bool OrchDaemon::init() TableConnector stateDbBfdSessionTable(m_stateDb, STATE_BFD_SESSION_TABLE_NAME); gBfdOrch = new BfdOrch(m_applDb, APP_BFD_SESSION_TABLE_NAME, stateDbBfdSessionTable); + static const vector route_pattern_tables = { + CFG_FLOW_COUNTER_ROUTE_PATTERN_TABLE_NAME, + }; + gFlowCounterRouteOrch = new FlowCounterRouteOrch(m_configDb, route_pattern_tables); + gDirectory.set(gFlowCounterRouteOrch); + vector vnet_tables = { APP_VNET_RT_TABLE_NAME, APP_VNET_RT_TUNNEL_TABLE_NAME @@ -331,7 +338,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, mux_orch, mux_cb_orch, gIntfsOrch, gNeighOrch, gNhgMapOrch, gNhgOrch, gCbfNhgOrch, gRouteOrch, gCoppOrch, gQosOrch, wm_orch, policer_orch, tunnel_decap_orch, sflow_orch, gDebugCounterOrch, gMacsecOrch, gBfdOrch, gSrv6Orch}; + m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, gFlowCounterRouteOrch, mux_orch, mux_cb_orch, gIntfsOrch, gNeighOrch, gNhgMapOrch, gNhgOrch, gCbfNhgOrch, gRouteOrch, gCoppOrch, gQosOrch, wm_orch, policer_orch, tunnel_decap_orch, sflow_orch, gDebugCounterOrch, gMacsecOrch, gBfdOrch, gSrv6Orch}; bool initialize_dtel = false; if (platform == BFN_PLATFORM_SUBSTRING || platform == VS_PLATFORM_SUBSTRING) diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index 35e531aa15c4..def4b7862940 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -11,6 +11,7 @@ #include "intfsorch.h" #include "neighorch.h" #include "routeorch.h" +#include "flowcounterrouteorch.h" #include "nhgorch.h" #include "cbf/cbfnhgorch.h" #include "cbf/nhgmaporch.h" diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index e3c27b98182a..a793ab8dcc4c 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -5,6 +5,7 @@ #include "nhgorch.h" #include "cbf/cbfnhgorch.h" #include "logger.h" +#include "flowcounterrouteorch.h" #include "swssnet.h" #include "crmorch.h" #include "directory.h" @@ -22,6 +23,7 @@ extern CrmOrch *gCrmOrch; extern Directory gDirectory; extern NhgOrch *gNhgOrch; extern CbfNhgOrch *gCbfNhgOrch; +extern FlowCounterRouteOrch *gFlowCounterRouteOrch; extern size_t gMaxBulkSize; @@ -145,7 +147,6 @@ RouteOrch::RouteOrch(DBConnector *db, vector &tableNames, addLinkLocalRouteToMe(gVirtualRouterId, default_link_local_prefix); SWSS_LOG_NOTICE("Created link local ipv6 route %s to cpu", default_link_local_prefix.to_string().c_str()); - } std::string RouteOrch::getLinkLocalEui64Addr(void) @@ -212,6 +213,8 @@ void RouteOrch::addLinkLocalRouteToMe(sai_object_id_t vrf_id, IpPrefix linklocal gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + gFlowCounterRouteOrch->onAddMiscRouteEntry(vrf_id, linklocal_prefix.getSubnet()); + SWSS_LOG_NOTICE("Created link local ipv6 route %s to cpu", linklocal_prefix.to_string().c_str()); } @@ -233,6 +236,8 @@ void RouteOrch::delLinkLocalRouteToMe(sai_object_id_t vrf_id, IpPrefix linklocal gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + gFlowCounterRouteOrch->onRemoveMiscRouteEntry(vrf_id, linklocal_prefix.getSubnet()); + SWSS_LOG_NOTICE("Deleted link local ipv6 route %s to cpu", linklocal_prefix.to_string().c_str()); } @@ -2212,6 +2217,11 @@ bool RouteOrch::addRoutePost(const RouteBulkContext& ctx, const NextHopGroupKey updateDefRouteState(ipPrefix.to_string(), true); } + if (it_route == m_syncdRoutes.at(vrf_id).end()) + { + gFlowCounterRouteOrch->handleRouteAdd(vrf_id, ipPrefix); + } + m_syncdRoutes[vrf_id][ipPrefix] = RouteNhg(nextHops, ctx.nhg_index); notifyNextHopChangeObservers(vrf_id, ipPrefix, nextHops, true); @@ -2419,6 +2429,7 @@ bool RouteOrch::removeRoutePost(const RouteBulkContext& ctx) } else { + gFlowCounterRouteOrch->handleRouteRemove(vrf_id, ipPrefix); it_route_table->second.erase(ipPrefix); /* Notify about the route next hop removal */ diff --git a/orchagent/routeorch.h b/orchagent/routeorch.h index 2c8826ecf7a3..5f297c6a0e6b 100644 --- a/orchagent/routeorch.h +++ b/orchagent/routeorch.h @@ -214,10 +214,11 @@ class RouteOrch : public Orch, public Subject unsigned int getNhgCount() { return m_nextHopGroupCount; } unsigned int getMaxNhgCount() { return m_maxNextHopGroupCount; } - + void increaseNextHopGroupCount(); void decreaseNextHopGroupCount(); bool checkNextHopGroupCount(); + const RouteTables& getSyncdRoutes() const { return m_syncdRoutes; } private: SwitchOrch *m_switchOrch; diff --git a/orchagent/swssnet.h b/orchagent/swssnet.h index be49708d4f01..82b5b6f94f57 100644 --- a/orchagent/swssnet.h +++ b/orchagent/swssnet.h @@ -3,6 +3,7 @@ // #pragma once +#include #include #include #include @@ -76,6 +77,53 @@ inline static sai_ip_prefix_t& copy(sai_ip_prefix_t& dst, const IpAddress& src) return dst; } +static int getPrefixLenFromAddrMask(const uint8_t *addr, int len) +{ + int i = 0; + uint8_t non_zero = 0xFF; + for (i = len - 1; i >=0; i--) + { + if (addr[i] != 0) + { + non_zero = addr[i]; + break; + } + } + + if (non_zero == 0xFF) + { + return (i + 1) * 8; + } + else + { + int j = 2; + while(((non_zero >> j) & 0x1) == 0) + { + ++j; + } + return (i + 1) * 8 - (j + 1); + } + +} + +inline static IpPrefix getIpPrefixFromSaiPrefix(const sai_ip_prefix_t& src) +{ + ip_addr_t ip; + switch(src.addr_family) + { + case SAI_IP_ADDR_FAMILY_IPV4: + ip.family = AF_INET; + ip.ip_addr.ipv4_addr = src.addr.ip4; + return IpPrefix(ip, getPrefixLenFromAddrMask(reinterpret_cast(&src.mask.ip4), 4)); + case SAI_IP_ADDR_FAMILY_IPV6: + ip.family = AF_INET6; + memcpy(ip.ip_addr.ipv6_addr, src.addr.ip6, 16); + return IpPrefix(ip, getPrefixLenFromAddrMask(src.mask.ip6, 16)); + default: + throw std::logic_error("Invalid family"); + } +} + inline static sai_ip_prefix_t& subnet(sai_ip_prefix_t& dst, const sai_ip_prefix_t& src) { dst.addr_family = src.addr_family; diff --git a/orchagent/vnetorch.cpp b/orchagent/vnetorch.cpp index e3f2cbac6eb6..5e81fb0d75f8 100644 --- a/orchagent/vnetorch.cpp +++ b/orchagent/vnetorch.cpp @@ -21,6 +21,7 @@ #include "neighorch.h" #include "crmorch.h" #include "routeorch.h" +#include "flowcounterrouteorch.h" extern sai_virtual_router_api_t* sai_virtual_router_api; extern sai_route_api_t* sai_route_api; @@ -37,6 +38,7 @@ extern PortsOrch *gPortsOrch; extern IntfsOrch *gIntfsOrch; extern NeighOrch *gNeighOrch; extern CrmOrch *gCrmOrch; +extern FlowCounterRouteOrch *gFlowCounterRouteOrch; extern RouteOrch *gRouteOrch; extern MacAddress gVxlanMacAddress; extern BfdOrch *gBfdOrch; @@ -97,6 +99,7 @@ bool VNetVrfObject::createObj(vector& attrs) vnet_name_.c_str(), status); throw std::runtime_error("Failed to create VR object"); } + gFlowCounterRouteOrch->onAddVR(router_id); return true; }; @@ -315,6 +318,7 @@ VNetVrfObject::~VNetVrfObject() SWSS_LOG_ERROR("Failed to remove virtual router name: %s, rv:%d", vnet_name_.c_str(), status); } + gFlowCounterRouteOrch->onRemoveVR(it); } SWSS_LOG_INFO("VNET '%s' deleted ", vnet_name_.c_str()); @@ -551,6 +555,40 @@ bool VNetOrch::delOperation(const Request& request) return true; } +bool VNetOrch::getVrfIdByVnetName(const std::string& vnet_name, sai_object_id_t &vrf_id) +{ + if (!isVnetExists(vnet_name)) + { + return false; + } + + auto *vrf_obj = getTypePtr(vnet_name); + // Now we only support ingress VR for VNET, so just get ingress VR ID + // Once we support egress VR, need revisit here. + vrf_id = vrf_obj->getVRidIngress(); + return vrf_id != SAI_NULL_OBJECT_ID; +} + +bool VNetOrch::getVnetNameByVrfId(sai_object_id_t vrf_id, std::string& vnet_name) +{ + for (auto &entry : vnet_table_) + { + auto *vrf_obj = dynamic_cast(entry.second.get()); + if (!vrf_obj) + { + continue; + } + + if (vrf_obj->getVRidIngress() == vrf_id) + { + vnet_name = entry.first; + return true; + } + } + + return false; +} + /* * Vnet Route Handling */ @@ -583,6 +621,8 @@ static bool del_route(sai_object_id_t vr_id, sai_ip_prefix_t& ip_pfx) gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); } + gFlowCounterRouteOrch->onRemoveMiscRouteEntry(vr_id, ip_pfx, false); + return true; } @@ -614,6 +654,8 @@ static bool add_route(sai_object_id_t vr_id, sai_ip_prefix_t& ip_pfx, sai_object gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); } + gFlowCounterRouteOrch->onAddMiscRouteEntry(vr_id, ip_pfx, false); + return true; } @@ -827,7 +869,7 @@ bool VNetRouteOrch::removeNextHopGroup(const string& vnet, const NextHopGroupKey template<> bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipPrefix, - NextHopGroupKey& nexthops, string& op, + NextHopGroupKey& nexthops, string& op, const map& monitors) { SWSS_LOG_ENTER(); @@ -923,8 +965,8 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP if (syncd_nexthop_groups_[vnet][nhg].active_members.empty()) { route_status = add_route(vr_id, pfx, nh_id); - } - else + } + else { route_status = update_route(vr_id, pfx, nh_id); } @@ -1267,7 +1309,7 @@ bool VNetRouteOrch::handleRoutes(const Request& request) SWSS_LOG_INFO("VNET-RT '%s' op '%s' for ip %s", vnet_name.c_str(), op.c_str(), ip_pfx.to_string().c_str()); - + if (op == SET_COMMAND) { addRoute(vnet_name, ip_pfx, nh); @@ -1321,7 +1363,7 @@ void VNetRouteOrch::attach(Observer* observer, const IpAddress& dstAddr) dstAddr.to_string().c_str()); for (auto vnetEntry : bestRoute->second) { - VNetNextHopUpdate update = + VNetNextHopUpdate update = { SET_COMMAND, vnetEntry.first, // vnet name @@ -1362,7 +1404,7 @@ void VNetRouteOrch::detach(Observer* observer, const IpAddress& dstAddr) { for (auto vnetEntry : bestRoute->second) { - VNetNextHopUpdate update = + VNetNextHopUpdate update = { DEL_COMMAND, vnetEntry.first, // vnet name @@ -1383,12 +1425,12 @@ void VNetRouteOrch::addRoute(const std::string& vnet, const IpPrefix& ipPrefix, { if (ipPrefix.isAddressInSubnet(next_hop_observer.first)) { - auto route_insert_result = next_hop_observer.second.routeTable.emplace(ipPrefix, VNetEntry()); + auto route_insert_result = next_hop_observer.second.routeTable.emplace(ipPrefix, VNetEntry()); auto vnet_result_result = route_insert_result.first->second.emplace(vnet, nh); if (!vnet_result_result.second) { - if (vnet_result_result.first->second.ips == nh.ips + if (vnet_result_result.first->second.ips == nh.ips && vnet_result_result.first->second.ifname == nh.ifname) { continue; @@ -1399,7 +1441,7 @@ void VNetRouteOrch::addRoute(const std::string& vnet, const IpPrefix& ipPrefix, // If the inserted route is the best route. (Table should not be empty. Because we inserted a new entry above) if (route_insert_result.first == --next_hop_observer.second.routeTable.end()) { - VNetNextHopUpdate update = + VNetNextHopUpdate update = { SET_COMMAND, vnet, // vnet name @@ -1437,7 +1479,7 @@ void VNetRouteOrch::delRoute(const IpPrefix& ipPrefix) if ( itr == next_hop_observer->second.routeTable.end()) { SWSS_LOG_ERROR( - "Failed to find any ip(%s) belong to this route(%s).", + "Failed to find any ip(%s) belong to this route(%s).", next_hop_observer->first.to_string().c_str(), ipPrefix.to_string().c_str()); assert(false); @@ -1875,7 +1917,7 @@ bool VNetRouteOrch::handleTunnel(const Request& request) SWSS_LOG_ERROR("Peer monitor size of %zu does not match endpoint size of %zu", monitor_list.size(), ip_list.size()); return false; } - + const std::string& vnet_name = request.getKeyString(0); auto ip_pfx = request.getKeyIpPrefix(1); auto op = request.getOperation(); diff --git a/orchagent/vnetorch.h b/orchagent/vnetorch.h index 77c278537166..26e073333770 100644 --- a/orchagent/vnetorch.h +++ b/orchagent/vnetorch.h @@ -190,6 +190,9 @@ class VNetVrfObject : public VNetObject void increaseNextHopRefCount(const nextHop&); void decreaseNextHopRefCount(const nextHop&); + const RouteMap &getRouteMap() const { return routes_; } + const TunnelRoutes &getTunnelRoutes() const { return tunnels_; } + ~VNetVrfObject(); private: @@ -247,6 +250,9 @@ class VNetOrch : public Orch2 return (vnet_exec_ == VNET_EXEC::VNET_EXEC_BRIDGE); } + bool getVrfIdByVnetName(const std::string& vnet_name, sai_object_id_t &vrf_id); + bool getVnetNameByVrfId(sai_object_id_t vrf_id, std::string& vnet_name); + private: virtual bool addOperation(const Request& request); virtual bool delOperation(const Request& request); diff --git a/orchagent/vrforch.cpp b/orchagent/vrforch.cpp index 19ca5c0fd885..776cf1eb0f2a 100644 --- a/orchagent/vrforch.cpp +++ b/orchagent/vrforch.cpp @@ -11,6 +11,7 @@ #include "request_parser.h" #include "vrforch.h" #include "vxlanorch.h" +#include "flowcounterrouteorch.h" #include "directory.h" using namespace std; @@ -18,8 +19,10 @@ using namespace swss; extern sai_virtual_router_api_t* sai_virtual_router_api; extern sai_object_id_t gSwitchId; -extern Directory gDirectory; -extern PortsOrch* gPortsOrch; + +extern Directory gDirectory; +extern PortsOrch* gPortsOrch; +extern FlowCounterRouteOrch* gFlowCounterRouteOrch; bool VRFOrch::addOperation(const Request& request) { @@ -104,6 +107,7 @@ bool VRFOrch::addOperation(const Request& request) vrf_table_[vrf_name].vrf_id = router_id; vrf_table_[vrf_name].ref_count = 0; vrf_id_table_[router_id] = vrf_name; + gFlowCounterRouteOrch->onAddVR(router_id); if (vni != 0) { SWSS_LOG_INFO("VRF '%s' vni %d add", vrf_name.c_str(), vni); @@ -176,6 +180,8 @@ bool VRFOrch::delOperation(const Request& request) } } + gFlowCounterRouteOrch->onRemoveVR(router_id); + vrf_table_.erase(vrf_name); vrf_id_table_.erase(router_id); error = delVrfVNIMap(vrf_name, 0); diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 42b9f17110f4..761aba19c66c 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -37,6 +37,7 @@ tests_SOURCES = aclorch_ut.cpp \ mock_redisreply.cpp \ bulker_ut.cpp \ fake_response_publisher.cpp \ + swssnet_ut.cpp \ $(top_srcdir)/lib/gearboxutils.cpp \ $(top_srcdir)/lib/subintf.cpp \ $(top_srcdir)/orchagent/orchdaemon.cpp \ @@ -93,7 +94,7 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/srv6orch.cpp \ $(top_srcdir)/orchagent/nvgreorch.cpp -tests_SOURCES += $(FLEX_CTR_DIR)/flex_counter_manager.cpp $(FLEX_CTR_DIR)/flex_counter_stat_manager.cpp $(FLEX_CTR_DIR)/flow_counter_handler.cpp +tests_SOURCES += $(FLEX_CTR_DIR)/flex_counter_manager.cpp $(FLEX_CTR_DIR)/flex_counter_stat_manager.cpp $(FLEX_CTR_DIR)/flow_counter_handler.cpp $(FLEX_CTR_DIR)/flowcounterrouteorch.cpp tests_SOURCES += $(DEBUG_CTR_DIR)/debug_counter.cpp $(DEBUG_CTR_DIR)/drop_counter.cpp tests_SOURCES += $(P4_ORCH_DIR)/p4orch.cpp \ $(P4_ORCH_DIR)/p4orch_util.cpp \ diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index 11afa57313cd..6f58b12a3b35 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -1,4 +1,5 @@ #include "ut_helper.h" +#include "flowcounterrouteorch.h" extern sai_object_id_t gSwitchId; @@ -6,6 +7,7 @@ extern SwitchOrch *gSwitchOrch; extern CrmOrch *gCrmOrch; extern PortsOrch *gPortsOrch; extern RouteOrch *gRouteOrch; +extern FlowCounterRouteOrch *gFlowCounterRouteOrch; extern IntfsOrch *gIntfsOrch; extern NeighOrch *gNeighOrch; extern FgNhgOrch *gFgNhgOrch; @@ -372,6 +374,11 @@ namespace aclorch_test ASSERT_EQ(gPortsOrch, nullptr); gPortsOrch = new PortsOrch(m_app_db.get(), m_state_db.get(), ports_tables, m_chassis_app_db.get()); + static const vector route_pattern_tables = { + CFG_FLOW_COUNTER_ROUTE_PATTERN_TABLE_NAME, + }; + gFlowCounterRouteOrch = new FlowCounterRouteOrch(m_config_db.get(), route_pattern_tables); + ASSERT_EQ(gVrfOrch, nullptr); gVrfOrch = new VRFOrch(m_app_db.get(), APP_VRF_TABLE_NAME, m_state_db.get(), STATE_VRF_OBJECT_TABLE_NAME); diff --git a/tests/mock_tests/mock_orchagent_main.h b/tests/mock_tests/mock_orchagent_main.h index 3166f3d9624f..ceaa2f553f2c 100644 --- a/tests/mock_tests/mock_orchagent_main.h +++ b/tests/mock_tests/mock_orchagent_main.h @@ -5,6 +5,7 @@ #include "crmorch.h" #include "portsorch.h" #include "routeorch.h" +#include "flowcounterrouteorch.h" #include "intfsorch.h" #include "neighorch.h" #include "fdborch.h" @@ -44,6 +45,7 @@ extern CrmOrch *gCrmOrch; extern PortsOrch *gPortsOrch; extern FgNhgOrch *gFgNhgOrch; extern RouteOrch *gRouteOrch; +extern FlowCounterRouteOrch *gFlowCounterRouteOrch; extern IntfsOrch *gIntfsOrch; extern NeighOrch *gNeighOrch; extern FdbOrch *gFdbOrch; diff --git a/tests/mock_tests/routeorch_ut.cpp b/tests/mock_tests/routeorch_ut.cpp index 84f92a088c48..66df4bfbcc82 100644 --- a/tests/mock_tests/routeorch_ut.cpp +++ b/tests/mock_tests/routeorch_ut.cpp @@ -185,6 +185,12 @@ namespace routeorch_test ASSERT_EQ(gPortsOrch, nullptr); gPortsOrch = new PortsOrch(m_app_db.get(), m_state_db.get(), ports_tables, m_chassis_app_db.get()); + static const vector route_pattern_tables = { + CFG_FLOW_COUNTER_ROUTE_PATTERN_TABLE_NAME, + }; + gFlowCounterRouteOrch = new FlowCounterRouteOrch(m_config_db.get(), route_pattern_tables); + gDirectory.set(gFlowCounterRouteOrch); + ASSERT_EQ(gVrfOrch, nullptr); gVrfOrch = new VRFOrch(m_app_db.get(), APP_VRF_TABLE_NAME, m_state_db.get(), STATE_VRF_OBJECT_TABLE_NAME); diff --git a/tests/mock_tests/swssnet_ut.cpp b/tests/mock_tests/swssnet_ut.cpp new file mode 100644 index 000000000000..6ec64e020255 --- /dev/null +++ b/tests/mock_tests/swssnet_ut.cpp @@ -0,0 +1,39 @@ +#include "ut_helper.h" +#include "swssnet.h" + +namespace swssnet_test +{ + struct SwssNetTest : public ::testing::Test + { + SwssNetTest() {} + }; + + TEST_F(SwssNetTest, CovertSAIPrefixToSONiCPrefix) + { + IpPrefix ip_prefix("1.2.3.4/24"); + sai_ip_prefix_t sai_prefix; + swss::copy(sai_prefix, ip_prefix); + IpPrefix ip_prefix_copied = swss::getIpPrefixFromSaiPrefix(sai_prefix); + ASSERT_EQ("1.2.3.4/24", ip_prefix_copied.to_string()); + + IpPrefix ip_prefix1("1.2.3.4/32"); + swss::copy(sai_prefix, ip_prefix1); + ip_prefix_copied = swss::getIpPrefixFromSaiPrefix(sai_prefix); + ASSERT_EQ("1.2.3.4/32", ip_prefix_copied.to_string()); + + IpPrefix ip_prefix2("0.0.0.0/0"); + swss::copy(sai_prefix, ip_prefix2); + ip_prefix_copied = swss::getIpPrefixFromSaiPrefix(sai_prefix); + ASSERT_EQ("0.0.0.0/0", ip_prefix_copied.to_string()); + + IpPrefix ip_prefix3("2000::1/128"); + swss::copy(sai_prefix, ip_prefix3); + ip_prefix_copied = swss::getIpPrefixFromSaiPrefix(sai_prefix); + ASSERT_EQ("2000::1/128", ip_prefix_copied.to_string()); + + IpPrefix ip_prefix4("::/0"); + swss::copy(sai_prefix, ip_prefix4); + ip_prefix_copied = swss::getIpPrefixFromSaiPrefix(sai_prefix); + ASSERT_EQ("::/0", ip_prefix_copied.to_string()); + } +} diff --git a/tests/test_flex_counters.py b/tests/test_flex_counters.py index bd95aa433d78..76a1a535f9a6 100644 --- a/tests/test_flex_counters.py +++ b/tests/test_flex_counters.py @@ -3,8 +3,9 @@ from swsscommon import swsscommon -TUNNEL_TYPE_MAP = "COUNTERS_TUNNEL_TYPE_MAP" -NUMBER_OF_RETRIES = 10 +TUNNEL_TYPE_MAP = "COUNTERS_TUNNEL_TYPE_MAP" +ROUTE_TO_PATTERN_MAP = "COUNTERS_ROUTE_TO_PATTERN_MAP" +NUMBER_OF_RETRIES = 10 CPU_PORT_OID = "0x0" PORT = "Ethernet0" PORT_MAP = "COUNTERS_PORT_NAME_MAP" @@ -62,6 +63,13 @@ 'name_map': 'ACL_COUNTER_RULE_MAP', 'pre_test': 'pre_acl_tunnel_counter_test', 'post_test': 'post_acl_tunnel_counter_test', + }, + 'route_flow_counter': { + 'key': 'FLOW_CNT_ROUTE', + 'group_name': 'ROUTE_FLOW_COUNTER', + 'name_map': 'COUNTERS_ROUTE_NAME_MAP', + 'pre_test': 'pre_route_flow_counter_test', + 'post_test': 'post_route_flow_counter_test', } } @@ -159,13 +167,14 @@ def verify_only_phy_ports_created(self, meta_data): 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 set_flex_counter_group_status(self, group, map, status='enable'): + def set_flex_counter_group_status(self, group, map, status='enable', check_name_map=True): group_stats_entry = {"FLEX_COUNTER_STATUS": status} self.config_db.create_entry("FLEX_COUNTER_TABLE", group, group_stats_entry) - if status == 'enable': - self.wait_for_table(map) - else: - self.wait_for_table_empty(map) + if check_name_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} @@ -186,6 +195,7 @@ def test_flex_counters(self, dvs, counter_type): counter_map = meta_data['name_map'] pre_test = meta_data.get('pre_test') post_test = meta_data.get('post_test') + meta_data['dvs'] = dvs self.verify_no_flex_counters_tables(counter_stat) @@ -227,6 +237,37 @@ def pre_acl_tunnel_counter_test(self, meta_data): } ) + def pre_route_flow_counter_test(self, meta_data): + dvs = meta_data['dvs'] + self.config_db.create_entry('FLOW_COUNTER_ROUTE_PATTERN', '1.1.0.0/16', + { + 'max_match_count': '30' + } + ) + self.config_db.create_entry('FLOW_COUNTER_ROUTE_PATTERN', '2000::/64', + { + 'max_match_count': '30' + } + ) + + self.create_l3_intf("Ethernet0", "") + self.add_ip_address("Ethernet0", "10.0.0.0/31") + self.set_admin_status("Ethernet0", "up") + dvs.servers[0].runcmd("ip address add 10.0.0.1/31 dev eth0") + dvs.servers[0].runcmd("ip route add default via 10.0.0.0") + dvs.servers[0].runcmd("ping -c 1 10.0.0.1") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 1.1.1.0/24 10.0.0.1\"") + + self.create_l3_intf("Ethernet4", "") + self.set_admin_status("Ethernet4", "up") + self.add_ip_address("Ethernet4", "2001::1/64") + dvs.runcmd("sysctl -w net.ipv6.conf.all.forwarding=1") + dvs.servers[1].runcmd("ip -6 address add 2001::2/64 dev eth0") + dvs.servers[1].runcmd("ip -6 route add default via 2001::1") + time.sleep(2) + dvs.servers[1].runcmd("ping -6 -c 1 2001::1") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ipv6 route 2000::/64 2001::2\"") + def post_rif_counter_test(self, meta_data): self.config_db.db_connection.hdel('INTERFACE|Ethernet0|192.168.0.1/24', "NULL") @@ -243,7 +284,7 @@ def post_trap_flow_counter_test(self, meta_data): 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') + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map'], 'disable') for counter_entry in counters_keys.items(): self.wait_for_id_list_remove(meta_data['group_name'], counter_entry[0], counter_entry[1]) @@ -260,6 +301,46 @@ def post_acl_tunnel_counter_test(self, meta_data): self.config_db.delete_entry('ACL_RULE', 'DATAACL|RULE0') self.config_db.delete_entry('ACL_TABLE', 'DATAACL') + def post_route_flow_counter_test(self, meta_data): + dvs = meta_data['dvs'] + # Verify prefix to route pattern name map + self.wait_for_table(ROUTE_TO_PATTERN_MAP) + + # Remove route pattern and verify related couters are removed + v4_name_map_key = '1.1.1.0/24' + counter_oid = self.counters_db.db_connection.hget(meta_data['name_map'], v4_name_map_key) + assert counter_oid + self.config_db.delete_entry('FLOW_COUNTER_ROUTE_PATTERN', '1.1.0.0/16') + self.wait_for_id_list_remove(meta_data['group_name'], v4_name_map_key, counter_oid) + counter_oid = self.counters_db.db_connection.hget(meta_data['name_map'], v4_name_map_key) + assert not counter_oid + route_pattern = self.counters_db.db_connection.hget(ROUTE_TO_PATTERN_MAP, v4_name_map_key) + assert not route_pattern + + # Disable route flow counter and verify all counters are removed + v6_name_map_key = '2000::/64' + counter_oid = self.counters_db.db_connection.hget(meta_data['name_map'], v6_name_map_key) + assert counter_oid + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map'], 'disable') + self.wait_for_id_list_remove(meta_data['group_name'], v6_name_map_key, counter_oid) + self.wait_for_table_empty(meta_data['name_map']) + self.wait_for_table_empty(ROUTE_TO_PATTERN_MAP) + + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route {} 10.0.0.1\"".format(v4_name_map_key)) + self.remove_ip_address("Ethernet0", "10.0.0.0/31") + self.remove_l3_intf("Ethernet0") + self.set_admin_status("Ethernet0", "down") + dvs.servers[0].runcmd("ip route del default dev eth0") + dvs.servers[0].runcmd("ip address del 10.0.0.1/31 dev eth0") + + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ipv6 route 2000::/64 2001::2\"") + self.remove_ip_address("Ethernet4", "2001::1/64") + self.remove_l3_intf("Ethernet4") + self.set_admin_status("Ethernet4", "down") + dvs.servers[1].runcmd("ip -6 route del default dev eth0") + 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 test_add_remove_trap(self, dvs): """Test steps: 1. Enable trap_flow_counter @@ -322,7 +403,7 @@ def test_add_remove_trap(self, dvs): 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') + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map'], 'disable') def test_remove_trap_group(self, dvs): """Remove trap group and verify that all related trap counters are removed @@ -373,7 +454,244 @@ def test_remove_trap_group(self, dvs): for trap_id in trap_ids: assert trap_id not in counters_keys + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map'], 'disable') + + def test_update_route_pattern(self, dvs): + self.setup_dbs(dvs) + self.config_db.create_entry('FLOW_COUNTER_ROUTE_PATTERN', '1.1.0.0/16', + { + 'max_match_count': '30' + } + ) + self.create_l3_intf("Ethernet0", "") + self.create_l3_intf("Ethernet4", "") + self.add_ip_address("Ethernet0", "10.0.0.0/31") + self.add_ip_address("Ethernet4", "10.0.0.2/31") + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") + # set ip address and default route + dvs.servers[0].runcmd("ip address add 10.0.0.1/31 dev eth0") + dvs.servers[0].runcmd("ip route add default via 10.0.0.0") + dvs.servers[1].runcmd("ip address add 10.0.0.3/31 dev eth0") + dvs.servers[1].runcmd("ip route add default via 10.0.0.2") + dvs.servers[0].runcmd("ping -c 1 10.0.0.3") + + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 1.1.1.0/24 10.0.0.1\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 2.2.2.0/24 10.0.0.3\"") + + meta_data = counter_group_meta['route_flow_counter'] + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map']) + self.wait_for_table(meta_data['name_map']) + self.wait_for_table(ROUTE_TO_PATTERN_MAP) + counter_oid = self.counters_db.db_connection.hget(meta_data['name_map'], '1.1.1.0/24') + self.wait_for_id_list(meta_data['group_name'], '1.1.1.0/24', counter_oid) + assert not self.counters_db.db_connection.hget(meta_data['name_map'], '2.2.2.0/24') + assert not self.counters_db.db_connection.hget(ROUTE_TO_PATTERN_MAP, '2.2.2.0/24') + + self.config_db.delete_entry('FLOW_COUNTER_ROUTE_PATTERN', '1.1.0.0/16') + self.wait_for_id_list_remove(meta_data['group_name'], '1.1.1.0/24', counter_oid) + self.wait_for_table_empty(meta_data['name_map']) + self.wait_for_table_empty(ROUTE_TO_PATTERN_MAP) + assert not self.counters_db.db_connection.hget(meta_data['name_map'], '1.1.1.0/24') + assert not self.counters_db.db_connection.hget(ROUTE_TO_PATTERN_MAP, '1.1.1.0/24') + + self.config_db.create_entry('FLOW_COUNTER_ROUTE_PATTERN', '2.2.0.0/16', + { + 'max_match_count': '30' + } + ) + self.wait_for_table(meta_data['name_map']) + self.wait_for_table(ROUTE_TO_PATTERN_MAP) + counter_oid = self.counters_db.db_connection.hget(meta_data['name_map'], '2.2.2.0/24') + self.wait_for_id_list(meta_data['group_name'], '2.2.2.0/24', counter_oid) + + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map'], 'disable') + self.wait_for_id_list_remove(meta_data['group_name'], '2.2.2.0/24', counter_oid) + self.wait_for_table_empty(meta_data['name_map']) + self.wait_for_table_empty(ROUTE_TO_PATTERN_MAP) + + self.config_db.delete_entry('FLOW_COUNTER_ROUTE_PATTERN', '2.2.0.0/16') + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route {} 10.0.0.1\"".format('1.1.1.0/24')) + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route {} 10.0.0.3\"".format('2.2.2.0/24')) + + # remove ip address + self.remove_ip_address("Ethernet0", "10.0.0.0/31") + self.remove_ip_address("Ethernet4", "10.0.0.2/31") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + self.remove_l3_intf("Ethernet4") + + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + + # remove ip address and default route + dvs.servers[0].runcmd("ip route del default dev eth0") + dvs.servers[0].runcmd("ip address del 10.0.0.1/31 dev eth0") + + dvs.servers[1].runcmd("ip route del default dev eth0") + dvs.servers[1].runcmd("ip address del 10.0.0.3/31 dev eth0") + + def test_add_remove_route_flow_counter(self, dvs): + self.setup_dbs(dvs) + self.config_db.create_entry('FLOW_COUNTER_ROUTE_PATTERN', '1.1.0.0/16', + { + 'max_match_count': '30' + } + ) + meta_data = counter_group_meta['route_flow_counter'] + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map'], check_name_map=False) + + self.create_l3_intf("Ethernet0", "") + self.add_ip_address("Ethernet0", "10.0.0.0/31") + self.set_admin_status("Ethernet0", "up") + dvs.servers[0].runcmd("ip address add 10.0.0.1/31 dev eth0") + dvs.servers[0].runcmd("ip route add default via 10.0.0.0") + dvs.servers[0].runcmd("ping -c 1 10.0.0.1") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 1.1.1.0/24 10.0.0.1\"") + + self.wait_for_table(meta_data['name_map']) + self.wait_for_table(ROUTE_TO_PATTERN_MAP) + counter_oid = self.counters_db.db_connection.hget(meta_data['name_map'], '1.1.1.0/24') + self.wait_for_id_list(meta_data['group_name'], '1.1.1.0/24', counter_oid) + + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route {} 10.0.0.1\"".format('1.1.1.0/24')) + self.wait_for_id_list_remove(meta_data['group_name'], '1.1.1.0/24', counter_oid) + self.wait_for_table_empty(meta_data['name_map']) + self.wait_for_table_empty(ROUTE_TO_PATTERN_MAP) + + self.config_db.delete_entry('FLOW_COUNTER_ROUTE_PATTERN', '1.1.0.0/16') self.set_flex_counter_group_status(meta_data['key'], meta_data['group_name'], 'disable') + + # remove ip address + self.remove_ip_address("Ethernet0", "10.0.0.0/31") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + + self.set_admin_status("Ethernet0", "down") + + # remove ip address and default route + dvs.servers[0].runcmd("ip route del default dev eth0") + dvs.servers[0].runcmd("ip address del 10.0.0.1/31 dev eth0") + + def test_router_flow_counter_max_match_count(self, dvs): + self.setup_dbs(dvs) + self.config_db.create_entry('FLOW_COUNTER_ROUTE_PATTERN', '1.1.0.0/16', + { + 'max_match_count': '1' + } + ) + meta_data = counter_group_meta['route_flow_counter'] + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map'], check_name_map=False) + self.create_l3_intf("Ethernet0", "") + self.create_l3_intf("Ethernet4", "") + self.add_ip_address("Ethernet0", "10.0.0.0/31") + self.add_ip_address("Ethernet4", "10.0.0.2/31") + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") + # set ip address and default route + dvs.servers[0].runcmd("ip address add 10.0.0.1/31 dev eth0") + dvs.servers[0].runcmd("ip route add default via 10.0.0.0") + dvs.servers[1].runcmd("ip address add 10.0.0.3/31 dev eth0") + dvs.servers[1].runcmd("ip route add default via 10.0.0.2") + dvs.servers[0].runcmd("ping -c 1 10.0.0.3") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 1.1.1.0/24 10.0.0.1\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 1.1.2.0/24 10.0.0.3\"") + + self.wait_for_table(meta_data['name_map']) + self.wait_for_table(ROUTE_TO_PATTERN_MAP) + counter_oid = self.counters_db.db_connection.hget(meta_data['name_map'], '1.1.1.0/24') + self.wait_for_id_list(meta_data['group_name'], '1.1.1.0/24', counter_oid) + assert not self.counters_db.db_connection.hget(meta_data['name_map'], '1.1.2.0/24') + self.config_db.update_entry('FLOW_COUNTER_ROUTE_PATTERN', '1.1.0.0/16', + { + 'max_match_count': '2' + } + ) + for _ in range(NUMBER_OF_RETRIES): + counter_oid = self.counters_db.db_connection.hget(meta_data['name_map'], '1.1.2.0/24') + if not counter_oid: + time.sleep(1) + else: + break + assert counter_oid + self.wait_for_id_list(meta_data['group_name'], '1.1.2.0/24', counter_oid) + + self.config_db.update_entry('FLOW_COUNTER_ROUTE_PATTERN', '1.1.0.0/16', + { + 'max_match_count': '1' + } + ) + + for _ in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + if len(counters_keys) == 1: + break + else: + time.sleep(1) + + assert len(counters_keys) == 1 + + to_remove = '1.1.2.0/24' if '1.1.2.0/24' in counters_keys else '1.1.1.0/24' + to_remove_nexthop = '10.0.0.3' if '1.1.2.0/24' in counters_keys else '10.0.0.1' + to_bound = '1.1.2.0/24' if '1.1.1.0/24' == to_remove else '1.1.1.0/24' + to_bound_nexthop = '10.0.0.1' if '1.1.2.0/24' in counters_keys else '10.0.0.3' + + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route {} {}\"".format(to_remove, to_remove_nexthop)) + for _ in range(NUMBER_OF_RETRIES): + counter_oid = self.counters_db.db_connection.hget(meta_data['name_map'], to_bound) + if not counter_oid: + time.sleep(1) + else: + break + assert counter_oid + self.wait_for_id_list(meta_data['group_name'], to_bound, counter_oid) + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + assert to_remove not in counters_keys + assert to_bound in counters_keys + counters_keys = self.counters_db.db_connection.hgetall(ROUTE_TO_PATTERN_MAP) + assert to_remove not in counters_keys + assert to_bound in counters_keys + + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route {} {}\"".format(to_bound, to_bound_nexthop)) + + # remove ip address + self.remove_ip_address("Ethernet0", "10.0.0.0/31") + self.remove_ip_address("Ethernet4", "10.0.0.2/31") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + self.remove_l3_intf("Ethernet4") + + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + + # remove ip address and default route + dvs.servers[0].runcmd("ip route del default dev eth0") + dvs.servers[0].runcmd("ip address del 10.0.0.1/31 dev eth0") + + dvs.servers[1].runcmd("ip route del default dev eth0") + dvs.servers[1].runcmd("ip address del 10.0.0.3/31 dev eth0") + self.config_db.delete_entry('FLOW_COUNTER_ROUTE_PATTERN', '1.1.0.0/16') + + def create_l3_intf(self, interface, vrf_name): + if len(vrf_name) == 0: + self.config_db.create_entry("INTERFACE", interface, {"NULL": "NULL"}) + else: + self.config_db.create_entry("INTERFACE", interface, {"vrf_name": vrf_name}) + + def remove_l3_intf(self, interface): + self.config_db.delete_entry("INTERFACE", interface) + + def add_ip_address(self, interface, ip): + self.config_db.create_entry("INTERFACE", interface + "|" + ip, {"NULL": "NULL"}) + + def remove_ip_address(self, interface, ip): + self.config_db.delete_entry("INTERFACE", interface + "|" + ip) + + def set_admin_status(self, interface, status): + self.config_db.update_entry("PORT", interface, {"admin_status": status}) def test_add_remove_ports(self, dvs): self.setup_dbs(dvs)