diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 5755b1446e..d39e73d737 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 3984fd956f..fb73cb0128 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 cc00735d84..34cb8361c0 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 312f54c51c..4d8e4ca363 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) @@ -53,6 +96,116 @@ 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 +414,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 +423,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 +552,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); - - 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); + auto attrs = getUdfGroupSaiAttrs(udf_field); - 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 +600,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 +711,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 +755,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 +900,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 6243c08cb4..f48d34c309 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 6caf67cade..92905ec622 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 c06849506b..74de14d2a5 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 0000000000..84f48a57b9 --- /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 0000000000..deb5b319e3 --- /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 0000000000..75d4d6f7d2 --- /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 0000000000..933f5792c8 --- /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 067bc5aa1a..dfecb74ad7 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 c41dc07eb3..3cbd46ee15 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 059aa76698..9a903baa6a 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 2ede9de763..4165bb90ed 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 3e2d9ff548..2a9bbcf8f9 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 7b4a318f87..eda1ac0001 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 ec9775f8e4..17b6e9ec84 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 f4ff6e3433..63215846a6 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 6f7b86ab8f..325acf9503 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 57d50aa5ce..717f23bc93 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 42159f3981..e39041802b 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 e5d4479436..5ff0c058d4 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 a3684a5fb8..995ccf9b27 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 7732a143e5..e029489fde 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 6e494709b3..0a50c294bd 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 ea9abf083a..43dc652eb6 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 a300b2a7a4..73d994ff06 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 daeb484136..97f91e3285 100644 --- a/orchagent/p4orch/tests/Makefile.am +++ b/orchagent/p4orch/tests/Makefile.am @@ -1,3 +1,5 @@ +AUTOMAKE_OPTIONS = subdir-objects + ORCHAGENT_DIR = $(top_srcdir)/orchagent P4ORCH_DIR = $(ORCHAGENT_DIR)/p4orch INCLUDES = -I $(top_srcdir) -I $(ORCHAGENT_DIR) -I $(P4ORCH_DIR) -I $(top_srcdir)/lib -I $(ORCHAGENT_DIR)/flex_counter @@ -34,6 +36,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 +45,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 +61,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 +73,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 55abf24606..b480b6bf9c 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,10 @@ 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_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 +1098,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 +1120,11 @@ 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_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 +1196,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 +1304,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 +1322,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 +1343,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 +1365,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 +1387,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 +1397,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 +1424,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 +1439,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 +1455,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 +1811,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 +1962,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 +1999,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 +2021,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 +2032,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 +2052,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 +2060,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 +2068,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 +2102,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 +2129,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 +2167,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 +2188,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 +2202,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 +2226,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 +2236,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 +2304,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 +2349,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 +2361,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 +2603,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 +2710,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 +2790,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 +3502,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 +3923,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 +3998,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 +4026,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 +4089,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 +4208,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 +4259,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 +4277,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 +4331,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 +4347,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 +4413,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 +4430,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 +4453,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 +4471,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 +4557,725 @@ 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_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_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 1709d9d977..89487fad61 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 0000000000..ebd0b54ce4 --- /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 0000000000..6d0d67dd0e --- /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 c45a0d9bcd..bc5563a078 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 0000000000..e82f38f520 --- /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 0000000000..8fbc4af16d --- /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 9c0caa3004..5d7b7e1172 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 0000000000..5a2a165b02 --- /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 e3986ef701..ae91f4f567 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 a78310cc8d..72bfb53233 100644 --- a/orchagent/p4orch/tests/next_hop_manager_test.cpp +++ b/orchagent/p4orch/tests/next_hop_manager_test.cpp @@ -10,10 +10,13 @@ #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 "mock_sai_udf.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 +35,109 @@ 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_udf_api_t *sai_udf_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 +156,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 +201,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 +223,28 @@ class NextHopManagerTest : public ::testing::Test protected: NextHopManagerTest() : next_hop_manager_(&p4_oid_mapper_, &publisher_) { + mock_sai_udf = &mock_sai_udf_; + mock_sai_hostif = &mock_sai_hostif_; + mock_sai_switch = &mock_sai_switch_; + sai_udf_api->create_udf_match = create_udf_match; + sai_udf_api->remove_udf_match = remove_udf_match; + 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_udf_, create_udf_match(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + 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() + { + EXPECT_CALL(mock_sai_udf_, remove_udf_match(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + delete gP4Orch; + delete copp_orch_; } void SetUp() override @@ -170,6 +257,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 +272,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 +304,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 +332,40 @@ class NextHopManagerTest : public ::testing::Test MockResponsePublisher publisher_; P4OidMapper p4_oid_mapper_; NextHopManager next_hop_manager_; + StrictMock mock_sai_hostif_; + StrictMock mock_sai_switch_; + StrictMock mock_sai_udf_; + 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 +398,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 +458,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 +473,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 +529,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 +616,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 +705,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 +713,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 +725,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 +741,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 +751,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 +763,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 +781,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 +857,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 +983,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 131ee7aedb..bde2ee656b 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 8c171b1e6e..ba86624d4c 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 7a866827d7..7ab21121aa 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 de1238761b..06095d5ebe 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 661fe33efa..9c81310e5f 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 b2cc68aaaa..1c4981b665 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 6078f92221..96c0a12eb3 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 4c6629a398..d1a6e025bc 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 87a1a761e1..ed154784b7 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 172b74b559..6ab8570676 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 48ecd1fd35..31134dd093 100644 --- a/orchagent/switchorch.cpp +++ b/orchagent/switchorch.cpp @@ -71,16 +71,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) + ": "); @@ -89,12 +87,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(); @@ -115,8 +113,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) @@ -124,12 +123,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(); @@ -137,17 +136,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 5b09a67640..232bcfb1cc 100644 --- a/orchagent/switchorch.h +++ b/orchagent/switchorch.h @@ -34,7 +34,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(); @@ -54,17 +54,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/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index dedd4445f7..3546e6f934 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 362e1446d3..7cabdc2224 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 283ba95ce6..6623bb3fcf 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 c5f656aa2e..31cd0b3b95 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 0000000000..18fcc88482 --- /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 bbe7d07653..e88a24dd30 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 == True + 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 == True + 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 == True + 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 0000000000..8b138dbd23 --- /dev/null +++ b/tests/p4rt/test_l3_admit.py @@ -0,0 +1,262 @@ +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" + ) + + 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 == True + 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 == True + 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 52989e5b72..cfa1c0fb45 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 bc218df147..db7109ccd1 100644 --- a/tests/p4rt/test_p4rt_mirror.py +++ b/tests/p4rt/test_p4rt_mirror.py @@ -3,46 +3,48 @@ 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 +73,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 +97,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 +111,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 +123,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 +145,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 +187,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 +217,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 +228,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 778a54960d..ac46a48587 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)