From 9eb44220819acca2fb8d4740443aee53f1f77061 Mon Sep 17 00:00:00 2001 From: mint570 <70396898+mint570@users.noreply.github.com> Date: Thu, 18 Aug 2022 10:14:52 -0700 Subject: [PATCH] Upstream new development on p4orch (#2237) * Upstream new development on p4orch: 1. New actions in route manager: trap, set metadata. 2. New match attribute in ACL manager: ROUTE_DST_USER_META. 3. Bulk SAI call in route manager. 4. Added l3 admin manager. 5. Added GRE tunnel manager. 6. P4orch state verification for internal cache and ASIC DB (place holder). 7. Bug fixes and code enhancements. Co-authored-by: Runming Wu [runmingwu@google.com](mailto:runmingwu@google.com) --- orchagent/Makefile.am | 4 +- orchagent/p4orch/acl_rule_manager.cpp | 800 ++++- orchagent/p4orch/acl_rule_manager.h | 9 +- orchagent/p4orch/acl_table_manager.cpp | 668 +++- orchagent/p4orch/acl_table_manager.h | 19 +- orchagent/p4orch/acl_util.cpp | 105 +- orchagent/p4orch/acl_util.h | 111 +- orchagent/p4orch/gre_tunnel_manager.cpp | 619 ++++ orchagent/p4orch/gre_tunnel_manager.h | 111 + orchagent/p4orch/l3_admit_manager.cpp | 460 +++ orchagent/p4orch/l3_admit_manager.h | 98 + orchagent/p4orch/mirror_session_manager.cpp | 295 +- orchagent/p4orch/mirror_session_manager.h | 7 + orchagent/p4orch/neighbor_manager.cpp | 218 +- orchagent/p4orch/neighbor_manager.h | 4 + orchagent/p4orch/next_hop_manager.cpp | 364 +- orchagent/p4orch/next_hop_manager.h | 16 +- orchagent/p4orch/object_manager_interface.h | 3 + orchagent/p4orch/p4oidmapper.cpp | 51 +- orchagent/p4orch/p4oidmapper.h | 14 +- orchagent/p4orch/p4orch.cpp | 13 + orchagent/p4orch/p4orch.h | 5 + orchagent/p4orch/p4orch_util.cpp | 79 + orchagent/p4orch/p4orch_util.h | 64 +- orchagent/p4orch/route_manager.cpp | 1071 ++++-- orchagent/p4orch/route_manager.h | 95 +- orchagent/p4orch/router_interface_manager.cpp | 244 +- orchagent/p4orch/router_interface_manager.h | 4 + orchagent/p4orch/tests/Makefile.am | 5 + orchagent/p4orch/tests/acl_manager_test.cpp | 1126 +++++- orchagent/p4orch/tests/fake_dbconnector.cpp | 28 + .../p4orch/tests/gre_tunnel_manager_test.cpp | 902 +++++ .../p4orch/tests/l3_admit_manager_test.cpp | 653 ++++ .../tests/mirror_session_manager_test.cpp | 200 +- orchagent/p4orch/tests/mock_sai_my_mac.h | 31 + .../tests/mock_sai_router_interface.cpp | 26 + .../p4orch/tests/mock_sai_router_interface.h | 22 +- orchagent/p4orch/tests/mock_sai_tunnel.h | 31 + .../p4orch/tests/neighbor_manager_test.cpp | 122 +- .../p4orch/tests/next_hop_manager_test.cpp | 454 ++- orchagent/p4orch/tests/p4oidmapper_test.cpp | 38 +- orchagent/p4orch/tests/p4orch_util_test.cpp | 53 + orchagent/p4orch/tests/return_code_test.cpp | 38 + orchagent/p4orch/tests/route_manager_test.cpp | 3114 +++++++++++++---- .../tests/router_interface_manager_test.cpp | 129 +- orchagent/p4orch/tests/wcmp_manager_test.cpp | 201 +- orchagent/p4orch/wcmp_manager.cpp | 270 +- orchagent/p4orch/wcmp_manager.h | 11 + orchagent/return_code.h | 43 +- orchagent/saihelper.cpp | 3 + orchagent/switchorch.cpp | 29 +- orchagent/switchorch.h | 8 +- tests/gcov_support.sh | 1 + tests/mock_tests/Makefile.am | 4 +- tests/mock_tests/mock_dbconnector.cpp | 2 + tests/p4rt/acl.py | 3 + tests/p4rt/l3.py | 374 +- tests/p4rt/l3_admit.py | 84 + tests/p4rt/test_l3.py | 2186 +++++++++--- tests/p4rt/test_l3_admit.py | 263 ++ tests/p4rt/test_p4rt_acl.py | 219 +- tests/p4rt/test_p4rt_mirror.py | 137 +- tests/p4rt/util.py | 6 +- 63 files changed, 14015 insertions(+), 2352 deletions(-) create mode 100644 orchagent/p4orch/gre_tunnel_manager.cpp create mode 100644 orchagent/p4orch/gre_tunnel_manager.h create mode 100644 orchagent/p4orch/l3_admit_manager.cpp create mode 100644 orchagent/p4orch/l3_admit_manager.h create mode 100644 orchagent/p4orch/tests/gre_tunnel_manager_test.cpp create mode 100644 orchagent/p4orch/tests/l3_admit_manager_test.cpp create mode 100644 orchagent/p4orch/tests/mock_sai_my_mac.h create mode 100644 orchagent/p4orch/tests/mock_sai_router_interface.cpp create mode 100644 orchagent/p4orch/tests/mock_sai_tunnel.h create mode 100644 tests/p4rt/l3_admit.py create mode 100644 tests/p4rt/test_l3_admit.py diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 5755b1446ec5..d39e73d7376a 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -106,6 +106,7 @@ orchagent_SOURCES += p4orch/p4orch.cpp \ p4orch/p4orch_util.cpp \ p4orch/p4oidmapper.cpp \ p4orch/router_interface_manager.cpp \ + p4orch/gre_tunnel_manager.cpp \ p4orch/neighbor_manager.cpp \ p4orch/next_hop_manager.cpp \ p4orch/route_manager.cpp \ @@ -113,7 +114,8 @@ orchagent_SOURCES += p4orch/p4orch.cpp \ p4orch/acl_table_manager.cpp \ p4orch/acl_rule_manager.cpp \ p4orch/wcmp_manager.cpp \ - p4orch/mirror_session_manager.cpp + p4orch/mirror_session_manager.cpp \ + p4orch/l3_admit_manager.cpp orchagent_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) orchagent_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) diff --git a/orchagent/p4orch/acl_rule_manager.cpp b/orchagent/p4orch/acl_rule_manager.cpp index 3984fd956f7c..fb73cb012832 100644 --- a/orchagent/p4orch/acl_rule_manager.cpp +++ b/orchagent/p4orch/acl_rule_manager.cpp @@ -4,8 +4,11 @@ #include #include +#include "SaiAttributeList.h" #include "converter.h" #include "crmorch.h" +#include "dbconnector.h" +#include "intfsorch.h" #include "json.hpp" #include "logger.h" #include "orch.h" @@ -13,12 +16,15 @@ #include "p4orch/p4orch_util.h" #include "portsorch.h" #include "sai_serialize.h" +#include "table.h" #include "tokenize.h" extern "C" { #include "sai.h" } +using ::p4orch::kTableKeyDelimiter; + extern sai_object_id_t gSwitchId; extern sai_acl_api_t *sai_acl_api; extern sai_policer_api_t *sai_policer_api; @@ -37,6 +43,126 @@ const std::string concatTableNameAndRuleKey(const std::string &table_name, const return table_name + kTableKeyDelimiter + rule_key; } +std::vector getRuleSaiAttrs(const P4AclRule &acl_rule) +{ + std::vector acl_entry_attrs; + sai_attribute_t acl_entry_attr; + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_TABLE_ID; + acl_entry_attr.value.oid = acl_rule.acl_table_oid; + acl_entry_attrs.push_back(acl_entry_attr); + + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_PRIORITY; + acl_entry_attr.value.u32 = acl_rule.priority; + acl_entry_attrs.push_back(acl_entry_attr); + + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ADMIN_STATE; + acl_entry_attr.value.booldata = true; + acl_entry_attrs.push_back(acl_entry_attr); + + // Add matches + for (const auto &match_fv : acl_rule.match_fvs) + { + acl_entry_attr.id = fvField(match_fv); + acl_entry_attr.value = fvValue(match_fv); + acl_entry_attrs.push_back(acl_entry_attr); + } + + // Add actions + for (const auto &action_fv : acl_rule.action_fvs) + { + acl_entry_attr.id = fvField(action_fv); + acl_entry_attr.value = fvValue(action_fv); + acl_entry_attrs.push_back(acl_entry_attr); + } + + // Add meter + if (acl_rule.meter.enabled) + { + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ACTION_SET_POLICER; + acl_entry_attr.value.aclaction.parameter.oid = acl_rule.meter.meter_oid; + acl_entry_attr.value.aclaction.enable = true; + acl_entry_attrs.push_back(acl_entry_attr); + } + + // Add counter + if (acl_rule.counter.packets_enabled || acl_rule.counter.bytes_enabled) + { + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ACTION_COUNTER; + acl_entry_attr.value.aclaction.enable = true; + acl_entry_attr.value.aclaction.parameter.oid = acl_rule.counter.counter_oid; + acl_entry_attrs.push_back(acl_entry_attr); + } + + return acl_entry_attrs; +} + +std::vector getCounterSaiAttrs(const P4AclRule &acl_rule) +{ + sai_attribute_t attr; + std::vector counter_attrs; + attr.id = SAI_ACL_COUNTER_ATTR_TABLE_ID; + attr.value.oid = acl_rule.acl_table_oid; + counter_attrs.push_back(attr); + + if (acl_rule.counter.bytes_enabled) + { + attr.id = SAI_ACL_COUNTER_ATTR_ENABLE_BYTE_COUNT; + attr.value.booldata = true; + counter_attrs.push_back(attr); + } + + if (acl_rule.counter.packets_enabled) + { + attr.id = SAI_ACL_COUNTER_ATTR_ENABLE_PACKET_COUNT; + attr.value.booldata = true; + counter_attrs.push_back(attr); + } + + return counter_attrs; +} + +std::vector getMeterSaiAttrs(const P4AclMeter &p4_acl_meter) +{ + std::vector meter_attrs; + sai_attribute_t meter_attr; + + meter_attr.id = SAI_POLICER_ATTR_MODE; + meter_attr.value.s32 = p4_acl_meter.mode; + meter_attrs.push_back(meter_attr); + + if (p4_acl_meter.enabled) + { + meter_attr.id = SAI_POLICER_ATTR_METER_TYPE; + meter_attr.value.s32 = p4_acl_meter.type; + meter_attrs.push_back(meter_attr); + + meter_attr.id = SAI_POLICER_ATTR_CBS; + meter_attr.value.u64 = p4_acl_meter.cburst; + meter_attrs.push_back(meter_attr); + + meter_attr.id = SAI_POLICER_ATTR_CIR; + meter_attr.value.u64 = p4_acl_meter.cir; + meter_attrs.push_back(meter_attr); + + meter_attr.id = SAI_POLICER_ATTR_PIR; + meter_attr.value.u64 = p4_acl_meter.pir; + meter_attrs.push_back(meter_attr); + + meter_attr.id = SAI_POLICER_ATTR_PBS; + meter_attr.value.u64 = p4_acl_meter.pburst; + meter_attrs.push_back(meter_attr); + } + + for (const auto &packet_color_action : p4_acl_meter.packet_color_actions) + { + meter_attr.id = fvField(packet_color_action); + meter_attr.value.s32 = fvValue(packet_color_action); + meter_attrs.push_back(meter_attr); + } + + return meter_attrs; +} + } // namespace void AclRuleManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) @@ -232,43 +358,18 @@ void AclRuleManager::doAclCounterStatsTask() } ReturnCode AclRuleManager::createAclCounter(const std::string &acl_table_name, const std::string &counter_key, - const P4AclCounter &p4_acl_counter, sai_object_id_t *counter_oid) + const P4AclRule &acl_rule, sai_object_id_t *counter_oid) { SWSS_LOG_ENTER(); - sai_attribute_t attr; - std::vector counter_attrs; - sai_object_id_t acl_table_oid; - if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name, &acl_table_oid)) - { - LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) - << "Invalid ACL counter to create: ACL table key " << QuotedVar(acl_table_name) - << " not found."); - } - attr.id = SAI_ACL_COUNTER_ATTR_TABLE_ID; - attr.value.oid = acl_table_oid; - counter_attrs.push_back(attr); - - if (p4_acl_counter.bytes_enabled) - { - attr.id = SAI_ACL_COUNTER_ATTR_ENABLE_BYTE_COUNT; - attr.value.booldata = true; - counter_attrs.push_back(attr); - } - - if (p4_acl_counter.packets_enabled) - { - attr.id = SAI_ACL_COUNTER_ATTR_ENABLE_PACKET_COUNT; - attr.value.booldata = true; - counter_attrs.push_back(attr); - } + auto attrs = getCounterSaiAttrs(acl_rule); CHECK_ERROR_AND_LOG_AND_RETURN( - sai_acl_api->create_acl_counter(counter_oid, gSwitchId, (uint32_t)counter_attrs.size(), counter_attrs.data()), - "Faied to create counter for the rule in table " << sai_serialize_object_id(acl_table_oid)); + sai_acl_api->create_acl_counter(counter_oid, gSwitchId, (uint32_t)attrs.size(), attrs.data()), + "Faied to create counter for the rule in table " << sai_serialize_object_id(acl_rule.acl_table_oid)); SWSS_LOG_NOTICE("Suceeded to create ACL counter %s ", sai_serialize_object_id(*counter_oid).c_str()); m_p4OidMapper->setOID(SAI_OBJECT_TYPE_ACL_COUNTER, counter_key, *counter_oid); - gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, acl_table_oid); + gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, acl_rule.acl_table_oid); m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name); return ReturnCode(); } @@ -305,44 +406,10 @@ ReturnCode AclRuleManager::createAclMeter(const P4AclMeter &p4_acl_meter, const { SWSS_LOG_ENTER(); - std::vector meter_attrs; - sai_attribute_t meter_attr; - meter_attr.id = SAI_POLICER_ATTR_METER_TYPE; - meter_attr.value.s32 = p4_acl_meter.type; - meter_attrs.push_back(meter_attr); - - meter_attr.id = SAI_POLICER_ATTR_MODE; - meter_attr.value.s32 = p4_acl_meter.mode; - meter_attrs.push_back(meter_attr); - - if (p4_acl_meter.enabled) - { - meter_attr.id = SAI_POLICER_ATTR_CBS; - meter_attr.value.u64 = p4_acl_meter.cburst; - meter_attrs.push_back(meter_attr); - - meter_attr.id = SAI_POLICER_ATTR_CIR; - meter_attr.value.u64 = p4_acl_meter.cir; - meter_attrs.push_back(meter_attr); - - meter_attr.id = SAI_POLICER_ATTR_PIR; - meter_attr.value.u64 = p4_acl_meter.pir; - meter_attrs.push_back(meter_attr); - - meter_attr.id = SAI_POLICER_ATTR_PBS; - meter_attr.value.u64 = p4_acl_meter.pburst; - meter_attrs.push_back(meter_attr); - } - - for (const auto &packet_color_action : p4_acl_meter.packet_color_actions) - { - meter_attr.id = fvField(packet_color_action); - meter_attr.value.s32 = fvValue(packet_color_action); - meter_attrs.push_back(meter_attr); - } + auto attrs = getMeterSaiAttrs(p4_acl_meter); CHECK_ERROR_AND_LOG_AND_RETURN( - sai_policer_api->create_policer(meter_oid, gSwitchId, (uint32_t)meter_attrs.size(), meter_attrs.data()), + sai_policer_api->create_policer(meter_oid, gSwitchId, (uint32_t)attrs.size(), attrs.data()), "Failed to create ACL meter"); m_p4OidMapper->setOID(SAI_OBJECT_TYPE_POLICER, meter_key, *meter_oid); SWSS_LOG_NOTICE("Suceeded to create ACL meter %s ", sai_serialize_object_id(*meter_oid).c_str()); @@ -645,39 +712,37 @@ ReturnCode AclRuleManager::setAclRuleCounterStats(const P4AclRule &acl_rule) std::to_string(meter_stats[i])}); } } - else + // Query general packets/bytes stats by ACL counter object id. + std::vector counter_attrs; + sai_attribute_t counter_attr; + if (acl_rule.counter.packets_enabled) { - // Query general packets/bytes stats by ACL counter object id. - std::vector counter_attrs; - sai_attribute_t counter_attr; - if (acl_rule.counter.packets_enabled) - { - counter_attr.id = SAI_ACL_COUNTER_ATTR_PACKETS; - counter_attrs.push_back(counter_attr); - } - if (acl_rule.counter.bytes_enabled) + counter_attr.id = SAI_ACL_COUNTER_ATTR_PACKETS; + counter_attrs.push_back(counter_attr); + } + if (acl_rule.counter.bytes_enabled) + { + counter_attr.id = SAI_ACL_COUNTER_ATTR_BYTES; + counter_attrs.push_back(counter_attr); + } + CHECK_ERROR_AND_LOG_AND_RETURN(sai_acl_api->get_acl_counter_attribute(acl_rule.counter.counter_oid, + static_cast(counter_attrs.size()), + counter_attrs.data()), + "Failed to get counters stats for " << QuotedVar(acl_rule.acl_table_name)); + for (const auto &counter_attr : counter_attrs) + { + if (counter_attr.id == SAI_ACL_COUNTER_ATTR_PACKETS) { - counter_attr.id = SAI_ACL_COUNTER_ATTR_BYTES; - counter_attrs.push_back(counter_attr); + counter_stats_values.push_back( + swss::FieldValueTuple{P4_COUNTER_STATS_PACKETS, std::to_string(counter_attr.value.u64)}); } - CHECK_ERROR_AND_LOG_AND_RETURN( - sai_acl_api->get_acl_counter_attribute(acl_rule.counter.counter_oid, - static_cast(counter_attrs.size()), counter_attrs.data()), - "Failed to get counters stats for " << QuotedVar(acl_rule.acl_table_name)); - for (const auto &counter_attr : counter_attrs) + if (counter_attr.id == SAI_ACL_COUNTER_ATTR_BYTES) { - if (counter_attr.id == SAI_ACL_COUNTER_ATTR_PACKETS) - { - counter_stats_values.push_back( - swss::FieldValueTuple{P4_COUNTER_STATS_PACKETS, std::to_string(counter_attr.value.u64)}); - } - if (counter_attr.id == SAI_ACL_COUNTER_ATTR_BYTES) - { - counter_stats_values.push_back( - swss::FieldValueTuple{P4_COUNTER_STATS_BYTES, std::to_string(counter_attr.value.u64)}); - } + counter_stats_values.push_back( + swss::FieldValueTuple{P4_COUNTER_STATS_BYTES, std::to_string(counter_attr.value.u64)}); } } + // Set field value tuples for counters stats in COUNTERS_DB m_countersTable->set(acl_rule.db_key, counter_stats_values); return ReturnCode(); @@ -916,6 +981,7 @@ ReturnCode AclRuleManager::setMatchValue(const acl_entry_attr_union_t attr_name, break; } case SAI_ACL_ENTRY_ATTR_FIELD_TUNNEL_VNI: + case SAI_ACL_ENTRY_ATTR_FIELD_ROUTE_DST_USER_META: case SAI_ACL_ENTRY_ATTR_FIELD_IPV6_FLOW_LABEL: { const std::vector &value_and_mask = tokenize(attr_value, kDataMaskDelimiter); value->aclfield.data.u32 = to_uint(trim(value_and_mask[0])); @@ -1402,41 +1468,26 @@ ReturnCode AclRuleManager::setMeterValue(const P4AclTableDefinition *acl_table, { acl_meter.packet_color_actions = action_color_it->second; } + + // SAI_POLICER_MODE_TR_TCM mode is used by default. + // Meter rate limit config is not present for the ACL rule + // Mark the packet as GREEN by setting rate limit to max. + if (!acl_meter.packet_color_actions.empty() && !acl_meter.enabled) + { + acl_meter.enabled = true; + acl_meter.type = SAI_METER_TYPE_PACKETS; + acl_meter.cburst = 0x7fffffff; + acl_meter.cir = 0x7fffffff; + acl_meter.pir = 0x7fffffff; + acl_meter.pburst = 0x7fffffff; + } + return ReturnCode(); } ReturnCode AclRuleManager::createAclRule(P4AclRule &acl_rule) { SWSS_LOG_ENTER(); - std::vector acl_entry_attrs; - sai_attribute_t acl_entry_attr; - acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_TABLE_ID; - acl_entry_attr.value.oid = acl_rule.acl_table_oid; - acl_entry_attrs.push_back(acl_entry_attr); - - acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_PRIORITY; - acl_entry_attr.value.u32 = acl_rule.priority; - acl_entry_attrs.push_back(acl_entry_attr); - - acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ADMIN_STATE; - acl_entry_attr.value.booldata = true; - acl_entry_attrs.push_back(acl_entry_attr); - - // Add matches - for (const auto &match_fv : acl_rule.match_fvs) - { - acl_entry_attr.id = fvField(match_fv); - acl_entry_attr.value = fvValue(match_fv); - acl_entry_attrs.push_back(acl_entry_attr); - } - - // Add actions - for (const auto &action_fv : acl_rule.action_fvs) - { - acl_entry_attr.id = fvField(action_fv); - acl_entry_attr.value = fvValue(action_fv); - acl_entry_attrs.push_back(acl_entry_attr); - } // Track if the entry creats a new counter or meter bool created_meter = false; @@ -1444,7 +1495,7 @@ ReturnCode AclRuleManager::createAclRule(P4AclRule &acl_rule) const auto &table_name_and_rule_key = concatTableNameAndRuleKey(acl_rule.acl_table_name, acl_rule.acl_rule_key); // Add meter - if (acl_rule.meter.enabled || !acl_rule.meter.packet_color_actions.empty()) + if (acl_rule.meter.enabled) { if (acl_rule.meter.meter_oid == SAI_NULL_OBJECT_ID) { @@ -1456,10 +1507,6 @@ ReturnCode AclRuleManager::createAclRule(P4AclRule &acl_rule) } created_meter = true; } - acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ACTION_SET_POLICER; - acl_entry_attr.value.aclaction.parameter.oid = acl_rule.meter.meter_oid; - acl_entry_attr.value.aclaction.enable = true; - acl_entry_attrs.push_back(acl_entry_attr); } // Add counter @@ -1467,7 +1514,7 @@ ReturnCode AclRuleManager::createAclRule(P4AclRule &acl_rule) { if (acl_rule.counter.counter_oid == SAI_NULL_OBJECT_ID) { - auto status = createAclCounter(acl_rule.acl_table_name, table_name_and_rule_key, acl_rule.counter, + auto status = createAclCounter(acl_rule.acl_table_name, table_name_and_rule_key, acl_rule, &acl_rule.counter.counter_oid); if (!status.ok()) { @@ -1484,14 +1531,12 @@ ReturnCode AclRuleManager::createAclRule(P4AclRule &acl_rule) } created_counter = true; } - acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ACTION_COUNTER; - acl_entry_attr.value.aclaction.enable = true; - acl_entry_attr.value.aclaction.parameter.oid = acl_rule.counter.counter_oid; - acl_entry_attrs.push_back(acl_entry_attr); } - auto sai_status = sai_acl_api->create_acl_entry(&acl_rule.acl_entry_oid, gSwitchId, - (uint32_t)acl_entry_attrs.size(), acl_entry_attrs.data()); + auto attrs = getRuleSaiAttrs(acl_rule); + + auto sai_status = + sai_acl_api->create_acl_entry(&acl_rule.acl_entry_oid, gSwitchId, (uint32_t)attrs.size(), attrs.data()); if (sai_status != SAI_STATUS_SUCCESS) { ReturnCode status = ReturnCode(sai_status) @@ -1665,7 +1710,7 @@ ReturnCode AclRuleManager::removeAclRule(const std::string &acl_table_name, cons << sai_serialize_object_id(acl_rule->acl_entry_oid) << " in table " << QuotedVar(acl_table_name)); bool deleted_meter = false; - if (acl_rule->meter.enabled || !acl_rule->meter.packet_color_actions.empty()) + if (acl_rule->meter.enabled) { m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); auto status = removeAclMeter(table_name_and_rule_key); @@ -1750,7 +1795,7 @@ ReturnCode AclRuleManager::removeAclRule(const std::string &acl_table_name, cons ReturnCode AclRuleManager::processAddRuleRequest(const std::string &acl_rule_key, const P4AclRuleAppDbEntry &app_db_entry) { - P4AclRule acl_rule; + P4AclRule acl_rule{}; acl_rule.priority = app_db_entry.priority; acl_rule.acl_rule_key = acl_rule_key; acl_rule.p4_action = app_db_entry.action; @@ -1832,7 +1877,6 @@ ReturnCode AclRuleManager::processAddRuleRequest(const std::string &acl_rule_key gPortsOrch->increasePortRefCount(port_alias); } gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, acl_rule.acl_table_oid); - m_aclRuleTables[acl_rule.acl_table_name][acl_rule.acl_rule_key] = acl_rule; m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, acl_rule.acl_table_name); const auto &table_name_and_rule_key = concatTableNameAndRuleKey(acl_rule.acl_table_name, acl_rule.acl_rule_key); m_p4OidMapper->setOID(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key, acl_rule.acl_entry_oid); @@ -1841,13 +1885,15 @@ ReturnCode AclRuleManager::processAddRuleRequest(const std::string &acl_rule_key // Counter was created, increase ACL rule ref count m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key); } - if (acl_rule.meter.enabled || !acl_rule.meter.packet_color_actions.empty()) + if (acl_rule.meter.enabled) { // Meter was created, increase ACL rule ref count m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); } - SWSS_LOG_NOTICE("Suceeded to create ACL rule %s : %s", QuotedVar(acl_rule.acl_rule_key).c_str(), - sai_serialize_object_id(acl_rule.acl_entry_oid).c_str()); + m_aclRuleTables[acl_table->acl_table_name][acl_rule_key] = std::move(acl_rule); + SWSS_LOG_NOTICE( + "Suceeded to create ACL rule %s : %s", QuotedVar(acl_rule_key).c_str(), + sai_serialize_object_id(m_aclRuleTables[acl_table->acl_table_name][acl_rule_key].acl_entry_oid).c_str()); return status; } @@ -1868,7 +1914,7 @@ ReturnCode AclRuleManager::processUpdateRuleRequest(const P4AclRuleAppDbEntry &a { SWSS_LOG_ENTER(); - P4AclRule acl_rule; + P4AclRule acl_rule{}; const auto *acl_table = gP4Orch->getAclTableManager()->getAclTable(app_db_entry.acl_table_name); acl_rule.acl_table_oid = acl_table->table_oid; acl_rule.acl_table_name = acl_table->acl_table_name; @@ -1876,9 +1922,6 @@ ReturnCode AclRuleManager::processUpdateRuleRequest(const P4AclRuleAppDbEntry &a // Skip match field comparison because the acl_rule_key including match // field value and priority should be the same with old one. - acl_rule.match_fvs = old_acl_rule.match_fvs; - acl_rule.in_ports = old_acl_rule.in_ports; - acl_rule.out_ports = old_acl_rule.out_ports; acl_rule.priority = app_db_entry.priority; acl_rule.acl_rule_key = old_acl_rule.acl_rule_key; // Skip Counter comparison since the counter unit is defined in table @@ -1906,8 +1949,7 @@ ReturnCode AclRuleManager::processUpdateRuleRequest(const P4AclRuleAppDbEntry &a bool created_meter = false; bool updated_meter = false; LOG_AND_RETURN_IF_ERROR(setMeterValue(acl_table, app_db_entry, acl_rule.meter)); - if (old_acl_rule.meter.meter_oid == SAI_NULL_OBJECT_ID && - (acl_rule.meter.enabled || !acl_rule.meter.packet_color_actions.empty())) + if (old_acl_rule.meter.meter_oid == SAI_NULL_OBJECT_ID && acl_rule.meter.enabled) { // Create new meter auto status = createAclMeter(acl_rule.meter, table_name_and_rule_key, &acl_rule.meter.meter_oid); @@ -1926,8 +1968,7 @@ ReturnCode AclRuleManager::processUpdateRuleRequest(const P4AclRuleAppDbEntry &a acl_entry_attr.value.aclaction.parameter.oid = SAI_NULL_OBJECT_ID; rollback_attrs.push_back(acl_entry_attr); } - else if (old_acl_rule.meter.meter_oid != SAI_NULL_OBJECT_ID && !acl_rule.meter.enabled && - acl_rule.meter.packet_color_actions.empty()) + else if (old_acl_rule.meter.meter_oid != SAI_NULL_OBJECT_ID && !acl_rule.meter.enabled) { // Remove old meter remove_meter = true; @@ -2001,9 +2042,468 @@ ReturnCode AclRuleManager::processUpdateRuleRequest(const P4AclRuleAppDbEntry &a } return status; } - - m_aclRuleTables[acl_rule.acl_table_name][acl_rule.acl_rule_key] = acl_rule; + // Move match_fvs and referred pointers from old rule to new rule + acl_rule.in_ports = std::move(old_acl_rule.in_ports); + acl_rule.out_ports = std::move(old_acl_rule.out_ports); + acl_rule.in_ports_oids = std::move(old_acl_rule.in_ports_oids); + acl_rule.out_ports_oids = std::move(old_acl_rule.out_ports_oids); + acl_rule.udf_data_masks = std::move(old_acl_rule.udf_data_masks); + acl_rule.match_fvs = std::move(old_acl_rule.match_fvs); + m_aclRuleTables[acl_rule.acl_table_name][acl_rule.acl_rule_key] = std::move(acl_rule); return ReturnCode(); } +std::string AclRuleManager::verifyState(const std::string &key, const std::vector &tuple) +{ + SWSS_LOG_ENTER(); + + auto pos = key.find_first_of(kTableKeyDelimiter); + if (pos == std::string::npos) + { + return std::string("Invalid key: ") + key; + } + std::string p4rt_table = key.substr(0, pos); + std::string p4rt_key = key.substr(pos + 1); + if (p4rt_table != APP_P4RT_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + std::string table_name; + std::string key_content; + parseP4RTKey(p4rt_key, &table_name, &key_content); + + ReturnCode status; + auto app_db_entry_or = deserializeAclRuleAppDbEntry(table_name, key_content, tuple); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + std::stringstream msg; + msg << "Unable to deserialize key " << QuotedVar(key) << ": " << status.message(); + return msg.str(); + } + auto &app_db_entry = *app_db_entry_or; + + const auto &acl_table_name = app_db_entry.acl_table_name; + const auto &acl_rule_key = + KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, std::to_string(app_db_entry.priority)); + auto *acl_rule = getAclRule(acl_table_name, acl_rule_key); + if (acl_rule == nullptr) + { + std::stringstream msg; + msg << "No entry found with key " << QuotedVar(key); + return msg.str(); + } + + std::string cache_result = verifyStateCache(app_db_entry, acl_rule); + std::string asic_db_result = verifyStateAsicDb(acl_rule); + if (cache_result.empty()) + { + return asic_db_result; + } + if (asic_db_result.empty()) + { + return cache_result; + } + return cache_result + "; " + asic_db_result; +} + +std::string AclRuleManager::verifyStateCache(const P4AclRuleAppDbEntry &app_db_entry, const P4AclRule *acl_rule) +{ + ReturnCode status = validateAclRuleAppDbEntry(app_db_entry); + if (!status.ok()) + { + std::stringstream msg; + msg << "Validation failed for ACL rule DB entry with key " << QuotedVar(acl_rule->acl_rule_key) << ": " + << status.message(); + return msg.str(); + } + + const auto &acl_rule_key = + KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, std::to_string(app_db_entry.priority)); + if (acl_rule->acl_rule_key != acl_rule_key) + { + std::stringstream msg; + msg << "ACL rule " << QuotedVar(acl_rule_key) << " does not match internal cache " + << QuotedVar(acl_rule->acl_rule_key) << " in ACL rule manager."; + return msg.str(); + } + if (acl_rule->acl_table_name != app_db_entry.acl_table_name) + { + std::stringstream msg; + msg << "ACL rule " << QuotedVar(acl_rule_key) << " with table name " << QuotedVar(app_db_entry.acl_table_name) + << " does not match internal cache " << QuotedVar(acl_rule->acl_table_name) << " in ACL rule manager."; + return msg.str(); + } + if (acl_rule->db_key != app_db_entry.db_key) + { + std::stringstream msg; + msg << "ACL rule " << QuotedVar(acl_rule_key) << " with DB key " << QuotedVar(app_db_entry.db_key) + << " does not match internal cache " << QuotedVar(acl_rule->db_key) << " in ACL rule manager."; + return msg.str(); + } + if (acl_rule->p4_action != app_db_entry.action) + { + std::stringstream msg; + msg << "ACL rule " << QuotedVar(acl_rule_key) << " with action " << QuotedVar(app_db_entry.action) + << " does not match internal cache " << QuotedVar(acl_rule->p4_action) << " in ACL rule manager."; + return msg.str(); + } + if (acl_rule->priority != app_db_entry.priority) + { + std::stringstream msg; + msg << "ACL rule " << QuotedVar(acl_rule_key) << " with priority " << app_db_entry.priority + << " does not match internal cache " << acl_rule->priority << " in ACL rule manager."; + return msg.str(); + } + + auto *acl_table = gP4Orch->getAclTableManager()->getAclTable(app_db_entry.acl_table_name); + if (acl_table == nullptr) + { + std::stringstream msg; + msg << "ACL table " << QuotedVar(app_db_entry.acl_table_name) << " not found in ACL rule " + << QuotedVar(acl_rule_key); + return msg.str(); + } + if (acl_rule->acl_table_name != acl_table->acl_table_name) + { + std::stringstream msg; + msg << "ACL rule " << QuotedVar(acl_rule_key) << " with ACL table name " << QuotedVar(acl_rule->acl_table_name) + << " mismatch with ACl table " << QuotedVar(acl_table->acl_table_name) << " in ACl rule manager."; + return msg.str(); + } + if (acl_rule->acl_table_oid != acl_table->table_oid) + { + std::stringstream msg; + msg << "ACL rule " << QuotedVar(acl_rule_key) << " with ACL table OID " << acl_rule->acl_table_oid + << " mismatch with ACl table " << acl_table->table_oid << " in ACl rule manager."; + return msg.str(); + } + + P4AclRule acl_rule_entry{}; + acl_rule_entry.priority = app_db_entry.priority; + acl_rule_entry.acl_rule_key = acl_rule_key; + acl_rule_entry.p4_action = app_db_entry.action; + acl_rule_entry.db_key = app_db_entry.db_key; + status = setAllMatchFieldValues(app_db_entry, acl_table, acl_rule_entry); + if (!status.ok()) + { + std::stringstream msg; + msg << "Failed to set match field values for ACL rule " << QuotedVar(acl_rule_key); + return msg.str(); + } + status = setAllActionFieldValues(app_db_entry, acl_table, acl_rule_entry); + if (!status.ok()) + { + std::stringstream msg; + msg << "Failed to set action field values for ACL rule " << QuotedVar(acl_rule_key); + return msg.str(); + } + status = setMeterValue(acl_table, app_db_entry, acl_rule_entry.meter); + if (!status.ok()) + { + std::stringstream msg; + msg << "Failed to set meter value for ACL rule " << QuotedVar(acl_rule_key); + return msg.str(); + } + if (!acl_table->counter_unit.empty()) + { + if (acl_table->counter_unit == P4_COUNTER_UNIT_PACKETS) + { + acl_rule_entry.counter.packets_enabled = true; + } + else if (acl_table->counter_unit == P4_COUNTER_UNIT_BYTES) + { + acl_rule_entry.counter.bytes_enabled = true; + } + else if (acl_table->counter_unit == P4_COUNTER_UNIT_BOTH) + { + acl_rule_entry.counter.bytes_enabled = true; + acl_rule_entry.counter.packets_enabled = true; + } + } + + if (acl_rule->match_fvs.size() != acl_rule_entry.match_fvs.size()) + { + std::stringstream msg; + msg << "ACL rule " << QuotedVar(acl_rule_key) << " with match fvs size " << acl_rule_entry.match_fvs.size() + << " does not match internal cache " << acl_rule->match_fvs.size() << " in ACl rule manager."; + return msg.str(); + } + for (const auto &match_fv : acl_rule_entry.match_fvs) + { + const auto &it = acl_rule->match_fvs.find(fvField(match_fv)); + if (it == acl_rule->match_fvs.end()) + { + std::stringstream msg; + msg << "ACL match field " << fvField(match_fv) << " not found in internal cache in ACL rule " + << QuotedVar(acl_rule_key); + return msg.str(); + } + else if (isDiffMatchFieldValue(fvField(match_fv), fvValue(match_fv), it->second, acl_rule_entry, *acl_rule)) + { + std::stringstream msg; + msg << "ACL match field " << fvField(match_fv) << " mismatch in internal cache in ACL rule " + << QuotedVar(acl_rule_key); + return msg.str(); + } + } + + if (acl_rule->action_fvs.size() != acl_rule_entry.action_fvs.size()) + { + std::stringstream msg; + msg << "ACL rule " << QuotedVar(acl_rule_key) << " with action fvs size " << acl_rule_entry.action_fvs.size() + << " does not match internal cache " << acl_rule->action_fvs.size() << " in ACl rule manager."; + return msg.str(); + } + for (const auto &action_fv : acl_rule_entry.action_fvs) + { + const auto &it = acl_rule->action_fvs.find(fvField(action_fv)); + if (it == acl_rule->action_fvs.end()) + { + std::stringstream msg; + msg << "ACL action field " << fvField(action_fv) << " not found in internal cache in ACL rule " + << QuotedVar(acl_rule_key); + return msg.str(); + } + else if (isDiffActionFieldValue(fvField(action_fv), fvValue(action_fv), it->second, acl_rule_entry, *acl_rule)) + { + std::stringstream msg; + msg << "ACL action field " << fvField(action_fv) << " mismatch in internal cache in ACL rule " + << QuotedVar(acl_rule_key); + return msg.str(); + } + } + + if (acl_rule->meter != acl_rule_entry.meter) + { + std::stringstream msg; + msg << "Meter mismatch on ACL rule " << QuotedVar(acl_rule_key); + return msg.str(); + } + if (acl_rule->counter != acl_rule_entry.counter) + { + std::stringstream msg; + msg << "Counter mismatch on ACL rule " << QuotedVar(acl_rule_key); + return msg.str(); + } + if (acl_rule->action_qos_queue_num != acl_rule_entry.action_qos_queue_num) + { + std::stringstream msg; + msg << "ACL rule " << QuotedVar(acl_rule_key) << " with qos queue number " + << acl_rule_entry.action_qos_queue_num << " mismatch with internal cache " << acl_rule->action_qos_queue_num + << " in ACl rule manager."; + return msg.str(); + } + if (acl_rule->action_redirect_nexthop_key != acl_rule_entry.action_redirect_nexthop_key) + { + std::stringstream msg; + msg << "ACL rule " << QuotedVar(acl_rule_key) << " with redirect nexthop key " + << QuotedVar(acl_rule_entry.action_redirect_nexthop_key) << " mismatch with internal cache " + << QuotedVar(acl_rule->action_redirect_nexthop_key) << " in ACl rule manager."; + return msg.str(); + } + if (acl_rule->action_mirror_sessions != acl_rule_entry.action_mirror_sessions) + { + std::stringstream msg; + msg << "Mirror sessions mismatch on ACL rule " << QuotedVar(acl_rule_key); + return msg.str(); + } + if (!acl_rule->action_mirror_sessions.empty()) + { + for (const auto &fv : acl_rule->action_mirror_sessions) + { + if (acl_rule->action_fvs.find(fvField(fv)) == acl_rule->action_fvs.end() || + acl_rule->action_fvs.at(fvField(fv)).aclaction.parameter.objlist.list != &fvValue(fv).oid) + { + std::stringstream msg; + msg << "Mirror session " << QuotedVar(std::to_string(fvField(fv))) + << " mismatch on internal cache ACL rule " << QuotedVar(acl_rule_key); + return msg.str(); + } + } + } + + if (acl_rule->udf_data_masks != acl_rule_entry.udf_data_masks) + { + std::stringstream msg; + msg << "UDF data masks mismatch on ACL rule " << QuotedVar(acl_rule_key); + return msg.str(); + } + if (!acl_rule->udf_data_masks.empty()) + { + std::stringstream msg; + for (const auto &fv : acl_rule->udf_data_masks) + { + if (acl_rule->match_fvs.find(fvField(fv)) == acl_rule->match_fvs.end()) + { + msg << "UDF group " << QuotedVar(std::to_string(fvField(fv))) + << " are missing in in internal cache in ACL rule match_fvs" << QuotedVar(acl_rule_key); + return msg.str(); + } + if (acl_rule->match_fvs.at(fvField(fv)).aclfield.data.u8list.list != fvValue(fv).data.data()) + { + msg << "UDF data for field " << QuotedVar(std::to_string(fvField(fv))) + << " mismatches between match_fvs and " + "udf_data_masks in internal cache in ACL rule" + << QuotedVar(acl_rule_key); + return msg.str(); + } + if (acl_rule->match_fvs.at(fvField(fv)).aclfield.mask.u8list.list != fvValue(fv).mask.data()) + { + msg << "UDF mask for field " << QuotedVar(std::to_string(fvField(fv))) + << " mismatches between match_fvs and " + "udf_data_masks in internal cache in ACL rule" + << QuotedVar(acl_rule_key); + return msg.str(); + } + } + } + if (acl_rule->in_ports != acl_rule_entry.in_ports) + { + std::stringstream msg; + msg << "In ports mismatch on ACL rule " << QuotedVar(acl_rule_key); + return msg.str(); + } + if (acl_rule->out_ports != acl_rule_entry.out_ports) + { + std::stringstream msg; + msg << "Out ports mismatch on ACL rule " << QuotedVar(acl_rule_key); + return msg.str(); + } + if (acl_rule->in_ports_oids != acl_rule_entry.in_ports_oids) + { + std::stringstream msg; + msg << "In port OIDs mismatch on ACL rule " << QuotedVar(acl_rule_key); + return msg.str(); + } + if (!acl_rule->in_ports_oids.empty() && + (acl_rule->match_fvs.find(SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS) == acl_rule->match_fvs.end() || + acl_rule->match_fvs.at(SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS).aclfield.data.objlist.list != + acl_rule->in_ports_oids.data())) + { + std::stringstream msg; + msg << "In port OIDs mismatch between match_fvs and " + "in_ports_oids in internal cache in ACL rule" + << QuotedVar(acl_rule_key); + return msg.str(); + } + + if (acl_rule->out_ports_oids != acl_rule_entry.out_ports_oids) + { + std::stringstream msg; + msg << "Out port OIDs mismatch on ACL rule " << QuotedVar(acl_rule_key); + return msg.str(); + } + if (!acl_rule->out_ports_oids.empty() && + (acl_rule->match_fvs.find(SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS) == acl_rule->match_fvs.end() || + acl_rule->match_fvs.at(SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS).aclfield.data.objlist.list != + acl_rule->out_ports_oids.data())) + { + std::stringstream msg; + msg << "Out port OIDs mismatch between match_fvs and " + "out_ports_oids in internal cache in ACL rule" + << QuotedVar(acl_rule_key); + return msg.str(); + } + + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(acl_rule->acl_table_name, acl_rule->acl_rule_key); + std::string err_msg = + m_p4OidMapper->verifyOIDMapping(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key, acl_rule->acl_entry_oid); + if (!err_msg.empty()) + { + return err_msg; + } + if (!acl_table->counter_unit.empty()) + { + err_msg = m_p4OidMapper->verifyOIDMapping(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key, + acl_rule->counter.counter_oid); + if (!err_msg.empty()) + { + return err_msg; + } + } + if (acl_rule_entry.meter.enabled) + { + err_msg = m_p4OidMapper->verifyOIDMapping(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, + acl_rule->meter.meter_oid); + if (!err_msg.empty()) + { + return err_msg; + } + } + + return ""; +} + +std::string AclRuleManager::verifyStateAsicDb(const P4AclRule *acl_rule) +{ + swss::DBConnector db("ASIC_DB", 0); + swss::Table table(&db, "ASIC_STATE"); + + // Verify rule. + auto attrs = getRuleSaiAttrs(*acl_rule); + std::vector exp = + saimeta::SaiAttributeList::serialize_attr_list(SAI_OBJECT_TYPE_ACL_ENTRY, (uint32_t)attrs.size(), attrs.data(), + /*countOnly=*/false); + std::string key = + sai_serialize_object_type(SAI_OBJECT_TYPE_ACL_ENTRY) + ":" + sai_serialize_object_id(acl_rule->acl_entry_oid); + std::vector values; + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + std::string err_msg = verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/true); + if (!err_msg.empty()) + { + return err_msg; + } + + // Verify counter. + if (acl_rule->counter.packets_enabled || acl_rule->counter.bytes_enabled) + { + attrs = getCounterSaiAttrs(*acl_rule); + exp = saimeta::SaiAttributeList::serialize_attr_list(SAI_OBJECT_TYPE_ACL_COUNTER, (uint32_t)attrs.size(), + attrs.data(), + /*countOnly=*/false); + key = sai_serialize_object_type(SAI_OBJECT_TYPE_ACL_COUNTER) + ":" + + sai_serialize_object_id(acl_rule->counter.counter_oid); + values.clear(); + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + err_msg = verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/true); + if (!err_msg.empty()) + { + return err_msg; + } + } + + // Verify meter. + if (acl_rule->meter.enabled) + { + attrs = getMeterSaiAttrs(acl_rule->meter); + exp = saimeta::SaiAttributeList::serialize_attr_list(SAI_OBJECT_TYPE_POLICER, (uint32_t)attrs.size(), + attrs.data(), + /*countOnly=*/false); + key = sai_serialize_object_type(SAI_OBJECT_TYPE_POLICER) + ":" + + sai_serialize_object_id(acl_rule->meter.meter_oid); + values.clear(); + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + err_msg = verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/true); + if (!err_msg.empty()) + { + return err_msg; + } + } + + return ""; +} + } // namespace p4orch diff --git a/orchagent/p4orch/acl_rule_manager.h b/orchagent/p4orch/acl_rule_manager.h index cc00735d844a..34cb8361c0f4 100644 --- a/orchagent/p4orch/acl_rule_manager.h +++ b/orchagent/p4orch/acl_rule_manager.h @@ -43,6 +43,7 @@ class AclRuleManager : public ObjectManagerInterface void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; void drain() override; + std::string verifyState(const std::string &key, const std::vector &tuple) override; // Update counters stats for every rule in each ACL table in COUNTERS_DB, if // counters are enabled in rules. @@ -77,7 +78,7 @@ class AclRuleManager : public ObjectManagerInterface // Create an ACL counter. ReturnCode createAclCounter(const std::string &acl_table_name, const std::string &counter_key, - const P4AclCounter &p4_acl_counter, sai_object_id_t *counter_oid); + const P4AclRule &acl_rule, sai_object_id_t *counter_oid); // Create an ACL meter. ReturnCode createAclMeter(const P4AclMeter &p4_acl_meter, const std::string &meter_key, sai_object_id_t *meter_oid); @@ -136,6 +137,12 @@ class AclRuleManager : public ObjectManagerInterface // clean up. ReturnCode cleanUpUserDefinedTraps(); + // Verifies internal cache for an entry. + std::string verifyStateCache(const P4AclRuleAppDbEntry &app_db_entry, const P4AclRule *acl_rule); + + // Verifies ASIC DB for an entry. + std::string verifyStateAsicDb(const P4AclRule *acl_rule); + P4OidMapper *m_p4OidMapper; ResponsePublisherInterface *m_publisher; P4AclRuleTables m_aclRuleTables; diff --git a/orchagent/p4orch/acl_table_manager.cpp b/orchagent/p4orch/acl_table_manager.cpp index 312f54c51cac..6412803c9f83 100644 --- a/orchagent/p4orch/acl_table_manager.cpp +++ b/orchagent/p4orch/acl_table_manager.cpp @@ -4,7 +4,9 @@ #include #include +#include "SaiAttributeList.h" #include "crmorch.h" +#include "dbconnector.h" #include "json.hpp" #include "logger.h" #include "orch.h" @@ -12,12 +14,15 @@ #include "p4orch/p4orch_util.h" #include "sai_serialize.h" #include "switchorch.h" +#include "table.h" #include "tokenize.h" extern "C" { #include "sai.h" } +using ::p4orch::kTableKeyDelimiter; + extern sai_object_id_t gSwitchId; extern sai_acl_api_t *sai_acl_api; extern sai_udf_api_t *sai_udf_api; @@ -29,6 +34,44 @@ extern int gBatchSize; namespace p4orch { +namespace +{ + +std::vector getGroupMemSaiAttrs(const P4AclTableDefinition &acl_table) +{ + std::vector acl_mem_attrs; + sai_attribute_t acl_mem_attr; + acl_mem_attr.id = SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_GROUP_ID; + acl_mem_attr.value.oid = acl_table.group_oid; + acl_mem_attrs.push_back(acl_mem_attr); + + acl_mem_attr.id = SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_ID; + acl_mem_attr.value.oid = acl_table.table_oid; + acl_mem_attrs.push_back(acl_mem_attr); + + acl_mem_attr.id = SAI_ACL_TABLE_GROUP_MEMBER_ATTR_PRIORITY; + acl_mem_attr.value.u32 = acl_table.priority; + acl_mem_attrs.push_back(acl_mem_attr); + + return acl_mem_attrs; +} + +std::vector getUdfGroupSaiAttrs(const P4UdfField &udf_field) +{ + std::vector udf_group_attrs; + sai_attribute_t udf_group_attr; + udf_group_attr.id = SAI_UDF_GROUP_ATTR_TYPE; + udf_group_attr.value.s32 = SAI_UDF_GROUP_TYPE_GENERIC; + udf_group_attrs.push_back(udf_group_attr); + + udf_group_attr.id = SAI_UDF_GROUP_ATTR_LENGTH; + udf_group_attr.value.u16 = udf_field.length; + udf_group_attrs.push_back(udf_group_attr); + + return udf_group_attrs; +} + +} // namespace AclTableManager::AclTableManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) : m_p4OidMapper(p4oidMapper), m_publisher(publisher) @@ -41,7 +84,7 @@ AclTableManager::AclTableManager(P4OidMapper *p4oidMapper, ResponsePublisherInte AclTableManager::~AclTableManager() { sai_object_id_t udf_match_oid; - if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, &udf_match_oid)) + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, &udf_match_oid)) { return; } @@ -53,6 +96,115 @@ AclTableManager::~AclTableManager() } } +ReturnCodeOr> AclTableManager::getTableSaiAttrs(const P4AclTableDefinition &acl_table) +{ + std::vector acl_attr_list; + sai_attribute_t acl_attr; + acl_attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; + acl_attr.value.s32 = acl_table.stage; + acl_attr_list.push_back(acl_attr); + + if (acl_table.size > 0) + { + acl_attr.id = SAI_ACL_TABLE_ATTR_SIZE; + acl_attr.value.u32 = acl_table.size; + acl_attr_list.push_back(acl_attr); + } + + std::set table_match_fields_to_add; + if (!acl_table.ip_type_bit_type_lookup.empty()) + { + acl_attr.id = SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE; + acl_attr.value.booldata = true; + acl_attr_list.push_back(acl_attr); + table_match_fields_to_add.insert(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE); + } + + for (const auto &match_field : acl_table.sai_match_field_lookup) + { + const auto &sai_match_field = fvValue(match_field); + // Avoid duplicate match attribute to add + if (table_match_fields_to_add.find(sai_match_field.table_attr) != table_match_fields_to_add.end()) + continue; + acl_attr.id = sai_match_field.table_attr; + acl_attr.value.booldata = true; + acl_attr_list.push_back(acl_attr); + table_match_fields_to_add.insert(sai_match_field.table_attr); + } + + for (const auto &match_fields : acl_table.composite_sai_match_fields_lookup) + { + const auto &sai_match_fields = fvValue(match_fields); + for (const auto &sai_match_field : sai_match_fields) + { + // Avoid duplicate match attribute to add + if (table_match_fields_to_add.find(sai_match_field.table_attr) != table_match_fields_to_add.end()) + continue; + acl_attr.id = sai_match_field.table_attr; + acl_attr.value.booldata = true; + acl_attr_list.push_back(acl_attr); + table_match_fields_to_add.insert(sai_match_field.table_attr); + } + } + + // Add UDF group attributes + for (const auto &udf_group_idx : acl_table.udf_group_attr_index_lookup) + { + acl_attr.id = SAI_ACL_TABLE_ATTR_USER_DEFINED_FIELD_GROUP_MIN + fvValue(udf_group_idx); + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_GROUP, fvField(udf_group_idx), &acl_attr.value.oid)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "THe UDF group with id " << QuotedVar(fvField(udf_group_idx)) << " was not found."); + } + acl_attr_list.push_back(acl_attr); + } + + m_acl_action_list[0] = SAI_ACL_ACTION_TYPE_COUNTER; + acl_attr.id = SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST; + acl_attr.value.s32list.count = 1; + acl_attr.value.s32list.list = m_acl_action_list; + acl_attr_list.push_back(acl_attr); + + return acl_attr_list; +} + +ReturnCodeOr> AclTableManager::getUdfSaiAttrs(const P4UdfField &udf_field) +{ + sai_object_id_t udf_group_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_GROUP, udf_field.group_id, &udf_group_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "UDF group " << QuotedVar(udf_field.group_id) << " does not exist"; + } + sai_object_id_t udf_match_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, &udf_match_oid)) + { + // Create the default UDF match + LOG_AND_RETURN_IF_ERROR(createDefaultUdfMatch() + << "Failed to create ACL UDF default match " << QuotedVar(P4_UDF_MATCH_DEFAULT)); + m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, &udf_match_oid); + } + std::vector udf_attrs; + sai_attribute_t udf_attr; + udf_attr.id = SAI_UDF_ATTR_GROUP_ID; + udf_attr.value.oid = udf_group_oid; + udf_attrs.push_back(udf_attr); + + udf_attr.id = SAI_UDF_ATTR_MATCH_ID; + udf_attr.value.oid = udf_match_oid; + udf_attrs.push_back(udf_attr); + + udf_attr.id = SAI_UDF_ATTR_BASE; + udf_attr.value.s32 = udf_field.base; + udf_attrs.push_back(udf_attr); + + udf_attr.id = SAI_UDF_ATTR_OFFSET; + udf_attr.value.u16 = udf_field.offset; + udf_attrs.push_back(udf_attr); + + return udf_attrs; +} + void AclTableManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) { m_entries.push_back(entry); @@ -261,7 +413,7 @@ ReturnCode AclTableManager::processAddTableRequest(const P4AclTableDefinitionApp << "ACL table stage " << QuotedVar(app_db_entry.stage) << " is invalid"); } - if (gSwitchOrch->getAclGroupOidsBindingToSwitch().empty()) + if (gSwitchOrch->getAclGroupsBindingToSwitch().empty()) { // Create default ACL groups binding to switch gSwitchOrch->initAclGroupsBindToSwitch(); @@ -270,13 +422,14 @@ ReturnCode AclTableManager::processAddTableRequest(const P4AclTableDefinitionApp P4AclTableDefinition acl_table_definition(app_db_entry.acl_table_name, stage, app_db_entry.priority, app_db_entry.size, app_db_entry.meter_unit, app_db_entry.counter_unit); - auto group_it = gSwitchOrch->getAclGroupOidsBindingToSwitch().find(acl_table_definition.stage); - if (group_it == gSwitchOrch->getAclGroupOidsBindingToSwitch().end()) + auto &group_map = gSwitchOrch->getAclGroupsBindingToSwitch(); + auto group_it = group_map.find(acl_table_definition.stage); + if (group_it == group_map.end()) { RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to find ACL group binding to switch at stage " << acl_table_definition.stage); } - acl_table_definition.group_oid = group_it->second; + acl_table_definition.group_oid = group_it->second.m_saiObjectId; auto build_match_rc = buildAclTableDefinitionMatchFieldValues(app_db_entry.match_field_lookup, &acl_table_definition); @@ -398,21 +551,12 @@ ReturnCode AclTableManager::createUdfGroup(const P4UdfField &udf_field) { SWSS_LOG_ENTER(); sai_object_id_t udf_group_oid; - std::vector udf_group_attrs; - sai_attribute_t udf_group_attr; - udf_group_attr.id = SAI_UDF_GROUP_ATTR_TYPE; - udf_group_attr.value.s32 = SAI_UDF_GROUP_TYPE_GENERIC; - udf_group_attrs.push_back(udf_group_attr); + auto attrs = getUdfGroupSaiAttrs(udf_field); - udf_group_attr.id = SAI_UDF_GROUP_ATTR_LENGTH; - udf_group_attr.value.u16 = udf_field.length; - udf_group_attrs.push_back(udf_group_attr); - - CHECK_ERROR_AND_LOG_AND_RETURN(sai_udf_api->create_udf_group(&udf_group_oid, gSwitchId, - (uint32_t)udf_group_attrs.size(), - udf_group_attrs.data()), - "Failed to create UDF group " << QuotedVar(udf_field.group_id) - << " from SAI call sai_udf_api->create_udf_group"); + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_udf_api->create_udf_group(&udf_group_oid, gSwitchId, (uint32_t)attrs.size(), attrs.data()), + "Failed to create UDF group " << QuotedVar(udf_field.group_id) + << " from SAI call sai_udf_api->create_udf_group"); m_p4OidMapper->setOID(SAI_OBJECT_TYPE_UDF_GROUP, udf_field.group_id, udf_group_oid); SWSS_LOG_INFO("Suceeded to create UDF group %s with object ID %s ", QuotedVar(udf_field.group_id).c_str(), sai_serialize_object_id(udf_group_oid).c_str()); @@ -455,43 +599,13 @@ ReturnCode AclTableManager::createUdf(const P4UdfField &udf_field) { SWSS_LOG_ENTER(); const auto &udf_id = udf_field.udf_id; - sai_object_id_t udf_group_oid; - if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_GROUP, udf_field.group_id, &udf_group_oid)) - { - return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) - << "UDF group " << QuotedVar(udf_field.group_id) << " does not exist"; - } - sai_object_id_t udf_match_oid; - if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, &udf_match_oid)) - { - // Create the default UDF match - LOG_AND_RETURN_IF_ERROR(createDefaultUdfMatch() - << "Failed to create ACL UDF default match " - << QuotedVar(P4_UDF_MATCH_DEFAULT)); - m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, &udf_match_oid); - } - std::vector udf_attrs; - sai_attribute_t udf_attr; - udf_attr.id = SAI_UDF_ATTR_GROUP_ID; - udf_attr.value.oid = udf_group_oid; - udf_attrs.push_back(udf_attr); - udf_attr.id = SAI_UDF_ATTR_MATCH_ID; - udf_attr.value.oid = udf_match_oid; - udf_attrs.push_back(udf_attr); - - udf_attr.id = SAI_UDF_ATTR_BASE; - udf_attr.value.s32 = udf_field.base; - udf_attrs.push_back(udf_attr); - - udf_attr.id = SAI_UDF_ATTR_OFFSET; - udf_attr.value.u16 = udf_field.offset; - udf_attrs.push_back(udf_attr); + ASSIGN_OR_RETURN(auto attrs, getUdfSaiAttrs(udf_field)); sai_object_id_t udf_oid; - CHECK_ERROR_AND_LOG_AND_RETURN( - sai_udf_api->create_udf(&udf_oid, gSwitchId, (uint32_t)udf_attrs.size(), udf_attrs.data()), - "Failed to create UDF " << QuotedVar(udf_id) << " from SAI call sai_udf_api->create_udf"); + CHECK_ERROR_AND_LOG_AND_RETURN(sai_udf_api->create_udf(&udf_oid, gSwitchId, (uint32_t)attrs.size(), attrs.data()), + "Failed to create UDF " << QuotedVar(udf_id) + << " from SAI call sai_udf_api->create_udf"); m_p4OidMapper->setOID(SAI_OBJECT_TYPE_UDF, udf_id, udf_oid); // Increase UDF group and match reference count m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT); @@ -596,78 +710,10 @@ ReturnCode AclTableManager::createAclTable(P4AclTableDefinition &acl_table, sai_ sai_object_id_t *acl_group_member_oid) { // Prepare SAI ACL attributes list to create ACL table - std::vector acl_attr_list; - sai_attribute_t acl_attr; - acl_attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; - acl_attr.value.s32 = acl_table.stage; - acl_attr_list.push_back(acl_attr); - - if (acl_table.size > 0) - { - acl_attr.id = SAI_ACL_TABLE_ATTR_SIZE; - acl_attr.value.u32 = acl_table.size; - acl_attr_list.push_back(acl_attr); - } - - std::set table_match_fields_to_add; - if (!acl_table.ip_type_bit_type_lookup.empty()) - { - acl_attr.id = SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE; - acl_attr.value.booldata = true; - acl_attr_list.push_back(acl_attr); - table_match_fields_to_add.insert(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE); - } - - for (const auto &match_field : acl_table.sai_match_field_lookup) - { - const auto &sai_match_field = fvValue(match_field); - // Avoid duplicate match attribute to add - if (table_match_fields_to_add.find(sai_match_field.table_attr) != table_match_fields_to_add.end()) - continue; - acl_attr.id = sai_match_field.table_attr; - acl_attr.value.booldata = true; - acl_attr_list.push_back(acl_attr); - table_match_fields_to_add.insert(sai_match_field.table_attr); - } - - for (const auto &match_fields : acl_table.composite_sai_match_fields_lookup) - { - const auto &sai_match_fields = fvValue(match_fields); - for (const auto &sai_match_field : sai_match_fields) - { - // Avoid duplicate match attribute to add - if (table_match_fields_to_add.find(sai_match_field.table_attr) != table_match_fields_to_add.end()) - continue; - acl_attr.id = sai_match_field.table_attr; - acl_attr.value.booldata = true; - acl_attr_list.push_back(acl_attr); - table_match_fields_to_add.insert(sai_match_field.table_attr); - } - } - - // Add UDF group attributes - for (const auto &udf_group_idx : acl_table.udf_group_attr_index_lookup) - { - acl_attr.id = SAI_ACL_TABLE_ATTR_USER_DEFINED_FIELD_GROUP_MIN + fvValue(udf_group_idx); - if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_GROUP, fvField(udf_group_idx), &acl_attr.value.oid)) - { - LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) - << "THe UDF group with id " << QuotedVar(fvField(udf_group_idx)) << " was not found."); - } - acl_attr_list.push_back(acl_attr); - } - - // OA workaround to fix b/191114070: always add counter action in ACL table - // action list during creation - int32_t acl_action_list[1]; - acl_action_list[0] = SAI_ACL_ACTION_TYPE_COUNTER; - acl_attr.id = SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST; - acl_attr.value.s32list.count = 1; - acl_attr.value.s32list.list = acl_action_list; - acl_attr_list.push_back(acl_attr); + ASSIGN_OR_RETURN(auto attrs, getTableSaiAttrs(acl_table)); CHECK_ERROR_AND_LOG_AND_RETURN( - sai_acl_api->create_acl_table(acl_table_oid, gSwitchId, (uint32_t)acl_attr_list.size(), acl_attr_list.data()), + sai_acl_api->create_acl_table(acl_table_oid, gSwitchId, (uint32_t)attrs.size(), attrs.data()), "Failed to create ACL table " << QuotedVar(acl_table.acl_table_name)); SWSS_LOG_NOTICE("Called SAI API to create ACL table %s ", sai_serialize_object_id(*acl_table_oid).c_str()); auto status = createAclGroupMember(acl_table, acl_group_member_oid); @@ -708,7 +754,7 @@ ReturnCode AclTableManager::removeAclTable(P4AclTableDefinition &acl_table) { SWSS_LOG_ENTER(); - auto status = removeAclGroupMember(acl_table.acl_table_name); + auto status = removeAclGroupMember(acl_table); if (!status.ok()) { SWSS_LOG_ERROR("Failed to remove ACL table with key %s : failed to delete group " @@ -853,51 +899,377 @@ ReturnCode AclTableManager::createAclGroupMember(const P4AclTableDefinition &acl sai_object_id_t *acl_grp_mem_oid) { SWSS_LOG_ENTER(); - std::vector acl_mem_attrs; - sai_attribute_t acl_mem_attr; - acl_mem_attr.id = SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_GROUP_ID; - acl_mem_attr.value.oid = acl_table.group_oid; - acl_mem_attrs.push_back(acl_mem_attr); - - acl_mem_attr.id = SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_ID; - acl_mem_attr.value.oid = acl_table.table_oid; - acl_mem_attrs.push_back(acl_mem_attr); - - acl_mem_attr.id = SAI_ACL_TABLE_GROUP_MEMBER_ATTR_PRIORITY; - acl_mem_attr.value.u32 = acl_table.priority; - acl_mem_attrs.push_back(acl_mem_attr); + auto attrs = getGroupMemSaiAttrs(acl_table); CHECK_ERROR_AND_LOG_AND_RETURN( - sai_acl_api->create_acl_table_group_member(acl_grp_mem_oid, gSwitchId, (uint32_t)acl_mem_attrs.size(), - acl_mem_attrs.data()), + sai_acl_api->create_acl_table_group_member(acl_grp_mem_oid, gSwitchId, (uint32_t)attrs.size(), attrs.data()), "Failed to create ACL group member in group " << sai_serialize_object_id(acl_table.group_oid)); m_p4OidMapper->setOID(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, acl_table.acl_table_name, *acl_grp_mem_oid); - m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE_GROUP, std::to_string(acl_table.stage)); + // Add reference on the ACL group + auto &group_map = gSwitchOrch->getAclGroupsBindingToSwitch(); + auto group_it = group_map.find(acl_table.stage); + if (group_it == group_map.end()) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to find ACL group binding to switch at stage " + << acl_table.stage); + } + auto *referenced_group = &group_it->second; + referenced_group->m_objsDependingOnMe.insert(sai_serialize_object_id(*acl_grp_mem_oid)); SWSS_LOG_NOTICE("ACL group member for table %s was created successfully: %s", QuotedVar(acl_table.acl_table_name).c_str(), sai_serialize_object_id(*acl_grp_mem_oid).c_str()); return ReturnCode(); } -ReturnCode AclTableManager::removeAclGroupMember(const std::string &acl_table_name) +ReturnCode AclTableManager::removeAclGroupMember(P4AclTableDefinition &acl_table) { SWSS_LOG_ENTER(); sai_object_id_t grp_mem_oid; - if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, acl_table_name, &grp_mem_oid)) + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, acl_table.acl_table_name, &grp_mem_oid)) { LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to remove ACL group member " << sai_serialize_object_id(grp_mem_oid) - << " for table " << QuotedVar(acl_table_name) << ": invalid table key."); + << " for table " << QuotedVar(acl_table.acl_table_name) << ": invalid table key."); } CHECK_ERROR_AND_LOG_AND_RETURN(sai_acl_api->remove_acl_table_group_member(grp_mem_oid), "Failed to remove ACL group member " << sai_serialize_object_id(grp_mem_oid) - << " for table " << QuotedVar(acl_table_name)); - m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, acl_table_name); - m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE_GROUP, - std::to_string(m_aclTableDefinitions[acl_table_name].stage).c_str()); + << " for table " + << QuotedVar(acl_table.acl_table_name)); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, acl_table.acl_table_name); + // Remove reference on the ACL group + auto &group_map = gSwitchOrch->getAclGroupsBindingToSwitch(); + auto group_it = group_map.find(acl_table.stage); + if (group_it == group_map.end()) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to find ACL group binding to switch at stage " + << acl_table.stage); + } + auto *referenced_group = &group_it->second; + referenced_group->m_objsDependingOnMe.erase(sai_serialize_object_id(grp_mem_oid)); SWSS_LOG_NOTICE("ACL table member %s for table %s was removed successfully.", - sai_serialize_object_id(grp_mem_oid).c_str(), QuotedVar(acl_table_name).c_str()); + sai_serialize_object_id(grp_mem_oid).c_str(), QuotedVar(acl_table.acl_table_name).c_str()); return ReturnCode(); } +std::string AclTableManager::verifyState(const std::string &key, const std::vector &tuple) +{ + SWSS_LOG_ENTER(); + + auto pos = key.find_first_of(kTableKeyDelimiter); + if (pos == std::string::npos) + { + return std::string("Invalid key: ") + key; + } + std::string p4rt_table = key.substr(0, pos); + std::string p4rt_key = key.substr(pos + 1); + if (p4rt_table != APP_P4RT_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + std::string table_name; + std::string key_content; + parseP4RTKey(p4rt_key, &table_name, &key_content); + if (table_name != APP_P4RT_ACL_TABLE_DEFINITION_NAME) + { + return std::string("Invalid key: ") + key; + } + + ReturnCode status; + auto app_db_entry_or = deserializeAclTableDefinitionAppDbEntry(key_content, tuple); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + std::stringstream msg; + msg << "Unable to deserialize key " << QuotedVar(key) << ": " << status.message(); + return msg.str(); + } + auto &app_db_entry = *app_db_entry_or; + + auto *acl_table_definition = getAclTable(app_db_entry.acl_table_name); + if (acl_table_definition == nullptr) + { + std::stringstream msg; + msg << "No entry found with key " << QuotedVar(key); + return msg.str(); + } + + std::string cache_result = verifyStateCache(app_db_entry, acl_table_definition); + std::string asic_db_result = verifyStateAsicDb(acl_table_definition); + if (cache_result.empty()) + { + return asic_db_result; + } + if (asic_db_result.empty()) + { + return cache_result; + } + return cache_result + "; " + asic_db_result; +} + +std::string AclTableManager::verifyStateCache(const P4AclTableDefinitionAppDbEntry &app_db_entry, + const P4AclTableDefinition *acl_table) +{ + ReturnCode status = validateAclTableDefinitionAppDbEntry(app_db_entry); + if (!status.ok()) + { + std::stringstream msg; + msg << "Validation failed for ACL table DB entry " << QuotedVar(app_db_entry.acl_table_name) << ": " + << status.message(); + return msg.str(); + } + + auto stage_it = aclStageLookup.find(app_db_entry.stage); + sai_acl_stage_t stage; + if (stage_it != aclStageLookup.end()) + { + stage = stage_it->second; + } + else + { + std::stringstream msg; + msg << "Invalid stage " << QuotedVar(app_db_entry.stage) << " in ACL table manager."; + return msg.str(); + } + P4AclTableDefinition acl_table_definition_entry(app_db_entry.acl_table_name, stage, app_db_entry.priority, + app_db_entry.size, app_db_entry.meter_unit, + app_db_entry.counter_unit); + + if (acl_table->acl_table_name != app_db_entry.acl_table_name) + { + std::stringstream msg; + msg << "ACL table " << QuotedVar(app_db_entry.acl_table_name) << " does not match internal cache " + << QuotedVar(acl_table->acl_table_name) << " in ACL table manager."; + return msg.str(); + } + if (acl_table->stage != stage) + { + std::stringstream msg; + msg << "ACL table " << QuotedVar(app_db_entry.acl_table_name) << " with stage " << stage + << " does not match internal cache " << acl_table->stage << " in ACL table manager."; + return msg.str(); + } + if (acl_table->size != app_db_entry.size) + { + std::stringstream msg; + msg << "ACL table " << QuotedVar(app_db_entry.acl_table_name) << " with size " << app_db_entry.size + << " does not match internal cache " << acl_table->size << " in ACL table manager."; + return msg.str(); + } + if (acl_table->priority != app_db_entry.priority) + { + std::stringstream msg; + msg << "ACL table " << QuotedVar(app_db_entry.acl_table_name) << " with priority " << app_db_entry.priority + << " does not match internal cache " << acl_table->priority << " in ACL table manager."; + return msg.str(); + } + if (acl_table->meter_unit != app_db_entry.meter_unit) + { + std::stringstream msg; + msg << "ACL table " << QuotedVar(app_db_entry.acl_table_name) << " with meter unit " + << QuotedVar(app_db_entry.meter_unit) << " does not match internal cache " + << QuotedVar(acl_table->meter_unit) << " in ACL table manager."; + return msg.str(); + } + if (acl_table->counter_unit != app_db_entry.counter_unit) + { + std::stringstream msg; + msg << "ACL table " << QuotedVar(app_db_entry.acl_table_name) << " with counter unit " + << QuotedVar(app_db_entry.counter_unit) << " does not match internal cache " + << QuotedVar(acl_table->counter_unit) << " in ACL table manager."; + return msg.str(); + } + + status = buildAclTableDefinitionMatchFieldValues(app_db_entry.match_field_lookup, &acl_table_definition_entry); + if (!status.ok()) + { + std::stringstream msg; + msg << "Failed to build ACL table match field values for table " << QuotedVar(app_db_entry.acl_table_name); + return msg.str(); + } + status = buildAclTableDefinitionActionFieldValues(app_db_entry.action_field_lookup, + &acl_table_definition_entry.rule_action_field_lookup); + if (!status.ok()) + { + std::stringstream msg; + msg << "Failed to build ACL table action field values for table " << QuotedVar(app_db_entry.acl_table_name); + return msg.str(); + } + status = buildAclTableDefinitionActionColorFieldValues(app_db_entry.packet_action_color_lookup, + &acl_table_definition_entry.rule_action_field_lookup, + &acl_table_definition_entry.rule_packet_action_color_lookup); + if (!status.ok()) + { + std::stringstream msg; + msg << "Failed to build ACL table action color field values for table " + << QuotedVar(app_db_entry.acl_table_name); + return msg.str(); + } + + if (acl_table->composite_sai_match_fields_lookup != acl_table_definition_entry.composite_sai_match_fields_lookup) + { + std::stringstream msg; + msg << "Composite SAI match fields mismatch on ACL table " << QuotedVar(app_db_entry.acl_table_name); + return msg.str(); + } + if (acl_table->udf_fields_lookup != acl_table_definition_entry.udf_fields_lookup) + { + std::stringstream msg; + msg << "UDF fields lookup mismatch on ACL table " << QuotedVar(app_db_entry.acl_table_name); + return msg.str(); + } + if (acl_table->udf_group_attr_index_lookup != acl_table_definition_entry.udf_group_attr_index_lookup) + { + std::stringstream msg; + msg << "UDF group attr index lookup mismatch on ACL table " << QuotedVar(app_db_entry.acl_table_name); + return msg.str(); + } + if (acl_table->sai_match_field_lookup != acl_table_definition_entry.sai_match_field_lookup) + { + std::stringstream msg; + msg << "SAI match field lookup mismatch on ACL table " << QuotedVar(app_db_entry.acl_table_name); + return msg.str(); + } + if (acl_table->ip_type_bit_type_lookup != acl_table_definition_entry.ip_type_bit_type_lookup) + { + std::stringstream msg; + msg << "IP type bit type lookup mismatch on ACL table " << QuotedVar(app_db_entry.acl_table_name); + return msg.str(); + } + if (acl_table->rule_action_field_lookup != acl_table_definition_entry.rule_action_field_lookup) + { + std::stringstream msg; + msg << "Rule action field lookup mismatch on ACL table " << QuotedVar(app_db_entry.acl_table_name); + return msg.str(); + } + if (acl_table->rule_packet_action_color_lookup != acl_table_definition_entry.rule_packet_action_color_lookup) + { + std::stringstream msg; + msg << "Rule packet action color lookup mismatch on ACL table " << QuotedVar(app_db_entry.acl_table_name); + return msg.str(); + } + + std::string err_msg = m_p4OidMapper->verifyOIDMapping(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, + app_db_entry.acl_table_name, acl_table->group_member_oid); + if (!err_msg.empty()) + { + return err_msg; + } + err_msg = + m_p4OidMapper->verifyOIDMapping(SAI_OBJECT_TYPE_ACL_TABLE, app_db_entry.acl_table_name, acl_table->table_oid); + if (!err_msg.empty()) + { + return err_msg; + } + + return ""; +} + +std::string AclTableManager::verifyStateAsicDb(const P4AclTableDefinition *acl_table) +{ + swss::DBConnector db("ASIC_DB", 0); + swss::Table table(&db, "ASIC_STATE"); + + // Verify table. + auto attrs_or = getTableSaiAttrs(*acl_table); + if (!attrs_or.ok()) + { + return std::string("Failed to get SAI attrs: ") + attrs_or.status().message(); + } + std::vector attrs = *attrs_or; + std::vector exp = + saimeta::SaiAttributeList::serialize_attr_list(SAI_OBJECT_TYPE_ACL_TABLE, (uint32_t)attrs.size(), attrs.data(), + /*countOnly=*/false); + std::string key = + sai_serialize_object_type(SAI_OBJECT_TYPE_ACL_TABLE) + ":" + sai_serialize_object_id(acl_table->table_oid); + std::vector values; + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + std::string err_msg = verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); + if (!err_msg.empty()) + { + return err_msg; + } + + // Verify group member. + attrs = getGroupMemSaiAttrs(*acl_table); + exp = saimeta::SaiAttributeList::serialize_attr_list(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, (uint32_t)attrs.size(), + attrs.data(), /*countOnly=*/false); + key = sai_serialize_object_type(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER) + ":" + + sai_serialize_object_id(acl_table->group_member_oid); + values.clear(); + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + err_msg = verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); + if (!err_msg.empty()) + { + return err_msg; + } + + for (auto &udf_fields : acl_table->udf_fields_lookup) + { + for (auto &udf_field : fvValue(udf_fields)) + { + sai_object_id_t udf_group_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_GROUP, udf_field.group_id, &udf_group_oid)) + { + return std::string("UDF group ") + udf_field.group_id + " does not exist"; + } + sai_object_id_t udf_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF, udf_field.udf_id, &udf_oid)) + { + return std::string("UDF ") + udf_field.udf_id + " does not exist"; + } + + // Verify UDF group. + attrs = getUdfGroupSaiAttrs(udf_field); + exp = saimeta::SaiAttributeList::serialize_attr_list(SAI_OBJECT_TYPE_UDF_GROUP, (uint32_t)attrs.size(), + attrs.data(), + /*countOnly=*/false); + key = sai_serialize_object_type(SAI_OBJECT_TYPE_UDF_GROUP) + ":" + sai_serialize_object_id(udf_group_oid); + values.clear(); + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + err_msg = verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); + if (!err_msg.empty()) + { + return err_msg; + } + + // Verify UDF. + attrs_or = getUdfSaiAttrs(udf_field); + if (!attrs_or.ok()) + { + return std::string("Failed to get SAI attrs: ") + attrs_or.status().message(); + } + attrs = *attrs_or; + exp = saimeta::SaiAttributeList::serialize_attr_list(SAI_OBJECT_TYPE_UDF, (uint32_t)attrs.size(), + attrs.data(), + /*countOnly=*/false); + key = sai_serialize_object_type(SAI_OBJECT_TYPE_UDF) + ":" + sai_serialize_object_id(udf_oid); + values.clear(); + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + err_msg = verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); + if (!err_msg.empty()) + { + return err_msg; + } + } + } + + return ""; +} + } // namespace p4orch diff --git a/orchagent/p4orch/acl_table_manager.h b/orchagent/p4orch/acl_table_manager.h index 6243c08cb4f8..f48d34c30979 100644 --- a/orchagent/p4orch/acl_table_manager.h +++ b/orchagent/p4orch/acl_table_manager.h @@ -33,6 +33,7 @@ class AclTableManager : public ObjectManagerInterface void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; void drain() override; + std::string verifyState(const std::string &key, const std::vector &tuple) override; // Get ACL table definition by table name in cache. Return nullptr if not // found. @@ -92,7 +93,20 @@ class AclTableManager : public ObjectManagerInterface ReturnCode createAclGroupMember(const P4AclTableDefinition &acl_table, sai_object_id_t *acl_grp_mem_oid); // Remove ACL group member for given ACL table. - ReturnCode removeAclGroupMember(const std::string &acl_table_name); + ReturnCode removeAclGroupMember(P4AclTableDefinition &acl_table); + + // Verifies internal cache for an entry. + std::string verifyStateCache(const P4AclTableDefinitionAppDbEntry &app_db_entry, + const P4AclTableDefinition *acl_table); + + // Verifies ASIC DB for an entry. + std::string verifyStateAsicDb(const P4AclTableDefinition *acl_table); + + // Returns ACl table SAI attributes. + ReturnCodeOr> getTableSaiAttrs(const P4AclTableDefinition &acl_table); + + // Returns UDF SAI attributes. + ReturnCodeOr> getUdfSaiAttrs(const P4UdfField &udf_field); P4OidMapper *m_p4OidMapper; ResponsePublisherInterface *m_publisher; @@ -100,6 +114,9 @@ class AclTableManager : public ObjectManagerInterface std::deque m_entries; std::map> m_aclTablesByStage; + // Always add counter action in ACL table action list during creation + int32_t m_acl_action_list[1]; + friend class p4orch::test::AclManagerTest; }; diff --git a/orchagent/p4orch/acl_util.cpp b/orchagent/p4orch/acl_util.cpp index 6caf67cadeec..92905ec62246 100644 --- a/orchagent/p4orch/acl_util.cpp +++ b/orchagent/p4orch/acl_util.cpp @@ -10,13 +10,6 @@ namespace p4orch { -std::string trim(const std::string &s) -{ - size_t end = s.find_last_not_of(WHITESPACE); - size_t start = s.find_first_not_of(WHITESPACE); - return (end == std::string::npos) ? EMPTY_STRING : s.substr(start, end - start + 1); -} - bool parseAclTableAppDbActionField(const std::string &aggr_actions_str, std::vector *action_list, std::vector *action_color_list) { @@ -327,8 +320,8 @@ ReturnCode validateAndSetCompositeMatchFieldJson( uint32_t composite_bitwidth = bitwidth_it.value(); auto elements_it = aggr_match_json.find(kAclMatchFieldElements); - // b/175596733: temp disable verification on composite elements field until - // p4rt implementation is added. + // TODO: temp disable verification on composite elements field until p4rt + // implementation is added. if (elements_it == aggr_match_json.end()) { (*udf_fields_lookup)[p4_match]; @@ -871,4 +864,98 @@ bool isDiffActionFieldValue(const acl_entry_attr_union_t attr_name, const sai_at } } +bool isDiffMatchFieldValue(const acl_entry_attr_union_t attr_name, const sai_attribute_value_t &value, + const sai_attribute_value_t &old_value, const P4AclRule &acl_rule, + const P4AclRule &old_acl_rule) +{ + if (attr_name >= SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN && + attr_name <= SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MAX) + { + // We compare the size here only. The list is explicitly verified in the + // ACL rule. + return value.aclfield.data.u8list.count != old_value.aclfield.data.u8list.count; + } + switch (attr_name) + { + case SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS: { + // We compare the size here only. The list is explicitly verified in the + // ACL rule. + return value.aclfield.data.objlist.count != old_value.aclfield.data.objlist.count; + } + case SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS: { + // We compare the size here only. The list is explicitly verified in the + // ACL rule. + return value.aclfield.data.objlist.count != old_value.aclfield.data.objlist.count; + } + case SAI_ACL_ENTRY_ATTR_FIELD_IN_PORT: + case SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORT: { + return value.aclfield.data.oid != old_value.aclfield.data.oid; + } + case SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE: + case SAI_ACL_ENTRY_ATTR_FIELD_TUNNEL_VNI: + case SAI_ACL_ENTRY_ATTR_FIELD_ROUTE_DST_USER_META: + case SAI_ACL_ENTRY_ATTR_FIELD_IPV6_FLOW_LABEL: + case SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_FRAG: + case SAI_ACL_ENTRY_ATTR_FIELD_PACKET_VLAN: { + return value.aclfield.data.u32 != old_value.aclfield.data.u32 || + value.aclfield.mask.u32 != old_value.aclfield.mask.u32; + } + case SAI_ACL_ENTRY_ATTR_FIELD_TCP_FLAGS: + case SAI_ACL_ENTRY_ATTR_FIELD_IP_FLAGS: + case SAI_ACL_ENTRY_ATTR_FIELD_DSCP: + case SAI_ACL_ENTRY_ATTR_FIELD_TC: + case SAI_ACL_ENTRY_ATTR_FIELD_ICMP_TYPE: + case SAI_ACL_ENTRY_ATTR_FIELD_ICMP_CODE: + case SAI_ACL_ENTRY_ATTR_FIELD_ICMPV6_TYPE: + case SAI_ACL_ENTRY_ATTR_FIELD_ICMPV6_CODE: + case SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_PRI: + case SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_CFI: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_VLAN_PRI: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_VLAN_CFI: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_IP_PROTOCOL: + case SAI_ACL_ENTRY_ATTR_FIELD_IP_PROTOCOL: + case SAI_ACL_ENTRY_ATTR_FIELD_ECN: + case SAI_ACL_ENTRY_ATTR_FIELD_TTL: + case SAI_ACL_ENTRY_ATTR_FIELD_TOS: + case SAI_ACL_ENTRY_ATTR_FIELD_IPV6_NEXT_HEADER: { + return value.aclfield.data.u8 != old_value.aclfield.data.u8 || + value.aclfield.mask.u8 != old_value.aclfield.mask.u8; + } + case SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE: + case SAI_ACL_ENTRY_ATTR_FIELD_L4_SRC_PORT: + case SAI_ACL_ENTRY_ATTR_FIELD_L4_DST_PORT: + case SAI_ACL_ENTRY_ATTR_FIELD_IP_IDENTIFICATION: + case SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_ID: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_VLAN_ID: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_ETHER_TYPE: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_L4_SRC_PORT: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_L4_DST_PORT: { + return value.aclfield.data.u16 != old_value.aclfield.data.u16 || + value.aclfield.mask.u16 != old_value.aclfield.mask.u16; + } + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_IP: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_DST_IP: + case SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP: + case SAI_ACL_ENTRY_ATTR_FIELD_DST_IP: { + return value.aclfield.data.ip4 != old_value.aclfield.data.ip4 || + value.aclfield.mask.ip4 != old_value.aclfield.mask.ip4; + } + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_IPV6: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_DST_IPV6: + case SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6: + case SAI_ACL_ENTRY_ATTR_FIELD_DST_IPV6: { + return memcmp(value.aclfield.data.ip6, old_value.aclfield.data.ip6, sizeof(sai_ip6_t)) || + memcmp(value.aclfield.mask.ip6, old_value.aclfield.mask.ip6, sizeof(sai_ip6_t)); + } + case SAI_ACL_ENTRY_ATTR_FIELD_SRC_MAC: + case SAI_ACL_ENTRY_ATTR_FIELD_DST_MAC: { + return memcmp(value.aclfield.data.mac, old_value.aclfield.data.mac, sizeof(sai_mac_t)) || + memcmp(value.aclfield.mask.mac, old_value.aclfield.mask.mac, sizeof(sai_mac_t)); + } + default: { + return false; + } + } +} + } // namespace p4orch diff --git a/orchagent/p4orch/acl_util.h b/orchagent/p4orch/acl_util.h index c06849506b62..74de14d2a52a 100644 --- a/orchagent/p4orch/acl_util.h +++ b/orchagent/p4orch/acl_util.h @@ -51,6 +51,16 @@ struct P4AclCounter P4AclCounter() : bytes_enabled(false), packets_enabled(false), counter_oid(SAI_NULL_OBJECT_ID) { } + + bool operator==(const P4AclCounter &entry) const + { + return bytes_enabled == entry.bytes_enabled && packets_enabled == entry.packets_enabled; + } + + bool operator!=(const P4AclCounter &entry) const + { + return !(*this == entry); + } }; struct P4AclMeter @@ -71,6 +81,18 @@ struct P4AclMeter type(SAI_METER_TYPE_PACKETS), mode(SAI_POLICER_MODE_TR_TCM) { } + + bool operator==(const P4AclMeter &entry) const + { + return enabled == entry.enabled && type == entry.type && mode == entry.mode && cir == entry.cir && + cburst == entry.cburst && pir == entry.pir && pburst == entry.pburst && + packet_color_actions == entry.packet_color_actions; + } + + bool operator!=(const P4AclMeter &entry) const + { + return !(*this == entry); + } }; struct P4AclMirrorSession @@ -78,12 +100,32 @@ struct P4AclMirrorSession std::string name; std::string key; // KeyGenerator::generateMirrorSessionKey(name) sai_object_id_t oid; + + bool operator==(const P4AclMirrorSession &entry) const + { + return name == entry.name && key == entry.key && oid == entry.oid; + } + + bool operator!=(const P4AclMirrorSession &entry) const + { + return !(*this == entry); + } }; struct P4UdfDataMask { std::vector data; std::vector mask; + + bool operator==(const P4UdfDataMask &entry) const + { + return data == entry.data && mask == entry.mask; + } + + bool operator!=(const P4UdfDataMask &entry) const + { + return !(*this == entry); + } }; struct P4AclRule @@ -120,6 +162,16 @@ struct SaiActionWithParam acl_entry_attr_union_t action; std::string param_name; std::string param_value; + + bool operator==(const SaiActionWithParam &entry) const + { + return action == entry.action && param_name == entry.param_name && param_value == entry.param_value; + } + + bool operator!=(const SaiActionWithParam &entry) const + { + return !(*this == entry); + } }; struct SaiMatchField @@ -128,6 +180,17 @@ struct SaiMatchField acl_table_attr_union_t table_attr; uint32_t bitwidth; Format format; + + bool operator==(const SaiMatchField &entry) const + { + return entry_attr == entry.entry_attr && table_attr == entry.table_attr && bitwidth == entry.bitwidth && + format == entry.format; + } + + bool operator!=(const SaiMatchField &entry) const + { + return !(*this == entry); + } }; struct P4UdfField @@ -137,6 +200,17 @@ struct P4UdfField std::string udf_id; // {group_id}-base{base}-offset{offset} uint16_t offset; // in Bytes sai_udf_base_t base; + + bool operator==(const P4UdfField &entry) const + { + return length == entry.length && group_id == entry.group_id && udf_id == entry.udf_id && + offset == entry.offset && base == entry.base; + } + + bool operator!=(const P4UdfField &entry) const + { + return !(*this == entry); + } }; struct P4AclTableDefinition @@ -152,8 +226,8 @@ struct P4AclTableDefinition std::string meter_unit; std::string counter_unit; // go/p4-composite-fields - // Only SAI attributes for IPv6-64bit(IPV6_WORDn) are supported as sai_field - // elements in composite field + // Only SAI attributes for IPv6-64bit(IPV6_WORDn) are supported as + // sai_field elements in composite field std::map> composite_sai_match_fields_lookup; // go/gpins-acl-udf // p4_match string to a list of P4UdfFields mapping @@ -199,8 +273,6 @@ using P4AclRuleTables = std::map>; #define P4_FORMAT_IPV6 "IPV6" #define P4_FORMAT_STRING "STRING" -// complete p4 match fields and action list: -// https://docs.google.com/document/d/1gtxJe7aPIJgM2hTLo5gm62DuPJHB31eAyRAsV9zjwW0/edit#heading=h.dzb8jjrtxv49 #define P4_MATCH_IN_PORT "SAI_ACL_TABLE_ATTR_FIELD_IN_PORT" #define P4_MATCH_OUT_PORT "SAI_ACL_TABLE_ATTR_FIELD_OUT_PORT" #define P4_MATCH_IN_PORTS "SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS" @@ -251,6 +323,7 @@ using P4AclRuleTables = std::map>; #define P4_MATCH_DST_IPV6_WORD2 "SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD2" #define P4_MATCH_SRC_IPV6_WORD3 "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD3" #define P4_MATCH_SRC_IPV6_WORD2 "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2" +#define P4_MATCH_ROUTE_DST_USER_META "SAI_ACL_TABLE_ATTR_FIELD_ROUTE_DST_USER_META" #define P4_ACTION_PACKET_ACTION "SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION" #define P4_ACTION_REDIRECT "SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT" @@ -351,7 +424,6 @@ using P4AclRuleTables = std::map>; #define GENL_PACKET_TRAP_GROUP_NAME_PREFIX "trap.group.cpu.queue." -#define WHITESPACE " " #define EMPTY_STRING "" #define P4_CPU_QUEUE_MAX_NUM 8 #define IPV6_SINGLE_WORD_BYTES_LENGTH 4 @@ -411,6 +483,7 @@ static const acl_table_attr_lookup_t aclMatchTableAttrLookup = { {P4_MATCH_PACKET_VLAN, SAI_ACL_TABLE_ATTR_FIELD_PACKET_VLAN}, {P4_MATCH_TUNNEL_VNI, SAI_ACL_TABLE_ATTR_FIELD_TUNNEL_VNI}, {P4_MATCH_IPV6_NEXT_HEADER, SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER}, + {P4_MATCH_ROUTE_DST_USER_META, SAI_ACL_TABLE_ATTR_FIELD_ROUTE_DST_USER_META}, }; static const acl_table_attr_format_lookup_t aclMatchTableAttrFormatLookup = { @@ -459,6 +532,7 @@ static const acl_table_attr_format_lookup_t aclMatchTableAttrFormatLookup = { {SAI_ACL_TABLE_ATTR_FIELD_PACKET_VLAN, Format::STRING}, {SAI_ACL_TABLE_ATTR_FIELD_TUNNEL_VNI, Format::HEX_STRING}, {SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ROUTE_DST_USER_META, Format::HEX_STRING}, }; static const acl_table_attr_lookup_t aclCompositeMatchTableAttrLookup = { @@ -514,6 +588,7 @@ static const acl_rule_attr_lookup_t aclMatchEntryAttrLookup = { {P4_MATCH_PACKET_VLAN, SAI_ACL_ENTRY_ATTR_FIELD_PACKET_VLAN}, {P4_MATCH_TUNNEL_VNI, SAI_ACL_ENTRY_ATTR_FIELD_TUNNEL_VNI}, {P4_MATCH_IPV6_NEXT_HEADER, SAI_ACL_ENTRY_ATTR_FIELD_IPV6_NEXT_HEADER}, + {P4_MATCH_ROUTE_DST_USER_META, SAI_ACL_ENTRY_ATTR_FIELD_ROUTE_DST_USER_META}, }; static const acl_rule_attr_lookup_t aclCompositeMatchEntryAttrLookup = { @@ -629,9 +704,6 @@ static std::map aclCounterStatsIdNameMap = { {SAI_POLICER_STAT_RED_BYTES, P4_COUNTER_STATS_RED_BYTES}, }; -// Trim tailing and leading whitespace -std::string trim(const std::string &s); - // Parse ACL table definition APP DB entry action field to P4ActionParamName // action_list and P4PacketActionWithColor action_color_list bool parseAclTableAppDbActionField(const std::string &aggr_actions_str, std::vector *action_list, @@ -644,8 +716,8 @@ ReturnCode validateAndSetSaiMatchFieldJson(const nlohmann::json &match_json, con std::map *sai_match_field_lookup, std::map *ip_type_bit_type_lookup); -// Validate and set composite match field element with kind:sai_field. Composite -// SAI field only support IPv6-64bit now (IPV6_WORDn) +// Validate and set composite match field element with kind:sai_field. +// Composite SAI field only support IPv6-64bit now (IPV6_WORDn) ReturnCode validateAndSetCompositeElementSaiFieldJson( const nlohmann::json &element_match_json, const std::string &p4_match, std::map> *composite_sai_match_fields_lookup, @@ -681,9 +753,10 @@ ReturnCode buildAclTableDefinitionActionFieldValues( bool isSetUserTrapActionInAclTableDefinition( const std::map> &aggr_sai_actions_lookup); -// Build packet color(sai_policer_attr_t) to packet action(sai_packet_action_t) -// map for ACL table definition by P4PacketActionWithColor action map. If packet -// color is empty, then the packet action should add as a SaiActionWithParam +// Build packet color(sai_policer_attr_t) to packet +// action(sai_packet_action_t) map for ACL table definition by +// P4PacketActionWithColor action map. If packet color is empty, then the +// packet action should add as a SaiActionWithParam ReturnCode buildAclTableDefinitionActionColorFieldValues( const std::map> &action_color_lookup, std::map> *aggr_sai_actions_lookup, @@ -702,9 +775,17 @@ ReturnCode setCompositeSaiMatchValue(const acl_entry_attr_union_t attr_name, con ReturnCode setUdfMatchValue(const P4UdfField &udf_field, const std::string &attr_value, sai_attribute_value_t *value, P4UdfDataMask *udf_data_mask, uint16_t bytes_offset); -// Compares the action value difference if the action field is present in both -// new and old ACL rules. Returns true if action values are different. +// Compares the action value difference if the action field is present in +// both new and old ACL rules. Returns true if action values are different. bool isDiffActionFieldValue(const acl_entry_attr_union_t attr_name, const sai_attribute_value_t &value, const sai_attribute_value_t &old_value, const P4AclRule &acl_rule, const P4AclRule &old_acl_rule); + +// Compares the match value difference if the match field is present in +// both new and old ACL rules. Returns true if match values are different. +// This method is used in state verification only. +bool isDiffMatchFieldValue(const acl_entry_attr_union_t attr_name, const sai_attribute_value_t &value, + const sai_attribute_value_t &old_value, const P4AclRule &acl_rule, + const P4AclRule &old_acl_rule); + } // namespace p4orch diff --git a/orchagent/p4orch/gre_tunnel_manager.cpp b/orchagent/p4orch/gre_tunnel_manager.cpp new file mode 100644 index 000000000000..84f48a57b928 --- /dev/null +++ b/orchagent/p4orch/gre_tunnel_manager.cpp @@ -0,0 +1,619 @@ +#include "p4orch/gre_tunnel_manager.h" + +#include +#include +#include +#include + +#include "SaiAttributeList.h" +#include "crmorch.h" +#include "dbconnector.h" +#include "ipaddress.h" +#include "json.hpp" +#include "logger.h" +#include "p4orch/p4orch_util.h" +#include "sai_serialize.h" +#include "swssnet.h" +#include "table.h" +extern "C" +{ +#include "sai.h" +} + +using ::p4orch::kTableKeyDelimiter; + +extern sai_object_id_t gSwitchId; +extern sai_tunnel_api_t *sai_tunnel_api; +extern sai_router_interface_api_t *sai_router_intfs_api; +extern CrmOrch *gCrmOrch; +extern sai_object_id_t gVirtualRouterId; + +namespace +{ + +ReturnCode validateGreTunnelAppDbEntry(const P4GreTunnelAppDbEntry &app_db_entry) +{ + if (app_db_entry.action_str != p4orch::kTunnelAction) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid action " << QuotedVar(app_db_entry.action_str) << " of GRE Tunnel App DB entry"; + } + if (app_db_entry.router_interface_id.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << QuotedVar(prependParamField(p4orch::kTunnelId)) << " field is missing in table entry"; + } + if (app_db_entry.encap_src_ip.isZero()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << QuotedVar(prependParamField(p4orch::kEncapSrcIp)) << " field is missing in table entry"; + } + if (app_db_entry.encap_dst_ip.isZero()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << QuotedVar(prependParamField(p4orch::kEncapDstIp)) << " field is missing in table entry"; + } + return ReturnCode(); +} + +std::vector getSaiAttrs(const P4GreTunnelEntry &gre_tunnel_entry) +{ + std::vector tunnel_attrs; + sai_attribute_t tunnel_attr; + tunnel_attr.id = SAI_TUNNEL_ATTR_TYPE; + tunnel_attr.value.s32 = SAI_TUNNEL_TYPE_IPINIP_GRE; + tunnel_attrs.push_back(tunnel_attr); + + tunnel_attr.id = SAI_TUNNEL_ATTR_PEER_MODE; + tunnel_attr.value.s32 = SAI_TUNNEL_PEER_MODE_P2P; + tunnel_attrs.push_back(tunnel_attr); + + tunnel_attr.id = SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE; + tunnel_attr.value.oid = gre_tunnel_entry.underlay_if_oid; + tunnel_attrs.push_back(tunnel_attr); + + tunnel_attr.id = SAI_TUNNEL_ATTR_OVERLAY_INTERFACE; + tunnel_attr.value.oid = gre_tunnel_entry.overlay_if_oid; + tunnel_attrs.push_back(tunnel_attr); + + tunnel_attr.id = SAI_TUNNEL_ATTR_ENCAP_SRC_IP; + swss::copy(tunnel_attr.value.ipaddr, gre_tunnel_entry.encap_src_ip); + tunnel_attrs.push_back(tunnel_attr); + + tunnel_attr.id = SAI_TUNNEL_ATTR_ENCAP_DST_IP; + swss::copy(tunnel_attr.value.ipaddr, gre_tunnel_entry.encap_dst_ip); + tunnel_attrs.push_back(tunnel_attr); + return tunnel_attrs; +} + +} // namespace + +P4GreTunnelEntry::P4GreTunnelEntry(const std::string &tunnel_id, const std::string &router_interface_id, + const swss::IpAddress &encap_src_ip, const swss::IpAddress &encap_dst_ip) + : tunnel_id(tunnel_id), router_interface_id(router_interface_id), encap_src_ip(encap_src_ip), + encap_dst_ip(encap_dst_ip) +{ + SWSS_LOG_ENTER(); + tunnel_key = KeyGenerator::generateTunnelKey(tunnel_id); +} + +void GreTunnelManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void GreTunnelManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &key); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + const std::string &operation = kfvOp(key_op_fvs_tuple); + + ReturnCode status; + auto app_db_entry_or = deserializeP4GreTunnelAppDbEntry(key, attributes); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + SWSS_LOG_ERROR("Unable to deserialize GRE Tunnel APP DB entry with key %s: %s", + QuotedVar(kfvKey(key_op_fvs_tuple)).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + auto &app_db_entry = *app_db_entry_or; + + const std::string tunnel_key = KeyGenerator::generateTunnelKey(app_db_entry.tunnel_id); + + // Fulfill the operation. + if (operation == SET_COMMAND) + { + status = validateGreTunnelAppDbEntry(app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for GRE Tunnel APP DB entry with key %s: %s", + QuotedVar(kfvKey(key_op_fvs_tuple)).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + auto *gre_tunnel_entry = getGreTunnelEntry(tunnel_key); + if (gre_tunnel_entry == nullptr) + { + // Create new GRE tunnel. + status = processAddRequest(app_db_entry); + } + else + { + // Modify existing GRE tunnel. + status = processUpdateRequest(app_db_entry, gre_tunnel_entry); + } + } + else if (operation == DEL_COMMAND) + { + // Delete GRE tunnel. + status = processDeleteRequest(tunnel_key); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << QuotedVar(operation); + SWSS_LOG_ERROR("%s", status.message().c_str()); + } + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), status, + /*replace=*/true); + } + m_entries.clear(); +} + +P4GreTunnelEntry *GreTunnelManager::getGreTunnelEntry(const std::string &tunnel_key) +{ + SWSS_LOG_ENTER(); + + auto it = m_greTunnelTable.find(tunnel_key); + + if (it == m_greTunnelTable.end()) + { + return nullptr; + } + else + { + return &it->second; + } +}; + +ReturnCodeOr GreTunnelManager::getUnderlayIfFromGreTunnelEntry(const std::string &tunnel_key) +{ + SWSS_LOG_ENTER(); + + auto *tunnel = getGreTunnelEntry(tunnel_key); + if (tunnel == nullptr) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "GRE Tunnel with key " << QuotedVar(tunnel_key) << " was not found."; + } + else + { + return tunnel->router_interface_id; + } +} + +ReturnCodeOr GreTunnelManager::deserializeP4GreTunnelAppDbEntry( + const std::string &key, const std::vector &attributes) +{ + SWSS_LOG_ENTER(); + + P4GreTunnelAppDbEntry app_db_entry = {}; + app_db_entry.encap_src_ip = swss::IpAddress("0.0.0.0"); + app_db_entry.encap_dst_ip = swss::IpAddress("0.0.0.0"); + + try + { + nlohmann::json j = nlohmann::json::parse(key); + app_db_entry.tunnel_id = j[prependMatchField(p4orch::kTunnelId)]; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize GRE tunnel id"; + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == prependParamField(p4orch::kRouterInterfaceId)) + { + app_db_entry.router_interface_id = value; + } + else if (field == prependParamField(p4orch::kEncapSrcIp)) + { + try + { + app_db_entry.encap_src_ip = swss::IpAddress(value); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid IP address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + } + else if (field == prependParamField(p4orch::kEncapDstIp)) + { + try + { + app_db_entry.encap_dst_ip = swss::IpAddress(value); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid IP address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + } + else if (field == p4orch::kAction) + { + app_db_entry.action_str = value; + } + else if (field != p4orch::kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in table entry"; + } + } + + return app_db_entry; +} + +ReturnCode GreTunnelManager::processAddRequest(const P4GreTunnelAppDbEntry &app_db_entry) +{ + SWSS_LOG_ENTER(); + + P4GreTunnelEntry gre_tunnel_entry(app_db_entry.tunnel_id, app_db_entry.router_interface_id, + app_db_entry.encap_src_ip, app_db_entry.encap_dst_ip); + auto status = createGreTunnel(gre_tunnel_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create GRE tunnel with key %s", QuotedVar(gre_tunnel_entry.tunnel_key).c_str()); + } + return status; +} + +ReturnCode GreTunnelManager::createGreTunnel(P4GreTunnelEntry &gre_tunnel_entry) +{ + SWSS_LOG_ENTER(); + + // Check the existence of the GRE tunnel in GRE tunnel manager and centralized + // mapper. + if (getGreTunnelEntry(gre_tunnel_entry.tunnel_key) != nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_EXISTS) + << "GRE tunnel with key " << QuotedVar(gre_tunnel_entry.tunnel_key) + << " already exists in GRE tunnel manager"); + } + if (m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_entry.tunnel_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("GRE tunnel with key " << QuotedVar(gre_tunnel_entry.tunnel_key) + << " already exists in centralized mapper"); + } + + // From centralized mapper, get OID of router interface that GRE tunnel + // depends on. + const auto router_interface_key = KeyGenerator::generateRouterInterfaceKey(gre_tunnel_entry.router_interface_id); + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_interface_key, + &gre_tunnel_entry.underlay_if_oid)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Router intf " << QuotedVar(gre_tunnel_entry.router_interface_id) << " does not exist"); + } + + std::vector overlay_intf_attrs; + + sai_attribute_t overlay_intf_attr; + overlay_intf_attr.id = SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID; + overlay_intf_attr.value.oid = gVirtualRouterId; + overlay_intf_attrs.push_back(overlay_intf_attr); + + overlay_intf_attr.id = SAI_ROUTER_INTERFACE_ATTR_TYPE; + overlay_intf_attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_LOOPBACK; + overlay_intf_attrs.push_back(overlay_intf_attr); + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_router_intfs_api->create_router_interface(&gre_tunnel_entry.overlay_if_oid, gSwitchId, + (uint32_t)overlay_intf_attrs.size(), overlay_intf_attrs.data()), + "Failed to create the Loopback router interface for GRE tunnel " + "SAI_TUNNEL_ATTR_OVERLAY_INTERFACE attribute" + << QuotedVar(gre_tunnel_entry.tunnel_key)); + + // Prepare attributes for the SAI creation call. + std::vector tunnel_attrs = getSaiAttrs(gre_tunnel_entry); + + // Call SAI API. + auto sai_status = sai_tunnel_api->create_tunnel(&gre_tunnel_entry.tunnel_oid, gSwitchId, + (uint32_t)tunnel_attrs.size(), tunnel_attrs.data()); + if (sai_status != SAI_STATUS_SUCCESS) + { + auto status = ReturnCode(sai_status) << "Failed to create GRE tunnel " << QuotedVar(gre_tunnel_entry.tunnel_key) + << " on rif " << QuotedVar(gre_tunnel_entry.router_interface_id); + SWSS_LOG_ERROR("%s", status.message().c_str()); + auto recovery_status = sai_router_intfs_api->remove_router_interface(gre_tunnel_entry.overlay_if_oid); + if (recovery_status != SAI_STATUS_SUCCESS) + { + auto rc = ReturnCode(recovery_status) << "Failed to recover overlay router interface due to SAI call " + "failure: Failed to remove loopback router interface " + << QuotedVar(sai_serialize_object_id(gre_tunnel_entry.overlay_if_oid)) + << " while clean up dependencies."; + SWSS_LOG_ERROR("%s", rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE(rc.message()); + } + return status; + } + + // On successful creation, increment ref count. + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_interface_key); + + // Add created entry to internal table. + m_greTunnelTable.emplace(gre_tunnel_entry.tunnel_key, gre_tunnel_entry); + + // Add the key to OID map to centralized mapper. + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_entry.tunnel_key, gre_tunnel_entry.tunnel_oid); + + return ReturnCode(); +} + +ReturnCode GreTunnelManager::processUpdateRequest(const P4GreTunnelAppDbEntry &app_db_entry, + P4GreTunnelEntry *gre_tunnel_entry) +{ + SWSS_LOG_ENTER(); + + ReturnCode status = ReturnCode(StatusCode::SWSS_RC_UNIMPLEMENTED) + << "Currently GRE tunnel doesn't support update by SAI. GRE tunnel key " + << QuotedVar(gre_tunnel_entry->tunnel_key); + SWSS_LOG_ERROR("%s", status.message().c_str()); + return status; +} + +ReturnCode GreTunnelManager::processDeleteRequest(const std::string &tunnel_key) +{ + SWSS_LOG_ENTER(); + + auto status = removeGreTunnel(tunnel_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove GRE tunnel with key %s", QuotedVar(tunnel_key).c_str()); + } + + return status; +} + +ReturnCode GreTunnelManager::removeGreTunnel(const std::string &tunnel_key) +{ + SWSS_LOG_ENTER(); + + // Check the existence of the GRE tunnel in GRE tunnel manager and centralized + // mapper. + auto *gre_tunnel_entry = getGreTunnelEntry(tunnel_key); + if (gre_tunnel_entry == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "GRE tunnel with key " << QuotedVar(tunnel_key) + << " does not exist in GRE tunnel manager"); + } + + // Check if there is anything referring to the GRE tunnel before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_TUNNEL, tunnel_key, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count for GRE tunnel " + << QuotedVar(tunnel_key)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "GRE tunnel " << QuotedVar(gre_tunnel_entry->tunnel_key) + << " referenced by other objects (ref_count = " << ref_count); + } + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN(sai_tunnel_api->remove_tunnel(gre_tunnel_entry->tunnel_oid), + "Failed to remove GRE tunnel " << QuotedVar(gre_tunnel_entry->tunnel_key)); + + auto sai_status = sai_router_intfs_api->remove_router_interface(gre_tunnel_entry->overlay_if_oid); + if (sai_status != SAI_STATUS_SUCCESS) + { + auto status = ReturnCode(sai_status) << "Failed to remove loopback router interface " + << QuotedVar(sai_serialize_object_id(gre_tunnel_entry->overlay_if_oid)) + << " when removing GRE tunnel " << QuotedVar(gre_tunnel_entry->tunnel_key); + SWSS_LOG_ERROR("%s", status.message().c_str()); + + // Try to recreate the GRE tunnel + std::vector tunnel_attrs = getSaiAttrs(*gre_tunnel_entry); + + // Call SAI API. + auto recovery_status = sai_tunnel_api->create_tunnel(&gre_tunnel_entry->tunnel_oid, gSwitchId, + (uint32_t)tunnel_attrs.size(), tunnel_attrs.data()); + if (recovery_status != SAI_STATUS_SUCCESS) + { + auto rc = ReturnCode(recovery_status) << "Failed to recover the GRE tunnel due to SAI call failure : " + "Failed to create GRE tunnel " + << QuotedVar(gre_tunnel_entry->tunnel_key) << " on rif " + << QuotedVar(gre_tunnel_entry->router_interface_id); + SWSS_LOG_ERROR("%s", rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE(rc.message()); + } + return status; + } + + // On successful deletion, decrement ref count. + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(gre_tunnel_entry->router_interface_id)); + + // Remove the key to OID map to centralized mapper. + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_TUNNEL, tunnel_key); + + // Remove the entry from internal table. + m_greTunnelTable.erase(tunnel_key); + + return ReturnCode(); +} + +std::string GreTunnelManager::verifyState(const std::string &key, const std::vector &tuple) +{ + SWSS_LOG_ENTER(); + + auto pos = key.find_first_of(kTableKeyDelimiter); + if (pos == std::string::npos) + { + return std::string("Invalid key: ") + key; + } + std::string p4rt_table = key.substr(0, pos); + std::string p4rt_key = key.substr(pos + 1); + if (p4rt_table != APP_P4RT_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + std::string table_name; + std::string key_content; + parseP4RTKey(p4rt_key, &table_name, &key_content); + if (table_name != APP_P4RT_TUNNEL_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + + ReturnCode status; + auto app_db_entry_or = deserializeP4GreTunnelAppDbEntry(key_content, tuple); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + std::stringstream msg; + msg << "Unable to deserialize key " << QuotedVar(key) << ": " << status.message(); + return msg.str(); + } + auto &app_db_entry = *app_db_entry_or; + const std::string tunnel_key = KeyGenerator::generateTunnelKey(app_db_entry.tunnel_id); + auto *gre_tunnel_entry = getGreTunnelEntry(tunnel_key); + if (gre_tunnel_entry == nullptr) + { + std::stringstream msg; + msg << "No entry found with key " << QuotedVar(key); + return msg.str(); + } + + std::string cache_result = verifyStateCache(app_db_entry, gre_tunnel_entry); + std::string asic_db_result = verifyStateAsicDb(gre_tunnel_entry); + if (cache_result.empty()) + { + return asic_db_result; + } + if (asic_db_result.empty()) + { + return cache_result; + } + return cache_result + "; " + asic_db_result; +} + +std::string GreTunnelManager::verifyStateCache(const P4GreTunnelAppDbEntry &app_db_entry, + const P4GreTunnelEntry *gre_tunnel_entry) +{ + const std::string tunnel_key = KeyGenerator::generateTunnelKey(app_db_entry.tunnel_id); + ReturnCode status = validateGreTunnelAppDbEntry(app_db_entry); + if (!status.ok()) + { + std::stringstream msg; + msg << "Validation failed for GRE Tunnel DB entry with key " << QuotedVar(tunnel_key) << ": " + << status.message(); + return msg.str(); + } + + if (gre_tunnel_entry->tunnel_key != tunnel_key) + { + std::stringstream msg; + msg << "GreTunnel with key " << QuotedVar(tunnel_key) << " does not match internal cache " + << QuotedVar(gre_tunnel_entry->tunnel_key) << " in Gre Tunnel manager."; + return msg.str(); + } + if (gre_tunnel_entry->tunnel_id != app_db_entry.tunnel_id) + { + std::stringstream msg; + msg << "GreTunnel " << QuotedVar(app_db_entry.tunnel_id) << " does not match internal cache " + << QuotedVar(gre_tunnel_entry->tunnel_id) << " in GreTunnel manager."; + return msg.str(); + } + if (gre_tunnel_entry->router_interface_id != app_db_entry.router_interface_id) + { + std::stringstream msg; + msg << "GreTunnel " << QuotedVar(app_db_entry.tunnel_id) << " with ritf ID " + << QuotedVar(app_db_entry.router_interface_id) << " does not match internal cache " + << QuotedVar(gre_tunnel_entry->router_interface_id) << " in GreTunnel manager."; + return msg.str(); + } + if (gre_tunnel_entry->encap_src_ip.to_string() != app_db_entry.encap_src_ip.to_string()) + { + std::stringstream msg; + msg << "GreTunnel " << QuotedVar(app_db_entry.tunnel_id) << " with source IP " + << QuotedVar(app_db_entry.encap_src_ip.to_string()) << " does not match internal cache " + << QuotedVar(gre_tunnel_entry->encap_src_ip.to_string()) << " in GreTunnel manager."; + return msg.str(); + } + + if (gre_tunnel_entry->encap_dst_ip.to_string() != app_db_entry.encap_dst_ip.to_string()) + { + std::stringstream msg; + msg << "GreTunnel " << QuotedVar(app_db_entry.tunnel_id) << " with destination IP " + << QuotedVar(app_db_entry.encap_dst_ip.to_string()) << " does not match internal cache " + << QuotedVar(gre_tunnel_entry->encap_dst_ip.to_string()) << " in GreTunnel manager."; + return msg.str(); + } + + return m_p4OidMapper->verifyOIDMapping(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_entry->tunnel_key, + gre_tunnel_entry->tunnel_oid); +} + +std::string GreTunnelManager::verifyStateAsicDb(const P4GreTunnelEntry *gre_tunnel_entry) +{ + swss::DBConnector db("ASIC_DB", 0); + swss::Table table(&db, "ASIC_STATE"); + + // Verify Overlay router interface ASIC DB attributes + std::string key = sai_serialize_object_type(SAI_OBJECT_TYPE_ROUTER_INTERFACE) + ":" + + sai_serialize_object_id(gre_tunnel_entry->overlay_if_oid); + std::vector values; + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + + std::vector overlay_intf_attrs; + sai_attribute_t overlay_intf_attr; + overlay_intf_attr.id = SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID; + overlay_intf_attr.value.oid = gVirtualRouterId; + overlay_intf_attrs.push_back(overlay_intf_attr); + overlay_intf_attr.id = SAI_ROUTER_INTERFACE_ATTR_TYPE; + overlay_intf_attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_LOOPBACK; + overlay_intf_attrs.push_back(overlay_intf_attr); + std::vector exp = saimeta::SaiAttributeList::serialize_attr_list( + SAI_OBJECT_TYPE_ROUTER_INTERFACE, (uint32_t)overlay_intf_attrs.size(), overlay_intf_attrs.data(), + /*countOnly=*/false); + verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); + + // Verify Tunnel ASIC DB attributes + std::vector attrs = getSaiAttrs(*gre_tunnel_entry); + exp = saimeta::SaiAttributeList::serialize_attr_list(SAI_OBJECT_TYPE_TUNNEL, (uint32_t)attrs.size(), attrs.data(), + /*countOnly=*/false); + key = + sai_serialize_object_type(SAI_OBJECT_TYPE_TUNNEL) + ":" + sai_serialize_object_id(gre_tunnel_entry->tunnel_oid); + values.clear(); + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + + return verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); +} \ No newline at end of file diff --git a/orchagent/p4orch/gre_tunnel_manager.h b/orchagent/p4orch/gre_tunnel_manager.h new file mode 100644 index 000000000000..deb5b319e348 --- /dev/null +++ b/orchagent/p4orch/gre_tunnel_manager.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +#include "ipaddress.h" +#include "orch.h" +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "p4orch/router_interface_manager.h" +#include "response_publisher_interface.h" +#include "return_code.h" +extern "C" +{ +#include "sai.h" +} + +// P4GreTunnelEntry holds GreTunnelManager's internal cache of P4 GRE tunnel +// entry. Example: P4RT_TABLE:FIXED_TUNNEL_TABLE:{"match/tunnel_id":"tunnel-1"} +// "action" = "mark_for_tunnel_encap", +// "param/router_interface_id" = "intf-eth-1/2/3", +// "param/encap_src_ip" = "2607:f8b0:8096:3110::1", +// "param/encap_dst_ip" = "2607:f8b0:8096:311a::2", +// "controller_metadata" = "..." +struct P4GreTunnelEntry +{ + // Key of this entry, built from tunnel_id. + std::string tunnel_key; + + // Fields from P4 table. + // Match + std::string tunnel_id; + // Action + std::string router_interface_id; + swss::IpAddress encap_src_ip; + swss::IpAddress encap_dst_ip; + + // SAI OID associated with this entry. + sai_object_id_t tunnel_oid = SAI_NULL_OBJECT_ID; + // SAI OID of a loopback rif for SAI_TUNNEL_ATTR_OVERLAY_INTERFACE + sai_object_id_t overlay_if_oid = SAI_NULL_OBJECT_ID; + // SAI OID of the router_interface_id for SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE + sai_object_id_t underlay_if_oid = SAI_NULL_OBJECT_ID; + + P4GreTunnelEntry(const std::string &tunnel_id, const std::string &router_interface_id, + const swss::IpAddress &encap_src_ip, const swss::IpAddress &encap_dst_ip); +}; + +// GreTunnelManager listens to changes in table APP_P4RT_TUNNEL_TABLE_NAME and +// creates/updates/deletes tunnel SAI object accordingly. +class GreTunnelManager : public ObjectManagerInterface +{ + public: + GreTunnelManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + + virtual ~GreTunnelManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + std::string verifyState(const std::string &key, const std::vector &tuple) override; + + ReturnCodeOr getUnderlayIfFromGreTunnelEntry(const std::string &gre_tunnel_key); + + private: + // Gets the internal cached GRE tunnel entry by its key. + // Return nullptr if corresponding GRE tunnel entry is not cached. + P4GreTunnelEntry *getGreTunnelEntry(const std::string &gre_tunnel_key); + + // Deserializes an entry from table APP_P4RT_TUNNEL_TABLE_NAME. + ReturnCodeOr deserializeP4GreTunnelAppDbEntry( + const std::string &key, const std::vector &attributes); + + // Processes add operation for an entry. + ReturnCode processAddRequest(const P4GreTunnelAppDbEntry &app_db_entry); + + // Creates an GRE tunnel in the GRE tunnel table. Return true on success. + ReturnCode createGreTunnel(P4GreTunnelEntry &gre_tunnel_entry); + + // Processes update operation for an entry. + ReturnCode processUpdateRequest(const P4GreTunnelAppDbEntry &app_db_entry, P4GreTunnelEntry *gre_tunnel_entry); + + // Processes delete operation for an entry. + ReturnCode processDeleteRequest(const std::string &gre_tunnel_key); + + // Deletes a GRE tunnel in the GRE tunnel table. Return true on success. + ReturnCode removeGreTunnel(const std::string &gre_tunnel_key); + + std::string verifyStateCache(const P4GreTunnelAppDbEntry &app_db_entry, const P4GreTunnelEntry *gre_tunnel_entry); + std::string verifyStateAsicDb(const P4GreTunnelEntry *gre_tunnel_entry); + + // m_greTunnelTable: gre_tunnel_key, P4GreTunnelEntry + std::unordered_map m_greTunnelTable; + + // Owners of pointers below must outlive this class's instance. + P4OidMapper *m_p4OidMapper; + ResponsePublisherInterface *m_publisher; + std::deque m_entries; + + friend class GreTunnelManagerTest; + friend class NextHopManagerTest; +}; diff --git a/orchagent/p4orch/l3_admit_manager.cpp b/orchagent/p4orch/l3_admit_manager.cpp new file mode 100644 index 000000000000..75d4d6f7d29f --- /dev/null +++ b/orchagent/p4orch/l3_admit_manager.cpp @@ -0,0 +1,460 @@ +#include "p4orch/l3_admit_manager.h" + +#include +#include +#include +#include + +#include "SaiAttributeList.h" +#include "dbconnector.h" +#include "json.hpp" +#include "logger.h" +#include "p4orch/p4orch_util.h" +#include "portsorch.h" +#include "return_code.h" +#include "sai_serialize.h" +#include "table.h" +#include "tokenize.h" +extern "C" +{ +#include "sai.h" +} + +using ::p4orch::kTableKeyDelimiter; + +extern PortsOrch *gPortsOrch; +extern sai_object_id_t gSwitchId; +extern sai_my_mac_api_t *sai_my_mac_api; + +namespace +{ + +ReturnCodeOr> getSaiAttrs(const P4L3AdmitEntry &l3_admit_entry) +{ + std::vector l3_admit_attrs; + sai_attribute_t l3_admit_attr; + + l3_admit_attr.id = SAI_MY_MAC_ATTR_MAC_ADDRESS; + memcpy(l3_admit_attr.value.mac, l3_admit_entry.mac_address_data.getMac(), sizeof(sai_mac_t)); + l3_admit_attrs.push_back(l3_admit_attr); + + l3_admit_attr.id = SAI_MY_MAC_ATTR_MAC_ADDRESS_MASK; + memcpy(l3_admit_attr.value.mac, l3_admit_entry.mac_address_mask.getMac(), sizeof(sai_mac_t)); + l3_admit_attrs.push_back(l3_admit_attr); + + l3_admit_attr.id = SAI_MY_MAC_ATTR_PRIORITY; + l3_admit_attr.value.u32 = l3_admit_entry.priority; + l3_admit_attrs.push_back(l3_admit_attr); + + if (!l3_admit_entry.port_name.empty()) + { + Port port; + if (!gPortsOrch->getPort(l3_admit_entry.port_name, port)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Failed to get port info for port " << QuotedVar(l3_admit_entry.port_name)); + } + l3_admit_attr.id = SAI_MY_MAC_ATTR_PORT_ID; + l3_admit_attr.value.oid = port.m_port_id; + l3_admit_attrs.push_back(l3_admit_attr); + } + + return l3_admit_attrs; +} + +} // namespace + +void L3AdmitManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void L3AdmitManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &key); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + ReturnCode status; + auto app_db_entry_or = deserializeP4L3AdmitAppDbEntry(key, attributes); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + SWSS_LOG_ERROR("Unable to deserialize APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + key).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + auto &app_db_entry = *app_db_entry_or; + + const std::string l3_admit_key = + KeyGenerator::generateL3AdmitKey(app_db_entry.mac_address_data, app_db_entry.mac_address_mask, + app_db_entry.port_name, app_db_entry.priority); + + // Fulfill the operation. + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *l3_admit_entry = getL3AdmitEntry(l3_admit_key); + if (l3_admit_entry == nullptr) + { + // Create new l3 admit. + status = processAddRequest(app_db_entry, l3_admit_key); + } + else + { + // Duplicate l3 admit entry, no-op + status = ReturnCode(StatusCode::SWSS_RC_SUCCESS) + << "L3 Admit entry with the same key received: " << QuotedVar(l3_admit_key); + } + } + else if (operation == DEL_COMMAND) + { + // Delete l3 admit. + status = processDeleteRequest(l3_admit_key); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << QuotedVar(operation); + SWSS_LOG_ERROR("%s", status.message().c_str()); + } + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), status, + /*replace=*/true); + } + m_entries.clear(); +} + +P4L3AdmitEntry *L3AdmitManager::getL3AdmitEntry(const std::string &l3_admit_key) +{ + SWSS_LOG_ENTER(); + + auto it = m_l3AdmitTable.find(l3_admit_key); + + if (it == m_l3AdmitTable.end()) + { + return nullptr; + } + else + { + return &it->second; + } +} + +ReturnCodeOr L3AdmitManager::deserializeP4L3AdmitAppDbEntry( + const std::string &key, const std::vector &attributes) +{ + SWSS_LOG_ENTER(); + + P4L3AdmitAppDbEntry app_db_entry = {}; + + try + { + nlohmann::json j = nlohmann::json::parse(key); + // "match/dst_mac":"00:02:03:04:00:00&ff:ff:ff:ff:00:00" + if (j.find(prependMatchField(p4orch::kDstMac)) != j.end()) + { + std::string dst_mac_data_and_mask = j[prependMatchField(p4orch::kDstMac)]; + const auto &data_and_mask = swss::tokenize(dst_mac_data_and_mask, p4orch::kDataMaskDelimiter); + app_db_entry.mac_address_data = swss::MacAddress(trim(data_and_mask[0])); + if (data_and_mask.size() > 1) + { + app_db_entry.mac_address_mask = swss::MacAddress(trim(data_and_mask[1])); + } + else + { + app_db_entry.mac_address_mask = swss::MacAddress("ff:ff:ff:ff:ff:ff"); + } + } + else + { + // P4RT set "don't care" value for dst_mac - mask should be all 0 + app_db_entry.mac_address_data = swss::MacAddress("00:00:00:00:00:00"); + app_db_entry.mac_address_mask = swss::MacAddress("00:00:00:00:00:00"); + } + + // "priority":2030 + auto priority_j = j[p4orch::kPriority]; + if (!priority_j.is_number_unsigned()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid l3 admit entry priority type: should be uint32_t"; + } + app_db_entry.priority = static_cast(priority_j); + + // "match/in_port":"Ethernet0" + if (j.find(prependMatchField(p4orch::kInPort)) != j.end()) + { + app_db_entry.port_name = j[prependMatchField(p4orch::kInPort)]; + } + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize l3 admit key"; + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + // "action": "admit_to_l3" + if (field == p4orch::kAction) + { + if (value != p4orch::kL3AdmitAction) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected action " << QuotedVar(value) << " in L3 Admit table entry"; + } + } + else if (field != p4orch::kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in L3 Admit table entry"; + } + } + + return app_db_entry; +} + +ReturnCode L3AdmitManager::processAddRequest(const P4L3AdmitAppDbEntry &app_db_entry, const std::string &l3_admit_key) +{ + SWSS_LOG_ENTER(); + + // Check the existence of the l3 admit in l3 admit manager and centralized + // mapper. + if (getL3AdmitEntry(l3_admit_key) != nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_EXISTS) << "l3 admit with key " << QuotedVar(l3_admit_key) + << " already exists in l3 admit manager"); + } + if (m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_MY_MAC, l3_admit_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("l3 admit with key " << QuotedVar(l3_admit_key) + << " already exists in centralized mapper"); + } + // Create L3 admit entry + P4L3AdmitEntry l3_admit_entry(app_db_entry.mac_address_data, app_db_entry.mac_address_mask, app_db_entry.priority, + app_db_entry.port_name); + auto status = createL3Admit(l3_admit_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create l3 admit with key %s", QuotedVar(l3_admit_key).c_str()); + return status; + } + // Increase reference count to port + if (!l3_admit_entry.port_name.empty()) + { + gPortsOrch->increasePortRefCount(l3_admit_entry.port_name); + } + // Add created entry to internal table. + m_l3AdmitTable.emplace(l3_admit_key, l3_admit_entry); + + // Add the key to OID map to centralized mapper. + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_MY_MAC, l3_admit_key, l3_admit_entry.l3_admit_oid); + return status; +} + +ReturnCode L3AdmitManager::createL3Admit(P4L3AdmitEntry &l3_admit_entry) +{ + SWSS_LOG_ENTER(); + + ASSIGN_OR_RETURN(std::vector l3_admit_attrs, getSaiAttrs(l3_admit_entry)); + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_my_mac_api->create_my_mac(&l3_admit_entry.l3_admit_oid, gSwitchId, (uint32_t)l3_admit_attrs.size(), + l3_admit_attrs.data()), + "Failed to create l3 admit with mac:" << QuotedVar(l3_admit_entry.mac_address_data.to_string()) + << "; mac_mask:" << QuotedVar(l3_admit_entry.mac_address_mask.to_string()) + << "; priority:" << QuotedVar(std::to_string(l3_admit_entry.priority)) + << "; in_port:" << QuotedVar(l3_admit_entry.port_name)); + + return ReturnCode(); +} + +ReturnCode L3AdmitManager::processDeleteRequest(const std::string &l3_admit_key) +{ + SWSS_LOG_ENTER(); + + // Check the existence of the l3 admit in l3 admit manager and centralized + // mapper. + auto *l3_admit_entry = getL3AdmitEntry(l3_admit_key); + if (l3_admit_entry == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "l3 admit with key " << QuotedVar(l3_admit_key) + << " does not exist in l3 admit manager"); + } + + // Check if there is anything referring to the l3 admit before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_MY_MAC, l3_admit_key, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count for l3 admit " + << QuotedVar(l3_admit_key)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "l3 admit " << QuotedVar(l3_admit_key) + << " referenced by other objects (ref_count = " << ref_count); + } + + // Call SAI API + auto status = removeL3Admit(l3_admit_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove l3 admit with key %s", QuotedVar(l3_admit_key).c_str()); + return status; + } + + // Decrease reference count to port + if (!l3_admit_entry->port_name.empty()) + { + gPortsOrch->decreasePortRefCount(l3_admit_entry->port_name); + } + // Remove the key to OID map to centralized mapper. + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_MY_MAC, l3_admit_key); + + // Remove the entry from internal table. + m_l3AdmitTable.erase(l3_admit_key); + return status; +} + +ReturnCode L3AdmitManager::removeL3Admit(const std::string &l3_admit_key) +{ + SWSS_LOG_ENTER(); + + auto *l3_admit_entry = getL3AdmitEntry(l3_admit_key); + CHECK_ERROR_AND_LOG_AND_RETURN(sai_my_mac_api->remove_my_mac(l3_admit_entry->l3_admit_oid), + "Failed to remove l3 admit " << QuotedVar(l3_admit_key)); + + return ReturnCode(); +} + +std::string L3AdmitManager::verifyState(const std::string &key, const std::vector &tuple) +{ + SWSS_LOG_ENTER(); + + auto pos = key.find_first_of(kTableKeyDelimiter); + if (pos == std::string::npos) + { + return std::string("Invalid key: ") + key; + } + std::string p4rt_table = key.substr(0, pos); + std::string p4rt_key = key.substr(pos + 1); + if (p4rt_table != APP_P4RT_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + std::string table_name; + std::string key_content; + parseP4RTKey(p4rt_key, &table_name, &key_content); + if (table_name != APP_P4RT_L3_ADMIT_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + + ReturnCode status; + auto app_db_entry_or = deserializeP4L3AdmitAppDbEntry(key_content, tuple); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + std::stringstream msg; + msg << "Unable to deserialize key " << QuotedVar(key) << ": " << status.message(); + return msg.str(); + } + auto &app_db_entry = *app_db_entry_or; + const std::string l3_admit_key = KeyGenerator::generateL3AdmitKey( + app_db_entry.mac_address_data, app_db_entry.mac_address_mask, app_db_entry.port_name, app_db_entry.priority); + auto *l3_admit_entry = getL3AdmitEntry(l3_admit_key); + if (l3_admit_entry == nullptr) + { + std::stringstream msg; + msg << "No entry found with key " << QuotedVar(key); + return msg.str(); + } + + std::string cache_result = verifyStateCache(app_db_entry, l3_admit_entry); + std::string asic_db_result = verifyStateAsicDb(l3_admit_entry); + if (cache_result.empty()) + { + return asic_db_result; + } + if (asic_db_result.empty()) + { + return cache_result; + } + return cache_result + "; " + asic_db_result; +} + +std::string L3AdmitManager::verifyStateCache(const P4L3AdmitAppDbEntry &app_db_entry, + const P4L3AdmitEntry *l3_admit_entry) +{ + const std::string l3_admit_key = KeyGenerator::generateL3AdmitKey( + app_db_entry.mac_address_data, app_db_entry.mac_address_mask, app_db_entry.port_name, app_db_entry.priority); + + if (l3_admit_entry->port_name != app_db_entry.port_name) + { + std::stringstream msg; + msg << "L3 admit " << QuotedVar(l3_admit_key) << " with port " << QuotedVar(app_db_entry.port_name) + << " does not match internal cache " << QuotedVar(l3_admit_entry->port_name) << " in L3 admit manager."; + return msg.str(); + } + if (l3_admit_entry->mac_address_data.to_string() != app_db_entry.mac_address_data.to_string()) + { + std::stringstream msg; + msg << "L3 admit " << QuotedVar(l3_admit_key) << " with MAC addr " << app_db_entry.mac_address_data.to_string() + << " does not match internal cache " << l3_admit_entry->mac_address_data.to_string() + << " in L3 admit manager."; + return msg.str(); + } + if (l3_admit_entry->mac_address_mask.to_string() != app_db_entry.mac_address_mask.to_string()) + { + std::stringstream msg; + msg << "L3 admit " << QuotedVar(l3_admit_key) << " with MAC mask " << app_db_entry.mac_address_mask.to_string() + << " does not match internal cache " << l3_admit_entry->mac_address_mask.to_string() + << " in L3 admit manager."; + return msg.str(); + } + if (l3_admit_entry->priority != app_db_entry.priority) + { + std::stringstream msg; + msg << "L3 admit " << QuotedVar(l3_admit_key) << " with priority " << app_db_entry.priority + << " does not match internal cache " << l3_admit_entry->priority << " in L3 admit manager."; + return msg.str(); + } + + return m_p4OidMapper->verifyOIDMapping(SAI_OBJECT_TYPE_MY_MAC, l3_admit_key, l3_admit_entry->l3_admit_oid); +} + +std::string L3AdmitManager::verifyStateAsicDb(const P4L3AdmitEntry *l3_admit_entry) +{ + auto attrs_or = getSaiAttrs(*l3_admit_entry); + if (!attrs_or.ok()) + { + return std::string("Failed to get SAI attrs: ") + attrs_or.status().message(); + } + std::vector attrs = *attrs_or; + std::vector exp = + saimeta::SaiAttributeList::serialize_attr_list(SAI_OBJECT_TYPE_MY_MAC, (uint32_t)attrs.size(), attrs.data(), + /*countOnly=*/false); + + swss::DBConnector db("ASIC_DB", 0); + swss::Table table(&db, "ASIC_STATE"); + std::string key = + sai_serialize_object_type(SAI_OBJECT_TYPE_MY_MAC) + ":" + sai_serialize_object_id(l3_admit_entry->l3_admit_oid); + std::vector values; + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + + return verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); +} diff --git a/orchagent/p4orch/l3_admit_manager.h b/orchagent/p4orch/l3_admit_manager.h new file mode 100644 index 000000000000..933f5792c83c --- /dev/null +++ b/orchagent/p4orch/l3_admit_manager.h @@ -0,0 +1,98 @@ +#pragma once + +#include + +#include "orch.h" +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "response_publisher_interface.h" +#include "return_code.h" + +#define EMPTY_STRING "" + +struct P4L3AdmitEntry +{ + std::string port_name; // Optional + swss::MacAddress mac_address_data; + swss::MacAddress mac_address_mask; + sai_uint32_t priority; + sai_object_id_t l3_admit_oid = SAI_NULL_OBJECT_ID; + + P4L3AdmitEntry() = default; + P4L3AdmitEntry(const swss::MacAddress &mac_address_data, const swss::MacAddress &mac_address_mask, + const sai_uint32_t &priority, const std::string &port_name) + : port_name(port_name), mac_address_data(mac_address_data), mac_address_mask(mac_address_mask), + priority(priority) + { + } +}; + +// L3Admit manager is responsible for subscribing to APPL_DB FIXED_L3_ADMIT +// table. +// +// Example without optional port +// P4RT_TABLE:FIXED_L3_ADMIT_TABLE:{\"match/dst_mac\":\"00:02:03:04:00:00&ff:ff:ff:ff:00:00\",\"priority\":2030} +// "action": "admit_to_l3" +// "controller_metadata": "..." +// +// Example with optional port +// P4RT_TABLE:FIXED_L3_ADMIT_TABLE:{\"match/dst_mac\":\"00:02:03:04:00:00&ff:ff:ff:ff:00:00\",\"match/in_port\":\"Ethernet0\",\"priority\":2030} +// "action": "admit_to_l3" +// "controller_metadata": "..." +// +// Example without optional port/dst_mac +// P4RT:FIXED_L3_ADMIT_TABLE:{\"priority\":2030} +// "action": "admit_to_l3" +// "controller_metadata": "..." +class L3AdmitManager : public ObjectManagerInterface +{ + public: + L3AdmitManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + + virtual ~L3AdmitManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + std::string verifyState(const std::string &key, const std::vector &tuple) override; + + private: + // Gets the internal cached next hop entry by its key. + // Return nullptr if corresponding next hop entry is not cached. + P4L3AdmitEntry *getL3AdmitEntry(const std::string &l3_admit_key); + + // Deserializes an entry from table APP_P4RT_L3_ADMIT_TABLE_NAME. + ReturnCodeOr deserializeP4L3AdmitAppDbEntry( + const std::string &key, const std::vector &attributes); + + ReturnCode processAddRequest(const P4L3AdmitAppDbEntry &app_db_entry, const std::string &l3_admit_key); + + // Creates a L3 Admit entry. Return true on success. + ReturnCode createL3Admit(P4L3AdmitEntry &l3_admit_entry); + + ReturnCode processDeleteRequest(const std::string &l3_admit_key); + + // Deletes a L3 Admit entry. Return true on success. + ReturnCode removeL3Admit(const std::string &l3_admit_key); + + // state verification DB helper functions. Return err string or empty string. + std::string verifyStateCache(const P4L3AdmitAppDbEntry &app_db_entry, const P4L3AdmitEntry *l3_admit_entry); + std::string verifyStateAsicDb(const P4L3AdmitEntry *l3_admit_entry); + + // m_l3AdmitTable: l3_admit_key, P4L3AdmitEntry + std::unordered_map m_l3AdmitTable; + + ResponsePublisherInterface *m_publisher; + std::deque m_entries; + P4OidMapper *m_p4OidMapper; + + friend class L3AdmitManagerTest; +}; \ No newline at end of file diff --git a/orchagent/p4orch/mirror_session_manager.cpp b/orchagent/p4orch/mirror_session_manager.cpp index 067bc5aa1a8f..dfecb74ad7df 100644 --- a/orchagent/p4orch/mirror_session_manager.cpp +++ b/orchagent/p4orch/mirror_session_manager.cpp @@ -1,10 +1,18 @@ #include "p4orch/mirror_session_manager.h" +#include + +#include "SaiAttributeList.h" +#include "dbconnector.h" #include "json.hpp" #include "p4orch/p4orch_util.h" #include "portsorch.h" +#include "sai_serialize.h" #include "swss/logger.h" #include "swssnet.h" +#include "table.h" + +using ::p4orch::kTableKeyDelimiter; extern PortsOrch *gPortsOrch; extern sai_mirror_api_t *sai_mirror_api; @@ -78,6 +86,72 @@ void MirrorSessionManager::drain() m_entries.clear(); } +ReturnCodeOr> getSaiAttrs(const P4MirrorSessionEntry &mirror_session_entry) +{ + swss::Port port; + if (!gPortsOrch->getPort(mirror_session_entry.port, port)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Failed to get port info for port " << QuotedVar(mirror_session_entry.port)); + } + if (port.m_type != Port::Type::PHY) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Port " << QuotedVar(mirror_session_entry.port) << "'s type " << port.m_type + << " is not physical and is invalid as destination " + "port for mirror packet."); + } + + std::vector attrs; + sai_attribute_t attr; + + attr.id = SAI_MIRROR_SESSION_ATTR_MONITOR_PORT; + attr.value.oid = port.m_port_id; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_TYPE; + attr.value.s32 = SAI_MIRROR_SESSION_TYPE_ENHANCED_REMOTE; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE; + attr.value.s32 = SAI_ERSPAN_ENCAPSULATION_TYPE_MIRROR_L3_GRE_TUNNEL; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION; + attr.value.u8 = MIRROR_SESSION_DEFAULT_IP_HDR_VER; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_TOS; + attr.value.u8 = mirror_session_entry.tos; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_TTL; + attr.value.u8 = mirror_session_entry.ttl; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS; + swss::copy(attr.value.ipaddr, mirror_session_entry.src_ip); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS; + swss::copy(attr.value.ipaddr, mirror_session_entry.dst_ip); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS; + memcpy(attr.value.mac, mirror_session_entry.src_mac.getMac(), sizeof(sai_mac_t)); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS; + memcpy(attr.value.mac, mirror_session_entry.dst_mac.getMac(), sizeof(sai_mac_t)); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE; + attr.value.u16 = GRE_PROTOCOL_ERSPAN; + attrs.push_back(attr); + + return attrs; +} + ReturnCodeOr MirrorSessionManager::deserializeP4MirrorSessionAppDbEntry( const std::string &key, const std::vector &attributes) { @@ -264,68 +338,8 @@ ReturnCode MirrorSessionManager::createMirrorSession(P4MirrorSessionEntry mirror << QuotedVar(mirror_session_entry.mirror_session_key) << " already exists in centralized mapper"); } - - swss::Port port; - if (!gPortsOrch->getPort(mirror_session_entry.port, port)) - { - LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) - << "Failed to get port info for port " << QuotedVar(mirror_session_entry.port)); - } - if (port.m_type != Port::Type::PHY) - { - LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) - << "Port " << QuotedVar(mirror_session_entry.port) << "'s type " << port.m_type - << " is not physical and is invalid as destination " - "port for mirror packet."); - } - // Prepare attributes for the SAI creation call. - std::vector attrs; - sai_attribute_t attr; - - attr.id = SAI_MIRROR_SESSION_ATTR_MONITOR_PORT; - attr.value.oid = port.m_port_id; - attrs.push_back(attr); - - attr.id = SAI_MIRROR_SESSION_ATTR_TYPE; - attr.value.s32 = SAI_MIRROR_SESSION_TYPE_ENHANCED_REMOTE; - attrs.push_back(attr); - - attr.id = SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE; - attr.value.s32 = SAI_ERSPAN_ENCAPSULATION_TYPE_MIRROR_L3_GRE_TUNNEL; - attrs.push_back(attr); - - attr.id = SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION; - attr.value.u8 = MIRROR_SESSION_DEFAULT_IP_HDR_VER; - attrs.push_back(attr); - - attr.id = SAI_MIRROR_SESSION_ATTR_TOS; - attr.value.u8 = mirror_session_entry.tos; - attrs.push_back(attr); - - attr.id = SAI_MIRROR_SESSION_ATTR_TTL; - attr.value.u8 = mirror_session_entry.ttl; - attrs.push_back(attr); - - attr.id = SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS; - swss::copy(attr.value.ipaddr, mirror_session_entry.src_ip); - attrs.push_back(attr); - - attr.id = SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS; - swss::copy(attr.value.ipaddr, mirror_session_entry.dst_ip); - attrs.push_back(attr); - - attr.id = SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS; - memcpy(attr.value.mac, mirror_session_entry.src_mac.getMac(), sizeof(sai_mac_t)); - attrs.push_back(attr); - - attr.id = SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS; - memcpy(attr.value.mac, mirror_session_entry.dst_mac.getMac(), sizeof(sai_mac_t)); - attrs.push_back(attr); - - attr.id = SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE; - attr.value.u16 = GRE_PROTOCOL_ERSPAN; - attrs.push_back(attr); + ASSIGN_OR_RETURN(std::vector attrs, getSaiAttrs(mirror_session_entry)); // Call SAI API. CHECK_ERROR_AND_LOG_AND_RETURN( @@ -723,4 +737,163 @@ ReturnCode MirrorSessionManager::processDeleteRequest(const std::string &mirror_ return ReturnCode(); } +std::string MirrorSessionManager::verifyState(const std::string &key, const std::vector &tuple) +{ + SWSS_LOG_ENTER(); + + auto pos = key.find_first_of(kTableKeyDelimiter); + if (pos == std::string::npos) + { + return std::string("Invalid key: ") + key; + } + std::string p4rt_table = key.substr(0, pos); + std::string p4rt_key = key.substr(pos + 1); + if (p4rt_table != APP_P4RT_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + std::string table_name; + std::string key_content; + parseP4RTKey(p4rt_key, &table_name, &key_content); + if (table_name != APP_P4RT_MIRROR_SESSION_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + + ReturnCode status; + auto app_db_entry_or = deserializeP4MirrorSessionAppDbEntry(key_content, tuple); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + std::stringstream msg; + msg << "Unable to deserialize key " << QuotedVar(key) << ": " << status.message(); + return msg.str(); + } + auto &app_db_entry = *app_db_entry_or; + const std::string mirror_session_key = KeyGenerator::generateMirrorSessionKey(app_db_entry.mirror_session_id); + auto *mirror_session_entry = getMirrorSessionEntry(mirror_session_key); + if (mirror_session_entry == nullptr) + { + std::stringstream msg; + msg << "No entry found with key " << QuotedVar(key); + return msg.str(); + } + + std::string cache_result = verifyStateCache(app_db_entry, mirror_session_entry); + std::string asic_db_result = verifyStateAsicDb(mirror_session_entry); + if (cache_result.empty()) + { + return asic_db_result; + } + if (asic_db_result.empty()) + { + return cache_result; + } + return cache_result + "; " + asic_db_result; +} + +std::string MirrorSessionManager::verifyStateCache(const P4MirrorSessionAppDbEntry &app_db_entry, + const P4MirrorSessionEntry *mirror_session_entry) +{ + const std::string mirror_session_key = KeyGenerator::generateMirrorSessionKey(app_db_entry.mirror_session_id); + + if (mirror_session_entry->mirror_session_key != mirror_session_key) + { + std::stringstream msg; + msg << "Mirror section with key " << QuotedVar(mirror_session_key) << " does not match internal cache " + << QuotedVar(mirror_session_entry->mirror_session_key) << " in mirror section manager."; + return msg.str(); + } + if (mirror_session_entry->mirror_session_id != app_db_entry.mirror_session_id) + { + std::stringstream msg; + msg << "Mirror section " << QuotedVar(app_db_entry.mirror_session_id) << " does not match internal cache " + << QuotedVar(mirror_session_entry->mirror_session_id) << " in mirror section manager."; + return msg.str(); + } + if (mirror_session_entry->port != app_db_entry.port) + { + std::stringstream msg; + msg << "Mirror section " << QuotedVar(app_db_entry.mirror_session_id) << " with port " + << QuotedVar(app_db_entry.port) << " does not match internal cache " + << QuotedVar(mirror_session_entry->port) << " in mirror section manager."; + return msg.str(); + } + if (mirror_session_entry->src_ip.to_string() != app_db_entry.src_ip.to_string()) + { + std::stringstream msg; + msg << "Mirror section " << QuotedVar(app_db_entry.mirror_session_id) << " with source IP " + << app_db_entry.src_ip.to_string() << " does not match internal cache " + << mirror_session_entry->src_ip.to_string() << " in mirror section manager."; + return msg.str(); + } + if (mirror_session_entry->dst_ip.to_string() != app_db_entry.dst_ip.to_string()) + { + std::stringstream msg; + msg << "Mirror section " << QuotedVar(app_db_entry.mirror_session_id) << " with dest IP " + << app_db_entry.dst_ip.to_string() << " does not match internal cache " + << mirror_session_entry->dst_ip.to_string() << " in mirror section manager."; + return msg.str(); + } + if (mirror_session_entry->src_mac.to_string() != app_db_entry.src_mac.to_string()) + { + std::stringstream msg; + msg << "Mirror section " << QuotedVar(app_db_entry.mirror_session_id) << " with source MAC " + << app_db_entry.src_mac.to_string() << " does not match internal cache " + << mirror_session_entry->src_mac.to_string() << " in mirror section manager."; + return msg.str(); + } + if (mirror_session_entry->dst_mac.to_string() != app_db_entry.dst_mac.to_string()) + { + std::stringstream msg; + msg << "Mirror section " << QuotedVar(app_db_entry.mirror_session_id) << " with dest MAC " + << app_db_entry.dst_mac.to_string() << " does not match internal cache " + << mirror_session_entry->dst_mac.to_string() << " in mirror section manager."; + return msg.str(); + } + if (mirror_session_entry->ttl != app_db_entry.ttl) + { + std::stringstream msg; + msg << "Mirror section " << QuotedVar(app_db_entry.mirror_session_id) << " with ttl " << app_db_entry.ttl + << " does not match internal cache " << mirror_session_entry->ttl << " in mirror section manager."; + return msg.str(); + } + if (mirror_session_entry->tos != app_db_entry.tos) + { + std::stringstream msg; + msg << "Mirror section " << QuotedVar(app_db_entry.mirror_session_id) << " with tos " << app_db_entry.tos + << " does not match internal cache " << mirror_session_entry->tos << " in mirror section manager."; + return msg.str(); + } + + return m_p4OidMapper->verifyOIDMapping(SAI_OBJECT_TYPE_MIRROR_SESSION, mirror_session_entry->mirror_session_key, + mirror_session_entry->mirror_session_oid); +} + +std::string MirrorSessionManager::verifyStateAsicDb(const P4MirrorSessionEntry *mirror_session_entry) +{ + auto attrs_or = getSaiAttrs(*mirror_session_entry); + if (!attrs_or.ok()) + { + return std::string("Failed to get SAI attrs: ") + attrs_or.status().message(); + } + std::vector attrs = *attrs_or; + std::vector exp = saimeta::SaiAttributeList::serialize_attr_list( + SAI_OBJECT_TYPE_MIRROR_SESSION, (uint32_t)attrs.size(), attrs.data(), + /*countOnly=*/false); + + swss::DBConnector db("ASIC_DB", 0); + swss::Table table(&db, "ASIC_STATE"); + std::string key = sai_serialize_object_type(SAI_OBJECT_TYPE_MIRROR_SESSION) + ":" + + sai_serialize_object_id(mirror_session_entry->mirror_session_oid); + std::vector values; + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + + return verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); +} + } // namespace p4orch diff --git a/orchagent/p4orch/mirror_session_manager.h b/orchagent/p4orch/mirror_session_manager.h index c41dc07eb384..3cbd46ee1597 100644 --- a/orchagent/p4orch/mirror_session_manager.h +++ b/orchagent/p4orch/mirror_session_manager.h @@ -85,6 +85,8 @@ class MirrorSessionManager : public ObjectManagerInterface void drain() override; + std::string verifyState(const std::string &key, const std::vector &tuple) override; + private: ReturnCodeOr deserializeP4MirrorSessionAppDbEntry( const std::string &key, const std::vector &attributes); @@ -108,6 +110,11 @@ class MirrorSessionManager : public ObjectManagerInterface ReturnCode processDeleteRequest(const std::string &mirror_session_key); + // state verification DB helper functions. Return err string or empty string. + std::string verifyStateCache(const P4MirrorSessionAppDbEntry &app_db_entry, + const P4MirrorSessionEntry *mirror_session_entry); + std::string verifyStateAsicDb(const P4MirrorSessionEntry *mirror_session_entry); + std::unordered_map m_mirrorSessionTable; // Owners of pointers below must outlive this class's instance. diff --git a/orchagent/p4orch/neighbor_manager.cpp b/orchagent/p4orch/neighbor_manager.cpp index 059aa766988b..9a903baa6a2c 100644 --- a/orchagent/p4orch/neighbor_manager.cpp +++ b/orchagent/p4orch/neighbor_manager.cpp @@ -4,23 +4,51 @@ #include #include +#include "SaiAttributeList.h" #include "crmorch.h" +#include "dbconnector.h" #include "json.hpp" #include "logger.h" #include "orch.h" #include "p4orch/p4orch_util.h" +#include "sai_serialize.h" #include "swssnet.h" +#include "table.h" extern "C" { #include "sai.h" } +using ::p4orch::kTableKeyDelimiter; + extern sai_object_id_t gSwitchId; extern sai_neighbor_api_t *sai_neighbor_api; extern CrmOrch *gCrmOrch; +namespace +{ + +std::vector getSaiAttrs(const P4NeighborEntry &neighbor_entry) +{ + std::vector attrs; + sai_attribute_t attr; + attr.id = SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS; + memcpy(attr.value.mac, neighbor_entry.dst_mac_address.getMac(), sizeof(sai_mac_t)); + attrs.push_back(attr); + + // Do not program host route. + // This is mainly for neighbor with IPv6 link-local addresses. + attr.id = SAI_NEIGHBOR_ENTRY_ATTR_NO_HOST_ROUTE; + attr.value.booldata = true; + attrs.push_back(attr); + + return attrs; +} + +} // namespace + P4NeighborEntry::P4NeighborEntry(const std::string &router_interface_id, const swss::IpAddress &ip_address, const swss::MacAddress &mac_address) { @@ -34,6 +62,25 @@ P4NeighborEntry::P4NeighborEntry(const std::string &router_interface_id, const s neighbor_key = KeyGenerator::generateNeighborKey(router_intf_id, neighbor_id); } +ReturnCodeOr NeighborManager::getSaiEntry(const P4NeighborEntry &neighbor_entry) +{ + const std::string &router_intf_key = neighbor_entry.router_intf_key; + sai_object_id_t router_intf_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, &router_intf_oid)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Router intf key " << QuotedVar(router_intf_key) + << " does not exist in certralized map"); + } + + sai_neighbor_entry_t neigh_entry; + neigh_entry.switch_id = gSwitchId; + copy(neigh_entry.ip_address, neighbor_entry.neighbor_id); + neigh_entry.rif_id = router_intf_oid; + + return neigh_entry; +} + ReturnCodeOr NeighborManager::deserializeNeighborEntry( const std::string &key, const std::vector &attributes) { @@ -138,37 +185,14 @@ ReturnCode NeighborManager::createNeighbor(P4NeighborEntry &neighbor_entry) << " already exists in centralized map"); } - const std::string &router_intf_key = neighbor_entry.router_intf_key; - sai_object_id_t router_intf_oid; - if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, &router_intf_oid)) - { - LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) - << "Router intf key " << QuotedVar(router_intf_key) - << " does not exist in certralized map"); - } - - neighbor_entry.neigh_entry.switch_id = gSwitchId; - copy(neighbor_entry.neigh_entry.ip_address, neighbor_entry.neighbor_id); - neighbor_entry.neigh_entry.rif_id = router_intf_oid; + ASSIGN_OR_RETURN(neighbor_entry.neigh_entry, getSaiEntry(neighbor_entry)); + auto attrs = getSaiAttrs(neighbor_entry); - std::vector neigh_attrs; - sai_attribute_t neigh_attr; - neigh_attr.id = SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS; - memcpy(neigh_attr.value.mac, neighbor_entry.dst_mac_address.getMac(), sizeof(sai_mac_t)); - neigh_attrs.push_back(neigh_attr); - - // Do not program host route. - // This is mainly for neighbor with IPv6 link-local addresses. - neigh_attr.id = SAI_NEIGHBOR_ENTRY_ATTR_NO_HOST_ROUTE; - neigh_attr.value.booldata = true; - neigh_attrs.push_back(neigh_attr); - - CHECK_ERROR_AND_LOG_AND_RETURN(sai_neighbor_api->create_neighbor_entry(&neighbor_entry.neigh_entry, - static_cast(neigh_attrs.size()), - neigh_attrs.data()), + CHECK_ERROR_AND_LOG_AND_RETURN(sai_neighbor_api->create_neighbor_entry( + &neighbor_entry.neigh_entry, static_cast(attrs.size()), attrs.data()), "Failed to create neighbor with key " << QuotedVar(neighbor_key)); - m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, neighbor_entry.router_intf_key); if (neighbor_entry.neighbor_id.isV4()) { gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEIGHBOR); @@ -374,3 +398,141 @@ void NeighborManager::drain() } m_entries.clear(); } + +std::string NeighborManager::verifyState(const std::string &key, const std::vector &tuple) +{ + SWSS_LOG_ENTER(); + + auto pos = key.find_first_of(kTableKeyDelimiter); + if (pos == std::string::npos) + { + return std::string("Invalid key: ") + key; + } + std::string p4rt_table = key.substr(0, pos); + std::string p4rt_key = key.substr(pos + 1); + if (p4rt_table != APP_P4RT_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + std::string table_name; + std::string key_content; + parseP4RTKey(p4rt_key, &table_name, &key_content); + if (table_name != APP_P4RT_NEIGHBOR_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + + ReturnCode status; + auto app_db_entry_or = deserializeNeighborEntry(key_content, tuple); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + std::stringstream msg; + msg << "Unable to deserialize key " << QuotedVar(key) << ": " << status.message(); + return msg.str(); + } + auto &app_db_entry = *app_db_entry_or; + + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(app_db_entry.router_intf_id, app_db_entry.neighbor_id); + auto *neighbor_entry = getNeighborEntry(neighbor_key); + if (neighbor_entry == nullptr) + { + std::stringstream msg; + msg << "No entry found with key " << QuotedVar(key); + return msg.str(); + } + + std::string cache_result = verifyStateCache(app_db_entry, neighbor_entry); + std::string asic_db_result = verifyStateAsicDb(neighbor_entry); + if (cache_result.empty()) + { + return asic_db_result; + } + if (asic_db_result.empty()) + { + return cache_result; + } + return cache_result + "; " + asic_db_result; +} + +std::string NeighborManager::verifyStateCache(const P4NeighborAppDbEntry &app_db_entry, + const P4NeighborEntry *neighbor_entry) +{ + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(app_db_entry.router_intf_id, app_db_entry.neighbor_id); + ReturnCode status = validateNeighborAppDbEntry(app_db_entry); + if (!status.ok()) + { + std::stringstream msg; + msg << "Validation failed for neighbor DB entry with key " << QuotedVar(neighbor_key) << ": " + << status.message(); + return msg.str(); + } + + if (neighbor_entry->router_intf_id != app_db_entry.router_intf_id) + { + std::stringstream msg; + msg << "Neighbor " << QuotedVar(neighbor_key) << " with ritf ID " << QuotedVar(app_db_entry.router_intf_id) + << " does not match internal cache " << QuotedVar(neighbor_entry->router_intf_id) + << " in neighbor manager."; + return msg.str(); + } + if (neighbor_entry->neighbor_id.to_string() != app_db_entry.neighbor_id.to_string()) + { + std::stringstream msg; + msg << "Neighbor " << QuotedVar(neighbor_key) << " with neighbor ID " << app_db_entry.neighbor_id.to_string() + << " does not match internal cache " << neighbor_entry->neighbor_id.to_string() << " in neighbor manager."; + return msg.str(); + } + if (neighbor_entry->dst_mac_address.to_string() != app_db_entry.dst_mac_address.to_string()) + { + std::stringstream msg; + msg << "Neighbor " << QuotedVar(neighbor_key) << " with dest MAC " << app_db_entry.dst_mac_address.to_string() + << " does not match internal cache " << neighbor_entry->dst_mac_address.to_string() + << " in neighbor manager."; + return msg.str(); + } + if (neighbor_entry->router_intf_key != KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_intf_id)) + { + std::stringstream msg; + msg << "Neighbor " << QuotedVar(neighbor_key) << " does not match internal cache on ritf key " + << QuotedVar(neighbor_entry->router_intf_key) << " in neighbor manager."; + return msg.str(); + } + if (neighbor_entry->neighbor_key != neighbor_key) + { + std::stringstream msg; + msg << "Neighbor " << QuotedVar(neighbor_key) << " does not match internal cache on neighbor key " + << QuotedVar(neighbor_entry->neighbor_key) << " in neighbor manager."; + return msg.str(); + } + return ""; +} + +std::string NeighborManager::verifyStateAsicDb(const P4NeighborEntry *neighbor_entry) +{ + auto sai_entry_or = getSaiEntry(*neighbor_entry); + if (!sai_entry_or.ok()) + { + return std::string("Failed to get SAI entry: ") + sai_entry_or.status().message(); + } + sai_neighbor_entry_t sai_entry = *sai_entry_or; + auto attrs = getSaiAttrs(*neighbor_entry); + std::vector exp = saimeta::SaiAttributeList::serialize_attr_list( + SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, (uint32_t)attrs.size(), attrs.data(), + /*countOnly=*/false); + + swss::DBConnector db("ASIC_DB", 0); + swss::Table table(&db, "ASIC_STATE"); + std::string key = + sai_serialize_object_type(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY) + ":" + sai_serialize_neighbor_entry(sai_entry); + std::vector values; + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + + return verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); +} diff --git a/orchagent/p4orch/neighbor_manager.h b/orchagent/p4orch/neighbor_manager.h index 2ede9de76378..4165bb90ed52 100644 --- a/orchagent/p4orch/neighbor_manager.h +++ b/orchagent/p4orch/neighbor_manager.h @@ -51,6 +51,7 @@ class NeighborManager : public ObjectManagerInterface void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; void drain() override; + std::string verifyState(const std::string &key, const std::vector &tuple) override; private: ReturnCodeOr deserializeNeighborEntry(const std::string &key, @@ -63,6 +64,9 @@ class NeighborManager : public ObjectManagerInterface ReturnCode processAddRequest(const P4NeighborAppDbEntry &app_db_entry, const std::string &neighbor_key); ReturnCode processUpdateRequest(const P4NeighborAppDbEntry &app_db_entry, P4NeighborEntry *neighbor_entry); ReturnCode processDeleteRequest(const std::string &neighbor_key); + std::string verifyStateCache(const P4NeighborAppDbEntry &app_db_entry, const P4NeighborEntry *neighbor_entry); + std::string verifyStateAsicDb(const P4NeighborEntry *neighbor_entry); + ReturnCodeOr getSaiEntry(const P4NeighborEntry &neighbor_entry); P4OidMapper *m_p4OidMapper; P4NeighborTable m_neighborTable; diff --git a/orchagent/p4orch/next_hop_manager.cpp b/orchagent/p4orch/next_hop_manager.cpp index 3e2d9ff54866..2a9bbcf8f941 100644 --- a/orchagent/p4orch/next_hop_manager.cpp +++ b/orchagent/p4orch/next_hop_manager.cpp @@ -4,29 +4,145 @@ #include #include +#include "SaiAttributeList.h" #include "crmorch.h" +#include "dbconnector.h" #include "ipaddress.h" #include "json.hpp" #include "logger.h" +#include "p4orch/p4orch.h" #include "p4orch/p4orch_util.h" +#include "sai_serialize.h" #include "swssnet.h" +#include "table.h" extern "C" { #include "sai.h" } +using ::p4orch::kTableKeyDelimiter; + extern sai_object_id_t gSwitchId; extern sai_next_hop_api_t *sai_next_hop_api; extern CrmOrch *gCrmOrch; +extern P4Orch *gP4Orch; P4NextHopEntry::P4NextHopEntry(const std::string &next_hop_id, const std::string &router_interface_id, - const swss::IpAddress &neighbor_id) - : next_hop_id(next_hop_id), router_interface_id(router_interface_id), neighbor_id(neighbor_id) + const std::string &gre_tunnel_id, const swss::IpAddress &neighbor_id) + : next_hop_id(next_hop_id), router_interface_id(router_interface_id), gre_tunnel_id(gre_tunnel_id), + neighbor_id(neighbor_id) { SWSS_LOG_ENTER(); next_hop_key = KeyGenerator::generateNextHopKey(next_hop_id); } +namespace +{ + +ReturnCode validateAppDbEntry(const P4NextHopAppDbEntry &app_db_entry) +{ + if (app_db_entry.action_str != p4orch::kSetIpNexthop && app_db_entry.action_str != p4orch::kSetNexthop && + app_db_entry.action_str != p4orch::kSetTunnelNexthop) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid action " << QuotedVar(app_db_entry.action_str) << " of Nexthop App DB entry"; + } + if (app_db_entry.neighbor_id.isZero()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Missing field " << QuotedVar(prependParamField(p4orch::kNeighborId)) << " for action " + << QuotedVar(p4orch::kSetIpNexthop) << " in table entry"; + } + if (app_db_entry.action_str == p4orch::kSetIpNexthop || app_db_entry.action_str == p4orch::kSetNexthop) + { + if (!app_db_entry.gre_tunnel_id.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(prependParamField(p4orch::kTunnelId)) << " for action " + << QuotedVar(p4orch::kSetIpNexthop) << " in table entry"; + } + if (app_db_entry.router_interface_id.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Missing field " << QuotedVar(prependParamField(p4orch::kRouterInterfaceId)) << " for action " + << QuotedVar(p4orch::kSetIpNexthop) << " in table entry"; + } + } + + if (app_db_entry.action_str == p4orch::kSetTunnelNexthop) + { + if (!app_db_entry.router_interface_id.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(prependParamField(p4orch::kRouterInterfaceId)) << " for action " + << QuotedVar(p4orch::kSetTunnelNexthop) << " in table entry"; + } + if (app_db_entry.gre_tunnel_id.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Missing field " << QuotedVar(prependParamField(p4orch::kTunnelId)) << " for action " + << QuotedVar(p4orch::kSetTunnelNexthop) << " in table entry"; + } + } + return ReturnCode(); +} + +} // namespace + +ReturnCodeOr> NextHopManager::getSaiAttrs(const P4NextHopEntry &next_hop_entry) +{ + std::vector next_hop_attrs; + sai_attribute_t next_hop_attr; + + if (!next_hop_entry.gre_tunnel_id.empty()) + { + // From centralized mapper and, get gre tunnel that next hop depends on. Get + // underlay router interface from gre tunnel manager, + sai_object_id_t tunnel_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_TUNNEL, + KeyGenerator::generateTunnelKey(next_hop_entry.gre_tunnel_id), &tunnel_oid)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "GRE Tunnel " << QuotedVar(next_hop_entry.gre_tunnel_id) << " does not exist"); + } + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_TYPE; + next_hop_attr.value.s32 = SAI_NEXT_HOP_TYPE_TUNNEL_ENCAP; + next_hop_attrs.push_back(next_hop_attr); + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_TUNNEL_ID; + next_hop_attr.value.oid = tunnel_oid; + next_hop_attrs.push_back(next_hop_attr); + } + else + { + // From centralized mapper, get OID of router interface that next hop + // depends on. + sai_object_id_t rif_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(next_hop_entry.router_interface_id), + &rif_oid)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Router intf " << QuotedVar(next_hop_entry.router_interface_id) + << " does not exist"); + } + next_hop_attr.id = SAI_NEXT_HOP_ATTR_TYPE; + next_hop_attr.value.s32 = SAI_NEXT_HOP_TYPE_IP; + next_hop_attrs.push_back(next_hop_attr); + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID; + next_hop_attr.value.oid = rif_oid; + next_hop_attrs.push_back(next_hop_attr); + } + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_IP; + swss::copy(next_hop_attr.value.ipaddr, next_hop_entry.neighbor_id); + next_hop_attrs.push_back(next_hop_attr); + + return next_hop_attrs; +} + void NextHopManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) { m_entries.push_back(entry); @@ -63,6 +179,16 @@ void NextHopManager::drain() const std::string &operation = kfvOp(key_op_fvs_tuple); if (operation == SET_COMMAND) { + status = validateAppDbEntry(app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for Nexthop APP DB entry with key %s: %s", + QuotedVar(kfvKey(key_op_fvs_tuple)).c_str(), status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } auto *next_hop_entry = getNextHopEntry(next_hop_key); if (next_hop_entry == nullptr) { @@ -113,6 +239,7 @@ ReturnCodeOr NextHopManager::deserializeP4NextHopAppDbEntry SWSS_LOG_ENTER(); P4NextHopAppDbEntry app_db_entry = {}; + app_db_entry.neighbor_id = swss::IpAddress("0.0.0.0"); try { @@ -131,7 +258,6 @@ ReturnCodeOr NextHopManager::deserializeP4NextHopAppDbEntry if (field == prependParamField(p4orch::kRouterInterfaceId)) { app_db_entry.router_interface_id = value; - app_db_entry.is_set_router_interface_id = true; } else if (field == prependParamField(p4orch::kNeighborId)) { @@ -144,9 +270,16 @@ ReturnCodeOr NextHopManager::deserializeP4NextHopAppDbEntry return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid IP address " << QuotedVar(value) << " of field " << QuotedVar(field); } - app_db_entry.is_set_neighbor_id = true; } - else if (field != p4orch::kAction && field != p4orch::kControllerMetadata) + else if (field == prependParamField(p4orch::kTunnelId)) + { + app_db_entry.gre_tunnel_id = value; + } + else if (field == p4orch::kAction) + { + app_db_entry.action_str = value; + } + else if (field != p4orch::kControllerMetadata) { return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unexpected field " << QuotedVar(field) << " in table entry"; @@ -160,7 +293,8 @@ ReturnCode NextHopManager::processAddRequest(const P4NextHopAppDbEntry &app_db_e { SWSS_LOG_ENTER(); - P4NextHopEntry next_hop_entry(app_db_entry.next_hop_id, app_db_entry.router_interface_id, app_db_entry.neighbor_id); + P4NextHopEntry next_hop_entry(app_db_entry.next_hop_id, app_db_entry.router_interface_id, + app_db_entry.gre_tunnel_id, app_db_entry.neighbor_id); auto status = createNextHop(next_hop_entry); if (!status.ok()) { @@ -187,20 +321,23 @@ ReturnCode NextHopManager::createNextHop(P4NextHopEntry &next_hop_entry) << " already exists in centralized mapper"); } - // From centralized mapper, get OID of router interface that next hop depends - // on. - const auto router_interface_key = KeyGenerator::generateRouterInterfaceKey(next_hop_entry.router_interface_id); - sai_object_id_t rif_oid; - if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_interface_key, &rif_oid)) + std::string router_interface_id = next_hop_entry.router_interface_id; + if (!next_hop_entry.gre_tunnel_id.empty()) { - LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) - << "Router intf " << QuotedVar(next_hop_entry.router_interface_id) << " does not exist"); + auto underlay_if_or = gP4Orch->getGreTunnelManager()->getUnderlayIfFromGreTunnelEntry( + KeyGenerator::generateTunnelKey(next_hop_entry.gre_tunnel_id)); + if (!underlay_if_or.ok()) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "GRE Tunnel " << QuotedVar(next_hop_entry.gre_tunnel_id) + << " does not exist in GRE Tunnel Manager"); + } + router_interface_id = *underlay_if_or; } // Neighbor doesn't have OID and the IP addr needed in next hop creation is // neighbor_id, so only check neighbor existence in centralized mapper. - const auto neighbor_key = - KeyGenerator::generateNeighborKey(next_hop_entry.router_interface_id, next_hop_entry.neighbor_id); + const auto neighbor_key = KeyGenerator::generateNeighborKey(router_interface_id, next_hop_entry.neighbor_id); if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key)) { LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) @@ -208,31 +345,26 @@ ReturnCode NextHopManager::createNextHop(P4NextHopEntry &next_hop_entry) << " does not exist in centralized mapper"); } - // Prepare attributes for the SAI creation call. - std::vector next_hop_attrs; - sai_attribute_t next_hop_attr; - - next_hop_attr.id = SAI_NEXT_HOP_ATTR_TYPE; - next_hop_attr.value.s32 = SAI_NEXT_HOP_TYPE_IP; - next_hop_attrs.push_back(next_hop_attr); - - next_hop_attr.id = SAI_NEXT_HOP_ATTR_IP; - swss::copy(next_hop_attr.value.ipaddr, next_hop_entry.neighbor_id); - next_hop_attrs.push_back(next_hop_attr); - - next_hop_attr.id = SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID; - next_hop_attr.value.oid = rif_oid; - next_hop_attrs.push_back(next_hop_attr); + ASSIGN_OR_RETURN(std::vector attrs, getSaiAttrs(next_hop_entry)); // Call SAI API. CHECK_ERROR_AND_LOG_AND_RETURN(sai_next_hop_api->create_next_hop(&next_hop_entry.next_hop_oid, gSwitchId, - (uint32_t)next_hop_attrs.size(), - next_hop_attrs.data()), - "Failed to create next hop " << QuotedVar(next_hop_entry.next_hop_key) << " on rif " - << QuotedVar(next_hop_entry.router_interface_id)); + (uint32_t)attrs.size(), attrs.data()), + "Failed to create next hop " << QuotedVar(next_hop_entry.next_hop_key)); + + if (!next_hop_entry.gre_tunnel_id.empty()) + { + // On successful creation, increment ref count for tunnel object + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_TUNNEL, + KeyGenerator::generateTunnelKey(next_hop_entry.gre_tunnel_id)); + } + else + { + // On successful creation, increment ref count for router intf object + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(next_hop_entry.router_interface_id)); + } - // On successful creation, increment ref count. - m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_interface_key); m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key); if (next_hop_entry.neighbor_id.isV4()) { @@ -308,12 +440,35 @@ ReturnCode NextHopManager::removeNextHop(const std::string &next_hop_key) CHECK_ERROR_AND_LOG_AND_RETURN(sai_next_hop_api->remove_next_hop(next_hop_entry->next_hop_oid), "Failed to remove next hop " << QuotedVar(next_hop_entry->next_hop_key)); - // On successful deletion, decrement ref count. - m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, - KeyGenerator::generateRouterInterfaceKey(next_hop_entry->router_interface_id)); + if (!next_hop_entry->gre_tunnel_id.empty()) + { + // On successful deletion, decrement ref count for tunnel object + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_TUNNEL, + KeyGenerator::generateTunnelKey(next_hop_entry->gre_tunnel_id)); + } + else + { + // On successful deletion, decrement ref count for router intf object + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(next_hop_entry->router_interface_id)); + } + + std::string router_interface_id = next_hop_entry->router_interface_id; + if (!next_hop_entry->gre_tunnel_id.empty()) + { + auto underlay_if_or = gP4Orch->getGreTunnelManager()->getUnderlayIfFromGreTunnelEntry( + KeyGenerator::generateTunnelKey(next_hop_entry->gre_tunnel_id)); + if (!underlay_if_or.ok()) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "GRE Tunnel " << QuotedVar(next_hop_entry->gre_tunnel_id) + << " does not exist in GRE Tunnel Manager"); + } + router_interface_id = *underlay_if_or; + } m_p4OidMapper->decreaseRefCount( SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, - KeyGenerator::generateNeighborKey(next_hop_entry->router_interface_id, next_hop_entry->neighbor_id)); + KeyGenerator::generateNeighborKey(router_interface_id, next_hop_entry->neighbor_id)); if (next_hop_entry->neighbor_id.isV4()) { gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEXTHOP); @@ -331,3 +486,132 @@ ReturnCode NextHopManager::removeNextHop(const std::string &next_hop_key) return ReturnCode(); } + +std::string NextHopManager::verifyState(const std::string &key, const std::vector &tuple) +{ + SWSS_LOG_ENTER(); + + auto pos = key.find_first_of(kTableKeyDelimiter); + if (pos == std::string::npos) + { + return std::string("Invalid key: ") + key; + } + std::string p4rt_table = key.substr(0, pos); + std::string p4rt_key = key.substr(pos + 1); + if (p4rt_table != APP_P4RT_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + std::string table_name; + std::string key_content; + parseP4RTKey(p4rt_key, &table_name, &key_content); + if (table_name != APP_P4RT_NEXTHOP_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + + ReturnCode status; + auto app_db_entry_or = deserializeP4NextHopAppDbEntry(key_content, tuple); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + std::stringstream msg; + msg << "Unable to deserialize key " << QuotedVar(key) << ": " << status.message(); + return msg.str(); + } + auto &app_db_entry = *app_db_entry_or; + const std::string next_hop_key = KeyGenerator::generateNextHopKey(app_db_entry.next_hop_id); + auto *next_hop_entry = getNextHopEntry(next_hop_key); + if (next_hop_entry == nullptr) + { + std::stringstream msg; + msg << "No entry found with key " << QuotedVar(key); + return msg.str(); + } + + std::string cache_result = verifyStateCache(app_db_entry, next_hop_entry); + std::string asic_db_result = verifyStateAsicDb(next_hop_entry); + if (cache_result.empty()) + { + return asic_db_result; + } + if (asic_db_result.empty()) + { + return cache_result; + } + return cache_result + "; " + asic_db_result; +} + +std::string NextHopManager::verifyStateCache(const P4NextHopAppDbEntry &app_db_entry, + const P4NextHopEntry *next_hop_entry) +{ + const std::string next_hop_key = KeyGenerator::generateNextHopKey(app_db_entry.next_hop_id); + if (next_hop_entry->next_hop_key != next_hop_key) + { + std::stringstream msg; + msg << "Nexthop with key " << QuotedVar(next_hop_key) << " does not match internal cache " + << QuotedVar(next_hop_entry->next_hop_key) << " in nexthop manager."; + return msg.str(); + } + if (next_hop_entry->next_hop_id != app_db_entry.next_hop_id) + { + std::stringstream msg; + msg << "Nexthop " << QuotedVar(app_db_entry.next_hop_id) << " does not match internal cache " + << QuotedVar(next_hop_entry->next_hop_id) << " in nexthop manager."; + return msg.str(); + } + if (next_hop_entry->router_interface_id != app_db_entry.router_interface_id) + { + std::stringstream msg; + msg << "Nexthop " << QuotedVar(app_db_entry.next_hop_id) << " with ritf ID " + << QuotedVar(app_db_entry.router_interface_id) << " does not match internal cache " + << QuotedVar(next_hop_entry->router_interface_id) << " in nexthop manager."; + return msg.str(); + } + if (next_hop_entry->neighbor_id.to_string() != app_db_entry.neighbor_id.to_string()) + { + std::stringstream msg; + msg << "Nexthop " << QuotedVar(app_db_entry.next_hop_id) << " with neighbor ID " + << app_db_entry.neighbor_id.to_string() << " does not match internal cache " + << next_hop_entry->neighbor_id.to_string() << " in nexthop manager."; + return msg.str(); + } + + if (next_hop_entry->gre_tunnel_id != app_db_entry.gre_tunnel_id) + { + std::stringstream msg; + msg << "Nexthop " << QuotedVar(app_db_entry.next_hop_id) << " with GRE tunnel ID " + << QuotedVar(app_db_entry.gre_tunnel_id) << " does not match internal cache " + << QuotedVar(next_hop_entry->gre_tunnel_id) << " in nexthop manager."; + return msg.str(); + } + + return m_p4OidMapper->verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_entry->next_hop_key, + next_hop_entry->next_hop_oid); +} + +std::string NextHopManager::verifyStateAsicDb(const P4NextHopEntry *next_hop_entry) +{ + auto attrs_or = getSaiAttrs(*next_hop_entry); + if (!attrs_or.ok()) + { + return std::string("Failed to get SAI attrs: ") + attrs_or.status().message(); + } + std::vector attrs = *attrs_or; + std::vector exp = + saimeta::SaiAttributeList::serialize_attr_list(SAI_OBJECT_TYPE_NEXT_HOP, (uint32_t)attrs.size(), attrs.data(), + /*countOnly=*/false); + + swss::DBConnector db("ASIC_DB", 0); + swss::Table table(&db, "ASIC_STATE"); + std::string key = sai_serialize_object_type(SAI_OBJECT_TYPE_NEXT_HOP) + ":" + + sai_serialize_object_id(next_hop_entry->next_hop_oid); + std::vector values; + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + + return verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); +} diff --git a/orchagent/p4orch/next_hop_manager.h b/orchagent/p4orch/next_hop_manager.h index 7b4a318f87a2..eda1ac00011c 100644 --- a/orchagent/p4orch/next_hop_manager.h +++ b/orchagent/p4orch/next_hop_manager.h @@ -6,6 +6,7 @@ #include "ipaddress.h" #include "orch.h" +#include "p4orch/gre_tunnel_manager.h" #include "p4orch/neighbor_manager.h" #include "p4orch/object_manager_interface.h" #include "p4orch/p4oidmapper.h" @@ -29,13 +30,14 @@ struct P4NextHopEntry std::string next_hop_id; // Action std::string router_interface_id; + std::string gre_tunnel_id; swss::IpAddress neighbor_id; // SAI OID associated with this entry. - sai_object_id_t next_hop_oid = 0; + sai_object_id_t next_hop_oid = SAI_NULL_OBJECT_ID; P4NextHopEntry(const std::string &next_hop_id, const std::string &router_interface_id, - const swss::IpAddress &neighbor_id); + const std::string &gre_tunnel_id, const swss::IpAddress &neighbor_id); }; // NextHopManager listens to changes in table APP_P4RT_NEXTHOP_TABLE_NAME and @@ -57,6 +59,7 @@ class NextHopManager : public ObjectManagerInterface void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; void drain() override; + std::string verifyState(const std::string &key, const std::vector &tuple) override; private: // Gets the internal cached next hop entry by its key. @@ -82,6 +85,15 @@ class NextHopManager : public ObjectManagerInterface // Deletes an next hop in the next hop table. Return true on success. ReturnCode removeNextHop(const std::string &next_hop_key); + // Verifies internal cache for an entry. + std::string verifyStateCache(const P4NextHopAppDbEntry &app_db_entry, const P4NextHopEntry *next_hop_entry); + + // Verifies ASIC DB for an entry. + std::string verifyStateAsicDb(const P4NextHopEntry *next_hop_entry); + + // Returns the SAI attributes for an entry. + ReturnCodeOr> getSaiAttrs(const P4NextHopEntry &next_hop_entry); + // m_nextHopTable: next_hop_key, P4NextHopEntry std::unordered_map m_nextHopTable; diff --git a/orchagent/p4orch/object_manager_interface.h b/orchagent/p4orch/object_manager_interface.h index ec9775f8e4d2..17b6e9ec84fe 100644 --- a/orchagent/p4orch/object_manager_interface.h +++ b/orchagent/p4orch/object_manager_interface.h @@ -12,4 +12,7 @@ class ObjectManagerInterface // Processes all entries in the queue virtual void drain() = 0; + + // StateVerification helper function for the manager + virtual std::string verifyState(const std::string &key, const std::vector &tuple) = 0; }; diff --git a/orchagent/p4orch/p4oidmapper.cpp b/orchagent/p4orch/p4oidmapper.cpp index f4ff6e34332b..63215846a62e 100644 --- a/orchagent/p4orch/p4oidmapper.cpp +++ b/orchagent/p4orch/p4oidmapper.cpp @@ -1,6 +1,7 @@ #include "p4oidmapper.h" #include +#include #include #include "logger.h" @@ -41,7 +42,8 @@ bool P4OidMapper::setOID(_In_ sai_object_type_t object_type, _In_ const std::str return true; } -bool P4OidMapper::getOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, _Out_ sai_object_id_t *oid) +bool P4OidMapper::getOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, + _Out_ sai_object_id_t *oid) const { SWSS_LOG_ENTER(); @@ -57,12 +59,12 @@ bool P4OidMapper::getOID(_In_ sai_object_type_t object_type, _In_ const std::str return false; } - *oid = m_oidTables[object_type][key].sai_oid; + *oid = m_oidTables[object_type].at(key).sai_oid; return true; } bool P4OidMapper::getRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key, - _Out_ uint32_t *ref_count) + _Out_ uint32_t *ref_count) const { SWSS_LOG_ENTER(); @@ -80,7 +82,7 @@ bool P4OidMapper::getRefCount(_In_ sai_object_type_t object_type, _In_ const std return false; } - *ref_count = m_oidTables[object_type][key].ref_count; + *ref_count = m_oidTables[object_type].at(key).ref_count; return true; } @@ -117,14 +119,14 @@ void P4OidMapper::eraseAllOIDs(_In_ sai_object_type_t object_type) m_table.del(""); } -size_t P4OidMapper::getNumEntries(_In_ sai_object_type_t object_type) +size_t P4OidMapper::getNumEntries(_In_ sai_object_type_t object_type) const { SWSS_LOG_ENTER(); return (m_oidTables[object_type].size()); } -bool P4OidMapper::existsOID(_In_ sai_object_type_t object_type, _In_ const std::string &key) +bool P4OidMapper::existsOID(_In_ sai_object_type_t object_type, _In_ const std::string &key) const { SWSS_LOG_ENTER(); @@ -178,3 +180,40 @@ bool P4OidMapper::decreaseRefCount(_In_ sai_object_type_t object_type, _In_ cons m_oidTables[object_type][key].ref_count--; return true; } + +std::string P4OidMapper::verifyOIDMapping(_In_ sai_object_type_t object_type, _In_ const std::string &key, + _In_ sai_object_id_t oid) +{ + SWSS_LOG_ENTER(); + + sai_object_id_t mapper_oid; + if (!getOID(object_type, key, &mapper_oid)) + { + std::stringstream msg; + msg << "OID not found in mapper for key " << key; + return msg.str(); + } + if (mapper_oid != oid) + { + std::stringstream msg; + msg << "OID mismatched in mapper for key " << key << ": " << sai_serialize_object_id(oid) << " vs " + << sai_serialize_object_id(mapper_oid); + return msg.str(); + } + std::string db_oid; + if (!m_table.hget("", convertToDBField(object_type, key), db_oid)) + { + std::stringstream msg; + msg << "OID not found in mapper DB for key " << key; + return msg.str(); + } + if (db_oid != sai_serialize_object_id(oid)) + { + std::stringstream msg; + msg << "OID mismatched in mapper DB for key " << key << ": " << db_oid << " vs " + << sai_serialize_object_id(oid); + return msg.str(); + } + + return ""; +} diff --git a/orchagent/p4orch/p4oidmapper.h b/orchagent/p4orch/p4oidmapper.h index 6f7b86ab8f4e..325acf9503ce 100644 --- a/orchagent/p4orch/p4oidmapper.h +++ b/orchagent/p4orch/p4oidmapper.h @@ -37,11 +37,11 @@ class P4OidMapper // Gets oid for the given key for the SAI object_type. // Returns true on success. - bool getOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, _Out_ sai_object_id_t *oid); + bool getOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, _Out_ sai_object_id_t *oid) const; // Gets the reference count for the given key for the SAI object_type. // Returns true on success. - bool getRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key, _Out_ uint32_t *ref_count); + bool getRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key, _Out_ uint32_t *ref_count) const; // Erases oid for the given key for the SAI object_type. // This function checks if the reference count is zero or not before the @@ -54,11 +54,11 @@ class P4OidMapper void eraseAllOIDs(_In_ sai_object_type_t object_type); // Gets the number of oids for the SAI object_type. - size_t getNumEntries(_In_ sai_object_type_t object_type); + size_t getNumEntries(_In_ sai_object_type_t object_type) const; // Checks whether OID mapping exists for the given key for the specific // object type. - bool existsOID(_In_ sai_object_type_t object_type, _In_ const std::string &key); + bool existsOID(_In_ sai_object_type_t object_type, _In_ const std::string &key) const; // Increases the reference count for the given object. // Returns true on success. @@ -68,6 +68,12 @@ class P4OidMapper // Returns true on success. bool decreaseRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key); + // Verifies the OID mapping. + // Returns an empty string if the input has the correct mapping. Returns a + // non-empty error string otherwise. + std::string verifyOIDMapping(_In_ sai_object_type_t object_type, _In_ const std::string &key, + _In_ sai_object_id_t oid); + private: struct MapperEntry { diff --git a/orchagent/p4orch/p4orch.cpp b/orchagent/p4orch/p4orch.cpp index 57d50aa5ce29..717f23bc9313 100644 --- a/orchagent/p4orch/p4orch.cpp +++ b/orchagent/p4orch/p4orch.cpp @@ -10,6 +10,8 @@ #include "orch.h" #include "p4orch/acl_rule_manager.h" #include "p4orch/acl_table_manager.h" +#include "p4orch/gre_tunnel_manager.h" +#include "p4orch/l3_admit_manager.h" #include "p4orch/neighbor_manager.h" #include "p4orch/next_hop_manager.h" #include "p4orch/route_manager.h" @@ -28,6 +30,7 @@ P4Orch::P4Orch(swss::DBConnector *db, std::vector tableNames, VRFOr SWSS_LOG_ENTER(); m_routerIntfManager = std::make_unique(&m_p4OidMapper, &m_publisher); + m_greTunnelManager = std::make_unique(&m_p4OidMapper, &m_publisher); m_neighborManager = std::make_unique(&m_p4OidMapper, &m_publisher); m_nextHopManager = std::make_unique(&m_p4OidMapper, &m_publisher); m_routeManager = std::make_unique(&m_p4OidMapper, vrfOrch, &m_publisher); @@ -35,17 +38,21 @@ P4Orch::P4Orch(swss::DBConnector *db, std::vector tableNames, VRFOr m_aclTableManager = std::make_unique(&m_p4OidMapper, &m_publisher); m_aclRuleManager = std::make_unique(&m_p4OidMapper, vrfOrch, coppOrch, &m_publisher); m_wcmpManager = std::make_unique(&m_p4OidMapper, &m_publisher); + m_l3AdmitManager = std::make_unique(&m_p4OidMapper, &m_publisher); m_p4TableToManagerMap[APP_P4RT_ROUTER_INTERFACE_TABLE_NAME] = m_routerIntfManager.get(); m_p4TableToManagerMap[APP_P4RT_NEIGHBOR_TABLE_NAME] = m_neighborManager.get(); + m_p4TableToManagerMap[APP_P4RT_TUNNEL_TABLE_NAME] = m_greTunnelManager.get(); m_p4TableToManagerMap[APP_P4RT_NEXTHOP_TABLE_NAME] = m_nextHopManager.get(); m_p4TableToManagerMap[APP_P4RT_IPV4_TABLE_NAME] = m_routeManager.get(); m_p4TableToManagerMap[APP_P4RT_IPV6_TABLE_NAME] = m_routeManager.get(); m_p4TableToManagerMap[APP_P4RT_MIRROR_SESSION_TABLE_NAME] = m_mirrorSessionManager.get(); m_p4TableToManagerMap[APP_P4RT_ACL_TABLE_DEFINITION_NAME] = m_aclTableManager.get(); m_p4TableToManagerMap[APP_P4RT_WCMP_GROUP_TABLE_NAME] = m_wcmpManager.get(); + m_p4TableToManagerMap[APP_P4RT_L3_ADMIT_TABLE_NAME] = m_l3AdmitManager.get(); m_p4ManagerPrecedence.push_back(m_routerIntfManager.get()); + m_p4ManagerPrecedence.push_back(m_greTunnelManager.get()); m_p4ManagerPrecedence.push_back(m_neighborManager.get()); m_p4ManagerPrecedence.push_back(m_nextHopManager.get()); m_p4ManagerPrecedence.push_back(m_wcmpManager.get()); @@ -53,6 +60,7 @@ P4Orch::P4Orch(swss::DBConnector *db, std::vector tableNames, VRFOr m_p4ManagerPrecedence.push_back(m_mirrorSessionManager.get()); m_p4ManagerPrecedence.push_back(m_aclTableManager.get()); m_p4ManagerPrecedence.push_back(m_aclRuleManager.get()); + m_p4ManagerPrecedence.push_back(m_l3AdmitManager.get()); // Add timer executor to update ACL counters stats in COUNTERS_DB auto interv = timespec{.tv_sec = P4_COUNTERS_READ_INTERVAL, .tv_nsec = 0}; @@ -235,3 +243,8 @@ p4orch::WcmpManager *P4Orch::getWcmpManager() { return m_wcmpManager.get(); } + +GreTunnelManager *P4Orch::getGreTunnelManager() +{ + return m_greTunnelManager.get(); +} diff --git a/orchagent/p4orch/p4orch.h b/orchagent/p4orch/p4orch.h index 42159f398196..e39041802bf4 100644 --- a/orchagent/p4orch/p4orch.h +++ b/orchagent/p4orch/p4orch.h @@ -12,6 +12,8 @@ #include "orch.h" #include "p4orch/acl_rule_manager.h" #include "p4orch/acl_table_manager.h" +#include "p4orch/gre_tunnel_manager.h" +#include "p4orch/l3_admit_manager.h" #include "p4orch/mirror_session_manager.h" #include "p4orch/neighbor_manager.h" #include "p4orch/next_hop_manager.h" @@ -34,6 +36,7 @@ class P4Orch : public Orch p4orch::AclTableManager *getAclTableManager(); p4orch::AclRuleManager *getAclRuleManager(); p4orch::WcmpManager *getWcmpManager(); + GreTunnelManager *getGreTunnelManager(); private: void doTask(Consumer &consumer); @@ -49,6 +52,7 @@ class P4Orch : public Orch swss::SelectableTimer *m_aclCounterStatsTimer; P4OidMapper m_p4OidMapper; std::unique_ptr m_routerIntfManager; + std::unique_ptr m_greTunnelManager; std::unique_ptr m_neighborManager; std::unique_ptr m_nextHopManager; std::unique_ptr m_routeManager; @@ -56,6 +60,7 @@ class P4Orch : public Orch std::unique_ptr m_aclTableManager; std::unique_ptr m_aclRuleManager; std::unique_ptr m_wcmpManager; + std::unique_ptr m_l3AdmitManager; // Notification consumer for port state change swss::NotificationConsumer *m_portStatusNotificationConsumer; diff --git a/orchagent/p4orch/p4orch_util.cpp b/orchagent/p4orch/p4orch_util.cpp index e5d44794364f..5ff0c058d414 100644 --- a/orchagent/p4orch/p4orch_util.cpp +++ b/orchagent/p4orch/p4orch_util.cpp @@ -29,6 +29,57 @@ void parseP4RTKey(const std::string &key, std::string *table_name, std::string * *key_content = key.substr(pos + 1); } +std::string verifyAttrs(const std::vector &targets, + const std::vector &exp, const std::vector &opt, + bool allow_unknown) +{ + std::map exp_map; + for (const auto &fv : exp) + { + exp_map[fvField(fv)] = fvValue(fv); + } + std::map opt_map; + for (const auto &fv : opt) + { + opt_map[fvField(fv)] = fvValue(fv); + } + + std::set fields; + for (const auto &fv : targets) + { + fields.insert(fvField(fv)); + bool found = false; + if (exp_map.count(fvField(fv))) + { + found = true; + if (fvValue(fv) != exp_map.at(fvField(fv))) + { + return fvField(fv) + " value mismatch, exp " + exp_map.at(fvField(fv)) + " got " + fvValue(fv); + } + } + if (opt_map.count(fvField(fv))) + { + found = true; + if (fvValue(fv) != opt_map.at(fvField(fv))) + { + return fvField(fv) + " value mismatch, exp " + opt_map.at(fvField(fv)) + " got " + fvValue(fv); + } + } + if (!found && !allow_unknown) + { + return std::string("Unexpected field ") + fvField(fv); + } + } + for (const auto &it : exp_map) + { + if (!fields.count(it.first)) + { + return std::string("Missing field ") + it.first; + } + } + return ""; +} + std::string KeyGenerator::generateRouteKey(const std::string &vrf_id, const swss::IpPrefix &ip_prefix) { std::map fv_map = { @@ -80,6 +131,27 @@ std::string KeyGenerator::generateAclRuleKey(const std::map fv_map = {}; + fv_map.emplace(std::string(p4orch::kMatchPrefix) + p4orch::kFieldDelimiter + p4orch::kDstMac, + mac_address_data.to_string() + p4orch::kDataMaskDelimiter + mac_address_mask.to_string()); + if (!port_name.empty()) + { + fv_map.emplace(std::string(p4orch::kMatchPrefix) + p4orch::kFieldDelimiter + p4orch::kInPort, port_name); + } + fv_map.emplace(p4orch::kPriority, std::to_string(priority)); + return generateKey(fv_map); +} + +std::string KeyGenerator::generateTunnelKey(const std::string &tunnel_id) +{ + std::map fv_map = {{p4orch::kTunnelId, tunnel_id}}; + return generateKey(fv_map); +} + std::string KeyGenerator::generateKey(const std::map &fv_map) { std::string key; @@ -101,3 +173,10 @@ std::string KeyGenerator::generateKey(const std::map & return key; } + +std::string trim(const std::string &s) +{ + size_t end = s.find_last_not_of(" "); + size_t start = s.find_first_not_of(" "); + return (end == std::string::npos) ? "" : s.substr(start, end - start + 1); +} diff --git a/orchagent/p4orch/p4orch_util.h b/orchagent/p4orch/p4orch_util.h index a3684a5fb86c..995ccf9b2732 100644 --- a/orchagent/p4orch/p4orch_util.h +++ b/orchagent/p4orch/p4orch_util.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -9,6 +10,7 @@ #include "ipaddress.h" #include "ipprefix.h" #include "macaddress.h" +#include "table.h" namespace p4orch { @@ -16,6 +18,7 @@ namespace p4orch // Field names in P4RT APP DB entry. constexpr char *kRouterInterfaceId = "router_interface_id"; constexpr char *kPort = "port"; +constexpr char *kInPort = "in_port"; constexpr char *kSrcMac = "src_mac"; constexpr char *kAction = "action"; constexpr char *kActions = "actions"; @@ -24,13 +27,22 @@ constexpr char *kWatchPort = "watch_port"; constexpr char *kNeighborId = "neighbor_id"; constexpr char *kDstMac = "dst_mac"; constexpr char *kNexthopId = "nexthop_id"; +constexpr char *kTunnelId = "tunnel_id"; constexpr char *kVrfId = "vrf_id"; constexpr char *kIpv4Dst = "ipv4_dst"; constexpr char *kIpv6Dst = "ipv6_dst"; constexpr char *kWcmpGroupId = "wcmp_group_id"; +constexpr char *kRouteMetadata = "route_metadata"; constexpr char *kSetNexthopId = "set_nexthop_id"; constexpr char *kSetWcmpGroupId = "set_wcmp_group_id"; +constexpr char *kSetNexthopIdAndMetadata = "set_nexthop_id_and_metadata"; +constexpr char *kSetWcmpGroupIdAndMetadata = "set_wcmp_group_id_and_metadata"; +constexpr char *kSetMetadataAndDrop = "set_metadata_and_drop"; +constexpr char *kSetNexthop = "set_nexthop"; +constexpr char *kSetIpNexthop = "set_ip_nexthop"; +constexpr char *kSetTunnelNexthop = "set_tunnel_encap_nexthop"; constexpr char *kDrop = "drop"; +constexpr char *kTrap = "trap"; constexpr char *kStage = "stage"; constexpr char *kSize = "size"; constexpr char *kPriority = "priority"; @@ -61,9 +73,13 @@ constexpr char *kAclUdfOffset = "offset"; constexpr char *kMirrorSessionId = "mirror_session_id"; constexpr char *kSrcIp = "src_ip"; constexpr char *kDstIp = "dst_ip"; +constexpr char *kEncapSrcIp = "encap_src_ip"; +constexpr char *kEncapDstIp = "encap_dst_ip"; constexpr char *kTtl = "ttl"; constexpr char *kTos = "tos"; constexpr char *kMirrorAsIpv4Erspan = "mirror_as_ipv4_erspan"; +constexpr char *kL3AdmitAction = "admit_to_l3"; +constexpr char *kTunnelAction = "mark_for_tunnel_encap"; } // namespace p4orch // Prepends "match/" to the input string str to construct a new string. @@ -89,6 +105,17 @@ struct P4NeighborAppDbEntry bool is_set_dst_mac = false; }; +struct P4GreTunnelAppDbEntry +{ + // Match + std::string tunnel_id; + // Action + std::string router_interface_id; + swss::IpAddress encap_src_ip; + swss::IpAddress encap_dst_ip; + std::string action_str; +}; + // P4NextHopAppDbEntry holds entry deserialized from table // APP_P4RT_NEXTHOP_TABLE_NAME. struct P4NextHopAppDbEntry @@ -97,9 +124,20 @@ struct P4NextHopAppDbEntry std::string next_hop_id; // Fields std::string router_interface_id; + std::string gre_tunnel_id; swss::IpAddress neighbor_id; - bool is_set_router_interface_id = false; - bool is_set_neighbor_id = false; + std::string action_str; +}; + +// P4L3AdmitAppDbEntry holds entry deserialized from table +// APP_P4RT_L3_ADMIT_TABLE_NAME. +struct P4L3AdmitAppDbEntry +{ + // Key (match parameters) + std::string port_name; // Optional + swss::MacAddress mac_address_data; + swss::MacAddress mac_address_mask; + uint32_t priority; }; struct P4MirrorSessionAppDbEntry @@ -190,6 +228,19 @@ struct P4AclRuleAppDbEntry // Key content: {content} void parseP4RTKey(const std::string &key, std::string *table_name, std::string *key_content); +// State verification function that verifies the table attributes. +// Returns a non-empty string if verification fails. +// +// targets: the table attributes that we need to verify. +// exp: the attributes that must be included and have correct value. +// opt: the attributes that can be excluded, but must have correct value if +// included. +// allow_unknown: if set to false, verification will fail if there is an +// attribute that is not in exp or opt. +std::string verifyAttrs(const std::vector &targets, + const std::vector &exp, const std::vector &opt, + bool allow_unknown); + // class KeyGenerator includes member functions to generate keys for entries // stored in P4 Orch managers. class KeyGenerator @@ -210,6 +261,12 @@ class KeyGenerator static std::string generateAclRuleKey(const std::map &match_fields, const std::string &priority); + static std::string generateL3AdmitKey(const swss::MacAddress &mac_address_data, + const swss::MacAddress &mac_address_mask, const std::string &port_name, + const uint32_t &priority); + + static std::string generateTunnelKey(const std::string &tunnel_id); + // Generates key used by object managers and centralized mapper. // Takes map of as input and returns a concatenated string // of the form id1=value1:id2=value2... @@ -224,3 +281,6 @@ template std::string QuotedVar(T name) ss << std::quoted(name, '\''); return ss.str(); } + +// Trim tailing and leading whitespace +std::string trim(const std::string &s); \ No newline at end of file diff --git a/orchagent/p4orch/route_manager.cpp b/orchagent/p4orch/route_manager.cpp index 7732a143e50c..e029489fde4b 100644 --- a/orchagent/p4orch/route_manager.cpp +++ b/orchagent/p4orch/route_manager.cpp @@ -3,14 +3,22 @@ #include #include #include +#include #include #include +#include "SaiAttributeList.h" +#include "converter.h" #include "crmorch.h" +#include "dbconnector.h" #include "json.hpp" #include "logger.h" #include "p4orch/p4orch_util.h" +#include "sai_serialize.h" #include "swssnet.h" +#include "table.h" + +using ::p4orch::kTableKeyDelimiter; extern sai_object_id_t gSwitchId; extern sai_object_id_t gVirtualRouterId; @@ -19,86 +27,284 @@ extern sai_route_api_t *sai_route_api; extern CrmOrch *gCrmOrch; +extern size_t gMaxBulkSize; + namespace { -// This function will perform a route update. A route update will have two -// attribute update. If the second attribut update fails, the function will try -// to revert the first attribute. If the revert fails, the function will raise -// critical state. -ReturnCode UpdateRouteAttrs(sai_packet_action_t old_action, sai_packet_action_t new_action, sai_object_id_t old_nexthop, - sai_object_id_t new_nexthop, const std::string &route_entry_key, - sai_route_entry_t *rotue_entry) +ReturnCode checkNextHopAndWcmpGroupAndRouteMetadataExistence(bool expected_next_hop_existence, + bool expected_wcmp_group_existence, + bool expected_route_metadata_existence, + const P4RouteEntry &route_entry) { - SWSS_LOG_ENTER(); - // For drop action, we will update the action attribute first. - bool action_first = (new_action == SAI_PACKET_ACTION_DROP); + if (route_entry.nexthop_id.empty() && expected_next_hop_existence) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Empty nexthop_id for route with " << route_entry.action << " action"; + } + if (!route_entry.nexthop_id.empty() && !expected_next_hop_existence) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Non-empty nexthop_id for route with " << route_entry.action << " action"; + } + if (route_entry.wcmp_group.empty() && expected_wcmp_group_existence) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Empty wcmp_group_id for route with " << route_entry.action << " action"; + } + if (!route_entry.wcmp_group.empty() && !expected_wcmp_group_existence) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Non-empty wcmp_group_id for route with " << route_entry.action << " action"; + } + if (route_entry.route_metadata.empty() && expected_route_metadata_existence) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Empty route_metadata for route with " << route_entry.action << " action"; + } + if (!route_entry.route_metadata.empty() && !expected_route_metadata_existence) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Non-empty route_metadata for route with " << route_entry.action << " action"; + } + return ReturnCode(); +} - // First attribute - sai_attribute_t route_attr; - route_attr.id = (action_first) ? SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION : SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; - if (action_first) +// Returns the nexthop OID of the given entry. +// Raise critical state if OID cannot be found. +sai_object_id_t getNexthopOid(const P4RouteEntry &route_entry, const P4OidMapper &mapper) +{ + sai_object_id_t oid = SAI_NULL_OBJECT_ID; + if (route_entry.action == p4orch::kSetNexthopId || route_entry.action == p4orch::kSetNexthopIdAndMetadata) { - route_attr.value.s32 = new_action; + auto nexthop_key = KeyGenerator::generateNextHopKey(route_entry.nexthop_id); + if (!mapper.getOID(SAI_OBJECT_TYPE_NEXT_HOP, nexthop_key, &oid)) + { + std::stringstream msg; + msg << "Nexthop " << QuotedVar(route_entry.nexthop_id) << " does not exist"; + SWSS_LOG_ERROR("%s", msg.str().c_str()); + SWSS_RAISE_CRITICAL_STATE(msg.str()); + return oid; + } } - else + else if (route_entry.action == p4orch::kSetWcmpGroupId || route_entry.action == p4orch::kSetWcmpGroupIdAndMetadata) { - route_attr.value.oid = new_nexthop; + auto wcmp_group_key = KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group); + if (!mapper.getOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key, &oid)) + { + std::stringstream msg; + msg << "WCMP group " << QuotedVar(route_entry.nexthop_id) << " does not exist"; + SWSS_LOG_ERROR("%s", msg.str().c_str()); + SWSS_RAISE_CRITICAL_STATE(msg.str()); + return oid; + } } - CHECK_ERROR_AND_LOG_AND_RETURN(sai_route_api->set_route_entry_attribute(rotue_entry, &route_attr), - "Failed to set SAI attribute " - << (action_first ? "SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION" - : "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID") - << " when updating route " << QuotedVar(route_entry_key)); + return oid; +} - // Second attribute - route_attr.id = (action_first) ? SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID : SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; - if (action_first) +// Returns the SAI action of the given entry. +sai_packet_action_t getSaiAction(const P4RouteEntry &route_entry) +{ + if (route_entry.action == p4orch::kDrop || route_entry.action == p4orch::kSetMetadataAndDrop) { - route_attr.value.oid = new_nexthop; + return SAI_PACKET_ACTION_DROP; } - else + else if (route_entry.action == p4orch::kTrap) { - route_attr.value.s32 = new_action; + return SAI_PACKET_ACTION_TRAP; } - ReturnCode status; - auto sai_status = sai_route_api->set_route_entry_attribute(rotue_entry, &route_attr); - if (sai_status == SAI_STATUS_SUCCESS) + return SAI_PACKET_ACTION_FORWARD; +} + +// Returns the metadata of the given entry. +uint32_t getMetadata(const P4RouteEntry &route_entry) +{ + if (route_entry.route_metadata.empty()) { - return ReturnCode(); + return 0; } - status = ReturnCode(sai_status) << "Failed to set SAI attribute " - << (action_first ? "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID" - : "SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION") - << " when updating route " << QuotedVar(route_entry_key); - SWSS_LOG_ERROR("%s SAI_STATUS: %s", status.message().c_str(), sai_serialize_status(sai_status).c_str()); + return swss::to_uint(route_entry.route_metadata); +} - // Revert the first attribute - route_attr.id = (action_first) ? SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION : SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; - if (action_first) +// Returns a list of SAI actions for route update. +std::vector getSaiActions(const std::string action) +{ + static const auto *const kRouteActionToSaiActions = + new std::unordered_map>({ + {p4orch::kSetNexthopId, + std::vector{SAI_ROUTE_ENTRY_ATTR_META_DATA, SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID, + SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION}}, + {p4orch::kSetWcmpGroupId, + std::vector{SAI_ROUTE_ENTRY_ATTR_META_DATA, SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID, + SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION}}, + {p4orch::kSetNexthopIdAndMetadata, + std::vector{SAI_ROUTE_ENTRY_ATTR_META_DATA, SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID, + SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION}}, + {p4orch::kSetWcmpGroupIdAndMetadata, + std::vector{SAI_ROUTE_ENTRY_ATTR_META_DATA, SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID, + SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION}}, + {p4orch::kDrop, + std::vector{SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION, SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID, + SAI_ROUTE_ENTRY_ATTR_META_DATA}}, + {p4orch::kTrap, + std::vector{SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION, SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID, + SAI_ROUTE_ENTRY_ATTR_META_DATA}}, + {p4orch::kSetMetadataAndDrop, + std::vector{SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION, SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID, + SAI_ROUTE_ENTRY_ATTR_META_DATA}}, + }); + + if (kRouteActionToSaiActions->count(action) == 0) { - route_attr.value.s32 = old_action; + return std::vector{}; } - else + return kRouteActionToSaiActions->at(action); +} + +} // namespace + +RouteUpdater::RouteUpdater(const P4RouteEntry &old_route, const P4RouteEntry &new_route, P4OidMapper *mapper) + : m_oldRoute(old_route), m_newRoute(new_route), m_p4OidMapper(mapper), m_actions(getSaiActions(new_route.action)) +{ + updateIdx(); +} + +P4RouteEntry RouteUpdater::getOldEntry() const +{ + return m_oldRoute; +} + +P4RouteEntry RouteUpdater::getNewEntry() const +{ + return m_newRoute; +} + +sai_route_entry_t RouteUpdater::getSaiEntry() const +{ + return m_newRoute.sai_route_entry; +} + +sai_attribute_t RouteUpdater::getSaiAttr() const +{ + sai_attribute_t route_attr = {}; + if (m_idx < 0 || m_idx >= static_cast(m_actions.size())) { - route_attr.value.oid = old_nexthop; + return route_attr; } - sai_status = sai_route_api->set_route_entry_attribute(rotue_entry, &route_attr); + route_attr.id = m_actions[m_idx]; + switch (m_actions[m_idx]) + { + case SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID: + route_attr.value.oid = + (m_revert) ? getNexthopOid(m_oldRoute, *m_p4OidMapper) : getNexthopOid(m_newRoute, *m_p4OidMapper); + break; + case SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION: + route_attr.value.s32 = (m_revert) ? getSaiAction(m_oldRoute) : getSaiAction(m_newRoute); + break; + default: + route_attr.value.u32 = (m_revert) ? getMetadata(m_oldRoute) : getMetadata(m_newRoute); + } + return route_attr; +} + +bool RouteUpdater::updateResult(sai_status_t sai_status) +{ if (sai_status != SAI_STATUS_SUCCESS) { - // Raise critical state if we fail to recover. - std::stringstream msg; - msg << "Failed to revert route attribute " - << (action_first ? "SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION" : "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID") - << " for route " << QuotedVar(route_entry_key); - SWSS_LOG_ERROR("%s SAI_STATUS: %s", msg.str().c_str(), sai_serialize_status(sai_status).c_str()); - SWSS_RAISE_CRITICAL_STATE(msg.str()); + if (m_revert) + { + std::stringstream msg; + msg << "Failed to revert SAI attribute for route entry " << QuotedVar(m_newRoute.route_entry_key); + SWSS_LOG_ERROR("%s SAI_STATUS: %s", msg.str().c_str(), sai_serialize_status(sai_status).c_str()); + SWSS_RAISE_CRITICAL_STATE(msg.str()); + } + else + { + m_status = ReturnCode(sai_status) + << "Failed to update route entry " << QuotedVar(m_newRoute.route_entry_key); + m_revert = true; + } } + return updateIdx(); +} - return status; +ReturnCode RouteUpdater::getStatus() const +{ + return m_status; } -} // namespace +bool RouteUpdater::updateIdx() +{ + if (m_revert) + { + for (--m_idx; m_idx >= 0; --m_idx) + { + if (checkAction()) + { + return false; + } + } + return true; + } + for (++m_idx; m_idx < static_cast(m_actions.size()); ++m_idx) + { + if (checkAction()) + { + return false; + } + } + return true; +} + +bool RouteUpdater::checkAction() const +{ + if (m_idx < 0 || m_idx >= static_cast(m_actions.size())) + { + return false; + } + switch (m_actions[m_idx]) + { + case SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID: + if (getNexthopOid(m_oldRoute, *m_p4OidMapper) == getNexthopOid(m_newRoute, *m_p4OidMapper)) + { + return false; + } + return true; + case SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION: + if (getSaiAction(m_oldRoute) == getSaiAction(m_newRoute)) + { + return false; + } + return true; + default: + if (getMetadata(m_oldRoute) == getMetadata(m_newRoute)) + { + return false; + } + return true; + } + return false; +} + +RouteManager::RouteManager(P4OidMapper *p4oidMapper, VRFOrch *vrfOrch, ResponsePublisherInterface *publisher) + : m_vrfOrch(vrfOrch), m_routerBulker(sai_route_api, gMaxBulkSize) +{ + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; +} + +sai_route_entry_t RouteManager::getSaiEntry(const P4RouteEntry &route_entry) +{ + sai_route_entry_t sai_entry; + sai_entry.vr_id = m_vrfOrch->getVRFid(route_entry.vrf_id); + sai_entry.switch_id = gSwitchId; + copy(sai_entry.destination, route_entry.route_prefix); + return sai_entry; +} bool RouteManager::mergeRouteEntry(const P4RouteEntry &dest, const P4RouteEntry &src, P4RouteEntry *ret) { @@ -106,11 +312,8 @@ bool RouteManager::mergeRouteEntry(const P4RouteEntry &dest, const P4RouteEntry *ret = src; ret->sai_route_entry = dest.sai_route_entry; - if (ret->action.empty()) - { - ret->action = dest.action; - } - if (ret->action != dest.action || ret->nexthop_id != dest.nexthop_id || ret->wcmp_group != dest.wcmp_group) + if (ret->action != dest.action || ret->nexthop_id != dest.nexthop_id || ret->wcmp_group != dest.wcmp_group || + ret->route_metadata != dest.route_metadata) { return true; } @@ -183,6 +386,10 @@ ReturnCodeOr RouteManager::deserializeRouteEntry(const std::string { route_entry.wcmp_group = value; } + else if (field == prependParamField(p4orch::kRouteMetadata)) + { + route_entry.route_metadata = value; + } else if (field != p4orch::kControllerMetadata) { return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) @@ -203,7 +410,7 @@ P4RouteEntry *RouteManager::getRouteEntry(const std::string &route_entry_key) return &m_routeTable[route_entry_key]; } -ReturnCode RouteManager::validateRouteEntry(const P4RouteEntry &route_entry) +ReturnCode RouteManager::validateRouteEntry(const P4RouteEntry &route_entry, const std::string &operation) { SWSS_LOG_ENTER(); @@ -229,7 +436,16 @@ ReturnCode RouteManager::validateRouteEntry(const P4RouteEntry &route_entry) { return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "No VRF found with name " << QuotedVar(route_entry.vrf_id); } - return ReturnCode(); + + if (operation == SET_COMMAND) + { + return validateSetRouteEntry(route_entry); + } + else if (operation == DEL_COMMAND) + { + return validateDelRouteEntry(route_entry); + } + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << QuotedVar(operation); } ReturnCode RouteManager::validateSetRouteEntry(const P4RouteEntry &route_entry) @@ -258,45 +474,65 @@ ReturnCode RouteManager::validateSetRouteEntry(const P4RouteEntry &route_entry) } if (action == p4orch::kSetNexthopId) { - if (route_entry.nexthop_id.empty()) - { - return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Empty nexthop_id for route with nexthop_id action"; - } - if (!route_entry.wcmp_group.empty()) - { - return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) - << "Non-empty wcmp_group_id for route with nexthop_id action"; - } + RETURN_IF_ERROR(checkNextHopAndWcmpGroupAndRouteMetadataExistence( + /*expected_next_hop_existence=*/true, + /*expected_wcmp_group_existence=*/false, + /*expected_route_metadata_existence=*/false, route_entry)); } else if (action == p4orch::kSetWcmpGroupId) { - if (!route_entry.nexthop_id.empty()) - { - return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) - << "Non-empty nexthop_id for route with wcmp_group action"; - } - if (route_entry.wcmp_group.empty()) - { - return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) - << "Empty wcmp_group_id for route with wcmp_group action"; - } + RETURN_IF_ERROR(checkNextHopAndWcmpGroupAndRouteMetadataExistence( + /*expected_next_hop_existence=*/false, + /*expected_wcmp_group_existence=*/true, + /*expected_route_metadata_existence=*/false, route_entry)); + } + else if (action == p4orch::kSetNexthopIdAndMetadata) + { + RETURN_IF_ERROR(checkNextHopAndWcmpGroupAndRouteMetadataExistence( + /*expected_next_hop_existence=*/true, + /*expected_wcmp_group_existence=*/false, + /*expected_route_metadata_existence=*/true, route_entry)); + } + else if (action == p4orch::kSetWcmpGroupIdAndMetadata) + { + RETURN_IF_ERROR(checkNextHopAndWcmpGroupAndRouteMetadataExistence( + /*expected_next_hop_existence=*/false, + /*expected_wcmp_group_existence=*/true, + /*expected_route_metadata_existence=*/true, route_entry)); + } + else if (action == p4orch::kDrop || action == p4orch::kTrap) + { + RETURN_IF_ERROR(checkNextHopAndWcmpGroupAndRouteMetadataExistence( + /*expected_next_hop_existence=*/false, + /*expected_wcmp_group_existence=*/false, + /*expected_route_metadata_existence=*/false, route_entry)); + } + else if (action == p4orch::kSetMetadataAndDrop) + { + RETURN_IF_ERROR(checkNextHopAndWcmpGroupAndRouteMetadataExistence( + /*expected_next_hop_existence=*/false, + /*expected_wcmp_group_existence=*/false, + /*expected_route_metadata_existence=*/true, route_entry)); + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid action " << QuotedVar(action); } - else if (action == p4orch::kDrop) + + if (!route_entry.route_metadata.empty()) { - if (!route_entry.nexthop_id.empty()) + try { - return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Non-empty nexthop_id for route with drop action"; + swss::to_uint(route_entry.route_metadata); } - if (!route_entry.wcmp_group.empty()) + catch (std::exception &e) { return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) - << "Non-empty wcmp_group_id for route with drop action"; + << "Action attribute " << QuotedVar(p4orch::kRouteMetadata) << " is invalid for " + << QuotedVar(route_entry.route_entry_key) << ": Expect integer but got " + << QuotedVar(route_entry.route_metadata); } } - else - { - return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid action " << QuotedVar(action); - } return ReturnCode(); } @@ -322,177 +558,283 @@ ReturnCode RouteManager::validateDelRouteEntry(const P4RouteEntry &route_entry) { return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Non-empty wcmp_group for Del route"; } + if (!route_entry.route_metadata.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Non-empty route_metadata for Del route"; + } return ReturnCode(); } -ReturnCode RouteManager::createRouteEntry(const P4RouteEntry &route_entry) +std::vector RouteManager::createRouteEntries(const std::vector &route_entries) { SWSS_LOG_ENTER(); - sai_route_entry_t sai_route_entry; - sai_route_entry.vr_id = m_vrfOrch->getVRFid(route_entry.vrf_id); - sai_route_entry.switch_id = gSwitchId; - copy(sai_route_entry.destination, route_entry.route_prefix); - if (route_entry.action == p4orch::kSetNexthopId) - { - auto nexthop_key = KeyGenerator::generateNextHopKey(route_entry.nexthop_id); - sai_object_id_t next_hop_oid; - m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP, nexthop_key, &next_hop_oid); - sai_attribute_t route_attr; - route_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; - route_attr.value.oid = next_hop_oid; - // Default SAI_ROUTE_ATTR_PACKET_ACTION is SAI_PACKET_ACTION_FORWARD. - CHECK_ERROR_AND_LOG_AND_RETURN(sai_route_api->create_route_entry(&sai_route_entry, /*size=*/1, &route_attr), - "Failed to create route " << QuotedVar(route_entry.route_entry_key) - << " with next hop " - << QuotedVar(route_entry.nexthop_id)); - m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, nexthop_key); - } - else if (route_entry.action == p4orch::kSetWcmpGroupId) - { - auto wcmp_group_key = KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group); - sai_object_id_t wcmp_group_oid; - m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key, &wcmp_group_oid); - sai_attribute_t route_attr; - route_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; - route_attr.value.oid = wcmp_group_oid; - // Default SAI_ROUTE_ATTR_PACKET_ACTION is SAI_PACKET_ACTION_FORWARD. - CHECK_ERROR_AND_LOG_AND_RETURN(sai_route_api->create_route_entry(&sai_route_entry, /*size=*/1, &route_attr), - "Failed to create route " << QuotedVar(route_entry.route_entry_key) - << " with wcmp group " - << QuotedVar(route_entry.wcmp_group)); - m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key); - } - else - { - sai_attribute_t route_attr; - route_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; - route_attr.value.s32 = SAI_PACKET_ACTION_DROP; - CHECK_ERROR_AND_LOG_AND_RETURN(sai_route_api->create_route_entry(&sai_route_entry, /*size=*/1, &route_attr), - "Failed to create route " << QuotedVar(route_entry.route_entry_key) - << " with action drop"); - } + std::vector sai_route_entries(route_entries.size()); + // Currently, there are maximum of 2 SAI attributes for route creation. + // For drop and trap routes, there is one SAI attribute: + // SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION. + // For forwarding routes, the default SAI_ROUTE_ATTR_PACKET_ACTION is already + // SAI_PACKET_ACTION_FORWARD, so we don't need SAI_ROUTE_ATTR_PACKET_ACTION. + // But we need SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID and optionally + // SAI_ROUTE_ENTRY_ATTR_META_DATA. + std::vector sai_attrs(2 * route_entries.size()); + std::vector object_statuses(route_entries.size()); + std::vector statuses(route_entries.size()); - m_routeTable[route_entry.route_entry_key] = route_entry; - m_routeTable[route_entry.route_entry_key].sai_route_entry = sai_route_entry; - m_p4OidMapper->setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); - if (route_entry.route_prefix.isV4()) + for (size_t i = 0; i < route_entries.size(); ++i) { - gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); + const auto &route_entry = route_entries[i]; + sai_route_entries[i] = getSaiEntry(route_entry); + uint32_t num_attrs = 1; + if (route_entry.action == p4orch::kDrop) + { + sai_attrs[2 * i].id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + sai_attrs[2 * i].value.s32 = SAI_PACKET_ACTION_DROP; + } + else if (route_entry.action == p4orch::kTrap) + { + sai_attrs[2 * i].id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + sai_attrs[2 * i].value.s32 = SAI_PACKET_ACTION_TRAP; + } + else if (route_entry.action == p4orch::kSetMetadataAndDrop) + { + sai_attrs[2 * i].id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + sai_attrs[2 * i].value.s32 = SAI_PACKET_ACTION_DROP; + sai_attrs[2 * i + 1].id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + sai_attrs[2 * i + 1].value.u32 = swss::to_uint(route_entry.route_metadata); + num_attrs++; + } + else + { + // Default SAI_ROUTE_ATTR_PACKET_ACTION is SAI_PACKET_ACTION_FORWARD. + sai_attrs[2 * i].id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + sai_attrs[2 * i].value.oid = getNexthopOid(route_entry, *m_p4OidMapper); + if (route_entry.action == p4orch::kSetNexthopIdAndMetadata || + route_entry.action == p4orch::kSetWcmpGroupIdAndMetadata) + { + sai_attrs[2 * i + 1].id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + sai_attrs[2 * i + 1].value.u32 = swss::to_uint(route_entry.route_metadata); + num_attrs++; + } + } + object_statuses[i] = + m_routerBulker.create_entry(&object_statuses[i], &sai_route_entries[i], num_attrs, &sai_attrs[2 * i]); } - else + + m_routerBulker.flush(); + + for (size_t i = 0; i < route_entries.size(); ++i) { - gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + const auto &route_entry = route_entries[i]; + CHECK_ERROR_AND_LOG(object_statuses[i], + "Failed to create route entry " << QuotedVar(route_entry.route_entry_key)); + if (object_statuses[i] == SAI_STATUS_SUCCESS) + { + if (route_entry.action == p4orch::kSetNexthopId || route_entry.action == p4orch::kSetNexthopIdAndMetadata) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(route_entry.nexthop_id)); + } + else if (route_entry.action == p4orch::kSetWcmpGroupId || + route_entry.action == p4orch::kSetWcmpGroupIdAndMetadata) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group)); + } + m_routeTable[route_entry.route_entry_key] = route_entry; + m_routeTable[route_entry.route_entry_key].sai_route_entry = sai_route_entries[i]; + m_p4OidMapper->setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); + if (route_entry.route_prefix.isV4()) + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); + } + else + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + } + m_vrfOrch->increaseVrfRefCount(route_entry.vrf_id); + statuses[i] = ReturnCode(); + } + else + { + statuses[i] = ReturnCode(object_statuses[i]) + << "Failed to create route entry " << QuotedVar(route_entry.route_entry_key); + } } - m_vrfOrch->increaseVrfRefCount(route_entry.vrf_id); - return ReturnCode(); + + return statuses; } -ReturnCodeOr RouteManager::getNexthopOid(const P4RouteEntry &route_entry) +void RouteManager::updateRouteEntriesMeta(const P4RouteEntry &old_entry, const P4RouteEntry &new_entry) { - sai_object_id_t oid = SAI_NULL_OBJECT_ID; - if (route_entry.action == p4orch::kSetNexthopId) + if (getNexthopOid(old_entry, *m_p4OidMapper) != getNexthopOid(new_entry, *m_p4OidMapper)) { - auto nexthop_key = KeyGenerator::generateNextHopKey(route_entry.nexthop_id); - if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP, nexthop_key, &oid)) + if (new_entry.action == p4orch::kSetNexthopId || new_entry.action == p4orch::kSetNexthopIdAndMetadata) { - RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Nexthop " << QuotedVar(route_entry.nexthop_id) - << " does not exist"); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(new_entry.nexthop_id)); } - } - else if (route_entry.action == p4orch::kSetWcmpGroupId) - { - auto wcmp_group_key = KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group); - if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key, &oid)) + else if (new_entry.action == p4orch::kSetWcmpGroupId || new_entry.action == p4orch::kSetWcmpGroupIdAndMetadata) { - RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("WCMP group " << QuotedVar(route_entry.wcmp_group) - << " does not exist"); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(new_entry.wcmp_group)); + } + if (old_entry.action == p4orch::kSetNexthopId || old_entry.action == p4orch::kSetNexthopIdAndMetadata) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(old_entry.nexthop_id)); + } + else if (old_entry.action == p4orch::kSetWcmpGroupId || old_entry.action == p4orch::kSetWcmpGroupIdAndMetadata) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(old_entry.wcmp_group)); } } - return oid; + m_routeTable[new_entry.route_entry_key] = new_entry; } -ReturnCode RouteManager::updateRouteEntry(const P4RouteEntry &route_entry) +void RouteManager::updateRouteAttrs(int size, const std::vector> &updaters, + std::vector &indice, std::vector &statuses) { - SWSS_LOG_ENTER(); - - auto *route_entry_ptr = getRouteEntry(route_entry.route_entry_key); - P4RouteEntry new_route_entry; - if (!mergeRouteEntry(*route_entry_ptr, route_entry, &new_route_entry)) + std::vector sai_route_entries(size); + std::vector sai_attrs(size); + std::vector object_statuses(size); + // We will perform route update in multiple SAI calls. + // If error is encountered, the previous SAI calls will be reverted. + // Raise critical state if the revert fails. + // We avoid changing multiple attributes of the same entry in a single bulk + // call. + constexpr int kMaxAttrUpdate = 20; + int i; + for (i = 0; i < kMaxAttrUpdate; ++i) { - return ReturnCode(); - } - - ASSIGN_OR_RETURN(sai_object_id_t old_nexthop, getNexthopOid(*route_entry_ptr)); - ASSIGN_OR_RETURN(sai_object_id_t new_nexthop, getNexthopOid(new_route_entry)); - RETURN_IF_ERROR(UpdateRouteAttrs( - (route_entry_ptr->action == p4orch::kDrop) ? SAI_PACKET_ACTION_DROP : SAI_PACKET_ACTION_FORWARD, - (new_route_entry.action == p4orch::kDrop) ? SAI_PACKET_ACTION_DROP : SAI_PACKET_ACTION_FORWARD, old_nexthop, - new_nexthop, new_route_entry.route_entry_key, &new_route_entry.sai_route_entry)); - - if (new_route_entry.action == p4orch::kSetNexthopId) - { - m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, - KeyGenerator::generateNextHopKey(new_route_entry.nexthop_id)); + for (int j = 0; j < size; ++j) + { + sai_route_entries[j] = updaters[indice[j]]->getSaiEntry(); + sai_attrs[j] = updaters[indice[j]]->getSaiAttr(); + m_routerBulker.set_entry_attribute(&object_statuses[j], &sai_route_entries[j], &sai_attrs[j]); + } + m_routerBulker.flush(); + int new_size = 0; + for (int j = 0; j < size; j++) + { + if (updaters[indice[j]]->updateResult(object_statuses[j])) + { + statuses[indice[j]] = updaters[indice[j]]->getStatus(); + if (statuses[indice[j]].ok()) + { + updateRouteEntriesMeta(updaters[indice[j]]->getOldEntry(), updaters[indice[j]]->getNewEntry()); + } + } + else + { + indice[new_size++] = indice[j]; + } + } + if (new_size == 0) + { + break; + } + size = new_size; } - if (new_route_entry.action == p4orch::kSetWcmpGroupId) + // Just a safety check to prevent infinite loop. Should not happen. + if (i == kMaxAttrUpdate) { - m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, - KeyGenerator::generateWcmpGroupKey(new_route_entry.wcmp_group)); + SWSS_RAISE_CRITICAL_STATE("Route update operation did not terminate."); } + return; +} + +std::vector RouteManager::updateRouteEntries(const std::vector &route_entries) +{ + SWSS_LOG_ENTER(); - if (route_entry_ptr->action == p4orch::kSetNexthopId) + std::vector> updaters(route_entries.size()); + std::vector indice(route_entries.size()); // index to the route_entries + std::vector statuses(route_entries.size()); + + int size = 0; + for (size_t i = 0; i < route_entries.size(); ++i) { - if (new_route_entry.action != p4orch::kSetNexthopId || - new_route_entry.nexthop_id != route_entry_ptr->nexthop_id) + const auto &route_entry = route_entries[i]; + auto *route_entry_ptr = getRouteEntry(route_entry.route_entry_key); + P4RouteEntry new_entry; + if (!mergeRouteEntry(*route_entry_ptr, route_entry, &new_entry)) { - m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, - KeyGenerator::generateNextHopKey(route_entry_ptr->nexthop_id)); + statuses[i] = ReturnCode(); + continue; } + updaters[i] = std::unique_ptr(new RouteUpdater(*route_entry_ptr, new_entry, m_p4OidMapper)); + indice[size++] = i; } - if (route_entry_ptr->action == p4orch::kSetWcmpGroupId) + if (size == 0) { - if (new_route_entry.action != p4orch::kSetWcmpGroupId || - new_route_entry.wcmp_group != route_entry_ptr->wcmp_group) - { - m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, - KeyGenerator::generateWcmpGroupKey(route_entry_ptr->wcmp_group)); - } + return statuses; } - m_routeTable[route_entry.route_entry_key] = new_route_entry; - return ReturnCode(); + + updateRouteAttrs(size, updaters, indice, statuses); + return statuses; } -ReturnCode RouteManager::deleteRouteEntry(const P4RouteEntry &route_entry) +std::vector RouteManager::deleteRouteEntries(const std::vector &route_entries) { SWSS_LOG_ENTER(); - auto *route_entry_ptr = getRouteEntry(route_entry.route_entry_key); - CHECK_ERROR_AND_LOG_AND_RETURN(sai_route_api->remove_route_entry(&route_entry_ptr->sai_route_entry), - "Failed to delete route " << QuotedVar(route_entry.route_entry_key)); + std::vector sai_route_entries(route_entries.size()); + std::vector object_statuses(route_entries.size()); + std::vector statuses(route_entries.size()); - if (route_entry_ptr->action == p4orch::kSetNexthopId) - { - m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, - KeyGenerator::generateNextHopKey(route_entry_ptr->nexthop_id)); - } - if (route_entry_ptr->action == p4orch::kSetWcmpGroupId) + for (size_t i = 0; i < route_entries.size(); ++i) { - m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, - KeyGenerator::generateWcmpGroupKey(route_entry_ptr->wcmp_group)); + const auto &route_entry = route_entries[i]; + auto *route_entry_ptr = getRouteEntry(route_entry.route_entry_key); + sai_route_entries[i] = route_entry_ptr->sai_route_entry; + object_statuses[i] = m_routerBulker.remove_entry(&object_statuses[i], &sai_route_entries[i]); } - m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); - if (route_entry.route_prefix.isV4()) - { - gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); - } - else + + m_routerBulker.flush(); + + for (size_t i = 0; i < route_entries.size(); ++i) { - gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + const auto &route_entry = route_entries[i]; + auto *route_entry_ptr = getRouteEntry(route_entry.route_entry_key); + CHECK_ERROR_AND_LOG(object_statuses[i], + "Failed to delete route entry " << QuotedVar(route_entry.route_entry_key)); + if (object_statuses[i] == SAI_STATUS_SUCCESS) + { + if (route_entry_ptr->action == p4orch::kSetNexthopId || + route_entry_ptr->action == p4orch::kSetNexthopIdAndMetadata) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(route_entry_ptr->nexthop_id)); + } + else if (route_entry_ptr->action == p4orch::kSetWcmpGroupId || + route_entry_ptr->action == p4orch::kSetWcmpGroupIdAndMetadata) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(route_entry_ptr->wcmp_group)); + } + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); + if (route_entry.route_prefix.isV4()) + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); + } + else + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + } + m_vrfOrch->decreaseVrfRefCount(route_entry.vrf_id); + m_routeTable.erase(route_entry.route_entry_key); + statuses[i] = ReturnCode(); + } + else + { + statuses[i] = ReturnCode(object_statuses[i]) + << "Failed to delete route entry " << QuotedVar(route_entry.route_entry_key); + } } - m_vrfOrch->decreaseVrfRefCount(route_entry.vrf_id); - m_routeTable.erase(route_entry.route_entry_key); - return ReturnCode(); + + return statuses; } void RouteManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) @@ -504,6 +846,14 @@ void RouteManager::drain() { SWSS_LOG_ENTER(); + std::vector create_route_list; + std::vector update_route_list; + std::vector delete_route_list; + std::vector create_tuple_list; + std::vector update_tuple_list; + std::vector delete_tuple_list; + std::unordered_set route_entry_list; + for (const auto &key_op_fvs_tuple : m_entries) { std::string table_name; @@ -525,7 +875,19 @@ void RouteManager::drain() } auto &route_entry = *route_entry_or; - status = validateRouteEntry(route_entry); + // A single batch should not modify the same route more than once. + if (route_entry_list.count(route_entry.route_entry_key) != 0) + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Route entry has been included in the same batch"; + SWSS_LOG_ERROR("%s: %s", status.message().c_str(), QuotedVar(route_entry.route_entry_key).c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), + status, + /*replace=*/true); + continue; + } + + const std::string &operation = kfvOp(key_op_fvs_tuple); + status = validateRouteEntry(route_entry, operation); if (!status.ok()) { SWSS_LOG_ERROR("Validation failed for Route APP DB entry with key %s: %s", @@ -535,45 +897,270 @@ void RouteManager::drain() /*replace=*/true); continue; } + route_entry_list.insert(route_entry.route_entry_key); - const std::string &operation = kfvOp(key_op_fvs_tuple); if (operation == SET_COMMAND) { - status = validateSetRouteEntry(route_entry); - if (!status.ok()) - { - SWSS_LOG_ERROR("Validation failed for Set Route APP DB entry with key %s: %s", - QuotedVar(table_name + ":" + key).c_str(), status.message().c_str()); - } - else if (getRouteEntry(route_entry.route_entry_key) == nullptr) + if (getRouteEntry(route_entry.route_entry_key) == nullptr) { - status = createRouteEntry(route_entry); + create_route_list.push_back(route_entry); + create_tuple_list.push_back(key_op_fvs_tuple); } else { - status = updateRouteEntry(route_entry); + update_route_list.push_back(route_entry); + update_tuple_list.push_back(key_op_fvs_tuple); } } - else if (operation == DEL_COMMAND) + else { - status = validateDelRouteEntry(route_entry); - if (!status.ok()) - { - SWSS_LOG_ERROR("Validation failed for Del Route APP DB entry with key %s: %s", - QuotedVar(table_name + ":" + key).c_str(), status.message().c_str()); - } - else - { - status = deleteRouteEntry(route_entry); - } + delete_route_list.push_back(route_entry); + delete_tuple_list.push_back(key_op_fvs_tuple); } - else + } + + if (!create_route_list.empty()) + { + auto statuses = createRouteEntries(create_route_list); + for (size_t i = 0; i < create_route_list.size(); ++i) + { + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(create_tuple_list[i]), + kfvFieldsValues(create_tuple_list[i]), statuses[i], + /*replace=*/true); + } + } + if (!update_route_list.empty()) + { + auto statuses = updateRouteEntries(update_route_list); + for (size_t i = 0; i < update_route_list.size(); ++i) + { + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(update_tuple_list[i]), + kfvFieldsValues(update_tuple_list[i]), statuses[i], + /*replace=*/true); + } + } + if (!delete_route_list.empty()) + { + auto statuses = deleteRouteEntries(delete_route_list); + for (size_t i = 0; i < delete_route_list.size(); ++i) { - status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << QuotedVar(operation); - SWSS_LOG_ERROR("%s", status.message().c_str()); + m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(delete_tuple_list[i]), + kfvFieldsValues(delete_tuple_list[i]), statuses[i], + /*replace=*/true); } - m_publisher->publish(APP_P4RT_TABLE_NAME, kfvKey(key_op_fvs_tuple), kfvFieldsValues(key_op_fvs_tuple), status, - /*replace=*/true); } m_entries.clear(); } + +std::string RouteManager::verifyState(const std::string &key, const std::vector &tuple) +{ + SWSS_LOG_ENTER(); + + auto pos = key.find_first_of(kTableKeyDelimiter); + if (pos == std::string::npos) + { + return std::string("Invalid key: ") + key; + } + std::string p4rt_table = key.substr(0, pos); + std::string p4rt_key = key.substr(pos + 1); + if (p4rt_table != APP_P4RT_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + std::string table_name; + std::string key_content; + parseP4RTKey(p4rt_key, &table_name, &key_content); + if (table_name != APP_P4RT_IPV4_TABLE_NAME && table_name != APP_P4RT_IPV6_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + + ReturnCode status; + auto app_db_entry_or = deserializeRouteEntry(key_content, tuple, table_name); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + std::stringstream msg; + msg << "Unable to deserialize key " << QuotedVar(key) << ": " << status.message(); + return msg.str(); + } + auto &app_db_entry = *app_db_entry_or; + + auto *route_entry = getRouteEntry(app_db_entry.route_entry_key); + if (route_entry == nullptr) + { + std::stringstream msg; + msg << "No entry found with key " << QuotedVar(key); + return msg.str(); + } + + std::string cache_result = verifyStateCache(app_db_entry, route_entry); + std::string asic_db_result = verifyStateAsicDb(route_entry); + if (cache_result.empty()) + { + return asic_db_result; + } + if (asic_db_result.empty()) + { + return cache_result; + } + return cache_result + "; " + asic_db_result; +} + +std::string RouteManager::verifyStateCache(const P4RouteEntry &app_db_entry, const P4RouteEntry *route_entry) +{ + ReturnCode status = validateRouteEntry(app_db_entry, SET_COMMAND); + if (!status.ok()) + { + std::stringstream msg; + msg << "Validation failed for route DB entry with key " << QuotedVar(app_db_entry.route_entry_key) << ": " + << status.message(); + return msg.str(); + } + + if (route_entry->route_entry_key != app_db_entry.route_entry_key) + { + std::stringstream msg; + msg << "Route entry " << QuotedVar(app_db_entry.route_entry_key) << " does not match internal cache " + << QuotedVar(route_entry->route_entry_key) << " in route manager."; + return msg.str(); + } + if (route_entry->vrf_id != app_db_entry.vrf_id) + { + std::stringstream msg; + msg << "Route entry " << QuotedVar(app_db_entry.route_entry_key) << " with VRF " + << QuotedVar(app_db_entry.vrf_id) << " does not match internal cache " << QuotedVar(route_entry->vrf_id) + << " in route manager."; + return msg.str(); + } + if (route_entry->route_prefix.to_string() != app_db_entry.route_prefix.to_string()) + { + std::stringstream msg; + msg << "Route entry " << QuotedVar(app_db_entry.route_entry_key) << " with route prefix " + << app_db_entry.route_prefix.to_string() << " does not match internal cache " + << route_entry->route_prefix.to_string() << " in route manager."; + return msg.str(); + } + if (route_entry->action != app_db_entry.action) + { + std::stringstream msg; + msg << "Route entry " << QuotedVar(app_db_entry.route_entry_key) << " with action " + << QuotedVar(app_db_entry.action) << " does not match internal cache " << QuotedVar(route_entry->action) + << " in route manager."; + return msg.str(); + } + if (route_entry->nexthop_id != app_db_entry.nexthop_id) + { + std::stringstream msg; + msg << "Route entry " << QuotedVar(app_db_entry.route_entry_key) << " with nexthop ID " + << QuotedVar(app_db_entry.nexthop_id) << " does not match internal cache " + << QuotedVar(route_entry->nexthop_id) << " in route manager."; + return msg.str(); + } + if (route_entry->wcmp_group != app_db_entry.wcmp_group) + { + std::stringstream msg; + msg << "Route entry " << QuotedVar(app_db_entry.route_entry_key) << " with WCMP group " + << QuotedVar(app_db_entry.wcmp_group) << " does not match internal cache " + << QuotedVar(route_entry->wcmp_group) << " in route manager."; + return msg.str(); + } + if (route_entry->route_metadata != app_db_entry.route_metadata) + { + std::stringstream msg; + msg << "Route entry " << QuotedVar(app_db_entry.route_entry_key) << " with metadata " + << QuotedVar(app_db_entry.route_metadata) << " does not match internal cache " + << QuotedVar(route_entry->route_metadata) << " in route manager."; + return msg.str(); + } + + return ""; +} + +std::string RouteManager::verifyStateAsicDb(const P4RouteEntry *route_entry) +{ + std::vector exp_attrs; + std::vector opt_attrs; + sai_attribute_t attr; + + if (route_entry->action == p4orch::kDrop) + { + attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + attr.value.s32 = SAI_PACKET_ACTION_DROP; + exp_attrs.push_back(attr); + } + else if (route_entry->action == p4orch::kTrap) + { + attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + attr.value.s32 = SAI_PACKET_ACTION_TRAP; + exp_attrs.push_back(attr); + } + else if (route_entry->action == p4orch::kSetMetadataAndDrop) + { + attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + attr.value.s32 = SAI_PACKET_ACTION_DROP; + exp_attrs.push_back(attr); + attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + attr.value.u32 = swss::to_uint(route_entry->route_metadata); + exp_attrs.push_back(attr); + } + else + { + attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + attr.value.oid = getNexthopOid(*route_entry, *m_p4OidMapper); + exp_attrs.push_back(attr); + if (route_entry->action == p4orch::kSetNexthopIdAndMetadata || + route_entry->action == p4orch::kSetWcmpGroupIdAndMetadata) + { + attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + attr.value.u32 = swss::to_uint(route_entry->route_metadata); + exp_attrs.push_back(attr); + } + } + + if (route_entry->action == p4orch::kDrop || route_entry->action == p4orch::kTrap) + { + attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + attr.value.oid = SAI_NULL_OBJECT_ID; + opt_attrs.push_back(attr); + attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + attr.value.u32 = 0; + opt_attrs.push_back(attr); + } + else if (route_entry->action == p4orch::kSetMetadataAndDrop) + { + attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + attr.value.oid = SAI_NULL_OBJECT_ID; + opt_attrs.push_back(attr); + } + else + { + attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + attr.value.s32 = SAI_PACKET_ACTION_FORWARD; + opt_attrs.push_back(attr); + if (route_entry->action != p4orch::kSetNexthopIdAndMetadata && + route_entry->action != p4orch::kSetWcmpGroupIdAndMetadata) + { + attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + attr.value.u32 = 0; + opt_attrs.push_back(attr); + } + } + + std::vector exp = saimeta::SaiAttributeList::serialize_attr_list( + SAI_OBJECT_TYPE_ROUTE_ENTRY, (uint32_t)exp_attrs.size(), exp_attrs.data(), /*countOnly=*/false); + std::vector opt = saimeta::SaiAttributeList::serialize_attr_list( + SAI_OBJECT_TYPE_ROUTE_ENTRY, (uint32_t)opt_attrs.size(), opt_attrs.data(), /*countOnly=*/false); + + swss::DBConnector db("ASIC_DB", 0); + swss::Table table(&db, "ASIC_STATE"); + std::string key = sai_serialize_object_type(SAI_OBJECT_TYPE_ROUTE_ENTRY) + ":" + + sai_serialize_route_entry(getSaiEntry(*route_entry)); + std::vector values; + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + + return verifyAttrs(values, exp, opt, /*allow_unknown=*/false); +} diff --git a/orchagent/p4orch/route_manager.h b/orchagent/p4orch/route_manager.h index 6e494709b34e..0a50c294bd69 100644 --- a/orchagent/p4orch/route_manager.h +++ b/orchagent/p4orch/route_manager.h @@ -4,7 +4,9 @@ #include #include #include +#include +#include "bulker.h" #include "ipprefix.h" #include "orch.h" #include "p4orch/next_hop_manager.h" @@ -27,28 +29,63 @@ struct P4RouteEntry std::string action; std::string nexthop_id; std::string wcmp_group; + std::string route_metadata; // go/gpins-pinball-vip-stats sai_route_entry_t sai_route_entry; }; // P4RouteTable: Route ID, P4RouteEntry typedef std::unordered_map P4RouteTable; +// RouteUpdater is a helper class in performing route update. +// It keeps track of the state of the route update. It provides the next SAI +// attribute required in the route update. +// RouteUpdater will raise critical state if recovery fails or nexthop OID +// cannot be found. +class RouteUpdater +{ + public: + RouteUpdater(const P4RouteEntry &old_route, const P4RouteEntry &new_route, P4OidMapper *mapper); + ~RouteUpdater() = default; + + P4RouteEntry getOldEntry() const; + P4RouteEntry getNewEntry() const; + sai_route_entry_t getSaiEntry() const; + // Returns the next SAI attribute that should be performed. + sai_attribute_t getSaiAttr() const; + // Updates the state by the given SAI result. + // Returns true if all operations are completed. + // This method will raise critical state if a recovery action fails. + bool updateResult(sai_status_t sai_status); + // Returns the overall status of the route update. + // This method should only be called after UpdateResult returns true. + ReturnCode getStatus() const; + + private: + // Updates the action index. + // Returns true if there are no more actions. + bool updateIdx(); + // Checks if the current action should be performed or not. + // Returns true if the action should be performed. + bool checkAction() const; + + P4OidMapper *m_p4OidMapper; + P4RouteEntry m_oldRoute; + P4RouteEntry m_newRoute; + ReturnCode m_status; + std::vector m_actions; + bool m_revert = false; + int m_idx = -1; +}; + class RouteManager : public ObjectManagerInterface { public: - RouteManager(P4OidMapper *p4oidMapper, VRFOrch *vrfOrch, ResponsePublisherInterface *publisher) : m_vrfOrch(vrfOrch) - { - SWSS_LOG_ENTER(); - - assert(p4oidMapper != nullptr); - m_p4OidMapper = p4oidMapper; - assert(publisher != nullptr); - m_publisher = publisher; - } + RouteManager(P4OidMapper *p4oidMapper, VRFOrch *vrfOrch, ResponsePublisherInterface *publisher); virtual ~RouteManager() = default; void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; void drain() override; + std::string verifyState(const std::string &key, const std::vector &tuple) override; private: // Applies route entry updates from src to dest. The merged result will be @@ -66,8 +103,8 @@ class RouteManager : public ObjectManagerInterface // Return nullptr if corresponding route entry is not cached. P4RouteEntry *getRouteEntry(const std::string &route_entry_key); - // Validated non-empty fields in a route entry. - ReturnCode validateRouteEntry(const P4RouteEntry &route_entry); + // Performs route entry validation. + ReturnCode validateRouteEntry(const P4RouteEntry &route_entry, const std::string &operation); // Performs route entry validation for SET command. ReturnCode validateSetRouteEntry(const P4RouteEntry &route_entry); @@ -75,26 +112,36 @@ class RouteManager : public ObjectManagerInterface // Performs route entry validation for DEL command. ReturnCode validateDelRouteEntry(const P4RouteEntry &route_entry); - // Creates a route entry. - // Returns a SWSS status code. - ReturnCode createRouteEntry(const P4RouteEntry &route_entry); + // Creates a list of route entries. + std::vector createRouteEntries(const std::vector &route_entries); + + // Updates a list of route entries. + std::vector updateRouteEntries(const std::vector &route_entries); + + // Deletes a list of route entries. + std::vector deleteRouteEntries(const std::vector &route_entries); + + // On a successful route entry update, updates the reference counters and + // internal data. + void updateRouteEntriesMeta(const P4RouteEntry &old_entry, const P4RouteEntry &new_entry); + + // Auxiliary method to perform route update. + void updateRouteAttrs(int size, const std::vector> &updaters, + std::vector &indice, std::vector &statuses); - // Updates a route entry. - // Returns a SWSS status code. - ReturnCode updateRouteEntry(const P4RouteEntry &route_entry); + // Verifies internal cache for an entry. + std::string verifyStateCache(const P4RouteEntry &app_db_entry, const P4RouteEntry *route_entry); - // Deletes a route entry. - // Returns a SWSS status code. - ReturnCode deleteRouteEntry(const P4RouteEntry &route_entry); + // Verifies ASIC DB for an entry. + std::string verifyStateAsicDb(const P4RouteEntry *route_entry); - // Returns the nexthop OID for a given route entry. - // This method will raise critical state if the OID cannot be found. So this - // should only be called after validation. - ReturnCodeOr getNexthopOid(const P4RouteEntry &route_entry); + // Returns the SAI entry. + sai_route_entry_t getSaiEntry(const P4RouteEntry &route_entry); P4RouteTable m_routeTable; P4OidMapper *m_p4OidMapper; VRFOrch *m_vrfOrch; + EntityBulker m_routerBulker; ResponsePublisherInterface *m_publisher; std::deque m_entries; diff --git a/orchagent/p4orch/router_interface_manager.cpp b/orchagent/p4orch/router_interface_manager.cpp index ea9abf083ac9..43dc652eb6de 100644 --- a/orchagent/p4orch/router_interface_manager.cpp +++ b/orchagent/p4orch/router_interface_manager.cpp @@ -1,19 +1,26 @@ #include "p4orch/router_interface_manager.h" +#include #include #include #include #include #include +#include "SaiAttributeList.h" +#include "dbconnector.h" #include "directory.h" #include "json.hpp" #include "logger.h" #include "orch.h" #include "p4orch/p4orch_util.h" #include "portsorch.h" +#include "sai_serialize.h" +#include "table.h" #include "vrforch.h" +using ::p4orch::kTableKeyDelimiter; + extern sai_object_id_t gSwitchId; extern sai_object_id_t gVirtualRouterId; @@ -49,6 +56,68 @@ ReturnCode validateRouterInterfaceAppDbEntry(const P4RouterInterfaceAppDbEntry & return ReturnCode(); } +ReturnCodeOr> getSaiAttrs(const P4RouterInterfaceEntry &router_intf_entry) +{ + Port port; + if (!gPortsOrch->getPort(router_intf_entry.port_name, port)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Failed to get port info for port " << QuotedVar(router_intf_entry.port_name)); + } + + std::vector attrs; + sai_attribute_t attr; + + // Map all P4 router interfaces to default VRF as virtual router is mandatory + // parameter for creation of router interfaces in SAI. + attr.id = SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID; + attr.value.oid = gVirtualRouterId; + attrs.push_back(attr); + + // If mac address is not set then swss::MacAddress initializes mac address + // to 00:00:00:00:00:00. + if (router_intf_entry.src_mac_address.to_string() != "00:00:00:00:00:00") + { + attr.id = SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS; + memcpy(attr.value.mac, router_intf_entry.src_mac_address.getMac(), sizeof(sai_mac_t)); + attrs.push_back(attr); + } + + attr.id = SAI_ROUTER_INTERFACE_ATTR_TYPE; + switch (port.m_type) + { + case Port::PHY: + attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_PORT; + attrs.push_back(attr); + attr.id = SAI_ROUTER_INTERFACE_ATTR_PORT_ID; + attr.value.oid = port.m_port_id; + break; + case Port::LAG: + attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_PORT; + attrs.push_back(attr); + attr.id = SAI_ROUTER_INTERFACE_ATTR_PORT_ID; + attr.value.oid = port.m_lag_id; + break; + case Port::VLAN: + attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_VLAN; + attrs.push_back(attr); + attr.id = SAI_ROUTER_INTERFACE_ATTR_VLAN_ID; + attr.value.oid = port.m_vlan_info.vlan_oid; + break; + // TODO: add support for PORT::SUBPORT + default: + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unsupported port type: " << port.m_type); + } + attrs.push_back(attr); + + // Configure port MTU on router interface + attr.id = SAI_ROUTER_INTERFACE_ATTR_MTU; + attr.value.u32 = port.m_mtu; + attrs.push_back(attr); + + return attrs; +} + } // namespace ReturnCodeOr RouterInterfaceManager::deserializeRouterIntfEntry( @@ -127,62 +196,7 @@ ReturnCode RouterInterfaceManager::createRouterInterface(const std::string &rout << " already exists in the centralized map"); } - Port port; - if (!gPortsOrch->getPort(router_intf_entry.port_name, port)) - { - LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) - << "Failed to get port info for port " << QuotedVar(router_intf_entry.port_name)); - } - - std::vector attrs; - sai_attribute_t attr; - - // Map all P4 router interfaces to default VRF as virtual router is mandatory - // parameter for creation of router interfaces in SAI. - attr.id = SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID; - attr.value.oid = gVirtualRouterId; - attrs.push_back(attr); - - // If mac address is not set then swss::MacAddress initializes mac address - // to 00:00:00:00:00:00. - if (router_intf_entry.src_mac_address.to_string() != "00:00:00:00:00:00") - { - attr.id = SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS; - memcpy(attr.value.mac, router_intf_entry.src_mac_address.getMac(), sizeof(sai_mac_t)); - attrs.push_back(attr); - } - - attr.id = SAI_ROUTER_INTERFACE_ATTR_TYPE; - switch (port.m_type) - { - case Port::PHY: - attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_PORT; - attrs.push_back(attr); - attr.id = SAI_ROUTER_INTERFACE_ATTR_PORT_ID; - attr.value.oid = port.m_port_id; - break; - case Port::LAG: - attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_PORT; - attrs.push_back(attr); - attr.id = SAI_ROUTER_INTERFACE_ATTR_PORT_ID; - attr.value.oid = port.m_lag_id; - break; - case Port::VLAN: - attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_VLAN; - attrs.push_back(attr); - attr.id = SAI_ROUTER_INTERFACE_ATTR_VLAN_ID; - attr.value.oid = port.m_vlan_info.vlan_oid; - break; - // TODO: add support for PORT::SUBPORT - default: - LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unsupported port type: " << port.m_type); - } - attrs.push_back(attr); - - // Configure port MTU on router interface - attr.id = SAI_ROUTER_INTERFACE_ATTR_MTU; - attr.value.u32 = port.m_mtu; - attrs.push_back(attr); + ASSIGN_OR_RETURN(std::vector attrs, getSaiAttrs(router_intf_entry)); CHECK_ERROR_AND_LOG_AND_RETURN( sai_router_intfs_api->create_router_interface(&router_intf_entry.router_interface_oid, gSwitchId, @@ -396,3 +410,121 @@ void RouterInterfaceManager::drain() } m_entries.clear(); } + +std::string RouterInterfaceManager::verifyState(const std::string &key, const std::vector &tuple) +{ + SWSS_LOG_ENTER(); + + auto pos = key.find_first_of(kTableKeyDelimiter); + if (pos == std::string::npos) + { + return std::string("Invalid key: ") + key; + } + std::string p4rt_table = key.substr(0, pos); + std::string p4rt_key = key.substr(pos + 1); + if (p4rt_table != APP_P4RT_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + std::string table_name; + std::string key_content; + parseP4RTKey(p4rt_key, &table_name, &key_content); + if (table_name != APP_P4RT_ROUTER_INTERFACE_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + + auto app_db_entry_or = deserializeRouterIntfEntry(key_content, tuple); + if (!app_db_entry_or.ok()) + { + ReturnCode status = app_db_entry_or.status(); + std::stringstream msg; + msg << "Unable to deserialize key " << QuotedVar(key) << ": " << status.message(); + return msg.str(); + } + auto &app_db_entry = *app_db_entry_or; + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); + auto *router_intf_entry = getRouterInterfaceEntry(router_intf_key); + if (router_intf_entry == nullptr) + { + std::stringstream msg; + msg << "No entry found with key " << QuotedVar(key); + return msg.str(); + } + + std::string cache_result = verifyStateCache(app_db_entry, router_intf_entry); + std::string asic_db_result = verifyStateAsicDb(router_intf_entry); + if (cache_result.empty()) + { + return asic_db_result; + } + if (asic_db_result.empty()) + { + return cache_result; + } + return cache_result + "; " + asic_db_result; +} + +std::string RouterInterfaceManager::verifyStateCache(const P4RouterInterfaceAppDbEntry &app_db_entry, + const P4RouterInterfaceEntry *router_intf_entry) +{ + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); + ReturnCode status = validateRouterInterfaceAppDbEntry(app_db_entry); + if (!status.ok()) + { + std::stringstream msg; + msg << "Validation failed for Router Interface DB entry with key " << QuotedVar(router_intf_key) << ": " + << status.message(); + return msg.str(); + } + if (router_intf_entry->router_interface_id != app_db_entry.router_interface_id) + { + std::stringstream msg; + msg << "Router interface ID " << QuotedVar(app_db_entry.router_interface_id) + << " does not match internal cache " << QuotedVar(router_intf_entry->router_interface_id) + << " in router interface manager."; + return msg.str(); + } + if (router_intf_entry->port_name != app_db_entry.port_name) + { + std::stringstream msg; + msg << "Port name " << QuotedVar(app_db_entry.port_name) << " does not match internal cache " + << QuotedVar(router_intf_entry->port_name) << " in router interface manager."; + return msg.str(); + } + if (router_intf_entry->src_mac_address.to_string() != app_db_entry.src_mac_address.to_string()) + { + std::stringstream msg; + msg << "Source MAC address " << app_db_entry.src_mac_address.to_string() << " does not match internal cache " + << router_intf_entry->src_mac_address.to_string() << " in router interface manager."; + return msg.str(); + } + return m_p4OidMapper->verifyOIDMapping(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, + router_intf_entry->router_interface_oid); +} + +std::string RouterInterfaceManager::verifyStateAsicDb(const P4RouterInterfaceEntry *router_intf_entry) +{ + auto attrs_or = getSaiAttrs(*router_intf_entry); + if (!attrs_or.ok()) + { + return std::string("Failed to get SAI attrs: ") + attrs_or.status().message(); + } + std::vector attrs = *attrs_or; + std::vector exp = saimeta::SaiAttributeList::serialize_attr_list( + SAI_OBJECT_TYPE_ROUTER_INTERFACE, (uint32_t)attrs.size(), attrs.data(), /*countOnly=*/false); + + swss::DBConnector db("ASIC_DB", 0); + swss::Table table(&db, "ASIC_STATE"); + std::string key = sai_serialize_object_type(SAI_OBJECT_TYPE_ROUTER_INTERFACE) + ":" + + sai_serialize_object_id(router_intf_entry->router_interface_oid); + std::vector values; + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + + return verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); +} diff --git a/orchagent/p4orch/router_interface_manager.h b/orchagent/p4orch/router_interface_manager.h index a300b2a7a49a..73d994ff06f1 100644 --- a/orchagent/p4orch/router_interface_manager.h +++ b/orchagent/p4orch/router_interface_manager.h @@ -51,6 +51,7 @@ class RouterInterfaceManager : public ObjectManagerInterface void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; void drain() override; + std::string verifyState(const std::string &key, const std::vector &tuple) override; private: ReturnCodeOr deserializeRouterIntfEntry( @@ -63,6 +64,9 @@ class RouterInterfaceManager : public ObjectManagerInterface ReturnCode processUpdateRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, P4RouterInterfaceEntry *router_intf_entry); ReturnCode processDeleteRequest(const std::string &router_intf_key); + std::string verifyStateCache(const P4RouterInterfaceAppDbEntry &app_db_entry, + const P4RouterInterfaceEntry *router_intf_entry); + std::string verifyStateAsicDb(const P4RouterInterfaceEntry *router_intf_entry); P4RouterInterfaceTable m_routerIntfTable; P4OidMapper *m_p4OidMapper; diff --git a/orchagent/p4orch/tests/Makefile.am b/orchagent/p4orch/tests/Makefile.am index daeb48413621..2af4e8e6134e 100644 --- a/orchagent/p4orch/tests/Makefile.am +++ b/orchagent/p4orch/tests/Makefile.am @@ -34,6 +34,7 @@ p4orch_tests_SOURCES = $(ORCHAGENT_DIR)/orch.cpp \ $(P4ORCH_DIR)/p4orch.cpp \ $(P4ORCH_DIR)/p4orch_util.cpp \ $(P4ORCH_DIR)/router_interface_manager.cpp \ + $(P4ORCH_DIR)/gre_tunnel_manager.cpp \ $(P4ORCH_DIR)/neighbor_manager.cpp \ $(P4ORCH_DIR)/next_hop_manager.cpp \ $(P4ORCH_DIR)/route_manager.cpp \ @@ -42,6 +43,7 @@ p4orch_tests_SOURCES = $(ORCHAGENT_DIR)/orch.cpp \ $(P4ORCH_DIR)/acl_rule_manager.cpp \ $(P4ORCH_DIR)/wcmp_manager.cpp \ $(P4ORCH_DIR)/mirror_session_manager.cpp \ + $(P4ORCH_DIR)/l3_admit_manager.cpp \ $(top_srcdir)/tests/mock_tests/fake_response_publisher.cpp \ fake_portorch.cpp \ fake_crmorch.cpp \ @@ -57,9 +59,11 @@ p4orch_tests_SOURCES = $(ORCHAGENT_DIR)/orch.cpp \ p4orch_util_test.cpp \ return_code_test.cpp \ route_manager_test.cpp \ + gre_tunnel_manager_test.cpp \ next_hop_manager_test.cpp \ wcmp_manager_test.cpp \ acl_manager_test.cpp \ + l3_admit_manager_test.cpp \ router_interface_manager_test.cpp \ neighbor_manager_test.cpp \ mirror_session_manager_test.cpp \ @@ -67,6 +71,7 @@ p4orch_tests_SOURCES = $(ORCHAGENT_DIR)/orch.cpp \ mock_sai_acl.cpp \ mock_sai_hostif.cpp \ mock_sai_serialize.cpp \ + mock_sai_router_interface.cpp \ mock_sai_switch.cpp \ mock_sai_udf.cpp diff --git a/orchagent/p4orch/tests/acl_manager_test.cpp b/orchagent/p4orch/tests/acl_manager_test.cpp index 55abf24606de..b18fdc4fcb02 100644 --- a/orchagent/p4orch/tests/acl_manager_test.cpp +++ b/orchagent/p4orch/tests/acl_manager_test.cpp @@ -23,6 +23,8 @@ #include "tokenize.h" #include "vrforch.h" +using ::p4orch::kTableKeyDelimiter; + extern swss::DBConnector *gAppDb; extern swss::DBConnector *gStateDb; extern swss::DBConnector *gCountersDb; @@ -78,8 +80,85 @@ constexpr sai_object_id_t kAclMeterOid2 = 2002; constexpr sai_object_id_t kAclCounterOid1 = 3001; constexpr sai_object_id_t kUdfGroupOid1 = 4001; constexpr sai_object_id_t kUdfMatchOid1 = 5001; +constexpr sai_object_id_t kUdfOid1 = 6001; constexpr char *kAclIngressTableName = "ACL_PUNT_TABLE"; +// Matches the policer sai_attribute_t[] argument. +bool MatchSaiPolicerAttribute(const int attrs_size, const sai_meter_type_t expected_type, + const sai_packet_action_t expected_gpa, const sai_packet_action_t expected_ypa, + const sai_packet_action_t expected_rpa, const sai_uint64_t expected_cir, + const sai_uint64_t expected_pir, const sai_uint64_t expected_cbs, + const sai_uint64_t expected_pbs, const sai_attribute_t *attr_list) +{ + if (attr_list == nullptr) + { + return false; + } + for (int i = 0; i < attrs_size; ++i) + { + switch (attr_list[i].id) + { + case SAI_POLICER_ATTR_CBS: + if (attr_list[i].value.u64 != expected_cbs) + { + return false; + } + break; + case SAI_POLICER_ATTR_PBS: + if (attr_list[i].value.u64 != expected_pbs) + { + return false; + } + break; + case SAI_POLICER_ATTR_CIR: + if (attr_list[i].value.u64 != expected_cir) + { + return false; + } + break; + case SAI_POLICER_ATTR_PIR: + if (attr_list[i].value.u64 != expected_pir) + { + return false; + } + break; + case SAI_POLICER_ATTR_GREEN_PACKET_ACTION: + if (attr_list[i].value.s32 != expected_gpa) + { + return false; + } + break; + case SAI_POLICER_ATTR_YELLOW_PACKET_ACTION: + if (attr_list[i].value.s32 != expected_ypa) + { + return false; + } + break; + case SAI_POLICER_ATTR_RED_PACKET_ACTION: + if (attr_list[i].value.s32 != expected_rpa) + { + return false; + } + break; + case SAI_POLICER_ATTR_MODE: + if (attr_list[i].value.s32 != SAI_POLICER_MODE_TR_TCM) + { + return false; + } + break; + case SAI_POLICER_ATTR_METER_TYPE: + if (attr_list[i].value.s32 != expected_type) + { + return false; + } + break; + default: + break; + } + } + return true; +} + // Check the ACL stage sai_attribute_t list for ACL table group bool MatchSaiAttributeAclGroupStage(const sai_acl_stage_t expected_stage, const sai_attribute_t *attr_list) { @@ -464,6 +543,8 @@ std::vector getDefaultTableDefFieldValueTuples() swss::FieldValueTuple{"match/icmp_type", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMP_TYPE)}); attributes.push_back( swss::FieldValueTuple{"match/l4_dst_port", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_L4_DST_PORT)}); + attributes.push_back(swss::FieldValueTuple{ + "match/udf2", BuildMatchFieldJsonStrKindUdf("SAI_UDF_BASE_L3", 56, P4_FORMAT_HEX_STRING, 16)}); attributes.push_back(swss::FieldValueTuple{"action/copy_and_set_tc", "[{\"action\":\"SAI_PACKET_ACTION_COPY\",\"packet_color\":\"SAI_" "PACKET_" @@ -484,7 +565,7 @@ P4AclTableDefinitionAppDbEntry getDefaultAclTableDefAppDbEntry() app_db_entry.stage = STAGE_INGRESS; app_db_entry.priority = 234; app_db_entry.meter_unit = P4_METER_UNIT_BYTES; - app_db_entry.counter_unit = P4_COUNTER_UNIT_BYTES; + app_db_entry.counter_unit = P4_COUNTER_UNIT_BOTH; // Match field mapping from P4 program to SAI entry attribute app_db_entry.match_field_lookup["ether_type"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ETHER_TYPE); app_db_entry.match_field_lookup["ether_dst"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_MAC, P4_FORMAT_MAC); @@ -553,6 +634,8 @@ P4AclTableDefinitionAppDbEntry getDefaultAclTableDefAppDbEntry() app_db_entry.match_field_lookup["inner_vlan_pri"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_VLAN_PRI); app_db_entry.match_field_lookup["inner_vlan_id"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_VLAN_ID); app_db_entry.match_field_lookup["inner_vlan_cfi"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_VLAN_CFI); + app_db_entry.match_field_lookup["l3_class_id"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ROUTE_DST_USER_META, P4_FORMAT_HEX_STRING, /*bitwidth=*/6); app_db_entry.match_field_lookup["src_ipv6_64bit"] = BuildMatchFieldJsonStrKindComposite( {nlohmann::json::parse(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_SRC_IPV6_WORD3, P4_FORMAT_IPV6, 32)), nlohmann::json::parse(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_SRC_IPV6_WORD2, P4_FORMAT_IPV6, 32))}, @@ -625,6 +708,24 @@ P4AclTableDefinitionAppDbEntry getDefaultAclTableDefAppDbEntry() app_db_entry.action_field_lookup["set_vrf"].push_back({.sai_action = P4_ACTION_SET_VRF, .p4_param_name = "vrf"}); app_db_entry.action_field_lookup["qos_queue"].push_back( {.sai_action = P4_ACTION_SET_QOS_QUEUE, .p4_param_name = "cpu_queue"}); + + // "action/acl_trap" = [ + // {"action": "SAI_PACKET_ACTION_TRAP", "packet_color": + // "SAI_PACKET_COLOR_GREEN"}, + // {"action": "SAI_PACKET_ACTION_DROP", "packet_color": + // "SAI_PACKET_COLOR_YELLOW"}, + // {"action": "SAI_PACKET_ACTION_DROP", "packet_color": + // "SAI_PACKET_COLOR_RED"}, + // {"action": "QOS_QUEUE", "param": "queue"} + // ] + app_db_entry.action_field_lookup["acl_trap"].push_back( + {.sai_action = P4_ACTION_SET_QOS_QUEUE, .p4_param_name = "queue"}); + app_db_entry.packet_action_color_lookup["acl_trap"].push_back( + {.packet_action = P4_PACKET_ACTION_PUNT, .packet_color = P4_PACKET_COLOR_GREEN}); + app_db_entry.packet_action_color_lookup["acl_trap"].push_back( + {.packet_action = P4_PACKET_ACTION_DROP, .packet_color = P4_PACKET_COLOR_YELLOW}); + app_db_entry.packet_action_color_lookup["acl_trap"].push_back( + {.packet_action = P4_PACKET_ACTION_DROP, .packet_color = P4_PACKET_COLOR_RED}); return app_db_entry; } @@ -659,7 +760,7 @@ P4AclRuleAppDbEntry getDefaultAclRuleAppDbEntryWithoutAction() "\"fdf8:f53b:82e4::53\",\"match/ether_dst\": \"AA:BB:CC:DD:EE:FF\", " "\"match/ether_src\": \"AA:BB:CC:DD:EE:FF\", \"match/ipv6_next_header\": " "\"1\", \"match/src_ipv6_64bit\": " - "\"fdf8:f53b:82e4::\",\"match/arp_tpa\": \"0xff11223\",\"match/udf2\": " + "\"fdf8:f53b:82e4::\",\"match/arp_tpa\": \"0xff112231\",\"match/udf2\": " "\"0x9876 & 0xAAAA\",\"priority\":100}"; // ACL meter fields app_db_entry.meter.enabled = true; @@ -686,14 +787,17 @@ class AclManagerTest : public ::testing::Test setUpCoppOrch(); setUpSwitchOrch(); setUpP4Orch(); - // const auto& acl_groups = gSwitchOrch->getAclGroupOidsBindingToSwitch(); + // const auto& acl_groups = gSwitchOrch->getAclGroupsBindingToSwitch(); // EXPECT_EQ(3, acl_groups.size()); // EXPECT_NE(acl_groups.end(), acl_groups.find(SAI_ACL_STAGE_INGRESS)); - // EXPECT_EQ(kAclGroupIngressOid, acl_groups.at(SAI_ACL_STAGE_INGRESS)); + // EXPECT_EQ(kAclGroupIngressOid, + // acl_groups.at(SAI_ACL_STAGE_INGRESS).m_saiObjectId); // EXPECT_NE(acl_groups.end(), acl_groups.find(SAI_ACL_STAGE_EGRESS)); - // EXPECT_EQ(kAclGroupEgressOid, acl_groups.at(SAI_ACL_STAGE_EGRESS)); + // EXPECT_EQ(kAclGroupEgressOid, + // acl_groups.at(SAI_ACL_STAGE_EGRESS).m_saiObjectId); // EXPECT_NE(acl_groups.end(), acl_groups.find(SAI_ACL_STAGE_PRE_INGRESS)); - // EXPECT_EQ(kAclGroupLookupOid, acl_groups.at(SAI_ACL_STAGE_PRE_INGRESS)); + // EXPECT_EQ(kAclGroupLookupOid, + // acl_groups.at(SAI_ACL_STAGE_PRE_INGRESS).m_saiObjectId); p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_MIRROR_SESSION, KeyGenerator::generateMirrorSessionKey(gMirrorSession1), kMirrorSessionOid1); p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_MIRROR_SESSION, KeyGenerator::generateMirrorSessionKey(gMirrorSession2), @@ -863,7 +967,9 @@ class AclManagerTest : public ::testing::Test EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) .Times(3) .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .Times(3) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); ASSERT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddTableRequest(app_db_entry)); @@ -879,6 +985,10 @@ class AclManagerTest : public ::testing::Test { acl_table_manager_->enqueue(entry); } + std::string VerifyTableState(const std::string &key, const std::vector &tuple) + { + return acl_table_manager_->verifyState(key, tuple); + } void DrainRuleTuples() { @@ -888,6 +998,10 @@ class AclManagerTest : public ::testing::Test { acl_rule_manager_->enqueue(entry); } + std::string VerifyRuleState(const std::string &key, const std::vector &tuple) + { + return acl_rule_manager_->verifyState(key, tuple); + } ReturnCodeOr DeserializeAclTableDefinitionAppDbEntry( const std::string &key, const std::vector &attributes) @@ -942,6 +1056,11 @@ class AclManagerTest : public ::testing::Test acl_rule_manager_->doAclCounterStatsTask(); } + ReturnCode CreateAclGroupMember(const P4AclTableDefinition &acl_table, sai_object_id_t *acl_grp_mem_oid) + { + return acl_table_manager_->createAclGroupMember(acl_table, acl_grp_mem_oid); + } + StrictMock mock_sai_acl_; StrictMock mock_sai_serialize_; StrictMock mock_sai_policer_; @@ -968,6 +1087,12 @@ TEST_F(AclManagerTest, DrainTableTuplesToProcessSetDelRequestSucceeds) .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, Eq(gSwitchId), Eq(3), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf_match(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); DrainTableTuples(); EXPECT_NE(nullptr, GetAclTable(kAclIngressTableName)); @@ -975,6 +1100,8 @@ TEST_F(AclManagerTest, DrainTableTuplesToProcessSetDelRequestSucceeds) EXPECT_CALL(mock_sai_acl_, remove_acl_table(Eq(kAclTableIngressOid))).WillOnce(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(Eq(kAclGroupMemberIngressOid))) .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(Eq(kUdfGroupOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, DEL_COMMAND, {}})); DrainTableTuples(); EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); @@ -995,6 +1122,13 @@ TEST_F(AclManagerTest, DrainTableTuplesToProcessUpdateRequestExpectFails) .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, Eq(gSwitchId), Eq(3), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf_match(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); + DrainTableTuples(); EXPECT_NE(nullptr, GetAclTable(kAclIngressTableName)); @@ -1066,6 +1200,20 @@ TEST_F(AclManagerTest, CreateIngressPuntTableSucceeds) ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); auto acl_table = GetAclTable(kAclIngressTableName); EXPECT_NE(nullptr, acl_table); + sai_object_id_t grp_member_oid; + sai_object_id_t grp_oid; + sai_object_id_t table_oid; + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, kAclIngressTableName, &grp_member_oid)); + EXPECT_EQ(kAclGroupMemberIngressOid, grp_member_oid); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_ACL_TABLE, kAclIngressTableName, &table_oid)); + EXPECT_EQ(kAclTableIngressOid, table_oid); + // The ACL group creation logic is moved out from P4Orch to SwitchOrch + EXPECT_FALSE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_ACL_TABLE_GROUP, kAclIngressTableName, &grp_oid)); + const auto &acl_groups = gSwitchOrch->getAclGroupsBindingToSwitch(); + ASSERT_NE(acl_groups.end(), acl_groups.find(SAI_ACL_STAGE_INGRESS)); + EXPECT_EQ(1, acl_groups.at(SAI_ACL_STAGE_INGRESS).m_objsDependingOnMe.size()); + EXPECT_NE(acl_groups.at(SAI_ACL_STAGE_INGRESS).m_objsDependingOnMe.end(), + acl_groups.at(SAI_ACL_STAGE_INGRESS).m_objsDependingOnMe.find(sai_serialize_object_id(grp_member_oid))); } TEST_F(AclManagerTest, CreatePuntTableFailsWhenUserTrapsSaiCallFails) @@ -1160,7 +1308,9 @@ TEST_F(AclManagerTest, CreateIngressPuntTableFailsWhenCapabilityExceeds) .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .Times(3) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)).WillOnce(Return(SAI_STATUS_INSUFFICIENT_RESOURCES)); EXPECT_CALL(mock_sai_udf_, remove_udf_group(Eq(kUdfGroupOid1))).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_udf_, remove_udf(_)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); @@ -1176,7 +1326,9 @@ TEST_F(AclManagerTest, CreateIngressPuntTableFailsWhenFailedToCreateTableGroupMe .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .Times(3) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); @@ -1195,14 +1347,16 @@ TEST_F(AclManagerTest, CreateIngressPuntTableRaisesCriticalStateWhenAclTableReco .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .Times(3) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_FAILURE)); EXPECT_CALL(mock_sai_udf_, remove_udf_group(Eq(kUdfGroupOid1))).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_udf_, remove_udf(_)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); } @@ -1215,14 +1369,16 @@ TEST_F(AclManagerTest, CreateIngressPuntTableRaisesCriticalStateWhenUdfGroupReco .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .Times(3) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_udf_, remove_udf_group(Eq(kUdfGroupOid1))).Times(3).WillRepeatedly(Return(SAI_STATUS_FAILURE)); EXPECT_CALL(mock_sai_udf_, remove_udf(_)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); - // (TODO): Expect critical state x3. + // TODO: Expect critical state x3. EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); } @@ -1235,7 +1391,9 @@ TEST_F(AclManagerTest, CreateIngressPuntTableRaisesCriticalStateWhenUdfRecoveryF .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .Times(3) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); @@ -1243,7 +1401,7 @@ TEST_F(AclManagerTest, CreateIngressPuntTableRaisesCriticalStateWhenUdfRecoveryF // UDF recovery failure will also cause UDF group recovery failure since the // reference count will not be zero if UDF failed to be removed. EXPECT_CALL(mock_sai_udf_, remove_udf(_)).Times(3).WillRepeatedly(Return(SAI_STATUS_FAILURE)); - // (TODO): Expect critical state x6. + // TODO: Expect critical state x6. EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); } @@ -1270,7 +1428,8 @@ TEST_F(AclManagerTest, CreateIngressPuntTableFailsWhenFailedToCreateUdf) EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) .WillOnce(Return(SAI_STATUS_SUCCESS)) .WillOnce(Return(SAI_STATUS_FAILURE)); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_udf_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); @@ -1284,10 +1443,11 @@ TEST_F(AclManagerTest, CreateIngressPuntTableFailsWhenFailedToCreateUdf) EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) .WillOnce(Return(SAI_STATUS_SUCCESS)) .WillOnce(Return(SAI_STATUS_FAILURE)); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_udf_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_FAILURE)); EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF, std::string(kAclIngressTableName) + "-arp_tpa-0-base1-offset24")); @@ -1299,9 +1459,10 @@ TEST_F(AclManagerTest, CreateIngressPuntTableFailsWhenFailedToCreateUdf) EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) .WillOnce(Return(SAI_STATUS_SUCCESS)) .WillOnce(Return(SAI_STATUS_FAILURE)); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_FAILURE)); - // (TODO): Expect critical state x2. + // TODO: Expect critical state x2. EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF, std::string(kAclIngressTableName) + "-arp_tpa-0-base1-offset24")); @@ -1654,6 +1815,18 @@ TEST_F(AclManagerTest, CreatePuntTableWithInvalidPacketColorFieldFails) EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); } +TEST_F(AclManagerTest, CreateAclGroupMemberFailsWhenAclGroupWasNotFound) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto *acl_table = GetAclTable(kAclIngressTableName); + acl_table->stage = SAI_ACL_STAGE_INGRESS_MACSEC; + sai_object_id_t grp_member_oid; + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + // TODO: Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, CreateAclGroupMember(*acl_table, &grp_member_oid)); +} + TEST_F(AclManagerTest, DeserializeValidAclTableDefAppDbSucceeds) { auto app_db_entry_or = @@ -1793,7 +1966,15 @@ TEST_F(AclManagerTest, RemoveIngressPuntTableSucceeds) EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_udf_, remove_udf_group(Eq(kUdfGroupOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + const auto &acl_groups = gSwitchOrch->getAclGroupsBindingToSwitch(); + ASSERT_NE(acl_groups.end(), acl_groups.find(SAI_ACL_STAGE_INGRESS)); + EXPECT_EQ(1, acl_groups.at(SAI_ACL_STAGE_INGRESS).m_objsDependingOnMe.size()); + EXPECT_NE(acl_groups.at(SAI_ACL_STAGE_INGRESS).m_objsDependingOnMe.end(), + acl_groups.at(SAI_ACL_STAGE_INGRESS) + .m_objsDependingOnMe.find(sai_serialize_object_id(kAclGroupMemberIngressOid))); EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteTableRequest(kAclIngressTableName)); + ASSERT_NE(acl_groups.end(), acl_groups.find(SAI_ACL_STAGE_INGRESS)); + EXPECT_EQ(0, acl_groups.at(SAI_ACL_STAGE_INGRESS).m_objsDependingOnMe.size()); } TEST_F(AclManagerTest, RemoveIngressPuntRuleFails) @@ -1822,7 +2003,7 @@ TEST_F(AclManagerTest, RemoveIngressPuntRuleFails) const auto &table_name_and_rule_key = concatTableNameAndRuleKey(kAclIngressTableName, acl_rule_key); // Fails to remove ACL rule when rule does not exist p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key, kAclIngressRuleOid1); @@ -1844,7 +2025,7 @@ TEST_F(AclManagerTest, RemoveIngressPuntRuleFails) EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_FAILURE))); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); // Fails to remove ACL rule when the counter does not exist. @@ -1855,7 +2036,7 @@ TEST_F(AclManagerTest, RemoveIngressPuntRuleFails) .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key, kAclCounterOid1, 1); p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, kAclMeterOid1); @@ -1875,7 +2056,7 @@ TEST_F(AclManagerTest, RemoveIngressPuntRuleFails) .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_FAILURE))); EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); // Fails to remove ACL rule when sai_acl_api->remove_acl_counter() fails and @@ -1883,7 +2064,7 @@ TEST_F(AclManagerTest, RemoveIngressPuntRuleFails) EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_FAILURE))); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); // Fails to remove ACL rule when the meter does not exist. @@ -1891,7 +2072,7 @@ TEST_F(AclManagerTest, RemoveIngressPuntRuleFails) // not exist. EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, kAclMeterOid1); } @@ -1925,7 +2106,7 @@ TEST_F(AclManagerTest, RemoveAclTableFailsWhenTableDoesNotExist) { ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_ACL_TABLE, kAclIngressTableName); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteTableRequest(kAclIngressTableName)); } @@ -1952,7 +2133,7 @@ TEST_F(AclManagerTest, RemoveAclTableRaisesCriticalStateWhenAclGroupMemberRecove EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_FAILURE)); EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); } @@ -1990,7 +2171,8 @@ TEST_F(AclManagerTest, RemoveAclTableFailsWhenRemoveUdfGroupSaiCallFails) EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_udf_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_FAILURE)); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) @@ -2010,7 +2192,7 @@ TEST_F(AclManagerTest, RemoveAclTableFailsRaisesCriticalStateWhenUdfRecoveryFail .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); } @@ -2024,7 +2206,7 @@ TEST_F(AclManagerTest, RemoveAclTableFailsRaisesCriticalStateWhenUdfGroupRecover // If UDF group recovery fails, UDF recovery and ACL table recovery will also // fail since they depend on the UDF group. EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); - // (TODO): Expect critical state x3. + // TODO: Expect critical state x3. EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); } @@ -2048,7 +2230,8 @@ TEST_F(AclManagerTest, RemoveAclTableFailsWhenUdfGroupHasNonZeroRefCount) EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, _, _, _)) @@ -2057,6 +2240,16 @@ TEST_F(AclManagerTest, RemoveAclTableFailsWhenUdfGroupHasNonZeroRefCount) EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteTableRequest(kAclIngressTableName)); } +TEST_F(AclManagerTest, RemoveAclTableFailsWhenAclGroupWasNotFound) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto *acl_table = GetAclTable(kAclIngressTableName); + acl_table->stage = SAI_ACL_STAGE_INGRESS_MACSEC; + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + // TODO: Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteTableRequest(kAclIngressTableName)); +} + TEST_F(AclManagerTest, RemoveAclGroupsSucceedsAfterCleanup) { // Create ACL table @@ -2115,7 +2308,9 @@ TEST_F(AclManagerTest, DrainRuleTuplesToProcessSetRequestSucceeds) .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .Times(3) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); ASSERT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddTableRequest(app_db_entry)); @@ -2158,7 +2353,8 @@ TEST_F(AclManagerTest, DrainRuleTuplesToProcessSetDelRequestSucceeds) // Drain ACL rule tuple to process SET request EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); - EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclCounterOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); DrainRuleTuples(); @@ -2169,6 +2365,11 @@ TEST_F(AclManagerTest, DrainRuleTuplesToProcessSetDelRequestSucceeds) counters[0] = 100; // green_bytes }), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, get_acl_counter_attribute(Eq(kAclCounterOid1), _, _)) + .WillOnce(DoAll(Invoke([](sai_object_id_t acl_counter_id, uint32_t attr_count, sai_attribute_t *counter_attr) { + counter_attr[0].value.u64 = 100; // bytes + }), + Return(SAI_STATUS_SUCCESS))); DoAclCounterStatsTask(); auto counters_table = std::make_unique(gCountersDb, std::string(COUNTERS_TABLE) + DEFAULT_KEY_SEPARATOR + APP_P4RT_TABLE_NAME); @@ -2406,7 +2607,7 @@ TEST_F(AclManagerTest, CreateAclRuleWithInvalidSaiMatchFails) acl_table->udf_group_attr_index_lookup.clear(); app_db_entry.match_fvs["arp_tpa"] = "0xff112231"; acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); app_db_entry.match_fvs.erase("arp_tpa"); acl_table->udf_group_attr_index_lookup = saved_udf_group_attr_index_lookup; @@ -2513,6 +2714,35 @@ TEST_F(AclManagerTest, AclRuleWithValidMatchFields) EXPECT_EQ(2, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS].aclfield.data.objlist.count); EXPECT_EQ(0x9988, acl_rule->out_ports_oids[0]); EXPECT_EQ(0x56789abcdef, acl_rule->out_ports_oids[1]); + + // Verify SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN + EXPECT_EQ(2, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.data.u8list.count); + EXPECT_EQ(acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].data[0], + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.data.u8list.list[0]); + EXPECT_EQ(acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].data[1], + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.data.u8list.list[1]); + EXPECT_EQ(acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].mask[0], + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.mask.u8list.list[0]); + EXPECT_EQ(acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].mask[1], + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.mask.u8list.list[1]); + EXPECT_EQ(0xff, acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].data[0]); + EXPECT_EQ(0x11, acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].data[1]); + EXPECT_EQ(0xff, acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].mask[0]); + EXPECT_EQ(0xff, acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].mask[1]); + // Verify SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1 + EXPECT_EQ(2, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].aclfield.data.objlist.count); + EXPECT_EQ(acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].data[0], + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].aclfield.data.u8list.list[0]); + EXPECT_EQ(acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].data[1], + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].aclfield.data.u8list.list[1]); + EXPECT_EQ(acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].mask[0], + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].aclfield.mask.u8list.list[0]); + EXPECT_EQ(acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].mask[1], + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].aclfield.mask.u8list.list[1]); + EXPECT_EQ(0x22, acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].data[0]); + EXPECT_EQ(0x31, acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].data[1]); + EXPECT_EQ(0xff, acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].mask[0]); + EXPECT_EQ(0xff, acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1].mask[1]); EXPECT_EQ(0xaabbccdd, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORT].aclfield.data.oid); EXPECT_EQ(0x56789abcdff, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORT].aclfield.data.oid); EXPECT_EQ(0x2, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_TCP_FLAGS].aclfield.data.u8); @@ -2564,6 +2794,54 @@ TEST_F(AclManagerTest, AclRuleWithValidMatchFields) EXPECT_EQ(0x20, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); } +TEST_F(AclManagerTest, AclRuleWithColorPacketActionsButNoRateLimit) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + // Create app_db_entry with color packet action, but no rate limit attributes + P4AclRuleAppDbEntry app_db_entry; + app_db_entry.acl_table_name = kAclIngressTableName; + app_db_entry.priority = 100; + // ACL rule match fields + app_db_entry.match_fvs["ether_type"] = "0x0800"; + app_db_entry.match_fvs["ipv6_dst"] = "fdf8:f53b:82e4::53"; + app_db_entry.match_fvs["ether_dst"] = "AA:BB:CC:DD:EE:FF"; + app_db_entry.match_fvs["ether_src"] = "AA:BB:CC:DD:EE:FF"; + app_db_entry.match_fvs["ipv6_next_header"] = "1"; + app_db_entry.match_fvs["src_ipv6_64bit"] = "fdf8:f53b:82e4::"; + app_db_entry.match_fvs["arp_tpa"] = "0xff112231"; + app_db_entry.match_fvs["udf2"] = "0x9876 & 0xAAAA"; + app_db_entry.db_key = "ACL_PUNT_TABLE:{\"match/ether_type\": \"0x0800\",\"match/ipv6_dst\": " + "\"fdf8:f53b:82e4::53\",\"match/ether_dst\": \"AA:BB:CC:DD:EE:FF\", " + "\"match/ether_src\": \"AA:BB:CC:DD:EE:FF\", \"match/ipv6_next_header\": " + "\"1\", \"match/src_ipv6_64bit\": " + "\"fdf8:f53b:82e4::\",\"match/arp_tpa\": \"0xff112231\",\"match/udf2\": " + "\"0x9876 & 0xAAAA\",\"priority\":100}"; + + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + // Set user defined trap for QOS_QUEUE, and color packet actions in meter + int queue_num = 8; + app_db_entry.action = "acl_trap"; + app_db_entry.action_param_fvs["queue"] = std::to_string(queue_num); + // Install rule + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, + create_policer(_, Eq(gSwitchId), Eq(9), + Truly(std::bind(MatchSaiPolicerAttribute, 9, SAI_METER_TYPE_PACKETS, + SAI_PACKET_ACTION_TRAP, SAI_PACKET_ACTION_DROP, SAI_PACKET_ACTION_DROP, + 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + auto acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(gUserDefinedTrapStartOid + queue_num, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID].aclaction.parameter.oid); +} + TEST_F(AclManagerTest, AclRuleWithValidAction) { ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); @@ -3228,11 +3506,11 @@ TEST_F(AclManagerTest, UpdateAclRuleWithActionMeterChange) EXPECT_EQ(2, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.enable); EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); - EXPECT_FALSE(acl_rule->meter.enabled); - EXPECT_EQ(0, acl_rule->meter.cburst); - EXPECT_EQ(0, acl_rule->meter.cir); - EXPECT_EQ(0, acl_rule->meter.pburst); - EXPECT_EQ(0, acl_rule->meter.pir); + EXPECT_TRUE(acl_rule->meter.enabled); + EXPECT_EQ(0x7fffffff, acl_rule->meter.cburst); + EXPECT_EQ(0x7fffffff, acl_rule->meter.cir); + EXPECT_EQ(0x7fffffff, acl_rule->meter.pburst); + EXPECT_EQ(0x7fffffff, acl_rule->meter.pir); // Update meter: enable rate limiting and reset green packet action app_db_entry.action = "punt_and_set_tc"; @@ -3649,7 +3927,7 @@ TEST_F(AclManagerTest, UpdateAclRuleFailsWhenSaiCallFails) .WillOnce(Return(SAI_STATUS_SUCCESS)) .WillOnce(Return(SAI_STATUS_FAILURE)) .WillOnce(Return(SAI_STATUS_FAILURE)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); ASSERT_NE(nullptr, acl_rule); @@ -3724,7 +4002,7 @@ TEST_F(AclManagerTest, UpdateAclRuleFailsWhenSaiCallFails) .WillOnce(Return(SAI_STATUS_FAILURE)); EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); ASSERT_NE(nullptr, acl_rule); @@ -3752,7 +4030,7 @@ TEST_F(AclManagerTest, UpdateAclRuleFailsWhenSaiCallFails) .WillOnce(Return(SAI_STATUS_SUCCESS)) .WillOnce(Return(SAI_STATUS_SUCCESS)) .WillOnce(Return(SAI_STATUS_FAILURE)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); ASSERT_NE(nullptr, acl_rule); @@ -3815,7 +4093,7 @@ TEST_F(AclManagerTest, UpdateAclRuleFailsWhenSaiCallFails) EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) .WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid2))).WillOnce(Return(SAI_STATUS_FAILURE)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); ASSERT_NE(nullptr, acl_rule); @@ -3934,7 +4212,7 @@ TEST_F(AclManagerTest, CreateAclRuleWithInvalidUnitsInTableFails) // Invalid counter unit acl_table->counter_unit = "INVALID"; EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); - acl_table->counter_unit = P4_COUNTER_UNIT_BYTES; + acl_table->counter_unit = P4_COUNTER_UNIT_BOTH; } TEST_F(AclManagerTest, CreateAclRuleFailsWhenSaiCallFails) @@ -3985,7 +4263,7 @@ TEST_F(AclManagerTest, CreateAclRuleFailsWhenSaiCallFails) EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)).WillOnce(Return(SAI_STATUS_NOT_IMPLEMENTED)); EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_FAILURE)); EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_FAILURE)); - // (TODO): Expect critical state x2. + // TODO: Expect critical state x2. EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); // Fails to create ACL rule when sai_acl_api->create_policer() fails @@ -4003,7 +4281,7 @@ TEST_F(AclManagerTest, CreateAclRuleFailsWhenSaiCallFails) EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_NOT_IMPLEMENTED)); EXPECT_CALL(mock_sai_policer_, remove_policer(_)).WillOnce(Return(SAI_STATUS_FAILURE)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); } @@ -4057,7 +4335,7 @@ TEST_F(AclManagerTest, DeleteAclRuleWhenTableDoesNotExistFails) p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_ACL_TABLE, kAclIngressTableName); EXPECT_CALL(mock_sai_acl_, remove_acl_entry(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); EXPECT_CALL(mock_sai_policer_, remove_policer(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); } @@ -4073,7 +4351,9 @@ TEST_F(AclManagerTest, DoAclCounterStatsTaskSucceeds) .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); - EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .Times(3) + .WillRepeatedly(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); ASSERT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddTableRequest(app_db_def_entry)); @@ -4137,6 +4417,12 @@ TEST_F(AclManagerTest, DoAclCounterStatsTaskSucceeds) counters[1] = 100; // green_bytes }), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, get_acl_counter_attribute(Eq(kAclCounterOid1), _, _)) + .WillOnce(DoAll(Invoke([](sai_object_id_t acl_counter_id, uint32_t attr_count, sai_attribute_t *counter_attr) { + counter_attr[0].value.u64 = 10; // packets + counter_attr[1].value.u64 = 100; // bytes + }), + Return(SAI_STATUS_SUCCESS))); DoAclCounterStatsTask(); // Only green_packets and green_bytes are populated in COUNTERS_DB EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_GREEN_PACKETS, stats)); @@ -4148,8 +4434,10 @@ TEST_F(AclManagerTest, DoAclCounterStatsTaskSucceeds) EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_RED_BYTES, stats)); EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_YELLOW_PACKETS, stats)); EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_YELLOW_BYTES, stats)); - EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_PACKETS, stats)); - EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_BYTES, stats)); + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_PACKETS, stats)); + EXPECT_EQ("10", stats); + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_BYTES, stats)); + EXPECT_EQ("100", stats); // Remove rule EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); @@ -4169,6 +4457,12 @@ TEST_F(AclManagerTest, DoAclCounterStatsTaskSucceeds) counters[3] = 300; // red_bytes }), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, get_acl_counter_attribute(Eq(kAclCounterOid1), _, _)) + .WillOnce(DoAll(Invoke([](sai_object_id_t acl_counter_id, uint32_t attr_count, sai_attribute_t *counter_attr) { + counter_attr[0].value.u64 = 50; // packets + counter_attr[1].value.u64 = 500; // bytes + }), + Return(SAI_STATUS_SUCCESS))); DoAclCounterStatsTask(); // Only yellow/red_packets and yellow/red_bytes are populated in COUNTERS_DB EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_YELLOW_PACKETS, stats)); @@ -4181,8 +4475,10 @@ TEST_F(AclManagerTest, DoAclCounterStatsTaskSucceeds) EXPECT_EQ("300", stats); EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_GREEN_PACKETS, stats)); EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_GREEN_BYTES, stats)); - EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_PACKETS, stats)); - EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_BYTES, stats)); + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_PACKETS, stats)); + EXPECT_EQ("50", stats); + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_BYTES, stats)); + EXPECT_EQ("500", stats); // Remove rule EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); @@ -4265,5 +4561,729 @@ TEST_F(AclManagerTest, DISABLED_InitBindGroupToSwitchFails) EXPECT_THROW(new SwitchOrch(gAppDb, switch_tables, stateDbSwitchTable), std::runtime_error); } +TEST_F(AclManagerTest, AclTableVerifyStateTest) +{ + const auto &p4rtAclTableName = + std::string(APP_P4RT_ACL_TABLE_DEFINITION_NAME) + kTableKeyDelimiter + kAclIngressTableName; + std::vector attributes = getDefaultTableDefFieldValueTuples(); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, Eq(gSwitchId), Gt(2), + Truly(std::bind(MatchSaiAttributeAclTableStage, SAI_ACL_STAGE_INGRESS, + std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, Eq(gSwitchId), Eq(3), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf_match(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); + DrainTableTuples(); + auto *acl_table = GetAclTable(kAclIngressTableName); + EXPECT_NE(acl_table, nullptr); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_ACL_TABLE:oid:0x7000000000606", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_ACL_STAGE", "SAI_ACL_STAGE_INGRESS"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_SIZE", "123"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_DST_MAC", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_TTL", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_USER_DEFINED_FIELD_GROUP_MIN", "oid:0xfa1"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST", "1:SAI_ACL_ACTION_TYPE_COUNTER"}}); + table.set("SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER:oid:0xc000000000607", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_GROUP_ID", "oid:0xb00000000058f"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_ID", "oid:0x7000000000606"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_GROUP_MEMBER_ATTR_PRIORITY", "234"}}); + table.set("SAI_OBJECT_TYPE_UDF_GROUP:oid:0xfa1", + std::vector{ + swss::FieldValueTuple{"SAI_UDF_GROUP_ATTR_TYPE", "SAI_UDF_GROUP_TYPE_GENERIC"}, + swss::FieldValueTuple{"SAI_UDF_GROUP_ATTR_LENGTH", "2"}}); + table.set("SAI_OBJECT_TYPE_UDF:oid:0x1771", + std::vector{swss::FieldValueTuple{"SAI_UDF_ATTR_GROUP_ID", "oid:0xfa1"}, + swss::FieldValueTuple{"SAI_UDF_ATTR_MATCH_ID", "oid:0x1389"}, + swss::FieldValueTuple{"SAI_UDF_ATTR_BASE", "SAI_UDF_BASE_L3"}, + swss::FieldValueTuple{"SAI_UDF_ATTR_OFFSET", "56"}}); + + // Verification should succeed with vaild key and value. + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + p4rtAclTableName; + EXPECT_EQ(VerifyTableState(db_key, attributes), ""); + + // Invalid key should fail verification. + EXPECT_FALSE(VerifyTableState("invalid", attributes).empty()); + EXPECT_FALSE(VerifyTableState("invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyTableState(std::string(APP_P4RT_TABLE_NAME) + ":invalid", attributes).empty()); + EXPECT_FALSE(VerifyTableState(std::string(APP_P4RT_TABLE_NAME) + ":invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyTableState(std::string(APP_P4RT_TABLE_NAME) + ":DEFINITION:invalid", attributes).empty()); + + // Verification should fail with invalid attribute. + EXPECT_FALSE( + VerifyTableState(db_key, std::vector{swss::FieldValueTuple{kSize, "-1"}}).empty()); + EXPECT_FALSE( + VerifyTableState(db_key, std::vector{swss::FieldValueTuple{"meter/unit", "invalid"}}) + .empty()); + EXPECT_FALSE( + VerifyTableState(db_key, std::vector{swss::FieldValueTuple{kStage, "invalid"}}).empty()); + EXPECT_FALSE(VerifyTableState(db_key, + std::vector{ + swss::FieldValueTuple{kStage, STAGE_INGRESS}, swss::FieldValueTuple{kSize, "123"}, + swss::FieldValueTuple{kPriority, "234"}, + swss::FieldValueTuple{"meter/unit", P4_METER_UNIT_BYTES}, + swss::FieldValueTuple{"counter/unit", P4_COUNTER_UNIT_BOTH}, + swss::FieldValueTuple{"match/ether_type", "invalid"}}) + .empty()); + EXPECT_FALSE(VerifyTableState(db_key, + std::vector{ + swss::FieldValueTuple{kStage, STAGE_INGRESS}, swss::FieldValueTuple{kSize, "123"}, + swss::FieldValueTuple{kPriority, "234"}, + swss::FieldValueTuple{"meter/unit", P4_METER_UNIT_BYTES}, + swss::FieldValueTuple{"counter/unit", P4_COUNTER_UNIT_BOTH}, + swss::FieldValueTuple{"action/copy_and_set_tc", "[{\"action\":\"invalid\"}]"}}) + .empty()); + EXPECT_FALSE( + VerifyTableState(db_key, + std::vector{ + swss::FieldValueTuple{kStage, STAGE_INGRESS}, swss::FieldValueTuple{kSize, "123"}, + swss::FieldValueTuple{kPriority, "234"}, + swss::FieldValueTuple{"meter/unit", P4_METER_UNIT_BYTES}, + swss::FieldValueTuple{"counter/unit", P4_COUNTER_UNIT_BOTH}, + swss::FieldValueTuple{"action/punt_and_set_tc", "[{\"action\":\"SAI_PACKET_ACTION_COPY\"," + "\"packet_color\":\"invalid\"}]"}}) + .empty()); + + // Verification should fail if ACL table name mismatches. + auto saved_acl_table_name = acl_table->acl_table_name; + acl_table->acl_table_name = "invalid"; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->acl_table_name = saved_acl_table_name; + + // Verification should fail if stage mismatches. + auto saved_stage = acl_table->stage; + acl_table->stage = SAI_ACL_STAGE_EGRESS; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->stage = saved_stage; + + // Verification should fail if size mismatches. + auto saved_size = acl_table->size; + acl_table->size = 111; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->size = saved_size; + + // Verification should fail if priority mismatches. + auto saved_priority = acl_table->priority; + acl_table->priority = 111; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->priority = saved_priority; + + // Verification should fail if meter unit mismatches. + auto saved_meter_unit = acl_table->meter_unit; + acl_table->meter_unit = P4_METER_UNIT_PACKETS; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->meter_unit = saved_meter_unit; + + // Verification should fail if counter unit mismatches. + auto saved_counter_unit = acl_table->counter_unit; + acl_table->counter_unit = P4_COUNTER_UNIT_PACKETS; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->counter_unit = saved_counter_unit; + + // Verification should fail if composite SAI match fields lookup mismatches. + acl_table->composite_sai_match_fields_lookup["invalid"] = std::vector{}; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->composite_sai_match_fields_lookup.erase("invalid"); + + // Verification should fail if UDF fields lookup mismatches. + acl_table->udf_fields_lookup["invalid"] = std::vector{}; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->udf_fields_lookup.erase("invalid"); + + // Verification should fail if UDF group attr index lookup mismatches. + acl_table->udf_group_attr_index_lookup["invalid"] = 0; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->udf_group_attr_index_lookup.erase("invalid"); + + // Verification should fail if SAI match field mismatches. + acl_table->sai_match_field_lookup["invalid"] = SaiMatchField{}; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->sai_match_field_lookup.erase("invalid"); + + // Verification should fail if IP type bit type lookup mismatches. + acl_table->ip_type_bit_type_lookup["invalid"] = "invalid"; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->ip_type_bit_type_lookup.erase("invalid"); + + // Verification should fail if rule action field lookup mismatches. + acl_table->rule_action_field_lookup["invalid"] = std::vector{}; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->rule_action_field_lookup.erase("invalid"); + + // Verification should fail if rule packet action color lookup mismatches. + acl_table->rule_packet_action_color_lookup["invalid"] = std::map{}; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->rule_packet_action_color_lookup.erase("invalid"); + + // Verification should fail if group member OID mapping mismatches. + auto saved_group_member_oid = acl_table->group_member_oid; + acl_table->group_member_oid = 0; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->group_member_oid = saved_group_member_oid; + + // Verification should fail if ACL table OID mapping mismatches. + auto saved_table_oid = acl_table->table_oid; + acl_table->table_oid = 0; + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + acl_table->table_oid = saved_table_oid; +} + +TEST_F(AclManagerTest, AclRuleVerifyStateTest) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{kAction, "mirror_ingress"}); + attributes.push_back(swss::FieldValueTuple{"param/target", gMirrorSession1}); + attributes.push_back(swss::FieldValueTuple{"meter/cir", "80"}); + attributes.push_back(swss::FieldValueTuple{"meter/cburst", "80"}); + attributes.push_back(swss::FieldValueTuple{"meter/pir", "200"}); + attributes.push_back(swss::FieldValueTuple{"meter/pburst", "200"}); + attributes.push_back(swss::FieldValueTuple{"controller_metadata", "..."}); + const auto &acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\",\"match/arp_tpa\": \"0xff112231\", " + "\"match/in_ports\": \"Ethernet1,Ethernet2\", \"match/out_ports\": " + "\"Ethernet4,Ethernet5\", \"priority\":15}"; + const auto &rule_tuple_key = std::string(kAclIngressTableName) + kTableKeyDelimiter + acl_rule_json_key; + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, SET_COMMAND, attributes})); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclCounterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + DrainRuleTuples(); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set( + "SAI_OBJECT_TYPE_ACL_ENTRY:oid:0x3e9", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_TABLE_ID", "oid:0x7000000000606"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_PRIORITY", "15"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_ADMIN_STATE", "true"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_FIELD_DST_IPV6", "fdf8:f53b:82e4::53&mask:fdf8:f53b:82e4::53"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE", "2048&mask:0xffff"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE", + "SAI_ACL_IP_TYPE_ANY&mask:0xffffffffffffffff"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN", "2:255,17&mask:2:0xff,0xff"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_1", "2:34,49&mask:2:0xff,0xff"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS", "2:oid:0x112233,oid:0x1fed3"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS", "2:oid:0x9988,oid:0x56789abcdef"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS", "1:oid:0x2329"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_ACTION_SET_POLICER", "oid:0x7d1"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_ACTION_COUNTER", "oid:0xbb9"}}); + table.set("SAI_OBJECT_TYPE_ACL_COUNTER:oid:0xbb9", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_COUNTER_ATTR_TABLE_ID", "oid:0x7000000000606"}, + swss::FieldValueTuple{"SAI_ACL_COUNTER_ATTR_ENABLE_BYTE_COUNT", "true"}, + swss::FieldValueTuple{"SAI_ACL_COUNTER_ATTR_ENABLE_PACKET_COUNT", "true"}}); + table.set( + "SAI_OBJECT_TYPE_POLICER:oid:0x7d1", + std::vector{ + swss::FieldValueTuple{"SAI_POLICER_ATTR_MODE", "SAI_POLICER_MODE_TR_TCM"}, + swss::FieldValueTuple{"SAI_POLICER_ATTR_METER_TYPE", "SAI_METER_TYPE_BYTES"}, + swss::FieldValueTuple{"SAI_POLICER_ATTR_CBS", "80"}, swss::FieldValueTuple{"SAI_POLICER_ATTR_CIR", "80"}, + swss::FieldValueTuple{"SAI_POLICER_ATTR_PIR", "200"}, swss::FieldValueTuple{"SAI_POLICER_ATTR_PBS", "200"}, + swss::FieldValueTuple{"SAI_POLICER_ATTR_GREEN_PACKET_ACTION", "SAI_PACKET_ACTION_COPY"}}); + + // Verification should succeed with vaild key and value. + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + rule_tuple_key; + EXPECT_EQ(VerifyRuleState(db_key, attributes), ""); + + // Invalid key should fail verification. + EXPECT_FALSE(VerifyRuleState("invalid", attributes).empty()); + EXPECT_FALSE(VerifyRuleState("invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyRuleState(std::string(APP_P4RT_TABLE_NAME) + ":invalid", attributes).empty()); + EXPECT_FALSE(VerifyRuleState(std::string(APP_P4RT_TABLE_NAME) + ":invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyRuleState(std::string(APP_P4RT_TABLE_NAME) + ":ACL_PUNT_TABLE:invalid", attributes).empty()); + EXPECT_FALSE(VerifyRuleState(std::string(APP_P4RT_TABLE_NAME) + + ":ACL_PUNT_TABLE:{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\",\"priority\":0}", + attributes) + .empty()); + EXPECT_FALSE(VerifyRuleState(std::string(APP_P4RT_TABLE_NAME) + + ":ACL_PUNT_TABLE:{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"127.0.0.1/24\",\"priority\":15}", + attributes) + .empty()); + + // Verification should fail if entry does not exist. + EXPECT_FALSE(VerifyRuleState(std::string(APP_P4RT_TABLE_NAME) + + ":ACL_PUNT_TABLE:{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::54 & " + "fdf8:f53b:82e4::54\",\"priority\":15}", + attributes) + .empty()); + + // Verification should fail with invalid attribute. + EXPECT_FALSE(VerifyTableState(db_key, std::vector{{kAction, "invalid"}}).empty()); + + auto *acl_table = GetAclTable(kAclIngressTableName); + EXPECT_NE(acl_table, nullptr); + const auto &acl_rule_key = "match/arp_tpa=0xff112231:match/ether_type=0x0800:match/" + "in_ports=Ethernet1,Ethernet2:match/ipv6_dst=fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53:match/out_ports=Ethernet4,Ethernet5:priority=15"; + auto *acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(acl_rule, nullptr); + + // Verification should fail if ACL rule key mismatches. + auto saved_acl_rule_key = acl_rule->acl_rule_key; + acl_rule->acl_rule_key = "invalid"; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->acl_rule_key = saved_acl_rule_key; + + // Verification should fail if ACL table name mismatches. + auto saved_acl_table_name = acl_rule->acl_table_name; + acl_rule->acl_table_name = "invalid"; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->acl_table_name = saved_acl_table_name; + + // Verification should fail if DB key mismatches. + auto saved_db_key = acl_rule->db_key; + acl_rule->db_key = "invalid"; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->db_key = saved_db_key; + + // Verification should fail if action mismatches. + auto saved_p4_action = acl_rule->p4_action; + acl_rule->p4_action = "invalid"; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->p4_action = saved_p4_action; + + // Verification should fail if priority mismatches. + auto saved_priority = acl_rule->priority; + acl_rule->priority = 111; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->priority = saved_priority; + + // Verification should fail if ACL table name mismatches. + saved_acl_table_name = acl_table->acl_table_name; + acl_table->acl_table_name = "invalid"; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_table->acl_table_name = saved_acl_table_name; + + // Verification should fail if ACL table OID mismatches. + auto saved_acl_table_oid = acl_rule->acl_table_oid; + acl_rule->acl_table_oid = 0; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->acl_table_oid = saved_acl_table_oid; + + // Verification should fail if ACL table meter unit is invalid. + auto saved_meter_unit = acl_table->meter_unit; + acl_table->meter_unit = "invalid"; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_table->meter_unit = saved_meter_unit; + + // Verification should fail if ACL table counter unit mismatches. + auto saved_counter_unit = acl_table->counter_unit; + acl_table->counter_unit = P4_COUNTER_UNIT_PACKETS; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_table->counter_unit = P4_COUNTER_UNIT_BYTES; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_table->counter_unit = saved_counter_unit; + + // Verification should fail if mirror_ingress action is incorrect + EXPECT_NE(acl_rule->action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS), acl_rule->action_fvs.end()); + auto mirror_sessions = std::move(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS]); + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS] = sai_attribute_value_t{}; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + + acl_rule->action_fvs.erase(SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS); + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS] = sai_attribute_value_t{}; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS] = std::move(mirror_sessions); + acl_rule->action_fvs.erase(SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS); + + // Verification should fail if match fvs mismatches. + auto saved_match_fvs = acl_rule->match_fvs; + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORT] = sai_attribute_value_t{}; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->match_fvs.erase(SAI_ACL_ENTRY_ATTR_FIELD_IN_PORT); + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE] = sai_attribute_value_t{}; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->match_fvs.erase(SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE); + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORT] = sai_attribute_value_t{}; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->match_fvs = saved_match_fvs; + + // Verification should fail if action fvs mismatches. + auto saved_action_fvs = acl_rule->action_fvs; + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT] = sai_attribute_value_t{}; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->action_fvs.erase(SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT); + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC] = sai_attribute_value_t{}; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->action_fvs.erase(SAI_ACL_ENTRY_ATTR_ACTION_SET_TC); + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT] = sai_attribute_value_t{}; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->action_fvs = saved_action_fvs; + + // Verification should fail if meter mismatches. + auto saved_meter_cir = acl_rule->meter.cir; + acl_rule->meter.cir = 0; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->meter.cir = saved_meter_cir; + + // Verification should fail if counter mismatches. + auto saved_counter_bytes_enabled = acl_rule->counter.bytes_enabled; + acl_rule->counter.bytes_enabled = false; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->counter.bytes_enabled = saved_counter_bytes_enabled; + + // Verification should fail if action qos queue number mismatches. + auto saved_action_qos_queue_num = acl_rule->action_qos_queue_num; + acl_rule->action_qos_queue_num = 111; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->action_qos_queue_num = saved_action_qos_queue_num; + + // Verification should fail if action redirect nexthop key mismatches. + auto saved_action_redirect_nexthop_key = acl_rule->action_redirect_nexthop_key; + acl_rule->action_redirect_nexthop_key = 111; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->action_redirect_nexthop_key = saved_action_redirect_nexthop_key; + + // Verification should fail if action mirror section mismatches. + acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS] = P4AclMirrorSession{}; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->action_mirror_sessions.erase(SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS); + + // Verification should fail if UDF data mask mismatches. + acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_2] = P4UdfDataMask{}; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->udf_data_masks.erase(SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_2); + + // Verification should fail if UDF data mask pointer mismatches. + auto udf_data_mask = std::move(acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN]); + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN] = sai_attribute_value_t{}; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()) << VerifyRuleState(db_key, attributes); + + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.data.u8list.count = 1; + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.mask.u8list.count = 2; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()) << VerifyRuleState(db_key, attributes); + + acl_rule->match_fvs.erase(SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN); + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_2] = sai_attribute_value_t{}; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()) << VerifyRuleState(db_key, attributes); + acl_rule->match_fvs.erase(SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_2); + + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.data.u8list.list = nullptr; + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.data.u8list.count = 2; + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.mask.u8list.list = + acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].mask.data(); + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.mask.u8list.count = 2; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()) << VerifyRuleState(db_key, attributes); + + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.data.u8list.list = + acl_rule->udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].data.data(); + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN].aclfield.mask.u8list.list = nullptr; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()) << VerifyRuleState(db_key, attributes); + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN] = std::move(udf_data_mask); + + // Verification should fail if in ports mismatches. + acl_rule->in_ports.push_back("invalid"); + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->in_ports.pop_back(); + + // Verification should fail if out ports mismatches. + acl_rule->out_ports.push_back("invalid"); + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->out_ports.pop_back(); + + // Verification should fail if in ports OIDs mismatches. + acl_rule->in_ports_oids.push_back(0); + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->in_ports_oids.pop_back(); + + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS].aclfield.data.objlist.list = nullptr; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()) << VerifyRuleState(db_key, attributes); + + acl_rule->match_fvs.erase(SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS); + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()) << VerifyRuleState(db_key, attributes); + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS].aclfield.data.objlist.list = acl_rule->in_ports_oids.data(); + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS].aclfield.enable = true; + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS].aclfield.data.objlist.count = 2; + EXPECT_TRUE(VerifyRuleState(db_key, attributes).empty()) << VerifyRuleState(db_key, attributes); + + // Verification should fail if out ports OIDs mismatches. + acl_rule->out_ports_oids.push_back(0); + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()) << VerifyRuleState(db_key, attributes); + acl_rule->out_ports_oids.pop_back(); + + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS].aclfield.data.objlist.list = nullptr; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()) << VerifyRuleState(db_key, attributes); + + acl_rule->match_fvs.erase(SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS); + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()) << VerifyRuleState(db_key, attributes); + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS].aclfield.data.objlist.list = + acl_rule->out_ports_oids.data(); + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS].aclfield.data.objlist.count = 2; + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS].aclfield.enable = true; + EXPECT_TRUE(VerifyRuleState(db_key, attributes).empty()) << VerifyRuleState(db_key, attributes); + + // Verification should fail if ACL rule OID mismatches. + auto saved_acl_entry_oid = acl_rule->acl_entry_oid; + acl_rule->acl_entry_oid = 0; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->acl_entry_oid = saved_acl_entry_oid; + + // Verification should fail if meter OID mismatches. + auto saved_meter_oid = acl_rule->meter.meter_oid; + acl_rule->meter.meter_oid = 0; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->meter.meter_oid = saved_meter_oid; + + // Verification should fail if counter OID mismatches. + auto saved_counter_oid = acl_rule->counter.counter_oid; + acl_rule->counter.counter_oid = 0; + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + acl_rule->counter.counter_oid = saved_counter_oid; +} + +TEST_F(AclManagerTest, AclTableVerifyStateAsicDbTest) +{ + const auto &p4rtAclTableName = + std::string(APP_P4RT_ACL_TABLE_DEFINITION_NAME) + kTableKeyDelimiter + kAclIngressTableName; + std::vector attributes = getDefaultTableDefFieldValueTuples(); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + EXPECT_CALL(mock_sai_acl_, create_acl_table(_, Eq(gSwitchId), Gt(2), + Truly(std::bind(MatchSaiAttributeAclTableStage, SAI_ACL_STAGE_INGRESS, + std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kAclTableIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group_member(_, Eq(gSwitchId), Eq(3), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf_match(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfOid1), Return(SAI_STATUS_SUCCESS))); + DrainTableTuples(); + auto *acl_table = GetAclTable(kAclIngressTableName); + EXPECT_NE(acl_table, nullptr); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_ACL_TABLE:oid:0x7000000000606", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_ACL_STAGE", "SAI_ACL_STAGE_INGRESS"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_SIZE", "123"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_DST_MAC", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_TTL", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_USER_DEFINED_FIELD_GROUP_MIN", "oid:0xfa1"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST", "1:SAI_ACL_ACTION_TYPE_COUNTER"}}); + table.set("SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER:oid:0xc000000000607", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_GROUP_ID", "oid:0xb00000000058f"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_ID", "oid:0x7000000000606"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_GROUP_MEMBER_ATTR_PRIORITY", "234"}}); + table.set("SAI_OBJECT_TYPE_UDF_GROUP:oid:0xfa1", + std::vector{ + swss::FieldValueTuple{"SAI_UDF_GROUP_ATTR_TYPE", "SAI_UDF_GROUP_TYPE_GENERIC"}, + swss::FieldValueTuple{"SAI_UDF_GROUP_ATTR_LENGTH", "2"}}); + table.set("SAI_OBJECT_TYPE_UDF:oid:0x1771", + std::vector{swss::FieldValueTuple{"SAI_UDF_ATTR_GROUP_ID", "oid:0xfa1"}, + swss::FieldValueTuple{"SAI_UDF_ATTR_MATCH_ID", "oid:0x1389"}, + swss::FieldValueTuple{"SAI_UDF_ATTR_BASE", "SAI_UDF_BASE_L3"}, + swss::FieldValueTuple{"SAI_UDF_ATTR_OFFSET", "56"}}); + + // Verification should succeed with correct ASIC DB values. + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + p4rtAclTableName; + EXPECT_EQ(VerifyTableState(db_key, attributes), ""); + + // Verification should fail if ACL table mismatch. + table.set("SAI_OBJECT_TYPE_ACL_TABLE:oid:0x7000000000606", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_ACL_STAGE", "SAI_ACL_STAGE_EGRESS"}}); + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + + // Verification should fail if ACL table is missing. + table.del("SAI_OBJECT_TYPE_ACL_TABLE:oid:0x7000000000606"); + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + table.set("SAI_OBJECT_TYPE_ACL_TABLE:oid:0x7000000000606", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_ACL_STAGE", "SAI_ACL_STAGE_INGRESS"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_SIZE", "123"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_DST_MAC", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_FIELD_TTL", "true"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_USER_DEFINED_FIELD_GROUP_MIN", "oid:0xfa1"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST", "1:SAI_ACL_ACTION_TYPE_COUNTER"}}); + + // Verification should fail if table group member mismatch. + table.set( + "SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER:oid:0xc000000000607", + std::vector{swss::FieldValueTuple{"SAI_ACL_TABLE_GROUP_MEMBER_ATTR_PRIORITY", "0"}}); + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + + // Verification should fail if table group member is missing. + table.del("SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER:oid:0xc000000000607"); + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + table.set("SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER:oid:0xc000000000607", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_GROUP_ID", "oid:0xb00000000058f"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_ID", "oid:0x7000000000606"}, + swss::FieldValueTuple{"SAI_ACL_TABLE_GROUP_MEMBER_ATTR_PRIORITY", "234"}}); + + // Verification should fail if udf group mismatch. + table.set("SAI_OBJECT_TYPE_UDF_GROUP:oid:0xfa1", + std::vector{swss::FieldValueTuple{"SAI_UDF_GROUP_ATTR_LENGTH", "1"}}); + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + + // Verification should fail if udf group is missing. + table.del("SAI_OBJECT_TYPE_UDF_GROUP:oid:0xfa1"); + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + table.set("SAI_OBJECT_TYPE_UDF_GROUP:oid:0xfa1", + std::vector{ + swss::FieldValueTuple{"SAI_UDF_GROUP_ATTR_TYPE", "SAI_UDF_GROUP_TYPE_GENERIC"}, + swss::FieldValueTuple{"SAI_UDF_GROUP_ATTR_LENGTH", "2"}}); + + // Verification should fail if udf mismatch. + table.set("SAI_OBJECT_TYPE_UDF:oid:0x1771", + std::vector{swss::FieldValueTuple{"SAI_UDF_ATTR_OFFSET", "1"}}); + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + + // Verification should fail if udf is missing. + table.del("SAI_OBJECT_TYPE_UDF:oid:0x1771"); + EXPECT_FALSE(VerifyTableState(db_key, attributes).empty()); + table.set("SAI_OBJECT_TYPE_UDF:oid:0x1771", + std::vector{swss::FieldValueTuple{"SAI_UDF_ATTR_GROUP_ID", "oid:0xfa1"}, + swss::FieldValueTuple{"SAI_UDF_ATTR_MATCH_ID", "oid:0x1389"}, + swss::FieldValueTuple{"SAI_UDF_ATTR_BASE", "SAI_UDF_BASE_L3"}, + swss::FieldValueTuple{"SAI_UDF_ATTR_OFFSET", "56"}}); +} + +TEST_F(AclManagerTest, AclRuleVerifyStateAsicDbTest) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto attributes = getDefaultRuleFieldValueTuples(); + const auto &acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\",\"priority\":15}"; + const auto &rule_tuple_key = std::string(kAclIngressTableName) + kTableKeyDelimiter + acl_rule_json_key; + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, SET_COMMAND, attributes})); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclCounterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + DrainRuleTuples(); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set( + "SAI_OBJECT_TYPE_ACL_ENTRY:oid:0x3e9", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_TABLE_ID", "oid:0x7000000000606"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_PRIORITY", "15"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_ADMIN_STATE", "true"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_FIELD_DST_IPV6", "fdf8:f53b:82e4::53&mask:fdf8:f53b:82e4::53"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE", "2048&mask:0xffff"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE", + "SAI_ACL_IP_TYPE_ANY&mask:0xffffffffffffffff"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_ACTION_SET_TC", "32"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_ACTION_SET_POLICER", "oid:0x7d1"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_ACTION_COUNTER", "oid:0xbb9"}}); + table.set("SAI_OBJECT_TYPE_ACL_COUNTER:oid:0xbb9", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_COUNTER_ATTR_TABLE_ID", "oid:0x7000000000606"}, + swss::FieldValueTuple{"SAI_ACL_COUNTER_ATTR_ENABLE_BYTE_COUNT", "true"}, + swss::FieldValueTuple{"SAI_ACL_COUNTER_ATTR_ENABLE_PACKET_COUNT", "true"}}); + table.set( + "SAI_OBJECT_TYPE_POLICER:oid:0x7d1", + std::vector{ + swss::FieldValueTuple{"SAI_POLICER_ATTR_MODE", "SAI_POLICER_MODE_TR_TCM"}, + swss::FieldValueTuple{"SAI_POLICER_ATTR_METER_TYPE", "SAI_METER_TYPE_BYTES"}, + swss::FieldValueTuple{"SAI_POLICER_ATTR_CBS", "80"}, swss::FieldValueTuple{"SAI_POLICER_ATTR_CIR", "80"}, + swss::FieldValueTuple{"SAI_POLICER_ATTR_PIR", "200"}, swss::FieldValueTuple{"SAI_POLICER_ATTR_PBS", "200"}, + swss::FieldValueTuple{"SAI_POLICER_ATTR_GREEN_PACKET_ACTION", "SAI_PACKET_ACTION_COPY"}}); + + // Verification should succeed with correct ASIC DB values. + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + rule_tuple_key; + EXPECT_EQ(VerifyRuleState(db_key, attributes), ""); + + // Verification should fail if ACL entry mismatch. + table.set("SAI_OBJECT_TYPE_ACL_ENTRY:oid:0x3e9", + std::vector{swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_PRIORITY", "20"}}); + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + + // Verification should fail if ACL entry is missing. + table.del("SAI_OBJECT_TYPE_ACL_ENTRY:oid:0x3e9"); + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + table.set( + "SAI_OBJECT_TYPE_ACL_ENTRY:oid:0x3e9", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_TABLE_ID", "oid:0x7000000000606"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_PRIORITY", "15"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_ADMIN_STATE", "true"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_FIELD_DST_IPV6", "fdf8:f53b:82e4::53&mask:fdf8:f53b:82e4::53"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE", "2048&mask:0xffff"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE", + "SAI_ACL_IP_TYPE_ANY&mask:0xffffffffffffffff"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_ACTION_SET_TC", "32"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_ACTION_SET_POLICER", "oid:0x7d1"}, + swss::FieldValueTuple{"SAI_ACL_ENTRY_ATTR_ACTION_COUNTER", "oid:0xbb9"}}); + + // Verification should fail if counter entry mismatch. + table.set("SAI_OBJECT_TYPE_ACL_COUNTER:oid:0xbb9", + std::vector{swss::FieldValueTuple{"SAI_ACL_COUNTER_ATTR_TABLE_ID", "oid:0x0"}}); + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + + // Verification should fail if counter entry is missing. + table.del("SAI_OBJECT_TYPE_ACL_COUNTER:oid:0xbb9"); + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + table.set("SAI_OBJECT_TYPE_ACL_COUNTER:oid:0xbb9", + std::vector{ + swss::FieldValueTuple{"SAI_ACL_COUNTER_ATTR_TABLE_ID", "oid:0x7000000000606"}, + swss::FieldValueTuple{"SAI_ACL_COUNTER_ATTR_ENABLE_BYTE_COUNT", "true"}, + swss::FieldValueTuple{"SAI_ACL_COUNTER_ATTR_ENABLE_PACKET_COUNT", "true"}}); + + // Verification should fail if meter entry mismatch. + table.set("SAI_OBJECT_TYPE_POLICER:oid:0x7d1", + std::vector{swss::FieldValueTuple{"SAI_POLICER_ATTR_CBS", "0"}}); + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + + // Verification should fail if meter entry is missing. + table.del("SAI_OBJECT_TYPE_POLICER:oid:0x7d1"); + EXPECT_FALSE(VerifyRuleState(db_key, attributes).empty()); + table.set( + "SAI_OBJECT_TYPE_POLICER:oid:0x7d1", + std::vector{ + swss::FieldValueTuple{"SAI_POLICER_ATTR_MODE", "SAI_POLICER_MODE_TR_TCM"}, + swss::FieldValueTuple{"SAI_POLICER_ATTR_METER_TYPE", "SAI_METER_TYPE_BYTES"}, + swss::FieldValueTuple{"SAI_POLICER_ATTR_CBS", "80"}, swss::FieldValueTuple{"SAI_POLICER_ATTR_CIR", "80"}, + swss::FieldValueTuple{"SAI_POLICER_ATTR_PIR", "200"}, swss::FieldValueTuple{"SAI_POLICER_ATTR_PBS", "200"}, + swss::FieldValueTuple{"SAI_POLICER_ATTR_GREEN_PACKET_ACTION", "SAI_PACKET_ACTION_COPY"}}); +} + } // namespace test } // namespace p4orch diff --git a/orchagent/p4orch/tests/fake_dbconnector.cpp b/orchagent/p4orch/tests/fake_dbconnector.cpp index 1709d9d9772b..89487fad61b2 100644 --- a/orchagent/p4orch/tests/fake_dbconnector.cpp +++ b/orchagent/p4orch/tests/fake_dbconnector.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include "dbconnector.h" @@ -10,6 +12,17 @@ static std::map dbNameIdMap = { {"APPL_DB", 0}, {"ASIC_DB", 1}, {"COUNTERS_DB", 2}, {"CONFIG_DB", 4}, {"FLEX_COUNTER_DB", 5}, {"STATE_DB", 6}, }; +using DbDataT = std::map>>; + +namespace fake_db_connector +{ + +DbDataT gDB; + +} // namespace fake_db_connector + +using namespace fake_db_connector; + RedisContext::RedisContext() { } @@ -43,4 +56,19 @@ int DBConnector::getDbId() const return m_dbId; } +void DBConnector::hset(const std::string &key, const std::string &field, const std::string &value) +{ + gDB[m_dbId][key][field] = value; +} + +std::vector DBConnector::keys(const std::string &key) +{ + std::vector list; + for (auto const &x : gDB[m_dbId]) + { + list.push_back(x.first); + } + return list; +} + } // namespace swss diff --git a/orchagent/p4orch/tests/gre_tunnel_manager_test.cpp b/orchagent/p4orch/tests/gre_tunnel_manager_test.cpp new file mode 100644 index 000000000000..ebd0b54ce42a --- /dev/null +++ b/orchagent/p4orch/tests/gre_tunnel_manager_test.cpp @@ -0,0 +1,902 @@ +#include "gre_tunnel_manager.h" + +#include +#include + +#include +#include +#include + +#include "ipaddress.h" +#include "json.hpp" +#include "mock_response_publisher.h" +#include "mock_sai_router_interface.h" +#include "mock_sai_serialize.h" +#include "mock_sai_tunnel.h" +#include "p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "p4orch_util.h" +#include "return_code.h" +#include "swssnet.h" +extern "C" +{ +#include "sai.h" +} + +using ::p4orch::kTableKeyDelimiter; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::Truly; + +extern sai_object_id_t gSwitchId; +extern sai_tunnel_api_t *sai_tunnel_api; +extern sai_router_interface_api_t *sai_router_intfs_api; +extern MockSaiTunnel *mock_sai_tunnel; + +namespace +{ +constexpr char *kRouterInterfaceId1 = "intf-eth-1/2/3"; +constexpr sai_object_id_t kRouterInterfaceOid1 = 1; +constexpr char *kGreTunnelP4AppDbId1 = "tunnel-1"; +constexpr char *kGreTunnelP4AppDbKey1 = R"({"match/tunnel_id":"tunnel-1"})"; +constexpr sai_object_id_t kGreTunnelOid1 = 0x11; +constexpr sai_object_id_t kOverlayRifOid1 = 0x101; + +// APP DB entries for Add request. +const P4GreTunnelAppDbEntry kP4GreTunnelAppDbEntry1{/*tunnel_id=*/"tunnel-1", + /*router_interface_id=*/"intf-eth-1/2/3", + /*encap_src_ip=*/swss::IpAddress("2607:f8b0:8096:3110::1"), + /*encap_dst_ip=*/swss::IpAddress("2607:f8b0:8096:311a::2"), + /*action_str=*/"mark_for_tunnel_encap"}; + +std::unordered_map CreateAttributeListForGreTunnelObject( + const P4GreTunnelAppDbEntry &app_entry, const sai_object_id_t &rif_oid) +{ + std::unordered_map tunnel_attrs; + sai_attribute_t tunnel_attr; + + tunnel_attr.id = SAI_TUNNEL_ATTR_TYPE; + tunnel_attr.value.s32 = SAI_TUNNEL_TYPE_IPINIP_GRE; + tunnel_attrs.insert({tunnel_attr.id, tunnel_attr.value}); + + tunnel_attr.id = SAI_TUNNEL_ATTR_PEER_MODE; + tunnel_attr.value.s32 = SAI_TUNNEL_PEER_MODE_P2P; + tunnel_attrs.insert({tunnel_attr.id, tunnel_attr.value}); + + tunnel_attr.id = SAI_TUNNEL_ATTR_OVERLAY_INTERFACE; + tunnel_attr.value.oid = kOverlayRifOid1; + tunnel_attrs.insert({tunnel_attr.id, tunnel_attr.value}); + + tunnel_attr.id = SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE; + tunnel_attr.value.oid = rif_oid; + tunnel_attrs.insert({tunnel_attr.id, tunnel_attr.value}); + + tunnel_attr.id = SAI_TUNNEL_ATTR_ENCAP_SRC_IP; + swss::copy(tunnel_attr.value.ipaddr, app_entry.encap_src_ip); + tunnel_attrs.insert({tunnel_attr.id, tunnel_attr.value}); + + tunnel_attr.id = SAI_TUNNEL_ATTR_ENCAP_DST_IP; + swss::copy(tunnel_attr.value.ipaddr, app_entry.encap_dst_ip); + tunnel_attrs.insert({tunnel_attr.id, tunnel_attr.value}); + + return tunnel_attrs; +} + +// Verifies whether the attribute list is the same as expected. +// Returns true if they match; otherwise, false. +bool MatchCreateGreTunnelArgAttrList(const sai_attribute_t *attr_list, + const std::unordered_map &expected_attr_list) +{ + if (attr_list == nullptr) + { + return false; + } + + // Sanity check for expected_attr_list. + const auto end = expected_attr_list.end(); + if (expected_attr_list.size() < 3 || expected_attr_list.find(SAI_TUNNEL_ATTR_TYPE) == end || + expected_attr_list.find(SAI_TUNNEL_ATTR_PEER_MODE) == end || + expected_attr_list.find(SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE) == end || + expected_attr_list.find(SAI_TUNNEL_ATTR_OVERLAY_INTERFACE) == end || + expected_attr_list.find(SAI_TUNNEL_ATTR_ENCAP_SRC_IP) == end || + expected_attr_list.find(SAI_TUNNEL_ATTR_ENCAP_DST_IP) == end) + { + return false; + } + + size_t valid_attrs_num = 0; + for (size_t i = 0; i < expected_attr_list.size(); ++i) + { + switch (attr_list[i].id) + { + case SAI_TUNNEL_ATTR_TYPE: { + if (attr_list[i].value.s32 != expected_attr_list.at(SAI_TUNNEL_ATTR_TYPE).s32) + { + return false; + } + valid_attrs_num++; + break; + } + case SAI_TUNNEL_ATTR_PEER_MODE: { + if (attr_list[i].value.s32 != expected_attr_list.at(SAI_TUNNEL_ATTR_PEER_MODE).s32) + { + return false; + } + valid_attrs_num++; + break; + } + case SAI_TUNNEL_ATTR_ENCAP_SRC_IP: { + if (attr_list[i].value.ipaddr.addr_family != + expected_attr_list.at(SAI_TUNNEL_ATTR_ENCAP_SRC_IP).ipaddr.addr_family || + (attr_list[i].value.ipaddr.addr_family == SAI_IP_ADDR_FAMILY_IPV4 && + attr_list[i].value.ipaddr.addr.ip4 != + expected_attr_list.at(SAI_TUNNEL_ATTR_ENCAP_SRC_IP).ipaddr.addr.ip4) || + (attr_list[i].value.ipaddr.addr_family == SAI_IP_ADDR_FAMILY_IPV6 && + memcmp(&attr_list[i].value.ipaddr.addr.ip6, + &expected_attr_list.at(SAI_TUNNEL_ATTR_ENCAP_SRC_IP).ipaddr.addr.ip6, sizeof(sai_ip6_t)) != 0)) + { + return false; + } + valid_attrs_num++; + break; + } + case SAI_TUNNEL_ATTR_ENCAP_DST_IP: { + if (attr_list[i].value.ipaddr.addr_family != + expected_attr_list.at(SAI_TUNNEL_ATTR_ENCAP_DST_IP).ipaddr.addr_family || + (attr_list[i].value.ipaddr.addr_family == SAI_IP_ADDR_FAMILY_IPV4 && + attr_list[i].value.ipaddr.addr.ip4 != + expected_attr_list.at(SAI_TUNNEL_ATTR_ENCAP_DST_IP).ipaddr.addr.ip4) || + (attr_list[i].value.ipaddr.addr_family == SAI_IP_ADDR_FAMILY_IPV6 && + memcmp(&attr_list[i].value.ipaddr.addr.ip6, + &expected_attr_list.at(SAI_TUNNEL_ATTR_ENCAP_DST_IP).ipaddr.addr.ip6, sizeof(sai_ip6_t)) != 0)) + { + return false; + } + valid_attrs_num++; + break; + } + case SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE: { + if (expected_attr_list.find(SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE) == end || + expected_attr_list.at(SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE).oid != attr_list[i].value.oid) + { + return false; + } + valid_attrs_num++; + break; + } + case SAI_TUNNEL_ATTR_OVERLAY_INTERFACE: { + if (expected_attr_list.find(SAI_TUNNEL_ATTR_OVERLAY_INTERFACE) == end || + expected_attr_list.at(SAI_TUNNEL_ATTR_OVERLAY_INTERFACE).oid != attr_list[i].value.oid) + { + return false; + } + valid_attrs_num++; + break; + } + default: + return false; + } + } + + if (expected_attr_list.size() != valid_attrs_num) + { + return false; + } + + return true; +} +} // namespace + +class GreTunnelManagerTest : public ::testing::Test +{ + protected: + GreTunnelManagerTest() : gre_tunnel_manager_(&p4_oid_mapper_, &publisher_) + { + } + + void SetUp() override + { + // Set up mock stuff for SAI tunnel API structure. + mock_sai_tunnel = &mock_sai_tunnel_; + sai_tunnel_api->create_tunnel = mock_create_tunnel; + sai_tunnel_api->remove_tunnel = mock_remove_tunnel; + // Set up mock stuff for SAI router interface API structure. + mock_sai_router_intf = &mock_sai_router_intf_; + sai_router_intfs_api->create_router_interface = mock_create_router_interface; + sai_router_intfs_api->remove_router_interface = mock_remove_router_interface; + + mock_sai_serialize = &mock_sai_serialize_; + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + gre_tunnel_manager_.enqueue(entry); + } + + void Drain() + { + gre_tunnel_manager_.drain(); + } + + std::string VerifyState(const std::string &key, const std::vector &tuple) + { + return gre_tunnel_manager_.verifyState(key, tuple); + } + + ReturnCode ProcessAddRequest(const P4GreTunnelAppDbEntry &app_db_entry) + { + return gre_tunnel_manager_.processAddRequest(app_db_entry); + } + + ReturnCode ProcessDeleteRequest(const std::string &tunnel_key) + { + return gre_tunnel_manager_.processDeleteRequest(tunnel_key); + } + + P4GreTunnelEntry *GetGreTunnelEntry(const std::string &tunnel_key) + { + return gre_tunnel_manager_.getGreTunnelEntry(tunnel_key); + } + + ReturnCodeOr DeserializeP4GreTunnelAppDbEntry( + const std::string &key, const std::vector &attributes) + { + return gre_tunnel_manager_.deserializeP4GreTunnelAppDbEntry(key, attributes); + } + + // Adds the gre tunnel entry -- kP4GreTunnelAppDbEntry1, via gre tunnel + // manager's ProcessAddRequest (). This function also takes care of all the + // dependencies of the gre tunnel entry. Returns a valid pointer to gre tunnel + // entry on success. + P4GreTunnelEntry *AddGreTunnelEntry1(); + + // Validates that a P4 App gre tunnel entry is correctly added in gre tunnel + // manager and centralized mapper. Returns true on success. + bool ValidateGreTunnelEntryAdd(const P4GreTunnelAppDbEntry &app_db_entry); + + // Return true if the specified the object has the expected number of + // reference. + bool ValidateRefCnt(sai_object_type_t object_type, const std::string &key, uint32_t expected_ref_count) + { + uint32_t ref_count; + if (!p4_oid_mapper_.getRefCount(object_type, key, &ref_count)) + return false; + return ref_count == expected_ref_count; + } + + StrictMock mock_sai_tunnel_; + StrictMock mock_sai_router_intf_; + StrictMock mock_sai_serialize_; + MockResponsePublisher publisher_; + P4OidMapper p4_oid_mapper_; + GreTunnelManager gre_tunnel_manager_; +}; + +P4GreTunnelEntry *GreTunnelManagerTest::AddGreTunnelEntry1() +{ + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + EXPECT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + + // Set up mock call. + EXPECT_CALL(mock_sai_router_intf_, create_router_interface(::testing::NotNull(), Eq(gSwitchId), Eq(2), _)) + .WillOnce(DoAll(SetArgPointee<0>(kOverlayRifOid1), Return(SAI_STATUS_SUCCESS))); + + EXPECT_CALL(mock_sai_tunnel_, create_tunnel(::testing::NotNull(), Eq(gSwitchId), Eq(6), + Truly(std::bind(MatchCreateGreTunnelArgAttrList, std::placeholders::_1, + CreateAttributeListForGreTunnelObject( + kP4GreTunnelAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(DoAll(SetArgPointee<0>(kGreTunnelOid1), Return(SAI_STATUS_SUCCESS))); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(kP4GreTunnelAppDbEntry1)); + + return GetGreTunnelEntry(gre_tunnel_key); +} + +bool GreTunnelManagerTest::ValidateGreTunnelEntryAdd(const P4GreTunnelAppDbEntry &app_db_entry) +{ + const auto *p4_gre_tunnel_entry = GetGreTunnelEntry(KeyGenerator::generateTunnelKey(app_db_entry.tunnel_id)); + if (p4_gre_tunnel_entry == nullptr || p4_gre_tunnel_entry->encap_src_ip != app_db_entry.encap_src_ip || + p4_gre_tunnel_entry->encap_dst_ip != app_db_entry.encap_dst_ip || + p4_gre_tunnel_entry->router_interface_id != app_db_entry.router_interface_id || + p4_gre_tunnel_entry->tunnel_id != app_db_entry.tunnel_id) + { + return false; + } + + return true; +} + +TEST_F(GreTunnelManagerTest, ProcessAddRequestShouldSucceedAddingNewGreTunnel) +{ + AddGreTunnelEntry1(); + EXPECT_TRUE(ValidateGreTunnelEntryAdd(kP4GreTunnelAppDbEntry1)); +} + +TEST_F(GreTunnelManagerTest, ProcessAddRequestShouldFailWhenGreTunnelExistInCentralMapper) +{ + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + ASSERT_EQ(gre_tunnel_key, "tunnel_id=tunnel-1"); + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_key, kGreTunnelOid1)); + // TODO: Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessAddRequest(kP4GreTunnelAppDbEntry1)); +} + +TEST_F(GreTunnelManagerTest, ProcessAddRequestShouldFailWhenDependingPortIsNotPresent) +{ + const P4GreTunnelAppDbEntry kAppDbEntry{/*tunnel_id=*/"tunnel-1", + /*router_interface_id=*/"intf-eth-1/2/3", + /*encap_src_ip=*/swss::IpAddress("2607:f8b0:8096:3110::1"), + /*encap_dst_ip=*/swss::IpAddress("2607:f8b0:8096:311a::2"), + /*action_str=*/"mark_for_tunnel_encap"}; + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kAppDbEntry.tunnel_id); + + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(kAppDbEntry)); + + EXPECT_EQ(GetGreTunnelEntry(gre_tunnel_key), nullptr); +} + +TEST_F(GreTunnelManagerTest, ProcessAddRequestShouldFailWhenRifSaiCallFails) +{ + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + EXPECT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + // Set up mock call. + EXPECT_CALL(mock_sai_router_intf_, create_router_interface(::testing::NotNull(), Eq(gSwitchId), Eq(2), _)) + .WillOnce(DoAll(SetArgPointee<0>(kOverlayRifOid1), Return(SAI_STATUS_FAILURE))); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddRequest(kP4GreTunnelAppDbEntry1)); + + // The add request failed for the gre tunnel entry. + EXPECT_EQ(GetGreTunnelEntry(gre_tunnel_key), nullptr); +} + +TEST_F(GreTunnelManagerTest, ProcessAddRequestShouldFailWhenTunnelSaiCallFails) +{ + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + EXPECT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + // Set up mock call. + EXPECT_CALL(mock_sai_router_intf_, create_router_interface(::testing::NotNull(), Eq(gSwitchId), Eq(2), _)) + .WillOnce(DoAll(SetArgPointee<0>(kOverlayRifOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_tunnel_, create_tunnel(::testing::NotNull(), Eq(gSwitchId), Eq(6), + Truly(std::bind(MatchCreateGreTunnelArgAttrList, std::placeholders::_1, + CreateAttributeListForGreTunnelObject( + kP4GreTunnelAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(Eq(kOverlayRifOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddRequest(kP4GreTunnelAppDbEntry1)); + + // The add request failed for the gre tunnel entry. + EXPECT_EQ(GetGreTunnelEntry(gre_tunnel_key), nullptr); +} + +TEST_F(GreTunnelManagerTest, ProcessAddRequestShouldRaiseCriticalWhenRecoverySaiCallFails) +{ + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + EXPECT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + // Set up mock call. + EXPECT_CALL(mock_sai_router_intf_, create_router_interface(::testing::NotNull(), Eq(gSwitchId), Eq(2), _)) + .WillOnce(DoAll(SetArgPointee<0>(kOverlayRifOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_tunnel_, create_tunnel(::testing::NotNull(), Eq(gSwitchId), Eq(6), + Truly(std::bind(MatchCreateGreTunnelArgAttrList, std::placeholders::_1, + CreateAttributeListForGreTunnelObject( + kP4GreTunnelAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(Eq(kOverlayRifOid1))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + // TODO: Expect critical state. + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddRequest(kP4GreTunnelAppDbEntry1)); + + // The add request failed for the gre tunnel entry. + EXPECT_EQ(GetGreTunnelEntry(gre_tunnel_key), nullptr); +} + +TEST_F(GreTunnelManagerTest, ProcessDeleteRequestShouldFailForNonExistingGreTunnel) +{ + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessDeleteRequest(gre_tunnel_key)); +} + +TEST_F(GreTunnelManagerTest, ProcessDeleteRequestShouldFailIfGreTunnelEntryIsAbsentInCentralMapper) +{ + auto *p4_tunnel_entry = AddGreTunnelEntry1(); + ASSERT_NE(p4_tunnel_entry, nullptr); + + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + + ASSERT_TRUE(p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_key)); + + // TODO: Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRequest(gre_tunnel_key)); + + // Validate the gre tunnel entry is not deleted in P4 gre tunnel manager. + p4_tunnel_entry = GetGreTunnelEntry(gre_tunnel_key); + ASSERT_NE(p4_tunnel_entry, nullptr); +} + +TEST_F(GreTunnelManagerTest, ProcessDeleteRequestShouldFailIfGreTunnelEntryIsStillReferenced) +{ + auto *p4_tunnel_entry = AddGreTunnelEntry1(); + ASSERT_NE(p4_tunnel_entry, nullptr); + + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + ASSERT_TRUE(p4_oid_mapper_.increaseRefCount(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_key)); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessDeleteRequest(gre_tunnel_key)); + + // Validate the gre tunnel entry is not deleted in either P4 gre tunnel + // manager or central mapper. + p4_tunnel_entry = GetGreTunnelEntry(gre_tunnel_key); + ASSERT_NE(p4_tunnel_entry, nullptr); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_key, 1)); +} + +TEST_F(GreTunnelManagerTest, ProcessDeleteRequestShouldFailIfTunnelSaiCallFails) +{ + auto *p4_tunnel_entry = AddGreTunnelEntry1(); + ASSERT_NE(p4_tunnel_entry, nullptr); + + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + + // Set up mock call. + EXPECT_CALL(mock_sai_tunnel_, remove_tunnel(Eq(p4_tunnel_entry->tunnel_oid))).WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteRequest(gre_tunnel_key)); + + // Validate the gre tunnel entry is not deleted in either P4 gre tunnel + // manager or central mapper. + p4_tunnel_entry = GetGreTunnelEntry(gre_tunnel_key); + ASSERT_NE(p4_tunnel_entry, nullptr); + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_key)); +} + +TEST_F(GreTunnelManagerTest, ProcessDeleteRequestShouldFailIfRifSaiCallFails) +{ + auto *p4_tunnel_entry = AddGreTunnelEntry1(); + ASSERT_NE(p4_tunnel_entry, nullptr); + + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + + // Set up mock call. + EXPECT_CALL(mock_sai_tunnel_, remove_tunnel(Eq(p4_tunnel_entry->tunnel_oid))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(Eq(kOverlayRifOid1))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_tunnel_, create_tunnel(::testing::NotNull(), Eq(gSwitchId), Eq(6), + Truly(std::bind(MatchCreateGreTunnelArgAttrList, std::placeholders::_1, + CreateAttributeListForGreTunnelObject( + kP4GreTunnelAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(DoAll(SetArgPointee<0>(kGreTunnelOid1), Return(SAI_STATUS_SUCCESS))); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteRequest(gre_tunnel_key)); + + // Validate the gre tunnel entry is not deleted in either P4 gre tunnel + // manager or central mapper. + p4_tunnel_entry = GetGreTunnelEntry(gre_tunnel_key); + ASSERT_NE(p4_tunnel_entry, nullptr); + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_key)); +} + +TEST_F(GreTunnelManagerTest, ProcessDeleteRequestShouldRaiseCriticalIfRecoverySaiCallFails) +{ + auto *p4_tunnel_entry = AddGreTunnelEntry1(); + ASSERT_NE(p4_tunnel_entry, nullptr); + + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + + // Set up mock call. + EXPECT_CALL(mock_sai_tunnel_, remove_tunnel(Eq(p4_tunnel_entry->tunnel_oid))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(Eq(kOverlayRifOid1))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_tunnel_, create_tunnel(::testing::NotNull(), Eq(gSwitchId), Eq(6), + Truly(std::bind(MatchCreateGreTunnelArgAttrList, std::placeholders::_1, + CreateAttributeListForGreTunnelObject( + kP4GreTunnelAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + // TODO: Expect critical state. + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteRequest(gre_tunnel_key)); + + // Validate the gre tunnel entry is not deleted in either P4 gre tunnel + // manager or central mapper. + p4_tunnel_entry = GetGreTunnelEntry(gre_tunnel_key); + ASSERT_NE(p4_tunnel_entry, nullptr); + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_key)); +} + +TEST_F(GreTunnelManagerTest, GetGreTunnelEntryShouldReturnNullPointerForNonexistingGreTunnel) +{ + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + EXPECT_EQ(GetGreTunnelEntry(gre_tunnel_key), nullptr); +} + +TEST_F(GreTunnelManagerTest, DeserializeP4GreTunnelAppDbEntryShouldReturnNullPointerForInvalidField) +{ + std::vector attributes = {swss::FieldValueTuple(p4orch::kAction, p4orch::kTunnelAction), + swss::FieldValueTuple("UNKNOWN_FIELD", "UNKOWN")}; + + EXPECT_FALSE(DeserializeP4GreTunnelAppDbEntry(kGreTunnelP4AppDbKey1, attributes).ok()); +} + +TEST_F(GreTunnelManagerTest, DeserializeP4GreTunnelAppDbEntryShouldReturnNullPointerForInvalidIP) +{ + std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, p4orch::kTunnelAction), + swss::FieldValueTuple(prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId1), + swss::FieldValueTuple(prependParamField(p4orch::kEncapSrcIp), "1.2.3.4"), + swss::FieldValueTuple(prependParamField(p4orch::kEncapDstIp), "2.3.4.5")}; + EXPECT_TRUE(DeserializeP4GreTunnelAppDbEntry(kGreTunnelP4AppDbKey1, attributes).ok()); + attributes = {swss::FieldValueTuple(p4orch::kAction, p4orch::kTunnelAction), + swss::FieldValueTuple(prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId1), + swss::FieldValueTuple(prependParamField(p4orch::kEncapSrcIp), "1:2:3:4"), + swss::FieldValueTuple(prependParamField(p4orch::kEncapDstIp), "1.2.3.5")}; + EXPECT_FALSE(DeserializeP4GreTunnelAppDbEntry(kGreTunnelP4AppDbKey1, attributes).ok()); + attributes = {swss::FieldValueTuple(p4orch::kAction, p4orch::kTunnelAction), + swss::FieldValueTuple(prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId1), + swss::FieldValueTuple(prependParamField(p4orch::kEncapSrcIp), "1.2.3.4"), + swss::FieldValueTuple(prependParamField(p4orch::kEncapDstIp), "1:2:3:5")}; + EXPECT_FALSE(DeserializeP4GreTunnelAppDbEntry(kGreTunnelP4AppDbKey1, attributes).ok()); +} + +TEST_F(GreTunnelManagerTest, DeserializeP4GreTunnelAppDbEntryShouldReturnNullPointerForInvalidKey) +{ + std::vector attributes = { + {p4orch::kAction, p4orch::kTunnelAction}, + {prependParamField(p4orch::kRouterInterfaceId), kP4GreTunnelAppDbEntry1.router_interface_id}, + {prependParamField(p4orch::kEncapSrcIp), kP4GreTunnelAppDbEntry1.encap_src_ip.to_string()}, + {prependParamField(p4orch::kEncapDstIp), kP4GreTunnelAppDbEntry1.encap_dst_ip.to_string()}}; + constexpr char *kInvalidAppDbKey = R"({"tunnel_id":1})"; + EXPECT_FALSE(DeserializeP4GreTunnelAppDbEntry(kInvalidAppDbKey, attributes).ok()); +} + +TEST_F(GreTunnelManagerTest, DrainDuplicateSetRequestShouldSucceed) +{ + auto *p4_tunnel_entry = AddGreTunnelEntry1(); + ASSERT_NE(p4_tunnel_entry, nullptr); + + nlohmann::json j; + j[prependMatchField(p4orch::kTunnelId)] = kP4GreTunnelAppDbEntry1.tunnel_id; + + std::vector fvs{ + {p4orch::kAction, p4orch::kTunnelAction}, + {prependParamField(p4orch::kRouterInterfaceId), kP4GreTunnelAppDbEntry1.router_interface_id}, + {prependParamField(p4orch::kEncapSrcIp), kP4GreTunnelAppDbEntry1.encap_src_ip.to_string()}, + {prependParamField(p4orch::kEncapDstIp), kP4GreTunnelAppDbEntry1.encap_dst_ip.to_string()}}; + + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_TUNNEL_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + SET_COMMAND, fvs); + + Enqueue(app_db_entry); + Drain(); + + // Expect that the update call will fail, so gre tunnel entry's fields stay + // the same. + EXPECT_TRUE(ValidateGreTunnelEntryAdd(kP4GreTunnelAppDbEntry1)); +} + +TEST_F(GreTunnelManagerTest, DrainDeleteRequestShouldSucceedForExistingGreTunnel) +{ + auto *p4_tunnel_entry = AddGreTunnelEntry1(); + ASSERT_NE(p4_tunnel_entry, nullptr); + EXPECT_EQ(p4_tunnel_entry->tunnel_oid, kGreTunnelOid1); + + nlohmann::json j; + j[prependMatchField(p4orch::kTunnelId)] = kP4GreTunnelAppDbEntry1.tunnel_id; + + std::vector fvs; + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_TUNNEL_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + DEL_COMMAND, fvs); + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(Eq(kOverlayRifOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_tunnel_, remove_tunnel(Eq(p4_tunnel_entry->tunnel_oid))).WillOnce(Return(SAI_STATUS_SUCCESS)); + + Enqueue(app_db_entry); + Drain(); + + // Validate the gre tunnel entry has been deleted in both P4 gre tunnel + // manager and centralized mapper. + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + p4_tunnel_entry = GetGreTunnelEntry(gre_tunnel_key); + EXPECT_EQ(p4_tunnel_entry, nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_key)); +} + +TEST_F(GreTunnelManagerTest, DrainValidAppEntryShouldSucceed) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kTunnelId)] = kGreTunnelP4AppDbId1; + EXPECT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + + std::vector fvs{ + {p4orch::kAction, p4orch::kTunnelAction}, + {prependParamField(p4orch::kRouterInterfaceId), kP4GreTunnelAppDbEntry1.router_interface_id}, + {prependParamField(p4orch::kEncapSrcIp), kP4GreTunnelAppDbEntry1.encap_src_ip.to_string()}, + {prependParamField(p4orch::kEncapDstIp), kP4GreTunnelAppDbEntry1.encap_dst_ip.to_string()}}; + + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_TUNNEL_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + SET_COMMAND, fvs); + + Enqueue(app_db_entry); + EXPECT_CALL(mock_sai_router_intf_, create_router_interface(::testing::NotNull(), Eq(gSwitchId), Eq(2), _)) + .WillOnce(DoAll(SetArgPointee<0>(kOverlayRifOid1), Return(SAI_STATUS_SUCCESS))); + + EXPECT_CALL(mock_sai_tunnel_, create_tunnel(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kGreTunnelOid1), Return(SAI_STATUS_SUCCESS))); + + Drain(); + + EXPECT_TRUE(ValidateGreTunnelEntryAdd(kP4GreTunnelAppDbEntry1)); +} + +TEST_F(GreTunnelManagerTest, DrainInvalidAppEntryShouldFail) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kTunnelId)] = kGreTunnelP4AppDbId1; + j[p4orch::kTunnelId] = 1000; + + std::vector fvs{ + {p4orch::kAction, p4orch::kTunnelAction}, + {prependParamField(p4orch::kRouterInterfaceId), kP4GreTunnelAppDbEntry1.router_interface_id}, + {prependParamField(p4orch::kEncapSrcIp), "1"}, + {prependParamField(p4orch::kEncapDstIp), kP4GreTunnelAppDbEntry1.encap_dst_ip.to_string()}}; + + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_TUNNEL_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + SET_COMMAND, fvs); + + Enqueue(app_db_entry); + + Drain(); + EXPECT_EQ(GetGreTunnelEntry(kGreTunnelP4AppDbKey1), nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_TUNNEL, kGreTunnelP4AppDbKey1)); + + // Invalid action_str + fvs = {{p4orch::kAction, "set_nexthop"}, + {prependParamField(p4orch::kRouterInterfaceId), kP4GreTunnelAppDbEntry1.router_interface_id}, + {prependParamField(p4orch::kEncapSrcIp), kP4GreTunnelAppDbEntry1.encap_src_ip.to_string()}, + {prependParamField(p4orch::kEncapDstIp), kP4GreTunnelAppDbEntry1.encap_dst_ip.to_string()}}; + + app_db_entry = {std::string(APP_P4RT_TUNNEL_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + + Drain(); + EXPECT_EQ(GetGreTunnelEntry(kGreTunnelP4AppDbKey1), nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_TUNNEL, kGreTunnelP4AppDbKey1)); + + // Miss action + fvs = {{prependParamField(p4orch::kRouterInterfaceId), kP4GreTunnelAppDbEntry1.router_interface_id}, + {prependParamField(p4orch::kEncapSrcIp), kP4GreTunnelAppDbEntry1.encap_src_ip.to_string()}, + {prependParamField(p4orch::kEncapDstIp), kP4GreTunnelAppDbEntry1.encap_dst_ip.to_string()}}; + + app_db_entry = {std::string(APP_P4RT_TUNNEL_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + + Drain(); + EXPECT_EQ(GetGreTunnelEntry(kGreTunnelP4AppDbKey1), nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_TUNNEL, kGreTunnelP4AppDbKey1)); + + // Miss router_interface_id + fvs = {{p4orch::kAction, p4orch::kTunnelAction}, + {prependParamField(p4orch::kEncapSrcIp), kP4GreTunnelAppDbEntry1.encap_src_ip.to_string()}, + {prependParamField(p4orch::kEncapDstIp), kP4GreTunnelAppDbEntry1.encap_dst_ip.to_string()}}; + + app_db_entry = {std::string(APP_P4RT_TUNNEL_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + + Drain(); + EXPECT_EQ(GetGreTunnelEntry(kGreTunnelP4AppDbKey1), nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_TUNNEL, kGreTunnelP4AppDbKey1)); + + // Miss encap_src_ip + fvs = {{p4orch::kAction, p4orch::kTunnelAction}, + {prependParamField(p4orch::kRouterInterfaceId), kP4GreTunnelAppDbEntry1.router_interface_id}, + {prependParamField(p4orch::kEncapDstIp), kP4GreTunnelAppDbEntry1.encap_dst_ip.to_string()}}; + + app_db_entry = {std::string(APP_P4RT_TUNNEL_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + + Drain(); + EXPECT_EQ(GetGreTunnelEntry(kGreTunnelP4AppDbKey1), nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_TUNNEL, kGreTunnelP4AppDbKey1)); + + // Miss encap_dst_ip + fvs = {{p4orch::kAction, p4orch::kTunnelAction}, + {prependParamField(p4orch::kRouterInterfaceId), kP4GreTunnelAppDbEntry1.router_interface_id}, + {prependParamField(p4orch::kEncapSrcIp), kP4GreTunnelAppDbEntry1.encap_src_ip.to_string()}}; + + app_db_entry = {std::string(APP_P4RT_TUNNEL_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + + Drain(); + EXPECT_EQ(GetGreTunnelEntry(kGreTunnelP4AppDbKey1), nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_TUNNEL, kGreTunnelP4AppDbKey1)); +} + +TEST_F(GreTunnelManagerTest, VerifyStateTest) +{ + auto *p4_tunnel_entry = AddGreTunnelEntry1(); + ASSERT_NE(p4_tunnel_entry, nullptr); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_TUNNEL:oid:0x11", + std::vector{ + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_TYPE", "SAI_TUNNEL_TYPE_IPINIP_GRE"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_PEER_MODE", "SAI_TUNNEL_PEER_MODE_P2P"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_ENCAP_SRC_IP", "2607:f8b0:8096:3110::1"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_ENCAP_DST_IP", "2607:f8b0:8096:311a::2"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE", "oid:0x1"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_OVERLAY_INTERFACE", "oid:0x101"}}); + + // Overlay router interface + table.set("SAI_OBJECT_TYPE_ROUTER_INTERFACE:oid:0x101", + std::vector{ + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID", "oid:0x0"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_TYPE", "SAI_ROUTER_INTERFACE_TYPE_LOOPBACK"}}); + + // Underlay router interface + table.set("SAI_OBJECT_TYPE_ROUTER_INTERFACE:oid:0x1", + std::vector{ + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID", "oid:0x0"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS", "00:01:02:03:04:05"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_TYPE", "SAI_ROUTER_INTERFACE_TYPE_PORT"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_PORT_ID", "oid:0x1234"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_MTU", "9100"}}); + + nlohmann::json j; + j[prependMatchField(p4orch::kTunnelId)] = kGreTunnelP4AppDbId1; + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_TUNNEL_TABLE_NAME + + kTableKeyDelimiter + j.dump(); + std::vector attributes; + + // Verification should succeed with vaild key and value. + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kTunnelAction}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kRouterInterfaceId), + kP4GreTunnelAppDbEntry1.router_interface_id}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kEncapSrcIp), + kP4GreTunnelAppDbEntry1.encap_src_ip.to_string()}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kEncapDstIp), + kP4GreTunnelAppDbEntry1.encap_dst_ip.to_string()}); + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Invalid key should fail verification. + EXPECT_FALSE(VerifyState("invalid", attributes).empty()); + EXPECT_FALSE(VerifyState("invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":FIXED_TUNNEL_TABLE:invalid", attributes).empty()); + + // Verification should fail if entry does not exist. + j[prependMatchField(p4orch::kTunnelId)] = "invalid"; + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_TUNNEL_TABLE_NAME + + kTableKeyDelimiter + j.dump(), + attributes) + .empty()); + + // Verification should fail if router interface name mismatches. + auto saved_router_interface_id = p4_tunnel_entry->router_interface_id; + p4_tunnel_entry->router_interface_id = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_tunnel_entry->router_interface_id = saved_router_interface_id; + + // Verification should fail if tunnel key mismatches. + auto saved_tunnel_key = p4_tunnel_entry->tunnel_key; + p4_tunnel_entry->tunnel_key = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_tunnel_entry->tunnel_key = saved_tunnel_key; + + // Verification should fail if IP mismatches. + auto saved_SRC_IP = p4_tunnel_entry->encap_src_ip; + p4_tunnel_entry->encap_src_ip = swss::IpAddress("1.1.1.1"); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_tunnel_entry->encap_src_ip = saved_SRC_IP; + + // Verification should fail if IP mask mismatches. + auto saved_DST_IP = p4_tunnel_entry->encap_dst_ip; + p4_tunnel_entry->encap_dst_ip = swss::IpAddress("2.2.2.2"); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_tunnel_entry->encap_dst_ip = saved_DST_IP; + + // Verification should fail if tunnel_id mismatches. + auto saved_tunnel_id = p4_tunnel_entry->tunnel_id; + p4_tunnel_entry->tunnel_id = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_tunnel_entry->tunnel_id = saved_tunnel_id; + + // Verification should fail if OID mapper mismatches. + const auto gre_tunnel_key = KeyGenerator::generateTunnelKey(kP4GreTunnelAppDbEntry1.tunnel_id); + p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_key); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_TUNNEL, gre_tunnel_key, kGreTunnelOid1); +} + +TEST_F(GreTunnelManagerTest, VerifyStateAsicDbTest) +{ + auto *p4_tunnel_entry = AddGreTunnelEntry1(); + ASSERT_NE(p4_tunnel_entry, nullptr); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_TUNNEL:oid:0x11", + std::vector{ + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_TYPE", "SAI_TUNNEL_TYPE_IPINIP_GRE"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_PEER_MODE", "SAI_TUNNEL_PEER_MODE_P2P"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_ENCAP_SRC_IP", "2607:f8b0:8096:3110::1"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_ENCAP_DST_IP", "2607:f8b0:8096:311a::2"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE", "oid:0x1"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_OVERLAY_INTERFACE", "oid:0x101"}}); + + // Overlay router interface + table.set("SAI_OBJECT_TYPE_ROUTER_INTERFACE:oid:0x101", + std::vector{ + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID", "oid:0x0"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_TYPE", "SAI_ROUTER_INTERFACE_TYPE_LOOPBACK"}}); + + // Underlay router interface + table.set("SAI_OBJECT_TYPE_ROUTER_INTERFACE:oid:0x1", + std::vector{ + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID", "oid:0x0"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS", "00:01:02:03:04:05"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_TYPE", "SAI_ROUTER_INTERFACE_TYPE_PORT"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_PORT_ID", "oid:0x1234"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_MTU", "9100"}}); + + nlohmann::json j; + j[prependMatchField(p4orch::kTunnelId)] = kGreTunnelP4AppDbId1; + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_TUNNEL_TABLE_NAME + + kTableKeyDelimiter + j.dump(); + std::vector attributes; + + // Verification should succeed with vaild key and value. + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kTunnelAction}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kRouterInterfaceId), + kP4GreTunnelAppDbEntry1.router_interface_id}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kEncapSrcIp), + kP4GreTunnelAppDbEntry1.encap_src_ip.to_string()}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kEncapDstIp), + kP4GreTunnelAppDbEntry1.encap_dst_ip.to_string()}); + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Verification should fail if ASIC DB values mismatch. + table.set("SAI_OBJECT_TYPE_TUNNEL:oid:0x11", std::vector{swss::FieldValueTuple{ + "SAI_TUNNEL_ATTR_ENCAP_SRC_IP", "2607:f8b0:8096:3110::3"}}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + // Verification should fail if ASIC DB table is missing. + table.del("SAI_OBJECT_TYPE_TUNNEL:oid:0x11"); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + table.set("SAI_OBJECT_TYPE_TUNNEL:oid:0x11", + std::vector{ + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_TYPE", "SAI_TUNNEL_TYPE_IPINIP_GRE"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_PEER_MODE", "SAI_TUNNEL_PEER_MODE_P2P"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_ENCAP_SRC_IP", "2607:f8b0:8096:3110::1"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_ENCAP_DST_IP", "2607:f8b0:8096:311a::2"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE", "oid:0x1"}, + swss::FieldValueTuple{"SAI_TUNNEL_ATTR_OVERLAY_INTERFACE", "oid:0x101"}}); + + // Verification should fail if SAI attr cannot be constructed. + p4_tunnel_entry->encap_src_ip = swss::IpAddress("1.2.3.4"); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_tunnel_entry->encap_src_ip = swss::IpAddress("2607:f8b0:8096:3110::1"); +} \ No newline at end of file diff --git a/orchagent/p4orch/tests/l3_admit_manager_test.cpp b/orchagent/p4orch/tests/l3_admit_manager_test.cpp new file mode 100644 index 000000000000..6d0d67dd0e63 --- /dev/null +++ b/orchagent/p4orch/tests/l3_admit_manager_test.cpp @@ -0,0 +1,653 @@ +#include "l3_admit_manager.h" + +#include +#include + +#include +#include +#include + +#include "json.hpp" +#include "mock_response_publisher.h" +#include "mock_sai_my_mac.h" +#include "p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "p4orch_util.h" +#include "return_code.h" +extern "C" +{ +#include "sai.h" +} + +using ::p4orch::kTableKeyDelimiter; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::Truly; + +extern sai_object_id_t gSwitchId; +extern sai_my_mac_api_t *sai_my_mac_api; +extern MockSaiMyMac *mock_sai_my_mac; + +namespace +{ +constexpr char *kPortName1 = "Ethernet1"; +constexpr sai_object_id_t kPortOid1 = 0x112233; +constexpr uint32_t kMtu1 = 1500; + +constexpr char *kPortName2 = "Ethernet2"; +constexpr sai_object_id_t kPortOid2 = 0x1fed3; +constexpr uint32_t kMtu2 = 4500; + +constexpr char *kL3AdmitP4AppDbKey1 = R"({"match/dst_mac":"00:02:03:04:00:00&ff:ff:ff:ff:00:00","priority":2030})"; +constexpr sai_object_id_t kL3AdmitOid1 = 0x1; +constexpr sai_object_id_t kL3AdmitOid2 = 0x2; + +// APP DB entries for Add request. +const P4L3AdmitAppDbEntry kP4L3AdmitAppDbEntry1{/*port_name=*/"", + /*mac_address_data=*/swss::MacAddress("00:02:03:04:00:00"), + /*mac_address_mask=*/swss::MacAddress("ff:ff:ff:ff:00:00"), + /*priority=*/2030}; + +const P4L3AdmitAppDbEntry kP4L3AdmitAppDbEntry2{/*port_name=*/kPortName1, + /*mac_address_data=*/swss::MacAddress("00:02:03:04:05:00"), + /*mac_address_mask=*/swss::MacAddress("ff:ff:ff:ff:ff:00"), + /*priority=*/2030}; + +std::unordered_map CreateAttributeListForL3AdmitObject( + const P4L3AdmitAppDbEntry &app_entry, const sai_object_id_t &port_oid) +{ + std::unordered_map my_mac_attrs; + sai_attribute_t my_mac_attr; + + my_mac_attr.id = SAI_MY_MAC_ATTR_PRIORITY; + my_mac_attr.value.u32 = app_entry.priority; + my_mac_attrs.insert({my_mac_attr.id, my_mac_attr.value}); + + my_mac_attr.id = SAI_MY_MAC_ATTR_MAC_ADDRESS; + memcpy(my_mac_attr.value.mac, app_entry.mac_address_data.getMac(), sizeof(sai_mac_t)); + my_mac_attrs.insert({my_mac_attr.id, my_mac_attr.value}); + + my_mac_attr.id = SAI_MY_MAC_ATTR_MAC_ADDRESS_MASK; + memcpy(my_mac_attr.value.mac, app_entry.mac_address_mask.getMac(), sizeof(sai_mac_t)); + my_mac_attrs.insert({my_mac_attr.id, my_mac_attr.value}); + + if (port_oid != SAI_NULL_OBJECT_ID) + { + my_mac_attr.id = SAI_MY_MAC_ATTR_PORT_ID; + my_mac_attr.value.oid = port_oid; + my_mac_attrs.insert({my_mac_attr.id, my_mac_attr.value}); + } + + return my_mac_attrs; +} + +// Verifies whether the attribute list is the same as expected. +// Returns true if they match; otherwise, false. +bool MatchCreateL3AdmitArgAttrList(const sai_attribute_t *attr_list, + const std::unordered_map &expected_attr_list) +{ + if (attr_list == nullptr) + { + return false; + } + + // Sanity check for expected_attr_list. + const auto end = expected_attr_list.end(); + if (expected_attr_list.size() < 3 || expected_attr_list.find(SAI_MY_MAC_ATTR_PRIORITY) == end || + expected_attr_list.find(SAI_MY_MAC_ATTR_MAC_ADDRESS) == end || + expected_attr_list.find(SAI_MY_MAC_ATTR_MAC_ADDRESS_MASK) == end) + { + return false; + } + + size_t valid_attrs_num = 0; + for (size_t i = 0; i < expected_attr_list.size(); ++i) + { + switch (attr_list[i].id) + { + case SAI_MY_MAC_ATTR_PRIORITY: { + if (attr_list[i].value.u32 != expected_attr_list.at(SAI_MY_MAC_ATTR_PRIORITY).u32) + { + return false; + } + valid_attrs_num++; + break; + } + case SAI_MY_MAC_ATTR_MAC_ADDRESS: { + auto macaddr = swss::MacAddress(attr_list[i].value.mac); + auto expected_macaddr = swss::MacAddress(expected_attr_list.at(SAI_MY_MAC_ATTR_MAC_ADDRESS).mac); + if (macaddr != expected_macaddr) + { + return false; + } + valid_attrs_num++; + break; + } + case SAI_MY_MAC_ATTR_MAC_ADDRESS_MASK: { + auto macaddr = swss::MacAddress(attr_list[i].value.mac); + auto expected_macaddr = swss::MacAddress(expected_attr_list.at(SAI_MY_MAC_ATTR_MAC_ADDRESS_MASK).mac); + if (macaddr != expected_macaddr) + { + return false; + } + valid_attrs_num++; + break; + } + case SAI_MY_MAC_ATTR_PORT_ID: { + if (expected_attr_list.find(SAI_MY_MAC_ATTR_PORT_ID) == end || + expected_attr_list.at(SAI_MY_MAC_ATTR_PORT_ID).oid != attr_list[i].value.oid) + { + return false; + } + valid_attrs_num++; + break; + } + default: + return false; + } + } + + if (expected_attr_list.size() != valid_attrs_num) + { + return false; + } + + return true; +} +} // namespace + +class L3AdmitManagerTest : public ::testing::Test +{ + protected: + L3AdmitManagerTest() : l3_admit_manager_(&p4_oid_mapper_, &publisher_) + { + } + + void SetUp() override + { + // Set up mock stuff for SAI l3 admit API structure. + mock_sai_my_mac = &mock_sai_my_mac_; + sai_my_mac_api->create_my_mac = mock_create_my_mac; + sai_my_mac_api->remove_my_mac = mock_remove_my_mac; + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + l3_admit_manager_.enqueue(entry); + } + + void Drain() + { + l3_admit_manager_.drain(); + } + + std::string VerifyState(const std::string &key, const std::vector &tuple) + { + return l3_admit_manager_.verifyState(key, tuple); + } + + ReturnCode ProcessAddRequest(const P4L3AdmitAppDbEntry &app_db_entry, const std::string &l3_admit_key) + { + return l3_admit_manager_.processAddRequest(app_db_entry, l3_admit_key); + } + + ReturnCode ProcessDeleteRequest(const std::string &my_mac_key) + { + return l3_admit_manager_.processDeleteRequest(my_mac_key); + } + + P4L3AdmitEntry *GetL3AdmitEntry(const std::string &my_mac_key) + { + return l3_admit_manager_.getL3AdmitEntry(my_mac_key); + } + + ReturnCodeOr DeserializeP4L3AdmitAppDbEntry( + const std::string &key, const std::vector &attributes) + { + return l3_admit_manager_.deserializeP4L3AdmitAppDbEntry(key, attributes); + } + + // Adds the l3 admit entry -- kP4L3AdmitAppDbEntry1, via l3 admit manager's + // ProcessAddRequest (). This function also takes care of all the dependencies + // of the l3 admit entry. + // Returns a valid pointer to l3 admit entry on success. + P4L3AdmitEntry *AddL3AdmitEntry1(); + + // Validates that a P4 App l3 admit entry is correctly added in l3 admit + // manager and centralized mapper. Returns true on success. + bool ValidateL3AdmitEntryAdd(const P4L3AdmitAppDbEntry &app_db_entry); + + // Return true if the specified the object has the expected number of + // reference. + bool ValidateRefCnt(sai_object_type_t object_type, const std::string &key, uint32_t expected_ref_count) + { + uint32_t ref_count; + if (!p4_oid_mapper_.getRefCount(object_type, key, &ref_count)) + return false; + return ref_count == expected_ref_count; + } + + StrictMock mock_sai_my_mac_; + MockResponsePublisher publisher_; + P4OidMapper p4_oid_mapper_; + L3AdmitManager l3_admit_manager_; +}; + +P4L3AdmitEntry *L3AdmitManagerTest::AddL3AdmitEntry1() +{ + const auto l3admit_key = + KeyGenerator::generateL3AdmitKey(kP4L3AdmitAppDbEntry1.mac_address_data, kP4L3AdmitAppDbEntry1.mac_address_mask, + kP4L3AdmitAppDbEntry1.port_name, kP4L3AdmitAppDbEntry1.priority); + + // Set up mock call. + EXPECT_CALL( + mock_sai_my_mac_, + create_my_mac(::testing::NotNull(), Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchCreateL3AdmitArgAttrList, std::placeholders::_1, + CreateAttributeListForL3AdmitObject(kP4L3AdmitAppDbEntry1, SAI_NULL_OBJECT_ID))))) + .WillOnce(DoAll(SetArgPointee<0>(kL3AdmitOid1), Return(SAI_STATUS_SUCCESS))); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(kP4L3AdmitAppDbEntry1, l3admit_key)); + + return GetL3AdmitEntry(l3admit_key); +} + +bool L3AdmitManagerTest::ValidateL3AdmitEntryAdd(const P4L3AdmitAppDbEntry &app_db_entry) +{ + const auto *p4_l3_admit_entry = GetL3AdmitEntry(KeyGenerator::generateL3AdmitKey( + app_db_entry.mac_address_data, app_db_entry.mac_address_mask, app_db_entry.port_name, app_db_entry.priority)); + if (p4_l3_admit_entry == nullptr || p4_l3_admit_entry->mac_address_data != app_db_entry.mac_address_data || + p4_l3_admit_entry->mac_address_mask != app_db_entry.mac_address_mask || + p4_l3_admit_entry->port_name != app_db_entry.port_name || p4_l3_admit_entry->priority != app_db_entry.priority) + { + return false; + } + + return true; +} + +TEST_F(L3AdmitManagerTest, ProcessAddRequestShouldSucceedAddingNewL3Admit) +{ + AddL3AdmitEntry1(); + EXPECT_TRUE(ValidateL3AdmitEntryAdd(kP4L3AdmitAppDbEntry1)); +} + +TEST_F(L3AdmitManagerTest, ProcessAddRequestShouldFailWhenL3AdmitExistInCentralMapper) +{ + const auto l3admit_key = + KeyGenerator::generateL3AdmitKey(kP4L3AdmitAppDbEntry1.mac_address_data, kP4L3AdmitAppDbEntry1.mac_address_mask, + kP4L3AdmitAppDbEntry1.port_name, kP4L3AdmitAppDbEntry1.priority); + ASSERT_EQ(l3admit_key, "match/dst_mac=00:02:03:04:00:00&ff:ff:ff:ff:00:00:priority=2030"); + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_MY_MAC, l3admit_key, kL3AdmitOid1)); + // TODO: Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessAddRequest(kP4L3AdmitAppDbEntry1, l3admit_key)); +} + +TEST_F(L3AdmitManagerTest, ProcessAddRequestShouldFailWhenDependingPortIsNotPresent) +{ + const P4L3AdmitAppDbEntry kAppDbEntry{/*port_name=*/"Ethernet100", + /*mac_address_data=*/swss::MacAddress("00:02:03:04:00:00"), + /*mac_address_mask=*/swss::MacAddress("ff:ff:ff:ff:00:00"), + /*priority=*/2030}; + const auto l3admit_key = KeyGenerator::generateL3AdmitKey( + kAppDbEntry.mac_address_data, kAppDbEntry.mac_address_mask, kAppDbEntry.port_name, kAppDbEntry.priority); + + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(kAppDbEntry, l3admit_key)); + + EXPECT_EQ(GetL3AdmitEntry(l3admit_key), nullptr); +} + +TEST_F(L3AdmitManagerTest, ProcessAddRequestShouldFailWhenSaiCallFails) +{ + const auto l3admit_key = + KeyGenerator::generateL3AdmitKey(kP4L3AdmitAppDbEntry1.mac_address_data, kP4L3AdmitAppDbEntry1.mac_address_mask, + kP4L3AdmitAppDbEntry1.port_name, kP4L3AdmitAppDbEntry1.priority); + // Set up mock call. + EXPECT_CALL( + mock_sai_my_mac_, + create_my_mac(::testing::NotNull(), Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchCreateL3AdmitArgAttrList, std::placeholders::_1, + CreateAttributeListForL3AdmitObject(kP4L3AdmitAppDbEntry1, SAI_NULL_OBJECT_ID))))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddRequest(kP4L3AdmitAppDbEntry1, l3admit_key)); + + // The add request failed for the l3 admit entry. + EXPECT_EQ(GetL3AdmitEntry(l3admit_key), nullptr); +} + +TEST_F(L3AdmitManagerTest, ProcessDeleteRequestShouldFailForNonExistingL3Admit) +{ + const auto l3admit_key = + KeyGenerator::generateL3AdmitKey(kP4L3AdmitAppDbEntry1.mac_address_data, kP4L3AdmitAppDbEntry1.mac_address_mask, + kP4L3AdmitAppDbEntry1.port_name, kP4L3AdmitAppDbEntry1.priority); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessDeleteRequest(l3admit_key)); +} + +TEST_F(L3AdmitManagerTest, ProcessDeleteRequestShouldFailIfL3AdmitEntryIsAbsentInCentralMapper) +{ + auto *p4_my_mac_entry = AddL3AdmitEntry1(); + ASSERT_NE(p4_my_mac_entry, nullptr); + + const auto l3admit_key = + KeyGenerator::generateL3AdmitKey(kP4L3AdmitAppDbEntry1.mac_address_data, kP4L3AdmitAppDbEntry1.mac_address_mask, + kP4L3AdmitAppDbEntry1.port_name, kP4L3AdmitAppDbEntry1.priority); + + ASSERT_TRUE(p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_MY_MAC, l3admit_key)); + + // TODO: Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRequest(l3admit_key)); + + // Validate the l3 admit entry is not deleted in P4 l3 admit manager. + p4_my_mac_entry = GetL3AdmitEntry(l3admit_key); + ASSERT_NE(p4_my_mac_entry, nullptr); +} + +TEST_F(L3AdmitManagerTest, ProcessDeleteRequestShouldFailIfL3AdmitEntryIsStillReferenced) +{ + auto *p4_my_mac_entry = AddL3AdmitEntry1(); + ASSERT_NE(p4_my_mac_entry, nullptr); + + const auto l3admit_key = + KeyGenerator::generateL3AdmitKey(kP4L3AdmitAppDbEntry1.mac_address_data, kP4L3AdmitAppDbEntry1.mac_address_mask, + kP4L3AdmitAppDbEntry1.port_name, kP4L3AdmitAppDbEntry1.priority); + ASSERT_TRUE(p4_oid_mapper_.increaseRefCount(SAI_OBJECT_TYPE_MY_MAC, l3admit_key)); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessDeleteRequest(l3admit_key)); + + // Validate the l3 admit entry is not deleted in either P4 l3 admit manager + // or central mapper. + p4_my_mac_entry = GetL3AdmitEntry(l3admit_key); + ASSERT_NE(p4_my_mac_entry, nullptr); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_MY_MAC, l3admit_key, 1)); +} + +TEST_F(L3AdmitManagerTest, ProcessDeleteRequestShouldFailIfSaiCallFails) +{ + auto *p4_my_mac_entry = AddL3AdmitEntry1(); + ASSERT_NE(p4_my_mac_entry, nullptr); + + const auto l3admit_key = + KeyGenerator::generateL3AdmitKey(kP4L3AdmitAppDbEntry1.mac_address_data, kP4L3AdmitAppDbEntry1.mac_address_mask, + kP4L3AdmitAppDbEntry1.port_name, kP4L3AdmitAppDbEntry1.priority); + + // Set up mock call. + EXPECT_CALL(mock_sai_my_mac_, remove_my_mac(Eq(p4_my_mac_entry->l3_admit_oid))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteRequest(l3admit_key)); + + // Validate the l3 admit entry is not deleted in either P4 l3 admit manager + // or central mapper. + p4_my_mac_entry = GetL3AdmitEntry(l3admit_key); + ASSERT_NE(p4_my_mac_entry, nullptr); + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_MY_MAC, l3admit_key)); +} + +TEST_F(L3AdmitManagerTest, GetL3AdmitEntryShouldReturnNullPointerForNonexistingL3Admit) +{ + const auto l3admit_key = + KeyGenerator::generateL3AdmitKey(kP4L3AdmitAppDbEntry1.mac_address_data, kP4L3AdmitAppDbEntry1.mac_address_mask, + kP4L3AdmitAppDbEntry1.port_name, kP4L3AdmitAppDbEntry1.priority); + EXPECT_EQ(GetL3AdmitEntry(l3admit_key), nullptr); +} + +TEST_F(L3AdmitManagerTest, DeserializeP4L3AdmitAppDbEntryShouldReturnNullPointerForInvalidAction) +{ + std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_nexthop")}; // Invalid action. + + EXPECT_FALSE(DeserializeP4L3AdmitAppDbEntry(kL3AdmitP4AppDbKey1, attributes).ok()); +} + +TEST_F(L3AdmitManagerTest, DeserializeP4L3AdmitAppDbEntryShouldReturnNullPointerForInvalidField) +{ + std::vector attributes = {swss::FieldValueTuple(p4orch::kAction, p4orch::kL3AdmitAction), + swss::FieldValueTuple("UNKNOWN_FIELD", "UNKOWN")}; + + EXPECT_FALSE(DeserializeP4L3AdmitAppDbEntry(kL3AdmitP4AppDbKey1, attributes).ok()); +} + +TEST_F(L3AdmitManagerTest, DeserializeP4L3AdmitAppDbEntryShouldReturnNullPointerForInvalidMac) +{ + std::vector attributes = {swss::FieldValueTuple(p4orch::kAction, p4orch::kL3AdmitAction)}; + constexpr char *kValidAppDbKey = R"({"match/dst_mac":"00:02:03:04:00:00","priority":2030})"; + EXPECT_TRUE(DeserializeP4L3AdmitAppDbEntry(kValidAppDbKey, attributes).ok()); + constexpr char *kInvalidAppDbKey = R"({"match/dst_mac":"123.123.123.123","priority":2030})"; + EXPECT_FALSE(DeserializeP4L3AdmitAppDbEntry(kInvalidAppDbKey, attributes).ok()); +} + +TEST_F(L3AdmitManagerTest, DeserializeP4L3AdmitAppDbEntryShouldReturnNullPointerForInvalidPriority) +{ + std::vector attributes = {swss::FieldValueTuple(p4orch::kAction, p4orch::kL3AdmitAction)}; + constexpr char *kInvalidAppDbKey = R"({"match/dst_mac":"00:02:03:04:00:00","priority":-1})"; + EXPECT_FALSE(DeserializeP4L3AdmitAppDbEntry(kInvalidAppDbKey, attributes).ok()); +} + +TEST_F(L3AdmitManagerTest, DeserializeP4L3AdmitAppDbEntryShouldSucceedWithoutDstMac) +{ + std::vector attributes = {swss::FieldValueTuple(p4orch::kAction, p4orch::kL3AdmitAction)}; + constexpr char *kValidAppDbKey = R"({"priority":1})"; + EXPECT_TRUE(DeserializeP4L3AdmitAppDbEntry(kValidAppDbKey, attributes).ok()); +} + +TEST_F(L3AdmitManagerTest, DrainDuplicateSetRequestShouldSucceed) +{ + auto *p4_my_mac_entry = AddL3AdmitEntry1(); + ASSERT_NE(p4_my_mac_entry, nullptr); + + nlohmann::json j; + j[prependMatchField(p4orch::kDstMac)] = kP4L3AdmitAppDbEntry1.mac_address_data.to_string() + + p4orch::kDataMaskDelimiter + + kP4L3AdmitAppDbEntry1.mac_address_mask.to_string(); + j[p4orch::kPriority] = kP4L3AdmitAppDbEntry1.priority; + + std::vector fvs{{p4orch::kAction, p4orch::kL3AdmitAction}}; + + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_L3_ADMIT_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + SET_COMMAND, fvs); + + Enqueue(app_db_entry); + Drain(); + + // Expect that the update call will fail, so l3 admit entry's fields stay + // the same. + EXPECT_TRUE(ValidateL3AdmitEntryAdd(kP4L3AdmitAppDbEntry1)); +} + +TEST_F(L3AdmitManagerTest, DrainDeleteRequestShouldSucceedForExistingL3Admit) +{ + auto *p4_my_mac_entry = AddL3AdmitEntry1(); + ASSERT_NE(p4_my_mac_entry, nullptr); + + nlohmann::json j; + j[prependMatchField(p4orch::kDstMac)] = kP4L3AdmitAppDbEntry1.mac_address_data.to_string() + + p4orch::kDataMaskDelimiter + + kP4L3AdmitAppDbEntry1.mac_address_mask.to_string(); + j[p4orch::kPriority] = kP4L3AdmitAppDbEntry1.priority; + + std::vector fvs; + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + DEL_COMMAND, fvs); + EXPECT_CALL(mock_sai_my_mac_, remove_my_mac(Eq(p4_my_mac_entry->l3_admit_oid))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + Enqueue(app_db_entry); + Drain(); + + // Validate the l3 admit entry has been deleted in both P4 l3 admit + // manager + // and centralized mapper. + const auto l3admit_key = + KeyGenerator::generateL3AdmitKey(kP4L3AdmitAppDbEntry1.mac_address_data, kP4L3AdmitAppDbEntry1.mac_address_mask, + kP4L3AdmitAppDbEntry1.port_name, kP4L3AdmitAppDbEntry1.priority); + p4_my_mac_entry = GetL3AdmitEntry(l3admit_key); + EXPECT_EQ(p4_my_mac_entry, nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_MY_MAC, l3admit_key)); +} + +TEST_F(L3AdmitManagerTest, DrainValidAppEntryShouldSucceed) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kDstMac)] = kP4L3AdmitAppDbEntry2.mac_address_data.to_string() + + p4orch::kDataMaskDelimiter + + kP4L3AdmitAppDbEntry2.mac_address_mask.to_string(); + j[p4orch::kPriority] = kP4L3AdmitAppDbEntry2.priority; + j[prependMatchField(p4orch::kInPort)] = kP4L3AdmitAppDbEntry2.port_name; + + std::vector fvs{{p4orch::kAction, p4orch::kL3AdmitAction}}; + + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_L3_ADMIT_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + SET_COMMAND, fvs); + + Enqueue(app_db_entry); + EXPECT_CALL(mock_sai_my_mac_, create_my_mac(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kL3AdmitOid2), Return(SAI_STATUS_SUCCESS))); + + Drain(); + + EXPECT_TRUE(ValidateL3AdmitEntryAdd(kP4L3AdmitAppDbEntry2)); +} + +TEST_F(L3AdmitManagerTest, DrainInValidAppEntryShouldSucceed) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kDstMac)] = "1"; // Invalid Mac + j[p4orch::kPriority] = 1000; + + std::vector fvs{{p4orch::kAction, p4orch::kL3AdmitAction}}; + + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_L3_ADMIT_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + SET_COMMAND, fvs); + + Enqueue(app_db_entry); + + Drain(); + constexpr char *kL3AdmitKey = R"({"match/dst_mac":"1","priority":1000})"; + EXPECT_EQ(GetL3AdmitEntry(kL3AdmitKey), nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_MY_MAC, kL3AdmitKey)); +} + +TEST_F(L3AdmitManagerTest, VerifyStateTest) +{ + auto *p4_my_mac_entry = AddL3AdmitEntry1(); + ASSERT_NE(p4_my_mac_entry, nullptr); + nlohmann::json j; + j[prependMatchField(p4orch::kDstMac)] = kP4L3AdmitAppDbEntry1.mac_address_data.to_string() + + p4orch::kDataMaskDelimiter + + kP4L3AdmitAppDbEntry1.mac_address_mask.to_string(); + j[p4orch::kPriority] = kP4L3AdmitAppDbEntry1.priority; + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_L3_ADMIT_TABLE_NAME + + kTableKeyDelimiter + j.dump(); + std::vector attributes; + + // Verification should succeed with vaild key and value. + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kL3AdmitAction}); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set( + "SAI_OBJECT_TYPE_MY_MAC:oid:0x1", + std::vector{ + swss::FieldValueTuple{"SAI_MY_MAC_ATTR_MAC_ADDRESS", kP4L3AdmitAppDbEntry1.mac_address_data.to_string()}, + swss::FieldValueTuple{"SAI_MY_MAC_ATTR_MAC_ADDRESS_MASK", "FF:FF:FF:FF:00:00"}, + swss::FieldValueTuple{"SAI_MY_MAC_ATTR_PRIORITY", "2030"}}); + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Invalid key should fail verification. + EXPECT_FALSE(VerifyState("invalid", attributes).empty()); + EXPECT_FALSE(VerifyState("invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":FIXED_L3_ADMIT_TABLE:invalid", attributes).empty()); + + // Verification should fail if MAC does not exist. + j[prependMatchField(p4orch::kDstMac)] = kP4L3AdmitAppDbEntry2.mac_address_data.to_string() + + p4orch::kDataMaskDelimiter + + kP4L3AdmitAppDbEntry2.mac_address_mask.to_string(); + j[p4orch::kPriority] = kP4L3AdmitAppDbEntry1.priority; + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_L3_ADMIT_TABLE_NAME + + kTableKeyDelimiter + j.dump(), + attributes) + .empty()); + + // Verification should fail if port name mismatches. + auto saved_port_name = p4_my_mac_entry->port_name; + p4_my_mac_entry->port_name = kP4L3AdmitAppDbEntry2.port_name; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_my_mac_entry->port_name = saved_port_name; + + // Verification should fail if MAC mismatches. + auto saved_mac_address_data = p4_my_mac_entry->mac_address_data; + p4_my_mac_entry->mac_address_data = kP4L3AdmitAppDbEntry2.mac_address_data; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_my_mac_entry->mac_address_data = saved_mac_address_data; + + // Verification should fail if MAC mask mismatches. + auto saved_mac_address_mask = p4_my_mac_entry->mac_address_mask; + p4_my_mac_entry->mac_address_mask = kP4L3AdmitAppDbEntry2.mac_address_mask; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_my_mac_entry->mac_address_mask = saved_mac_address_mask; + + // Verification should fail if priority mismatches. + auto saved_priority = p4_my_mac_entry->priority; + p4_my_mac_entry->priority = 1111; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_my_mac_entry->priority = saved_priority; + + // Verification should fail if OID mapper mismatches. + const auto l3admit_key = + KeyGenerator::generateL3AdmitKey(kP4L3AdmitAppDbEntry1.mac_address_data, kP4L3AdmitAppDbEntry1.mac_address_mask, + kP4L3AdmitAppDbEntry1.port_name, kP4L3AdmitAppDbEntry1.priority); + p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_MY_MAC, l3admit_key); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_MY_MAC, l3admit_key, kL3AdmitOid1); +} + +TEST_F(L3AdmitManagerTest, VerifyStateAsicDbTest) +{ + auto *p4_my_mac_entry = AddL3AdmitEntry1(); + ASSERT_NE(p4_my_mac_entry, nullptr); + nlohmann::json j; + j[prependMatchField(p4orch::kDstMac)] = kP4L3AdmitAppDbEntry1.mac_address_data.to_string() + + p4orch::kDataMaskDelimiter + + kP4L3AdmitAppDbEntry1.mac_address_mask.to_string(); + j[p4orch::kPriority] = kP4L3AdmitAppDbEntry1.priority; + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_L3_ADMIT_TABLE_NAME + + kTableKeyDelimiter + j.dump(); + std::vector attributes; + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set( + "SAI_OBJECT_TYPE_MY_MAC:oid:0x1", + std::vector{ + swss::FieldValueTuple{"SAI_MY_MAC_ATTR_MAC_ADDRESS", kP4L3AdmitAppDbEntry1.mac_address_data.to_string()}, + swss::FieldValueTuple{"SAI_MY_MAC_ATTR_MAC_ADDRESS_MASK", "FF:FF:FF:FF:00:00"}, + swss::FieldValueTuple{"SAI_MY_MAC_ATTR_PRIORITY", "2030"}}); + + // Verification should succeed with vaild key and value. + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kL3AdmitAction}); + + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Verification should fail if ASIC DB values mismatch. + table.set("SAI_OBJECT_TYPE_MY_MAC:oid:0x1", + std::vector{swss::FieldValueTuple{"SAI_MY_MAC_ATTR_PRIORITY", "1000"}}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + // Verification should fail if ASIC DB table is missing. + table.del("SAI_OBJECT_TYPE_MY_MAC:oid:0x1"); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + table.set( + "SAI_OBJECT_TYPE_MY_MAC:oid:0x1", + std::vector{ + swss::FieldValueTuple{"SAI_MY_MAC_ATTR_MAC_ADDRESS", kP4L3AdmitAppDbEntry1.mac_address_data.to_string()}, + swss::FieldValueTuple{"SAI_MY_MAC_ATTR_MAC_ADDRESS_MASK", "FF:FF:FF:FF:00:00"}, + swss::FieldValueTuple{"SAI_MY_MAC_ATTR_PRIORITY", "2030"}}); +} \ No newline at end of file diff --git a/orchagent/p4orch/tests/mirror_session_manager_test.cpp b/orchagent/p4orch/tests/mirror_session_manager_test.cpp index c45a0d9bcd17..bc5563a07860 100644 --- a/orchagent/p4orch/tests/mirror_session_manager_test.cpp +++ b/orchagent/p4orch/tests/mirror_session_manager_test.cpp @@ -20,6 +20,8 @@ extern "C" #include "sai.h" } +using ::p4orch::kTableKeyDelimiter; + extern sai_mirror_api_t *sai_mirror_api; extern sai_object_id_t gSwitchId; extern PortsOrch *gPortsOrch; @@ -223,6 +225,11 @@ class MirrorSessionManagerTest : public ::testing::Test return mirror_session_manager_.drain(); } + std::string VerifyState(const std::string &key, const std::vector &tuple) + { + return mirror_session_manager_.verifyState(key, tuple); + } + ReturnCodeOr DeserializeP4MirrorSessionAppDbEntry( const std::string &key, const std::vector &attributes) { @@ -712,7 +719,7 @@ TEST_F(MirrorSessionManagerTest, CreateExistingMirrorSessionInMapperShouldFail) ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_MIRROR_SESSION, mirror_session_entry.mirror_session_key, mirror_session_entry.mirror_session_oid)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_FALSE(CreateMirrorSession(mirror_session_entry).ok()); } @@ -735,7 +742,7 @@ TEST_F(MirrorSessionManagerTest, UpdatingNonexistingMirrorSessionShouldFail) { P4MirrorSessionAppDbEntry app_db_entry; // Fail because existing_mirror_session_entry is nullptr. - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_FALSE(ProcessUpdateRequest(app_db_entry, /*existing_mirror_session_entry=*/nullptr) .ok()); @@ -746,7 +753,7 @@ TEST_F(MirrorSessionManagerTest, UpdatingNonexistingMirrorSessionShouldFail) kTtl1Num, kTos1Num); // Fail because the mirror session is not added into centralized mapper. - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_FALSE(ProcessUpdateRequest(app_db_entry, &existing_mirror_session_entry).ok()); } @@ -975,7 +982,7 @@ TEST_F(MirrorSessionManagerTest, UpdateRecoveryFailureShouldRaiseCriticalState) .WillOnce(Return(SAI_STATUS_SUCCESS)) .WillOnce(Return(SAI_STATUS_SUCCESS)) .WillOnce(Return(SAI_STATUS_FAILURE)); - // (TODO): Expect critical state. + // TODO: Expect critical state. Drain(); @@ -1002,10 +1009,193 @@ TEST_F(MirrorSessionManagerTest, DeleteMirrorSessionNotInMapperShouldFail) { AddDefaultMirrorSection(); p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_MIRROR_SESSION, KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); - // (TODO): Expect critical state. + // TODO: Expect critical state. ASSERT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRequest(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId))); } +TEST_F(MirrorSessionManagerTest, VerifyStateTest) +{ + AddDefaultMirrorSection(); + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + + APP_P4RT_MIRROR_SESSION_TABLE_NAME + kTableKeyDelimiter + j.dump(); + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_MIRROR_SESSION:oid:0x445566", + std::vector{ + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_MONITOR_PORT", "oid:0x112233"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_TYPE", "SAI_MIRROR_SESSION_TYPE_ENHANCED_REMOTE"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE", + "SAI_ERSPAN_ENCAPSULATION_TYPE_MIRROR_L3_GRE_TUNNEL"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION", "4"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_TOS", "0"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_TTL", "64"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS", "10.206.196.31"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS", "172.20.0.203"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS", "00:02:03:04:05:06"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS", "00:1A:11:17:5F:80"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE", "35006"}, + }); + std::vector attributes; + + // Verification should succeed with vaild key and value. + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), kPort1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcIp), kSrcIp1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kDstIp), kDstIp1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kSrcMac1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kDstMac), kDstMac1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kTtl), kTtl1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kTos), kTos1}); + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Invalid key should fail verification. + EXPECT_FALSE(VerifyState("invalid", attributes).empty()); + EXPECT_FALSE(VerifyState("invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid:invalid", attributes).empty()); + EXPECT_FALSE( + VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":FIXED_MIRROR_SESSION_TABLE:invalid", attributes).empty()); + + // Verification should fail if entry does not exist. + j[prependMatchField(p4orch::kMirrorSessionId)] = "invalid"; + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + + APP_P4RT_MIRROR_SESSION_TABLE_NAME + kTableKeyDelimiter + j.dump(), + attributes) + .empty()); + + auto *mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); + + // Verification should fail if mirror section key mismatches. + auto saved_mirror_session_key = mirror_entry->mirror_session_key; + mirror_entry->mirror_session_key = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + mirror_entry->mirror_session_key = saved_mirror_session_key; + + // Verification should fail if mirror section ID mismatches. + auto saved_mirror_session_id = mirror_entry->mirror_session_id; + mirror_entry->mirror_session_id = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + mirror_entry->mirror_session_id = saved_mirror_session_id; + + // Verification should fail if port mismatches. + auto saved_port = mirror_entry->port; + mirror_entry->port = kPort2; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + mirror_entry->port = saved_port; + + // Verification should fail if source IP mismatches. + auto saved_src_ip = mirror_entry->src_ip; + mirror_entry->src_ip = swss::IpAddress(kSrcIp2); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + mirror_entry->src_ip = saved_src_ip; + + // Verification should fail if dest IP mismatches. + auto saved_dst_ip = mirror_entry->dst_ip; + mirror_entry->dst_ip = swss::IpAddress(kDstIp2); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + mirror_entry->dst_ip = saved_dst_ip; + + // Verification should fail if source MAC mismatches. + auto saved_src_mac = mirror_entry->src_mac; + mirror_entry->src_mac = swss::MacAddress(kSrcMac2); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + mirror_entry->src_mac = saved_src_mac; + + // Verification should fail if dest MAC mismatches. + auto saved_dst_mac = mirror_entry->dst_mac; + mirror_entry->dst_mac = swss::MacAddress(kDstMac2); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + mirror_entry->dst_mac = saved_dst_mac; + + // Verification should fail if ttl mismatches. + auto saved_ttl = mirror_entry->ttl; + mirror_entry->ttl = kTtl2Num; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + mirror_entry->ttl = saved_ttl; + + // Verification should fail if tos mismatches. + auto saved_tos = mirror_entry->tos; + mirror_entry->tos = kTos2Num; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + mirror_entry->tos = saved_tos; + + // Verification should fail if OID mapper mismatches. + p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_MIRROR_SESSION, KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_MIRROR_SESSION, KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), + kMirrorSessionOid); +} + +TEST_F(MirrorSessionManagerTest, VerifyStateAsicDbTest) +{ + AddDefaultMirrorSection(); + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + + APP_P4RT_MIRROR_SESSION_TABLE_NAME + kTableKeyDelimiter + j.dump(); + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_MIRROR_SESSION:oid:0x445566", + std::vector{ + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_MONITOR_PORT", "oid:0x112233"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_TYPE", "SAI_MIRROR_SESSION_TYPE_ENHANCED_REMOTE"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE", + "SAI_ERSPAN_ENCAPSULATION_TYPE_MIRROR_L3_GRE_TUNNEL"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION", "4"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_TOS", "0"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_TTL", "64"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS", "10.206.196.31"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS", "172.20.0.203"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS", "00:02:03:04:05:06"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS", "00:1A:11:17:5F:80"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE", "35006"}, + }); + + std::vector attributes; + + // Verification should succeed with vaild key and value. + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), kPort1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcIp), kSrcIp1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kDstIp), kDstIp1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kSrcMac1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kDstMac), kDstMac1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kTtl), kTtl1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kTos), kTos1}); + + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // set differenet SRC IP ADDR and expect the VerifyState to fail + table.set("SAI_OBJECT_TYPE_MIRROR_SESSION:oid:0x445566", + std::vector{ + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS", "10.206.196.32"}}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + // Delete the ASIC DB entry and expect the VerifyState to fail + table.del("SAI_OBJECT_TYPE_MIRROR_SESSION:oid:0x445566"); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + // Restore the ASIC DB entry + table.set("SAI_OBJECT_TYPE_MIRROR_SESSION:oid:0x445566", + std::vector{ + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_MONITOR_PORT", "oid:0x112233"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_TYPE", "SAI_MIRROR_SESSION_TYPE_ENHANCED_REMOTE"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE", + "SAI_ERSPAN_ENCAPSULATION_TYPE_MIRROR_L3_GRE_TUNNEL"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION", "4"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_TOS", "0"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_TTL", "64"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS", "10.206.196.31"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS", "172.20.0.203"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS", "00:02:03:04:05:06"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS", "00:1A:11:17:5F:80"}, + swss::FieldValueTuple{"SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE", "35006"}, + }); +} + } // namespace test } // namespace p4orch diff --git a/orchagent/p4orch/tests/mock_sai_my_mac.h b/orchagent/p4orch/tests/mock_sai_my_mac.h new file mode 100644 index 000000000000..e82f38f520dd --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_my_mac.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock Class mapping methods to my_mac object SAI APIs. +class MockSaiMyMac +{ + public: + MOCK_METHOD4(create_my_mac, sai_status_t(_Out_ sai_object_id_t *my_mac_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_my_mac, sai_status_t(_In_ sai_object_id_t my_mac_id)); +}; + +MockSaiMyMac *mock_sai_my_mac; + +sai_status_t mock_create_my_mac(_Out_ sai_object_id_t *my_mac_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_my_mac->create_my_mac(my_mac_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_my_mac(_In_ sai_object_id_t my_mac_id) +{ + return mock_sai_my_mac->remove_my_mac(my_mac_id); +} diff --git a/orchagent/p4orch/tests/mock_sai_router_interface.cpp b/orchagent/p4orch/tests/mock_sai_router_interface.cpp new file mode 100644 index 000000000000..8fbc4af16d8a --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_router_interface.cpp @@ -0,0 +1,26 @@ +#include "mock_sai_router_interface.h" + +MockSaiRouterInterface *mock_sai_router_intf; + +sai_status_t mock_create_router_interface(_Out_ sai_object_id_t *router_interface_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_router_intf->create_router_interface(router_interface_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_router_interface(_In_ sai_object_id_t router_interface_id) +{ + return mock_sai_router_intf->remove_router_interface(router_interface_id); +} + +sai_status_t mock_set_router_interface_attribute(_In_ sai_object_id_t router_interface_id, + _In_ const sai_attribute_t *attr) +{ + return mock_sai_router_intf->set_router_interface_attribute(router_interface_id, attr); +} + +sai_status_t mock_get_router_interface_attribute(_In_ sai_object_id_t router_interface_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_router_intf->get_router_interface_attribute(router_interface_id, attr_count, attr_list); +} \ No newline at end of file diff --git a/orchagent/p4orch/tests/mock_sai_router_interface.h b/orchagent/p4orch/tests/mock_sai_router_interface.h index 9c0caa300440..5d7b7e117248 100644 --- a/orchagent/p4orch/tests/mock_sai_router_interface.h +++ b/orchagent/p4orch/tests/mock_sai_router_interface.h @@ -25,27 +25,15 @@ class MockSaiRouterInterface _Inout_ sai_attribute_t *attr_list)); }; -MockSaiRouterInterface *mock_sai_router_intf; +extern MockSaiRouterInterface *mock_sai_router_intf; sai_status_t mock_create_router_interface(_Out_ sai_object_id_t *router_interface_id, _In_ sai_object_id_t switch_id, - _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) -{ - return mock_sai_router_intf->create_router_interface(router_interface_id, switch_id, attr_count, attr_list); -} + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list); -sai_status_t mock_remove_router_interface(_In_ sai_object_id_t router_interface_id) -{ - return mock_sai_router_intf->remove_router_interface(router_interface_id); -} +sai_status_t mock_remove_router_interface(_In_ sai_object_id_t router_interface_id); sai_status_t mock_set_router_interface_attribute(_In_ sai_object_id_t router_interface_id, - _In_ const sai_attribute_t *attr) -{ - return mock_sai_router_intf->set_router_interface_attribute(router_interface_id, attr); -} + _In_ const sai_attribute_t *attr); sai_status_t mock_get_router_interface_attribute(_In_ sai_object_id_t router_interface_id, _In_ uint32_t attr_count, - _Inout_ sai_attribute_t *attr_list) -{ - return mock_sai_router_intf->get_router_interface_attribute(router_interface_id, attr_count, attr_list); -} + _Inout_ sai_attribute_t *attr_list); diff --git a/orchagent/p4orch/tests/mock_sai_tunnel.h b/orchagent/p4orch/tests/mock_sai_tunnel.h new file mode 100644 index 000000000000..5a2a165b0251 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_tunnel.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock Class mapping methods to tunnel object SAI APIs. +class MockSaiTunnel +{ + public: + MOCK_METHOD4(create_tunnel, sai_status_t(_Out_ sai_object_id_t *tunnel_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_tunnel, sai_status_t(_In_ sai_object_id_t tunnel_id)); +}; + +MockSaiTunnel *mock_sai_tunnel; + +sai_status_t mock_create_tunnel(_Out_ sai_object_id_t *tunnel_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_tunnel->create_tunnel(tunnel_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_tunnel(_In_ sai_object_id_t tunnel_id) +{ + return mock_sai_tunnel->remove_tunnel(tunnel_id); +} diff --git a/orchagent/p4orch/tests/neighbor_manager_test.cpp b/orchagent/p4orch/tests/neighbor_manager_test.cpp index e3986ef701ae..ae91f4f567ca 100644 --- a/orchagent/p4orch/tests/neighbor_manager_test.cpp +++ b/orchagent/p4orch/tests/neighbor_manager_test.cpp @@ -138,6 +138,11 @@ class NeighborManagerTest : public ::testing::Test neighbor_manager_.drain(); } + std::string VerifyState(const std::string &key, const std::vector &tuple) + { + return neighbor_manager_.verifyState(key, tuple); + } + ReturnCodeOr DeserializeNeighborEntry(const std::string &key, const std::vector &attributes) { @@ -280,7 +285,7 @@ TEST_F(NeighborManagerTest, CreateNeighborEntryExistsInP4OidMapper) P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); p4_oid_mapper_.setDummyOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_entry.neighbor_key); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, CreateNeighbor(neighbor_entry)); auto current_entry = GetNeighborEntry(neighbor_entry.neighbor_key); @@ -342,7 +347,7 @@ TEST_F(NeighborManagerTest, RemoveNeighborNotExistInMapper) AddNeighborEntry(neighbor_entry, kRouterInterfaceOid2); ASSERT_TRUE(p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_entry.neighbor_key)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, RemoveNeighbor(neighbor_entry.neighbor_key)); } @@ -820,3 +825,116 @@ TEST_F(NeighborManagerTest, DrainInvalidOperation) P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); } + +TEST_F(NeighborManagerTest, VerifyStateTest) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid1); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_NEIGHBOR_ENTRY:{\"ip\":\"10.0.0.22\",\"rif\":\"oid:" + "0x295100\",\"switch_id\":\"oid:0x0\"}", + std::vector{ + swss::FieldValueTuple{"SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS", "00:01:02:03:04:05"}, + swss::FieldValueTuple{"SAI_NEIGHBOR_ENTRY_ATTR_NO_HOST_ROUTE", "true"}}); + + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_NEIGHBOR_TABLE_NAME + + kTableKeyDelimiter + CreateNeighborAppDbKey(kRouterInterfaceId1, kNeighborId1); + std::vector attributes; + + // Verification should succeed with vaild key and value. + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kDstMac), kMacAddress1.to_string()}); + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Invalid key should fail verification. + EXPECT_FALSE(VerifyState("invalid", attributes).empty()); + EXPECT_FALSE(VerifyState("invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":FIXED_NEIGHBOR_TABLE:invalid", attributes).empty()); + + // Non-existing router intf should fail verification. + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_NEIGHBOR_TABLE_NAME + + kTableKeyDelimiter + CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId1), + attributes) + .empty()); + + // Non-existing entry should fail verification. + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_NEIGHBOR_TABLE_NAME + + kTableKeyDelimiter + CreateNeighborAppDbKey(kRouterInterfaceId1, kNeighborId2), + attributes) + .empty()); + + auto *current_entry = GetNeighborEntry(KeyGenerator::generateNeighborKey(kRouterInterfaceId1, kNeighborId1)); + EXPECT_NE(current_entry, nullptr); + + // Verification should fail if ritf ID mismatches. + auto saved_router_intf_id = current_entry->router_intf_id; + current_entry->router_intf_id = kRouterInterfaceId2; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + current_entry->router_intf_id = saved_router_intf_id; + + // Verification should fail if neighbor ID mismatches. + auto saved_neighbor_id = current_entry->neighbor_id; + current_entry->neighbor_id = kNeighborId2; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + current_entry->neighbor_id = saved_neighbor_id; + + // Verification should fail if dest MAC mismatches. + auto saved_dst_mac_address = current_entry->dst_mac_address; + current_entry->dst_mac_address = kMacAddress2; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + current_entry->dst_mac_address = saved_dst_mac_address; + + // Verification should fail if router intf key mismatches. + auto saved_router_intf_key = current_entry->router_intf_key; + current_entry->router_intf_key = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + current_entry->router_intf_key = saved_router_intf_key; + + // Verification should fail if neighbor key mismatches. + auto saved_neighbor_key = current_entry->neighbor_key; + current_entry->neighbor_key = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + current_entry->neighbor_key = saved_neighbor_key; +} + +TEST_F(NeighborManagerTest, VerifyStateAsicDbTest) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid1); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_NEIGHBOR_ENTRY:{\"ip\":\"10.0.0.22\",\"rif\":\"oid:" + "0x295100\",\"switch_id\":\"oid:0x0\"}", + std::vector{ + swss::FieldValueTuple{"SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS", "00:01:02:03:04:05"}, + swss::FieldValueTuple{"SAI_NEIGHBOR_ENTRY_ATTR_NO_HOST_ROUTE", "true"}}); + + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_NEIGHBOR_TABLE_NAME + + kTableKeyDelimiter + CreateNeighborAppDbKey(kRouterInterfaceId1, kNeighborId1); + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kDstMac), kMacAddress1.to_string()}); + + // Verification should succeed with correct ASIC DB values. + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Verification should fail if ASIC DB values mismatch. + table.set("SAI_OBJECT_TYPE_NEIGHBOR_ENTRY:{\"ip\":\"10.0.0.22\",\"rif\":\"oid:" + "0x295100\",\"switch_id\":\"oid:0x0\"}", + std::vector{ + swss::FieldValueTuple{"SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS", "00:ff:ee:dd:cc:bb"}}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + // Verification should fail if ASIC DB table is missing. + table.del("SAI_OBJECT_TYPE_NEIGHBOR_ENTRY:{\"ip\":\"10.0.0.22\",\"rif\":\"oid:" + "0x295100\",\"switch_id\":\"oid:0x0\"}"); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + table.set("SAI_OBJECT_TYPE_NEIGHBOR_ENTRY:{\"ip\":\"10.0.0.22\",\"rif\":\"oid:" + "0x295100\",\"switch_id\":\"oid:0x0\"}", + std::vector{ + swss::FieldValueTuple{"SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS", "00:01:02:03:04:05"}, + swss::FieldValueTuple{"SAI_NEIGHBOR_ENTRY_ATTR_NO_HOST_ROUTE", "true"}}); +} diff --git a/orchagent/p4orch/tests/next_hop_manager_test.cpp b/orchagent/p4orch/tests/next_hop_manager_test.cpp index a78310cc8d35..64416bb3b3ce 100644 --- a/orchagent/p4orch/tests/next_hop_manager_test.cpp +++ b/orchagent/p4orch/tests/next_hop_manager_test.cpp @@ -10,10 +10,12 @@ #include "ipaddress.h" #include "json.hpp" #include "mock_response_publisher.h" +#include "mock_sai_hostif.h" #include "mock_sai_next_hop.h" +#include "mock_sai_serialize.h" +#include "mock_sai_switch.h" #include "p4oidmapper.h" -#include "p4orch/p4orch_util.h" -#include "p4orch_util.h" +#include "p4orch.h" #include "return_code.h" #include "swssnet.h" extern "C" @@ -32,54 +34,108 @@ using ::testing::StrictMock; using ::testing::Truly; extern sai_object_id_t gSwitchId; -extern sai_next_hop_api_t *sai_next_hop_api; extern MockSaiNextHop *mock_sai_next_hop; +extern P4Orch *gP4Orch; +extern VRFOrch *gVrfOrch; +extern swss::DBConnector *gAppDb; +extern sai_hostif_api_t *sai_hostif_api; +extern sai_switch_api_t *sai_switch_api; +extern sai_next_hop_api_t *sai_next_hop_api; namespace { constexpr char *kNextHopId = "8"; constexpr char *kNextHopP4AppDbKey = R"({"match/nexthop_id":"8"})"; -constexpr sai_object_id_t kNextHopOid = 1; +constexpr sai_object_id_t kNextHopOid = 101; +constexpr char *kTunnelNextHopId = "tunnel-nexthop-1"; +constexpr char *kTunnelNextHopP4AppDbKey = R"({"match/nexthop_id":"tunnel-nexthop-1"})"; +constexpr sai_object_id_t kTunnelNextHopOid = 102; constexpr char *kRouterInterfaceId1 = "16"; constexpr char *kRouterInterfaceId2 = "17"; constexpr sai_object_id_t kRouterInterfaceOid1 = 1; constexpr sai_object_id_t kRouterInterfaceOid2 = 2; +constexpr char *kTunnelId1 = "tunnel-1"; +constexpr char *kTunnelId2 = "tunnel-2"; +constexpr sai_object_id_t kTunnelOid1 = 11; +constexpr sai_object_id_t kTunnelOid2 = 12; constexpr char *kNeighborId1 = "10.0.0.1"; constexpr char *kNeighborId2 = "fe80::21a:11ff:fe17:5f80"; // APP DB entries for Add and Update request. -const P4NextHopAppDbEntry kP4NextHopAppDbEntry1{/*next_hop_id=*/kNextHopId, /*router_interface_id=*/kRouterInterfaceId1, +const P4NextHopAppDbEntry kP4NextHopAppDbEntry1{/*next_hop_id=*/kNextHopId, + /*router_interface_id=*/kRouterInterfaceId1, + /*gre_tunnel_id=*/"", /*neighbor_id=*/swss::IpAddress(kNeighborId1), - /*is_set_router_interface_id=*/true, /*is_set_neighbor_id=*/true}; + /*action_str=*/"set_ip_nexthop"}; -const P4NextHopAppDbEntry kP4NextHopAppDbEntry2{/*next_hop_id=*/kNextHopId, /*router_interface_id=*/kRouterInterfaceId2, +const P4NextHopAppDbEntry kP4NextHopAppDbEntry2{/*next_hop_id=*/kNextHopId, + /*router_interface_id=*/kRouterInterfaceId2, + /*gre_tunnel_id=*/"", /*neighbor_id=*/swss::IpAddress(kNeighborId2), - /*is_set_router_interface_id=*/true, /*is_set_neighbor_id=*/true}; + /*action_str=*/"set_ip_nexthop"}; // APP DB entries for Delete request. -const P4NextHopAppDbEntry kP4NextHopAppDbEntry3{/*next_hop_id=*/kNextHopId, /*router_interface_id=*/"", +const P4NextHopAppDbEntry kP4NextHopAppDbEntry3{/*next_hop_id=*/kNextHopId, + /*router_interface_id=*/"", + /*gre_tunnel_id=*/"", /*neighbor_id=*/swss::IpAddress(), - /*is_set_router_interface_id=*/false, /*is_set_neighbor_id=*/false}; + /*action_str=*/""}; + +// APP DB entry for tunnel next hop entry +const P4NextHopAppDbEntry kP4TunnelNextHopAppDbEntry1{/*next_hop_id=*/kTunnelNextHopId, + /*router_interface_id=*/"", + /*gre_tunnel_id=*/kTunnelId1, + /*neighbor_id=*/swss::IpAddress(kNeighborId1), + /*action_str=*/"set_tunnel_encap_nexthop"}; + +const P4NextHopAppDbEntry kP4TunnelNextHopAppDbEntry2{/*next_hop_id=*/kTunnelNextHopId, + /*router_interface_id=*/"", + /*gre_tunnel_id=*/kTunnelId2, + /*neighbor_id=*/swss::IpAddress(kNeighborId2), + /*action_str=*/"set_tunnel_encap_nexthop"}; + +const P4GreTunnelEntry kP4TunnelEntry1( + /*tunnel_id=*/kTunnelId1, + /*router_interface_id=*/kRouterInterfaceId1, + /*src_ip=*/swss::IpAddress("1.2.3.4"), + /*dst_ip=*/swss::IpAddress("5.6.7.8")); + +const P4GreTunnelEntry kP4TunnelEntry2( + /*tunnel_id=*/kTunnelId2, + /*router_interface_id=*/kRouterInterfaceId2, + /*src_ip=*/swss::IpAddress("1.2.3.4"), + /*dst_ip=*/swss::IpAddress("5.6.7.8")); std::unordered_map CreateAttributeListForNextHopObject( - const P4NextHopAppDbEntry &app_entry, const sai_object_id_t &rif_oid) + const P4NextHopAppDbEntry &app_entry, const sai_object_id_t &oid) { std::unordered_map next_hop_attrs; sai_attribute_t next_hop_attr; - next_hop_attr.id = SAI_NEXT_HOP_ATTR_TYPE; - next_hop_attr.value.s32 = SAI_NEXT_HOP_TYPE_IP; - next_hop_attrs.insert({next_hop_attr.id, next_hop_attr.value}); + if (app_entry.action_str == p4orch::kSetTunnelNexthop) + { + next_hop_attr.id = SAI_NEXT_HOP_ATTR_TYPE; + next_hop_attr.value.s32 = SAI_NEXT_HOP_TYPE_TUNNEL_ENCAP; + next_hop_attrs.insert({next_hop_attr.id, next_hop_attr.value}); + next_hop_attr.id = SAI_NEXT_HOP_ATTR_TUNNEL_ID; + next_hop_attr.value.oid = oid; + next_hop_attrs.insert({next_hop_attr.id, next_hop_attr.value}); + } + else + { + next_hop_attr.id = SAI_NEXT_HOP_ATTR_TYPE; + next_hop_attr.value.s32 = SAI_NEXT_HOP_TYPE_IP; + next_hop_attrs.insert({next_hop_attr.id, next_hop_attr.value}); + next_hop_attr.id = SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID; + next_hop_attr.value.oid = oid; + next_hop_attrs.insert({next_hop_attr.id, next_hop_attr.value}); + } next_hop_attr.id = SAI_NEXT_HOP_ATTR_IP; swss::copy(next_hop_attr.value.ipaddr, app_entry.neighbor_id); next_hop_attrs.insert({next_hop_attr.id, next_hop_attr.value}); - next_hop_attr.id = SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID; - next_hop_attr.value.oid = rif_oid; - next_hop_attrs.insert({next_hop_attr.id, next_hop_attr.value}); - return next_hop_attrs; } @@ -98,7 +154,8 @@ bool MatchCreateNextHopArgAttrList(const sai_attribute_t *attr_list, const auto end = expected_attr_list.end(); if (expected_attr_list.size() != 3 || expected_attr_list.find(SAI_NEXT_HOP_ATTR_TYPE) == end || expected_attr_list.find(SAI_NEXT_HOP_ATTR_IP) == end || - expected_attr_list.find(SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID) == end) + (expected_attr_list.find(SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID) == end && + expected_attr_list.find(SAI_NEXT_HOP_ATTR_TUNNEL_ID) == end)) { return false; } @@ -142,6 +199,12 @@ bool MatchCreateNextHopArgAttrList(const sai_attribute_t *attr_list, return false; } break; + case SAI_NEXT_HOP_ATTR_TUNNEL_ID: + if (attr_list[i].value.oid != expected_attr_list.at(SAI_NEXT_HOP_ATTR_TUNNEL_ID).oid) + { + return false; + } + break; default: // Invalid attribute ID in next hop's attribute list. return false; @@ -158,6 +221,23 @@ class NextHopManagerTest : public ::testing::Test protected: NextHopManagerTest() : next_hop_manager_(&p4_oid_mapper_, &publisher_) { + mock_sai_hostif = &mock_sai_hostif_; + mock_sai_switch = &mock_sai_switch_; + sai_switch_api->get_switch_attribute = mock_get_switch_attribute; + sai_hostif_api->create_hostif_trap = mock_create_hostif_trap; + sai_hostif_api->create_hostif_table_entry = mock_create_hostif_table_entry; + EXPECT_CALL(mock_sai_hostif_, create_hostif_table_entry(_, _, _, _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_hostif_, create_hostif_trap(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_switch_, get_switch_attribute(_, _, _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + copp_orch_ = new CoppOrch(gAppDb, APP_COPP_TABLE_NAME); + std::vector p4_tables; + gP4Orch = new P4Orch(gAppDb, p4_tables, gVrfOrch, copp_orch_); + } + + ~NextHopManagerTest() + { + delete gP4Orch; + delete copp_orch_; } void SetUp() override @@ -170,6 +250,11 @@ class NextHopManagerTest : public ::testing::Test sai_next_hop_api->get_next_hop_attribute = mock_get_next_hop_attribute; } + void TearDown() override + { + gP4Orch->getGreTunnelManager()->m_greTunnelTable.clear(); + } + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) { next_hop_manager_.enqueue(entry); @@ -180,6 +265,11 @@ class NextHopManagerTest : public ::testing::Test next_hop_manager_.drain(); } + std::string VerifyState(const std::string &key, const std::vector &tuple) + { + return next_hop_manager_.verifyState(key, tuple); + } + ReturnCode ProcessAddRequest(const P4NextHopAppDbEntry &app_db_entry) { return next_hop_manager_.processAddRequest(app_db_entry); @@ -207,7 +297,7 @@ class NextHopManagerTest : public ::testing::Test } // Resolves the dependency of a next hop entry by adding depended router - // interface and neighbor into centralized mapper. + // interface/tunnel and neighbor into centralized mapper. // Returns true on succuess. bool ResolveNextHopEntryDependency(const P4NextHopAppDbEntry &app_db_entry, const sai_object_id_t &rif_oid); @@ -235,18 +325,39 @@ class NextHopManagerTest : public ::testing::Test MockResponsePublisher publisher_; P4OidMapper p4_oid_mapper_; NextHopManager next_hop_manager_; + StrictMock mock_sai_hostif_; + StrictMock mock_sai_switch_; + CoppOrch *copp_orch_; }; bool NextHopManagerTest::ResolveNextHopEntryDependency(const P4NextHopAppDbEntry &app_db_entry, - const sai_object_id_t &rif_oid) + const sai_object_id_t &oid) { - const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); - if (!p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, rif_oid)) + std::string rif_id; + if (app_db_entry.action_str == p4orch::kSetTunnelNexthop) { - return false; + const std::string tunnel_key = KeyGenerator::generateTunnelKey(app_db_entry.gre_tunnel_id); + if (!p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_TUNNEL, tunnel_key, oid)) + { + return false; + } + gP4Orch->getGreTunnelManager()->m_greTunnelTable.emplace( + tunnel_key, app_db_entry.gre_tunnel_id == kTunnelId1 ? kP4TunnelEntry1 : kP4TunnelEntry2); + auto rif_id_or = gP4Orch->getGreTunnelManager()->getUnderlayIfFromGreTunnelEntry(tunnel_key); + EXPECT_TRUE(rif_id_or.ok()); + rif_id = *rif_id_or; } - const std::string neighbor_key = - KeyGenerator::generateNeighborKey(app_db_entry.router_interface_id, app_db_entry.neighbor_id); + else + { + rif_id = app_db_entry.router_interface_id; + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); + if (!p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, oid)) + { + return false; + } + } + + const std::string neighbor_key = KeyGenerator::generateNeighborKey(rif_id, app_db_entry.neighbor_id); if (!p4_oid_mapper_.setDummyOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key)) { return false; @@ -279,10 +390,23 @@ bool NextHopManagerTest::ValidateNextHopEntryAdd(const P4NextHopAppDbEntry &app_ { const auto *p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(app_db_entry.next_hop_id)); if (p4_next_hop_entry == nullptr || p4_next_hop_entry->next_hop_id != app_db_entry.next_hop_id || - p4_next_hop_entry->router_interface_id != app_db_entry.router_interface_id || p4_next_hop_entry->neighbor_id != app_db_entry.neighbor_id || p4_next_hop_entry->next_hop_oid != expected_next_hop_oid) + { + return false; + } + + if (app_db_entry.action_str == p4orch::kSetTunnelNexthop && + p4_next_hop_entry->gre_tunnel_id != app_db_entry.gre_tunnel_id) + { return false; + } + + if (app_db_entry.action_str == p4orch::kSetIpNexthop && + p4_next_hop_entry->router_interface_id != app_db_entry.router_interface_id) + { + return false; + } sai_object_id_t next_hop_oid; if (!p4_oid_mapper_.getOID(SAI_OBJECT_TYPE_NEXT_HOP, p4_next_hop_entry->next_hop_key, &next_hop_oid) || @@ -326,7 +450,7 @@ TEST_F(NextHopManagerTest, ProcessAddRequestShouldFailWhenNextHopExistInCentralM ASSERT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry1, kRouterInterfaceOid1)); ASSERT_TRUE(p4_oid_mapper_.setOID( SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id), kNextHopOid)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessAddRequest(kP4NextHopAppDbEntry1)); } @@ -341,6 +465,17 @@ TEST_F(NextHopManagerTest, ProcessAddRequestShouldFailWhenDependingRifIsAbsentIn EXPECT_EQ(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)), nullptr); } +TEST_F(NextHopManagerTest, ProcessAddRequestShouldFailWhenDependingTunnelIsAbsentInCentralMapper) +{ + const std::string neighbor_key = KeyGenerator::generateNeighborKey(kP4TunnelNextHopAppDbEntry1.router_interface_id, + kP4TunnelNextHopAppDbEntry1.neighbor_id); + ASSERT_TRUE(p4_oid_mapper_.setDummyOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key)); + + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(kP4TunnelNextHopAppDbEntry1)); + + EXPECT_EQ(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4TunnelNextHopAppDbEntry1.next_hop_id)), nullptr); +} + TEST_F(NextHopManagerTest, ProcessAddRequestShouldFailWhenDependingNeigherIsAbsentInCentralMapper) { const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); @@ -386,6 +521,35 @@ TEST_F(NextHopManagerTest, ProcessAddRequestShouldDoNoOpForDuplicateAddRequest) EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); } +TEST_F(NextHopManagerTest, ProcessAddRequestShouldSuccessForTunnelNexthop) +{ + ASSERT_TRUE(ResolveNextHopEntryDependency(kP4TunnelNextHopAppDbEntry1, kTunnelOid1)); + + // Set up mock call. + EXPECT_CALL(mock_sai_next_hop_, + create_next_hop( + ::testing::NotNull(), Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchCreateNextHopArgAttrList, std::placeholders::_1, + CreateAttributeListForNextHopObject(kP4TunnelNextHopAppDbEntry1, kTunnelOid1))))) + .WillOnce(DoAll(SetArgPointee<0>(kTunnelNextHopOid), Return(SAI_STATUS_SUCCESS))); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(kP4TunnelNextHopAppDbEntry1)); + + EXPECT_NE(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4TunnelNextHopAppDbEntry1.next_hop_id)), nullptr); + + // Add the same next hop entry again. + EXPECT_EQ(StatusCode::SWSS_RC_EXISTS, ProcessAddRequest(kP4TunnelNextHopAppDbEntry1)); + + // Adding the same next hop entry multiple times should have the same outcome + // as adding it once. + EXPECT_TRUE(ValidateNextHopEntryAdd(kP4TunnelNextHopAppDbEntry1, kTunnelNextHopOid)); + const std::string tunnel_key = KeyGenerator::generateTunnelKey(kP4TunnelNextHopAppDbEntry1.gre_tunnel_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4TunnelEntry1.router_interface_id, kP4TunnelNextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_TUNNEL, tunnel_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + TEST_F(NextHopManagerTest, ProcessUpdateRequestShouldFailAsItIsUnsupported) { auto *p4_next_hop_entry = AddNextHopEntry1(); @@ -444,7 +608,7 @@ TEST_F(NextHopManagerTest, ProcessDeleteRequestShouldFailIfNextHopEntryIsAbsentI ASSERT_TRUE(p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_NEXT_HOP, p4_next_hop_entry->next_hop_key)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRequest(p4_next_hop_entry->next_hop_key)); // Validate the next hop entry is not deleted in P4 next hop manager. @@ -533,7 +697,7 @@ TEST_F(NextHopManagerTest, GetNextHopEntryShouldReturnNullPointerForNonexistingN TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldSucceedForValidNextHopSetEntry) { std::vector attributes = { - swss::FieldValueTuple(p4orch::kAction, "set_nexthop"), + swss::FieldValueTuple(p4orch::kAction, "set_ip_nexthop"), swss::FieldValueTuple(prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId1), swss::FieldValueTuple(prependParamField(p4orch::kNeighborId), kNeighborId1)}; @@ -541,9 +705,9 @@ TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldSucceedForValidNe EXPECT_TRUE(app_db_entry_or.ok()); auto &app_db_entry = *app_db_entry_or; EXPECT_EQ(app_db_entry.next_hop_id, kNextHopId); - EXPECT_TRUE(app_db_entry.is_set_router_interface_id); + EXPECT_FALSE(app_db_entry.router_interface_id.empty()); EXPECT_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); - EXPECT_TRUE(app_db_entry.is_set_neighbor_id); + EXPECT_FALSE(app_db_entry.neighbor_id.isZero()); EXPECT_EQ(app_db_entry.neighbor_id, swss::IpAddress(kNeighborId1)); } @@ -553,8 +717,8 @@ TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldSucceedForValidNe EXPECT_TRUE(app_db_entry_or.ok()); auto &app_db_entry = *app_db_entry_or; EXPECT_EQ(app_db_entry.next_hop_id, kNextHopId); - EXPECT_FALSE(app_db_entry.is_set_router_interface_id); - EXPECT_FALSE(app_db_entry.is_set_neighbor_id); + EXPECT_TRUE(app_db_entry.router_interface_id.empty()); + EXPECT_TRUE(app_db_entry.neighbor_id.isZero()); } TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldReturnNullPointerWhenFailToDeserializeNextHopId) @@ -569,7 +733,7 @@ TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldReturnNullPointer TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldReturnNullPointerForInvalidIpAddr) { std::vector attributes = { - swss::FieldValueTuple(p4orch::kAction, "set_nexthop"), + swss::FieldValueTuple(p4orch::kAction, "set_ip_nexthop"), swss::FieldValueTuple(prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId1), swss::FieldValueTuple(prependParamField(p4orch::kNeighborId), "0.0.0.0.0.0")}; // Invalid IP address. @@ -579,7 +743,7 @@ TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldReturnNullPointer TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldReturnNullPointerDueToUnexpectedField) { std::vector attributes = { - swss::FieldValueTuple(p4orch::kAction, "set_nexthop"), + swss::FieldValueTuple(p4orch::kAction, "set_ip_nexthop"), swss::FieldValueTuple(p4orch::kRouterInterfaceId, kRouterInterfaceId1), swss::FieldValueTuple("unexpected_field", "unexpected_value")}; @@ -591,7 +755,8 @@ TEST_F(NextHopManagerTest, DrainValidAppEntryShouldSucceed) nlohmann::json j; j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; - std::vector fvs{{prependParamField(p4orch::kNeighborId), kNeighborId2}, + std::vector fvs{{p4orch::kAction, p4orch::kSetIpNexthop}, + {prependParamField(p4orch::kNeighborId), kNeighborId2}, {prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId2}}; swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), @@ -608,6 +773,52 @@ TEST_F(NextHopManagerTest, DrainValidAppEntryShouldSucceed) EXPECT_TRUE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry2, kNextHopOid)); } +TEST_F(NextHopManagerTest, DrainValidTunnelNexthopAppEntryShouldSucceed) +{ + nlohmann::json tunnel_j; + tunnel_j[prependMatchField(p4orch::kNexthopId)] = kTunnelNextHopId; + std::vector tunnel_fvs = {{p4orch::kAction, p4orch::kSetTunnelNexthop}, + {prependParamField(p4orch::kNeighborId), kNeighborId2}, + {prependParamField(p4orch::kTunnelId), kTunnelId2}}; + + swss::KeyOpFieldsValuesTuple tunnel_app_db_entry( + std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + tunnel_j.dump(), SET_COMMAND, tunnel_fvs); + + Enqueue(tunnel_app_db_entry); + + EXPECT_TRUE(ResolveNextHopEntryDependency(kP4TunnelNextHopAppDbEntry2, kTunnelOid2)); + EXPECT_CALL(mock_sai_next_hop_, create_next_hop(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kTunnelNextHopOid), Return(SAI_STATUS_SUCCESS))); + + Drain(); + + EXPECT_TRUE(ValidateNextHopEntryAdd(kP4TunnelNextHopAppDbEntry2, kTunnelNextHopOid)); + + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kTunnelNextHopId; + std::vector fvs; + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + DEL_COMMAND, fvs); + EXPECT_CALL(mock_sai_next_hop_, remove_next_hop(Eq(kTunnelNextHopOid))).WillOnce(Return(SAI_STATUS_SUCCESS)); + + Enqueue(app_db_entry); + Drain(); + + // Validate the next hop entry has been deleted in both P4 next hop manager + // and centralized mapper. + auto p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4TunnelNextHopAppDbEntry2.next_hop_id)); + EXPECT_EQ(p4_next_hop_entry, nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(kP4TunnelNextHopAppDbEntry2.next_hop_id))); + + // Validate ref count decrement. + const std::string tunnel_key = KeyGenerator::generateTunnelKey(kP4TunnelNextHopAppDbEntry2.gre_tunnel_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4TunnelEntry2.router_interface_id, kP4TunnelNextHopAppDbEntry2.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_TUNNEL, tunnel_key, 0)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 0)); +} + TEST_F(NextHopManagerTest, DrainAppEntryWithInvalidOpShouldBeNoOp) { nlohmann::json j; @@ -638,15 +849,72 @@ TEST_F(NextHopManagerTest, DrainAppEntryWithInvalidFieldShouldBeNoOp) {"unexpected_field", "unexpected_value"}}; swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), - "INVALID_OP", fvs); + SET_COMMAND, fvs); Enqueue(app_db_entry); - EXPECT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry2, kRouterInterfaceOid2)); + Drain(); + EXPECT_FALSE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry2, kNextHopOid)); + + // Missing action field + fvs = {{prependParamField(p4orch::kNeighborId), kNeighborId2}, + {prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId2}}; + app_db_entry = {std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); Drain(); + EXPECT_FALSE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry2, kNextHopOid)); + + // Missing neighbor field + fvs = {{p4orch::kAction, p4orch::kSetIpNexthop}, + {prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId2}}; + app_db_entry = {std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + Enqueue(app_db_entry); + + Drain(); + EXPECT_FALSE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry2, kNextHopOid)); + + // set_ip_nexthop + missing router_interface_id + fvs = {{p4orch::kAction, p4orch::kSetIpNexthop}, {prependParamField(p4orch::kNeighborId), kNeighborId2}}; + app_db_entry = {std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + + Drain(); EXPECT_FALSE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry2, kNextHopOid)); + + // set_ip_nexthop + invalid param/tunnel_id + fvs = {{p4orch::kAction, p4orch::kSetIpNexthop}, + {prependParamField(p4orch::kNeighborId), kNeighborId2}, + {prependParamField(p4orch::kTunnelId), kTunnelId1}}; + app_db_entry = {std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + + Drain(); + EXPECT_FALSE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry2, kNextHopOid)); + + // set_tunnel_encap_nexthop + invalid router_interface_id + fvs = {{p4orch::kAction, p4orch::kSetTunnelNexthop}, + {prependParamField(p4orch::kNeighborId), kNeighborId2}, + {prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId1}}; + app_db_entry = {std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + + Drain(); + EXPECT_FALSE(ValidateNextHopEntryAdd(kP4TunnelNextHopAppDbEntry2, kNextHopOid)); + + // set_tunnel_encap_nexthop + missing tunnel_id + fvs = {{p4orch::kAction, p4orch::kSetTunnelNexthop}, {prependParamField(p4orch::kNeighborId), kNeighborId2}}; + app_db_entry = {std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + + Drain(); + EXPECT_FALSE(ValidateNextHopEntryAdd(kP4TunnelNextHopAppDbEntry2, kNextHopOid)); } TEST_F(NextHopManagerTest, DrainUpdateRequestShouldBeUnsupported) @@ -707,3 +975,111 @@ TEST_F(NextHopManagerTest, DrainDeleteRequestShouldSucceedForExistingNextHop) EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 0)); EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 0)); } + +TEST_F(NextHopManagerTest, VerifyStateTest) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set( + "SAI_OBJECT_TYPE_NEXT_HOP:oid:0x65", + std::vector{swss::FieldValueTuple{"SAI_NEXT_HOP_ATTR_TYPE", "SAI_NEXT_HOP_TYPE_IP"}, + swss::FieldValueTuple{"SAI_NEXT_HOP_ATTR_IP", "10.0.0.1"}, + swss::FieldValueTuple{"SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID", "oid:0x1"}}); + + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_NEXTHOP_TABLE_NAME + + kTableKeyDelimiter + j.dump(); + std::vector attributes; + + // Verification should succeed with vaild key and value. + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNeighborId), kNeighborId1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId1}); + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Invalid key should fail verification. + EXPECT_FALSE(VerifyState("invalid", attributes).empty()); + EXPECT_FALSE(VerifyState("invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":FIXED_NEXTHOP_TABLE:invalid", attributes).empty()); + + // Verification should fail with non-existing nexthop. + j[prependMatchField(p4orch::kNexthopId)] = "invalid"; + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_NEXTHOP_TABLE_NAME + + kTableKeyDelimiter + j.dump(), + attributes) + .empty()); + + // Verification should fail if nexthop key mismatches. + auto saved_next_hop_key = p4_next_hop_entry->next_hop_key; + p4_next_hop_entry->next_hop_key = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_next_hop_entry->next_hop_key = saved_next_hop_key; + + // Verification should fail if nexthop ID mismatches. + auto saved_next_hop_id = p4_next_hop_entry->next_hop_id; + p4_next_hop_entry->next_hop_id = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_next_hop_entry->next_hop_id = saved_next_hop_id; + + // Verification should fail if ritf ID mismatches. + auto saved_router_interface_id = p4_next_hop_entry->router_interface_id; + p4_next_hop_entry->router_interface_id = kRouterInterfaceId2; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_next_hop_entry->router_interface_id = saved_router_interface_id; + + // Verification should fail if neighbor ID mismatches. + auto saved_neighbor_id = p4_next_hop_entry->neighbor_id; + p4_next_hop_entry->neighbor_id = swss::IpAddress(kNeighborId2); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_next_hop_entry->neighbor_id = saved_neighbor_id; + + // Verification should fail if tunnel ID mismatches. + auto saved_gre_tunnel_id = p4_next_hop_entry->gre_tunnel_id; + p4_next_hop_entry->gre_tunnel_id = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_next_hop_entry->gre_tunnel_id = saved_gre_tunnel_id; +} + +TEST_F(NextHopManagerTest, VerifyStateAsicDbTest) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set( + "SAI_OBJECT_TYPE_NEXT_HOP:oid:0x65", + std::vector{swss::FieldValueTuple{"SAI_NEXT_HOP_ATTR_TYPE", "SAI_NEXT_HOP_TYPE_IP"}, + swss::FieldValueTuple{"SAI_NEXT_HOP_ATTR_IP", "10.0.0.1"}, + swss::FieldValueTuple{"SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID", "oid:0x1"}}); + + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_NEXTHOP_TABLE_NAME + + kTableKeyDelimiter + j.dump(); + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNeighborId), kNeighborId1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId1}); + + // Verification should succeed with correct ASIC DB values. + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Verification should fail if ASIC DB values mismatch. + table.set("SAI_OBJECT_TYPE_NEXT_HOP:oid:0x65", std::vector{swss::FieldValueTuple{ + "SAI_NEXT_HOP_ATTR_IP", "fe80::21a:11ff:fe17:5f80"}}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + // Verification should fail if ASIC DB table is missing. + table.del("SAI_OBJECT_TYPE_NEXT_HOP:oid:0x65"); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + table.set( + "SAI_OBJECT_TYPE_NEXT_HOP:oid:0x65", + std::vector{swss::FieldValueTuple{"SAI_NEXT_HOP_ATTR_TYPE", "SAI_NEXT_HOP_TYPE_IP"}, + swss::FieldValueTuple{"SAI_NEXT_HOP_ATTR_IP", "10.0.0.1"}, + swss::FieldValueTuple{"SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID", "oid:0x1"}}); +} diff --git a/orchagent/p4orch/tests/p4oidmapper_test.cpp b/orchagent/p4orch/tests/p4oidmapper_test.cpp index 131ee7aedb55..bde2ee656bd0 100644 --- a/orchagent/p4orch/tests/p4oidmapper_test.cpp +++ b/orchagent/p4orch/tests/p4oidmapper_test.cpp @@ -4,9 +4,11 @@ #include +#include "sai_serialize.h" + extern "C" { -#include "saitypes.h" +#include "sai.h" } namespace @@ -19,6 +21,11 @@ constexpr char *kRouteObject2 = "Route2"; constexpr sai_object_id_t kOid1 = 1; constexpr sai_object_id_t kOid2 = 2; +std::string convertToDBField(_In_ const sai_object_type_t object_type, _In_ const std::string &key) +{ + return sai_serialize_object_type(object_type) + ":" + key; +} + TEST(P4OidMapperTest, MapperTest) { P4OidMapper mapper; @@ -41,6 +48,10 @@ TEST(P4OidMapperTest, MapperTest) EXPECT_EQ(kOid1, oid); EXPECT_TRUE(mapper.getOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, &oid)); EXPECT_EQ(kOid2, oid); + EXPECT_TRUE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid1).empty()); + EXPECT_TRUE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, kOid2).empty()); + EXPECT_FALSE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid2).empty()); + EXPECT_FALSE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, kOid1).empty()); uint32_t ref_count; EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, &ref_count)); @@ -74,6 +85,8 @@ TEST(P4OidMapperTest, MapperTest) EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2)); EXPECT_FALSE(mapper.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); EXPECT_FALSE(mapper.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + EXPECT_FALSE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid1).empty()); + EXPECT_FALSE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, kOid2).empty()); } TEST(P4OidMapperTest, ErrorTest) @@ -119,4 +132,27 @@ TEST(P4OidMapperTest, ErrorTest) EXPECT_FALSE(mapper.decreaseRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); } +TEST(P4OidMapperTest, VerifyMapperTest) +{ + P4OidMapper mapper; + swss::Table table(nullptr, "P4RT_KEY_TO_OID"); + EXPECT_TRUE(mapper.setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid1)); + EXPECT_TRUE(mapper.setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, kOid2, + /*ref_count=*/100)); + + EXPECT_TRUE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid1).empty()); + EXPECT_TRUE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, kOid2).empty()); + EXPECT_FALSE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid2).empty()); + EXPECT_FALSE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, kOid1).empty()); + EXPECT_FALSE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, "invalid", kOid1).empty()); + + // Verification should fail if OID in DB mismatches. + table.hset("", convertToDBField(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1), sai_serialize_object_id(kOid2)); + EXPECT_FALSE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid1).empty()); + + // Verification should fail if OID in DB is not found. + table.hdel("", convertToDBField(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + EXPECT_FALSE(mapper.verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid1).empty()); +} + } // namespace diff --git a/orchagent/p4orch/tests/p4orch_util_test.cpp b/orchagent/p4orch/tests/p4orch_util_test.cpp index 8c171b1e6e4b..ba86624d4c44 100644 --- a/orchagent/p4orch/tests/p4orch_util_test.cpp +++ b/orchagent/p4orch/tests/p4orch_util_test.cpp @@ -77,4 +77,57 @@ TEST(P4OrchUtilTest, QuotedVarTest) EXPECT_EQ(QuotedVar(bar.c_str()), "'a string has \\\'quote\\\''"); } +TEST(P4OrchUtilTest, VerifyAttrsTest) +{ + EXPECT_TRUE(verifyAttrs(std::vector{swss::FieldValueTuple{"k1", "v1"}}, + std::vector{swss::FieldValueTuple{"k1", "v1"}}, + std::vector{}, + /*allow_unknown=*/false) + .empty()); + EXPECT_FALSE(verifyAttrs(std::vector{swss::FieldValueTuple{"k1", "v1"}, + swss::FieldValueTuple{"k2", "v2"}}, + std::vector{swss::FieldValueTuple{"k1", "v1"}}, + std::vector{}, + /*allow_unknown=*/false) + .empty()); + EXPECT_TRUE(verifyAttrs(std::vector{swss::FieldValueTuple{"k1", "v1"}, + swss::FieldValueTuple{"k2", "v2"}}, + std::vector{swss::FieldValueTuple{"k1", "v1"}}, + std::vector{}, + /*allow_unknown=*/true) + .empty()); + EXPECT_TRUE(verifyAttrs(std::vector{swss::FieldValueTuple{"k1", "v1"}, + swss::FieldValueTuple{"k2", "v2"}}, + std::vector{swss::FieldValueTuple{"k1", "v1"}}, + std::vector{swss::FieldValueTuple{"k2", "v2"}}, + /*allow_unknown=*/false) + .empty()); + EXPECT_FALSE(verifyAttrs(std::vector{swss::FieldValueTuple{"k1", "v1"}, + swss::FieldValueTuple{"k2", "v2"}}, + std::vector{swss::FieldValueTuple{"k1", "v3"}}, + std::vector{swss::FieldValueTuple{"k2", "v2"}}, + /*allow_unknown=*/false) + .empty()); + EXPECT_FALSE(verifyAttrs(std::vector{swss::FieldValueTuple{"k1", "v1"}, + swss::FieldValueTuple{"k2", "v2"}}, + std::vector{swss::FieldValueTuple{"k1", "v1"}}, + std::vector{swss::FieldValueTuple{"k2", "v3"}}, + /*allow_unknown=*/false) + .empty()); + EXPECT_FALSE( + verifyAttrs( + std::vector{swss::FieldValueTuple{"k1", "v1"}, swss::FieldValueTuple{"k2", "v2"}}, + std::vector{swss::FieldValueTuple{"k1", "v1"}, swss::FieldValueTuple{"k3", "v3"}}, + std::vector{swss::FieldValueTuple{"k2", "v2"}}, + /*allow_unknown=*/true) + .empty()); + EXPECT_TRUE( + verifyAttrs( + std::vector{swss::FieldValueTuple{"k1", "v1"}, swss::FieldValueTuple{"k2", "v2"}}, + std::vector{swss::FieldValueTuple{"k1", "v1"}}, + std::vector{swss::FieldValueTuple{"k2", "v2"}, swss::FieldValueTuple{"k3", "v3"}}, + /*allow_unknown=*/false) + .empty()); +} + } // namespace diff --git a/orchagent/p4orch/tests/return_code_test.cpp b/orchagent/p4orch/tests/return_code_test.cpp index 7a866827d7ce..7ab21121aa41 100644 --- a/orchagent/p4orch/tests/return_code_test.cpp +++ b/orchagent/p4orch/tests/return_code_test.cpp @@ -4,6 +4,7 @@ #include #include +#include extern "C" { @@ -105,6 +106,43 @@ TEST(ReturnCodeTest, CopyAndAppendStringInMsg) EXPECT_EQ("SWSS_RC_INVALID_PARAM:Detailed reasons. More details.", return_code.toString()); } +TEST(ReturnCodeTest, SaiCodeToReturnCodeMapping) +{ + std::unordered_map expect_mapping = { + {SAI_STATUS_SUCCESS, StatusCode::SWSS_RC_SUCCESS}, + {SAI_STATUS_NOT_SUPPORTED, StatusCode::SWSS_RC_UNIMPLEMENTED}, + {SAI_STATUS_NO_MEMORY, StatusCode::SWSS_RC_NO_MEMORY}, + {SAI_STATUS_INSUFFICIENT_RESOURCES, StatusCode::SWSS_RC_FULL}, + {SAI_STATUS_INVALID_PARAMETER, StatusCode::SWSS_RC_INVALID_PARAM}, + {SAI_STATUS_ITEM_ALREADY_EXISTS, StatusCode::SWSS_RC_EXISTS}, + {SAI_STATUS_ITEM_NOT_FOUND, StatusCode::SWSS_RC_NOT_FOUND}, + {SAI_STATUS_TABLE_FULL, StatusCode::SWSS_RC_FULL}, + {SAI_STATUS_NOT_IMPLEMENTED, StatusCode::SWSS_RC_UNIMPLEMENTED}, + {SAI_STATUS_OBJECT_IN_USE, StatusCode::SWSS_RC_IN_USE}, + {SAI_STATUS_FAILURE, StatusCode::SWSS_RC_UNKNOWN}, + {SAI_STATUS_INVALID_ATTRIBUTE_0, StatusCode::SWSS_RC_INVALID_PARAM}, + {SAI_STATUS_INVALID_ATTRIBUTE_10, StatusCode::SWSS_RC_INVALID_PARAM}, + {SAI_STATUS_INVALID_ATTRIBUTE_MAX, StatusCode::SWSS_RC_INVALID_PARAM}, + {SAI_STATUS_INVALID_ATTR_VALUE_0, StatusCode::SWSS_RC_INVALID_PARAM}, + {SAI_STATUS_INVALID_ATTR_VALUE_10, StatusCode::SWSS_RC_INVALID_PARAM}, + {SAI_STATUS_INVALID_ATTR_VALUE_MAX, StatusCode::SWSS_RC_INVALID_PARAM}, + {SAI_STATUS_ATTR_NOT_IMPLEMENTED_0, StatusCode::SWSS_RC_UNIMPLEMENTED}, + {SAI_STATUS_ATTR_NOT_IMPLEMENTED_10, StatusCode::SWSS_RC_UNIMPLEMENTED}, + {SAI_STATUS_ATTR_NOT_IMPLEMENTED_MAX, StatusCode::SWSS_RC_UNIMPLEMENTED}, + {SAI_STATUS_UNKNOWN_ATTRIBUTE_0, StatusCode::SWSS_RC_INVALID_PARAM}, + {SAI_STATUS_UNKNOWN_ATTRIBUTE_10, StatusCode::SWSS_RC_INVALID_PARAM}, + {SAI_STATUS_UNKNOWN_ATTRIBUTE_MAX, StatusCode::SWSS_RC_INVALID_PARAM}, + {SAI_STATUS_ATTR_NOT_SUPPORTED_0, StatusCode::SWSS_RC_UNIMPLEMENTED}, + {SAI_STATUS_ATTR_NOT_SUPPORTED_10, StatusCode::SWSS_RC_UNIMPLEMENTED}, + {SAI_STATUS_ATTR_NOT_SUPPORTED_MAX, StatusCode::SWSS_RC_UNIMPLEMENTED}, + }; + for (const auto &it : expect_mapping) + { + ReturnCode return_code(it.first); + EXPECT_EQ(return_code, it.second); + } +} + TEST(ReturnCodeTest, ReturnCodeOrHasInt) { ReturnCodeOr return_code_or = 42; diff --git a/orchagent/p4orch/tests/route_manager_test.cpp b/orchagent/p4orch/tests/route_manager_test.cpp index de1238761b5a..06095d5ebed7 100644 --- a/orchagent/p4orch/tests/route_manager_test.cpp +++ b/orchagent/p4orch/tests/route_manager_test.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "ipprefix.h" #include "json.hpp" @@ -23,14 +24,14 @@ using ::testing::_; using ::testing::DoAll; using ::testing::Eq; using ::testing::Return; -using ::testing::SetArgPointee; +using ::testing::SetArrayArgument; using ::testing::StrictMock; -using ::testing::Truly; extern sai_object_id_t gSwitchId; extern sai_object_id_t gVirtualRouterId; extern sai_object_id_t gVrfOid; extern char *gVrfName; +extern size_t gMaxBulkSize; extern sai_route_api_t *sai_route_api; extern VRFOrch *gVrfOrch; @@ -38,6 +39,7 @@ namespace { constexpr char *kIpv4Prefix = "10.11.12.0/24"; +constexpr char *kIpv4Prefix2 = "10.12.12.0/24"; constexpr char *kIpv6Prefix = "2001:db8:1::/32"; constexpr char *kNexthopId1 = "ju1u32m1.atl11:qe-3/7"; constexpr sai_object_id_t kNexthopOid1 = 1; @@ -47,6 +49,10 @@ constexpr char *kWcmpGroup1 = "wcmp-group-1"; constexpr sai_object_id_t kWcmpGroupOid1 = 3; constexpr char *kWcmpGroup2 = "wcmp-group-2"; constexpr sai_object_id_t kWcmpGroupOid2 = 4; +constexpr char *kMetadata1 = "1"; +constexpr char *kMetadata2 = "2"; +uint32_t kMetadataInt1 = 1; +uint32_t kMetadataInt2 = 2; // Returns true if the two prefixes are equal. False otherwise. // Arguments must be non-nullptr. @@ -65,61 +71,114 @@ bool PrefixCmp(const sai_ip_prefix_t *x, const sai_ip_prefix_t *y) memcmp(&x->mask.ip6, &y->mask.ip6, sizeof(sai_ip6_t)) == 0; } -// Matches the sai_route_entry_t argument. -bool MatchSaiRouteEntry(const sai_ip_prefix_t &expected_prefix, const sai_route_entry_t *route_entry, - const sai_object_id_t expected_vrf_oid) +// Matches two SAI route entries. +bool MatchSaiRouteEntry(const sai_route_entry_t &route_entry, const sai_route_entry_t &exp_route_entry) { - if (route_entry == nullptr) + if (route_entry.switch_id != exp_route_entry.switch_id) { return false; } - if (route_entry->vr_id != expected_vrf_oid) + if (route_entry.vr_id != exp_route_entry.vr_id) { return false; } - if (route_entry->switch_id != gSwitchId) - { - return false; - } - if (!PrefixCmp(&route_entry->destination, &expected_prefix)) + if (!PrefixCmp(&route_entry.destination, &exp_route_entry.destination)) { return false; } return true; } -// Matches the action type sai_attribute_t argument. -bool MatchSaiAttributeAction(sai_packet_action_t expected_action, const sai_attribute_t *attr) +// Matches two SAI attributes. +bool MatchSaiAttribute(const sai_attribute_t &attr, const sai_attribute_t &exp_attr) { - if (attr == nullptr) + if (exp_attr.id == SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION) { - return false; + if (attr.id != SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION || attr.value.s32 != exp_attr.value.s32) + { + return false; + } } - if (attr->id != SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION) + if (exp_attr.id == SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID) { - return false; + if (attr.id != SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID || attr.value.oid != exp_attr.value.oid) + { + return false; + } } - if (attr->value.s32 != expected_action) + if (exp_attr.id == SAI_ROUTE_ENTRY_ATTR_META_DATA) { - return false; + if (attr.id != SAI_ROUTE_ENTRY_ATTR_META_DATA || attr.value.u32 != exp_attr.value.u32) + { + return false; + } } return true; } -// Matches the nexthop ID type sai_attribute_t argument. -bool MatchSaiAttributeNexthopId(sai_object_id_t expected_oid, const sai_attribute_t *attr) +MATCHER_P(ArrayEq, array, "") { - if (attr == nullptr) + for (size_t i = 0; i < array.size(); ++i) { - return false; + if (arg[i] != array[i]) + { + return false; + } } - if (attr->id != SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID) + return true; +} + +MATCHER_P(RouteEntryArrayEq, array, "") +{ + for (size_t i = 0; i < array.size(); ++i) { - return false; + if (!MatchSaiRouteEntry(arg[i], array[i])) + { + return false; + } + } + return true; +} + +MATCHER_P(AttrArrayEq, array, "") +{ + for (size_t i = 0; i < array.size(); ++i) + { + if (!MatchSaiAttribute(arg[i], array[i])) + { + return false; + } } - if (attr->value.oid != expected_oid) + return true; +} + +MATCHER_P(AttrArrayArrayEq, array, "") +{ + for (size_t i = 0; i < array.size(); ++i) { - return false; + for (size_t j = 0; j < array[i].size(); j++) + { + if (!MatchSaiAttribute(arg[i][j], array[i][j])) + { + return false; + } + } + } + return true; +} + +MATCHER_P(FieldValueTupleArrayEq, array, "") +{ + for (size_t i = 0; i < array.size(); ++i) + { + if (fvField(arg[i]) != fvField(array[i])) + { + return false; + } + if (fvValue(arg[i]) != fvValue(array[i])) + { + return false; + } } return true; } @@ -163,78 +222,162 @@ class RouteManagerTest : public ::testing::Test return route_manager_.getRouteEntry(route_entry_key); } - ReturnCode ValidateRouteEntry(const P4RouteEntry &route_entry) + ReturnCode ValidateRouteEntry(const P4RouteEntry &route_entry, const std::string &operation) { - return route_manager_.validateRouteEntry(route_entry); + return route_manager_.validateRouteEntry(route_entry, operation); } - ReturnCode ValidateSetRouteEntry(const P4RouteEntry &route_entry) + std::vector CreateRouteEntries(const std::vector &route_entries) { - return route_manager_.validateSetRouteEntry(route_entry); + return route_manager_.createRouteEntries(route_entries); } - ReturnCode ValidateDelRouteEntry(const P4RouteEntry &route_entry) + std::vector UpdateRouteEntries(const std::vector &route_entries) { - return route_manager_.validateDelRouteEntry(route_entry); + return route_manager_.updateRouteEntries(route_entries); } - ReturnCode CreateRouteEntry(const P4RouteEntry &route_entry) + std::vector DeleteRouteEntries(const std::vector &route_entries) { - return route_manager_.createRouteEntry(route_entry); + return route_manager_.deleteRouteEntries(route_entries); } - ReturnCode UpdateRouteEntry(const P4RouteEntry &route_entry) + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) { - return route_manager_.updateRouteEntry(route_entry); + route_manager_.enqueue(entry); } - ReturnCode DeleteRouteEntry(const P4RouteEntry &route_entry) + void Drain() { - return route_manager_.deleteRouteEntry(route_entry); + route_manager_.drain(); } - void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + std::string VerifyState(const std::string &key, const std::vector &tuple) { - route_manager_.enqueue(entry); + return route_manager_.verifyState(key, tuple); } - void Drain() + // Generates a KeyOpFieldsValuesTuple. + swss::KeyOpFieldsValuesTuple GenerateKeyOpFieldsValuesTuple(const std::string &vrf_id, + const swss::IpPrefix &route_prefix, + const std::string &command, const std::string &action, + const std::string &action_param, + const std::string &route_metadata = "") { - route_manager_.drain(); + nlohmann::json j; + std::string key_prefix; + j[prependMatchField(p4orch::kVrfId)] = vrf_id; + if (route_prefix.isV4()) + { + j[prependMatchField(p4orch::kIpv4Dst)] = route_prefix.to_string(); + key_prefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; + } + else + { + j[prependMatchField(p4orch::kIpv6Dst)] = route_prefix.to_string(); + key_prefix = std::string(APP_P4RT_IPV6_TABLE_NAME) + kTableKeyDelimiter; + } + std::vector attributes; + if (command == SET_COMMAND) + { + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, action}); + if (action == p4orch::kSetNexthopId || p4orch::kSetNexthopIdAndMetadata) + { + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), action_param}); + } + else if (action == p4orch::kSetWcmpGroupId || action == p4orch::kSetWcmpGroupIdAndMetadata) + { + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kWcmpGroupId), action_param}); + } + if (action == p4orch::kSetNexthopIdAndMetadata || action == p4orch::kSetWcmpGroupIdAndMetadata || + action == p4orch::kSetMetadataAndDrop) + { + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kRouteMetadata), route_metadata}); + } + } + return swss::KeyOpFieldsValuesTuple(key_prefix + j.dump(), command, attributes); } - // Sets up a nexthop route entry for test. - void SetupNexthopIdRouteEntry(const std::string &vrf_id, const swss::IpPrefix &route_prefix, - const std::string &nexthop_id, sai_object_id_t nexthop_oid) + // Generates a P4RouteEntry. + P4RouteEntry GenerateP4RouteEntry(const std::string &vrf_id, const swss::IpPrefix &route_prefix, + const std::string &action, const std::string &action_param, + const std::string &route_metadata = "") { P4RouteEntry route_entry = {}; route_entry.vrf_id = vrf_id; route_entry.route_prefix = route_prefix; - route_entry.action = p4orch::kSetNexthopId; - route_entry.nexthop_id = nexthop_id; + route_entry.route_metadata = route_metadata; + route_entry.action = action; + if (action == p4orch::kSetNexthopId || action == p4orch::kSetNexthopIdAndMetadata) + { + route_entry.nexthop_id = action_param; + } + else if (action == p4orch::kSetWcmpGroupId || action == p4orch::kSetWcmpGroupIdAndMetadata) + { + route_entry.wcmp_group = action_param; + } route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + return route_entry; + } + + // Sets up a nexthop route entry for test. + void SetupNexthopIdRouteEntry(const std::string &vrf_id, const swss::IpPrefix &route_prefix, + const std::string &nexthop_id, sai_object_id_t nexthop_oid, + const std::string &metadata = "") + { + auto route_entry = GenerateP4RouteEntry( + vrf_id, route_prefix, (metadata.empty()) ? p4orch::kSetNexthopId : p4orch::kSetNexthopIdAndMetadata, + nexthop_id, metadata); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), nexthop_oid); - EXPECT_CALL(mock_sai_route_, create_route_entry(_, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, create_route_entries(_, _, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); } // Sets up a wcmp route entry for test. void SetupWcmpGroupRouteEntry(const std::string &vrf_id, const swss::IpPrefix &route_prefix, - const std::string &wcmp_group_id, sai_object_id_t wcmp_group_oid) + const std::string &wcmp_group_id, sai_object_id_t wcmp_group_oid, + const std::string &metadata = "") { - P4RouteEntry route_entry = {}; - route_entry.vrf_id = vrf_id; - route_entry.route_prefix = route_prefix; - route_entry.action = p4orch::kSetWcmpGroupId; - route_entry.wcmp_group = wcmp_group_id; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry( + vrf_id, route_prefix, (metadata.empty()) ? p4orch::kSetWcmpGroupId : p4orch::kSetWcmpGroupIdAndMetadata, + wcmp_group_id, metadata); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), wcmp_group_oid); - EXPECT_CALL(mock_sai_route_, create_route_entry(_, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, create_route_entries(_, _, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + } + + // Sets up a drop route entry for test. + void SetupDropRouteEntry(const std::string &vrf_id, const swss::IpPrefix &route_prefix) + { + auto route_entry = GenerateP4RouteEntry(vrf_id, route_prefix, p4orch::kDrop, ""); + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, create_route_entries(_, _, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + } + + // Sets up a trap route entry for test. + void SetupTrapRouteEntry(const std::string &vrf_id, const swss::IpPrefix &route_prefix) + { + auto route_entry = GenerateP4RouteEntry(vrf_id, route_prefix, p4orch::kTrap, ""); + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, create_route_entries(_, _, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); } // Verifies the two given route entries are identical. @@ -246,6 +389,7 @@ class RouteManagerTest : public ::testing::Test EXPECT_EQ(x.action, y.action); EXPECT_EQ(x.nexthop_id, y.nexthop_id); EXPECT_EQ(x.wcmp_group, y.wcmp_group); + EXPECT_EQ(x.route_metadata, y.route_metadata); EXPECT_EQ(x.sai_route_entry.vr_id, y.sai_route_entry.vr_id); EXPECT_EQ(x.sai_route_entry.switch_id, y.sai_route_entry.switch_id); EXPECT_TRUE(PrefixCmp(&x.sai_route_entry.destination, &y.sai_route_entry.destination)); @@ -265,7 +409,7 @@ class RouteManagerTest : public ::testing::Test } StrictMock mock_sai_route_; - MockResponsePublisher publisher_; + StrictMock publisher_; P4OidMapper p4_oid_mapper_; RouteManager route_manager_; }; @@ -273,45 +417,36 @@ class RouteManagerTest : public ::testing::Test TEST_F(RouteManagerTest, MergeRouteEntryWithNexthopIdActionDestTest) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry dest = {}; - dest.vrf_id = gVrfName; - dest.route_prefix = swss_ipv4_route_prefix; - dest.action = p4orch::kSetNexthopId; - dest.nexthop_id = kNexthopId1; - dest.route_entry_key = KeyGenerator::generateRouteKey(dest.vrf_id, dest.route_prefix); + auto dest = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); dest.sai_route_entry.vr_id = gVrfOid; dest.sai_route_entry.switch_id = gSwitchId; copy(dest.sai_route_entry.destination, swss_ipv4_route_prefix); // Source is identical to destination. - P4RouteEntry src = {}; - src.vrf_id = gVrfName; - src.route_prefix = swss_ipv4_route_prefix; - src.action = p4orch::kSetNexthopId; - src.nexthop_id = kNexthopId1; - src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + auto src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); P4RouteEntry ret = {}; EXPECT_FALSE(MergeRouteEntry(dest, src, &ret)); VerifyRouteEntriesEq(dest, ret); // Source has different nexthop ID. - src = {}; - src.vrf_id = gVrfName; - src.route_prefix = swss_ipv4_route_prefix; - src.nexthop_id = kNexthopId2; - src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId2); EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); P4RouteEntry expect_entry = dest; expect_entry.nexthop_id = kNexthopId2; VerifyRouteEntriesEq(expect_entry, ret); + // Source has set nexthop ID and metadata action and dest has set nexthop ID + // action. + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopIdAndMetadata, kNexthopId1, + kMetadata1); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.action = p4orch::kSetNexthopIdAndMetadata; + expect_entry.route_metadata = kMetadata1; + VerifyRouteEntriesEq(expect_entry, ret); + // Source has wcmp group action and dest has nexhop ID action. - src = {}; - src.vrf_id = gVrfName; - src.route_prefix = swss_ipv4_route_prefix; - src.action = p4orch::kSetWcmpGroupId; - src.wcmp_group = kWcmpGroup1; - src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); expect_entry = dest; expect_entry.nexthop_id = ""; @@ -320,60 +455,101 @@ TEST_F(RouteManagerTest, MergeRouteEntryWithNexthopIdActionDestTest) VerifyRouteEntriesEq(expect_entry, ret); // Source has drop action and dest has nexhop ID action. - src = {}; - src.vrf_id = gVrfName; - src.route_prefix = swss_ipv4_route_prefix; - src.action = p4orch::kDrop; - src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.nexthop_id = ""; + expect_entry.action = p4orch::kDrop; + VerifyRouteEntriesEq(expect_entry, ret); +} + +TEST_F(RouteManagerTest, MergeRouteEntryWithNexthopIdAndMetadataActionDestTest) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + auto dest = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopIdAndMetadata, kNexthopId1, + kMetadata1); + dest.sai_route_entry.vr_id = gVrfOid; + dest.sai_route_entry.switch_id = gSwitchId; + copy(dest.sai_route_entry.destination, swss_ipv4_route_prefix); + + // Source is identical to destination. + auto src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopIdAndMetadata, kNexthopId1, + kMetadata1); + P4RouteEntry ret = {}; + EXPECT_FALSE(MergeRouteEntry(dest, src, &ret)); + VerifyRouteEntriesEq(dest, ret); + + // Source has different metadata. + src.route_metadata = kMetadata2; + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + P4RouteEntry expect_entry = dest; + expect_entry.route_metadata = kMetadata2; + VerifyRouteEntriesEq(expect_entry, ret); + + // Source has different nexthop ID and metadata. + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopIdAndMetadata, kNexthopId2, + kMetadata2); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.nexthop_id = kNexthopId2; + expect_entry.route_metadata = kMetadata2; + VerifyRouteEntriesEq(expect_entry, ret); + + // Source has wcmp group action and dest has nexhop ID and metadata action. + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.nexthop_id = ""; + expect_entry.action = p4orch::kSetWcmpGroupId; + expect_entry.wcmp_group = kWcmpGroup1; + expect_entry.route_metadata = ""; + VerifyRouteEntriesEq(expect_entry, ret); + + // Source has drop action and dest has nexhop ID and metadata action. + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); expect_entry = dest; expect_entry.nexthop_id = ""; expect_entry.action = p4orch::kDrop; + expect_entry.route_metadata = ""; + VerifyRouteEntriesEq(expect_entry, ret); + + // Source has wcmp group and metadata action and dest has nexhop ID and + // metadata action. + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupIdAndMetadata, kWcmpGroup1, + kMetadata2); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.nexthop_id = ""; + expect_entry.action = p4orch::kSetWcmpGroupIdAndMetadata; + expect_entry.wcmp_group = kWcmpGroup1; + expect_entry.route_metadata = kMetadata2; VerifyRouteEntriesEq(expect_entry, ret); } TEST_F(RouteManagerTest, MergeRouteEntryWithWcmpGroupActionDestTest) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry dest = {}; - dest.vrf_id = gVrfName; - dest.route_prefix = swss_ipv4_route_prefix; - dest.action = p4orch::kSetWcmpGroupId; - dest.wcmp_group = kWcmpGroup1; - dest.route_entry_key = KeyGenerator::generateRouteKey(dest.vrf_id, dest.route_prefix); + auto dest = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); dest.sai_route_entry.vr_id = gVrfOid; dest.sai_route_entry.switch_id = gSwitchId; copy(dest.sai_route_entry.destination, swss_ipv4_route_prefix); // Source is identical to destination. - P4RouteEntry src = {}; - src.vrf_id = gVrfName; - src.route_prefix = swss_ipv4_route_prefix; - src.action = p4orch::kSetWcmpGroupId; - src.wcmp_group = kWcmpGroup1; - src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + auto src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); P4RouteEntry ret = {}; EXPECT_FALSE(MergeRouteEntry(dest, src, &ret)); VerifyRouteEntriesEq(dest, ret); // Source has different wcmp group. - src = {}; - src.vrf_id = gVrfName; - src.route_prefix = swss_ipv4_route_prefix; - src.wcmp_group = kWcmpGroup2; - src.route_entry_key = KeyGenerator::generateRouteKey(dest.vrf_id, dest.route_prefix); + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup2); EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); P4RouteEntry expect_entry = dest; expect_entry.wcmp_group = kWcmpGroup2; VerifyRouteEntriesEq(expect_entry, ret); // Source has nexthop ID action and dest has wcmp group action. - src = {}; - src.vrf_id = gVrfName; - src.route_prefix = swss_ipv4_route_prefix; - src.action = p4orch::kSetNexthopId; - src.nexthop_id = kNexthopId1; - src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); expect_entry = dest; expect_entry.wcmp_group = ""; @@ -382,11 +558,7 @@ TEST_F(RouteManagerTest, MergeRouteEntryWithWcmpGroupActionDestTest) VerifyRouteEntriesEq(expect_entry, ret); // Source has drop action and dest has wcmp group action. - src = {}; - src.vrf_id = gVrfName; - src.route_prefix = swss_ipv4_route_prefix; - src.action = p4orch::kDrop; - src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); expect_entry = dest; expect_entry.wcmp_group = ""; @@ -397,45 +569,61 @@ TEST_F(RouteManagerTest, MergeRouteEntryWithWcmpGroupActionDestTest) TEST_F(RouteManagerTest, MergeRouteEntryWithDropActionDestTest) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry dest = {}; - dest.vrf_id = gVrfName; - dest.route_prefix = swss_ipv4_route_prefix; - dest.action = p4orch::kDrop; - dest.route_entry_key = KeyGenerator::generateRouteKey(dest.vrf_id, dest.route_prefix); + auto dest = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); dest.sai_route_entry.vr_id = gVrfOid; dest.sai_route_entry.switch_id = gSwitchId; copy(dest.sai_route_entry.destination, swss_ipv4_route_prefix); // Source is identical to destination. - P4RouteEntry src = {}; - src.vrf_id = gVrfName; - src.route_prefix = swss_ipv4_route_prefix; - src.action = p4orch::kDrop; - src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + auto src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); P4RouteEntry ret = {}; EXPECT_FALSE(MergeRouteEntry(dest, src, &ret)); VerifyRouteEntriesEq(dest, ret); // Source has nexthop ID action and dest has drop action. - src = {}; - src.vrf_id = gVrfName; - src.route_prefix = swss_ipv4_route_prefix; - src.action = p4orch::kSetNexthopId; - src.nexthop_id = kNexthopId1; - src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + P4RouteEntry expect_entry = dest; + expect_entry.action = p4orch::kSetNexthopId; + expect_entry.nexthop_id = kNexthopId1; + VerifyRouteEntriesEq(expect_entry, ret); + + // Source has wcmp group and metadata action and dest has drop action. + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupIdAndMetadata, kWcmpGroup1, + kMetadata1); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.action = p4orch::kSetWcmpGroupIdAndMetadata; + expect_entry.nexthop_id = ""; + expect_entry.wcmp_group = kWcmpGroup1; + expect_entry.route_metadata = kMetadata1; + VerifyRouteEntriesEq(expect_entry, ret); +} + +TEST_F(RouteManagerTest, MergeRouteEntryWithTrapActionDestTest) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + auto dest = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kTrap, ""); + dest.sai_route_entry.vr_id = gVrfOid; + dest.sai_route_entry.switch_id = gSwitchId; + copy(dest.sai_route_entry.destination, swss_ipv4_route_prefix); + + // Source is identical to destination. + auto src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kTrap, ""); + P4RouteEntry ret = {}; + EXPECT_FALSE(MergeRouteEntry(dest, src, &ret)); + VerifyRouteEntriesEq(dest, ret); + + // Source has nexthop ID action and dest has trap action. + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); P4RouteEntry expect_entry = dest; expect_entry.action = p4orch::kSetNexthopId; expect_entry.nexthop_id = kNexthopId1; VerifyRouteEntriesEq(expect_entry, ret); - // Source has wcmp group action and dest has drop action. - src = {}; - src.vrf_id = gVrfName; - src.route_prefix = swss_ipv4_route_prefix; - src.action = p4orch::kSetWcmpGroupId; - src.wcmp_group = kWcmpGroup1; - src.route_entry_key = KeyGenerator::generateRouteKey(src.vrf_id, src.route_prefix); + // Source has wcmp group action and dest has trap action. + src = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); expect_entry = dest; expect_entry.action = p4orch::kSetWcmpGroupId; @@ -452,12 +640,8 @@ TEST_F(RouteManagerTest, DeserializeRouteEntryWithNexthopIdActionTest) auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV4_TABLE_NAME); EXPECT_TRUE(route_entry_or.ok()); auto &route_entry = *route_entry_or; - P4RouteEntry expect_entry = {}; - expect_entry.vrf_id = "b4-traffic"; - expect_entry.route_prefix = swss::IpPrefix("10.11.12.0/24"); - expect_entry.action = p4orch::kSetNexthopId; - expect_entry.nexthop_id = kNexthopId1; - expect_entry.route_entry_key = KeyGenerator::generateRouteKey(expect_entry.vrf_id, expect_entry.route_prefix); + auto expect_entry = + GenerateP4RouteEntry("b4-traffic", swss::IpPrefix("10.11.12.0/24"), p4orch::kSetNexthopId, kNexthopId1); VerifyRouteEntriesEq(expect_entry, route_entry); } @@ -470,12 +654,38 @@ TEST_F(RouteManagerTest, DeserializeRouteEntryWithWcmpGroupActionTest) auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV4_TABLE_NAME); EXPECT_TRUE(route_entry_or.ok()); auto &route_entry = *route_entry_or; - P4RouteEntry expect_entry = {}; - expect_entry.vrf_id = "b4-traffic"; - expect_entry.route_prefix = swss::IpPrefix("10.11.12.0/24"); - expect_entry.action = p4orch::kSetWcmpGroupId; - expect_entry.wcmp_group = kWcmpGroup1; - expect_entry.route_entry_key = KeyGenerator::generateRouteKey(expect_entry.vrf_id, expect_entry.route_prefix); + auto expect_entry = + GenerateP4RouteEntry("b4-traffic", swss::IpPrefix("10.11.12.0/24"), p4orch::kSetWcmpGroupId, kWcmpGroup1); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithNexthopIdAdnMetadataActionTest) +{ + std::string key = R"({"match/vrf_id":"b4-traffic","match/ipv4_dst":"10.11.12.0/24"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopIdAndMetadata}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kRouteMetadata), kMetadata1}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV4_TABLE_NAME); + EXPECT_TRUE(route_entry_or.ok()); + auto &route_entry = *route_entry_or; + auto expect_entry = GenerateP4RouteEntry("b4-traffic", swss::IpPrefix("10.11.12.0/24"), + p4orch::kSetNexthopIdAndMetadata, kNexthopId1, kMetadata1); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithWcmpGroupAndMetadataActionTest) +{ + std::string key = R"({"match/vrf_id":"b4-traffic","match/ipv4_dst":"10.11.12.0/24"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetWcmpGroupIdAndMetadata}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kWcmpGroupId), kWcmpGroup1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kRouteMetadata), kMetadata1}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV4_TABLE_NAME); + EXPECT_TRUE(route_entry_or.ok()); + auto &route_entry = *route_entry_or; + auto expect_entry = GenerateP4RouteEntry("b4-traffic", swss::IpPrefix("10.11.12.0/24"), + p4orch::kSetWcmpGroupIdAndMetadata, kWcmpGroup1, kMetadata1); VerifyRouteEntriesEq(expect_entry, route_entry); } @@ -487,11 +697,19 @@ TEST_F(RouteManagerTest, DeserializeRouteEntryWithDropActionTest) auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV6_TABLE_NAME); EXPECT_TRUE(route_entry_or.ok()); auto &route_entry = *route_entry_or; - P4RouteEntry expect_entry = {}; - expect_entry.vrf_id = "b4-traffic"; - expect_entry.route_prefix = swss::IpPrefix("2001:db8:1::/32"); - expect_entry.action = p4orch::kDrop; - expect_entry.route_entry_key = KeyGenerator::generateRouteKey(expect_entry.vrf_id, expect_entry.route_prefix); + auto expect_entry = GenerateP4RouteEntry("b4-traffic", swss::IpPrefix("2001:db8:1::/32"), p4orch::kDrop, ""); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithTrapActionTest) +{ + std::string key = R"({"match/vrf_id":"b4-traffic","match/ipv6_dst":"2001:db8:1::/32"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kTrap}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV6_TABLE_NAME); + EXPECT_TRUE(route_entry_or.ok()); + auto &route_entry = *route_entry_or; + auto expect_entry = GenerateP4RouteEntry("b4-traffic", swss::IpPrefix("2001:db8:1::/32"), p4orch::kTrap, ""); VerifyRouteEntriesEq(expect_entry, route_entry); } @@ -530,11 +748,7 @@ TEST_F(RouteManagerTest, DeserializeRouteEntryWithoutIpv4WildcardLpmMatchShouldS auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV4_TABLE_NAME); EXPECT_TRUE(route_entry_or.ok()); auto &route_entry = *route_entry_or; - P4RouteEntry expect_entry = {}; - expect_entry.vrf_id = "b4-traffic"; - expect_entry.route_prefix = swss::IpPrefix("0.0.0.0/0"); - expect_entry.action = p4orch::kDrop; - expect_entry.route_entry_key = KeyGenerator::generateRouteKey(expect_entry.vrf_id, expect_entry.route_prefix); + auto expect_entry = GenerateP4RouteEntry("b4-traffic", swss::IpPrefix("0.0.0.0/0"), p4orch::kDrop, ""); VerifyRouteEntriesEq(expect_entry, route_entry); } @@ -546,279 +760,293 @@ TEST_F(RouteManagerTest, DeserializeRouteEntryWithoutIpv6WildcardLpmMatchShouldS auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV6_TABLE_NAME); EXPECT_TRUE(route_entry_or.ok()); auto &route_entry = *route_entry_or; - P4RouteEntry expect_entry = {}; - expect_entry.vrf_id = "b4-traffic"; - expect_entry.route_prefix = swss::IpPrefix("::/0"); - expect_entry.action = p4orch::kDrop; - expect_entry.route_entry_key = KeyGenerator::generateRouteKey(expect_entry.vrf_id, expect_entry.route_prefix); + auto expect_entry = GenerateP4RouteEntry("b4-traffic", swss::IpPrefix("::/0"), p4orch::kDrop, ""); VerifyRouteEntriesEq(expect_entry, route_entry); } -TEST_F(RouteManagerTest, ValidateRouteEntryTest) +TEST_F(RouteManagerTest, ValidateRouteEntryNexthopActionWithInvalidNexthopShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateRouteEntry(route_entry, SET_COMMAND)); +} - // ValidateRouteEntry should fail when the nexthop does not exist in - // centralized map. - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetNexthopId; - route_entry.nexthop_id = kNexthopId1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateRouteEntry(route_entry)); +TEST_F(RouteManagerTest, ValidateRouteEntryNexthopActionWithValidNexthopShouldSucceed) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), kNexthopOid1); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ValidateRouteEntry(route_entry)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateRouteEntryWcmpGroupActionWithInvalidWcmpGroupShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetWcmpGroupId; - route_entry.wcmp_group = kWcmpGroup1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateRouteEntryWcmpGroupActionWithValidWcmpGroupShouldSucceed) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetWcmpGroupId; - route_entry.wcmp_group = kWcmpGroup1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), kWcmpGroupOid1); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ValidateRouteEntry(route_entry)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ValidateRouteEntry(route_entry, SET_COMMAND)); +} + +TEST_F(RouteManagerTest, ValidateRouteEntryWithInvalidCommandShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, "invalid")); } TEST_F(RouteManagerTest, ValidateSetRouteEntryDoesNotExistInManagerShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.nexthop_id = kNexthopId1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + route_entry.action = ""; + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateSetRouteEntryExistsInMapperDoesNotExistInManagerShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetNexthopId; - route_entry.nexthop_id = kNexthopId1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); p4_oid_mapper_.setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); - EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateSetRouteEntry(route_entry)); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateSetRouteEntryExistsInManagerDoesNotExistInMapperShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.nexthop_id = kNexthopId2; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); - EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateSetRouteEntry(route_entry)); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateSetRouteEntryNexthopIdActionWithoutNexthopIdShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetNexthopId; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, ""); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateSetRouteEntryNexthopIdActionWithWcmpGroupShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetNexthopId; - route_entry.nexthop_id = kNexthopId1; + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); route_entry.wcmp_group = kWcmpGroup1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateSetRouteEntryWcmpGroupActionWithoutWcmpGroupShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetWcmpGroupId; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, ""); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateSetRouteEntryWcmpGroupActionWithNexthopIdShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetWcmpGroupId; + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); route_entry.nexthop_id = kNexthopId1; - route_entry.wcmp_group = kWcmpGroup1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateSetRouteEntryDropActionWithNexthopIdShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kDrop; + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); route_entry.nexthop_id = kNexthopId1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryWcmpGroupActionWithNonemptyMetadataShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + auto route_entry = + GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1, kMetadata1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryNexthopIdAndMetadataActionWithEmptyMetadataShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + auto route_entry = + GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopIdAndMetadata, kNexthopId1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryNexthopIdAndMetadataActionWithInvalidMetadataShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopIdAndMetadata, + kNexthopId1, "invalid_int"); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateSetRouteEntryDropActionWithWcmpGroupShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kDrop; + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); + route_entry.wcmp_group = kWcmpGroup1; + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryTrapActionWithNexthopIdShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kTrap, ""); + route_entry.nexthop_id = kNexthopId1; + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryTrapActionWithWcmpGroupShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kTrap, ""); route_entry.wcmp_group = kWcmpGroup1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateSetRouteEntryInvalidActionShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); route_entry.action = "invalid"; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateSetRouteEntry(route_entry)); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateSetRouteEntrySucceeds) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.nexthop_id = kNexthopId2; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ValidateSetRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId2); + route_entry.action = ""; + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid2); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ValidateRouteEntry(route_entry, SET_COMMAND)); } TEST_F(RouteManagerTest, ValidateDelRouteEntryDoesNotExistInManagerShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateDelRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, "", ""); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateRouteEntry(route_entry, DEL_COMMAND)); } TEST_F(RouteManagerTest, ValidateDelRouteEntryDoesNotExistInMapperShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, "", ""); p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); - // (TODO): Expect critical state. - EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ValidateDelRouteEntry(route_entry)); + // TODO: Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ValidateRouteEntry(route_entry, DEL_COMMAND)); } TEST_F(RouteManagerTest, ValidateDelRouteEntryHasActionShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetNexthopId; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateDelRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, ""); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, DEL_COMMAND)); } TEST_F(RouteManagerTest, ValidateDelRouteEntryHasNexthopIdShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.nexthop_id = kNexthopId1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateDelRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + route_entry.action = ""; + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, DEL_COMMAND)); } TEST_F(RouteManagerTest, ValidateDelRouteEntryHasWcmpGroupShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.wcmp_group = kWcmpGroup1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateDelRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); + route_entry.action = ""; + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, DEL_COMMAND)); +} + +TEST_F(RouteManagerTest, ValidateDelRouteEntryHasMetadataShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, "", "", kMetadata1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ValidateRouteEntry(route_entry, DEL_COMMAND)); } TEST_F(RouteManagerTest, ValidateDelRouteEntrySucceeds) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ValidateDelRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, "", ""); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ValidateRouteEntry(route_entry, DEL_COMMAND)); } TEST_F(RouteManagerTest, CreateRouteEntryWithSaiErrorShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - P4RouteEntry route_entry; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kDrop; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); - EXPECT_CALL(mock_sai_route_, create_route_entry(_, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_FAILURE)); - EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, CreateRouteEntry(route_entry)); + std::vector exp_status{SAI_STATUS_FAILURE}; + EXPECT_CALL(mock_sai_route_, create_route_entries(_, _, _, _, _, _)) + .Times(3) + .WillRepeatedly(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_FAILURE))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); route_entry.action = p4orch::kSetNexthopId; route_entry.nexthop_id = kNexthopId1; - EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, CreateRouteEntry(route_entry)); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); route_entry.action = p4orch::kSetWcmpGroupId; route_entry.wcmp_group = kWcmpGroup1; - EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, CreateRouteEntry(route_entry)); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid1); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); } TEST_F(RouteManagerTest, CreateNexthopIdIpv4RouteSucceeds) @@ -826,21 +1054,28 @@ TEST_F(RouteManagerTest, CreateNexthopIdIpv4RouteSucceeds) auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); sai_ip_prefix_t sai_ipv4_route_prefix; copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); - P4RouteEntry route_entry; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetNexthopId; - route_entry.nexthop_id = kNexthopId1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), kNexthopOid1); - EXPECT_CALL( - mock_sai_route_, - create_route_entry(Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Eq(1), Truly(std::bind(MatchSaiAttributeNexthopId, kNexthopOid1, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kNexthopOid1; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, + create_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + ArrayEq(std::vector{1}), + AttrArrayArrayEq(std::vector>{{exp_sai_attr}}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); uint32_t ref_cnt; EXPECT_TRUE( @@ -853,21 +1088,28 @@ TEST_F(RouteManagerTest, CreateNexthopIdIpv6RouteSucceeds) auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); sai_ip_prefix_t sai_ipv6_route_prefix; copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); - P4RouteEntry route_entry; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv6_route_prefix; - route_entry.action = p4orch::kSetNexthopId; - route_entry.nexthop_id = kNexthopId1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, p4orch::kSetNexthopId, kNexthopId1); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), kNexthopOid1); - EXPECT_CALL( - mock_sai_route_, - create_route_entry(Truly(std::bind(MatchSaiRouteEntry, sai_ipv6_route_prefix, std::placeholders::_1, gVrfOid)), - Eq(1), Truly(std::bind(MatchSaiAttributeNexthopId, kNexthopOid1, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv6_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kNexthopOid1; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, + create_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + ArrayEq(std::vector{1}), + AttrArrayArrayEq(std::vector>{{exp_sai_attr}}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); VerifyRouteEntry(route_entry, sai_ipv6_route_prefix, gVrfOid); uint32_t ref_cnt; EXPECT_TRUE( @@ -875,23 +1117,106 @@ TEST_F(RouteManagerTest, CreateNexthopIdIpv6RouteSucceeds) EXPECT_EQ(1, ref_cnt); } +TEST_F(RouteManagerTest, CreateNexthopIdWithMetadataIpv4RouteSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopIdAndMetadata, + kNexthopId1, kMetadata1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid1); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + std::vector exp_sai_attrs; + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kNexthopOid1; + exp_sai_attrs.push_back(exp_sai_attr); + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + exp_sai_attr.value.u32 = kMetadataInt1; + exp_sai_attrs.push_back(exp_sai_attr); + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, + create_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + ArrayEq(std::vector{static_cast(exp_sai_attrs.size())}), + AttrArrayArrayEq(std::vector>{exp_sai_attrs}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, CreateDropSetMetadataRouteSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + auto route_entry = + GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetMetadataAndDrop, "", kMetadata1); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + std::vector exp_sai_attrs; + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_DROP; + exp_sai_attrs.push_back(exp_sai_attr); + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + exp_sai_attr.value.u32 = kMetadataInt1; + exp_sai_attrs.push_back(exp_sai_attr); + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, + create_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + ArrayEq(std::vector{static_cast(exp_sai_attrs.size())}), + AttrArrayArrayEq(std::vector>{exp_sai_attrs}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); +} + TEST_F(RouteManagerTest, CreateDropIpv4RouteSucceeds) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); sai_ip_prefix_t sai_ipv4_route_prefix; copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); - P4RouteEntry route_entry; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kDrop; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_DROP; + + std::vector exp_status{SAI_STATUS_SUCCESS}; EXPECT_CALL(mock_sai_route_, - create_route_entry( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), Eq(1), - Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_DROP, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + create_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + ArrayEq(std::vector{1}), + AttrArrayArrayEq(std::vector>{{exp_sai_attr}}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); } @@ -900,18 +1225,82 @@ TEST_F(RouteManagerTest, CreateDropIpv6RouteSucceeds) auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); sai_ip_prefix_t sai_ipv6_route_prefix; copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); - P4RouteEntry route_entry; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv6_route_prefix; - route_entry.action = p4orch::kDrop; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, p4orch::kDrop, ""); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv6_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_DROP; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, + create_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + ArrayEq(std::vector{1}), + AttrArrayArrayEq(std::vector>{{exp_sai_attr}}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv6_route_prefix, gVrfOid); +} + +TEST_F(RouteManagerTest, CreateTrapIpv4RouteSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kTrap, ""); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_TRAP; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, + create_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + ArrayEq(std::vector{1}), + AttrArrayArrayEq(std::vector>{{exp_sai_attr}}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); +} + +TEST_F(RouteManagerTest, CreateTrapIpv6RouteSucceeds) +{ + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, p4orch::kTrap, ""); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv6_route_prefix; + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_TRAP; + + std::vector exp_status{SAI_STATUS_SUCCESS}; EXPECT_CALL(mock_sai_route_, - create_route_entry( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv6_route_prefix, std::placeholders::_1, gVrfOid)), Eq(1), - Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_DROP, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + create_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + ArrayEq(std::vector{1}), + AttrArrayArrayEq(std::vector>{{exp_sai_attr}}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); VerifyRouteEntry(route_entry, sai_ipv6_route_prefix, gVrfOid); } @@ -920,21 +1309,28 @@ TEST_F(RouteManagerTest, CreateWcmpIpv4RouteSucceeds) auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); sai_ip_prefix_t sai_ipv4_route_prefix; copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); - P4RouteEntry route_entry; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetWcmpGroupId; - route_entry.wcmp_group = kWcmpGroup1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), kWcmpGroupOid1); - EXPECT_CALL( - mock_sai_route_, - create_route_entry(Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Eq(1), Truly(std::bind(MatchSaiAttributeNexthopId, kWcmpGroupOid1, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kWcmpGroupOid1; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, + create_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + ArrayEq(std::vector{1}), + AttrArrayArrayEq(std::vector>{{exp_sai_attr}}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); uint32_t ref_cnt; EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, @@ -947,21 +1343,68 @@ TEST_F(RouteManagerTest, CreateWcmpIpv6RouteSucceeds) auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); sai_ip_prefix_t sai_ipv6_route_prefix; copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); - P4RouteEntry route_entry; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv6_route_prefix; - route_entry.action = p4orch::kSetWcmpGroupId; - route_entry.wcmp_group = kWcmpGroup1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), kWcmpGroupOid1); - EXPECT_CALL( - mock_sai_route_, - create_route_entry(Truly(std::bind(MatchSaiRouteEntry, sai_ipv6_route_prefix, std::placeholders::_1, gVrfOid)), - Eq(1), Truly(std::bind(MatchSaiAttributeNexthopId, kWcmpGroupOid1, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouteEntry(route_entry)); + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv6_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kWcmpGroupOid1; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, + create_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + ArrayEq(std::vector{1}), + AttrArrayArrayEq(std::vector>{{exp_sai_attr}}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv6_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, CreateWcmpWithMetadataIpv6RouteSucceeds) +{ + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, p4orch::kSetWcmpGroupIdAndMetadata, + kWcmpGroup1, kMetadata1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid1); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv6_route_prefix; + + std::vector exp_sai_attrs; + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kWcmpGroupOid1; + exp_sai_attrs.push_back(exp_sai_attr); + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + exp_sai_attr.value.u32 = kMetadataInt1; + exp_sai_attrs.push_back(exp_sai_attr); + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, + create_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + ArrayEq(std::vector{static_cast(exp_sai_attrs.size())}), + AttrArrayArrayEq(std::vector>{exp_sai_attrs}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); VerifyRouteEntry(route_entry, sai_ipv6_route_prefix, gVrfOid); uint32_t ref_cnt; EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, @@ -976,39 +1419,30 @@ TEST_F(RouteManagerTest, UpdateRouteEntryWcmpWithSaiErrorShouldFail) copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); SetupWcmpGroupRouteEntry(gVrfName, swss_ipv4_route_prefix, kWcmpGroup1, kWcmpGroupOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetWcmpGroupId; - route_entry.wcmp_group = kWcmpGroup2; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup2); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), kWcmpGroupOid2); - EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); - EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); - EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)) - .WillOnce(Return(SAI_STATUS_SUCCESS)) - .WillOnce(Return(SAI_STATUS_FAILURE)) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); + std::vector exp_status{SAI_STATUS_FAILURE}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_FAILURE))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); } -TEST_F(RouteManagerTest, UpdateRouteEntryWcmpNotExistInMapperShouldFail) +TEST_F(RouteManagerTest, UpdateRouteEntryWcmpNotExistInMapperShouldRaiseCriticalState) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); sai_ip_prefix_t sai_ipv4_route_prefix; copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); SetupWcmpGroupRouteEntry(gVrfName, swss_ipv4_route_prefix, kWcmpGroup1, kWcmpGroupOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetWcmpGroupId; - route_entry.wcmp_group = kWcmpGroup2; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - - // (TODO): Expect critical state. - EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, UpdateRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup2); + std::vector exp_status{SAI_STATUS_FAILURE}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_FAILURE))); + // TODO: Expect critical state. + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); } TEST_F(RouteManagerTest, UpdateRouteFromSetWcmpToSetNextHopSucceeds) @@ -1018,25 +1452,75 @@ TEST_F(RouteManagerTest, UpdateRouteFromSetWcmpToSetNextHopSucceeds) copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); SetupWcmpGroupRouteEntry(gVrfName, swss_ipv4_route_prefix, kWcmpGroup1, kWcmpGroupOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetNexthopId; - route_entry.nexthop_id = kNexthopId2; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId2); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), kNexthopOid2); - EXPECT_CALL(mock_sai_route_, - set_route_entry_attribute( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Truly(std::bind(MatchSaiAttributeNexthopId, kNexthopOid2, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_CALL(mock_sai_route_, - set_route_entry_attribute( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_FORWARD, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kNexthopOid2; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateRouteFromSetWcmpToSetNextHopAndMetadataSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupWcmpGroupRouteEntry(gVrfName, swss_ipv4_route_prefix, kWcmpGroup1, kWcmpGroupOid1); + + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopIdAndMetadata, + kNexthopId2, kMetadata2); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid2); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + exp_sai_attr.value.u32 = kMetadataInt2; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kNexthopOid2; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); uint32_t ref_cnt; EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, @@ -1054,25 +1538,27 @@ TEST_F(RouteManagerTest, UpdateRouteFromSetNexthopIdToSetWcmpSucceeds) copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetWcmpGroupId; - route_entry.wcmp_group = kWcmpGroup2; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup2); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), kWcmpGroupOid2); - EXPECT_CALL(mock_sai_route_, - set_route_entry_attribute( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Truly(std::bind(MatchSaiAttributeNexthopId, kWcmpGroupOid2, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_CALL(mock_sai_route_, - set_route_entry_attribute( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_FORWARD, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kWcmpGroupOid2; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); route_entry.action = p4orch::kSetWcmpGroupId; VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); uint32_t ref_cnt; @@ -1084,40 +1570,82 @@ TEST_F(RouteManagerTest, UpdateRouteFromSetNexthopIdToSetWcmpSucceeds) EXPECT_EQ(1, ref_cnt); } +TEST_F(RouteManagerTest, UpdateRouteFromSetNexthopIdAndMetadataToSetWcmpSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1, kMetadata2); + + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup2); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid2); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + exp_sai_attr.value.u32 = 0; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kWcmpGroupOid2; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + TEST_F(RouteManagerTest, UpdateRouteEntryNexthopIdWithSaiErrorShouldFail) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.nexthop_id = kNexthopId2; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId2); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), kNexthopOid2); - EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); - EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); - EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)) - .WillOnce(Return(SAI_STATUS_SUCCESS)) - .WillOnce(Return(SAI_STATUS_FAILURE)) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); + std::vector exp_failure_status{SAI_STATUS_FAILURE}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); } -TEST_F(RouteManagerTest, UpdateRouteEntryNexthopIdNotExistInMapperShouldFail) +TEST_F(RouteManagerTest, UpdateRouteEntryNexthopIdNotExistInMapperShouldRaiseCriticalState) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.nexthop_id = kNexthopId2; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - - // (TODO): Expect critical state. - EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, UpdateRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId2); + std::vector exp_failure_status{SAI_STATUS_FAILURE}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))); + // TODO: Expect critical state. + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); } TEST_F(RouteManagerTest, UpdateRouteEntryDropWithSaiErrorShouldFail) @@ -1125,18 +1653,67 @@ TEST_F(RouteManagerTest, UpdateRouteEntryDropWithSaiErrorShouldFail) auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kDrop; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); - EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); - EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)) - .WillOnce(Return(SAI_STATUS_SUCCESS)) - .WillOnce(Return(SAI_STATUS_FAILURE)) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); + std::vector exp_failure_status{SAI_STATUS_FAILURE}; + std::vector exp_success_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))); + // TODO: Expect critical state. + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); +} + +TEST_F(RouteManagerTest, UpdateRouteEntryTrapWithSaiErrorShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kTrap, ""); + std::vector exp_failure_status{SAI_STATUS_FAILURE}; + std::vector exp_success_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))); + // TODO: Expect critical state. + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); } TEST_F(RouteManagerTest, UpdateRouteWithDifferentNexthopIdsSucceeds) @@ -1146,24 +1723,27 @@ TEST_F(RouteManagerTest, UpdateRouteWithDifferentNexthopIdsSucceeds) copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.nexthop_id = kNexthopId2; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId2); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), kNexthopOid2); - EXPECT_CALL(mock_sai_route_, - set_route_entry_attribute( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Truly(std::bind(MatchSaiAttributeNexthopId, kNexthopOid2, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_CALL(mock_sai_route_, - set_route_entry_attribute( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_FORWARD, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kNexthopOid2; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); route_entry.action = p4orch::kSetNexthopId; VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); uint32_t ref_cnt; @@ -1175,6 +1755,54 @@ TEST_F(RouteManagerTest, UpdateRouteWithDifferentNexthopIdsSucceeds) EXPECT_EQ(1, ref_cnt); } +TEST_F(RouteManagerTest, UpdateRouteWithDifferentNexthopIdsAndMetadatasSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1, kMetadata1); + + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopIdAndMetadata, + kNexthopId2, kMetadata2); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid2); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + exp_sai_attr.value.u32 = kMetadataInt2; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kNexthopOid2; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + TEST_F(RouteManagerTest, UpdateRouteFromNexthopIdToDropSucceeds) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); @@ -1182,22 +1810,34 @@ TEST_F(RouteManagerTest, UpdateRouteFromNexthopIdToDropSucceeds) copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kDrop; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_CALL(mock_sai_route_, - set_route_entry_attribute( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_DROP, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_CALL(mock_sai_route_, - set_route_entry_attribute( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Truly(std::bind(MatchSaiAttributeNexthopId, SAI_NULL_OBJECT_ID, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_DROP; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = SAI_NULL_OBJECT_ID; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); uint32_t ref_cnt; EXPECT_TRUE( @@ -1205,32 +1845,147 @@ TEST_F(RouteManagerTest, UpdateRouteFromNexthopIdToDropSucceeds) EXPECT_EQ(0, ref_cnt); } -TEST_F(RouteManagerTest, UpdateRouteFromDropToNexthopIdSucceeds) +TEST_F(RouteManagerTest, UpdateRouteFromNexthopIdToRouteMetadataSucceeds) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); sai_ip_prefix_t sai_ipv4_route_prefix; copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetNexthopId; - route_entry.nexthop_id = kNexthopId2; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = + GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetMetadataAndDrop, "", kMetadata1); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + exp_sai_attr.value.s32 = kMetadataInt1; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = SAI_NULL_OBJECT_ID; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_DROP; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateRouteFromNexthopIdAndMetadataToDropSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1, kMetadata2); + + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_DROP; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = SAI_NULL_OBJECT_ID; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + exp_sai_attr.value.u32 = 0; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateRouteFromDropToNexthopIdSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupDropRouteEntry(gVrfName, swss_ipv4_route_prefix); + + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId2); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), kNexthopOid2); - EXPECT_CALL(mock_sai_route_, - set_route_entry_attribute( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Truly(std::bind(MatchSaiAttributeNexthopId, kNexthopOid2, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_CALL(mock_sai_route_, - set_route_entry_attribute( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_FORWARD, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kNexthopOid2; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_FORWARD; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); uint32_t ref_cnt; EXPECT_TRUE( @@ -1238,174 +1993,507 @@ TEST_F(RouteManagerTest, UpdateRouteFromDropToNexthopIdSucceeds) EXPECT_EQ(1, ref_cnt); } -TEST_F(RouteManagerTest, UpdateRouteWithDifferentWcmpGroupsSucceeds) +TEST_F(RouteManagerTest, UpdateRouteFromDropToWcmpWithMetadataSucceeds) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); sai_ip_prefix_t sai_ipv4_route_prefix; copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); - SetupWcmpGroupRouteEntry(gVrfName, swss_ipv4_route_prefix, kWcmpGroup1, kWcmpGroupOid1); + SetupDropRouteEntry(gVrfName, swss_ipv4_route_prefix); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetWcmpGroupId; - route_entry.wcmp_group = kWcmpGroup2; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupIdAndMetadata, + kWcmpGroup1, kMetadata2); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), - kWcmpGroupOid2); - EXPECT_CALL(mock_sai_route_, - set_route_entry_attribute( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Truly(std::bind(MatchSaiAttributeNexthopId, kWcmpGroupOid2, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_CALL(mock_sai_route_, - set_route_entry_attribute( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVrfOid)), - Truly(std::bind(MatchSaiAttributeAction, SAI_PACKET_ACTION_FORWARD, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); - route_entry.action = p4orch::kSetWcmpGroupId; + kWcmpGroupOid1); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + exp_sai_attr.value.u32 = kMetadataInt2; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kWcmpGroupOid1; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_FORWARD; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); uint32_t ref_cnt; EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); - EXPECT_EQ(0, ref_cnt); - EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, - KeyGenerator::generateWcmpGroupKey(kWcmpGroup2), &ref_cnt)); EXPECT_EQ(1, ref_cnt); } -TEST_F(RouteManagerTest, UpdateNexthopIdRouteWithNoChangeSucceeds) +TEST_F(RouteManagerTest, UpdateRouteFromTrapToDropAndSetMetadataSucceeds) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); sai_ip_prefix_t sai_ipv4_route_prefix; copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); - SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetNexthopId; - route_entry.nexthop_id = kNexthopId1; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, UpdateRouteEntry(route_entry)); + SetupTrapRouteEntry(gVrfName, swss_ipv4_route_prefix); + + auto route_entry = + GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetMetadataAndDrop, "", kMetadata2); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_DROP; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_META_DATA; + exp_sai_attr.value.u32 = kMetadataInt2; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); - uint32_t ref_cnt; - EXPECT_TRUE( - p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); - EXPECT_EQ(1, ref_cnt); } -TEST_F(RouteManagerTest, UpdateRouteEntryRecoverFailureShouldRaiseCriticalState) +TEST_F(RouteManagerTest, UpdateRouteFromTrapToDropSucceeds) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kDrop; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); - EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); - EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)) - .WillOnce(Return(SAI_STATUS_SUCCESS)) - .WillOnce(Return(SAI_STATUS_FAILURE)) - .WillOnce(Return(SAI_STATUS_FAILURE)); - // (TODO): Expect critical state. - EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, UpdateRouteEntry(route_entry)); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupTrapRouteEntry(gVrfName, swss_ipv4_route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_DROP; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); } -TEST_F(RouteManagerTest, DeleteRouteEntryWithSaiErrorShouldFail) +TEST_F(RouteManagerTest, UpdateRouteFromDropToTrapSucceeds) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); - SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - - EXPECT_CALL(mock_sai_route_, remove_route_entry(_)).WillOnce(Return(SAI_STATUS_FAILURE)); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, DeleteRouteEntry(route_entry)); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupDropRouteEntry(gVrfName, swss_ipv4_route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kTrap, ""); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_TRAP; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); } -TEST_F(RouteManagerTest, DeleteIpv4RouteSucceeds) +TEST_F(RouteManagerTest, UpdateRouteFromNexthopIdToTrapSucceeds) { auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); sai_ip_prefix_t sai_ipv4_route_prefix; copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); - - EXPECT_CALL(mock_sai_route_, remove_route_entry(Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, - std::placeholders::_1, gVrfOid)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, DeleteRouteEntry(route_entry)); - auto *route_entry_ptr = GetRouteEntry(route_entry.route_entry_key); - EXPECT_EQ(nullptr, route_entry_ptr); - EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kTrap, ""); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_TRAP; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = SAI_NULL_OBJECT_ID; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); uint32_t ref_cnt; EXPECT_TRUE( p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); EXPECT_EQ(0, ref_cnt); } -TEST_F(RouteManagerTest, DeleteIpv6RouteSucceeds) +TEST_F(RouteManagerTest, UpdateRouteFromTrapToNexthopIdSucceeds) { - auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); - sai_ip_prefix_t sai_ipv6_route_prefix; - copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); - SetupWcmpGroupRouteEntry(gVrfName, swss_ipv6_route_prefix, kWcmpGroup1, kWcmpGroupOid1); + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupTrapRouteEntry(gVrfName, swss_ipv4_route_prefix); - EXPECT_CALL(mock_sai_route_, remove_route_entry(Truly(std::bind(MatchSaiRouteEntry, sai_ipv6_route_prefix, - std::placeholders::_1, gVrfOid)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); - P4RouteEntry route_entry = {}; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv6_route_prefix; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); - EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, DeleteRouteEntry(route_entry)); - auto *route_entry_ptr = GetRouteEntry(route_entry.route_entry_key); - EXPECT_EQ(nullptr, route_entry_ptr); - EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId2); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid2); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kNexthopOid2; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr.value.s32 = SAI_PACKET_ACTION_FORWARD; + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); uint32_t ref_cnt; - EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, - KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); - EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); } -TEST_F(RouteManagerTest, RouteCreateAndUpdateInDrainSucceeds) +TEST_F(RouteManagerTest, UpdateRouteFromTrapToNexthopIdAndMetadataRecoverFailureShouldRaiseCriticalState) { - const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; - p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); - p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), kNexthopOid2); - nlohmann::json j; - j[prependMatchField(p4orch::kVrfId)] = gVrfName; - j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; - std::vector attributes; - attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); - attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); - Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); - attributes.clear(); - attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId2}); - Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupTrapRouteEntry(gVrfName, swss_ipv4_route_prefix); - EXPECT_CALL(mock_sai_route_, create_route_entry(_, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_CALL(mock_sai_route_, set_route_entry_attribute(_, _)).Times(2).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopIdAndMetadata, + kNexthopId2, kMetadata1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid2); + std::vector exp_failure_status{SAI_STATUS_FAILURE}; + std::vector exp_success_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))); + // TODO: Expect critical state. + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); +} - Drain(); +TEST_F(RouteManagerTest, UpdateRouteWithDifferentWcmpGroupsSucceeds) +{ auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); sai_ip_prefix_t sai_ipv4_route_prefix; copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); - P4RouteEntry route_entry; - route_entry.vrf_id = gVrfName; - route_entry.route_prefix = swss_ipv4_route_prefix; - route_entry.action = p4orch::kSetNexthopId; - route_entry.nexthop_id = kNexthopId2; - route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + SetupWcmpGroupRouteEntry(gVrfName, swss_ipv4_route_prefix, kWcmpGroup1, kWcmpGroupOid1); + + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup2); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group), + kWcmpGroupOid2); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kWcmpGroupOid2; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + AttrArrayEq(std::vector{exp_sai_attr}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + route_entry.action = p4orch::kSetWcmpGroupId; + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateNexthopIdRouteWithNoChangeSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, UpdateRouteFromNexthopIdAndMetadataToDropRecoverFailureShouldRaiseCriticalState) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1, kMetadata2); + + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kDrop, ""); + + std::vector exp_failure_status{SAI_STATUS_FAILURE}; + std::vector exp_success_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))); + // TODO: Expect critical state. + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); +} + +TEST_F(RouteManagerTest, UpdateRouteFromDifferentNexthopIdAndMetadataRecoverFailureShouldRaiseCriticalState) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1, kMetadata1); + + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopIdAndMetadata, + kNexthopId2, kMetadata2); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry.nexthop_id), + kNexthopOid2); + + std::vector exp_failure_status{SAI_STATUS_FAILURE}; + std::vector exp_success_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); + + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_success_status.begin(), exp_success_status.end()), + Return(SAI_STATUS_SUCCESS))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))) + .WillOnce(DoAll(SetArrayArgument<4>(exp_failure_status.begin(), exp_failure_status.end()), + Return(SAI_STATUS_FAILURE))); + // TODO: Expect critical state. + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); +} + +TEST_F(RouteManagerTest, DeleteRouteEntryWithSaiErrorShouldFail) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + std::vector exp_status{SAI_STATUS_FAILURE}; + EXPECT_CALL(mock_sai_route_, remove_route_entries(_, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<3>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_FAILURE))); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, "", ""); + EXPECT_THAT(DeleteRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_UNKNOWN})); +} + +TEST_F(RouteManagerTest, DeleteIpv4RouteSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, + remove_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<3>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, "", ""); + EXPECT_THAT(DeleteRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + auto *route_entry_ptr = GetRouteEntry(route_entry.route_entry_key); + EXPECT_EQ(nullptr, route_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key)); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, DeleteIpv6RouteSucceeds) +{ + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + SetupWcmpGroupRouteEntry(gVrfName, swss_ipv6_route_prefix, kWcmpGroup1, kWcmpGroupOid1); + + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVrfOid; + exp_sai_route_entry.destination = sai_ipv6_route_prefix; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, + remove_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<3>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, "", ""); + EXPECT_THAT(DeleteRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + auto *route_entry_ptr = GetRouteEntry(route_entry.route_entry_key); + EXPECT_EQ(nullptr, route_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key)); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, RouteCreateAndUpdateInDrainSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); + auto key_op_fvs_1 = GenerateKeyOpFieldsValuesTuple(gVrfName, swss_ipv4_route_prefix, SET_COMMAND, + p4orch::kSetNexthopId, kNexthopId1); + Enqueue(key_op_fvs_1); + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, create_route_entries(_, _, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs_1)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs_1)), + Eq(StatusCode::SWSS_RC_SUCCESS), Eq(true))) + .Times(1); + Drain(); + + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), kNexthopOid2); + auto key_op_fvs_2 = GenerateKeyOpFieldsValuesTuple(gVrfName, swss_ipv4_route_prefix, SET_COMMAND, + p4orch::kSetNexthopId, kNexthopId2); + Enqueue(key_op_fvs_2); + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs_2)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs_2)), + Eq(StatusCode::SWSS_RC_SUCCESS), Eq(true))) + .Times(1); + Drain(); + + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId2); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); uint32_t ref_cnt; EXPECT_TRUE( @@ -1414,26 +2502,55 @@ TEST_F(RouteManagerTest, RouteCreateAndUpdateInDrainSucceeds) EXPECT_TRUE( p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), &ref_cnt)); EXPECT_EQ(1, ref_cnt); + + auto key_op_fvs_3 = GenerateKeyOpFieldsValuesTuple(gVrfName, swss_ipv4_route_prefix, SET_COMMAND, + p4orch::kSetMetadataAndDrop, "", kMetadata1); + Enqueue(key_op_fvs_3); + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute(_, _, _, _, _)) + .WillRepeatedly(DoAll(SetArrayArgument<4>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs_3)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs_3)), + Eq(StatusCode::SWSS_RC_SUCCESS), Eq(true))) + .Times(1); + Drain(); + + route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetMetadataAndDrop, "", kMetadata1); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); } TEST_F(RouteManagerTest, RouteCreateAndDeleteInDrainSucceeds) { - const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); - nlohmann::json j; - j[prependMatchField(p4orch::kVrfId)] = gVrfName; - j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; - std::vector attributes; - attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); - attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); - Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); - attributes.clear(); - Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), DEL_COMMAND, attributes)); + auto key_op_fvs_1 = GenerateKeyOpFieldsValuesTuple(gVrfName, swss_ipv4_route_prefix, SET_COMMAND, + p4orch::kSetNexthopId, kNexthopId1); + Enqueue(key_op_fvs_1); + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, create_route_entries(_, _, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs_1)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs_1)), + Eq(StatusCode::SWSS_RC_SUCCESS), Eq(true))) + .Times(1); + Drain(); - EXPECT_CALL(mock_sai_route_, create_route_entry(_, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); - EXPECT_CALL(mock_sai_route_, remove_route_entry(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + auto key_op_fvs_2 = GenerateKeyOpFieldsValuesTuple(gVrfName, swss_ipv4_route_prefix, DEL_COMMAND, "", ""); + Enqueue(key_op_fvs_2); + EXPECT_CALL(mock_sai_route_, remove_route_entries(_, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<3>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs_2)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs_2)), + Eq(StatusCode::SWSS_RC_SUCCESS), Eq(true))) + .Times(1); Drain(); - std::string key = KeyGenerator::generateRouteKey(gVrfName, swss::IpPrefix(kIpv4Prefix)); + + std::string key = KeyGenerator::generateRouteKey(gVrfName, swss_ipv4_route_prefix); auto *route_entry_ptr = GetRouteEntry(key); EXPECT_EQ(nullptr, route_entry_ptr); EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, key)); @@ -1443,28 +2560,107 @@ TEST_F(RouteManagerTest, RouteCreateAndDeleteInDrainSucceeds) EXPECT_EQ(0, ref_cnt); } +TEST_F(RouteManagerTest, UpdateFailsWhenCreateAndUpdateTheSameRouteInDrain) +{ + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), kNexthopOid2); + auto key_op_fvs_1 = GenerateKeyOpFieldsValuesTuple(gVrfName, swss::IpPrefix(kIpv4Prefix), SET_COMMAND, + p4orch::kSetNexthopId, kNexthopId1); + Enqueue(key_op_fvs_1); + auto key_op_fvs_2 = GenerateKeyOpFieldsValuesTuple(gVrfName, swss::IpPrefix(kIpv4Prefix), SET_COMMAND, + p4orch::kSetNexthopId, kNexthopId2); + Enqueue(key_op_fvs_2); + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, create_route_entries(_, _, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs_1)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs_1)), + Eq(StatusCode::SWSS_RC_SUCCESS), Eq(true))) + .Times(1); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs_2)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs_2)), + Eq(StatusCode::SWSS_RC_INVALID_PARAM), Eq(true))) + .Times(1); + + Drain(); + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId2), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, DeleteFailsWhenCreateAndDeleteTheSameRouteInDrain) +{ + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); + auto key_op_fvs_1 = GenerateKeyOpFieldsValuesTuple(gVrfName, swss::IpPrefix(kIpv4Prefix), SET_COMMAND, + p4orch::kSetNexthopId, kNexthopId1); + Enqueue(key_op_fvs_1); + auto key_op_fvs_2 = GenerateKeyOpFieldsValuesTuple(gVrfName, swss::IpPrefix(kIpv4Prefix), DEL_COMMAND, "", ""); + Enqueue(key_op_fvs_2); + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, create_route_entries(_, _, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs_1)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs_1)), + Eq(StatusCode::SWSS_RC_SUCCESS), Eq(true))) + .Times(1); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs_2)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs_2)), + Eq(StatusCode::SWSS_RC_INVALID_PARAM), Eq(true))) + .Times(1); + Drain(); + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + TEST_F(RouteManagerTest, RouteCreateInDrainSucceedsWhenVrfIsEmpty) { - const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; const std::string kDefaultVrfName = ""; // Default Vrf auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); sai_ip_prefix_t sai_ipv4_route_prefix; copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); - nlohmann::json j; - j[prependMatchField(p4orch::kVrfId)] = kDefaultVrfName; - j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; - std::vector attributes; - attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); - attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); - Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + auto key_op_fvs = GenerateKeyOpFieldsValuesTuple(kDefaultVrfName, swss::IpPrefix(kIpv4Prefix), SET_COMMAND, + p4orch::kSetNexthopId, kNexthopId1); + Enqueue(key_op_fvs); - EXPECT_CALL( - mock_sai_route_, - create_route_entry( - Truly(std::bind(MatchSaiRouteEntry, sai_ipv4_route_prefix, std::placeholders::_1, gVirtualRouterId)), Eq(1), - Truly(std::bind(MatchSaiAttributeNexthopId, kNexthopOid1, std::placeholders::_1)))) - .WillOnce(Return(SAI_STATUS_SUCCESS)); + sai_route_entry_t exp_sai_route_entry; + exp_sai_route_entry.switch_id = gSwitchId; + exp_sai_route_entry.vr_id = gVirtualRouterId; + exp_sai_route_entry.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr; + exp_sai_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr.value.oid = kNexthopOid1; + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, + create_route_entries(Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry}), + ArrayEq(std::vector{1}), + AttrArrayArrayEq(std::vector>{{exp_sai_attr}}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(publisher_, + publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs)), Eq(StatusCode::SWSS_RC_SUCCESS), Eq(true))) + .Times(1); Drain(); std::string key = KeyGenerator::generateRouteKey(kDefaultVrfName, swss::IpPrefix(kIpv4Prefix)); @@ -1480,55 +2676,69 @@ TEST_F(RouteManagerTest, RouteCreateInDrainSucceedsWhenVrfIsEmpty) TEST_F(RouteManagerTest, DeserializeRouteEntryInDrainFails) { const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; - Enqueue( - swss::KeyOpFieldsValuesTuple(kKeyPrefix + "{{{{{{{{{{{{", SET_COMMAND, std::vector{})); + auto key_op_fvs = + swss::KeyOpFieldsValuesTuple(kKeyPrefix + "{{{{{{{{{{{{", SET_COMMAND, std::vector{}); + Enqueue(key_op_fvs); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs)), + Eq(StatusCode::SWSS_RC_INVALID_PARAM), Eq(true))) + .Times(1); Drain(); } TEST_F(RouteManagerTest, ValidateRouteEntryInDrainFailsWhenVrfDoesNotExist) { - const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); - nlohmann::json j; - j[prependMatchField(p4orch::kVrfId)] = "Invalid-Vrf"; - j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; - std::vector attributes; - attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); - // Vrf does not exist. - attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); - Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + auto key_op_fvs = GenerateKeyOpFieldsValuesTuple("Invalid-Vrf", swss::IpPrefix(kIpv4Prefix), SET_COMMAND, + p4orch::kSetNexthopId, kNexthopId1); + Enqueue(key_op_fvs); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs)), + Eq(StatusCode::SWSS_RC_NOT_FOUND), Eq(true))) + .Times(1); Drain(); } TEST_F(RouteManagerTest, ValidateRouteEntryInDrainFailsWhenNexthopDoesNotExist) { - const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; - nlohmann::json j; - j[prependMatchField(p4orch::kVrfId)] = gVrfName; - j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; - std::vector attributes; - attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); - // Nexthop ID does not exist. - attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); - Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + auto key_op_fvs = GenerateKeyOpFieldsValuesTuple(gVrfName, swss::IpPrefix(kIpv4Prefix), SET_COMMAND, + p4orch::kSetNexthopId, kNexthopId1); + Enqueue(key_op_fvs); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs)), + Eq(StatusCode::SWSS_RC_NOT_FOUND), Eq(true))) + .Times(1); Drain(); } -TEST_F(RouteManagerTest, ValidateSetRouteEntryInDrainFails) +TEST_F(RouteManagerTest, InvalidateSetRouteEntryInDrainFails) { - const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); - nlohmann::json j; - j[prependMatchField(p4orch::kVrfId)] = gVrfName; - j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; - std::vector attributes; - attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); // No nexthop ID with kSetNexthopId action. - Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + auto key_op_fvs = + GenerateKeyOpFieldsValuesTuple(gVrfName, swss::IpPrefix(kIpv4Prefix), SET_COMMAND, p4orch::kSetNexthopId, ""); + Enqueue(key_op_fvs); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs)), + Eq(StatusCode::SWSS_RC_INVALID_PARAM), Eq(true))) + .Times(1); Drain(); } -TEST_F(RouteManagerTest, ValidateDelRouteEntryInDrainFails) +TEST_F(RouteManagerTest, InvalidateDelRouteEntryInDrainFails) +{ + // Route does not exist. + auto key_op_fvs = GenerateKeyOpFieldsValuesTuple(gVrfName, swss::IpPrefix(kIpv4Prefix), DEL_COMMAND, + p4orch::kSetNexthopId, kNexthopId1); + Enqueue(key_op_fvs); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs)), + Eq(StatusCode::SWSS_RC_NOT_FOUND), Eq(true))) + .Times(1); + Drain(); +} + +TEST_F(RouteManagerTest, InvalidCommandInDrainFails) { const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); @@ -1536,23 +2746,543 @@ TEST_F(RouteManagerTest, ValidateDelRouteEntryInDrainFails) j[prependMatchField(p4orch::kVrfId)] = gVrfName; j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; std::vector attributes; - // Fields are non-empty for DEl. attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); - Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), DEL_COMMAND, attributes)); + auto key_op_fvs = swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), "INVALID_COMMAND", attributes); + Enqueue(key_op_fvs); + EXPECT_CALL(publisher_, publish(Eq(APP_P4RT_TABLE_NAME), Eq(kfvKey(key_op_fvs)), + FieldValueTupleArrayEq(kfvFieldsValues(key_op_fvs)), + Eq(StatusCode::SWSS_RC_INVALID_PARAM), Eq(true))) + .Times(1); Drain(); } -TEST_F(RouteManagerTest, InvalidCommandInDrainFails) +TEST_F(RouteManagerTest, BatchedCreateSucceeds) { - const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; - p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), kNexthopOid1); + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + auto route_entry_ipv4 = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry_ipv4.nexthop_id), + kNexthopOid1); + + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + auto route_entry_ipv6 = + GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(route_entry_ipv6.wcmp_group), kWcmpGroupOid1); + + sai_route_entry_t exp_sai_route_entry_ipv4; + exp_sai_route_entry_ipv4.switch_id = gSwitchId; + exp_sai_route_entry_ipv4.vr_id = gVrfOid; + exp_sai_route_entry_ipv4.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr_ipv4; + exp_sai_attr_ipv4.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr_ipv4.value.oid = kNexthopOid1; + + sai_route_entry_t exp_sai_route_entry_ipv6; + exp_sai_route_entry_ipv6.switch_id = gSwitchId; + exp_sai_route_entry_ipv6.vr_id = gVrfOid; + exp_sai_route_entry_ipv6.destination = sai_ipv6_route_prefix; + + sai_attribute_t exp_sai_attr_ipv6; + exp_sai_attr_ipv6.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr_ipv6.value.oid = kWcmpGroupOid1; + + std::vector exp_status{SAI_STATUS_SUCCESS, SAI_STATUS_SUCCESS}; + EXPECT_CALL( + mock_sai_route_, + create_route_entries( + Eq(2), + RouteEntryArrayEq(std::vector{exp_sai_route_entry_ipv6, exp_sai_route_entry_ipv4}), + ArrayEq(std::vector{1, 1}), + AttrArrayArrayEq(std::vector>{{exp_sai_attr_ipv6}, {exp_sai_attr_ipv4}}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry_ipv4, route_entry_ipv6}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS, StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry_ipv4, sai_ipv4_route_prefix, gVrfOid); + VerifyRouteEntry(route_entry_ipv6, sai_ipv6_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, BatchedCreatePartiallySucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + auto route_entry_ipv4 = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(route_entry_ipv4.nexthop_id), + kNexthopOid1); + + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + auto route_entry_ipv6 = + GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(route_entry_ipv6.wcmp_group), kWcmpGroupOid1); + + sai_route_entry_t exp_sai_route_entry_ipv4; + exp_sai_route_entry_ipv4.switch_id = gSwitchId; + exp_sai_route_entry_ipv4.vr_id = gVrfOid; + exp_sai_route_entry_ipv4.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr_ipv4; + exp_sai_attr_ipv4.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr_ipv4.value.oid = kNexthopOid1; + + sai_route_entry_t exp_sai_route_entry_ipv6; + exp_sai_route_entry_ipv6.switch_id = gSwitchId; + exp_sai_route_entry_ipv6.vr_id = gVrfOid; + exp_sai_route_entry_ipv6.destination = sai_ipv6_route_prefix; + + sai_attribute_t exp_sai_attr_ipv6; + exp_sai_attr_ipv6.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr_ipv6.value.oid = kWcmpGroupOid1; + + std::vector exp_status{SAI_STATUS_FAILURE, SAI_STATUS_SUCCESS}; + EXPECT_CALL( + mock_sai_route_, + create_route_entries( + Eq(2), + RouteEntryArrayEq(std::vector{exp_sai_route_entry_ipv6, exp_sai_route_entry_ipv4}), + ArrayEq(std::vector{1, 1}), + AttrArrayArrayEq(std::vector>{{exp_sai_attr_ipv6}, {exp_sai_attr_ipv4}}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_FAILURE))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry_ipv4, route_entry_ipv6}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS, StatusCode::SWSS_RC_UNKNOWN})); + VerifyRouteEntry(route_entry_ipv4, sai_ipv4_route_prefix, gVrfOid); + auto *route_entry_ptr_ipv6 = GetRouteEntry(route_entry_ipv6.route_entry_key); + EXPECT_EQ(nullptr, route_entry_ptr_ipv6); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry_ipv6.route_entry_key)); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, BatchedUpdateSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + auto route_entry_ipv4 = + GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), + kWcmpGroupOid1); + + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + auto route_entry_ipv6 = + GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup2); + SetupDropRouteEntry(gVrfName, swss_ipv6_route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(kWcmpGroup2), + kWcmpGroupOid2); + + sai_route_entry_t exp_sai_route_entry_ipv4; + exp_sai_route_entry_ipv4.switch_id = gSwitchId; + exp_sai_route_entry_ipv4.vr_id = gVrfOid; + exp_sai_route_entry_ipv4.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr_ipv4; + exp_sai_attr_ipv4.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr_ipv4.value.oid = kWcmpGroupOid1; + + sai_route_entry_t exp_sai_route_entry_ipv6; + exp_sai_route_entry_ipv6.switch_id = gSwitchId; + exp_sai_route_entry_ipv6.vr_id = gVrfOid; + exp_sai_route_entry_ipv6.destination = sai_ipv6_route_prefix; + + sai_attribute_t exp_sai_attr_ipv6; + exp_sai_attr_ipv6.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr_ipv6.value.oid = kWcmpGroupOid2; + + std::vector exp_status_1{SAI_STATUS_SUCCESS, SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(2), + RouteEntryArrayEq(std::vector{exp_sai_route_entry_ipv6, + exp_sai_route_entry_ipv4}), + AttrArrayEq(std::vector{exp_sai_attr_ipv6, exp_sai_attr_ipv4}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status_1.begin(), exp_status_1.end()), Return(SAI_STATUS_SUCCESS))); + + sai_attribute_t exp_sai_attr_ipv6_2; + exp_sai_attr_ipv6_2.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + exp_sai_attr_ipv6_2.value.s32 = SAI_PACKET_ACTION_FORWARD; + + std::vector exp_status_2{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(1), RouteEntryArrayEq(std::vector{exp_sai_route_entry_ipv6}), + AttrArrayEq(std::vector{exp_sai_attr_ipv6_2}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status_2.begin(), exp_status_2.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry_ipv4, route_entry_ipv6}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS, StatusCode::SWSS_RC_SUCCESS})); + VerifyRouteEntry(route_entry_ipv4, sai_ipv4_route_prefix, gVrfOid); + VerifyRouteEntry(route_entry_ipv6, sai_ipv6_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(RouteManagerTest, BatchedUpdatePartiallySucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + auto route_entry_ipv4 = + GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup1); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), + kWcmpGroupOid1); + + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + auto route_entry_ipv6 = + GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, p4orch::kSetWcmpGroupId, kWcmpGroup2); + SetupDropRouteEntry(gVrfName, swss_ipv6_route_prefix); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(kWcmpGroup2), + kWcmpGroupOid2); + + sai_route_entry_t exp_sai_route_entry_ipv4; + exp_sai_route_entry_ipv4.switch_id = gSwitchId; + exp_sai_route_entry_ipv4.vr_id = gVrfOid; + exp_sai_route_entry_ipv4.destination = sai_ipv4_route_prefix; + + sai_attribute_t exp_sai_attr_ipv4; + exp_sai_attr_ipv4.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr_ipv4.value.oid = kWcmpGroupOid1; + + sai_route_entry_t exp_sai_route_entry_ipv6; + exp_sai_route_entry_ipv6.switch_id = gSwitchId; + exp_sai_route_entry_ipv6.vr_id = gVrfOid; + exp_sai_route_entry_ipv6.destination = sai_ipv6_route_prefix; + + sai_attribute_t exp_sai_attr_ipv6; + exp_sai_attr_ipv6.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + exp_sai_attr_ipv6.value.oid = kWcmpGroupOid2; + + std::vector exp_status_1{SAI_STATUS_FAILURE, SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, set_route_entries_attribute( + Eq(2), + RouteEntryArrayEq(std::vector{exp_sai_route_entry_ipv6, + exp_sai_route_entry_ipv4}), + AttrArrayEq(std::vector{exp_sai_attr_ipv6, exp_sai_attr_ipv4}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<4>(exp_status_1.begin(), exp_status_1.end()), Return(SAI_STATUS_FAILURE))); + EXPECT_THAT(UpdateRouteEntries(std::vector{route_entry_ipv4, route_entry_ipv6}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS, StatusCode::SWSS_RC_UNKNOWN})); + VerifyRouteEntry(route_entry_ipv4, sai_ipv4_route_prefix, gVrfOid); + route_entry_ipv6 = GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, p4orch::kDrop, ""); + VerifyRouteEntry(route_entry_ipv6, sai_ipv6_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup1), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(kWcmpGroup2), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, BatchedDeleteSucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + auto route_entry_ipv4 = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + auto route_entry_ipv6 = GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, p4orch::kDrop, ""); + SetupDropRouteEntry(gVrfName, swss_ipv6_route_prefix); + + sai_route_entry_t exp_sai_route_entry_ipv4; + exp_sai_route_entry_ipv4.switch_id = gSwitchId; + exp_sai_route_entry_ipv4.vr_id = gVrfOid; + exp_sai_route_entry_ipv4.destination = sai_ipv4_route_prefix; + + sai_route_entry_t exp_sai_route_entry_ipv6; + exp_sai_route_entry_ipv6.switch_id = gSwitchId; + exp_sai_route_entry_ipv6.vr_id = gVrfOid; + exp_sai_route_entry_ipv6.destination = sai_ipv6_route_prefix; + + std::vector exp_status{SAI_STATUS_SUCCESS, SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, remove_route_entries(Eq(2), + RouteEntryArrayEq(std::vector{ + exp_sai_route_entry_ipv6, exp_sai_route_entry_ipv4}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<3>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(DeleteRouteEntries(std::vector{route_entry_ipv4, route_entry_ipv6}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS, StatusCode::SWSS_RC_SUCCESS})); + auto *route_entry_ptr_ipv4 = GetRouteEntry(route_entry_ipv4.route_entry_key); + EXPECT_EQ(nullptr, route_entry_ptr_ipv4); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry_ipv4.route_entry_key)); + auto *route_entry_ptr_ipv6 = GetRouteEntry(route_entry_ipv6.route_entry_key); + EXPECT_EQ(nullptr, route_entry_ptr_ipv6); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry_ipv6.route_entry_key)); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, BatchedDeletePartiallySucceeds) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + sai_ip_prefix_t sai_ipv4_route_prefix; + copy(sai_ipv4_route_prefix, swss_ipv4_route_prefix); + auto route_entry_ipv4 = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + sai_ip_prefix_t sai_ipv6_route_prefix; + copy(sai_ipv6_route_prefix, swss_ipv6_route_prefix); + auto route_entry_ipv6 = GenerateP4RouteEntry(gVrfName, swss_ipv6_route_prefix, p4orch::kDrop, ""); + SetupDropRouteEntry(gVrfName, swss_ipv6_route_prefix); + + sai_route_entry_t exp_sai_route_entry_ipv4; + exp_sai_route_entry_ipv4.switch_id = gSwitchId; + exp_sai_route_entry_ipv4.vr_id = gVrfOid; + exp_sai_route_entry_ipv4.destination = sai_ipv4_route_prefix; + + sai_route_entry_t exp_sai_route_entry_ipv6; + exp_sai_route_entry_ipv6.switch_id = gSwitchId; + exp_sai_route_entry_ipv6.vr_id = gVrfOid; + exp_sai_route_entry_ipv6.destination = sai_ipv6_route_prefix; + + std::vector exp_status{SAI_STATUS_FAILURE, SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, remove_route_entries(Eq(2), + RouteEntryArrayEq(std::vector{ + exp_sai_route_entry_ipv6, exp_sai_route_entry_ipv4}), + Eq(SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR), _)) + .WillOnce(DoAll(SetArrayArgument<3>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_FAILURE))); + EXPECT_THAT(DeleteRouteEntries(std::vector{route_entry_ipv4, route_entry_ipv6}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS, StatusCode::SWSS_RC_UNKNOWN})); + auto *route_entry_ptr_ipv4 = GetRouteEntry(route_entry_ipv4.route_entry_key); + EXPECT_EQ(nullptr, route_entry_ptr_ipv4); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry_ipv4.route_entry_key)); + VerifyRouteEntry(route_entry_ipv6, sai_ipv6_route_prefix, gVrfOid); + uint32_t ref_cnt; + EXPECT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(kNexthopId1), &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(RouteManagerTest, VerifyStateTest) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv4_route_prefix, kNexthopId1, kNexthopOid1); + auto route_entry = GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix, p4orch::kSetNexthopId, kNexthopId1); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"10.11.12.0/" + "24\",\"switch_id\":\"oid:0x0\",\"vr\":\"oid:0x6f\"}", + std::vector{swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID", "oid:0x1"}}); + nlohmann::json j; j[prependMatchField(p4orch::kVrfId)] = gVrfName; j[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_IPV4_TABLE_NAME + + kTableKeyDelimiter + j.dump(); std::vector attributes; + + // Verification should succeed with vaild key and value. attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); - Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), "INVALID_COMMAND", attributes)); - Drain(); + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // TODO: Expect critical state. + + // Invalid key should fail verification. + EXPECT_FALSE(VerifyState("invalid", attributes).empty()); + EXPECT_FALSE(VerifyState("invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":FIXED_IPV4_TABLE:invalid", attributes).empty()); + + // Verification should fail if nexthop ID does not exist. + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId2}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopId}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); + + // Verification should fail if entry does not exist. + j[prependMatchField(p4orch::kIpv4Dst)] = "1.1.1.0/24"; + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_IPV4_TABLE_NAME + + kTableKeyDelimiter + j.dump(), + attributes) + .empty()); + + auto *route_entry_ptr = GetRouteEntry(KeyGenerator::generateRouteKey(gVrfName, swss_ipv4_route_prefix)); + EXPECT_NE(route_entry_ptr, nullptr); + + // Verification should fail if route entry key mismatches. + auto saved_route_entry_key = route_entry_ptr->route_entry_key; + route_entry_ptr->route_entry_key = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + route_entry_ptr->route_entry_key = saved_route_entry_key; + + // Verification should fail if VRF ID mismatches. + auto saved_vrf_id = route_entry_ptr->vrf_id; + route_entry_ptr->vrf_id = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + route_entry_ptr->vrf_id = saved_vrf_id; + + // Verification should fail if route prefix mismatches. + auto saved_route_prefix = route_entry_ptr->route_prefix; + route_entry_ptr->route_prefix = swss::IpPrefix(kIpv6Prefix); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + route_entry_ptr->route_prefix = saved_route_prefix; + + // Verification should fail if action mismatches. + auto saved_action = route_entry_ptr->action; + route_entry_ptr->action = p4orch::kSetWcmpGroupId; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + route_entry_ptr->action = saved_action; + + // Verification should fail if nexthop ID mismatches. + auto saved_nexthop_id = route_entry_ptr->nexthop_id; + route_entry_ptr->nexthop_id = kNexthopId2; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + route_entry_ptr->nexthop_id = saved_nexthop_id; + + // Verification should fail if WCMP group mismatches. + auto saved_wcmp_group = route_entry_ptr->wcmp_group; + route_entry_ptr->wcmp_group = kWcmpGroup1; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + route_entry_ptr->wcmp_group = saved_wcmp_group; + + // Verification should fail if WCMP group mismatches. + auto saved_route_metadata = route_entry_ptr->route_metadata; + route_entry_ptr->route_metadata = kMetadata1; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + route_entry_ptr->route_metadata = saved_route_metadata; +} + +TEST_F(RouteManagerTest, VerifyStateAsicDbTest) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + SetupDropRouteEntry(gVrfName, swss_ipv4_route_prefix); + auto swss_ipv6_route_prefix = swss::IpPrefix(kIpv6Prefix); + SetupNexthopIdRouteEntry(gVrfName, swss_ipv6_route_prefix, kNexthopId1, kNexthopOid1, kMetadata1); + + auto swss_ipv4_route_prefix2 = swss::IpPrefix(kIpv4Prefix2); + auto route_entry = + GenerateP4RouteEntry(gVrfName, swss_ipv4_route_prefix2, p4orch::kSetMetadataAndDrop, "", kMetadata2); + + std::vector exp_status{SAI_STATUS_SUCCESS}; + EXPECT_CALL(mock_sai_route_, create_route_entries(_, _, _, _, _, _)) + .WillOnce(DoAll(SetArrayArgument<5>(exp_status.begin(), exp_status.end()), Return(SAI_STATUS_SUCCESS))); + EXPECT_THAT(CreateRouteEntries(std::vector{route_entry}), + ArrayEq(std::vector{StatusCode::SWSS_RC_SUCCESS})); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"10.11.12.0/" + "24\",\"switch_id\":\"oid:0x0\",\"vr\":\"oid:0x6f\"}", + std::vector{ + swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION", "SAI_PACKET_ACTION_DROP"}, + swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID", "oid:0x0"}}); + table.set("SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"2001:db8:1::/" + "32\",\"switch_id\":\"oid:0x0\",\"vr\":\"oid:0x6f\"}", + std::vector{swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID", "oid:0x1"}, + swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_META_DATA", "1"}}); + + table.set("SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"10.12.12.0/" + "24\",\"switch_id\":\"oid:0x0\",\"vr\":\"oid:0x6f\"}", + std::vector{ + swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION", "SAI_PACKET_ACTION_DROP"}, + swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_META_DATA", "2"}}); + + nlohmann::json j_1; + j_1[prependMatchField(p4orch::kVrfId)] = gVrfName; + j_1[prependMatchField(p4orch::kIpv4Dst)] = kIpv4Prefix; + const std::string db_key_1 = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_IPV4_TABLE_NAME + + kTableKeyDelimiter + j_1.dump(); + std::vector attributes_1; + attributes_1.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kDrop}); + nlohmann::json j_2; + j_2[prependMatchField(p4orch::kVrfId)] = gVrfName; + j_2[prependMatchField(p4orch::kIpv6Dst)] = kIpv6Prefix; + const std::string db_key_2 = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_IPV6_TABLE_NAME + + kTableKeyDelimiter + j_2.dump(); + std::vector attributes_2; + attributes_2.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetNexthopIdAndMetadata}); + attributes_2.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); + attributes_2.push_back(swss::FieldValueTuple{prependParamField(p4orch::kRouteMetadata), kMetadata1}); + + nlohmann::json j_3; + j_3[prependMatchField(p4orch::kVrfId)] = gVrfName; + j_3[prependMatchField(p4orch::kIpv6Dst)] = kIpv4Prefix2; + const std::string db_key_3 = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_IPV6_TABLE_NAME + + kTableKeyDelimiter + j_3.dump(); + std::vector attributes_3; + attributes_3.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kSetMetadataAndDrop}); + attributes_3.push_back(swss::FieldValueTuple{prependParamField(p4orch::kRouteMetadata), kMetadata2}); + + // Verification should succeed with correct ASIC DB values. + EXPECT_EQ(VerifyState(db_key_1, attributes_1), ""); + EXPECT_EQ(VerifyState(db_key_2, attributes_2), ""); + EXPECT_EQ(VerifyState(db_key_3, attributes_3), ""); + + // Verification should fail if ASIC DB values mismatch. + table.set("SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"10.11.12.0/" + "24\",\"switch_id\":\"oid:0x0\",\"vr\":\"oid:0x6f\"}", + std::vector{ + swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION", "SAI_PACKET_ACTION_FORWARD"}}); + table.set("SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"2001:db8:1::/" + "32\",\"switch_id\":\"oid:0x0\",\"vr\":\"oid:0x6f\"}", + std::vector{swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_META_DATA", "2"}}); + EXPECT_FALSE(VerifyState(db_key_1, attributes_1).empty()); + EXPECT_FALSE(VerifyState(db_key_2, attributes_2).empty()); + + // Verification should fail if ASIC DB table is missing. + table.del("SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"10.11.12.0/" + "24\",\"switch_id\":\"oid:0x0\",\"vr\":\"oid:0x6f\"}"); + table.del("SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"2001:db8:1::/" + "32\",\"switch_id\":\"oid:0x0\",\"vr\":\"oid:0x6f\"}"); + EXPECT_FALSE(VerifyState(db_key_1, attributes_1).empty()); + EXPECT_FALSE(VerifyState(db_key_2, attributes_2).empty()); + table.set("SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"10.11.12.0/" + "24\",\"switch_id\":\"oid:0x0\",\"vr\":\"oid:0x6f\"}", + std::vector{ + swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION", "SAI_PACKET_ACTION_DROP"}, + swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID", "oid:0x0"}}); + table.set("SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"2001:db8:1::/" + "32\",\"switch_id\":\"oid:0x0\",\"vr\":\"oid:0x6f\"}", + std::vector{swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID", "oid:0x1"}, + swss::FieldValueTuple{"SAI_ROUTE_ENTRY_ATTR_META_DATA", "1"}}); } diff --git a/orchagent/p4orch/tests/router_interface_manager_test.cpp b/orchagent/p4orch/tests/router_interface_manager_test.cpp index 661fe33efa20..9c81310e5fbe 100644 --- a/orchagent/p4orch/tests/router_interface_manager_test.cpp +++ b/orchagent/p4orch/tests/router_interface_manager_test.cpp @@ -172,6 +172,11 @@ class RouterInterfaceManagerTest : public ::testing::Test router_intf_manager_.drain(); } + std::string VerifyState(const std::string &key, const std::vector &tuple) + { + return router_intf_manager_.verifyState(key, tuple); + } + ReturnCodeOr DeserializeRouterIntfEntry( const std::string &key, const std::vector &attributes) { @@ -290,7 +295,7 @@ TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceEntryExistsInP4OidMapper p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, kRouterInterfaceOid2); P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId2, kPortName2, kMacAddress2); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, CreateRouterInterface(router_intf_key, router_intf_entry)); auto current_entry = GetRouterInterfaceEntry(router_intf_key); @@ -622,7 +627,7 @@ TEST_F(RouterInterfaceManagerTest, ProcessDeleteRequestInterfaceNotExistInMapper p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRequest(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id))); } @@ -841,3 +846,123 @@ TEST_F(RouterInterfaceManagerTest, DrainInvalidOperation) ValidateRouterInterfaceEntryNotPresent(kRouterInterfaceId1); } + +TEST_F(RouterInterfaceManagerTest, VerifyStateTest) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_ROUTER_INTERFACE:oid:0x295100", + std::vector{ + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID", "oid:0x0"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS", "00:01:02:03:04:05"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_TYPE", "SAI_ROUTER_INTERFACE_TYPE_PORT"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_PORT_ID", "oid:0x112233"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_MTU", "1500"}}); + + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + + APP_P4RT_ROUTER_INTERFACE_TABLE_NAME + kTableKeyDelimiter + kRouterIntfAppDbKey; + std::vector attributes; + + // Verification should succeed with vaild key and value. + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), kPortName1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()}); + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Invalid key should fail verification. + EXPECT_FALSE(VerifyState("invalid", attributes).empty()); + EXPECT_FALSE(VerifyState("invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid:invalid", attributes).empty()); + EXPECT_FALSE( + VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":FIXED_ROUTER_INTERFACE_TABLE:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":FIXED_ROUTER_INTERFACE_TABLE:{\"match/" + "router_interface_id\":\"invalid\"}", + attributes) + .empty()); + + // Invalid attributes should fail verification. + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), kPortName2}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), kPortName1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kMacAddress2.to_string()}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + // Invalid port should fail verification. + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), "invalid"}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + // Verification should fail if interface IDs mismatch. + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), kPortName1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()}); + auto *router_intf_entry_ptr = + GetRouterInterfaceEntry(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id)); + auto saved_ritf_id = router_intf_entry_ptr->router_interface_id; + router_intf_entry_ptr->router_interface_id = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + router_intf_entry_ptr->router_interface_id = saved_ritf_id; + + // Verification should fail if OID mapper mismatches. + p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id)); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); +} + +TEST_F(RouterInterfaceManagerTest, VerifyStateAsicDbTest) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, "Ethernet7", kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, 0x1234, 9100); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_ROUTER_INTERFACE:oid:0x295100", + std::vector{ + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID", "oid:0x0"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS", "00:01:02:03:04:05"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_TYPE", "SAI_ROUTER_INTERFACE_TYPE_PORT"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_PORT_ID", "oid:0x1234"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_MTU", "9100"}}); + + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + + APP_P4RT_ROUTER_INTERFACE_TABLE_NAME + kTableKeyDelimiter + kRouterIntfAppDbKey; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), "Ethernet7"}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()}); + + // Verification should succeed with correct ASIC DB values. + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Verification should fail if ASIC DB values mismatch. + table.set("SAI_OBJECT_TYPE_ROUTER_INTERFACE:oid:0x295100", + std::vector{swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_MTU", "1500"}}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + // Verification should fail if ASIC DB table is missing. + table.del("SAI_OBJECT_TYPE_ROUTER_INTERFACE:oid:0x295100"); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + table.set("SAI_OBJECT_TYPE_ROUTER_INTERFACE:oid:0x295100", + std::vector{ + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID", "oid:0x0"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS", "00:01:02:03:04:05"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_TYPE", "SAI_ROUTER_INTERFACE_TYPE_PORT"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_PORT_ID", "oid:0x1234"}, + swss::FieldValueTuple{"SAI_ROUTER_INTERFACE_ATTR_MTU", "9100"}}); + + // Verification should fail if SAI attr cannot be constructed. + auto *router_intf_entry_ptr = + GetRouterInterfaceEntry(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id)); + router_intf_entry_ptr->port_name = "Ethernet8"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + router_intf_entry_ptr->port_name = "Ethernet7"; +} \ No newline at end of file diff --git a/orchagent/p4orch/tests/wcmp_manager_test.cpp b/orchagent/p4orch/tests/wcmp_manager_test.cpp index b2cc68aaaa70..1c4981b66511 100644 --- a/orchagent/p4orch/tests/wcmp_manager_test.cpp +++ b/orchagent/p4orch/tests/wcmp_manager_test.cpp @@ -23,6 +23,8 @@ extern "C" #include "sai.h" } +using ::p4orch::kTableKeyDelimiter; + extern P4Orch *gP4Orch; extern VRFOrch *gVrfOrch; extern swss::DBConnector *gAppDb; @@ -50,6 +52,7 @@ namespace { constexpr char *kWcmpGroupId1 = "group-1"; +constexpr char *kWcmpGroupId2 = "group-2"; constexpr sai_object_id_t kWcmpGroupOid1 = 10; constexpr char *kNexthopId1 = "ju1u32m1.atl11:qe-3/7"; constexpr sai_object_id_t kNexthopOid1 = 1; @@ -201,6 +204,11 @@ class WcmpManagerTest : public ::testing::Test wcmp_group_manager_->drain(); } + std::string VerifyState(const std::string &key, const std::vector &tuple) + { + return wcmp_group_manager_->verifyState(key, tuple); + } + ReturnCode ProcessAddRequest(P4WcmpGroupEntry *app_db_entry) { return wcmp_group_manager_->processAddRequest(app_db_entry); @@ -301,10 +309,12 @@ P4WcmpGroupEntry WcmpManagerTest::getDefaultWcmpGroupEntryForTest() P4WcmpGroupEntry app_db_entry; app_db_entry.wcmp_group_id = kWcmpGroupId1; std::shared_ptr gm1 = std::make_shared(); + gm1->wcmp_group_id = kWcmpGroupId1; gm1->next_hop_id = kNexthopId1; gm1->weight = 2; app_db_entry.wcmp_group_members.push_back(gm1); std::shared_ptr gm2 = std::make_shared(); + gm2->wcmp_group_id = kWcmpGroupId1; gm2->next_hop_id = kNexthopId2; gm2->weight = 1; app_db_entry.wcmp_group_members.push_back(gm2); @@ -459,7 +469,7 @@ TEST_F(WcmpManagerTest, CreateWcmpGroupFailsWhenCreateGroupMemberSaiCallFailsPlu EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) .WillOnce(Return(SAI_STATUS_SUCCESS)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(&app_db_entry)); } @@ -486,7 +496,7 @@ TEST_F(WcmpManagerTest, CreateWcmpGroupFailsWhenCreateGroupMemberSaiCallFailsPlu EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) .WillOnce(Return(SAI_STATUS_FAILURE)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(&app_db_entry)); } @@ -570,7 +580,7 @@ TEST_F(WcmpManagerTest, RemoveWcmpGroupFailsWhenMemberRemovalFailsPlusRecoveryFa Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, kWcmpGroupOid1, std::placeholders::_1)))) .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_FAILURE))); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, RemoveWcmpGroup(kWcmpGroupId1)); } @@ -760,7 +770,7 @@ TEST_F(WcmpManagerTest, UpdateWcmpGroupFailsWhenRemoveGroupMemberSaiCallFails) Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, kWcmpGroupOid1, std::placeholders::_1)))) .WillOnce(Return(SAI_STATUS_TABLE_FULL)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ("Failed to remove WCMP group member with nexthop id " "'ju1u32m3.atl11:qe-3/7'", ProcessUpdateRequest(&wcmp_group).message()); @@ -886,7 +896,7 @@ TEST_F(WcmpManagerTest, UpdateWcmpGroupFailsWhenCreateNewGroupMemberSaiCallFails .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) .WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ("Failed to create next hop group member 'ju1u32m3.atl11:qe-3/7'", ProcessUpdateRequest(&updated_wcmp_group).message()); // WCMP group is as expected, but refcounts are not @@ -1023,7 +1033,7 @@ TEST_F(WcmpManagerTest, UpdateWcmpGroupFailsWhenIncreaseGroupMemberWeightSaiCall kWcmpGroupOid1, std::placeholders::_1)))) .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_NOT_SUPPORTED))); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ("Failed to create next hop group member " "'ju1u32m2.atl11:qe-3/7'", ProcessUpdateRequest(&wcmp_group).message()); @@ -1462,7 +1472,7 @@ TEST_F(WcmpManagerTest, RestorePrunedNextHopFailsWithNoOidMappingForWcmpGroup) EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1)); - // (TODO): Expect critical state. + // TODO: Expect critical state. RestorePrunedNextHops(port_name); EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); @@ -1482,7 +1492,7 @@ TEST_F(WcmpManagerTest, RestorePrunedNextHopFailsWithNextHopCreationFailure) Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, kWcmpGroupOid1, std::placeholders::_1)))) .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_FAILURE))); - // (TODO): Expect critical state. + // TODO: Expect critical state. RestorePrunedNextHops(port_name); EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); @@ -1762,7 +1772,7 @@ TEST_F(WcmpManagerTest, UpdateWcmpGroupWithOperationallyUpWatchportMemberFailsWi Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, kWcmpGroupOid1, std::placeholders::_1)))) .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_INSUFFICIENT_RESOURCES))); - // (TODO): Expect critical state. + // TODO: Expect critical state. EXPECT_EQ("Failed to create next hop group member " "'ju1u32m2.atl11:qe-3/7'", ProcessUpdateRequest(&updated_app_db_entry).message()); @@ -1837,5 +1847,178 @@ TEST_F(WcmpManagerTest, WatchportStateChangeFromOperUnknownToDownPrunesMemberOnl EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); } + +TEST_F(WcmpManagerTest, VerifyStateTest) +{ + AddWcmpGroupEntryWithWatchport("Ethernet6", true); + nlohmann::json j; + j[prependMatchField(p4orch::kWcmpGroupId)] = kWcmpGroupId1; + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_WCMP_GROUP_TABLE_NAME + + kTableKeyDelimiter + j.dump(); + std::vector attributes; + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_NEXT_HOP_GROUP:oid:0xa", + std::vector{swss::FieldValueTuple{ + "SAI_NEXT_HOP_GROUP_ATTR_TYPE", "SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP"}}); + table.set("SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER:oid:0xb", + std::vector{ + swss::FieldValueTuple{"SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID", "oid:0xa"}, + swss::FieldValueTuple{"SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID", "oid:0x1"}, + swss::FieldValueTuple{"SAI_NEXT_HOP_GROUP_MEMBER_ATTR_WEIGHT", "2"}}); + + // Verification should succeed with vaild key and value. + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[p4orch::kWeight] = 2; + action[p4orch::kWatchPort] = "Ethernet6"; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Invalid key should fail verification. + EXPECT_FALSE(VerifyState("invalid", attributes).empty()); + EXPECT_FALSE(VerifyState("invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":invalid:invalid", attributes).empty()); + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + ":FIXED_WCMP_GROUP_TABLE:invalid", attributes).empty()); + + // Non-existing entry should fail verification. + j[prependMatchField(p4orch::kWcmpGroupId)] = kWcmpGroupId2; + EXPECT_FALSE(VerifyState(std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_WCMP_GROUP_TABLE_NAME + + kTableKeyDelimiter + j.dump(), + attributes) + .empty()); + + // Non-existing nexthop should fail verification. + actions.clear(); + attributes.clear(); + action[prependParamField(p4orch::kNexthopId)] = "invalid"; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + actions.clear(); + attributes.clear(); + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[p4orch::kWeight] = 2; + action[p4orch::kWatchPort] = "Ethernet6"; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_NE(nullptr, wcmp_group_entry_ptr); + + // Verification should fail if WCMP group ID mismatches. + auto saved_wcmp_group_id = wcmp_group_entry_ptr->wcmp_group_id; + wcmp_group_entry_ptr->wcmp_group_id = kWcmpGroupId2; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + wcmp_group_entry_ptr->wcmp_group_id = saved_wcmp_group_id; + + // Verification should fail if WCMP group ID mismatches. + auto saved_wcmp_group_oid = wcmp_group_entry_ptr->wcmp_group_oid; + wcmp_group_entry_ptr->wcmp_group_oid = 1111; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + wcmp_group_entry_ptr->wcmp_group_oid = saved_wcmp_group_oid; + + // Verification should fail if group size mismatches. + wcmp_group_entry_ptr->wcmp_group_members.push_back(std::make_shared()); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + wcmp_group_entry_ptr->wcmp_group_members.pop_back(); + + // Verification should fail if member nexthop ID mismatches. + auto saved_next_hop_id = wcmp_group_entry_ptr->wcmp_group_members[0]->next_hop_id; + wcmp_group_entry_ptr->wcmp_group_members[0]->next_hop_id = kNexthopId3; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + wcmp_group_entry_ptr->wcmp_group_members[0]->next_hop_id = saved_next_hop_id; + + // Verification should fail if member weight mismatches. + auto saved_weight = wcmp_group_entry_ptr->wcmp_group_members[0]->weight; + wcmp_group_entry_ptr->wcmp_group_members[0]->weight = 3; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + wcmp_group_entry_ptr->wcmp_group_members[0]->weight = saved_weight; + + // Verification should fail if member watch port mismatches. + auto saved_watch_port = wcmp_group_entry_ptr->wcmp_group_members[0]->watch_port; + wcmp_group_entry_ptr->wcmp_group_members[0]->watch_port = "invalid"; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + wcmp_group_entry_ptr->wcmp_group_members[0]->watch_port = saved_watch_port; + + // Verification should fail if member WCMP group ID mismatches. + auto saved_member_wcmp_group_id = wcmp_group_entry_ptr->wcmp_group_members[0]->wcmp_group_id; + wcmp_group_entry_ptr->wcmp_group_members[0]->wcmp_group_id = kWcmpGroupId2; + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + wcmp_group_entry_ptr->wcmp_group_members[0]->wcmp_group_id = saved_member_wcmp_group_id; + + // Verification should fail if member OID mapper mismatches. + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER, + kWcmpGroupKey1 + kTableKeyDelimiter + sai_serialize_object_id(kWcmpGroupMemberOid1)); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER, + kWcmpGroupKey1 + kTableKeyDelimiter + sai_serialize_object_id(kWcmpGroupMemberOid1), + kWcmpGroupMemberOid1); +} + +TEST_F(WcmpManagerTest, VerifyStateAsicDbTest) +{ + AddWcmpGroupEntryWithWatchport("Ethernet6", true); + nlohmann::json j; + j[prependMatchField(p4orch::kWcmpGroupId)] = kWcmpGroupId1; + const std::string db_key = std::string(APP_P4RT_TABLE_NAME) + kTableKeyDelimiter + APP_P4RT_WCMP_GROUP_TABLE_NAME + + kTableKeyDelimiter + j.dump(); + std::vector attributes; + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[p4orch::kWeight] = 2; + action[p4orch::kWatchPort] = "Ethernet6"; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + + // Setup ASIC DB. + swss::Table table(nullptr, "ASIC_STATE"); + table.set("SAI_OBJECT_TYPE_NEXT_HOP_GROUP:oid:0xa", + std::vector{swss::FieldValueTuple{ + "SAI_NEXT_HOP_GROUP_ATTR_TYPE", "SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP"}}); + table.set("SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER:oid:0xb", + std::vector{ + swss::FieldValueTuple{"SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID", "oid:0xa"}, + swss::FieldValueTuple{"SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID", "oid:0x1"}, + swss::FieldValueTuple{"SAI_NEXT_HOP_GROUP_MEMBER_ATTR_WEIGHT", "2"}}); + + // Verification should succeed with correct ASIC DB values. + EXPECT_EQ(VerifyState(db_key, attributes), ""); + + // Verification should fail if group values mismatch. + table.set("SAI_OBJECT_TYPE_NEXT_HOP_GROUP:oid:0xa", + std::vector{swss::FieldValueTuple{"SAI_NEXT_HOP_GROUP_ATTR_TYPE", "invalid"}}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + // Verification should fail if group table is missing. + table.del("SAI_OBJECT_TYPE_NEXT_HOP_GROUP:oid:0xa"); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + table.set("SAI_OBJECT_TYPE_NEXT_HOP_GROUP:oid:0xa", + std::vector{swss::FieldValueTuple{ + "SAI_NEXT_HOP_GROUP_ATTR_TYPE", "SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP"}}); + + // Verification should fail if member values mismatch. + table.set("SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER:oid:0xb", + std::vector{swss::FieldValueTuple{"SAI_NEXT_HOP_GROUP_MEMBER_ATTR_WEIGHT", "1"}}); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + + // Verification should fail if member table is missing. + table.del("SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER:oid:0xb"); + EXPECT_FALSE(VerifyState(db_key, attributes).empty()); + table.set("SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER:oid:0xb", + std::vector{ + swss::FieldValueTuple{"SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID", "oid:0xa"}, + swss::FieldValueTuple{"SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID", "oid:0x1"}, + swss::FieldValueTuple{"SAI_NEXT_HOP_GROUP_MEMBER_ATTR_WEIGHT", "2"}}); +} + } // namespace test } // namespace p4orch diff --git a/orchagent/p4orch/wcmp_manager.cpp b/orchagent/p4orch/wcmp_manager.cpp index 6078f9222195..96c0a12eb30d 100644 --- a/orchagent/p4orch/wcmp_manager.cpp +++ b/orchagent/p4orch/wcmp_manager.cpp @@ -4,17 +4,22 @@ #include #include +#include "SaiAttributeList.h" #include "crmorch.h" +#include "dbconnector.h" #include "json.hpp" #include "logger.h" #include "p4orch/p4orch_util.h" #include "portsorch.h" #include "sai_serialize.h" +#include "table.h" extern "C" { #include "sai.h" } +using ::p4orch::kTableKeyDelimiter; + extern sai_object_id_t gSwitchId; extern sai_next_hop_group_api_t *sai_next_hop_group_api; extern CrmOrch *gCrmOrch; @@ -31,8 +36,45 @@ std::string getWcmpGroupMemberKey(const std::string &wcmp_group_key, const sai_o return wcmp_group_key + kTableKeyDelimiter + sai_serialize_object_id(wcmp_member_oid); } +std::vector getSaiGroupAttrs(const P4WcmpGroupEntry &wcmp_group_entry) +{ + std::vector attrs; + sai_attribute_t attr; + + // TODO: Update type to WCMP when SAI supports it. + attr.id = SAI_NEXT_HOP_GROUP_ATTR_TYPE; + attr.value.s32 = SAI_NEXT_HOP_GROUP_TYPE_ECMP; + attrs.push_back(attr); + + return attrs; +} + } // namespace +std::vector WcmpManager::getSaiMemberAttrs(const P4WcmpGroupMemberEntry &wcmp_member_entry, + const sai_object_id_t group_oid) +{ + std::vector attrs; + sai_attribute_t attr; + sai_object_id_t next_hop_oid = SAI_NULL_OBJECT_ID; + m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(wcmp_member_entry.next_hop_id), + &next_hop_oid); + + attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID; + attr.value.oid = group_oid; + attrs.push_back(attr); + + attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID; + attr.value.oid = next_hop_oid; + attrs.push_back(attr); + + attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_WEIGHT; + attr.value.u32 = (uint32_t)wcmp_member_entry.weight; + attrs.push_back(attr); + + return attrs; +} + ReturnCode WcmpManager::validateWcmpGroupEntry(const P4WcmpGroupEntry &app_db_entry) { for (auto &wcmp_group_member : app_db_entry.wcmp_group_members) @@ -173,27 +215,11 @@ ReturnCode WcmpManager::processAddRequest(P4WcmpGroupEntry *app_db_entry) ReturnCode WcmpManager::createWcmpGroupMember(std::shared_ptr wcmp_group_member, const sai_object_id_t group_oid, const std::string &wcmp_group_key) { - std::vector nhgm_attrs; - sai_attribute_t nhgm_attr; - sai_object_id_t next_hop_oid = SAI_NULL_OBJECT_ID; - m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(wcmp_group_member->next_hop_id), - &next_hop_oid); - - nhgm_attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID; - nhgm_attr.value.oid = group_oid; - nhgm_attrs.push_back(nhgm_attr); - - nhgm_attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID; - nhgm_attr.value.oid = next_hop_oid; - nhgm_attrs.push_back(nhgm_attr); - - nhgm_attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_WEIGHT; - nhgm_attr.value.u32 = (uint32_t)wcmp_group_member->weight; - nhgm_attrs.push_back(nhgm_attr); + auto attrs = getSaiMemberAttrs(*wcmp_group_member, group_oid); CHECK_ERROR_AND_LOG_AND_RETURN( sai_next_hop_group_api->create_next_hop_group_member(&wcmp_group_member->member_oid, gSwitchId, - (uint32_t)nhgm_attrs.size(), nhgm_attrs.data()), + (uint32_t)attrs.size(), attrs.data()), "Failed to create next hop group member " << QuotedVar(wcmp_group_member->next_hop_id)); // Update reference count @@ -337,18 +363,10 @@ ReturnCode WcmpManager::processWcmpGroupMemberRemoval(std::shared_ptr nhg_attrs; - - // TODO: Update type to WCMP when SAI supports it. - nhg_attr.id = SAI_NEXT_HOP_GROUP_ATTR_TYPE; - nhg_attr.value.s32 = SAI_NEXT_HOP_GROUP_TYPE_ECMP; - nhg_attrs.push_back(nhg_attr); + auto attrs = getSaiGroupAttrs(*wcmp_group); CHECK_ERROR_AND_LOG_AND_RETURN(sai_next_hop_group_api->create_next_hop_group(&wcmp_group->wcmp_group_oid, gSwitchId, - (uint32_t)nhg_attrs.size(), - nhg_attrs.data()), + (uint32_t)attrs.size(), attrs.data()), "Failed to create next hop group " << QuotedVar(wcmp_group->wcmp_group_id)); // Update reference count const auto &wcmp_group_key = KeyGenerator::generateWcmpGroupKey(wcmp_group->wcmp_group_id); @@ -757,4 +775,200 @@ void WcmpManager::drain() m_entries.clear(); } +std::string WcmpManager::verifyState(const std::string &key, const std::vector &tuple) +{ + SWSS_LOG_ENTER(); + + auto pos = key.find_first_of(kTableKeyDelimiter); + if (pos == std::string::npos) + { + return std::string("Invalid key: ") + key; + } + std::string p4rt_table = key.substr(0, pos); + std::string p4rt_key = key.substr(pos + 1); + if (p4rt_table != APP_P4RT_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + std::string table_name; + std::string key_content; + parseP4RTKey(p4rt_key, &table_name, &key_content); + if (table_name != APP_P4RT_WCMP_GROUP_TABLE_NAME) + { + return std::string("Invalid key: ") + key; + } + + ReturnCode status; + auto app_db_entry_or = deserializeP4WcmpGroupAppDbEntry(key_content, tuple); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + std::stringstream msg; + msg << "Unable to deserialize key " << QuotedVar(key) << ": " << status.message(); + return msg.str(); + } + auto &app_db_entry = *app_db_entry_or; + + auto *wcmp_group_entry = getWcmpGroupEntry(app_db_entry.wcmp_group_id); + if (wcmp_group_entry == nullptr) + { + std::stringstream msg; + msg << "No entry found with key " << QuotedVar(key); + return msg.str(); + } + + std::string cache_result = verifyStateCache(app_db_entry, wcmp_group_entry); + std::string asic_db_result = verifyStateAsicDb(wcmp_group_entry); + if (cache_result.empty()) + { + return asic_db_result; + } + if (asic_db_result.empty()) + { + return cache_result; + } + return cache_result + "; " + asic_db_result; +} + +std::string WcmpManager::verifyStateCache(const P4WcmpGroupEntry &app_db_entry, + const P4WcmpGroupEntry *wcmp_group_entry) +{ + const std::string &wcmp_group_key = KeyGenerator::generateWcmpGroupKey(app_db_entry.wcmp_group_id); + ReturnCode status = validateWcmpGroupEntry(app_db_entry); + if (!status.ok()) + { + std::stringstream msg; + msg << "Validation failed for WCMP group DB entry with key " << QuotedVar(wcmp_group_key) << ": " + << status.message(); + return msg.str(); + } + + if (wcmp_group_entry->wcmp_group_id != app_db_entry.wcmp_group_id) + { + std::stringstream msg; + msg << "WCMP group ID " << QuotedVar(app_db_entry.wcmp_group_id) << " does not match internal cache " + << QuotedVar(wcmp_group_entry->wcmp_group_id) << " in wcmp manager."; + return msg.str(); + } + + std::string err_msg = m_p4OidMapper->verifyOIDMapping(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key, + wcmp_group_entry->wcmp_group_oid); + if (!err_msg.empty()) + { + return err_msg; + } + + if (wcmp_group_entry->wcmp_group_members.size() != app_db_entry.wcmp_group_members.size()) + { + std::stringstream msg; + msg << "WCMP group with ID " << QuotedVar(app_db_entry.wcmp_group_id) << " has member size " + << app_db_entry.wcmp_group_members.size() << " non-matching internal cache " + << wcmp_group_entry->wcmp_group_members.size(); + return msg.str(); + } + + for (size_t i = 0; i < wcmp_group_entry->wcmp_group_members.size(); ++i) + { + if (wcmp_group_entry->wcmp_group_members[i]->next_hop_id != app_db_entry.wcmp_group_members[i]->next_hop_id) + { + std::stringstream msg; + msg << "WCMP group member " << QuotedVar(app_db_entry.wcmp_group_members[i]->next_hop_id) + << " does not match internal cache " << QuotedVar(wcmp_group_entry->wcmp_group_members[i]->next_hop_id) + << " in wcmp manager."; + return msg.str(); + } + if (wcmp_group_entry->wcmp_group_members[i]->weight != app_db_entry.wcmp_group_members[i]->weight) + { + std::stringstream msg; + msg << "WCMP group member " << QuotedVar(app_db_entry.wcmp_group_members[i]->next_hop_id) << " weight " + << app_db_entry.wcmp_group_members[i]->weight << " does not match internal cache " + << wcmp_group_entry->wcmp_group_members[i]->weight << " in wcmp manager."; + return msg.str(); + } + if (wcmp_group_entry->wcmp_group_members[i]->watch_port != app_db_entry.wcmp_group_members[i]->watch_port) + { + std::stringstream msg; + msg << "WCMP group member " << QuotedVar(app_db_entry.wcmp_group_members[i]->next_hop_id) << " watch port " + << QuotedVar(app_db_entry.wcmp_group_members[i]->watch_port) << " does not match internal cache " + << QuotedVar(wcmp_group_entry->wcmp_group_members[i]->watch_port) << " in wcmp manager."; + return msg.str(); + } + if (wcmp_group_entry->wcmp_group_members[i]->wcmp_group_id != app_db_entry.wcmp_group_members[i]->wcmp_group_id) + { + std::stringstream msg; + msg << "WCMP group member " << QuotedVar(app_db_entry.wcmp_group_members[i]->next_hop_id) << " group ID " + << QuotedVar(app_db_entry.wcmp_group_members[i]->wcmp_group_id) << " does not match internal cache " + << QuotedVar(wcmp_group_entry->wcmp_group_members[i]->wcmp_group_id) << " in wcmp manager."; + return msg.str(); + } + // Group member might not be created if it is a watch port. + if (!app_db_entry.wcmp_group_members[i]->watch_port.empty() && + wcmp_group_entry->wcmp_group_members[i]->member_oid == SAI_NULL_OBJECT_ID) + { + continue; + } + err_msg = m_p4OidMapper->verifyOIDMapping( + SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER, + getWcmpGroupMemberKey(wcmp_group_key, wcmp_group_entry->wcmp_group_members[i]->member_oid), + wcmp_group_entry->wcmp_group_members[i]->member_oid); + if (!err_msg.empty()) + { + return err_msg; + } + } + + return ""; +} + +std::string WcmpManager::verifyStateAsicDb(const P4WcmpGroupEntry *wcmp_group_entry) +{ + swss::DBConnector db("ASIC_DB", 0); + swss::Table table(&db, "ASIC_STATE"); + + auto group_attrs = getSaiGroupAttrs(*wcmp_group_entry); + std::vector exp = saimeta::SaiAttributeList::serialize_attr_list( + SAI_OBJECT_TYPE_NEXT_HOP_GROUP, (uint32_t)group_attrs.size(), group_attrs.data(), /*countOnly=*/false); + std::string key = sai_serialize_object_type(SAI_OBJECT_TYPE_NEXT_HOP_GROUP) + ":" + + sai_serialize_object_id(wcmp_group_entry->wcmp_group_oid); + std::vector values; + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + auto group_result = verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); + if (!group_result.empty()) + { + return group_result; + } + + for (const auto &member : wcmp_group_entry->wcmp_group_members) + { + // Group member might not be created if it is a watch port. + if (!member->watch_port.empty() && member->member_oid == SAI_NULL_OBJECT_ID) + { + continue; + } + auto member_attrs = getSaiMemberAttrs(*member, wcmp_group_entry->wcmp_group_oid); + std::vector exp = saimeta::SaiAttributeList::serialize_attr_list( + SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER, (uint32_t)member_attrs.size(), member_attrs.data(), + /*countOnly=*/false); + std::string key = sai_serialize_object_type(SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER) + ":" + + sai_serialize_object_id(member->member_oid); + std::vector values; + if (!table.get(key, values)) + { + return std::string("ASIC DB key not found ") + key; + } + auto member_result = verifyAttrs(values, exp, std::vector{}, + /*allow_unknown=*/false); + if (!member_result.empty()) + { + return member_result; + } + } + + return ""; +} + } // namespace p4orch diff --git a/orchagent/p4orch/wcmp_manager.h b/orchagent/p4orch/wcmp_manager.h index 4c6629a39817..d1a6e025bc32 100644 --- a/orchagent/p4orch/wcmp_manager.h +++ b/orchagent/p4orch/wcmp_manager.h @@ -77,6 +77,7 @@ class WcmpManager : public ObjectManagerInterface void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; void drain() override; + std::string verifyState(const std::string &key, const std::vector &tuple) override; // Prunes next hop members egressing through the given port. void pruneNextHops(const std::string &port); @@ -154,6 +155,16 @@ class WcmpManager : public ObjectManagerInterface // Gets port oper-status from port_oper_status_map if present bool getPortOperStatusFromMap(const std::string &port, sai_port_oper_status_t *status); + // Verifies the internal cache for an entry. + std::string verifyStateCache(const P4WcmpGroupEntry &app_db_entry, const P4WcmpGroupEntry *wcmp_group_entry); + + // Verifies the ASIC DB for an entry. + std::string verifyStateAsicDb(const P4WcmpGroupEntry *wcmp_group_entry); + + // Returns the SAI attributes for a group member. + std::vector getSaiMemberAttrs(const P4WcmpGroupMemberEntry &wcmp_member_entry, + const sai_object_id_t group_oid); + // Maps wcmp_group_id to P4WcmpGroupEntry std::unordered_map m_wcmpGroupTable; diff --git a/orchagent/return_code.h b/orchagent/return_code.h index 87a1a761e15c..ed154784b75d 100644 --- a/orchagent/return_code.h +++ b/orchagent/return_code.h @@ -145,6 +145,21 @@ using swss::StatusCode; return RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL_RC_; \ } while (0) +#define SAI_RANGED_STATUS_IS_INVALID_ATTRIBUTE(x) \ + ((SAI_STATUS_CODE(x) & ~(0xFFFFL)) == SAI_STATUS_CODE(SAI_STATUS_INVALID_ATTRIBUTE_0)) + +#define SAI_RANGED_STATUS_IS_INVALID_ATTR_VALUE(x) \ + ((SAI_STATUS_CODE(x) & ~(0xFFFFL)) == SAI_STATUS_CODE(SAI_STATUS_INVALID_ATTR_VALUE_0)) + +#define SAI_RANGED_STATUS_IS_ATTR_NOT_IMPLEMENTED(x) \ + ((SAI_STATUS_CODE(x) & ~(0xFFFFL)) == SAI_STATUS_CODE(SAI_STATUS_ATTR_NOT_IMPLEMENTED_0)) + +#define SAI_RANGED_STATUS_IS_UNKNOWN_ATTRIBUTE(x) \ + ((SAI_STATUS_CODE(x) & ~(0xFFFFL)) == SAI_STATUS_CODE(SAI_STATUS_UNKNOWN_ATTRIBUTE_0)) + +#define SAI_RANGED_STATUS_IS_ATTR_NOT_SUPPORTED(x) \ + ((SAI_STATUS_CODE(x) & ~(0xFFFFL)) == SAI_STATUS_CODE(SAI_STATUS_ATTR_NOT_SUPPORTED_0)) + class ReturnCode { public: @@ -164,7 +179,31 @@ class ReturnCode { if (m_saiStatusCodeLookup.find(status) == m_saiStatusCodeLookup.end()) { - status_ = StatusCode::SWSS_RC_UNKNOWN; + // Check for ranged SAI codes. + if (SAI_RANGED_STATUS_IS_INVALID_ATTRIBUTE(status)) + { + status_ = StatusCode::SWSS_RC_INVALID_PARAM; + } + else if (SAI_RANGED_STATUS_IS_INVALID_ATTR_VALUE(status)) + { + status_ = StatusCode::SWSS_RC_INVALID_PARAM; + } + else if (SAI_RANGED_STATUS_IS_ATTR_NOT_IMPLEMENTED(status)) + { + status_ = StatusCode::SWSS_RC_UNIMPLEMENTED; + } + else if (SAI_RANGED_STATUS_IS_UNKNOWN_ATTRIBUTE(status)) + { + status_ = StatusCode::SWSS_RC_INVALID_PARAM; + } + else if (SAI_RANGED_STATUS_IS_ATTR_NOT_SUPPORTED(status)) + { + status_ = StatusCode::SWSS_RC_UNIMPLEMENTED; + } + else + { + status_ = StatusCode::SWSS_RC_UNKNOWN; + } } else { @@ -259,7 +298,7 @@ class ReturnCode } private: - // SAI codes that are not included in this lookup map will map to + // Non-ranged SAI codes that are not included in this lookup map will map to // SWSS_RC_UNKNOWN. This includes the general SAI failure: SAI_STATUS_FAILURE. std::unordered_map m_saiStatusCodeLookup = { {SAI_STATUS_SUCCESS, StatusCode::SWSS_RC_SUCCESS}, diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 172b74b55961..6ab857067668 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -71,6 +71,7 @@ sai_srv6_api_t** sai_srv6_api;; sai_l2mc_group_api_t* sai_l2mc_group_api; sai_counter_api_t* sai_counter_api; sai_bfd_api_t* sai_bfd_api; +sai_my_mac_api_t* sai_my_mac_api; extern sai_object_id_t gSwitchId; extern bool gSairedisRecord; @@ -199,6 +200,7 @@ void initSaiApi() sai_api_query(SAI_API_L2MC_GROUP, (void **)&sai_l2mc_group_api); sai_api_query(SAI_API_COUNTER, (void **)&sai_counter_api); sai_api_query(SAI_API_BFD, (void **)&sai_bfd_api); + sai_api_query(SAI_API_MY_MAC, (void **)&sai_my_mac_api); sai_log_set(SAI_API_SWITCH, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BRIDGE, SAI_LOG_LEVEL_NOTICE); @@ -236,6 +238,7 @@ void initSaiApi() sai_log_set(SAI_API_L2MC_GROUP, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_COUNTER, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BFD, SAI_LOG_LEVEL_NOTICE); + sai_log_set(SAI_API_MY_MAC, SAI_LOG_LEVEL_NOTICE); } void initSaiRedis(const string &record_location, const std::string &record_filename) diff --git a/orchagent/switchorch.cpp b/orchagent/switchorch.cpp index 2ca291c1625d..951358774f6a 100644 --- a/orchagent/switchorch.cpp +++ b/orchagent/switchorch.cpp @@ -93,16 +93,14 @@ void SwitchOrch::initAclGroupsBindToSwitch() // Create an ACL group per stage, INGRESS, EGRESS and PRE_INGRESS for (auto stage_it : aclStageLookup) { - sai_object_id_t group_oid; - auto status = createAclGroup(fvValue(stage_it), &group_oid); + auto status = createAclGroup(fvValue(stage_it), &m_aclGroups[fvValue(stage_it)]); if (!status.ok()) { status.prepend("Failed to create ACL group for stage " + fvField(stage_it) + ": "); SWSS_LOG_THROW("%s", status.message().c_str()); } SWSS_LOG_NOTICE("Created ACL group for stage %s", fvField(stage_it).c_str()); - m_aclGroups[fvValue(stage_it)] = group_oid; - status = bindAclGroupToSwitch(fvValue(stage_it), group_oid); + status = bindAclGroupToSwitch(fvValue(stage_it), m_aclGroups[fvValue(stage_it)]); if (!status.ok()) { status.prepend("Failed to bind ACL group to stage " + fvField(stage_it) + ": "); @@ -111,12 +109,12 @@ void SwitchOrch::initAclGroupsBindToSwitch() } } -const std::map &SwitchOrch::getAclGroupOidsBindingToSwitch() +std::map &SwitchOrch::getAclGroupsBindingToSwitch() { return m_aclGroups; } -ReturnCode SwitchOrch::createAclGroup(const sai_acl_stage_t &group_stage, sai_object_id_t *acl_grp_oid) +ReturnCode SwitchOrch::createAclGroup(const sai_acl_stage_t &group_stage, referenced_object *acl_grp) { SWSS_LOG_ENTER(); @@ -137,8 +135,9 @@ ReturnCode SwitchOrch::createAclGroup(const sai_acl_stage_t &group_stage, sai_ob acl_grp_attr.value.s32list.list = bpoint_list.data(); acl_grp_attrs.push_back(acl_grp_attr); - CHECK_ERROR_AND_LOG_AND_RETURN(sai_acl_api->create_acl_table_group( - acl_grp_oid, gSwitchId, (uint32_t)acl_grp_attrs.size(), acl_grp_attrs.data()), + CHECK_ERROR_AND_LOG_AND_RETURN(sai_acl_api->create_acl_table_group(&acl_grp->m_saiObjectId, gSwitchId, + (uint32_t)acl_grp_attrs.size(), + acl_grp_attrs.data()), "Failed to create ACL group for stage " << group_stage); if (group_stage == SAI_ACL_STAGE_INGRESS || group_stage == SAI_ACL_STAGE_PRE_INGRESS || group_stage == SAI_ACL_STAGE_EGRESS) @@ -146,12 +145,12 @@ ReturnCode SwitchOrch::createAclGroup(const sai_acl_stage_t &group_stage, sai_ob gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_GROUP, (sai_acl_stage_t)group_stage, SAI_ACL_BIND_POINT_TYPE_SWITCH); } - SWSS_LOG_INFO("Suceeded to create ACL group %s in stage %d ", sai_serialize_object_id(*acl_grp_oid).c_str(), - group_stage); + SWSS_LOG_INFO("Suceeded to create ACL group %s in stage %d ", + sai_serialize_object_id(acl_grp->m_saiObjectId).c_str(), group_stage); return ReturnCode(); } -ReturnCode SwitchOrch::bindAclGroupToSwitch(const sai_acl_stage_t &group_stage, const sai_object_id_t &acl_grp_oid) +ReturnCode SwitchOrch::bindAclGroupToSwitch(const sai_acl_stage_t &group_stage, const referenced_object &acl_grp) { SWSS_LOG_ENTER(); @@ -159,17 +158,17 @@ ReturnCode SwitchOrch::bindAclGroupToSwitch(const sai_acl_stage_t &group_stage, if (switch_attr_it == aclStageToSwitchAttrLookup.end()) { LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) - << "Failed to set ACL group(" << acl_grp_oid << ") to the SWITCH bind point at stage " - << group_stage); + << "Failed to set ACL group(" << acl_grp.m_saiObjectId + << ") to the SWITCH bind point at stage " << group_stage); } sai_attribute_t attr; attr.id = switch_attr_it->second; - attr.value.oid = acl_grp_oid; + attr.value.oid = acl_grp.m_saiObjectId; auto sai_status = sai_switch_api->set_switch_attribute(gSwitchId, &attr); if (sai_status != SAI_STATUS_SUCCESS) { LOG_ERROR_AND_RETURN(ReturnCode(sai_status) << "[SAI] Failed to set_switch_attribute with attribute.id=" - << attr.id << " and acl group oid=" << acl_grp_oid); + << attr.id << " and acl group oid=" << acl_grp.m_saiObjectId); } return ReturnCode(); } diff --git a/orchagent/switchorch.h b/orchagent/switchorch.h index e1e39e5b8549..87e6b1a309cc 100644 --- a/orchagent/switchorch.h +++ b/orchagent/switchorch.h @@ -37,7 +37,7 @@ class SwitchOrch : public Orch // Return reference to ACL group created for each stage and the bind point is // the switch - const std::map &getAclGroupOidsBindingToSwitch(); + std::map &getAclGroupsBindingToSwitch(); // Initialize the ACL groups bind to Switch void initAclGroupsBindToSwitch(); @@ -57,17 +57,17 @@ class SwitchOrch : public Orch // Create the default ACL group for the given stage, bind point is // SAI_ACL_BIND_POINT_TYPE_SWITCH and group type is // SAI_ACL_TABLE_GROUP_TYPE_PARALLEL. - ReturnCode createAclGroup(const sai_acl_stage_t &group_stage, sai_object_id_t *acl_grp_oid); + ReturnCode createAclGroup(const sai_acl_stage_t &group_stage, referenced_object *acl_grp); // Bind the ACL group to switch for the given stage. // Set the SAI_SWITCH_ATTR_{STAGE}_ACL with the group oid. - ReturnCode bindAclGroupToSwitch(const sai_acl_stage_t &group_stage, const sai_object_id_t &acl_grp_oid); + ReturnCode bindAclGroupToSwitch(const sai_acl_stage_t &group_stage, const referenced_object &acl_grp); swss::NotificationConsumer* m_restartCheckNotificationConsumer; void doTask(swss::NotificationConsumer& consumer); swss::DBConnector *m_db; swss::Table m_switchTable; - std::map m_aclGroups; + std::map m_aclGroups; sai_object_id_t m_switchTunnelId; // ASIC temperature sensors diff --git a/tests/gcov_support.sh b/tests/gcov_support.sh index b9d334bd1672..c7ddddb961b5 100755 --- a/tests/gcov_support.sh +++ b/tests/gcov_support.sh @@ -247,6 +247,7 @@ gcov_support_generate_report() pushd common_work find . -name "*.gcda" -o -name "*.gz" -o -name "*.info" | xargs rm -rf popd + rm -rf ${container_id} done < container_dir_list # generate report with code diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index dedd4445f716..3546e6f93425 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -116,7 +116,9 @@ tests_SOURCES += $(P4_ORCH_DIR)/p4orch.cpp \ $(P4_ORCH_DIR)/acl_table_manager.cpp \ $(P4_ORCH_DIR)/acl_rule_manager.cpp \ $(P4_ORCH_DIR)/wcmp_manager.cpp \ - $(P4_ORCH_DIR)/mirror_session_manager.cpp + $(P4_ORCH_DIR)/mirror_session_manager.cpp \ + $(P4_ORCH_DIR)/gre_tunnel_manager.cpp \ + $(P4_ORCH_DIR)/l3_admit_manager.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/mock_tests/mock_dbconnector.cpp b/tests/mock_tests/mock_dbconnector.cpp index 362e1446d3e0..7cabdc2224fe 100644 --- a/tests/mock_tests/mock_dbconnector.cpp +++ b/tests/mock_tests/mock_dbconnector.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "dbconnector.h" namespace swss diff --git a/tests/p4rt/acl.py b/tests/p4rt/acl.py index 283ba95ce665..6623bb3fcfeb 100644 --- a/tests/p4rt/acl.py +++ b/tests/p4rt/acl.py @@ -15,6 +15,7 @@ class P4RtAclTableDefinitionWrapper(util.DBInterface): SAI_ATTR_MATCH_ETHER_TYPE = "SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE" SAI_ATTR_MATCH_IP_TYPE = "SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE" SAI_ATTR_MATCH_DST_MAC = "SAI_ACL_TABLE_ATTR_FIELD_DST_MAC" + SAI_ATTR_MATCH_ROUTE_DST_USER_META = "SAI_ACL_TABLE_ATTR_FIELD_ROUTE_DST_USER_META" SAI_ATTR_MATCH_SRC_IPV6_WORD3 = "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD3" SAI_ATTR_MATCH_SRC_IPV6_WORD2 = "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2" SAI_ATTR_MATCH_UDF_GROUP_MIN = "SAI_ACL_TABLE_ATTR_USER_DEFINED_FIELD_GROUP_MIN" @@ -31,6 +32,7 @@ class P4RtAclTableDefinitionWrapper(util.DBInterface): SIZE_FIELD = "size" MATCH_FIELD_ETHER_TYPE = "match/ether_type" MATCH_FIELD_ETHER_DST = "match/ether_dst" + MATCH_FIELD_L3_CLASS_ID = "match/l3_class_id" MATCH_FIELD_IS_IP = "match/is_ip" MATCH_FIELD_IS_IPV4 = "match/is_ipv4" MATCH_FIELD_IS_IPV6 = "match/is_ipv6" @@ -57,6 +59,7 @@ class P4RtAclRuleWrapper(util.DBInterface): SAI_ATTR_MATCH_ETHER_TYPE = "SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE" SAI_ATTR_MATCH_IP_TYPE = "SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE" SAI_ATTR_MATCH_DST_MAC = "SAI_ACL_ENTRY_ATTR_FIELD_DST_MAC" + SAI_ATTR_MATCH_ROUTE_DST_USER_META = "SAI_ACL_ENTRY_ATTR_FIELD_ROUTE_DST_USER_META" SAI_ATTR_MATCH_SRC_IPV6_WORD3 = "SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6_WORD3" SAI_ATTR_MATCH_SRC_IPV6_WORD2 = "SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6_WORD2" SAI_ATTR_MATCH_UDF_GROUP_MIN = "SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN" diff --git a/tests/p4rt/l3.py b/tests/p4rt/l3.py index c5f656aa2efd..31cd0b3b95c1 100644 --- a/tests/p4rt/l3.py +++ b/tests/p4rt/l3.py @@ -28,28 +28,141 @@ class P4RtRouterInterfaceWrapper(util.DBInterface): DEFAULT_SRC_MAC = "00:11:22:33:44:55" DEFAULT_ACTION = "set_port_and_src_mac" + # Fetch oid of the first newly created rif from created rif in ASIC + # db. This API should only be used when only one oid is expected to be + # created after the original entries. + # Original rif entries in asic db must be fetched using + # 'get_original_redis_entries' before fetching oid of newly created rif. + def get_newly_created_router_interface_oid(self, known_oids=set()): + rif_oid = None + rif_entries = util.get_keys(self.asic_db, self.ASIC_DB_TBL_NAME) + for key in rif_entries: + if ( + key + not in self._original_entries[ + "{}:{}".format(self.asic_db, self.ASIC_DB_TBL_NAME) + ] + and + key not in known_oids + ): + rif_oid = key + break + return rif_oid + def generate_app_db_key(self, router_interface_id): d = {} - d[util.prepend_match_field("router_interface_id") - ] = router_interface_id + d[util.prepend_match_field("router_interface_id")] = router_interface_id key = json.dumps(d, separators=(",", ":")) return self.TBL_NAME + ":" + key # create default router interface - def create_router_interface(self, - router_interace_id=None, port_id=None, - src_mac=None, action=None): + def create_router_interface( + self, router_interace_id=None, port_id=None, src_mac=None, action=None + ): router_interface_id = router_interace_id or self.DEFAULT_ROUTER_INTERFACE_ID port_id = port_id or self.DEFAULT_PORT_ID src_mac = src_mac or self.DEFAULT_SRC_MAC action = action or self.DEFAULT_ACTION - attr_list = [(util.prepend_param_field(self.PORT_FIELD), port_id), - (util.prepend_param_field(self.SRC_MAC_FIELD), src_mac), - (self.ACTION_FIELD, action)] + attr_list = [ + (util.prepend_param_field(self.PORT_FIELD), port_id), + (util.prepend_param_field(self.SRC_MAC_FIELD), src_mac), + (self.ACTION_FIELD, action), + ] router_intf_key = self.generate_app_db_key(router_interface_id) self.set_app_db_entry(router_intf_key, attr_list) return router_interface_id, router_intf_key, attr_list +class P4RtGreTunnelWrapper(util.DBInterface): + """Interface to interact with APP DB and ASIC DB tables for P4RT GRE Tunnel object.""" + + # database and SAI constants + APP_DB_TBL_NAME = swsscommon.APP_P4RT_TABLE_NAME + TBL_NAME = "FIXED_TUNNEL_TABLE" + ASIC_DB_TBL_NAME = "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL" + SAI_ATTR_TYPE = "SAI_TUNNEL_ATTR_TYPE" + SAI_ATTR_PEER_MODE = "SAI_TUNNEL_ATTR_PEER_MODE" + SAI_ATTR_UNDERLAY_INTERFACE = "SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE" + SAI_ATTR_OVERLAY_INTERFACE = "SAI_TUNNEL_ATTR_OVERLAY_INTERFACE" + SAI_ATTR_ENCAP_SRC_IP = "SAI_TUNNEL_ATTR_ENCAP_SRC_IP" + SAI_ATTR_ENCAP_DST_IP = "SAI_TUNNEL_ATTR_ENCAP_DST_IP" + + # attribute fields for tunnel object + ROUTER_ROUTER_INTERFACE_ID_FIELD = "router_interface_id" + ENCAP_SRC_IP_FIELD = "encap_src_ip" + ENCAP_DST_IP_FIELD = "encap_dst_ip" + + # default tunnel attribute values + DEFAULT_TUNNEL_ID = "tunnel-1" + DEFAULT_ROUTER_INTERFACE_ID = "16" + DEFAULT_ENCAP_SRC_IP = "1.2.3.4" + DEFAULT_ENCAP_DST_IP = "5.6.7.8" + DEFAULT_ACTION = "mark_for_tunnel_encap" + + def generate_app_db_key(self, tunnel_id): + d = {} + d[util.prepend_match_field("tunnel_id")] = tunnel_id + key = json.dumps(d, separators=(",", ":")) + return self.TBL_NAME + ":" + key + + # create default tunnel + def create_gre_tunnel( + self, tunnel_id=None, router_interface_id=None, encap_src_ip=None, encap_dst_ip=None, action=None + ): + tunnel_id = tunnel_id or self.DEFAULT_TUNNEL_ID + router_interface_id = router_interface_id or self.DEFAULT_ROUTER_INTERFACE_ID + encap_src_ip = encap_src_ip or self.DEFAULT_ENCAP_SRC_IP + encap_dst_ip = encap_dst_ip or self.DEFAULT_ENCAP_DST_IP + action = action or self.DEFAULT_ACTION + attr_list = [ + (util.prepend_param_field(self.ROUTER_ROUTER_INTERFACE_ID_FIELD), router_interface_id), + (util.prepend_param_field(self.ENCAP_SRC_IP_FIELD), encap_src_ip), + (util.prepend_param_field(self.ENCAP_DST_IP_FIELD), encap_dst_ip), + (self.ACTION_FIELD, action), + ] + tunnel_key = self.generate_app_db_key(tunnel_id) + self.set_app_db_entry(tunnel_key, attr_list) + return tunnel_id, tunnel_key, attr_list + + # Fetch oid of the first newly created tunnel from created tunnels in ASIC + # db. This API should only be used when only one oid is expected to be + # created after the original entries. + # Original tunnel entries in asic db must be fetched using + # 'get_original_redis_entries' before fetching oid of newly created tunnel. + def get_newly_created_tunnel_oid(self): + tunnel_oid = None + tunnel_entries = util.get_keys(self.asic_db, self.ASIC_DB_TBL_NAME) + for key in tunnel_entries: + if ( + key + not in self._original_entries[ + "{}:{}".format(self.asic_db, self.ASIC_DB_TBL_NAME) + ] + ): + tunnel_oid = key + break + return tunnel_oid + + def get_original_appl_db_entries_count(self): + return len( + self._original_entries[ + "%s:%s" % (self.appl_db, (self.APP_DB_TBL_NAME + ":" + self.TBL_NAME)) + ] + ) + + def get_original_appl_state_db_entries_count(self): + return len( + self._original_entries[ + "%s:%s" + % (self.appl_state_db, (self.APP_DB_TBL_NAME + ":" + self.TBL_NAME)) + ] + ) + + def get_original_asic_db_entries_count(self): + return len( + self._original_entries[ + "%s:%s" % (self.asic_db, self.ASIC_DB_TBL_NAME) + ] + ) class P4RtNeighborWrapper(util.DBInterface): """Interface to interact with APP DB and ASIC DB tables for P4RT neighbor object.""" @@ -72,24 +185,31 @@ class P4RtNeighborWrapper(util.DBInterface): def generate_app_db_key(self, router_interface_id, neighbor_id): d = {} - d[util.prepend_match_field("router_interface_id") - ] = router_interface_id + d[util.prepend_match_field("router_interface_id")] = router_interface_id d[util.prepend_match_field("neighbor_id")] = neighbor_id key = json.dumps(d, separators=(",", ":")) return self.TBL_NAME + ":" + key # create default neighbor - def create_neighbor(self, router_interface_id=None, neighbor_id=None, - dst_mac=None, action=None, ipv4=True): + def create_neighbor( + self, + router_interface_id=None, + neighbor_id=None, + dst_mac=None, + action=None, + ipv4=True, + ): router_interface_id = router_interface_id or self.DEFAULT_ROUTER_INTERFACE_ID - neighbor_id = neighbor_id or (self.DEFAULT_IPV4_NEIGHBOR_ID if ipv4 - else self.DEFAULT_IPV6_NEIGHBOR_ID) + neighbor_id = neighbor_id or ( + self.DEFAULT_IPV4_NEIGHBOR_ID if ipv4 else self.DEFAULT_IPV6_NEIGHBOR_ID + ) dst_mac = dst_mac or self.DEFAULT_DST_MAC action = action or self.DEFAULT_ACTION - attr_list = [(util.prepend_param_field(self.DST_MAC_FIELD), dst_mac), - (self.ACTION_FIELD, action)] - neighbor_key = self.generate_app_db_key( - router_interface_id, neighbor_id) + attr_list = [ + (util.prepend_param_field(self.DST_MAC_FIELD), dst_mac), + (self.ACTION_FIELD, action), + ] + neighbor_key = self.generate_app_db_key(router_interface_id, neighbor_id) self.set_app_db_entry(neighbor_key, attr_list) return neighbor_id, neighbor_key, attr_list @@ -103,38 +223,57 @@ class P4RtNextHopWrapper(util.DBInterface): ASIC_DB_TBL_NAME = "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP" SAI_ATTR_TYPE = "SAI_NEXT_HOP_ATTR_TYPE" SAI_ATTR_IP = "SAI_NEXT_HOP_ATTR_IP" + SAI_ATTR_TUNNEL_ENCAP = "SAI_NEXT_HOP_TYPE_TUNNEL_ENCAP" SAI_ATTR_ROUTER_INTF_OID = "SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID" + SAI_ATTR_TUNNEL_OID = "SAI_NEXT_HOP_ATTR_TUNNEL_ID" # attribute fields for nexthop object RIF_FIELD = "router_interface_id" NEIGHBOR_ID_FIELD = "neighbor_id" + TUNNEL_ID_FIELD = "tunnel_id" # default next hop attribute values - DEFAULT_ACTION = "set_nexthop" + DEFAULT_ACTION = "set_ip_nexthop" DEFAULT_NEXTHOP_ID = "8" DEFAULT_ROUTER_INTERFACE_ID = "16" DEFAULT_IPV4_NEIGHBOR_ID = "12.0.0.1" DEFAULT_IPV6_NEIGHBOR_ID = "fe80::21a:11ff:fe17:5f80" + # tunnel nexthop attribute values + TUNNEL_ACTION = "set_tunnel_encap_nexthop" + DEFAULT_TUNNEL_ID = "tunnel-1" + def generate_app_db_key(self, nexthop_id): d = {} d[util.prepend_match_field("nexthop_id")] = nexthop_id key = json.dumps(d, separators=(",", ":")) return self.TBL_NAME + ":" + key - # create default next hop - def create_next_hop(self, router_interface_id=None, neighbor_id=None, - action=None, nexthop_id=None, ipv4=True): - action = action or self.DEFAULT_ACTION + # create next hop + def create_next_hop( + self, + router_interface_id=None, + neighbor_id=None, + action=None, + nexthop_id=None, + ipv4=True, + tunnel_id=None, + ): + action = action or (self.DEFAULT_ACTION if tunnel_id == None else self.TUNNEL_ACTION) router_interface_id = router_interface_id or self.DEFAULT_ROUTER_INTERFACE_ID if ipv4 is True: neighbor_id = neighbor_id or self.DEFAULT_IPV4_NEIGHBOR_ID else: neighbor_id = neighbor_id or self.DEFAULT_IPV6_NEIGHBOR_ID nexthop_id = nexthop_id or self.DEFAULT_NEXTHOP_ID - attr_list = [(util.prepend_param_field(self.RIF_FIELD), router_interface_id), - (util.prepend_param_field(self.NEIGHBOR_ID_FIELD), neighbor_id), - (self.ACTION_FIELD, action)] + attr_list = [ + (util.prepend_param_field(self.NEIGHBOR_ID_FIELD), neighbor_id), + (self.ACTION_FIELD, action), + ] + if action == self.DEFAULT_ACTION: + attr_list.append((util.prepend_param_field(self.RIF_FIELD), router_interface_id)) + if tunnel_id != None: + attr_list.append((util.prepend_param_field(self.TUNNEL_ID_FIELD), tunnel_id)) nexthop_key = self.generate_app_db_key(nexthop_id) self.set_app_db_entry(nexthop_key, attr_list) return nexthop_id, nexthop_key, attr_list @@ -148,12 +287,37 @@ def get_newly_created_nexthop_oid(self): nexthop_oid = None nexthop_entries = util.get_keys(self.asic_db, self.ASIC_DB_TBL_NAME) for key in nexthop_entries: - if key not in self._original_entries["{}:{}".format(self.asic_db, - self.ASIC_DB_TBL_NAME)]: + if ( + key + not in self._original_entries[ + "{}:{}".format(self.asic_db, self.ASIC_DB_TBL_NAME) + ] + ): nexthop_oid = key break return nexthop_oid + def get_original_appl_db_entries_count(self): + return len( + self._original_entries[ + "%s:%s" % (self.appl_db, (self.APP_DB_TBL_NAME + ":" + self.TBL_NAME)) + ] + ) + + def get_original_appl_state_db_entries_count(self): + return len( + self._original_entries[ + "%s:%s" + % (self.appl_state_db, (self.APP_DB_TBL_NAME + ":" + self.TBL_NAME)) + ] + ) + + def get_original_asic_db_entries_count(self): + return len( + self._original_entries[ + "%s:%s" % (self.asic_db, self.ASIC_DB_TBL_NAME) + ] + ) class P4RtWcmpGroupWrapper(util.DBInterface): """Interface to interact with APP DB and ASIC DB tables for P4RT wcmp group object.""" @@ -163,9 +327,13 @@ class P4RtWcmpGroupWrapper(util.DBInterface): TBL_NAME = swsscommon.APP_P4RT_WCMP_GROUP_TABLE_NAME ASIC_DB_GROUP_TBL_NAME = "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP" SAI_ATTR_GROUP_TYPE = "SAI_NEXT_HOP_GROUP_ATTR_TYPE" - SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP = "SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP" + SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP = ( + "SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP" + ) ASIC_DB_GROUP_MEMBER_TBL_NAME = "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER" - SAI_ATTR_GROUP_MEMBER_NEXTHOP_GROUP_ID = "SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID" + SAI_ATTR_GROUP_MEMBER_NEXTHOP_GROUP_ID = ( + "SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID" + ) SAI_ATTR_GROUP_MEMBER_NEXTHOP_ID = "SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID" SAI_ATTR_GROUP_MEMBER_WEIGHT = "SAI_NEXT_HOP_GROUP_MEMBER_ATTR_WEIGHT" @@ -190,11 +358,14 @@ class P4RtWcmpGroupWrapper(util.DBInterface): # 'get_original_redis_entries' before fetching oid of newly created wcmp group. def get_newly_created_wcmp_group_oid(self): wcmp_group_oid = None - wcmp_group_entries = util.get_keys( - self.asic_db, self.ASIC_DB_GROUP_TBL_NAME) + wcmp_group_entries = util.get_keys(self.asic_db, self.ASIC_DB_GROUP_TBL_NAME) for key in wcmp_group_entries: - if key not in self._original_entries["{}:{}".format( - self.asic_db, self.ASIC_DB_GROUP_TBL_NAME)]: + if ( + key + not in self._original_entries[ + "{}:{}".format(self.asic_db, self.ASIC_DB_GROUP_TBL_NAME) + ] + ): wcmp_group_oid = key break return wcmp_group_oid @@ -207,11 +378,16 @@ def get_newly_created_wcmp_group_oid(self): # wcmp group member. def get_newly_created_wcmp_group_member_asic_db_key(self): asic_db_wcmp_group_member_key = None - wcmp_group_member_entries = util.get_keys(self.asic_db, - self.ASIC_DB_GROUP_MEMBER_TBL_NAME) + wcmp_group_member_entries = util.get_keys( + self.asic_db, self.ASIC_DB_GROUP_MEMBER_TBL_NAME + ) for key in wcmp_group_member_entries: - if key not in self._original_entries["{}:{}".format( - self.asic_db, self.ASIC_DB_GROUP_MEMBER_TBL_NAME)]: + if ( + key + not in self._original_entries[ + "{}:{}".format(self.asic_db, self.ASIC_DB_GROUP_MEMBER_TBL_NAME) + ] + ): asic_db_wcmp_group_member_key = key break return asic_db_wcmp_group_member_key @@ -223,16 +399,25 @@ def generate_app_db_key(self, group_id): return self.TBL_NAME + ":" + key # create default wcmp group - def create_wcmp_group(self, nexthop_id=None, wcmp_group_id=None, - action=None, weight=None, watch_port=None): + def create_wcmp_group( + self, + nexthop_id=None, + wcmp_group_id=None, + action=None, + weight=None, + watch_port=None, + ): wcmp_group_id = wcmp_group_id or self.DEFAULT_WCMP_GROUP_ID weight = weight or self.DEFAULT_WEIGHT action = action or self.DEFAULT_ACTION nexthop_id = nexthop_id or self.DEFAULT_NEXTHOP_ID watch_port = watch_port or self.DEFAULT_WATCH_PORT - action1 = {util.prepend_param_field(self.NEXTHOP_ID_FIELD): nexthop_id, - self.WEIGHT_FIELD: weight, self.ACTION_FIELD: action, - self.WATCH_PORT_FIELD: watch_port} + action1 = { + util.prepend_param_field(self.NEXTHOP_ID_FIELD): nexthop_id, + self.WEIGHT_FIELD: weight, + self.ACTION_FIELD: action, + self.WATCH_PORT_FIELD: watch_port, + } actions = [action1] attr_list = [(self.ACTIONS_FIELD, json.dumps(actions))] wcmp_group_key = self.generate_app_db_key(wcmp_group_id) @@ -240,22 +425,33 @@ def create_wcmp_group(self, nexthop_id=None, wcmp_group_id=None, return wcmp_group_id, wcmp_group_key, attr_list def get_original_appl_db_entries_count(self): - return len(self._original_entries["%s:%s" % (self.appl_db, - (self.APP_DB_TBL_NAME + ":" - + self.TBL_NAME))]) + return len( + self._original_entries[ + "%s:%s" % (self.appl_db, (self.APP_DB_TBL_NAME + ":" + self.TBL_NAME)) + ] + ) def get_original_appl_state_db_entries_count(self): - return len(self._original_entries["%s:%s" % (self.appl_state_db, - (self.APP_DB_TBL_NAME + ":" - + self.TBL_NAME))]) + return len( + self._original_entries[ + "%s:%s" + % (self.appl_state_db, (self.APP_DB_TBL_NAME + ":" + self.TBL_NAME)) + ] + ) def get_original_asic_db_group_entries_count(self): - return len(self._original_entries["%s:%s" % (self.asic_db, - self.ASIC_DB_GROUP_TBL_NAME)]) + return len( + self._original_entries[ + "%s:%s" % (self.asic_db, self.ASIC_DB_GROUP_TBL_NAME) + ] + ) def get_original_asic_db_member_entries_count(self): - return len(self._original_entries["%s:%s" % (self.asic_db, - self.ASIC_DB_GROUP_MEMBER_TBL_NAME)]) + return len( + self._original_entries[ + "%s:%s" % (self.asic_db, self.ASIC_DB_GROUP_MEMBER_TBL_NAME) + ] + ) class P4RtRouteWrapper(util.DBInterface): @@ -267,11 +463,14 @@ class P4RtRouteWrapper(util.DBInterface): SAI_ATTR_PACKET_ACTION = "SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION" SAI_ATTR_PACKET_ACTION_FORWARD = "SAI_PACKET_ACTION_FORWARD" SAI_ATTR_PACKET_ACTION_DROP = "SAI_PACKET_ACTION_DROP" + SAI_ATTR_PACKET_ACTION_TRAP = "SAI_PACKET_ACTION_TRAP" SAI_ATTR_NEXTHOP_ID = "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID" + SAI_ATTR_META_DATA = "SAI_ROUTE_ENTRY_ATTR_META_DATA" # attribute fields for route object NEXTHOP_ID_FIELD = "nexthop_id" WCMP_GROUP_ID_FIELD = "wcmp_group_id" + ROUTE_METADATA_FIELD = "route_metadata" # default route attribute values DEFAULT_ACTION = "set_nexthop_id" @@ -297,21 +496,44 @@ def set_ip_type(self, ip_type): self.TBL_NAME = "FIXED_" + ip_type + "_TABLE" # Create default route. - def create_route(self, nexthop_id=None, wcmp_group_id=None, action=None, - vrf_id=None, dst=None): + def create_route( + self, + nexthop_id=None, + wcmp_group_id=None, + action=None, + vrf_id=None, + dst=None, + metadata="", + ): action = action or self.DEFAULT_ACTION vrf_id = vrf_id or self.DEFAULT_VRF_ID dst = dst or self.DEFAULT_DST if action == "set_wcmp_group_id": wcmp_group_id = wcmp_group_id or self.DEFAULT_WCMP_GROUP_ID - attr_list = [(self.ACTION_FIELD, action), - (util.prepend_param_field( - self.WCMP_GROUP_ID_FIELD), wcmp_group_id)] + attr_list = [ + (self.ACTION_FIELD, action), + (util.prepend_param_field(self.WCMP_GROUP_ID_FIELD), wcmp_group_id), + ] elif action == "set_nexthop_id": nexthop_id = nexthop_id or self.DEFAULT_NEXTHOP_ID - attr_list = [(self.ACTION_FIELD, action), - (util.prepend_param_field(self.NEXTHOP_ID_FIELD), - nexthop_id)] + attr_list = [ + (self.ACTION_FIELD, action), + (util.prepend_param_field(self.NEXTHOP_ID_FIELD), nexthop_id), + ] + elif action == "set_wcmp_group_id_and_metadata": + wcmp_group_id = wcmp_group_id or self.DEFAULT_WCMP_GROUP_ID + attr_list = [ + (self.ACTION_FIELD, action), + (util.prepend_param_field(self.WCMP_GROUP_ID_FIELD), wcmp_group_id), + (util.prepend_param_field(self.ROUTE_METADATA_FIELD), metadata), + ] + elif action == "set_nexthop_id_and_metadata": + nexthop_id = nexthop_id or self.DEFAULT_NEXTHOP_ID + attr_list = [ + (self.ACTION_FIELD, action), + (util.prepend_param_field(self.NEXTHOP_ID_FIELD), nexthop_id), + (util.prepend_param_field(self.ROUTE_METADATA_FIELD), metadata), + ] else: attr_list = [(self.ACTION_FIELD, action)] route_key = self.generate_app_db_key(vrf_id, dst) @@ -327,22 +549,32 @@ def create_route(self, nexthop_id=None, wcmp_group_id=None, action=None, def get_newly_created_asic_db_key(self): route_entries = util.get_keys(self.asic_db, self.ASIC_DB_TBL_NAME) for key in route_entries: - if key not in self._original_entries["%s:%s" % (self.asic_db, - self.ASIC_DB_TBL_NAME)]: + if ( + key + not in self._original_entries[ + "%s:%s" % (self.asic_db, self.ASIC_DB_TBL_NAME) + ] + ): asic_db_key = key break return asic_db_key def get_original_appl_db_entries_count(self): - return len(self._original_entries["%s:%s" % (self.appl_db, - (self.APP_DB_TBL_NAME + ":" - + self.TBL_NAME))]) + return len( + self._original_entries[ + "%s:%s" % (self.appl_db, (self.APP_DB_TBL_NAME + ":" + self.TBL_NAME)) + ] + ) def get_original_appl_state_db_entries_count(self): - return len(self._original_entries["%s:%s" % (self.appl_state_db, - (self.APP_DB_TBL_NAME + ":" - + self.TBL_NAME))]) + return len( + self._original_entries[ + "%s:%s" + % (self.appl_state_db, (self.APP_DB_TBL_NAME + ":" + self.TBL_NAME)) + ] + ) def get_original_asic_db_entries_count(self): - return len(self._original_entries["%s:%s" % (self.asic_db, - self.ASIC_DB_TBL_NAME)]) + return len( + self._original_entries["%s:%s" % (self.asic_db, self.ASIC_DB_TBL_NAME)] + ) diff --git a/tests/p4rt/l3_admit.py b/tests/p4rt/l3_admit.py new file mode 100644 index 000000000000..18fcc8848274 --- /dev/null +++ b/tests/p4rt/l3_admit.py @@ -0,0 +1,84 @@ +from swsscommon import swsscommon + +import util +import json + + +class P4RtL3AdmitWrapper(util.DBInterface): + """Interface to interact with APP DB and ASIC DB tables for P4RT L3 Admit object.""" + + # database and SAI constants + APP_DB_TBL_NAME = swsscommon.APP_P4RT_TABLE_NAME + TBL_NAME = "FIXED_L3_ADMIT_TABLE" + ASIC_DB_TBL_NAME = "ASIC_STATE:SAI_OBJECT_TYPE_MY_MAC" + SAI_ATTR_DST_MAC = "SAI_MY_MAC_ATTR_MAC_ADDRESS" + SAI_ATTR_DST_MAC_MASK = "SAI_MY_MAC_ATTR_MAC_ADDRESS_MASK" + SAI_ATTR_PORT_ID = "SAI_MY_MAC_ATTR_PORT_ID" + SAI_ATTR_PRIORITY = "SAI_MY_MAC_ATTR_PRIORITY" + + # attribute fields for l3 admit object in APP DB + IN_PORT_FIELD = "in_port" + DST_MAC_FIELD = "dst_mac" + PRIORITY = "priority" + L3_ADMIT_ACTION = "admit_to_l3" + + def generate_app_db_key(self, dst_mac, priority, port_id=None): + d = {} + d[util.prepend_match_field(self.DST_MAC_FIELD)] = dst_mac + d[self.PRIORITY] = priority + if port_id != "" and port_id != None: + d[util.prepend_match_field(self.IN_PORT_FIELD)] = port_id + key = json.dumps(d, separators=(",", ":")) + return self.TBL_NAME + ":" + key + + # create default l3 admit + def create_l3_admit( + self, dst_mac, priority, port_id=None + ): + attr_list = [ + (self.ACTION_FIELD, self.L3_ADMIT_ACTION), + ] + l3_admit_key = self.generate_app_db_key(dst_mac, priority, port_id) + self.set_app_db_entry(l3_admit_key, attr_list) + return l3_admit_key, attr_list + + def get_original_appl_db_entries_count(self): + return len( + self._original_entries[ + "%s:%s" % (self.appl_db, (self.APP_DB_TBL_NAME + ":" + self.TBL_NAME)) + ] + ) + + def get_original_appl_state_db_entries_count(self): + return len( + self._original_entries[ + "%s:%s" + % (self.appl_state_db, (self.APP_DB_TBL_NAME + ":" + self.TBL_NAME)) + ] + ) + + def get_original_asic_db_entries_count(self): + return len( + self._original_entries[ + "%s:%s" % (self.asic_db, self.ASIC_DB_TBL_NAME) + ] + ) + + # Fetch the asic_db_key for the first newly created my mac entry from created + # my mac in ASIC db. This API should only be used when only one key is + # expected to be created after original entries. + # Original my mac entries in asic db must be fetched using + # 'get_original_redis_entries' before fetching asic db key of newly created + # my mac entries. + def get_newly_created_asic_db_key(self): + l3_admit_entries = util.get_keys(self.asic_db, self.ASIC_DB_TBL_NAME) + for key in l3_admit_entries: + if ( + key + not in self._original_entries[ + "%s:%s" % (self.asic_db, self.ASIC_DB_TBL_NAME) + ] + ): + asic_db_key = key + break + return asic_db_key \ No newline at end of file diff --git a/tests/p4rt/test_l3.py b/tests/p4rt/test_l3.py index bbe7d0765335..2816f3d4faa6 100644 --- a/tests/p4rt/test_l3.py +++ b/tests/p4rt/test_l3.py @@ -8,9 +8,9 @@ class TestP4RTL3(object): - def _set_up(self, dvs): self._p4rt_router_intf_obj = l3.P4RtRouterInterfaceWrapper() + self._p4rt_gre_tunnel_obj = l3.P4RtGreTunnelWrapper() self._p4rt_neighbor_obj = l3.P4RtNeighborWrapper() self._p4rt_nexthop_obj = l3.P4RtNextHopWrapper() self._p4rt_route_obj = l3.P4RtRouteWrapper() @@ -18,12 +18,15 @@ def _set_up(self, dvs): self._vrf_obj = test_vrf.TestVrf() self._p4rt_router_intf_obj.set_up_databases(dvs) + self._p4rt_gre_tunnel_obj.set_up_databases(dvs) self._p4rt_neighbor_obj.set_up_databases(dvs) self._p4rt_nexthop_obj.set_up_databases(dvs) self._p4rt_route_obj.set_up_databases(dvs) self._p4rt_wcmp_group_obj.set_up_databases(dvs) self.response_consumer = swsscommon.NotificationConsumer( - self._p4rt_route_obj.appl_state_db, "APPL_DB_P4RT_TABLE_RESPONSE_CHANNEL") + self._p4rt_route_obj.appl_db, "APPL_DB_" + + swsscommon.APP_P4RT_TABLE_NAME + "_RESPONSE_CHANNEL" + ) def _set_vrf(self, dvs): # Create VRF. @@ -45,17 +48,24 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): # Maintain list of original Application and ASIC DB entries before # adding new route. - db_list = ((self._p4rt_nexthop_obj.asic_db, - self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME),) + db_list = ( + (self._p4rt_nexthop_obj.asic_db, + self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME), + ) self._p4rt_nexthop_obj.get_original_redis_entries(db_list) - db_list = ((self._p4rt_route_obj.appl_db, - "%s:%s" % (self._p4rt_route_obj.APP_DB_TBL_NAME, - self._p4rt_route_obj.TBL_NAME)), - (self._p4rt_route_obj.appl_state_db, - "%s:%s" % (self._p4rt_route_obj.APP_DB_TBL_NAME, - self._p4rt_route_obj.TBL_NAME)), - (self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME)) + db_list = ( + ( + self._p4rt_route_obj.appl_db, + "%s:%s" + % (self._p4rt_route_obj.APP_DB_TBL_NAME, self._p4rt_route_obj.TBL_NAME), + ), + ( + self._p4rt_route_obj.appl_state_db, + "%s:%s" + % (self._p4rt_route_obj.APP_DB_TBL_NAME, self._p4rt_route_obj.TBL_NAME), + ), + (self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME), + ) self._p4rt_route_obj.get_original_redis_entries(db_list) # Fetch the original key to oid information from Redis DB. @@ -63,11 +73,14 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): _, original_key_oid_info = key_to_oid_helper.get_db_info() # Create router interface. - router_interface_id, router_intf_key, attr_list = ( - self._p4rt_router_intf_obj.create_router_interface() + ( + router_interface_id, + router_intf_key, + attr_list, + ) = self._p4rt_router_intf_obj.create_router_interface() + util.verify_response( + self.response_consumer, router_intf_key, attr_list, "SWSS_RC_SUCCESS" ) - util.verify_response(self.response_consumer, router_intf_key, - attr_list, "SWSS_RC_SUCCESS") # Verify that P4RT key to OID count incremented by 1 in Redis DB. count = 1 @@ -76,11 +89,10 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create neighbor. - neighbor_id, neighbor_key, attr_list = ( - self._p4rt_neighbor_obj.create_neighbor() + neighbor_id, neighbor_key, attr_list = self._p4rt_neighbor_obj.create_neighbor() + util.verify_response( + self.response_consumer, neighbor_key, attr_list, "SWSS_RC_SUCCESS" ) - util.verify_response(self.response_consumer, neighbor_key, attr_list, - "SWSS_RC_SUCCESS") # Verify that P4RT key to OID count incremented by 1 in Redis DB. count += 1 @@ -89,11 +101,10 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create nexthop. - nexthop_id, nexthop_key, attr_list = ( - self._p4rt_nexthop_obj.create_next_hop() + nexthop_id, nexthop_key, attr_list = self._p4rt_nexthop_obj.create_next_hop() + util.verify_response( + self.response_consumer, nexthop_key, attr_list, "SWSS_RC_SUCCESS" ) - util.verify_response(self.response_consumer, nexthop_key, attr_list, - "SWSS_RC_SUCCESS") # get nexthop_oid of newly created nexthop nexthop_oid = self._p4rt_nexthop_obj.get_newly_created_nexthop_oid() assert nexthop_oid is not None @@ -106,8 +117,9 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): # Create route entry. route_key, attr_list = self._p4rt_route_obj.create_route(nexthop_id) - util.verify_response(self.response_consumer, route_key, attr_list, - "SWSS_RC_SUCCESS") + util.verify_response( + self.response_consumer, route_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 1 in Redis DB. count += 1 @@ -118,36 +130,43 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): # Query application database for route entries. route_entries = util.get_keys( self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_appl_db_entries_count() + 1 ) # Query application database for newly created route key. - (status, fvs) = util.get_key(self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query application state database for route entries. state_route_entries = util.get_keys( self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(state_route_entries) == ( self._p4rt_route_obj.get_original_appl_state_db_entries_count() + 1 ) # Query application state database for newly created route key. - (status, fvs) = util.get_key(self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query ASIC database for route entries. - route_entries = util.get_keys(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME) + route_entries = util.get_keys( + self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_asic_db_entries_count() + 1 ) @@ -155,17 +174,22 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): # Query ASIC database for newly created route key. asic_db_key = self._p4rt_route_obj.get_newly_created_asic_db_key() assert asic_db_key is not None - (status, fvs) = util.get_key(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME, - asic_db_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.asic_db, + self._p4rt_route_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) assert status == True attr_list = [(self._p4rt_route_obj.SAI_ATTR_NEXTHOP_ID, nexthop_oid)] util.verify_attr(fvs, attr_list) - # Update route entry. - route_key, attr_list = self._p4rt_route_obj.create_route(action="drop") - util.verify_response(self.response_consumer, route_key, attr_list, - "SWSS_RC_SUCCESS") + # Update route entry to set_nexthop_id_and_metadata. + route_key, attr_list = self._p4rt_route_obj.create_route( + action="set_nexthop_id_and_metadata", metadata="2" + ) + util.verify_response( + self.response_consumer, route_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count did not change in Redis DB. status, fvs = key_to_oid_helper.get_db_info() @@ -175,38 +199,139 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): # Query application database for route entries. route_entries = util.get_keys( self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_appl_db_entries_count() + 1 ) # Query application database for the updated route key. - (status, fvs) = util.get_key(self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) + assert status == True + attr_list_appl_db = [ + (self._p4rt_route_obj.ACTION_FIELD, "set_nexthop_id_and_metadata"), + ( + util.prepend_param_field( + self._p4rt_route_obj.NEXTHOP_ID_FIELD), + nexthop_id, + ), + ( + util.prepend_param_field( + self._p4rt_route_obj.ROUTE_METADATA_FIELD), + "2", + ), + ] + util.verify_attr(fvs, attr_list_appl_db) + + # Query application state database for route entries. + state_route_entries = util.get_keys( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) + assert len(state_route_entries) == ( + self._p4rt_route_obj.get_original_appl_state_db_entries_count() + 1 + ) + + # Query application state database for the updated route key. + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) + assert status == True + util.verify_attr(fvs, attr_list) + + # Query ASIC database for route entries. + route_entries = util.get_keys( + self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME + ) + assert len(route_entries) == ( + self._p4rt_route_obj.get_original_asic_db_entries_count() + 1 + ) + + # Query ASIC database for the updated route key. + asic_db_key = self._p4rt_route_obj.get_newly_created_asic_db_key() + assert asic_db_key is not None + (status, fvs) = util.get_key( + self._p4rt_route_obj.asic_db, + self._p4rt_route_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) assert status == True - attr_list_appl_db = [(self._p4rt_route_obj.ACTION_FIELD, "drop"), - (util.prepend_param_field(self._p4rt_route_obj.NEXTHOP_ID_FIELD), nexthop_id)] + attr_list = [ + (self._p4rt_route_obj.SAI_ATTR_NEXTHOP_ID, nexthop_oid), + (self._p4rt_route_obj.SAI_ATTR_META_DATA, "2"), + ] + util.verify_attr(fvs, attr_list) + + # Update route entry to drop. + route_key, attr_list = self._p4rt_route_obj.create_route(action="drop") + util.verify_response( + self.response_consumer, route_key, attr_list, "SWSS_RC_SUCCESS" + ) + + # Verify that P4RT key to OID count did not change in Redis DB. + status, fvs = key_to_oid_helper.get_db_info() + assert status == True + assert len(fvs) == len(original_key_oid_info) + count + + # Query application database for route entries. + route_entries = util.get_keys( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) + assert len(route_entries) == ( + self._p4rt_route_obj.get_original_appl_db_entries_count() + 1 + ) + + # Query application database for the updated route key. + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) + assert status == True + attr_list_appl_db = [ + (self._p4rt_route_obj.ACTION_FIELD, "drop"), + ( + util.prepend_param_field( + self._p4rt_route_obj.NEXTHOP_ID_FIELD), + nexthop_id, + ), + ( + util.prepend_param_field( + self._p4rt_route_obj.ROUTE_METADATA_FIELD), + "2", + ), + ] util.verify_attr(fvs, attr_list_appl_db) # Query application state database for route entries. state_route_entries = util.get_keys( self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(state_route_entries) == ( self._p4rt_route_obj.get_original_appl_state_db_entries_count() + 1 ) # Query application state database for the updated route key. - (status, fvs) = util.get_key(self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query ASIC database for route entries. - route_entries = util.get_keys(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME) + route_entries = util.get_keys( + self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_asic_db_entries_count() + 1 ) @@ -214,18 +339,26 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): # Query ASIC database for the updated route key. asic_db_key = self._p4rt_route_obj.get_newly_created_asic_db_key() assert asic_db_key is not None - (status, fvs) = util.get_key(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME, - asic_db_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.asic_db, + self._p4rt_route_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) assert status == True - attr_list = [(self._p4rt_route_obj.SAI_ATTR_NEXTHOP_ID, "oid:0x0"), - (self._p4rt_route_obj.SAI_ATTR_PACKET_ACTION, self._p4rt_route_obj.SAI_ATTR_PACKET_ACTION_DROP)] + attr_list = [ + (self._p4rt_route_obj.SAI_ATTR_NEXTHOP_ID, "oid:0x0"), + ( + self._p4rt_route_obj.SAI_ATTR_PACKET_ACTION, + self._p4rt_route_obj.SAI_ATTR_PACKET_ACTION_DROP, + ), + (self._p4rt_route_obj.SAI_ATTR_META_DATA, "0"), + ] util.verify_attr(fvs, attr_list) # Remove route entry. self._p4rt_route_obj.remove_app_db_entry(route_key) - util.verify_response( - self.response_consumer, route_key, [], "SWSS_RC_SUCCESS") + util.verify_response(self.response_consumer, + route_key, [], "SWSS_RC_SUCCESS") # Verify that P4RT key to OID count decremented by 1 in Redis DB. count -= 1 @@ -235,8 +368,8 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): # Remove nexthop. self._p4rt_nexthop_obj.remove_app_db_entry(nexthop_key) - util.verify_response(self.response_consumer, nexthop_key, [], - "SWSS_RC_SUCCESS") + util.verify_response(self.response_consumer, + nexthop_key, [], "SWSS_RC_SUCCESS") # Verify that P4RT key to OID count decremented by 1 in Redis DB. count -= 1 @@ -246,8 +379,9 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): # Remove neighbor. self._p4rt_neighbor_obj.remove_app_db_entry(neighbor_key) - util.verify_response(self.response_consumer, neighbor_key, [], - "SWSS_RC_SUCCESS") + util.verify_response( + self.response_consumer, neighbor_key, [], "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count decremented by 1 in Redis DB. count -= 1 @@ -258,7 +392,8 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): # Remove router interface. self._p4rt_router_intf_obj.remove_app_db_entry(router_intf_key) util.verify_response( - self.response_consumer, router_intf_key, [], "SWSS_RC_SUCCESS") + self.response_consumer, router_intf_key, [], "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count is same as the original count. status, fvs = key_to_oid_helper.get_db_info() @@ -268,43 +403,52 @@ def test_IPv4RouteWithNexthopAddUpdateDeletePass(self, dvs, testlog): # Query application database for route entries. route_entries = util.get_keys( self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_appl_db_entries_count() ) # Verify that the route_key no longer exists in application database. - (status, fsv) = util.get_key(self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fsv) = util.get_key( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == False # Query application state database for route entries. state_route_entries = util.get_keys( self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(state_route_entries) == ( self._p4rt_route_obj.get_original_appl_state_db_entries_count() ) # Verify that the route_key no longer exists in application state # database. - (status, fsv) = util.get_key(self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fsv) = util.get_key( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == False # Query ASIC database for route entries. - route_entries = util.get_keys(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME) + route_entries = util.get_keys( + self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_asic_db_entries_count() ) # Verify that removed route no longer exists in ASIC database. - (status, fvs) = util.get_key(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME, - asic_db_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.asic_db, + self._p4rt_route_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) assert status == False self._clean_vrf(dvs) @@ -318,28 +462,51 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): # Maintain list of original Application and ASIC DB entries before # adding new route. - db_list = ((self._p4rt_route_obj.appl_db, - "%s:%s" % (self._p4rt_route_obj.APP_DB_TBL_NAME, - self._p4rt_route_obj.TBL_NAME)), - (self._p4rt_route_obj.appl_state_db, - "%s:%s" % (self._p4rt_route_obj.APP_DB_TBL_NAME, - self._p4rt_route_obj.TBL_NAME)), - (self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME)) + db_list = ( + ( + self._p4rt_route_obj.appl_db, + "%s:%s" + % (self._p4rt_route_obj.APP_DB_TBL_NAME, self._p4rt_route_obj.TBL_NAME), + ), + ( + self._p4rt_route_obj.appl_state_db, + "%s:%s" + % (self._p4rt_route_obj.APP_DB_TBL_NAME, self._p4rt_route_obj.TBL_NAME), + ), + (self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME), + ) self._p4rt_route_obj.get_original_redis_entries(db_list) - db_list = ((self._p4rt_nexthop_obj.asic_db, - self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME),) + db_list = ( + (self._p4rt_nexthop_obj.asic_db, + self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME), + ) self._p4rt_nexthop_obj.get_original_redis_entries(db_list) - db_list = ((self._p4rt_wcmp_group_obj.appl_db, - "%s:%s" % (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - self._p4rt_wcmp_group_obj.TBL_NAME)), - (self._p4rt_wcmp_group_obj.appl_state_db, - "%s:%s" % (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - self._p4rt_wcmp_group_obj.TBL_NAME)), - (self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME), - (self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME)) + db_list = ( + ( + self._p4rt_wcmp_group_obj.appl_db, + "%s:%s" + % ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + self._p4rt_wcmp_group_obj.TBL_NAME, + ), + ), + ( + self._p4rt_wcmp_group_obj.appl_state_db, + "%s:%s" + % ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + self._p4rt_wcmp_group_obj.TBL_NAME, + ), + ), + ( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + ), + ( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, + ), + ) self._p4rt_wcmp_group_obj.get_original_redis_entries(db_list) # Fetch the original key to oid information from Redis DB. @@ -347,11 +514,14 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): _, original_key_oid_info = key_to_oid_helper.get_db_info() # Create router interface. - router_interface_id, router_intf_key, attr_list = ( - self._p4rt_router_intf_obj.create_router_interface() + ( + router_interface_id, + router_intf_key, + attr_list, + ) = self._p4rt_router_intf_obj.create_router_interface() + util.verify_response( + self.response_consumer, router_intf_key, attr_list, "SWSS_RC_SUCCESS" ) - util.verify_response(self.response_consumer, router_intf_key, attr_list, - "SWSS_RC_SUCCESS") # Verify that P4RT key to OID count incremented by 1 in Redis DB. count = 1 @@ -360,11 +530,12 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create neighbor. - neighbor_id, neighbor_key, attr_list = ( - self._p4rt_neighbor_obj.create_neighbor(ipv4=False) + neighbor_id, neighbor_key, attr_list = self._p4rt_neighbor_obj.create_neighbor( + ipv4=False + ) + util.verify_response( + self.response_consumer, neighbor_key, attr_list, "SWSS_RC_SUCCESS" ) - util.verify_response(self.response_consumer, neighbor_key, attr_list, - "SWSS_RC_SUCCESS") # Verify that P4RT key to OID count incremented by 1 in Redis DB. count += 1 @@ -373,11 +544,12 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create nexthop. - nexthop_id, nexthop_key, attr_list = ( - self._p4rt_nexthop_obj.create_next_hop(ipv4=False) + nexthop_id, nexthop_key, attr_list = self._p4rt_nexthop_obj.create_next_hop( + ipv4=False + ) + util.verify_response( + self.response_consumer, nexthop_key, attr_list, "SWSS_RC_SUCCESS" ) - util.verify_response(self.response_consumer, nexthop_key, attr_list, - "SWSS_RC_SUCCESS") # Get the oid of the newly created nexthop. nexthop_oid = self._p4rt_nexthop_obj.get_newly_created_nexthop_oid() assert nexthop_oid is not None @@ -389,11 +561,14 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create wcmp group. - wcmp_group_id, wcmp_group_key, attr_list = ( - self._p4rt_wcmp_group_obj.create_wcmp_group() + ( + wcmp_group_id, + wcmp_group_key, + attr_list, + ) = self._p4rt_wcmp_group_obj.create_wcmp_group() + util.verify_response( + self.response_consumer, wcmp_group_key, attr_list, "SWSS_RC_SUCCESS" ) - util.verify_response(self.response_consumer, wcmp_group_key, attr_list, - "SWSS_RC_SUCCESS") # Verify that P4RT key to OID count incremented by 2 in Redis DB # (1 each for WCMP group and member). @@ -405,60 +580,76 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): # Query application database for wcmp group entries. wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + self._p4rt_wcmp_group_obj.TBL_NAME) + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME, + ) assert len(wcmp_group_entries) == ( self._p4rt_wcmp_group_obj.get_original_appl_db_entries_count() + 1 ) # Query application database for newly created wcmp group key. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.appl_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query application state database for wcmp group entries. state_wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_state_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + self._p4rt_wcmp_group_obj.TBL_NAME) + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME, + ) assert len(state_wcmp_group_entries) == ( - self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() + 1 ) # Query application state database for newly created wcmp group key. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.appl_state_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.appl_state_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query ASIC database for wcmp group entries. - wcmp_group_entries = util.get_keys(self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME) + wcmp_group_entries = util.get_keys( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + ) assert len(wcmp_group_entries) == ( - self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() + 1 ) # Query ASIC database for newly created wcmp group oid. wcmp_group_oid = self._p4rt_wcmp_group_obj.get_newly_created_wcmp_group_oid() assert wcmp_group_oid is not None - attr_list = [(self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_TYPE, - self._p4rt_wcmp_group_obj.SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP)] - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, - wcmp_group_oid) + attr_list = [ + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_TYPE, + self._p4rt_wcmp_group_obj.SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP, + ) + ] + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + wcmp_group_oid, + ) assert status == True util.verify_attr(fvs, attr_list) # Query ASIC database for wcmp group member entries. wcmp_group_member_entries = util.get_keys( self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME) + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, + ) assert len(wcmp_group_member_entries) == ( - self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() + 1 ) # Query ASIC database for newly crated wcmp group member key. @@ -466,23 +657,32 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): self._p4rt_wcmp_group_obj.get_newly_created_wcmp_group_member_asic_db_key() ) assert asic_db_group_member_key is not None - attr_list = [(self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_GROUP_ID, - wcmp_group_oid), - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_ID, - nexthop_oid), - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_WEIGHT, - str(self._p4rt_wcmp_group_obj.DEFAULT_WEIGHT))] - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, - asic_db_group_member_key) + attr_list = [ + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_GROUP_ID, + wcmp_group_oid, + ), + (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_ID, nexthop_oid), + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_WEIGHT, + str(self._p4rt_wcmp_group_obj.DEFAULT_WEIGHT), + ), + ] + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, + asic_db_group_member_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Create route entry. route_key, attr_list = self._p4rt_route_obj.create_route( - wcmp_group_id=wcmp_group_id, action="set_wcmp_group_id", dst="2001:db8::/32") - util.verify_response(self.response_consumer, route_key, attr_list, - "SWSS_RC_SUCCESS") + wcmp_group_id=wcmp_group_id, action="set_wcmp_group_id", dst="2001:db8::/32" + ) + util.verify_response( + self.response_consumer, route_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 1 in Redis DB. count += 1 @@ -493,36 +693,43 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): # Query application database for route entries. route_entries = util.get_keys( self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_appl_db_entries_count() + 1 ) # Query application database for newly created route key. - (status, fvs) = util.get_key(self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query application state database for route entries. state_route_entries = util.get_keys( self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(state_route_entries) == ( self._p4rt_route_obj.get_original_appl_state_db_entries_count() + 1 ) # Query application state database for newly created route key. - (status, fvs) = util.get_key(self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query ASIC database for route entries. - route_entries = util.get_keys(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME) + route_entries = util.get_keys( + self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_asic_db_entries_count() + 1 ) @@ -530,19 +737,23 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): # Query ASIC database for newly created route key. asic_db_key = self._p4rt_route_obj.get_newly_created_asic_db_key() assert asic_db_key is not None - (status, fvs) = util.get_key(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME, - asic_db_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.asic_db, + self._p4rt_route_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) assert status == True attr_list = [ (self._p4rt_route_obj.SAI_ATTR_NEXTHOP_ID, wcmp_group_oid)] util.verify_attr(fvs, attr_list) - # Update route entry. + # Update route entry to drop action route_key, attr_list = self._p4rt_route_obj.create_route( - action="drop", dst="2001:db8::/32") - util.verify_response(self.response_consumer, route_key, attr_list, - "SWSS_RC_SUCCESS") + action="drop", dst="2001:db8::/32" + ) + util.verify_response( + self.response_consumer, route_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count did not change in Redis DB. status, fvs = key_to_oid_helper.get_db_info() @@ -552,38 +763,51 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): # Query application database for route entries. route_entries = util.get_keys( self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_appl_db_entries_count() + 1 ) # Query application database for the updated route key. - (status, fvs) = util.get_key(self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == True - attr_list_appl_db = [(self._p4rt_route_obj.ACTION_FIELD, "drop"), - (util.prepend_param_field(self._p4rt_route_obj.WCMP_GROUP_ID_FIELD), wcmp_group_id)] + attr_list_appl_db = [ + (self._p4rt_route_obj.ACTION_FIELD, "drop"), + ( + util.prepend_param_field( + self._p4rt_route_obj.WCMP_GROUP_ID_FIELD), + wcmp_group_id, + ), + ] util.verify_attr(fvs, attr_list_appl_db) # Query application state database for route entries. state_route_entries = util.get_keys( self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(state_route_entries) == ( self._p4rt_route_obj.get_original_appl_state_db_entries_count() + 1 ) # Query application state database for the updated route key. - (status, fvs) = util.get_key(self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query ASIC database for route entries. - route_entries = util.get_keys(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME) + route_entries = util.get_keys( + self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_asic_db_entries_count() + 1 ) @@ -591,18 +815,108 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): # Query ASIC database for the updated route key. asic_db_key = self._p4rt_route_obj.get_newly_created_asic_db_key() assert asic_db_key is not None - (status, fvs) = util.get_key(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME, - asic_db_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.asic_db, + self._p4rt_route_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) assert status == True - attr_list = [(self._p4rt_route_obj.SAI_ATTR_NEXTHOP_ID, "oid:0x0"), - (self._p4rt_route_obj.SAI_ATTR_PACKET_ACTION, self._p4rt_route_obj.SAI_ATTR_PACKET_ACTION_DROP)] + attr_list = [ + (self._p4rt_route_obj.SAI_ATTR_NEXTHOP_ID, "oid:0x0"), + ( + self._p4rt_route_obj.SAI_ATTR_PACKET_ACTION, + self._p4rt_route_obj.SAI_ATTR_PACKET_ACTION_DROP, + ), + ] + util.verify_attr(fvs, attr_list) + + # Update route entry to trap action. + route_key, attr_list = self._p4rt_route_obj.create_route( + action="trap", dst="2001:db8::/32" + ) + util.verify_response( + self.response_consumer, route_key, attr_list, "SWSS_RC_SUCCESS" + ) + + # Verify that P4RT key to OID count did not change in Redis DB. + status, fvs = key_to_oid_helper.get_db_info() + assert status == True + assert len(fvs) == len(original_key_oid_info) + count + + # Query application database for route entries. + route_entries = util.get_keys( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) + assert len(route_entries) == ( + self._p4rt_route_obj.get_original_appl_db_entries_count() + 1 + ) + + # Query application database for the updated route key. + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) + assert status == True + attr_list_appl_db = [ + (self._p4rt_route_obj.ACTION_FIELD, "trap"), + ( + util.prepend_param_field( + self._p4rt_route_obj.WCMP_GROUP_ID_FIELD), + wcmp_group_id, + ), + ] + util.verify_attr(fvs, attr_list_appl_db) + + # Query application state database for route entries. + state_route_entries = util.get_keys( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) + assert len(state_route_entries) == ( + self._p4rt_route_obj.get_original_appl_state_db_entries_count() + 1 + ) + + # Query application state database for the updated route key. + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) + assert status == True + util.verify_attr(fvs, attr_list) + + # Query ASIC database for route entries. + route_entries = util.get_keys( + self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME + ) + assert len(route_entries) == ( + self._p4rt_route_obj.get_original_asic_db_entries_count() + 1 + ) + + # Query ASIC database for the updated route key. + asic_db_key = self._p4rt_route_obj.get_newly_created_asic_db_key() + assert asic_db_key is not None + (status, fvs) = util.get_key( + self._p4rt_route_obj.asic_db, + self._p4rt_route_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) + assert status == True + attr_list = [ + (self._p4rt_route_obj.SAI_ATTR_NEXTHOP_ID, "oid:0x0"), + ( + self._p4rt_route_obj.SAI_ATTR_PACKET_ACTION, + self._p4rt_route_obj.SAI_ATTR_PACKET_ACTION_TRAP, + ), + ] util.verify_attr(fvs, attr_list) # Remove route entry. self._p4rt_route_obj.remove_app_db_entry(route_key) - util.verify_response( - self.response_consumer, route_key, [], "SWSS_RC_SUCCESS") + util.verify_response(self.response_consumer, + route_key, [], "SWSS_RC_SUCCESS") # Verify that P4RT key to OID count decremented by 1 in Redis DB. count -= 1 @@ -612,8 +926,9 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): # Remove wcmp group entry. self._p4rt_wcmp_group_obj.remove_app_db_entry(wcmp_group_key) - util.verify_response(self.response_consumer, wcmp_group_key, [], - "SWSS_RC_SUCCESS") + util.verify_response( + self.response_consumer, wcmp_group_key, [], "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count decremented by 2 in Redis DB # (1 each for WCMP group and member). @@ -624,8 +939,8 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): # Remove nexthop. self._p4rt_nexthop_obj.remove_app_db_entry(nexthop_key) - util.verify_response(self.response_consumer, nexthop_key, [], - "SWSS_RC_SUCCESS") + util.verify_response(self.response_consumer, + nexthop_key, [], "SWSS_RC_SUCCESS") # Verify that P4RT key to OID count decremented by 1 in Redis DB. count -= 1 @@ -635,8 +950,9 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): # Remove neighbor. self._p4rt_neighbor_obj.remove_app_db_entry(neighbor_key) - util.verify_response(self.response_consumer, neighbor_key, [], - "SWSS_RC_SUCCESS") + util.verify_response( + self.response_consumer, neighbor_key, [], "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count decremented by 1 in Redis DB. count -= 1 @@ -647,7 +963,8 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): # Remove router interface. self._p4rt_router_intf_obj.remove_app_db_entry(router_intf_key) util.verify_response( - self.response_consumer, router_intf_key, [], "SWSS_RC_SUCCESS") + self.response_consumer, router_intf_key, [], "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count is same as original count. status, fvs = key_to_oid_helper.get_db_info() @@ -657,101 +974,536 @@ def test_IPv6WithWcmpRouteAddUpdateDeletePass(self, dvs, testlog): # Query application database for route entries. route_entries = util.get_keys( self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_appl_db_entries_count() ) # Verify that the route_key no longer exists in application database. - (status, fsv) = util.get_key(self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fsv) = util.get_key( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == False # Query application state database for route entries. state_route_entries = util.get_keys( self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(state_route_entries) == ( self._p4rt_route_obj.get_original_appl_state_db_entries_count() ) - # Verify that the route_key no longer exists in application state + # Verify that the route_key no longer exists in application state + # database. + (status, fsv) = util.get_key( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) + assert status == False + + # Query ASIC database for route entries. + route_entries = util.get_keys( + self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME + ) + assert len(route_entries) == ( + self._p4rt_route_obj.get_original_asic_db_entries_count() + ) + + # Verify that removed route no longer exists in ASIC database. + (status, fvs) = util.get_key( + self._p4rt_route_obj.asic_db, + self._p4rt_route_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) + assert status == False + + # Query application database for wcmp group entries. + wcmp_group_entries = util.get_keys( + self._p4rt_wcmp_group_obj.appl_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME, + ) + assert len(wcmp_group_entries) == ( + self._p4rt_wcmp_group_obj.get_original_appl_db_entries_count() + ) + + # Verify that the route_key no longer exists in application database. + (status, fsv) = util.get_key( + self._p4rt_wcmp_group_obj.appl_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) + assert status == False + + # Query application state database for wcmp group entries. + state_wcmp_group_entries = util.get_keys( + self._p4rt_wcmp_group_obj.appl_state_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME, + ) + assert len(state_wcmp_group_entries) == ( + self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() + ) + + # Verify that the wcmp_group_key no longer exists in application state + # database. + (status, fsv) = util.get_key( + self._p4rt_wcmp_group_obj.appl_state_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) + assert status == False + + # Query ASIC database for wcmp group entries. + wcmp_group_entries = util.get_keys( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + ) + assert len(wcmp_group_entries) == ( + self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() + ) + + # Verify that removed wcmp group no longer exists in ASIC database. + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + wcmp_group_oid, + ) + assert status == False + + # Query ASIC database for wcmp group member entries. + wcmp_group_member_entries = util.get_keys( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, + ) + assert len(wcmp_group_member_entries) == ( + self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() + ) + + # Verify that removed wcmp group member no longer exists in ASIC + # database. + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, + asic_db_group_member_key, + ) + assert status == False + + self._clean_vrf(dvs) + + def test_NexthopWithGreTunnelAddDeletePass(self, dvs, testlog): + # Initialize L3 objects and database connectors. + self._set_up(dvs) + self._set_vrf(dvs) + + # Maintain list of original Application and ASIC DB entries before + # adding new entries. + db_list = ( + ( + self._p4rt_nexthop_obj.appl_db, + "%s:%s" + % ( + self._p4rt_nexthop_obj.APP_DB_TBL_NAME, + self._p4rt_nexthop_obj.TBL_NAME, + ), + ), + ( + self._p4rt_nexthop_obj.appl_state_db, + "%s:%s" + % ( + self._p4rt_nexthop_obj.APP_DB_TBL_NAME, + self._p4rt_nexthop_obj.TBL_NAME, + ), + ), + (self._p4rt_nexthop_obj.asic_db, + self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME), + ) + self._p4rt_nexthop_obj.get_original_redis_entries(db_list) + db_list = ( + ( + self._p4rt_gre_tunnel_obj.appl_db, + "%s:%s" + % ( + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME, + self._p4rt_gre_tunnel_obj.TBL_NAME, + ), + ), + ( + self._p4rt_gre_tunnel_obj.appl_state_db, + "%s:%s" + % ( + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME, + self._p4rt_gre_tunnel_obj.TBL_NAME, + ), + ), + (self._p4rt_gre_tunnel_obj.asic_db, + self._p4rt_gre_tunnel_obj.ASIC_DB_TBL_NAME), + ) + self._p4rt_gre_tunnel_obj.get_original_redis_entries(db_list) + db_list = ( + (self._p4rt_router_intf_obj.asic_db, + self._p4rt_router_intf_obj.ASIC_DB_TBL_NAME), + ) + self._p4rt_router_intf_obj.get_original_redis_entries(db_list) + + # Fetch the original key to oid information from Redis DB. + key_to_oid_helper = util.KeyToOidDBHelper(dvs) + _, original_key_oid_info = key_to_oid_helper.get_db_info() + + # Create router interface. + ( + router_interface_id, + router_intf_key, + attr_list, + ) = self._p4rt_router_intf_obj.create_router_interface() + util.verify_response( + self.response_consumer, router_intf_key, attr_list, "SWSS_RC_SUCCESS" + ) + + # get router_interface_oid of newly created router_intf + router_intf_oid = self._p4rt_router_intf_obj.get_newly_created_router_interface_oid() + assert router_intf_oid is not None + + # Verify that P4RT key to OID count incremented by 1 in Redis DB. + count = 1 + status, fvs = key_to_oid_helper.get_db_info() + assert status == True + assert len(fvs) == len(original_key_oid_info) + count + + # Create tunnel. + tunnel_id, tunnel_key, attr_list = self._p4rt_gre_tunnel_obj.create_gre_tunnel() + util.verify_response( + self.response_consumer, tunnel_key, attr_list, "SWSS_RC_SUCCESS" + ) + # get tunnel_oid of newly created tunnel + tunnel_oid = self._p4rt_gre_tunnel_obj.get_newly_created_tunnel_oid() + assert tunnel_oid is not None + # get overlay router_interface_oid of newly created router_intf + overlay_router_intf_oid = self._p4rt_router_intf_obj.get_newly_created_router_interface_oid( + set([router_intf_oid])) + assert overlay_router_intf_oid is not None + + # Verify that P4RT key to OID count incremented by 1 in Redis DB. + count += 1 + status, fvs = key_to_oid_helper.get_db_info() + assert status == True + assert len(fvs) == len(original_key_oid_info) + count + + # Query application database for tunnel entries. + tunnel_entries = util.get_keys( + self._p4rt_gre_tunnel_obj.appl_db, + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME + + ":" + self._p4rt_gre_tunnel_obj.TBL_NAME, + ) + assert len(tunnel_entries) == ( + self._p4rt_gre_tunnel_obj.get_original_appl_db_entries_count() + 1 + ) + + # Query application database for newly created tunnel key. + (status, fvs) = util.get_key( + self._p4rt_gre_tunnel_obj.appl_db, + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME, + tunnel_key, + ) + assert status == True + util.verify_attr(fvs, attr_list) + + # Query application state database for tunnel entries. + state_tunnel_entries = util.get_keys( + self._p4rt_gre_tunnel_obj.appl_state_db, + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME + + ":" + self._p4rt_gre_tunnel_obj.TBL_NAME, + ) + assert len(state_tunnel_entries) == ( + self._p4rt_gre_tunnel_obj.get_original_appl_state_db_entries_count() + 1 + ) + + # Query application state database for newly created tunnel key. + (status, fvs) = util.get_key( + self._p4rt_gre_tunnel_obj.appl_state_db, + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME, + tunnel_key, + ) + assert status == True + util.verify_attr(fvs, attr_list) + + # Query ASIC database for tunnel entries. + tunnel_entries = util.get_keys( + self._p4rt_gre_tunnel_obj.asic_db, self._p4rt_gre_tunnel_obj.ASIC_DB_TBL_NAME + ) + assert len(tunnel_entries) == ( + self._p4rt_gre_tunnel_obj.get_original_asic_db_entries_count() + 1 + ) + + # Query ASIC database for newly created nexthop key. + asic_db_key = self._p4rt_gre_tunnel_obj.get_newly_created_tunnel_oid() + assert asic_db_key is not None + (status, fvs) = util.get_key( + self._p4rt_gre_tunnel_obj.asic_db, + self._p4rt_gre_tunnel_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) + assert status == True + attr_list = [ + (self._p4rt_gre_tunnel_obj.SAI_ATTR_UNDERLAY_INTERFACE, router_intf_oid), + (self._p4rt_gre_tunnel_obj.SAI_ATTR_OVERLAY_INTERFACE, + overlay_router_intf_oid), + (self._p4rt_gre_tunnel_obj.SAI_ATTR_TYPE, "SAI_TUNNEL_TYPE_IPINIP_GRE"), + (self._p4rt_gre_tunnel_obj.SAI_ATTR_PEER_MODE, "SAI_TUNNEL_PEER_MODE_P2P"), + (self._p4rt_gre_tunnel_obj.SAI_ATTR_ENCAP_SRC_IP, + self._p4rt_gre_tunnel_obj.DEFAULT_ENCAP_SRC_IP), + (self._p4rt_gre_tunnel_obj.SAI_ATTR_ENCAP_DST_IP, + self._p4rt_gre_tunnel_obj.DEFAULT_ENCAP_DST_IP), + ] + util.verify_attr(fvs, attr_list) + + # Create neighbor. + neighbor_id, neighbor_key, attr_list = self._p4rt_neighbor_obj.create_neighbor() + util.verify_response( + self.response_consumer, neighbor_key, attr_list, "SWSS_RC_SUCCESS" + ) + + # Verify that P4RT key to OID count incremented by 1 in Redis DB. + count += 1 + status, fvs = key_to_oid_helper.get_db_info() + assert status == True + assert len(fvs) == len(original_key_oid_info) + count + + # Create tunnel nexthop. + nexthop_id, nexthop_key, attr_list = self._p4rt_nexthop_obj.create_next_hop( + tunnel_id=tunnel_id + ) + util.verify_response( + self.response_consumer, nexthop_key, attr_list, "SWSS_RC_SUCCESS" + ) + # get nexthop_oid of newly created nexthop + nexthop_oid = self._p4rt_nexthop_obj.get_newly_created_nexthop_oid() + assert nexthop_oid is not None + + # Verify that P4RT key to OID count incremented by 1 in Redis DB. + count += 1 + status, fvs = key_to_oid_helper.get_db_info() + assert status == True + assert len(fvs) == len(original_key_oid_info) + count + + # Query application database for nexthop entries. + nexthop_entries = util.get_keys( + self._p4rt_nexthop_obj.appl_db, + self._p4rt_nexthop_obj.APP_DB_TBL_NAME + ":" + self._p4rt_nexthop_obj.TBL_NAME, + ) + assert len(nexthop_entries) == ( + self._p4rt_nexthop_obj.get_original_appl_db_entries_count() + 1 + ) + + # Query application database for newly created nexthop key. + (status, fvs) = util.get_key( + self._p4rt_nexthop_obj.appl_db, + self._p4rt_nexthop_obj.APP_DB_TBL_NAME, + nexthop_key, + ) + assert status == True + util.verify_attr(fvs, attr_list) + + # Query application state database for nexthop entries. + state_nexthop_entries = util.get_keys( + self._p4rt_nexthop_obj.appl_state_db, + self._p4rt_nexthop_obj.APP_DB_TBL_NAME + ":" + self._p4rt_nexthop_obj.TBL_NAME, + ) + assert len(state_nexthop_entries) == ( + self._p4rt_nexthop_obj.get_original_appl_state_db_entries_count() + 1 + ) + + # Query application state database for newly created nexthop key. + (status, fvs) = util.get_key( + self._p4rt_nexthop_obj.appl_state_db, + self._p4rt_nexthop_obj.APP_DB_TBL_NAME, + nexthop_key, + ) + assert status == True + util.verify_attr(fvs, attr_list) + + # Query ASIC database for nexthop entries. + nexthop_entries = util.get_keys( + self._p4rt_nexthop_obj.asic_db, self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME + ) + assert len(nexthop_entries) == ( + self._p4rt_nexthop_obj.get_original_asic_db_entries_count() + 1 + ) + + # Query ASIC database for newly created nexthop key. + asic_db_key = self._p4rt_nexthop_obj.get_newly_created_nexthop_oid() + assert asic_db_key is not None + (status, fvs) = util.get_key( + self._p4rt_nexthop_obj.asic_db, + self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) + assert status == True + attr_list = [ + (self._p4rt_nexthop_obj.SAI_ATTR_TUNNEL_OID, tunnel_oid), + (self._p4rt_nexthop_obj.SAI_ATTR_IP, + self._p4rt_nexthop_obj.DEFAULT_IPV4_NEIGHBOR_ID), + (self._p4rt_nexthop_obj.SAI_ATTR_TYPE, + self._p4rt_nexthop_obj.SAI_ATTR_TUNNEL_ENCAP) + ] + util.verify_attr(fvs, attr_list) + + # Remove nexthop. + self._p4rt_nexthop_obj.remove_app_db_entry(nexthop_key) + util.verify_response(self.response_consumer, + nexthop_key, [], "SWSS_RC_SUCCESS") + + # Verify that P4RT key to OID count decremented by 1 in Redis DB. + count -= 1 + status, fvs = key_to_oid_helper.get_db_info() + assert status == True + assert len(fvs) == len(original_key_oid_info) + count + + # Remove neighbor. + self._p4rt_neighbor_obj.remove_app_db_entry(neighbor_key) + util.verify_response( + self.response_consumer, neighbor_key, [], "SWSS_RC_SUCCESS" + ) + + # Verify that P4RT key to OID count decremented by 1 in Redis DB. + count -= 1 + status, fvs = key_to_oid_helper.get_db_info() + assert status == True + assert len(fvs) == len(original_key_oid_info) + count + + # Remove tunnel. + self._p4rt_gre_tunnel_obj.remove_app_db_entry(tunnel_key) + util.verify_response( + self.response_consumer, tunnel_key, [], "SWSS_RC_SUCCESS" + ) + + # Verify that P4RT key to OID count decremented by 1 in Redis DB. + count -= 1 + status, fvs = key_to_oid_helper.get_db_info() + assert status == True + assert len(fvs) == len(original_key_oid_info) + count + + # Remove router interface. + self._p4rt_router_intf_obj.remove_app_db_entry(router_intf_key) + util.verify_response( + self.response_consumer, router_intf_key, [], "SWSS_RC_SUCCESS" + ) + + # Verify that P4RT key to OID count is same as the original count. + status, fvs = key_to_oid_helper.get_db_info() + assert status == False + assert len(fvs) == len(original_key_oid_info) + + # Query application database for nexthop entries. + nexthop_entries = util.get_keys( + self._p4rt_nexthop_obj.appl_db, + self._p4rt_nexthop_obj.APP_DB_TBL_NAME + ":" + self._p4rt_nexthop_obj.TBL_NAME, + ) + assert len(nexthop_entries) == ( + self._p4rt_nexthop_obj.get_original_appl_db_entries_count() + ) + + # Verify that the nexthop_key no longer exists in application database. + (status, fsv) = util.get_key( + self._p4rt_nexthop_obj.appl_db, + self._p4rt_nexthop_obj.APP_DB_TBL_NAME, + nexthop_key, + ) + assert status == False + + # Query application state database for nexthop entries. + state_nexthop_entries = util.get_keys( + self._p4rt_nexthop_obj.appl_state_db, + self._p4rt_nexthop_obj.APP_DB_TBL_NAME + ":" + self._p4rt_nexthop_obj.TBL_NAME, + ) + assert len(state_nexthop_entries) == ( + self._p4rt_nexthop_obj.get_original_appl_state_db_entries_count() + ) + + # Verify that the nexthop_key no longer exists in application state # database. - (status, fsv) = util.get_key(self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fsv) = util.get_key( + self._p4rt_nexthop_obj.appl_state_db, + self._p4rt_nexthop_obj.APP_DB_TBL_NAME, + nexthop_key, + ) assert status == False - # Query ASIC database for route entries. - route_entries = util.get_keys(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME) - assert len(route_entries) == ( - self._p4rt_route_obj.get_original_asic_db_entries_count() + # Query ASIC database for nexthop entries. + nexthop_entries = util.get_keys( + self._p4rt_nexthop_obj.asic_db, self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME + ) + assert len(nexthop_entries) == ( + self._p4rt_nexthop_obj.get_original_asic_db_entries_count() ) - # Verify that removed route no longer exists in ASIC database. - (status, fvs) = util.get_key(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME, - asic_db_key) + # Verify that removed nexthop no longer exists in ASIC database. + (status, fvs) = util.get_key( + self._p4rt_nexthop_obj.asic_db, + self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) assert status == False - # Query application database for wcmp group entries. - wcmp_group_entries = util.get_keys( - self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + self._p4rt_wcmp_group_obj.TBL_NAME) - assert len(wcmp_group_entries) == ( - self._p4rt_wcmp_group_obj.get_original_appl_db_entries_count() + # Query application database for tunnel entries. + tunnel_entries = util.get_keys( + self._p4rt_gre_tunnel_obj.appl_db, + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME + + ":" + self._p4rt_gre_tunnel_obj.TBL_NAME, + ) + assert len(tunnel_entries) == ( + self._p4rt_gre_tunnel_obj.get_original_appl_db_entries_count() ) - # Verify that the route_key no longer exists in application database. - (status, fsv) = util.get_key(self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) + # Verify that the tunnel_key no longer exists in application database. + (status, fsv) = util.get_key( + self._p4rt_gre_tunnel_obj.appl_db, + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME, + tunnel_key, + ) assert status == False - # Query application state database for wcmp group entries. - state_wcmp_group_entries = util.get_keys( - self._p4rt_wcmp_group_obj.appl_state_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + self._p4rt_wcmp_group_obj.TBL_NAME) - assert len(state_wcmp_group_entries) == ( - self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() + # Query application state database for tunnel entries. + state_tunnel_entries = util.get_keys( + self._p4rt_gre_tunnel_obj.appl_state_db, + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME + + ":" + self._p4rt_gre_tunnel_obj.TBL_NAME, + ) + assert len(state_tunnel_entries) == ( + self._p4rt_gre_tunnel_obj.get_original_appl_state_db_entries_count() ) - # Verify that the wcmp_group_key no longer exists in application state + # Verify that the tunnel_key no longer exists in application state # database. - (status, fsv) = util.get_key(self._p4rt_wcmp_group_obj.appl_state_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) - assert status == False - - # Query ASIC database for wcmp group entries. - wcmp_group_entries = util.get_keys(self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME) - assert len(wcmp_group_entries) == ( - self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() + (status, fsv) = util.get_key( + self._p4rt_gre_tunnel_obj.appl_state_db, + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME, + tunnel_key, ) - - # Verify that removed wcmp group no longer exists in ASIC database. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, - wcmp_group_oid) assert status == False - # Query ASIC database for wcmp group member entries. - wcmp_group_member_entries = util.get_keys(self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME) - assert len(wcmp_group_member_entries) == ( - self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() + # Query ASIC database for tunnel entries. + runnel_entries = util.get_keys( + self._p4rt_gre_tunnel_obj.asic_db, self._p4rt_gre_tunnel_obj.ASIC_DB_TBL_NAME + ) + assert len(tunnel_entries) == ( + self._p4rt_gre_tunnel_obj.get_original_asic_db_entries_count() ) - # Verify that removed wcmp group member no longer exists in ASIC - # database. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, - asic_db_group_member_key) + # Verify that removed route no longer exists in ASIC database. + (status, fvs) = util.get_key( + self._p4rt_gre_tunnel_obj.asic_db, + self._p4rt_gre_tunnel_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) assert status == False - self._clean_vrf(dvs) def test_IPv4RouteAddWithInvalidNexthopFail(self, dvs, testlog): @@ -766,34 +1518,43 @@ def test_IPv4RouteAddWithInvalidNexthopFail(self, dvs, testlog): # Maintain list of original Application and ASIC DB entries before # adding new route. - db_list = ((self._p4rt_route_obj.appl_db, - "%s:%s" % (self._p4rt_route_obj.APP_DB_TBL_NAME, - self._p4rt_route_obj.TBL_NAME)), - (self._p4rt_route_obj.appl_state_db, - "%s:%s" % (self._p4rt_route_obj.APP_DB_TBL_NAME, - self._p4rt_route_obj.TBL_NAME)), - (self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME)) + db_list = ( + ( + self._p4rt_route_obj.appl_db, + "%s:%s" + % (self._p4rt_route_obj.APP_DB_TBL_NAME, self._p4rt_route_obj.TBL_NAME), + ), + ( + self._p4rt_route_obj.appl_state_db, + "%s:%s" + % (self._p4rt_route_obj.APP_DB_TBL_NAME, self._p4rt_route_obj.TBL_NAME), + ), + (self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME), + ) self._p4rt_route_obj.get_original_redis_entries(db_list) # Create route entry using invalid nexthop (expect failure). route_key, attr_list = self._p4rt_route_obj.create_route() err_log = "[OrchAgent] Nexthop ID '8' does not exist" - util.verify_response(self.response_consumer, route_key, attr_list, - "SWSS_RC_NOT_FOUND", err_log) + util.verify_response( + self.response_consumer, route_key, attr_list, "SWSS_RC_NOT_FOUND", err_log + ) # Query application database for route entries. route_entries = util.get_keys( self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_appl_db_entries_count() + 1 ) # Query application database for newly created route key. - (status, fvs) = util.get_key(self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == True util.verify_attr(fvs, attr_list) @@ -801,22 +1562,26 @@ def test_IPv4RouteAddWithInvalidNexthopFail(self, dvs, testlog): # expected). state_route_entries = util.get_keys( self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(state_route_entries) == ( self._p4rt_route_obj.get_original_appl_state_db_entries_count() ) # Verify that the newly added route key does not exist in application # state db. - (status, fvs) = util.get_key(self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == False # Query ASIC database for route entries (no new ASIC DB entry should be # created for route entry). - route_entries = util.get_keys(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME) + route_entries = util.get_keys( + self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_asic_db_entries_count() ) @@ -825,7 +1590,8 @@ def test_IPv4RouteAddWithInvalidNexthopFail(self, dvs, testlog): self._p4rt_route_obj.remove_app_db_entry(route_key) err_log = "[OrchAgent] Route entry does not exist" util.verify_response( - self.response_consumer, route_key, [], "SWSS_RC_NOT_FOUND", err_log) + self.response_consumer, route_key, [], "SWSS_RC_NOT_FOUND", err_log + ) self._clean_vrf(dvs) def test_IPv6RouteAddWithInvalidWcmpFail(self, dvs, testlog): @@ -840,56 +1606,72 @@ def test_IPv6RouteAddWithInvalidWcmpFail(self, dvs, testlog): # Maintain list of original Application and ASIC DB entries before # adding new route. - db_list = ((self._p4rt_route_obj.appl_db, - "%s:%s" % (self._p4rt_route_obj.APP_DB_TBL_NAME, - self._p4rt_route_obj.TBL_NAME)), - (self._p4rt_route_obj.appl_state_db, - "%s:%s" % (self._p4rt_route_obj.APP_DB_TBL_NAME, - self._p4rt_route_obj.TBL_NAME)), - (self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME)) + db_list = ( + ( + self._p4rt_route_obj.appl_db, + "%s:%s" + % (self._p4rt_route_obj.APP_DB_TBL_NAME, self._p4rt_route_obj.TBL_NAME), + ), + ( + self._p4rt_route_obj.appl_state_db, + "%s:%s" + % (self._p4rt_route_obj.APP_DB_TBL_NAME, self._p4rt_route_obj.TBL_NAME), + ), + (self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME), + ) self._p4rt_route_obj.get_original_redis_entries(db_list) # Create route entry using invalid wcmp group (expect failure). route_key, attr_list = self._p4rt_route_obj.create_route( - action="set_wcmp_group_id", wcmp_group_id="8") + action="set_wcmp_group_id", wcmp_group_id="8" + ) err_log = "[OrchAgent] WCMP group '8' does not exist" - util.verify_response(self.response_consumer, route_key, attr_list, - "SWSS_RC_NOT_FOUND", err_log) + util.verify_response( + self.response_consumer, route_key, attr_list, "SWSS_RC_NOT_FOUND", err_log + ) # Query application database for route entries - route_entries = util.get_keys(self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + route_entries = util.get_keys( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_appl_db_entries_count() + 1 ) # Query application database for newly created route key. - (status, fvs) = util.get_key(self._p4rt_route_obj.appl_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query application state database for route entries (no new APPL STATE DB # entry should be created for route entry). - state_route_entries = util.get_keys(self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME) + state_route_entries = util.get_keys( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME + ":" + self._p4rt_route_obj.TBL_NAME, + ) assert len(state_route_entries) == ( self._p4rt_route_obj.get_original_appl_state_db_entries_count() ) # Verify that newly created route key does not exist in application # state db. - (status, fvs) = util.get_key(self._p4rt_route_obj.appl_state_db, - self._p4rt_route_obj.APP_DB_TBL_NAME, - route_key) + (status, fvs) = util.get_key( + self._p4rt_route_obj.appl_state_db, + self._p4rt_route_obj.APP_DB_TBL_NAME, + route_key, + ) assert status == False # Query ASIC database for route entries (no new ASIC DB entry should be # created for route entry). - route_entries = util.get_keys(self._p4rt_route_obj.asic_db, - self._p4rt_route_obj.ASIC_DB_TBL_NAME) + route_entries = util.get_keys( + self._p4rt_route_obj.asic_db, self._p4rt_route_obj.ASIC_DB_TBL_NAME + ) assert len(route_entries) == ( self._p4rt_route_obj.get_original_asic_db_entries_count() ) @@ -898,7 +1680,8 @@ def test_IPv6RouteAddWithInvalidWcmpFail(self, dvs, testlog): self._p4rt_route_obj.remove_app_db_entry(route_key) err_log = "[OrchAgent] Route entry does not exist" util.verify_response( - self.response_consumer, route_key, [], "SWSS_RC_NOT_FOUND", err_log) + self.response_consumer, route_key, [], "SWSS_RC_NOT_FOUND", err_log + ) self._clean_vrf(dvs) def test_PruneAndRestoreNextHop(self, dvs, testlog): @@ -907,19 +1690,37 @@ def test_PruneAndRestoreNextHop(self, dvs, testlog): cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) # Maintain original WCMP group entries for ASIC DB. - db_list = ((self._p4rt_wcmp_group_obj.appl_db, - "%s:%s" % (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - self._p4rt_wcmp_group_obj.TBL_NAME)), - (self._p4rt_wcmp_group_obj.appl_state_db, - "%s:%s" % (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - self._p4rt_wcmp_group_obj.TBL_NAME)), - (self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME), - (self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME)) + db_list = ( + ( + self._p4rt_wcmp_group_obj.appl_db, + "%s:%s" + % ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + self._p4rt_wcmp_group_obj.TBL_NAME, + ), + ), + ( + self._p4rt_wcmp_group_obj.appl_state_db, + "%s:%s" + % ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + self._p4rt_wcmp_group_obj.TBL_NAME, + ), + ), + ( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + ), + ( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, + ), + ) self._p4rt_wcmp_group_obj.get_original_redis_entries(db_list) - db_list = ((self._p4rt_nexthop_obj.asic_db, - self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME),) + db_list = ( + (self._p4rt_nexthop_obj.asic_db, + self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME), + ) self._p4rt_nexthop_obj.get_original_redis_entries(db_list) # Fetch the original key to oid information from Redis DB. @@ -933,12 +1734,14 @@ def test_PruneAndRestoreNextHop(self, dvs, testlog): util.set_interface_status(dvs, if_name, "up") # Create router interface. - router_interface_id, router_intf_key, attr_list = ( - self._p4rt_router_intf_obj.create_router_interface() - ) + ( + router_interface_id, + router_intf_key, + attr_list, + ) = self._p4rt_router_intf_obj.create_router_interface() util.verify_response( - self.response_consumer, router_intf_key, attr_list, - "SWSS_RC_SUCCESS") + self.response_consumer, router_intf_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 1 in Redis DB. count = 1 @@ -947,11 +1750,10 @@ def test_PruneAndRestoreNextHop(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create neighbor. - neighbor_id, neighbor_key, attr_list = ( - self._p4rt_neighbor_obj.create_neighbor() - ) + neighbor_id, neighbor_key, attr_list = self._p4rt_neighbor_obj.create_neighbor() util.verify_response( - self.response_consumer, neighbor_key, attr_list, "SWSS_RC_SUCCESS") + self.response_consumer, neighbor_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 1 in Redis DB. count += 1 @@ -960,11 +1762,10 @@ def test_PruneAndRestoreNextHop(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create nexthop. - nexthop_id, nexthop_key, attr_list = ( - self._p4rt_nexthop_obj.create_next_hop() - ) + nexthop_id, nexthop_key, attr_list = self._p4rt_nexthop_obj.create_next_hop() util.verify_response( - self.response_consumer, nexthop_key, attr_list, "SWSS_RC_SUCCESS") + self.response_consumer, nexthop_key, attr_list, "SWSS_RC_SUCCESS" + ) # Get nexthop_oid of newly created nexthop. nexthop_oid = self._p4rt_nexthop_obj.get_newly_created_nexthop_oid() assert nexthop_oid is not None @@ -976,12 +1777,14 @@ def test_PruneAndRestoreNextHop(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create wcmp group with one member. - wcmp_group_id, wcmp_group_key, attr_list = ( - self._p4rt_wcmp_group_obj.create_wcmp_group(watch_port=port_name) - ) + ( + wcmp_group_id, + wcmp_group_key, + attr_list, + ) = self._p4rt_wcmp_group_obj.create_wcmp_group(watch_port=port_name) util.verify_response( - self.response_consumer, wcmp_group_key, attr_list, - "SWSS_RC_SUCCESS") + self.response_consumer, wcmp_group_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 2 in Redis DB # (1 each for WCMP group and member). @@ -993,40 +1796,50 @@ def test_PruneAndRestoreNextHop(self, dvs, testlog): # Query application database for wcmp group entries. wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + self._p4rt_wcmp_group_obj.TBL_NAME) + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME, + ) assert len(wcmp_group_entries) == ( self._p4rt_wcmp_group_obj.get_original_appl_db_entries_count() + 1 ) # Query application database for newly created wcmp group key. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.appl_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query application state database for wcmp group entries. state_wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_state_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + self._p4rt_wcmp_group_obj.TBL_NAME) + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME, + ) assert len(state_wcmp_group_entries) == ( - self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() + 1 ) # Query application state database for newly created wcmp group key. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.appl_state_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.appl_state_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query ASIC database for wcmp group entries. - wcmp_group_entries = util.get_keys(self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME) + wcmp_group_entries = util.get_keys( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + ) assert len(wcmp_group_entries) == ( - self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() + 1 ) # Query ASIC database for newly created wcmp group oid. @@ -1035,32 +1848,40 @@ def test_PruneAndRestoreNextHop(self, dvs, testlog): (status, fvs) = util.get_key( self._p4rt_wcmp_group_obj.asic_db, self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, - wcmp_group_oid + wcmp_group_oid, ) assert status == True asic_attr_list = [ - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_TYPE, - (self._p4rt_wcmp_group_obj. - SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP)) + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_TYPE, + ( + self._p4rt_wcmp_group_obj.SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP + ), + ) ] util.verify_attr(fvs, asic_attr_list) # Query ASIC database for newly created wcmp group member key. - asic_db_group_member_key = self._p4rt_wcmp_group_obj.get_newly_created_wcmp_group_member_asic_db_key() + asic_db_group_member_key = ( + self._p4rt_wcmp_group_obj.get_newly_created_wcmp_group_member_asic_db_key() + ) assert asic_db_group_member_key is not None (status, fvs) = util.get_key( self._p4rt_wcmp_group_obj.asic_db, self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, - asic_db_group_member_key + asic_db_group_member_key, ) assert status == True asic_attr_list = [ - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_GROUP_ID, - wcmp_group_oid), - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_ID, - nexthop_oid), - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_WEIGHT, - str(self._p4rt_wcmp_group_obj.DEFAULT_WEIGHT)) + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_GROUP_ID, + wcmp_group_oid, + ), + (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_ID, nexthop_oid), + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_WEIGHT, + str(self._p4rt_wcmp_group_obj.DEFAULT_WEIGHT), + ), ] util.verify_attr(fvs, asic_attr_list) @@ -1071,16 +1892,18 @@ def test_PruneAndRestoreNextHop(self, dvs, testlog): # pruned. wcmp_group_member_entries = util.get_keys( self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, ) assert len(wcmp_group_member_entries) == ( self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() ) # Check APPL STATE DB to verify no change. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.appl_state_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.appl_state_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) assert status == True util.verify_attr(fvs, attr_list) @@ -1090,18 +1913,19 @@ def test_PruneAndRestoreNextHop(self, dvs, testlog): # Check pruned next hop member is restored in ASIC DB. wcmp_group_member_entries = util.get_keys( self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, ) assert len(wcmp_group_member_entries) == ( - self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() + 1 + ) + asic_db_group_member_key = ( + self._p4rt_wcmp_group_obj.get_newly_created_wcmp_group_member_asic_db_key() ) - asic_db_group_member_key = self._p4rt_wcmp_group_obj.get_newly_created_wcmp_group_member_asic_db_key() assert asic_db_group_member_key is not None (status, fvs) = util.get_key( self._p4rt_wcmp_group_obj.asic_db, self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, - asic_db_group_member_key + asic_db_group_member_key, ) assert status == True util.verify_attr(fvs, asic_attr_list) @@ -1119,8 +1943,12 @@ def test_PruneAndRestoreNextHop(self, dvs, testlog): # Verify that APPL STATE DB is now updated. state_wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_state_db, - (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + - self._p4rt_wcmp_group_obj.TBL_NAME)) + ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME + ), + ) assert len(state_wcmp_group_entries) == ( self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() ) @@ -1157,19 +1985,37 @@ def test_PruneNextHopOnWarmBoot(self, dvs, testlog): cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) # Maintain original WCMP group entries for ASIC DB. - db_list = ((self._p4rt_wcmp_group_obj.appl_db, - "%s:%s" % (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - self._p4rt_wcmp_group_obj.TBL_NAME)), - (self._p4rt_wcmp_group_obj.appl_state_db, - "%s:%s" % (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - self._p4rt_wcmp_group_obj.TBL_NAME)), - (self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME), - (self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME)) + db_list = ( + ( + self._p4rt_wcmp_group_obj.appl_db, + "%s:%s" + % ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + self._p4rt_wcmp_group_obj.TBL_NAME, + ), + ), + ( + self._p4rt_wcmp_group_obj.appl_state_db, + "%s:%s" + % ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + self._p4rt_wcmp_group_obj.TBL_NAME, + ), + ), + ( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + ), + ( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, + ), + ) self._p4rt_wcmp_group_obj.get_original_redis_entries(db_list) - db_list = ((self._p4rt_nexthop_obj.asic_db, - self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME),) + db_list = ( + (self._p4rt_nexthop_obj.asic_db, + self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME), + ) self._p4rt_nexthop_obj.get_original_redis_entries(db_list) # Fetch the original key to oid information from Redis DB. @@ -1183,12 +2029,14 @@ def test_PruneNextHopOnWarmBoot(self, dvs, testlog): util.set_interface_status(dvs, if_name, "up") # Create router interface. - router_interface_id, router_intf_key, attr_list = ( - self._p4rt_router_intf_obj.create_router_interface() - ) + ( + router_interface_id, + router_intf_key, + attr_list, + ) = self._p4rt_router_intf_obj.create_router_interface() util.verify_response( - self.response_consumer, router_intf_key, attr_list, - "SWSS_RC_SUCCESS") + self.response_consumer, router_intf_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 1 in Redis DB. count = 1 @@ -1197,11 +2045,10 @@ def test_PruneNextHopOnWarmBoot(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create neighbor. - neighbor_id, neighbor_key, attr_list = ( - self._p4rt_neighbor_obj.create_neighbor() - ) + neighbor_id, neighbor_key, attr_list = self._p4rt_neighbor_obj.create_neighbor() util.verify_response( - self.response_consumer, neighbor_key, attr_list, "SWSS_RC_SUCCESS") + self.response_consumer, neighbor_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 1 in Redis DB. count += 1 @@ -1210,11 +2057,10 @@ def test_PruneNextHopOnWarmBoot(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create nexthop. - nexthop_id, nexthop_key, attr_list = ( - self._p4rt_nexthop_obj.create_next_hop() - ) + nexthop_id, nexthop_key, attr_list = self._p4rt_nexthop_obj.create_next_hop() util.verify_response( - self.response_consumer, nexthop_key, attr_list, "SWSS_RC_SUCCESS") + self.response_consumer, nexthop_key, attr_list, "SWSS_RC_SUCCESS" + ) # Get nexthop_oid of newly created nexthop. nexthop_oid = self._p4rt_nexthop_obj.get_newly_created_nexthop_oid() assert nexthop_oid is not None @@ -1226,12 +2072,14 @@ def test_PruneNextHopOnWarmBoot(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create wcmp group with one member. - wcmp_group_id, wcmp_group_key, attr_list = ( - self._p4rt_wcmp_group_obj.create_wcmp_group(watch_port=port_name) - ) + ( + wcmp_group_id, + wcmp_group_key, + attr_list, + ) = self._p4rt_wcmp_group_obj.create_wcmp_group(watch_port=port_name) util.verify_response( - self.response_consumer, wcmp_group_key, attr_list, - "SWSS_RC_SUCCESS") + self.response_consumer, wcmp_group_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 2 in Redis DB # (1 each for WCMP group and member). @@ -1243,40 +2091,50 @@ def test_PruneNextHopOnWarmBoot(self, dvs, testlog): # Query application database for wcmp group entries. wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + self._p4rt_wcmp_group_obj.TBL_NAME) + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME, + ) assert len(wcmp_group_entries) == ( self._p4rt_wcmp_group_obj.get_original_appl_db_entries_count() + 1 ) # Query application database for newly created wcmp group key. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.appl_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query application state database for wcmp group entries. state_wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_state_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + self._p4rt_wcmp_group_obj.TBL_NAME) + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME, + ) assert len(state_wcmp_group_entries) == ( - self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() + 1 ) # Query application state database for newly created wcmp group key. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.appl_state_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.appl_state_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query ASIC database for wcmp group entries. - wcmp_group_entries = util.get_keys(self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME) + wcmp_group_entries = util.get_keys( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + ) assert len(wcmp_group_entries) == ( - self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() + 1 ) # Query ASIC database for newly created wcmp group oid. @@ -1285,42 +2143,49 @@ def test_PruneNextHopOnWarmBoot(self, dvs, testlog): (status, fvs) = util.get_key( self._p4rt_wcmp_group_obj.asic_db, self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, - wcmp_group_oid + wcmp_group_oid, ) assert status == True asic_attr_list = [ - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_TYPE, - (self._p4rt_wcmp_group_obj. - SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP)) + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_TYPE, + ( + self._p4rt_wcmp_group_obj.SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP + ), + ) ] util.verify_attr(fvs, asic_attr_list) # Query ASIC database for wcmp group member entries. wcmp_group_member_entries = util.get_keys( self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, ) assert len(wcmp_group_member_entries) == ( - self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() + 1 ) # Query ASIC database for newly created wcmp group member key. - asic_db_group_member_key = self._p4rt_wcmp_group_obj.get_newly_created_wcmp_group_member_asic_db_key() + asic_db_group_member_key = ( + self._p4rt_wcmp_group_obj.get_newly_created_wcmp_group_member_asic_db_key() + ) assert asic_db_group_member_key is not None (status, fvs) = util.get_key( self._p4rt_wcmp_group_obj.asic_db, self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, - asic_db_group_member_key + asic_db_group_member_key, ) assert status == True asic_attr_list = [ - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_GROUP_ID, - wcmp_group_oid), - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_ID, - nexthop_oid), - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_WEIGHT, - str(self._p4rt_wcmp_group_obj.DEFAULT_WEIGHT)) + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_GROUP_ID, + wcmp_group_oid, + ), + (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_ID, nexthop_oid), + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_WEIGHT, + str(self._p4rt_wcmp_group_obj.DEFAULT_WEIGHT), + ), ] util.verify_attr(fvs, asic_attr_list) @@ -1338,7 +2203,7 @@ def test_PruneNextHopOnWarmBoot(self, dvs, testlog): # Verify that the associated next hop is pruned in ASIC DB. wcmp_group_member_entries = util.get_keys( self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, ) assert len(wcmp_group_member_entries) == ( self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() @@ -1357,8 +2222,12 @@ def test_PruneNextHopOnWarmBoot(self, dvs, testlog): # Verify that APPL STATE DB is updated. state_wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_state_db, - (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + - self._p4rt_wcmp_group_obj.TBL_NAME)) + ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME + ), + ) assert len(state_wcmp_group_entries) == ( self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() ) @@ -1395,19 +2264,37 @@ def test_CreateWcmpMemberForOperUpWatchportOnly(self, dvs, testlog): cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) # Maintain original WCMP group entries for ASIC DB. - db_list = ((self._p4rt_wcmp_group_obj.appl_db, - "%s:%s" % (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - self._p4rt_wcmp_group_obj.TBL_NAME)), - (self._p4rt_wcmp_group_obj.appl_state_db, - "%s:%s" % (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - self._p4rt_wcmp_group_obj.TBL_NAME)), - (self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME), - (self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME)) + db_list = ( + ( + self._p4rt_wcmp_group_obj.appl_db, + "%s:%s" + % ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + self._p4rt_wcmp_group_obj.TBL_NAME, + ), + ), + ( + self._p4rt_wcmp_group_obj.appl_state_db, + "%s:%s" + % ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + self._p4rt_wcmp_group_obj.TBL_NAME, + ), + ), + ( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + ), + ( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, + ), + ) self._p4rt_wcmp_group_obj.get_original_redis_entries(db_list) - db_list = ((self._p4rt_nexthop_obj.asic_db, - self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME),) + db_list = ( + (self._p4rt_nexthop_obj.asic_db, + self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME), + ) self._p4rt_nexthop_obj.get_original_redis_entries(db_list) # Fetch the original key to oid information from Redis DB. @@ -1421,12 +2308,14 @@ def test_CreateWcmpMemberForOperUpWatchportOnly(self, dvs, testlog): util.set_interface_status(dvs, if_name) # Create router interface. - router_interface_id, router_intf_key, attr_list = ( - self._p4rt_router_intf_obj.create_router_interface() - ) + ( + router_interface_id, + router_intf_key, + attr_list, + ) = self._p4rt_router_intf_obj.create_router_interface() util.verify_response( - self.response_consumer, router_intf_key, attr_list, - "SWSS_RC_SUCCESS") + self.response_consumer, router_intf_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 1 in Redis DB. count = 1 @@ -1435,11 +2324,10 @@ def test_CreateWcmpMemberForOperUpWatchportOnly(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create neighbor. - neighbor_id, neighbor_key, attr_list = ( - self._p4rt_neighbor_obj.create_neighbor() - ) + neighbor_id, neighbor_key, attr_list = self._p4rt_neighbor_obj.create_neighbor() util.verify_response( - self.response_consumer, neighbor_key, attr_list, "SWSS_RC_SUCCESS") + self.response_consumer, neighbor_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 1 in Redis DB. count += 1 @@ -1448,11 +2336,10 @@ def test_CreateWcmpMemberForOperUpWatchportOnly(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create nexthop. - nexthop_id, nexthop_key, attr_list = ( - self._p4rt_nexthop_obj.create_next_hop() - ) + nexthop_id, nexthop_key, attr_list = self._p4rt_nexthop_obj.create_next_hop() util.verify_response( - self.response_consumer, nexthop_key, attr_list, "SWSS_RC_SUCCESS") + self.response_consumer, nexthop_key, attr_list, "SWSS_RC_SUCCESS" + ) # Get nexthop_oid of newly created nexthop. nexthop_oid = self._p4rt_nexthop_obj.get_newly_created_nexthop_oid() assert nexthop_oid is not None @@ -1464,12 +2351,14 @@ def test_CreateWcmpMemberForOperUpWatchportOnly(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create wcmp group with one member. - wcmp_group_id, wcmp_group_key, attr_list = ( - self._p4rt_wcmp_group_obj.create_wcmp_group(watch_port=port_name) - ) + ( + wcmp_group_id, + wcmp_group_key, + attr_list, + ) = self._p4rt_wcmp_group_obj.create_wcmp_group(watch_port=port_name) util.verify_response( - self.response_consumer, wcmp_group_key, attr_list, - "SWSS_RC_SUCCESS") + self.response_consumer, wcmp_group_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 1 in Redis DB # (WCMP group member is not created for operationally down watchport). @@ -1481,39 +2370,50 @@ def test_CreateWcmpMemberForOperUpWatchportOnly(self, dvs, testlog): # Query application database for wcmp group entries. wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + self._p4rt_wcmp_group_obj.TBL_NAME) + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME, + ) assert len(wcmp_group_entries) == ( self._p4rt_wcmp_group_obj.get_original_appl_db_entries_count() + 1 ) # Query application database for newly created wcmp group key. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.appl_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query application state database for wcmp group entries. state_wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_state_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + self._p4rt_wcmp_group_obj.TBL_NAME) + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME, + ) assert len(state_wcmp_group_entries) == ( self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() + 1 ) # Query application state database for newly created wcmp group key. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.appl_state_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.appl_state_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query ASIC database for wcmp group entries. - wcmp_group_entries = util.get_keys(self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME) + wcmp_group_entries = util.get_keys( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + ) assert len(wcmp_group_entries) == ( - self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() + 1 ) # Query ASIC database for newly created wcmp group oid. @@ -1522,20 +2422,23 @@ def test_CreateWcmpMemberForOperUpWatchportOnly(self, dvs, testlog): (status, fvs) = util.get_key( self._p4rt_wcmp_group_obj.asic_db, self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, - wcmp_group_oid + wcmp_group_oid, ) assert status == True asic_attr_list = [ - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_TYPE, - (self._p4rt_wcmp_group_obj. - SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP)) + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_TYPE, + ( + self._p4rt_wcmp_group_obj.SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP + ), + ) ] util.verify_attr(fvs, asic_attr_list) # Query ASIC database for wcmp group member entries (expect no entry). wcmp_group_member_entries = util.get_keys( self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, ) assert len(wcmp_group_member_entries) == ( self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() @@ -1555,26 +2458,31 @@ def test_CreateWcmpMemberForOperUpWatchportOnly(self, dvs, testlog): # Verify that next hop member is now created in SAI. wcmp_group_member_entries = util.get_keys( self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, ) assert len(wcmp_group_member_entries) == ( - self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() + 1 + ) + asic_db_group_member_key = ( + self._p4rt_wcmp_group_obj.get_newly_created_wcmp_group_member_asic_db_key() ) - asic_db_group_member_key = self._p4rt_wcmp_group_obj.get_newly_created_wcmp_group_member_asic_db_key() assert asic_db_group_member_key is not None - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.asic_db, - (self._p4rt_wcmp_group_obj. - ASIC_DB_GROUP_MEMBER_TBL_NAME), - asic_db_group_member_key) + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.asic_db, + (self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME), + asic_db_group_member_key, + ) assert status == True asic_attr_list = [ - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_GROUP_ID, - wcmp_group_oid), - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_ID, - nexthop_oid), - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_WEIGHT, - str(self._p4rt_wcmp_group_obj.DEFAULT_WEIGHT)) + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_GROUP_ID, + wcmp_group_oid, + ), + (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_NEXTHOP_ID, nexthop_oid), + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_MEMBER_WEIGHT, + str(self._p4rt_wcmp_group_obj.DEFAULT_WEIGHT), + ), ] util.verify_attr(fvs, asic_attr_list) @@ -1591,8 +2499,12 @@ def test_CreateWcmpMemberForOperUpWatchportOnly(self, dvs, testlog): # Verify that APPL STATE DB is updated. state_wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_state_db, - (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + - self._p4rt_wcmp_group_obj.TBL_NAME)) + ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME + ), + ) assert len(state_wcmp_group_entries) == ( self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() ) @@ -1629,19 +2541,37 @@ def test_RemovePrunedWcmpGroupMember(self, dvs, testlog): cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) # Maintain original WCMP group entries for ASIC DB. - db_list = ((self._p4rt_wcmp_group_obj.appl_db, - "%s:%s" % (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - self._p4rt_wcmp_group_obj.TBL_NAME)), - (self._p4rt_wcmp_group_obj.appl_state_db, - "%s:%s" % (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - self._p4rt_wcmp_group_obj.TBL_NAME)), - (self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME), - (self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME)) + db_list = ( + ( + self._p4rt_wcmp_group_obj.appl_db, + "%s:%s" + % ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + self._p4rt_wcmp_group_obj.TBL_NAME, + ), + ), + ( + self._p4rt_wcmp_group_obj.appl_state_db, + "%s:%s" + % ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + self._p4rt_wcmp_group_obj.TBL_NAME, + ), + ), + ( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + ), + ( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, + ), + ) self._p4rt_wcmp_group_obj.get_original_redis_entries(db_list) - db_list = ((self._p4rt_nexthop_obj.asic_db, - self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME),) + db_list = ( + (self._p4rt_nexthop_obj.asic_db, + self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME), + ) self._p4rt_nexthop_obj.get_original_redis_entries(db_list) # Fetch the original key to oid information from Redis DB. @@ -1655,12 +2585,14 @@ def test_RemovePrunedWcmpGroupMember(self, dvs, testlog): util.set_interface_status(dvs, if_name) # Create router interface. - router_interface_id, router_intf_key, attr_list = ( - self._p4rt_router_intf_obj.create_router_interface() - ) + ( + router_interface_id, + router_intf_key, + attr_list, + ) = self._p4rt_router_intf_obj.create_router_interface() util.verify_response( - self.response_consumer, router_intf_key, attr_list, - "SWSS_RC_SUCCESS") + self.response_consumer, router_intf_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 1 in Redis DB. count = 1 @@ -1669,11 +2601,10 @@ def test_RemovePrunedWcmpGroupMember(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create neighbor. - neighbor_id, neighbor_key, attr_list = ( - self._p4rt_neighbor_obj.create_neighbor() - ) + neighbor_id, neighbor_key, attr_list = self._p4rt_neighbor_obj.create_neighbor() util.verify_response( - self.response_consumer, neighbor_key, attr_list, "SWSS_RC_SUCCESS") + self.response_consumer, neighbor_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 1 in Redis DB. count += 1 @@ -1682,11 +2613,10 @@ def test_RemovePrunedWcmpGroupMember(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create nexthop. - nexthop_id, nexthop_key, attr_list = ( - self._p4rt_nexthop_obj.create_next_hop() - ) + nexthop_id, nexthop_key, attr_list = self._p4rt_nexthop_obj.create_next_hop() util.verify_response( - self.response_consumer, nexthop_key, attr_list, "SWSS_RC_SUCCESS") + self.response_consumer, nexthop_key, attr_list, "SWSS_RC_SUCCESS" + ) # Get nexthop_oid of newly created nexthop. nexthop_oid = self._p4rt_nexthop_obj.get_newly_created_nexthop_oid() assert nexthop_oid is not None @@ -1698,12 +2628,14 @@ def test_RemovePrunedWcmpGroupMember(self, dvs, testlog): assert len(fvs) == len(original_key_oid_info) + count # Create wcmp group with one member. - wcmp_group_id, wcmp_group_key, attr_list = ( - self._p4rt_wcmp_group_obj.create_wcmp_group(watch_port=port_name) - ) + ( + wcmp_group_id, + wcmp_group_key, + attr_list, + ) = self._p4rt_wcmp_group_obj.create_wcmp_group(watch_port=port_name) util.verify_response( - self.response_consumer, wcmp_group_key, attr_list, - "SWSS_RC_SUCCESS") + self.response_consumer, wcmp_group_key, attr_list, "SWSS_RC_SUCCESS" + ) # Verify that P4RT key to OID count incremented by 1 in Redis DB # (WCMP group member is not created for operationally down watchport). @@ -1715,39 +2647,50 @@ def test_RemovePrunedWcmpGroupMember(self, dvs, testlog): # Query application database for wcmp group entries. wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + self._p4rt_wcmp_group_obj.TBL_NAME) + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME, + ) assert len(wcmp_group_entries) == ( self._p4rt_wcmp_group_obj.get_original_appl_db_entries_count() + 1 ) # Query application database for newly created wcmp group key. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.appl_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query application state database for wcmp group entries. state_wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + self._p4rt_wcmp_group_obj.TBL_NAME) + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME, + ) assert len(state_wcmp_group_entries) == ( self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() + 1 ) # Query application state database for newly created wcmp group key. - (status, fvs) = util.get_key(self._p4rt_wcmp_group_obj.appl_state_db, - self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, - wcmp_group_key) + (status, fvs) = util.get_key( + self._p4rt_wcmp_group_obj.appl_state_db, + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME, + wcmp_group_key, + ) assert status == True util.verify_attr(fvs, attr_list) # Query ASIC database for wcmp group entries. - wcmp_group_entries = util.get_keys(self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME) + wcmp_group_entries = util.get_keys( + self._p4rt_wcmp_group_obj.asic_db, + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, + ) assert len(wcmp_group_entries) == ( - self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() + 1 ) # Query ASIC database for newly created wcmp group oid. @@ -1756,33 +2699,37 @@ def test_RemovePrunedWcmpGroupMember(self, dvs, testlog): (status, fvs) = util.get_key( self._p4rt_wcmp_group_obj.asic_db, self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, - wcmp_group_oid + wcmp_group_oid, ) assert status == True asic_attr_list = [ - (self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_TYPE, - (self._p4rt_wcmp_group_obj. - SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP)) + ( + self._p4rt_wcmp_group_obj.SAI_ATTR_GROUP_TYPE, + ( + self._p4rt_wcmp_group_obj.SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP + ), + ) ] util.verify_attr(fvs, asic_attr_list) # Query ASIC database for wcmp group member entries. wcmp_group_member_entries = util.get_keys( self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, ) assert len(wcmp_group_member_entries) == ( - self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() - + 1 + self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() + 1 ) # Query ASIC database for wcmp group member entries (expect no entry). wcmp_group_member_entries = util.get_keys( self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, + ) + assert ( + len(wcmp_group_member_entries) + == self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() ) - assert len( - wcmp_group_member_entries) == self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() # Delete the pruned wcmp group member. self._p4rt_wcmp_group_obj.remove_app_db_entry(wcmp_group_key) @@ -1796,8 +2743,12 @@ def test_RemovePrunedWcmpGroupMember(self, dvs, testlog): # Verify that APPL STATE DB is updated. state_wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.appl_state_db, - (self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + ":" + - self._p4rt_wcmp_group_obj.TBL_NAME)) + ( + self._p4rt_wcmp_group_obj.APP_DB_TBL_NAME + + ":" + + self._p4rt_wcmp_group_obj.TBL_NAME + ), + ) assert len(state_wcmp_group_entries) == ( self._p4rt_wcmp_group_obj.get_original_appl_state_db_entries_count() ) @@ -1805,14 +2756,14 @@ def test_RemovePrunedWcmpGroupMember(self, dvs, testlog): # Verify that ASIC DB is updated. wcmp_group_entries = util.get_keys( self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_TBL_NAME, ) assert len(wcmp_group_entries) == ( self._p4rt_wcmp_group_obj.get_original_asic_db_group_entries_count() ) wcmp_group_member_entries = util.get_keys( self._p4rt_wcmp_group_obj.asic_db, - self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME + self._p4rt_wcmp_group_obj.ASIC_DB_GROUP_MEMBER_TBL_NAME, ) assert len(wcmp_group_member_entries) == ( self._p4rt_wcmp_group_obj.get_original_asic_db_member_entries_count() @@ -1843,3 +2794,144 @@ def test_RemovePrunedWcmpGroupMember(self, dvs, testlog): status, fvs = key_to_oid_helper.get_db_info() assert status == False assert len(fvs) == len(original_key_oid_info) + + def test_NexthopWithGreTunnelCreationFailIfDependenciesAreMissing(self, dvs, testlog): + # Initialize L3 objects and database connectors. + self._set_up(dvs) + self._set_vrf(dvs) + + # Maintain list of original Application and ASIC DB entries before + # adding new entries. + db_list = ( + ( + self._p4rt_nexthop_obj.appl_db, + "%s:%s" + % ( + self._p4rt_nexthop_obj.APP_DB_TBL_NAME, + self._p4rt_nexthop_obj.TBL_NAME, + ), + ), + ( + self._p4rt_nexthop_obj.appl_state_db, + "%s:%s" + % ( + self._p4rt_nexthop_obj.APP_DB_TBL_NAME, + self._p4rt_nexthop_obj.TBL_NAME, + ), + ), + (self._p4rt_nexthop_obj.asic_db, + self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME), + ) + self._p4rt_nexthop_obj.get_original_redis_entries(db_list) + db_list = ( + ( + self._p4rt_gre_tunnel_obj.appl_db, + "%s:%s" + % ( + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME, + self._p4rt_gre_tunnel_obj.TBL_NAME, + ), + ), + ( + self._p4rt_gre_tunnel_obj.appl_state_db, + "%s:%s" + % ( + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME, + self._p4rt_gre_tunnel_obj.TBL_NAME, + ), + ), + (self._p4rt_gre_tunnel_obj.asic_db, + self._p4rt_gre_tunnel_obj.ASIC_DB_TBL_NAME), + ) + self._p4rt_gre_tunnel_obj.get_original_redis_entries(db_list) + db_list = ( + (self._p4rt_router_intf_obj.asic_db, + self._p4rt_router_intf_obj.ASIC_DB_TBL_NAME), + ) + self._p4rt_router_intf_obj.get_original_redis_entries(db_list) + + # Fetch the original key to oid information from Redis DB. + key_to_oid_helper = util.KeyToOidDBHelper(dvs) + _, original_key_oid_info = key_to_oid_helper.get_db_info() + + # Create tunnel. + tunnel_id, tunnel_key, attr_list = self._p4rt_gre_tunnel_obj.create_gre_tunnel() + util.verify_response( + self.response_consumer, tunnel_key, attr_list, "SWSS_RC_NOT_FOUND", + "[OrchAgent] Router intf '16' does not exist" + ) + + # Verify that P4RT key to OID count does not change in Redis DB. + status, fvs = key_to_oid_helper.get_db_info() + assert status == False + assert len(fvs) == len(original_key_oid_info) + + # Query application database for tunnel entries. + tunnel_entries = util.get_keys( + self._p4rt_gre_tunnel_obj.appl_db, + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME + + ":" + self._p4rt_gre_tunnel_obj.TBL_NAME, + ) + assert len(tunnel_entries) == ( + self._p4rt_gre_tunnel_obj.get_original_appl_db_entries_count() + 1 + ) + + # Query application state database for tunnel entries. + state_tunnel_entries = util.get_keys( + self._p4rt_gre_tunnel_obj.appl_state_db, + self._p4rt_gre_tunnel_obj.APP_DB_TBL_NAME + + ":" + self._p4rt_gre_tunnel_obj.TBL_NAME, + ) + assert len(state_tunnel_entries) == ( + self._p4rt_gre_tunnel_obj.get_original_appl_state_db_entries_count() + ) + + # Query ASIC database for tunnel entries. + tunnel_entries = util.get_keys( + self._p4rt_gre_tunnel_obj.asic_db, self._p4rt_gre_tunnel_obj.ASIC_DB_TBL_NAME + ) + assert len(tunnel_entries) == ( + self._p4rt_gre_tunnel_obj.get_original_asic_db_entries_count() + ) + + # Create tunnel nexthop. + nexthop_id, nexthop_key, attr_list = self._p4rt_nexthop_obj.create_next_hop( + tunnel_id=tunnel_id + ) + util.verify_response( + self.response_consumer, nexthop_key, attr_list, "SWSS_RC_NOT_FOUND", + "[OrchAgent] GRE Tunnel 'tunnel-1' does not exist in GRE Tunnel Manager" + ) + + # Verify that P4RT key to OID count does not change in Redis DB. + status, fvs = key_to_oid_helper.get_db_info() + assert status == False + assert len(fvs) == len(original_key_oid_info) + + # Query application database for nexthop entries. + nexthop_entries = util.get_keys( + self._p4rt_nexthop_obj.appl_db, + self._p4rt_nexthop_obj.APP_DB_TBL_NAME + ":" + self._p4rt_nexthop_obj.TBL_NAME, + ) + assert len(nexthop_entries) == ( + self._p4rt_nexthop_obj.get_original_appl_db_entries_count() + 1 + ) + + # Query application state database for nexthop entries. + state_nexthop_entries = util.get_keys( + self._p4rt_nexthop_obj.appl_state_db, + self._p4rt_nexthop_obj.APP_DB_TBL_NAME + ":" + self._p4rt_nexthop_obj.TBL_NAME, + ) + assert len(state_nexthop_entries) == ( + self._p4rt_nexthop_obj.get_original_appl_state_db_entries_count() + ) + + # Query ASIC database for nexthop entries. + nexthop_entries = util.get_keys( + self._p4rt_nexthop_obj.asic_db, self._p4rt_nexthop_obj.ASIC_DB_TBL_NAME + ) + assert len(nexthop_entries) == ( + self._p4rt_nexthop_obj.get_original_asic_db_entries_count() + ) + + self._clean_vrf(dvs) diff --git a/tests/p4rt/test_l3_admit.py b/tests/p4rt/test_l3_admit.py new file mode 100644 index 000000000000..81ffdf884ad1 --- /dev/null +++ b/tests/p4rt/test_l3_admit.py @@ -0,0 +1,263 @@ +from swsscommon import swsscommon + +import pytest +import json +import util +import l3_admit + + +class TestP4RTL3Admit(object): + def _set_up(self, dvs): + self._p4rt_l3_admit_obj = l3_admit.P4RtL3AdmitWrapper() + + self._p4rt_l3_admit_obj.set_up_databases(dvs) + self.response_consumer = swsscommon.NotificationConsumer( + self._p4rt_l3_admit_obj.appl_db, "APPL_DB_" + + swsscommon.APP_P4RT_TABLE_NAME + "_RESPONSE_CHANNEL" + ) + + @pytest.mark.skip(reason="sairedis vs MY MAC support is not ready") + def test_DefaultL3AdmitAddDeletePass(self, dvs, testlog): + # Initialize database connectors. + self._set_up(dvs) + + # Maintain list of original Application and ASIC DB entries before + # adding new entries + db_list = ( + ( + self._p4rt_l3_admit_obj.appl_db, + "%s:%s" + % (self._p4rt_l3_admit_obj.APP_DB_TBL_NAME, self._p4rt_l3_admit_obj.TBL_NAME), + ), + ( + self._p4rt_l3_admit_obj.appl_state_db, + "%s:%s" + % (self._p4rt_l3_admit_obj.APP_DB_TBL_NAME, self._p4rt_l3_admit_obj.TBL_NAME), + ), + (self._p4rt_l3_admit_obj.asic_db, + self._p4rt_l3_admit_obj.ASIC_DB_TBL_NAME), + ) + self._p4rt_l3_admit_obj.get_original_redis_entries(db_list) + + # Fetch the original key to oid information from Redis DB. + key_to_oid_helper = util.KeyToOidDBHelper(dvs) + _, original_key_oid_info = key_to_oid_helper.get_db_info() + + # l3 admit entry attributes + # P4RT_TABLE:FIXED_L3_ADMIT_TABLE:{\"match/dst_mac\":\"00:02:03:04:00:00&ff:ff:ff:ff:00:00\",\"match/in_port\":\"Ethernet8\",\"priority\":2030} + # "action": "admit_to_l3" + # "controller_metadata": "..." + dst_mac_data = "00:02:03:04:00:00" + dst_mac_mask = "FF:FF:FF:FF:00:00" + in_port = "Ethernet8" + priority = 2030 + + # Create l3 admit entry. + ( + l3_admit_key, + attr_list, + ) = self._p4rt_l3_admit_obj.create_l3_admit(dst_mac_data + "&" + dst_mac_mask, priority, in_port) + util.verify_response( + self.response_consumer, l3_admit_key, attr_list, "SWSS_RC_SUCCESS" + ) + + # Verify that P4RT key to OID count incremented by 1 in Redis DB. + count = 1 + status, fvs = key_to_oid_helper.get_db_info() + assert status == True + assert len(fvs) == len(original_key_oid_info) + count + + # Query application database for l3 admit entries. + l3_admit_entries = util.get_keys( + self._p4rt_l3_admit_obj.appl_db, + self._p4rt_l3_admit_obj.APP_DB_TBL_NAME + + ":" + self._p4rt_l3_admit_obj.TBL_NAME, + ) + assert len(l3_admit_entries) == ( + self._p4rt_l3_admit_obj.get_original_appl_db_entries_count() + 1 + ) + + # Query application database for newly created l3 admit key. + (status, fvs) = util.get_key( + self._p4rt_l3_admit_obj.appl_db, + self._p4rt_l3_admit_obj.APP_DB_TBL_NAME, + l3_admit_key, + ) + assert status == True + util.verify_attr(fvs, attr_list) + + # Query application state database for l3 admit entries. + state_l3_admit_entries = util.get_keys( + self._p4rt_l3_admit_obj.appl_state_db, + self._p4rt_l3_admit_obj.APP_DB_TBL_NAME + + ":" + self._p4rt_l3_admit_obj.TBL_NAME, + ) + assert len(state_l3_admit_entries) == ( + self._p4rt_l3_admit_obj.get_original_appl_state_db_entries_count() + 1 + ) + + # Query application state database for newly created l3 admit key. + (status, fvs) = util.get_key( + self._p4rt_l3_admit_obj.appl_state_db, + self._p4rt_l3_admit_obj.APP_DB_TBL_NAME, + l3_admit_key, + ) + assert status == True + util.verify_attr(fvs, attr_list) + + # Query ASIC database for my mac entries. + asic_l3_admit_entries = util.get_keys( + self._p4rt_l3_admit_obj.asic_db, self._p4rt_l3_admit_obj.ASIC_DB_TBL_NAME + ) + assert len(asic_l3_admit_entries) == ( + self._p4rt_l3_admit_obj.get_original_asic_db_entries_count() + 1 + ) + + # Query ASIC database for newly created my mac key. + asic_db_key = self._p4rt_l3_admit_obj.get_newly_created_asic_db_key() + assert asic_db_key is not None + (status, fvs) = util.get_key( + self._p4rt_l3_admit_obj.asic_db, + self._p4rt_l3_admit_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) + assert status == True + attr_list = [(self._p4rt_l3_admit_obj.SAI_ATTR_DST_MAC, dst_mac_data), + (self._p4rt_l3_admit_obj.SAI_ATTR_DST_MAC_MASK, dst_mac_mask), + (self._p4rt_l3_admit_obj.SAI_ATTR_PRIORITY, str(priority)), + (self._p4rt_l3_admit_obj.SAI_ATTR_PORT_ID, util.get_port_oid_by_name(dvs, in_port))] + util.verify_attr(fvs, attr_list) + + # deplicate SET will be no-op. + new_l3_admit_key, new_attr_list = self._p4rt_l3_admit_obj.create_l3_admit( + dst_mac_data + "&" + dst_mac_mask, priority, in_port) + util.verify_response( + self.response_consumer, new_l3_admit_key, new_attr_list, + "SWSS_RC_SUCCESS", + "L3 Admit entry with the same key received: 'match/dst_mac=00:02:03:04:00:00&ff:ff:ff:ff:00:00:match/in_port=Ethernet8:priority=2030'" + ) + + # Verify that P4RT key to OID count did not change in Redis DB. + status, fvs = key_to_oid_helper.get_db_info() + assert status == True + assert len(fvs) == len(original_key_oid_info) + count + + # Remove l3 admit entry. + self._p4rt_l3_admit_obj.remove_app_db_entry(l3_admit_key) + util.verify_response(self.response_consumer, + l3_admit_key, [], "SWSS_RC_SUCCESS") + + # Verify that P4RT key to OID count decremented to orig in Redis DB. + status, fvs = key_to_oid_helper.get_db_info() + assert status == False + assert len(fvs) == len(original_key_oid_info) + + # Query application database for route entries. + l3_admit_entries = util.get_keys( + self._p4rt_l3_admit_obj.appl_db, + self._p4rt_l3_admit_obj.APP_DB_TBL_NAME + + ":" + self._p4rt_l3_admit_obj.TBL_NAME, + ) + assert len(l3_admit_entries) == ( + self._p4rt_l3_admit_obj.get_original_appl_db_entries_count() + ) + + # Verify that the route_key no longer exists in application database. + (status, fsv) = util.get_key( + self._p4rt_l3_admit_obj.appl_db, + self._p4rt_l3_admit_obj.APP_DB_TBL_NAME, + l3_admit_key, + ) + assert status == False + + # Query application database for route entries. + state_l3_admit_entries = util.get_keys( + self._p4rt_l3_admit_obj.appl_state_db, + self._p4rt_l3_admit_obj.APP_DB_TBL_NAME + + ":" + self._p4rt_l3_admit_obj.TBL_NAME, + ) + assert len(state_l3_admit_entries) == ( + self._p4rt_l3_admit_obj.get_original_appl_state_db_entries_count() + ) + + # Verify that the route_key no longer exists in application database. + (status, fsv) = util.get_key( + self._p4rt_l3_admit_obj.appl_state_db, + self._p4rt_l3_admit_obj.APP_DB_TBL_NAME, + l3_admit_key, + ) + assert status == False + + # Query ASIC database for my mac entries. + my_mac_entries = util.get_keys( + self._p4rt_l3_admit_obj.asic_db, self._p4rt_l3_admit_obj.ASIC_DB_TBL_NAME + ) + assert len(my_mac_entries) == ( + self._p4rt_l3_admit_obj.get_original_asic_db_entries_count() + ) + + # Verify that removed route no longer exists in ASIC database. + (status, fvs) = util.get_key( + self._p4rt_l3_admit_obj.asic_db, + self._p4rt_l3_admit_obj.ASIC_DB_TBL_NAME, + asic_db_key, + ) + assert status == False + + def test_InvalidL3AdmitKeyFailsToCreate(self, dvs, testlog): + # Initialize database connectors. + self._set_up(dvs) + + # Maintain list of original Application and ASIC DB entries before + # adding new entries + db_list = ( + ( + self._p4rt_l3_admit_obj.appl_db, + "%s:%s" + % (self._p4rt_l3_admit_obj.APP_DB_TBL_NAME, self._p4rt_l3_admit_obj.TBL_NAME), + ), + ( + self._p4rt_l3_admit_obj.appl_state_db, + "%s:%s" + % (self._p4rt_l3_admit_obj.APP_DB_TBL_NAME, self._p4rt_l3_admit_obj.TBL_NAME), + ), + (self._p4rt_l3_admit_obj.asic_db, + self._p4rt_l3_admit_obj.ASIC_DB_TBL_NAME), + ) + self._p4rt_l3_admit_obj.get_original_redis_entries(db_list) + + # Fetch the original key to oid information from Redis DB. + key_to_oid_helper = util.KeyToOidDBHelper(dvs) + _, original_key_oid_info = key_to_oid_helper.get_db_info() + + # Invalid l3 admit key + # P4RT_TABLE:FIXED_L3_ADMIT_TABLE:{\"match/dst_mac\":\"1\",\"match/in_port\":\"Ethernet8\",\"priority\":2030} + # "action": "admit_to_l3" + # "controller_metadata": "..." + dst_mac_data = "1" + in_port = "Ethernet8" + priority = 2030 + + # Create l3 admit entry. + ( + l3_admit_key, + attr_list, + ) = self._p4rt_l3_admit_obj.create_l3_admit(dst_mac_data, priority, in_port) + util.verify_response( + self.response_consumer, l3_admit_key, attr_list, + "SWSS_RC_INVALID_PARAM", + "[OrchAgent] Failed to deserialize l3 admit key" + ) + + # Verify that P4RT key to OID count not changed in Redis DB + status, fvs = key_to_oid_helper.get_db_info() + assert status == False + assert len(fvs) == len(original_key_oid_info) + + # Query ASIC database for my mac entries. Count remains the same + asic_l3_admit_entries = util.get_keys( + self._p4rt_l3_admit_obj.asic_db, self._p4rt_l3_admit_obj.ASIC_DB_TBL_NAME + ) + assert len(asic_l3_admit_entries) == ( + self._p4rt_l3_admit_obj.get_original_asic_db_entries_count() + ) diff --git a/tests/p4rt/test_p4rt_acl.py b/tests/p4rt/test_p4rt_acl.py index 52989e5b7225..cfa1c0fb454f 100644 --- a/tests/p4rt/test_p4rt_acl.py +++ b/tests/p4rt/test_p4rt_acl.py @@ -24,12 +24,13 @@ def verify_selected_attr_vals(db, table, key, expected_attrs): fv_dict = dict(fvs) for attr_name, expected_val in expected_attrs: - assert attr_name in fv_dict, "Attribute %s not found in %s" % (attr_name, key) + assert attr_name in fv_dict, "Attribute %s not found in %s" % ( + attr_name, key) assert fv_dict[attr_name] == expected_val, "Wrong value %s for the attribute %s = %s" % ( - fv_dict[attr_name], - attr_name, - expected_val, - ) + fv_dict[attr_name], + attr_name, + expected_val, + ) class TestP4RTAcl(object): @@ -63,7 +64,8 @@ def _set_up(self, dvs): self._p4rt_udf_obj.set_up_databases(dvs) self.response_consumer = swsscommon.NotificationConsumer( - self._p4rt_acl_table_definition_obj.appl_state_db, "APPL_DB_P4RT_TABLE_RESPONSE_CHANNEL" + self._p4rt_acl_table_definition_obj.appl_db, "APPL_DB_" + + swsscommon.APP_P4RT_TABLE_NAME + "_RESPONSE_CHANNEL" ) @pytest.mark.skip(reason="p4orch is not enabled") @@ -127,8 +129,8 @@ def test_AclRulesAddUpdateDelPass(self, dvs, testlog): "ASIC_STATE:SAI_OBJECT_TYPE_SWITCH", switch_oid, [("SAI_SWITCH_ATTR_PRE_INGRESS_ACL", pre_ingress_group_oids[0]), - ("SAI_SWITCH_ATTR_INGRESS_ACL",ingress_group_oids[0]), - ("SAI_SWITCH_ATTR_EGRESS_ACL", egress_group_oids[0])], + ("SAI_SWITCH_ATTR_INGRESS_ACL", ingress_group_oids[0]), + ("SAI_SWITCH_ATTR_EGRESS_ACL", egress_group_oids[0])], ) # Verify APP DB trap groups for QOS_QUEUE @@ -161,6 +163,7 @@ def test_AclRulesAddUpdateDelPass(self, dvs, testlog): size = "123" ether_type = '{"kind":"sai_field","sai_field":"SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE","format":"HEX_STRING","bitwidth":8}' ether_dst = '{"kind":"sai_field","sai_field":"SAI_ACL_TABLE_ATTR_FIELD_DST_MAC","format":"MAC","bitwidth":48}' + l3_class_id = '{"kind":"sai_field","sai_field":"SAI_ACL_TABLE_ATTR_FIELD_ROUTE_DST_USER_META","format":"HEX_STRING","bitwidth":6}' is_ip = '{"kind":"sai_field","sai_field":"SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE/IP","format":"HEX_STRING","bitwidth":1}' is_ipv4 = '{"kind":"sai_field","sai_field":"SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE/IPV4ANY","format":"HEX_STRING","bitwidth":1}' is_ipv6 = '{"kind":"sai_field","sai_field":"SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE/IPV6ANY","format":"HEX_STRING","bitwidth":1}' @@ -185,6 +188,7 @@ def test_AclRulesAddUpdateDelPass(self, dvs, testlog): (self._p4rt_acl_table_definition_obj.SIZE_FIELD, size), (self._p4rt_acl_table_definition_obj.MATCH_FIELD_ETHER_DST, ether_dst), (self._p4rt_acl_table_definition_obj.MATCH_FIELD_ETHER_TYPE, ether_type), + (self._p4rt_acl_table_definition_obj.MATCH_FIELD_L3_CLASS_ID, l3_class_id), (self._p4rt_acl_table_definition_obj.MATCH_FIELD_IS_IP, is_ip), (self._p4rt_acl_table_definition_obj.MATCH_FIELD_IS_IPV4, is_ipv4), (self._p4rt_acl_table_definition_obj.MATCH_FIELD_IS_IPV6, is_ipv6), @@ -330,7 +334,8 @@ def test_AclRulesAddUpdateDelPass(self, dvs, testlog): assert len(udfs_asic) == len(original_asic_udfs) + 2 # query ASIC database for newly created UDFs - udfs_asic_db_keys = [key for key in udfs_asic if key not in original_asic_udfs] + udfs_asic_db_keys = [ + key for key in udfs_asic if key not in original_asic_udfs] assert len(udfs_asic_db_keys) == 2 udfs_asic_db_keys.sort() udf_0_asic_db_key = udfs_asic_db_keys[0] @@ -391,6 +396,7 @@ def test_AclRulesAddUpdateDelPass(self, dvs, testlog): ), (self._p4rt_acl_table_definition_obj.SAI_ACL_TABLE_ATTR_SIZE, size), (self._p4rt_acl_table_definition_obj.SAI_ATTR_MATCH_ETHER_TYPE, "true"), + (self._p4rt_acl_table_definition_obj.SAI_ATTR_MATCH_ROUTE_DST_USER_META, "true"), (self._p4rt_acl_table_definition_obj.SAI_ATTR_MATCH_IP_TYPE, "true"), (self._p4rt_acl_table_definition_obj.SAI_ATTR_MATCH_DST_MAC, "true"), (self._p4rt_acl_table_definition_obj.SAI_ATTR_MATCH_SRC_IPV6_WORD3, "true"), @@ -449,7 +455,8 @@ def test_AclRulesAddUpdateDelPass(self, dvs, testlog): (self._p4rt_acl_rule_obj.METER_PBURST, meter_pbs), ] - self._p4rt_acl_rule_obj.set_app_db_entry(table_name_with_rule_key1, attr_list) + self._p4rt_acl_rule_obj.set_app_db_entry( + table_name_with_rule_key1, attr_list) util.verify_response( self.response_consumer, table_name_with_rule_key1, @@ -621,7 +628,8 @@ def test_AclRulesAddUpdateDelPass(self, dvs, testlog): (self._p4rt_acl_rule_obj.METER_PBURST, meter_pbs), ] - self._p4rt_acl_rule_obj.set_app_db_entry(table_name_with_rule_key1, attr_list) + self._p4rt_acl_rule_obj.set_app_db_entry( + table_name_with_rule_key1, attr_list) util.verify_response( self.response_consumer, table_name_with_rule_key1, @@ -794,7 +802,8 @@ def test_AclRulesAddUpdateDelPass(self, dvs, testlog): (self._p4rt_acl_rule_obj.METER_PBURST, meter_pbs), ] - self._p4rt_acl_rule_obj.set_app_db_entry(table_name_with_rule_key2, attr_list) + self._p4rt_acl_rule_obj.set_app_db_entry( + table_name_with_rule_key2, attr_list) util.verify_response( self.response_consumer, table_name_with_rule_key2, @@ -981,6 +990,189 @@ def test_AclRulesAddUpdateDelPass(self, dvs, testlog): ] util.verify_attr(fvs, attr_list) + # create ACL rule 3 with match field SAI_ACL_TABLE_ATTR_FIELD_ROUTE_DST_USER_META + rule_json_key3 = '{"match/ether_type":"0x0800","match/l3_class_id":"0x1", "priority":100}' + action = "copy_and_set_tc" + table_name_with_rule_key3 = table_name + ":" + rule_json_key3 + + attr_list = [ + (self._p4rt_acl_rule_obj.ACTION, action), + ("param/traffic_class", "1"), + ] + + self._p4rt_acl_rule_obj.set_app_db_entry( + table_name_with_rule_key3, attr_list) + util.verify_response( + self.response_consumer, + table_name_with_rule_key3, + attr_list, + "SWSS_RC_SUCCESS", + ) + + # query application database for ACL rules + acl_rules = util.get_keys( + self._p4rt_acl_rule_obj.appl_db, + self._p4rt_acl_rule_obj.APP_DB_TBL_NAME + ":" + table_name, + ) + assert len(acl_rules) == len(original_appl_acl_rules) + 3 + + # query application database for newly created ACL rule + (status, fvs) = util.get_key( + self._p4rt_acl_rule_obj.appl_db, + self._p4rt_acl_table_definition_obj.APP_DB_TBL_NAME, + table_name_with_rule_key3, + ) + assert status == True + util.verify_attr(fvs, attr_list) + + # query application state database for ACL rules + state_acl_rules = util.get_keys( + self._p4rt_acl_rule_obj.appl_state_db, + self._p4rt_acl_rule_obj.APP_DB_TBL_NAME + ":" + table_name, + ) + assert len(state_acl_rules) == len(original_appl_state_acl_rules) + 3 + + # query application state database for newly created ACL rule + (status, fvs) = util.get_key( + self._p4rt_acl_rule_obj.appl_state_db, + self._p4rt_acl_table_definition_obj.APP_DB_TBL_NAME, + table_name_with_rule_key3, + ) + assert status == True + util.verify_attr(fvs, attr_list) + + # query ASIC database for ACL counters + acl_asic_counters = util.get_keys( + self._p4rt_acl_counter_obj.asic_db, + self._p4rt_acl_counter_obj.ASIC_DB_TBL_NAME, + ) + assert len(acl_asic_counters) == len(original_asic_acl_counters) + 3 + + # query ASIC database for newly created ACL counter + counter_asic_db_keys = [ + key for key in acl_asic_counters + if key not in original_asic_acl_counters + and key != counter_asic_db_key1 + and key != counter_asic_db_key2 + ] + assert len(counter_asic_db_keys) == 1 + counter_asic_db_key3 = counter_asic_db_keys[0] + + (status, fvs) = util.get_key( + self._p4rt_acl_counter_obj.asic_db, + self._p4rt_acl_counter_obj.ASIC_DB_TBL_NAME, + counter_asic_db_key3, + ) + assert status == True + attr_list = [ + (self._p4rt_acl_counter_obj.SAI_ATTR_ENABLE_PACKET_COUNT, "true"), + (self._p4rt_acl_counter_obj.SAI_ATTR_ENABLE_BYTE_COUNT, "true"), + (self._p4rt_acl_counter_obj.SAI_ATTR_TABLE_ID, table_asic_db_key), + ] + util.verify_attr(fvs, attr_list) + + # query ASIC database for ACL rules + acl_asic_rules = util.get_keys( + self._p4rt_acl_rule_obj.asic_db, self._p4rt_acl_rule_obj.ASIC_DB_TBL_NAME + ) + assert len(acl_asic_rules) == len(original_asic_acl_rules) + 3 + + # query ASIC database for newly created ACL rule + rule_asic_db_keys = [ + key for key in acl_asic_rules + if key not in original_asic_acl_rules + and key != rule_asic_db_key1 + and key != rule_asic_db_key2 + ] + assert len(rule_asic_db_keys) == 1 + rule_asic_db_key3 = rule_asic_db_keys[0] + + (status, fvs) = util.get_key( + self._p4rt_acl_rule_obj.asic_db, + self._p4rt_acl_rule_obj.ASIC_DB_TBL_NAME, + rule_asic_db_key3, + ) + assert status == True + attr_list = [ + (self._p4rt_acl_rule_obj.SAI_ATTR_ACTION_SET_TC, "1"), + ( + self._p4rt_acl_rule_obj.SAI_ATTR_ACTION_PACKET_ACTION, + "SAI_PACKET_ACTION_COPY", + ), + (self._p4rt_acl_rule_obj.SAI_ATTR_MATCH_ETHER_TYPE, "2048&mask:0xffff"), + ( + self._p4rt_acl_rule_obj.SAI_ATTR_MATCH_ROUTE_DST_USER_META, + "1&mask:0xffffffff", + ), + ( + self._p4rt_acl_rule_obj.SAI_ATTR_MATCH_IP_TYPE, + "SAI_ACL_IP_TYPE_ANY&mask:0xffffffffffffffff", + ), + (self._p4rt_acl_rule_obj.SAI_ATTR_TABLE_ID, table_asic_db_key), + (self._p4rt_acl_rule_obj.SAI_ATTR_COUNTER, counter_asic_db_key3), + (self._p4rt_acl_rule_obj.SAI_ATTR_ADMIN_STATE, "true"), + (self._p4rt_acl_rule_obj.SAI_ATTR_PRIORITY, "100"), + ] + util.verify_attr(fvs, attr_list) + + # remove ACL rule 3 + self._p4rt_acl_rule_obj.remove_app_db_entry(table_name_with_rule_key3) + util.verify_response( + self.response_consumer, table_name_with_rule_key3, [], "SWSS_RC_SUCCESS" + ) + + # query application database for ACL rules + acl_rules = util.get_keys( + self._p4rt_acl_rule_obj.appl_db, + self._p4rt_acl_rule_obj.APP_DB_TBL_NAME + ":" + table_name, + ) + assert len(acl_rules) == len(original_appl_acl_rules) + 2 + + # verify that the ACL rule no longer exists in application database + (status, fvs) = util.get_key( + self._p4rt_acl_rule_obj.appl_db, + self._p4rt_acl_rule_obj.APP_DB_TBL_NAME, + table_name_with_rule_key3, + ) + assert status == False + + # query application state database for ACL rules + state_acl_rules = util.get_keys( + self._p4rt_acl_rule_obj.appl_state_db, + self._p4rt_acl_rule_obj.APP_DB_TBL_NAME + ":" + table_name, + ) + assert len(state_acl_rules) == len(original_appl_state_acl_rules) + 2 + + # verify that the ACL rule no longer exists in application state database + (status, fvs) = util.get_key( + self._p4rt_acl_rule_obj.appl_state_db, + self._p4rt_acl_rule_obj.APP_DB_TBL_NAME, + table_name_with_rule_key3, + ) + assert status == False + + # query ASIC database for ACL rules + acl_rules = util.get_keys( + self._p4rt_acl_rule_obj.asic_db, self._p4rt_acl_rule_obj.ASIC_DB_TBL_NAME + ) + assert len(acl_rules) == len(original_asic_acl_rules) + 2 + + # verify that removed ACL rule no longer exists in ASIC database + (status, fvs) = util.get_key( + self._p4rt_acl_rule_obj.asic_db, + self._p4rt_acl_rule_obj.ASIC_DB_TBL_NAME, + rule_asic_db_key3, + ) + assert status == False + + # verify that removed ACL counter no longer exists in ASIC database + (status, fvs) = util.get_key( + self._p4rt_acl_counter_obj.asic_db, + self._p4rt_acl_counter_obj.ASIC_DB_TBL_NAME, + counter_asic_db_key3, + ) + assert status == False + # remove ACL rule 1 self._p4rt_acl_rule_obj.remove_app_db_entry(table_name_with_rule_key1) util.verify_response( @@ -1210,7 +1402,8 @@ def test_AclRuleAddWithoutTableDefinitionFails(self, dvs, testlog): (self._p4rt_acl_rule_obj.METER_PBURST, meter_pbs), ] - self._p4rt_acl_rule_obj.set_app_db_entry(table_name_with_rule_key, attr_list) + self._p4rt_acl_rule_obj.set_app_db_entry( + table_name_with_rule_key, attr_list) util.verify_response( self.response_consumer, table_name_with_rule_key, diff --git a/tests/p4rt/test_p4rt_mirror.py b/tests/p4rt/test_p4rt_mirror.py index bc218df14742..c1327370c30a 100644 --- a/tests/p4rt/test_p4rt_mirror.py +++ b/tests/p4rt/test_p4rt_mirror.py @@ -1,48 +1,51 @@ from swsscommon import swsscommon +import pytest import util import json + class P4RtMirrorSessionWrapper(util.DBInterface): - """Interface to interact with APP DB and ASIC DB tables for P4RT mirror session object.""" - - # database and SAI constants - APP_DB_TBL_NAME = swsscommon.APP_P4RT_TABLE_NAME - TBL_NAME = swsscommon.APP_P4RT_MIRROR_SESSION_TABLE_NAME - ACTION = "action" - PORT = "port" - SRC_IP = "src_ip" - DST_IP = "dst_ip" - SRC_MAC = "src_mac" - DST_MAC = "dst_mac" - TTL = "ttl" - TOS = "tos" - - ASIC_DB_TBL_NAME = "ASIC_STATE:SAI_OBJECT_TYPE_MIRROR_SESSION" - SAI_MIRROR_SESSION_ATTR_MONITOR_PORT = "SAI_MIRROR_SESSION_ATTR_MONITOR_PORT" - SAI_MIRROR_SESSION_ATTR_TYPE = "SAI_MIRROR_SESSION_ATTR_TYPE" - SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE = "SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE" - SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION = "SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION" - SAI_MIRROR_SESSION_ATTR_TOS = "SAI_MIRROR_SESSION_ATTR_TOS" - SAI_MIRROR_SESSION_ATTR_TTL = "SAI_MIRROR_SESSION_ATTR_TTL" - SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS = "SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS" - SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS = "SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS" - SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS = "SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS" - SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS = "SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS" - SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE = "SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE" - - def generate_app_db_key(self, mirror_session_id): - d = {} - d[util.prepend_match_field("mirror_session_id")] = mirror_session_id - key = json.dumps(d, separators=(",", ":")) - return self.TBL_NAME + ":" + key + """Interface to interact with APP DB and ASIC DB tables for P4RT mirror session object.""" + + # database and SAI constants + APP_DB_TBL_NAME = swsscommon.APP_P4RT_TABLE_NAME + TBL_NAME = swsscommon.APP_P4RT_MIRROR_SESSION_TABLE_NAME + ACTION = "action" + PORT = "port" + SRC_IP = "src_ip" + DST_IP = "dst_ip" + SRC_MAC = "src_mac" + DST_MAC = "dst_mac" + TTL = "ttl" + TOS = "tos" + + ASIC_DB_TBL_NAME = "ASIC_STATE:SAI_OBJECT_TYPE_MIRROR_SESSION" + SAI_MIRROR_SESSION_ATTR_MONITOR_PORT = "SAI_MIRROR_SESSION_ATTR_MONITOR_PORT" + SAI_MIRROR_SESSION_ATTR_TYPE = "SAI_MIRROR_SESSION_ATTR_TYPE" + SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE = "SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE" + SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION = "SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION" + SAI_MIRROR_SESSION_ATTR_TOS = "SAI_MIRROR_SESSION_ATTR_TOS" + SAI_MIRROR_SESSION_ATTR_TTL = "SAI_MIRROR_SESSION_ATTR_TTL" + SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS = "SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS" + SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS = "SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS" + SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS = "SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS" + SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS = "SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS" + SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE = "SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE" + + def generate_app_db_key(self, mirror_session_id): + d = {} + d[util.prepend_match_field("mirror_session_id")] = mirror_session_id + key = json.dumps(d, separators=(",", ":")) + return self.TBL_NAME + ":" + key + class TestP4RTMirror(object): def _set_up(self, dvs): self._p4rt_mirror_session_wrapper = P4RtMirrorSessionWrapper() self._p4rt_mirror_session_wrapper.set_up_databases(dvs) self._response_consumer = swsscommon.NotificationConsumer( - self._p4rt_mirror_session_wrapper.appl_state_db, "APPL_DB_P4RT_TABLE_RESPONSE_CHANNEL") + self._p4rt_mirror_session_wrapper.appl_db, "APPL_DB_" + swsscommon.APP_P4RT_TABLE_NAME + "_RESPONSE_CHANNEL") def test_MirrorSessionAddModifyAndDelete(self, dvs, testlog): # Initialize database connectors @@ -71,13 +74,19 @@ def test_MirrorSessionAddModifyAndDelete(self, dvs, testlog): tos = "0x00" attr_list_in_app_db = [(self._p4rt_mirror_session_wrapper.ACTION, action), - (util.prepend_param_field(self._p4rt_mirror_session_wrapper.PORT), port), - (util.prepend_param_field(self._p4rt_mirror_session_wrapper.SRC_IP), src_ip), - (util.prepend_param_field(self._p4rt_mirror_session_wrapper.DST_IP), dst_ip), - (util.prepend_param_field(self._p4rt_mirror_session_wrapper.SRC_MAC), src_mac), - (util.prepend_param_field(self._p4rt_mirror_session_wrapper.DST_MAC), dst_mac), - (util.prepend_param_field(self._p4rt_mirror_session_wrapper.TTL), ttl), - (util.prepend_param_field(self._p4rt_mirror_session_wrapper.TOS), tos)] + (util.prepend_param_field( + self._p4rt_mirror_session_wrapper.PORT), port), + (util.prepend_param_field( + self._p4rt_mirror_session_wrapper.SRC_IP), src_ip), + (util.prepend_param_field( + self._p4rt_mirror_session_wrapper.DST_IP), dst_ip), + (util.prepend_param_field( + self._p4rt_mirror_session_wrapper.SRC_MAC), src_mac), + (util.prepend_param_field( + self._p4rt_mirror_session_wrapper.DST_MAC), dst_mac), + (util.prepend_param_field( + self._p4rt_mirror_session_wrapper.TTL), ttl), + (util.prepend_param_field(self._p4rt_mirror_session_wrapper.TOS), tos)] mirror_session_key = self._p4rt_mirror_session_wrapper.generate_app_db_key( mirror_session_id) self._p4rt_mirror_session_wrapper.set_app_db_entry( @@ -89,7 +98,8 @@ def test_MirrorSessionAddModifyAndDelete(self, dvs, testlog): appl_mirror_entries = util.get_keys( self._p4rt_mirror_session_wrapper.appl_db, self._p4rt_mirror_session_wrapper.APP_DB_TBL_NAME + ":" + self._p4rt_mirror_session_wrapper.TBL_NAME) - assert len(appl_mirror_entries) == len(original_appl_mirror_entries) + 1 + assert len(appl_mirror_entries) == len( + original_appl_mirror_entries) + 1 # Query application database for newly created mirror key (status, fvs) = util.get_key(self._p4rt_mirror_session_wrapper.appl_db, @@ -102,7 +112,8 @@ def test_MirrorSessionAddModifyAndDelete(self, dvs, testlog): appl_state_mirror_entries = util.get_keys( self._p4rt_mirror_session_wrapper.appl_state_db, self._p4rt_mirror_session_wrapper.APP_DB_TBL_NAME + ":" + self._p4rt_mirror_session_wrapper.TBL_NAME) - assert len(appl_state_mirror_entries) == len(original_appl_state_mirror_entries) + 1 + assert len(appl_state_mirror_entries) == len( + original_appl_state_mirror_entries) + 1 # Query application state database for newly created mirror key (status, fvs) = util.get_key(self._p4rt_mirror_session_wrapper.appl_state_db, @@ -113,8 +124,9 @@ def test_MirrorSessionAddModifyAndDelete(self, dvs, testlog): # Query ASIC database for mirror entries asic_mirror_entries = util.get_keys(self._p4rt_mirror_session_wrapper.asic_db, - self._p4rt_mirror_session_wrapper.ASIC_DB_TBL_NAME) - assert len(asic_mirror_entries) == len(original_asic_mirror_entries) + 1 + self._p4rt_mirror_session_wrapper.ASIC_DB_TBL_NAME) + assert len(asic_mirror_entries) == len( + original_asic_mirror_entries) + 1 # Query ASIC database for newly created mirror key asic_db_key = None @@ -134,23 +146,28 @@ def test_MirrorSessionAddModifyAndDelete(self, dvs, testlog): assert port_oid != None expected_attr_list_in_asic_db = [ - (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_MONITOR_PORT, port_oid), - (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_TYPE, "SAI_MIRROR_SESSION_TYPE_ENHANCED_REMOTE"), - (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE, "SAI_ERSPAN_ENCAPSULATION_TYPE_MIRROR_L3_GRE_TUNNEL"), - (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION, "4"), # MIRROR_SESSION_DEFAULT_IP_HDR_VER - (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_TOS, "0"), - (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_TTL, "64"), - (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS, src_ip), - (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS, dst_ip), - (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS, src_mac), - (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS, dst_mac), - (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE, "35006") # GRE_PROTOCOL_ERSPAN 0x88be + (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_MONITOR_PORT, port_oid), + (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_TYPE, + "SAI_MIRROR_SESSION_TYPE_ENHANCED_REMOTE"), + (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE, + "SAI_ERSPAN_ENCAPSULATION_TYPE_MIRROR_L3_GRE_TUNNEL"), + # MIRROR_SESSION_DEFAULT_IP_HDR_VER + (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION, "4"), + (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_TOS, "0"), + (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_TTL, "64"), + (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS, src_ip), + (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS, dst_ip), + (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS, src_mac), + (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS, dst_mac), + # GRE_PROTOCOL_ERSPAN 0x88be + (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE, "35006") ] util.verify_attr(fvs, expected_attr_list_in_asic_db) # 2. Modify the existing mirror session. new_dst_mac = "00:1A:11:17:5F:FF" - attr_list_in_app_db[5] = (util.prepend_param_field(self._p4rt_mirror_session_wrapper.DST_MAC), new_dst_mac) + attr_list_in_app_db[5] = (util.prepend_param_field( + self._p4rt_mirror_session_wrapper.DST_MAC), new_dst_mac) self._p4rt_mirror_session_wrapper.set_app_db_entry( mirror_session_key, attr_list_in_app_db) util.verify_response( @@ -171,7 +188,8 @@ def test_MirrorSessionAddModifyAndDelete(self, dvs, testlog): util.verify_attr(fvs, attr_list_in_app_db) # Query ASIC DB about the modified mirror session. - expected_attr_list_in_asic_db[9] = (self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS, new_dst_mac) + expected_attr_list_in_asic_db[9] = ( + self._p4rt_mirror_session_wrapper.SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS, new_dst_mac) (status, fvs) = util.get_key(self._p4rt_mirror_session_wrapper.asic_db, self._p4rt_mirror_session_wrapper.ASIC_DB_TBL_NAME, asic_db_key) @@ -200,7 +218,8 @@ def test_MirrorSessionAddModifyAndDelete(self, dvs, testlog): appl_state_mirror_entries = util.get_keys( self._p4rt_mirror_session_wrapper.appl_state_db, self._p4rt_mirror_session_wrapper.APP_DB_TBL_NAME + ":" + self._p4rt_mirror_session_wrapper.TBL_NAME) - assert len(appl_state_mirror_entries) == len(original_appl_state_mirror_entries) + assert len(appl_state_mirror_entries) == len( + original_appl_state_mirror_entries) # Query application state database for the deleted mirror key (status, fvs) = util.get_key(self._p4rt_mirror_session_wrapper.appl_state_db, @@ -210,7 +229,7 @@ def test_MirrorSessionAddModifyAndDelete(self, dvs, testlog): # Query ASIC database for mirror entries asic_mirror_entries = util.get_keys(self._p4rt_mirror_session_wrapper.asic_db, - self._p4rt_mirror_session_wrapper.ASIC_DB_TBL_NAME) + self._p4rt_mirror_session_wrapper.ASIC_DB_TBL_NAME) assert len(asic_mirror_entries) == len(original_asic_mirror_entries) # Query ASIC state database for the deleted mirror key diff --git a/tests/p4rt/util.py b/tests/p4rt/util.py index 778a54960d25..ac46a48587aa 100644 --- a/tests/p4rt/util.py +++ b/tests/p4rt/util.py @@ -54,8 +54,10 @@ def verify_response(consumer, key, attr_list, status, err_message = "SWSS_RC_SUC assert data == key assert op == status assert len(values) >= 1 - assert values[0][0] == "err_str" - assert values[0][1] == err_message + assert values[0][0] == "err_str", "Unexpected status '%s' received, expected '%s'" % \ + (values[0][0], "err_str") + assert values[0][1] == err_message, "Unexpected message '%s' received, expected '%s'" % \ + (values[0][1], err_message) values = values[1:] verify_attr(values, attr_list)