diff --git a/orchagent/p4orch/acl_rule_manager.cpp b/orchagent/p4orch/acl_rule_manager.cpp new file mode 100644 index 000000000000..3984fd956f7c --- /dev/null +++ b/orchagent/p4orch/acl_rule_manager.cpp @@ -0,0 +1,2009 @@ +#include "p4orch/acl_rule_manager.h" + +#include +#include +#include + +#include "converter.h" +#include "crmorch.h" +#include "json.hpp" +#include "logger.h" +#include "orch.h" +#include "p4orch.h" +#include "p4orch/p4orch_util.h" +#include "portsorch.h" +#include "sai_serialize.h" +#include "tokenize.h" +extern "C" +{ +#include "sai.h" +} + +extern sai_object_id_t gSwitchId; +extern sai_acl_api_t *sai_acl_api; +extern sai_policer_api_t *sai_policer_api; +extern sai_hostif_api_t *sai_hostif_api; +extern CrmOrch *gCrmOrch; +extern PortsOrch *gPortsOrch; +extern P4Orch *gP4Orch; + +namespace p4orch +{ +namespace +{ + +const std::string concatTableNameAndRuleKey(const std::string &table_name, const std::string &rule_key) +{ + return table_name + kTableKeyDelimiter + rule_key; +} + +} // namespace + +void AclRuleManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void AclRuleManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string db_key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &db_key); + const auto &op = kfvOp(key_op_fvs_tuple); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + SWSS_LOG_NOTICE("OP: %s, RULE_KEY: %s", op.c_str(), QuotedVar(db_key).c_str()); + + ReturnCode status; + auto app_db_entry_or = deserializeAclRuleAppDbEntry(table_name, db_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 + ":" + db_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; + + status = validateAclRuleAppDbEntry(app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for ACL rule APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + db_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; + } + + 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)); + + const auto &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *acl_rule = getAclRule(acl_table_name, acl_rule_key); + if (acl_rule == nullptr) + { + status = processAddRuleRequest(acl_rule_key, app_db_entry); + } + else + { + status = processUpdateRuleRequest(app_db_entry, *acl_rule); + } + } + else if (operation == DEL_COMMAND) + { + status = processDeleteRuleRequest(acl_table_name, acl_rule_key); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << 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(); +} + +ReturnCode AclRuleManager::setUpUserDefinedTraps() +{ + SWSS_LOG_ENTER(); + + const auto trapGroupMap = m_coppOrch->getTrapGroupMap(); + const auto trapGroupHostIfMap = m_coppOrch->getTrapGroupHostIfMap(); + for (int queue_num = 1; queue_num <= P4_CPU_QUEUE_MAX_NUM; queue_num++) + { + auto trap_group_it = trapGroupMap.find(GENL_PACKET_TRAP_GROUP_NAME_PREFIX + std::to_string(queue_num)); + if (trap_group_it == trapGroupMap.end()) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Trap group was not found given trap group name: " + << GENL_PACKET_TRAP_GROUP_NAME_PREFIX << queue_num); + } + const sai_object_id_t trap_group_oid = trap_group_it->second; + auto hostif_oid_it = trapGroupHostIfMap.find(trap_group_oid); + if (hostif_oid_it == trapGroupHostIfMap.end()) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Hostif object id was not found given trap group - " << trap_group_it->first); + } + // Create user defined trap + std::vector trap_attrs; + sai_attribute_t attr; + attr.id = SAI_HOSTIF_USER_DEFINED_TRAP_ATTR_TRAP_GROUP; + attr.value.oid = trap_group_oid; + trap_attrs.push_back(attr); + attr.id = SAI_HOSTIF_USER_DEFINED_TRAP_ATTR_TYPE; + attr.value.s32 = SAI_HOSTIF_USER_DEFINED_TRAP_TYPE_ACL; + trap_attrs.push_back(attr); + P4UserDefinedTrapHostifTableEntry udt_hostif; + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_hostif_api->create_hostif_user_defined_trap(&udt_hostif.user_defined_trap, gSwitchId, + (uint32_t)trap_attrs.size(), trap_attrs.data()), + "Failed to create trap by calling " + "sai_hostif_api->create_hostif_user_defined_trap"); + std::vector sai_host_table_attr; + + attr.id = SAI_HOSTIF_TABLE_ENTRY_ATTR_TYPE; + attr.value.s32 = SAI_HOSTIF_TABLE_ENTRY_TYPE_TRAP_ID; + sai_host_table_attr.push_back(attr); + + attr.id = SAI_HOSTIF_TABLE_ENTRY_ATTR_TRAP_ID; + attr.value.oid = udt_hostif.user_defined_trap; + sai_host_table_attr.push_back(attr); + + attr.id = SAI_HOSTIF_TABLE_ENTRY_ATTR_CHANNEL_TYPE; + attr.value.s32 = SAI_HOSTIF_TABLE_ENTRY_CHANNEL_TYPE_GENETLINK; + sai_host_table_attr.push_back(attr); + + attr.id = SAI_HOSTIF_TABLE_ENTRY_ATTR_HOST_IF; + attr.value.oid = hostif_oid_it->second; + sai_host_table_attr.push_back(attr); + + auto sai_status = + sai_hostif_api->create_hostif_table_entry(&udt_hostif.hostif_table_entry, gSwitchId, + (uint32_t)sai_host_table_attr.size(), sai_host_table_attr.data()); + if (sai_status != SAI_STATUS_SUCCESS) + { + ReturnCode return_code = ReturnCode(sai_status) << "Failed to create hostif table entry by calling " + "sai_hostif_api->remove_hostif_user_defined_trap"; + sai_hostif_api->remove_hostif_user_defined_trap(udt_hostif.user_defined_trap); + SWSS_LOG_ERROR("%s SAI_STATUS: %s", return_code.message().c_str(), + sai_serialize_status(sai_status).c_str()); + return return_code; + } + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, std::to_string(queue_num), + udt_hostif.user_defined_trap, /*ref_count=*/1); + m_userDefinedTraps.push_back(udt_hostif); + SWSS_LOG_NOTICE("Created user defined trap for QUEUE number %d: %s", queue_num, + sai_serialize_object_id(udt_hostif.user_defined_trap).c_str()); + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::cleanUpUserDefinedTraps() +{ + SWSS_LOG_ENTER(); + + for (size_t queue_num = 1; queue_num <= m_userDefinedTraps.size(); queue_num++) + { + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_hostif_api->remove_hostif_table_entry(m_userDefinedTraps[queue_num - 1].hostif_table_entry), + "Failed to create hostif table entry."); + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, std::to_string(queue_num)); + sai_hostif_api->remove_hostif_user_defined_trap(m_userDefinedTraps[queue_num - 1].user_defined_trap); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, std::to_string(queue_num)); + } + m_userDefinedTraps.clear(); + return ReturnCode(); +} + +void AclRuleManager::doAclCounterStatsTask() +{ + SWSS_LOG_ENTER(); + + for (const auto &table_it : m_aclRuleTables) + { + const auto &table_name = fvField(table_it); + for (const auto &rule_it : fvValue(table_it)) + { + if (!fvValue(rule_it).counter.packets_enabled && !fvValue(rule_it).counter.bytes_enabled) + continue; + auto status = setAclRuleCounterStats(fvValue(rule_it)); + if (!status.ok()) + { + status.prepend("Failed to set counters stats for ACL rule " + QuotedVar(table_name) + ":" + + QuotedVar(fvField(rule_it)) + " in COUNTERS_DB: "); + SWSS_LOG_ERROR("%s", status.message().c_str()); + continue; + } + } + } +} + +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) +{ + 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); + } + + 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)); + 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); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name); + return ReturnCode(); +} + +ReturnCode AclRuleManager::removeAclCounter(const std::string &acl_table_name, const std::string &counter_key) +{ + SWSS_LOG_ENTER(); + sai_object_id_t counter_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ACL_COUNTER, counter_key, &counter_oid)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to remove ACL counter by key " << QuotedVar(counter_key) + << ": invalid counter key."); + } + sai_object_id_t table_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name, &table_oid)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to remove ACL counter " + << sai_serialize_object_id(counter_oid) << " in table " + << QuotedVar(acl_table_name) << ": invalid table key."); + } + CHECK_ERROR_AND_LOG_AND_RETURN(sai_acl_api->remove_acl_counter(counter_oid), + "Failed to remove ACL counter " << sai_serialize_object_id(counter_oid) + << " in table " << QuotedVar(acl_table_name)); + + gCrmOrch->decCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, table_oid); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ACL_COUNTER, counter_key); + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name); + SWSS_LOG_NOTICE("Removing record about the counter %s from the DB", sai_serialize_object_id(counter_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclRuleManager::createAclMeter(const P4AclMeter &p4_acl_meter, const std::string &meter_key, + sai_object_id_t *meter_oid) +{ + 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); + } + + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_policer_api->create_policer(meter_oid, gSwitchId, (uint32_t)meter_attrs.size(), meter_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()); + return ReturnCode(); +} + +ReturnCode AclRuleManager::updateAclMeter(const P4AclMeter &new_acl_meter, const P4AclMeter &old_acl_meter) +{ + SWSS_LOG_ENTER(); + + std::vector meter_attrs; + std::vector rollback_attrs; + sai_attribute_t meter_attr; + + if (old_acl_meter.cburst != new_acl_meter.cburst) + { + meter_attr.id = SAI_POLICER_ATTR_CBS; + meter_attr.value.u64 = new_acl_meter.cburst; + meter_attrs.push_back(meter_attr); + meter_attr.value.u64 = old_acl_meter.cburst; + rollback_attrs.push_back(meter_attr); + } + if (old_acl_meter.cir != new_acl_meter.cir) + { + meter_attr.id = SAI_POLICER_ATTR_CIR; + meter_attr.value.u64 = new_acl_meter.cir; + meter_attrs.push_back(meter_attr); + meter_attr.value.u64 = old_acl_meter.cir; + rollback_attrs.push_back(meter_attr); + } + if (old_acl_meter.pir != new_acl_meter.pir) + { + meter_attr.id = SAI_POLICER_ATTR_PIR; + meter_attr.value.u64 = new_acl_meter.pir; + meter_attrs.push_back(meter_attr); + meter_attr.value.u64 = old_acl_meter.pir; + rollback_attrs.push_back(meter_attr); + } + if (old_acl_meter.pburst != new_acl_meter.pburst) + { + meter_attr.id = SAI_POLICER_ATTR_PBS; + meter_attr.value.u64 = new_acl_meter.pburst; + meter_attrs.push_back(meter_attr); + meter_attr.value.u64 = old_acl_meter.pburst; + rollback_attrs.push_back(meter_attr); + } + + std::set colors_to_reset; + for (const auto &old_color_action : old_acl_meter.packet_color_actions) + { + colors_to_reset.insert(fvField(old_color_action)); + } + + for (const auto &packet_color_action : new_acl_meter.packet_color_actions) + { + const auto &it = old_acl_meter.packet_color_actions.find(fvField(packet_color_action)); + if (it == old_acl_meter.packet_color_actions.end() || it->second != fvValue(packet_color_action)) + { + meter_attr.id = fvField(packet_color_action); + meter_attr.value.s32 = fvValue(packet_color_action); + meter_attrs.push_back(meter_attr); + meter_attr.value.s32 = + (it == old_acl_meter.packet_color_actions.end()) ? SAI_PACKET_ACTION_FORWARD : it->second; + rollback_attrs.push_back(meter_attr); + } + if (it != old_acl_meter.packet_color_actions.end()) + { + colors_to_reset.erase(fvField(packet_color_action)); + } + } + + for (const auto &packet_color : colors_to_reset) + { + meter_attr.id = packet_color; + meter_attr.value.s32 = SAI_PACKET_ACTION_FORWARD; + meter_attrs.push_back(meter_attr); + const auto &it = old_acl_meter.packet_color_actions.find(packet_color); + meter_attr.value.s32 = it->second; + rollback_attrs.push_back(meter_attr); + } + + ReturnCode status; + int i; + for (i = 0; i < static_cast(meter_attrs.size()); ++i) + { + status = ReturnCode(sai_policer_api->set_policer_attribute(old_acl_meter.meter_oid, &meter_attrs[i])); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to update ACL meter attributes: %s", status.message().c_str()); + break; + } + } + if (!status.ok()) + { + for (--i; i >= 0; --i) + { + auto sai_status = sai_policer_api->set_policer_attribute(old_acl_meter.meter_oid, &rollback_attrs[i]); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set ACL policer attribute. SAI_STATUS: %s", + sai_serialize_status(sai_status).c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to set ACL policer attribute in recovery."); + } + } + return status; + } + SWSS_LOG_NOTICE("Suceeded to update ACL meter %s ", sai_serialize_object_id(old_acl_meter.meter_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclRuleManager::removeAclMeter(const std::string &meter_key) +{ + SWSS_LOG_ENTER(); + sai_object_id_t meter_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_POLICER, meter_key, &meter_oid)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get ACL meter object id for ACL rule " + << QuotedVar(meter_key)); + } + CHECK_ERROR_AND_LOG_AND_RETURN(sai_policer_api->remove_policer(meter_oid), + "Failed to remove ACL meter for ACL rule " << QuotedVar(meter_key)); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_POLICER, meter_key); + SWSS_LOG_NOTICE("Suceeded to remove ACL meter %s: %s ", QuotedVar(meter_key).c_str(), + sai_serialize_object_id(meter_oid).c_str()); + return ReturnCode(); +} + +ReturnCodeOr AclRuleManager::deserializeAclRuleAppDbEntry( + const std::string &acl_table_name, const std::string &key, const std::vector &attributes) +{ + sai_object_id_t table_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name, &table_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "ACL table " << QuotedVar(acl_table_name) << " is not found"; + } + P4AclRuleAppDbEntry app_db_entry = {}; + app_db_entry.acl_table_name = acl_table_name; + app_db_entry.db_key = concatTableNameAndRuleKey(acl_table_name, key); + // Parse rule key : match fields and priority + try + { + const auto &rule_key_json = nlohmann::json::parse(key); + if (!rule_key_json.is_object()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid ACL rule key: should be a JSON object."; + } + for (auto rule_key_it = rule_key_json.begin(); rule_key_it != rule_key_json.end(); ++rule_key_it) + { + if (rule_key_it.key() == kPriority) + { + if (!rule_key_it.value().is_number_unsigned()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL rule priority type: should be uint32_t"; + } + app_db_entry.priority = rule_key_it.value(); + continue; + } + else + { + const auto &tokenized_match_field = tokenize(rule_key_it.key(), kFieldDelimiter); + if (tokenized_match_field.size() <= 1 || tokenized_match_field[0] != kMatchPrefix) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unknown ACL match field string " << QuotedVar(rule_key_it.key()); + } + app_db_entry.match_fvs[tokenized_match_field[1]] = rule_key_it.value(); + } + } + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize ACL rule match key"; + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == kControllerMetadata) + continue; + if (field == kAction) + { + app_db_entry.action = value; + continue; + } + const auto &tokenized_field = tokenize(field, kFieldDelimiter); + if (tokenized_field.size() <= 1) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown ACL rule field " << QuotedVar(field); + } + const auto &prefix = tokenized_field[0]; + if (prefix == kActionParamPrefix) + { + const auto ¶m_name = tokenized_field[1]; + app_db_entry.action_param_fvs[param_name] = value; + } + else if (prefix == kMeterPrefix) + { + const auto &meter_attr_name = tokenized_field[1]; + if (std::stoi(value) < 0) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL meter field value " << QuotedVar(field) << ": " << QuotedVar(value); + } + if (meter_attr_name == kMeterCir) + { + app_db_entry.meter.cir = std::stoi(value); + } + else if (meter_attr_name == kMeterCburst) + { + app_db_entry.meter.cburst = std::stoi(value); + } + else if (meter_attr_name == kMeterPir) + { + app_db_entry.meter.pir = std::stoi(value); + } + else if (meter_attr_name == kMeterPburst) + { + app_db_entry.meter.pburst = std::stoi(value); + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown ACL meter field " << QuotedVar(field); + } + app_db_entry.meter.enabled = true; + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown ACL rule field " << QuotedVar(field); + } + } + return app_db_entry; +} + +ReturnCode AclRuleManager::validateAclRuleAppDbEntry(const P4AclRuleAppDbEntry &app_db_entry) +{ + if (app_db_entry.priority == 0) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL rule in table " << QuotedVar(app_db_entry.acl_table_name) << " is missing priority"; + } + return ReturnCode(); +} + +P4AclRule *AclRuleManager::getAclRule(const std::string &acl_table_name, const std::string &acl_rule_key) +{ + if (m_aclRuleTables[acl_table_name].find(acl_rule_key) == m_aclRuleTables[acl_table_name].end()) + { + return nullptr; + } + return &m_aclRuleTables[acl_table_name][acl_rule_key]; +} + +ReturnCode AclRuleManager::setAclRuleCounterStats(const P4AclRule &acl_rule) +{ + SWSS_LOG_ENTER(); + + std::vector counter_stats_values; + // Query colored packets/bytes stats by ACL meter object id if packet color is + // defined + if (!acl_rule.meter.packet_color_actions.empty()) + { + std::vector counter_stats_ids; + const auto &packet_colors = acl_rule.meter.packet_color_actions; + for (const auto &pc : packet_colors) + { + if (acl_rule.counter.packets_enabled) + { + const auto &pkt_stats_id_it = aclCounterColoredPacketsStatsIdMap.find(fvField(pc)); + if (pkt_stats_id_it == aclCounterColoredPacketsStatsIdMap.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid meter attribute " << pkt_stats_id_it->first << " for packet color in ACL rule " + << QuotedVar(acl_rule.db_key); + } + counter_stats_ids.push_back(pkt_stats_id_it->second); + } + if (acl_rule.counter.bytes_enabled) + { + const auto &byte_stats_id_it = aclCounterColoredBytesStatsIdMap.find(fvField(pc)); + if (byte_stats_id_it == aclCounterColoredBytesStatsIdMap.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid meter attribute " << byte_stats_id_it->first << " for packet color in ACL rule " + << QuotedVar(acl_rule.db_key); + } + counter_stats_ids.push_back(byte_stats_id_it->second); + } + } + std::vector meter_stats(counter_stats_ids.size()); + CHECK_ERROR_AND_LOG_AND_RETURN(sai_policer_api->get_policer_stats( + acl_rule.meter.meter_oid, static_cast(counter_stats_ids.size()), + counter_stats_ids.data(), meter_stats.data()), + "Failed to get meter stats for ACL rule " << QuotedVar(acl_rule.db_key)); + for (size_t i = 0; i < counter_stats_ids.size(); i++) + { + counter_stats_values.push_back(swss::FieldValueTuple{aclCounterStatsIdNameMap.at(counter_stats_ids[i]), + 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) + { + 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_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)}); + } + } + } + // Set field value tuples for counters stats in COUNTERS_DB + m_countersTable->set(acl_rule.db_key, counter_stats_values); + return ReturnCode(); +} + +ReturnCode AclRuleManager::setMatchValue(const acl_entry_attr_union_t attr_name, const std::string &attr_value, + sai_attribute_value_t *value, P4AclRule *acl_rule, + const std::string &ip_type_bit_type) +{ + try + { + switch (attr_name) + { + case SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS: { + const auto &ports = tokenize(attr_value, kPortsDelimiter); + if (ports.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "IN_PORTS are emtpy."; + } + for (const auto &alias : ports) + { + Port port; + if (!gPortsOrch->getPort(alias, port)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Failed to locate port " << QuotedVar(alias); + } + acl_rule->in_ports.push_back(alias); + acl_rule->in_ports_oids.push_back(port.m_port_id); + } + value->aclfield.data.objlist.count = static_cast(acl_rule->in_ports_oids.size()); + value->aclfield.data.objlist.list = acl_rule->in_ports_oids.data(); + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS: { + const auto &ports = tokenize(attr_value, kPortsDelimiter); + if (ports.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "OUT_PORTS are emtpy."; + } + for (const auto &alias : ports) + { + Port port; + if (!gPortsOrch->getPort(alias, port)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Failed to locate port " << QuotedVar(alias); + } + acl_rule->out_ports.push_back(alias); + acl_rule->out_ports_oids.push_back(port.m_port_id); + } + value->aclfield.data.objlist.count = static_cast(acl_rule->out_ports_oids.size()); + value->aclfield.data.objlist.list = acl_rule->out_ports_oids.data(); + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_IN_PORT: { + Port port; + if (!gPortsOrch->getPort(attr_value, port)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Failed to locate port " << QuotedVar(attr_value); + } + value->aclfield.data.oid = port.m_port_id; + acl_rule->in_ports.push_back(attr_value); + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORT: { + Port port; + if (!gPortsOrch->getPort(attr_value, port)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Failed to locate port " << QuotedVar(attr_value); + } + value->aclfield.data.oid = port.m_port_id; + acl_rule->out_ports.push_back(attr_value); + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE: { + if (!setMatchFieldIpType(attr_value, value, ip_type_bit_type)) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to set IP_TYPE with value " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_TCP_FLAGS: + case SAI_ACL_ENTRY_ATTR_FIELD_IP_FLAGS: + case SAI_ACL_ENTRY_ATTR_FIELD_DSCP: { + // Support both exact value match and value/mask match + const auto &flag_data = tokenize(attr_value, kDataMaskDelimiter); + value->aclfield.data.u8 = to_uint(trim(flag_data[0]), 0, 0x3F); + + if (flag_data.size() == 2) + { + value->aclfield.mask.u8 = to_uint(trim(flag_data[1]), 0, 0x3F); + } + else + { + value->aclfield.mask.u8 = 0x3F; + } + break; + } + 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: { + const std::vector &value_and_mask = tokenize(attr_value, kDataMaskDelimiter); + value->aclfield.data.u16 = to_uint(trim(value_and_mask[0])); + if (value_and_mask.size() > 1) + { + value->aclfield.mask.u16 = to_uint(trim(value_and_mask[1])); + } + else + { + value->aclfield.mask.u16 = 0xFFFF; + } + break; + } + 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: { + const auto &tokenized_ip = tokenize(attr_value, kDataMaskDelimiter); + if (tokenized_ip.size() == 2) + { + // data & mask + swss::IpAddress ip_data(trim(tokenized_ip[0])); + if (!ip_data.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP data type should be v4 type: " << QuotedVar(attr_value); + } + swss::IpAddress ip_mask(trim(tokenized_ip[1])); + if (!ip_mask.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP mask type should be v4 type: " << QuotedVar(attr_value); + } + value->aclfield.data.ip4 = ip_data.getV4Addr(); + value->aclfield.mask.ip4 = ip_mask.getV4Addr(); + } + else + { + // LPM annotated value + swss::IpPrefix ip_prefix(trim(attr_value)); + if (!ip_prefix.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP type should be v6 type: " << QuotedVar(attr_value); + } + value->aclfield.data.ip4 = ip_prefix.getIp().getV4Addr(); + value->aclfield.mask.ip4 = ip_prefix.getMask().getV4Addr(); + } + break; + } + 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: { + const auto &tokenized_ip = tokenize(attr_value, kDataMaskDelimiter); + if (tokenized_ip.size() == 2) + { + // data & mask + swss::IpAddress ip_data(trim(tokenized_ip[0])); + if (ip_data.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP data type should be v6 type: " << QuotedVar(attr_value); + } + swss::IpAddress ip_mask(trim(tokenized_ip[1])); + if (ip_mask.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP mask type should be v6 type: " << QuotedVar(attr_value); + } + memcpy(value->aclfield.data.ip6, ip_data.getV6Addr(), sizeof(sai_ip6_t)); + memcpy(value->aclfield.mask.ip6, ip_mask.getV6Addr(), sizeof(sai_ip6_t)); + } + else + { + // LPM annotated value + swss::IpPrefix ip_prefix(trim(attr_value)); + if (ip_prefix.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP type should be v6 type: " << QuotedVar(attr_value); + } + memcpy(value->aclfield.data.ip6, ip_prefix.getIp().getV6Addr(), sizeof(sai_ip6_t)); + memcpy(value->aclfield.mask.ip6, ip_prefix.getMask().getV6Addr(), sizeof(sai_ip6_t)); + } + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_SRC_MAC: + case SAI_ACL_ENTRY_ATTR_FIELD_DST_MAC: { + const std::vector mask_and_value = tokenize(attr_value, kDataMaskDelimiter); + swss::MacAddress mac(trim(mask_and_value[0])); + memcpy(value->aclfield.data.mac, mac.getMac(), sizeof(sai_mac_t)); + if (mask_and_value.size() > 1) + { + swss::MacAddress mask(trim(mask_and_value[1])); + memcpy(value->aclfield.mask.mac, mask.getMac(), sizeof(sai_mac_t)); + } + else + { + const sai_mac_t mac_mask = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + memcpy(value->aclfield.mask.mac, mac_mask, sizeof(sai_mac_t)); + } + break; + } + 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: { + const std::vector &value_and_mask = tokenize(attr_value, kDataMaskDelimiter); + value->aclfield.data.u8 = to_uint(trim(value_and_mask[0])); + if (value_and_mask.size() > 1) + { + value->aclfield.mask.u8 = to_uint(trim(value_and_mask[1])); + } + else + { + value->aclfield.mask.u8 = 0xFF; + } + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_TUNNEL_VNI: + 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])); + if (value_and_mask.size() > 1) + { + value->aclfield.mask.u32 = to_uint(trim(value_and_mask[1])); + } + else + { + value->aclfield.mask.u32 = 0xFFFFFFFF; + } + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_FRAG: { + const auto &ip_frag_it = aclIpFragLookup.find(attr_value); + if (ip_frag_it == aclIpFragLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid IP frag " << QuotedVar(attr_value); + } + value->aclfield.data.u32 = ip_frag_it->second; + value->aclfield.mask.u32 = 0xFFFFFFFF; + break; + } + case SAI_ACL_ENTRY_ATTR_FIELD_PACKET_VLAN: { + const auto &packet_vlan_it = aclPacketVlanLookup.find(attr_value); + if (packet_vlan_it == aclPacketVlanLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid Packet VLAN " << QuotedVar(attr_value); + } + value->aclfield.data.u32 = packet_vlan_it->second; + value->aclfield.mask.u32 = 0xFFFFFFFF; + break; + } + default: { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL match field " << attr_name << " is not supported."; + } + } + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to parse match attribute " << attr_name << " value: " << QuotedVar(attr_value); + } + value->aclfield.enable = true; + return ReturnCode(); +} + +ReturnCode AclRuleManager::getRedirectActionPortOid(const std::string &target, sai_object_id_t *rediect_oid) +{ + // Try to parse physical port and LAG first + Port port; + if (gPortsOrch->getPort(target, port)) + { + if (port.m_type == Port::PHY) + { + *rediect_oid = port.m_port_id; + return ReturnCode(); + } + else if (port.m_type == Port::LAG) + { + *rediect_oid = port.m_lag_id; + return ReturnCode(); + } + else + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Wrong port type for REDIRECT action. Only " + "physical ports and LAG ports are supported"); + } + } + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Port " << QuotedVar(target) << " not found."; +} + +ReturnCode AclRuleManager::getRedirectActionNextHopOid(const std::string &target, sai_object_id_t *rediect_oid) +{ + // Try to get nexthop object id + const auto &next_hop_key = KeyGenerator::generateNextHopKey(target); + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, rediect_oid)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL Redirect action target next hop ip: " << QuotedVar(target) + << " doesn't exist on the switch"); + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::setAllMatchFieldValues(const P4AclRuleAppDbEntry &app_db_entry, + const P4AclTableDefinition *acl_table, P4AclRule &acl_rule) +{ + for (const auto &match_fv : app_db_entry.match_fvs) + { + const auto &match_field = fvField(match_fv); + const auto &match_value = fvValue(match_fv); + ReturnCode set_match_rc; + // Set UDF fields + auto udf_fields_it = acl_table->udf_fields_lookup.find(match_field); + if (udf_fields_it != acl_table->udf_fields_lookup.end()) + { + // Bytes Offset to extract Hex value from match_value string + uint16_t bytes_offset = 0; + for (const auto &udf_field : udf_fields_it->second) + { + auto udf_group_index_it = acl_table->udf_group_attr_index_lookup.find(udf_field.group_id); + if (udf_group_index_it == acl_table->udf_group_attr_index_lookup.end()) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL( + "No UDF group found in ACL table definition with id:" << QuotedVar(udf_field.group_id)); + } + set_match_rc = setUdfMatchValue( + udf_field, match_value, + &acl_rule.match_fvs[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN + udf_group_index_it->second], + &acl_rule + .udf_data_masks[SAI_ACL_ENTRY_ATTR_USER_DEFINED_FIELD_GROUP_MIN + udf_group_index_it->second], + bytes_offset); + if (!set_match_rc.ok()) + { + set_match_rc.prepend("Invalid ACL rule match field " + QuotedVar(match_field) + ": " + + QuotedVar(match_value) + " to add: "); + return set_match_rc; + } + bytes_offset = (uint16_t)(bytes_offset + udf_field.length); + } + continue; + } + // Set Composite SAI fields + auto composite_sai_match_field_it = acl_table->composite_sai_match_fields_lookup.find(match_field); + if (composite_sai_match_field_it != acl_table->composite_sai_match_fields_lookup.end()) + { + // Handle composite SAI match fields + for (const auto &sai_match_field : composite_sai_match_field_it->second) + { + set_match_rc = setCompositeSaiMatchValue(sai_match_field.entry_attr, match_value, + &acl_rule.match_fvs[sai_match_field.entry_attr]); + if (!set_match_rc.ok()) + { + set_match_rc.prepend("Invalid ACL rule match field " + QuotedVar(match_field) + ": " + + QuotedVar(match_value) + " to add: "); + return set_match_rc; + } + } + continue; + } + auto sai_match_field_it = acl_table->sai_match_field_lookup.find(match_field); + if (sai_match_field_it == acl_table->sai_match_field_lookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL rule match field " << QuotedVar(match_field) << ": " << QuotedVar(match_value) + << " is an invalid ACL rule attribute"; + } + auto &sai_match_field = sai_match_field_it->second; + if (sai_match_field.entry_attr == SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE && + acl_table->ip_type_bit_type_lookup.find(sai_match_field_it->first) != + acl_table->ip_type_bit_type_lookup.end()) + { + set_match_rc = + setMatchValue(sai_match_field.entry_attr, match_value, &acl_rule.match_fvs[sai_match_field.entry_attr], + &acl_rule, acl_table->ip_type_bit_type_lookup.at(sai_match_field_it->first)); + } + else + { + set_match_rc = setMatchValue(sai_match_field.entry_attr, match_value, + &acl_rule.match_fvs[sai_match_field.entry_attr], &acl_rule); + } + if (!set_match_rc.ok()) + { + set_match_rc.prepend("Invalid ACL rule match field " + QuotedVar(match_field) + ": " + + QuotedVar(match_value) + " to add: "); + return set_match_rc; + } + } + if (!acl_table->ip_type_bit_type_lookup.empty() && + acl_rule.match_fvs.find(SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE) == acl_rule.match_fvs.end()) + { + // Wildcard match on ip type bits + sai_attribute_value_t ip_type_attr; + ip_type_attr.aclfield.data.u32 = SAI_ACL_IP_TYPE_ANY; + ip_type_attr.aclfield.mask.u32 = 0xFFFFFFFF; + ip_type_attr.aclfield.enable = true; + acl_rule.match_fvs.insert({SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE, ip_type_attr}); + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::setAllActionFieldValues(const P4AclRuleAppDbEntry &app_db_entry, + const P4AclTableDefinition *acl_table, P4AclRule &acl_rule) +{ + const auto &action_param_list_it = acl_table->rule_action_field_lookup.find(app_db_entry.action); + if (action_param_list_it == acl_table->rule_action_field_lookup.end()) + { + ReturnCode status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid P4 ACL action " << QuotedVar(app_db_entry.action); + return status; + } + SaiActionWithParam sai_action_param; + for (const auto &action_param : action_param_list_it->second) + { + sai_action_param.action = action_param.action; + sai_action_param.param_name = action_param.param_name; + sai_action_param.param_value = action_param.param_value; + if (!action_param.param_name.empty()) + { + const auto ¶m_value_it = app_db_entry.action_param_fvs.find(action_param.param_name); + if (param_value_it == app_db_entry.action_param_fvs.end()) + { + ReturnCode status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "No action param found for action " << action_param.action; + return status; + } + if (!param_value_it->second.empty()) + { + sai_action_param.param_value = param_value_it->second; + } + } + auto set_action_rc = setActionValue(sai_action_param.action, sai_action_param.param_value, + &acl_rule.action_fvs[sai_action_param.action], &acl_rule); + if (!set_action_rc.ok()) + { + return set_action_rc; + } + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::setActionValue(const acl_entry_attr_union_t attr_name, const std::string &attr_value, + sai_attribute_value_t *value, P4AclRule *acl_rule) +{ + switch (attr_name) + { + case SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION: { + const auto it = aclPacketActionLookup.find(attr_value); + if (it != aclPacketActionLookup.end()) + { + value->aclaction.parameter.s32 = it->second; + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL packet action " << QuotedVar(attr_value) << " for " + << QuotedVar(acl_rule->acl_table_name); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT: { + sai_object_id_t redirect_oid; + if (getRedirectActionPortOid(attr_value, &redirect_oid).ok()) + { + value->aclaction.parameter.oid = redirect_oid; + break; + } + RETURN_IF_ERROR(getRedirectActionNextHopOid(attr_value, &redirect_oid)); + value->aclaction.parameter.oid = redirect_oid; + acl_rule->action_redirect_nexthop_key = KeyGenerator::generateNextHopKey(attr_value); + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP: { + try + { + swss::IpAddress ip(attr_value); + if (ip.isV4()) + { + value->aclaction.parameter.ip4 = ip.getV4Addr(); + } + else + { + memcpy(value->aclaction.parameter.ip6, ip.getV6Addr(), sizeof(sai_ip6_t)); + } + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect IP address but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS: + case SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS: { + sai_object_id_t mirror_session_oid; + std::string key = KeyGenerator::generateMirrorSessionKey(attr_value); + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_MIRROR_SESSION, key, &mirror_session_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Mirror session " << QuotedVar(attr_value) << " does not exist for " + << QuotedVar(acl_rule->acl_table_name); + } + auto &mirror_session = acl_rule->action_mirror_sessions[attr_name]; + mirror_session.name = attr_value; + mirror_session.key = key; + mirror_session.oid = mirror_session_oid; + value->aclaction.parameter.objlist.list = &mirror_session.oid; + value->aclaction.parameter.objlist.count = 1; + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_PACKET_COLOR: { + const auto &it = aclPacketColorLookup.find(attr_value); + if (it == aclPacketColorLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL packet color " << QuotedVar(attr_value) << " in action for " + << QuotedVar(acl_rule->acl_table_name); + } + value->aclaction.parameter.s32 = it->second; + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_MAC: { + try + { + swss::MacAddress mac(attr_value); + memcpy(value->aclaction.parameter.mac, mac.getMac(), sizeof(sai_mac_t)); + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect MAC address but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IP: { + try + { + swss::IpAddress ip(attr_value); + if (!ip.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect IPv4 address but got " + << QuotedVar(attr_value); + } + value->aclaction.parameter.ip4 = ip.getV4Addr(); + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect IP address but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IPV6: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6: { + try + { + swss::IpAddress ip(attr_value); + if (ip.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect IPv6 address but got " + << QuotedVar(attr_value); + } + memcpy(value->aclaction.parameter.ip6, ip.getV6Addr(), sizeof(sai_ip6_t)); + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect IP address but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID: { + try + { + uint32_t queue_num = to_uint(attr_value); + if (queue_num < 1 || queue_num > m_userDefinedTraps.size()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid CPU queue number " << QuotedVar(attr_value) << " for " + << QuotedVar(acl_rule->acl_table_name) + << ". Queue number should >= 1 and <= " << m_userDefinedTraps.size(); + } + value->aclaction.parameter.oid = m_userDefinedTraps[queue_num - 1].user_defined_trap; + acl_rule->action_qos_queue_num = queue_num; + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect integer but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_TC: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DSCP: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_ECN: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_PRI: { + try + { + value->aclaction.parameter.u8 = to_uint(attr_value); + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect integer but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID: { + try + { + value->aclaction.parameter.u32 = to_uint(attr_value); + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect integer but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_ID: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_SRC_PORT: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_DST_PORT: { + try + { + value->aclaction.parameter.u16 = to_uint(attr_value); + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action attribute " << QuotedVar(std::to_string(attr_name)) << " is invalid for " + << QuotedVar(acl_rule->acl_table_name) << ": Expect integer but got " << QuotedVar(attr_value); + } + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_FLOOD: + case SAI_ACL_ENTRY_ATTR_ACTION_DECREMENT_TTL: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DO_NOT_LEARN: { + // parameter is not needed + break; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF: { + if (!attr_value.empty() && !m_vrfOrch->isVRFexists(attr_value)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "No VRF found with name " << QuotedVar(attr_value) + << " for " << QuotedVar(acl_rule->acl_table_name); + } + value->aclaction.parameter.oid = m_vrfOrch->getVRFid(attr_value); + break; + } + default: { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL action " << attr_name << " for " << QuotedVar(acl_rule->acl_table_name); + } + } + value->aclaction.enable = true; + return ReturnCode(); +} + +ReturnCode AclRuleManager::setMeterValue(const P4AclTableDefinition *acl_table, const P4AclRuleAppDbEntry &app_db_entry, + P4AclMeter &acl_meter) +{ + if (app_db_entry.meter.enabled) + { + acl_meter.cir = app_db_entry.meter.cir; + acl_meter.cburst = app_db_entry.meter.cburst; + acl_meter.pir = app_db_entry.meter.pir; + acl_meter.pburst = app_db_entry.meter.pburst; + acl_meter.mode = SAI_POLICER_MODE_TR_TCM; + if (acl_table->meter_unit == P4_METER_UNIT_PACKETS) + { + acl_meter.type = SAI_METER_TYPE_PACKETS; + } + else if (acl_table->meter_unit == P4_METER_UNIT_BYTES) + { + acl_meter.type = SAI_METER_TYPE_BYTES; + } + else + { + ReturnCode status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL meter type " << QuotedVar(acl_table->meter_unit); + return status; + } + acl_meter.enabled = true; + } + const auto &action_color_it = acl_table->rule_packet_action_color_lookup.find(app_db_entry.action); + if (action_color_it != acl_table->rule_packet_action_color_lookup.end() && !action_color_it->second.empty()) + { + acl_meter.packet_color_actions = action_color_it->second; + } + 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; + bool created_counter = false; + 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.meter_oid == SAI_NULL_OBJECT_ID) + { + auto status = createAclMeter(acl_rule.meter, table_name_and_rule_key, &acl_rule.meter.meter_oid); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL meter for rule %s", QuotedVar(acl_rule.acl_rule_key).c_str()); + return status; + } + 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 + if (acl_rule.counter.packets_enabled || acl_rule.counter.bytes_enabled) + { + 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, + &acl_rule.counter.counter_oid); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL counter for rule %s", QuotedVar(acl_rule.acl_rule_key).c_str()); + if (created_meter) + { + auto rc = removeAclMeter(table_name_and_rule_key); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to remove ACL meter in recovery."); + } + } + return status; + } + 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()); + if (sai_status != SAI_STATUS_SUCCESS) + { + ReturnCode status = ReturnCode(sai_status) + << "Failed to create ACL entry in table " << QuotedVar(acl_rule.acl_table_name); + SWSS_LOG_ERROR("%s SAI_STATUS: %s", status.message().c_str(), sai_serialize_status(sai_status).c_str()); + if (created_meter) + { + auto rc = removeAclMeter(table_name_and_rule_key); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to remove ACL meter in recovery."); + } + } + if (created_counter) + { + auto rc = removeAclCounter(acl_rule.acl_table_name, table_name_and_rule_key); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to remove ACL counter in recovery."); + } + } + return status; + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::updateAclRule(const P4AclRule &acl_rule, const P4AclRule &old_acl_rule, + std::vector &acl_entry_attrs, + std::vector &rollback_attrs) +{ + SWSS_LOG_ENTER(); + + sai_attribute_t acl_entry_attr; + std::set actions_to_reset; + for (const auto &old_action_fv : old_acl_rule.action_fvs) + { + actions_to_reset.insert(fvField(old_action_fv)); + } + + for (const auto &action_fv : acl_rule.action_fvs) + { + const auto &it = old_acl_rule.action_fvs.find(fvField(action_fv)); + if (it == old_acl_rule.action_fvs.end()) + { + acl_entry_attr.id = fvField(action_fv); + acl_entry_attr.value = fvValue(action_fv); + acl_entry_attr.value.aclaction.enable = true; + acl_entry_attrs.push_back(acl_entry_attr); + acl_entry_attr.value.aclaction.enable = false; + rollback_attrs.push_back(acl_entry_attr); + } + else if (isDiffActionFieldValue(fvField(action_fv), fvValue(action_fv), it->second, acl_rule, old_acl_rule)) + { + acl_entry_attr.id = fvField(action_fv); + acl_entry_attr.value = fvValue(action_fv); + acl_entry_attr.value.aclaction.enable = true; + acl_entry_attrs.push_back(acl_entry_attr); + acl_entry_attr.value = it->second; + rollback_attrs.push_back(acl_entry_attr); + } + if (it != old_acl_rule.action_fvs.end()) + { + actions_to_reset.erase(fvField(action_fv)); + } + } + + for (const auto &action : actions_to_reset) + { + acl_entry_attr.id = action; + acl_entry_attr.value = old_acl_rule.action_fvs.at(action); + acl_entry_attr.value.aclaction.enable = false; + acl_entry_attrs.push_back(acl_entry_attr); + acl_entry_attr.value.aclaction.enable = true; + rollback_attrs.push_back(acl_entry_attr); + } + + ReturnCode status; + int i; + for (i = 0; i < static_cast(acl_entry_attrs.size()); ++i) + { + status = ReturnCode(sai_acl_api->set_acl_entry_attribute(old_acl_rule.acl_entry_oid, &acl_entry_attrs[i])); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to update ACL rule attributes: %s", status.message().c_str()); + break; + } + } + if (!status.ok()) + { + for (--i; i >= 0; --i) + { + auto sai_status = sai_acl_api->set_acl_entry_attribute(old_acl_rule.acl_entry_oid, &rollback_attrs[i]); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set ACL rule attribute. SAI_STATUS: %s", + sai_serialize_status(sai_status).c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to set ACL rule attribute in recovery."); + } + } + return status; + } + + // Clear old ACL rule dependent refcount and update refcount in new rule + if (!old_acl_rule.action_redirect_nexthop_key.empty()) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, old_acl_rule.action_redirect_nexthop_key); + } + if (!acl_rule.action_redirect_nexthop_key.empty()) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, acl_rule.action_redirect_nexthop_key); + } + for (const auto &mirror_session : old_acl_rule.action_mirror_sessions) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_MIRROR_SESSION, fvValue(mirror_session).key); + } + for (const auto &mirror_session : acl_rule.action_mirror_sessions) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_MIRROR_SESSION, fvValue(mirror_session).key); + } + auto old_set_vrf_action_it = old_acl_rule.action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF); + if (old_set_vrf_action_it != old_acl_rule.action_fvs.end()) + { + m_vrfOrch->decreaseVrfRefCount(old_set_vrf_action_it->second.aclaction.parameter.oid); + } + auto set_vrf_action_it = acl_rule.action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF); + if (set_vrf_action_it != acl_rule.action_fvs.end()) + { + m_vrfOrch->increaseVrfRefCount(set_vrf_action_it->second.aclaction.parameter.oid); + } + auto set_user_trap_it = acl_rule.action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID); + if (set_user_trap_it != acl_rule.action_fvs.end()) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, + std::to_string(acl_rule.action_qos_queue_num)); + } + auto old_set_user_trap_it = old_acl_rule.action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID); + if (old_set_user_trap_it != old_acl_rule.action_fvs.end()) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, + std::to_string(old_acl_rule.action_qos_queue_num)); + } + return ReturnCode(); +} + +ReturnCode AclRuleManager::removeAclRule(const std::string &acl_table_name, const std::string &acl_rule_key) +{ + auto *acl_rule = getAclRule(acl_table_name, acl_rule_key); + if (acl_rule == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "ACL rule with key " << QuotedVar(acl_rule_key) << " in table " + << QuotedVar(acl_table_name) << " does not exist"); + } + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(acl_table_name, acl_rule_key); + // Check if there is anything referring to the next hop before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count for ACL rule " + << QuotedVar(table_name_and_rule_key)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL rule " << QuotedVar(acl_rule_key) + << " referenced by other objects (ref_count = " << ref_count << ")"); + } + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_acl_api->remove_acl_entry(acl_rule->acl_entry_oid), + "Failed to remove ACL rule with key " + << 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()) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + auto status = removeAclMeter(table_name_and_rule_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL meter for rule with key %s in table %s.", + QuotedVar(acl_rule_key).c_str(), QuotedVar(acl_table_name).c_str()); + auto rc = createAclRule(*acl_rule); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to create ACL rule in recovery."); + } + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + return status; + } + acl_rule->meter.meter_oid = SAI_NULL_OBJECT_ID; + deleted_meter = true; + } + if (acl_rule->counter.packets_enabled || acl_rule->counter.bytes_enabled) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key); + auto status = removeAclCounter(acl_table_name, table_name_and_rule_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL counter for rule with key %s.", + QuotedVar(table_name_and_rule_key).c_str()); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key); + if (deleted_meter) + { + auto rc = createAclMeter(acl_rule->meter, table_name_and_rule_key, &acl_rule->meter.meter_oid); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to create ACL rule in recovery."); + return status; + } + } + auto rc = createAclRule(*acl_rule); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to create ACL rule in recovery."); + } + return status; + } + // Remove counter stats + m_countersTable->del(acl_rule->db_key); + } + gCrmOrch->decCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, acl_rule->acl_table_oid); + if (!acl_rule->action_redirect_nexthop_key.empty()) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, acl_rule->action_redirect_nexthop_key); + } + for (const auto &mirror_session : acl_rule->action_mirror_sessions) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_MIRROR_SESSION, fvValue(mirror_session).key); + } + auto set_vrf_action_it = acl_rule->action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF); + if (set_vrf_action_it != acl_rule->action_fvs.end()) + { + m_vrfOrch->decreaseVrfRefCount(set_vrf_action_it->second.aclaction.parameter.oid); + } + auto set_user_trap_it = acl_rule->action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID); + if (set_user_trap_it != acl_rule->action_fvs.end()) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, + std::to_string(acl_rule->action_qos_queue_num)); + } + for (const auto &port_alias : acl_rule->in_ports) + { + gPortsOrch->decreasePortRefCount(port_alias); + } + for (const auto &port_alias : acl_rule->out_ports) + { + gPortsOrch->decreasePortRefCount(port_alias); + } + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, acl_table_name); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key); + m_aclRuleTables[acl_table_name].erase(acl_rule_key); + return ReturnCode(); +} + +ReturnCode AclRuleManager::processAddRuleRequest(const std::string &acl_rule_key, + const P4AclRuleAppDbEntry &app_db_entry) +{ + 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; + acl_rule.db_key = app_db_entry.db_key; + 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; + + // Add match field values + LOG_AND_RETURN_IF_ERROR(setAllMatchFieldValues(app_db_entry, acl_table, acl_rule)); + + // Add action field values + auto status = setAllActionFieldValues(app_db_entry, acl_table, acl_rule); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to add action field values for ACL rule %s: %s", + QuotedVar(acl_rule.acl_rule_key).c_str(), status.message().c_str()); + return status; + } + + // Add meter + LOG_AND_RETURN_IF_ERROR(setMeterValue(acl_table, app_db_entry, acl_rule.meter)); + + // Add counter + if (!acl_table->counter_unit.empty()) + { + if (acl_table->counter_unit == P4_COUNTER_UNIT_PACKETS) + { + acl_rule.counter.packets_enabled = true; + } + else if (acl_table->counter_unit == P4_COUNTER_UNIT_BYTES) + { + acl_rule.counter.bytes_enabled = true; + } + else if (acl_table->counter_unit == P4_COUNTER_UNIT_BOTH) + { + acl_rule.counter.bytes_enabled = true; + acl_rule.counter.packets_enabled = true; + } + else + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL counter type " << QuotedVar(acl_table->counter_unit)); + } + } + status = createAclRule(acl_rule); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL rule with key %s in table %s", QuotedVar(acl_rule.acl_rule_key).c_str(), + QuotedVar(app_db_entry.acl_table_name).c_str()); + return status; + } + // ACL entry created in HW, update refcount + if (!acl_rule.action_redirect_nexthop_key.empty()) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, acl_rule.action_redirect_nexthop_key); + } + for (const auto &mirror_session : acl_rule.action_mirror_sessions) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_MIRROR_SESSION, fvValue(mirror_session).key); + } + auto set_vrf_action_it = acl_rule.action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF); + if (set_vrf_action_it != acl_rule.action_fvs.end()) + { + m_vrfOrch->increaseVrfRefCount(set_vrf_action_it->second.aclaction.parameter.oid); + } + auto set_user_trap_it = acl_rule.action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID); + if (set_user_trap_it != acl_rule.action_fvs.end()) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, + std::to_string(acl_rule.action_qos_queue_num)); + } + for (const auto &port_alias : acl_rule.in_ports) + { + gPortsOrch->increasePortRefCount(port_alias); + } + for (const auto &port_alias : acl_rule.out_ports) + { + 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); + if (acl_rule.counter.packets_enabled || acl_rule.counter.bytes_enabled) + { + // 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()) + { + // 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()); + return status; +} + +ReturnCode AclRuleManager::processDeleteRuleRequest(const std::string &acl_table_name, const std::string &acl_rule_key) +{ + SWSS_LOG_ENTER(); + auto status = removeAclRule(acl_table_name, acl_rule_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL rule with key %s in table %s", QuotedVar(acl_rule_key).c_str(), + QuotedVar(acl_table_name).c_str()); + } + return status; +} + +ReturnCode AclRuleManager::processUpdateRuleRequest(const P4AclRuleAppDbEntry &app_db_entry, + const P4AclRule &old_acl_rule) +{ + SWSS_LOG_ENTER(); + + 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; + acl_rule.db_key = app_db_entry.db_key; + + // 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 + // definition + acl_rule.counter = old_acl_rule.counter; + + std::vector acl_entry_attrs; + std::vector rollback_attrs; + sai_attribute_t acl_entry_attr; + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(acl_rule.acl_table_name, acl_rule.acl_rule_key); + + // Update action field + acl_rule.p4_action = app_db_entry.action; + acl_rule.acl_entry_oid = old_acl_rule.acl_entry_oid; + auto set_actions_rc = setAllActionFieldValues(app_db_entry, acl_table, acl_rule); + if (!set_actions_rc.ok()) + { + SWSS_LOG_ERROR("Failed to add action field values for ACL rule %s: %s", + QuotedVar(acl_rule.acl_rule_key).c_str(), set_actions_rc.message().c_str()); + return set_actions_rc; + } + + // Update meter + bool remove_meter = false; + 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())) + { + // Create new meter + auto status = createAclMeter(acl_rule.meter, table_name_and_rule_key, &acl_rule.meter.meter_oid); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL meter for rule %s", QuotedVar(acl_rule.acl_rule_key).c_str()); + return status; + } + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + created_meter = true; + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ACTION_SET_POLICER; + acl_entry_attr.value.aclaction.enable = true; + acl_entry_attr.value.aclaction.parameter.oid = acl_rule.meter.meter_oid; + acl_entry_attrs.push_back(acl_entry_attr); + acl_entry_attr.value.aclaction.enable = false; + 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()) + { + // Remove old meter + remove_meter = true; + acl_entry_attr.id = SAI_ACL_ENTRY_ATTR_ACTION_SET_POLICER; + acl_entry_attr.value.aclaction.enable = false; + acl_entry_attr.value.aclaction.parameter.oid = SAI_NULL_OBJECT_ID; + acl_entry_attrs.push_back(acl_entry_attr); + acl_entry_attr.value.aclaction.enable = true; + acl_entry_attr.value.aclaction.parameter.oid = old_acl_rule.meter.meter_oid; + rollback_attrs.push_back(acl_entry_attr); + } + else if (old_acl_rule.meter.meter_oid != SAI_NULL_OBJECT_ID) + { + // Update meter attributes + auto status = updateAclMeter(acl_rule.meter, old_acl_rule.meter); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to update ACL meter for rule %s", QuotedVar(acl_rule.acl_rule_key).c_str()); + return status; + } + updated_meter = true; + acl_rule.meter.meter_oid = old_acl_rule.meter.meter_oid; + } + + auto status = updateAclRule(acl_rule, old_acl_rule, acl_entry_attrs, rollback_attrs); + if (status.ok()) + { + // Remove old meter. + if (remove_meter) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + auto rc = removeAclMeter(table_name_and_rule_key); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL meter for rule %s", QuotedVar(acl_rule.acl_rule_key).c_str()); + for (const auto &entry_attr : rollback_attrs) + { + auto sai_status = sai_acl_api->set_acl_entry_attribute(old_acl_rule.acl_entry_oid, &entry_attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set ACL rule attribute. SAI_STATUS: %s", + sai_serialize_status(sai_status).c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to set ACL rule attribute in recovery."); + } + } + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + return rc; + } + } + } + else + { + SWSS_LOG_ERROR("Failed to update ACL rule %s", QuotedVar(acl_rule.acl_rule_key).c_str()); + // Clean up + if (created_meter) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key); + auto rc = removeAclMeter(table_name_and_rule_key); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to remove ACL meter in recovery."); + } + } + if (updated_meter) + { + auto rc = updateAclMeter(old_acl_rule.meter, acl_rule.meter); + if (!rc.ok()) + { + SWSS_RAISE_CRITICAL_STATE("Failed to update ACL meter in recovery."); + } + } + return status; + } + + m_aclRuleTables[acl_rule.acl_table_name][acl_rule.acl_rule_key] = acl_rule; + return ReturnCode(); +} + +} // namespace p4orch diff --git a/orchagent/p4orch/acl_rule_manager.h b/orchagent/p4orch/acl_rule_manager.h new file mode 100644 index 000000000000..cc00735d844a --- /dev/null +++ b/orchagent/p4orch/acl_rule_manager.h @@ -0,0 +1,153 @@ +#pragma once + +#include +#include +#include + +#include "copporch.h" +#include "orch.h" +#include "p4orch/acl_util.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" +#include "vrforch.h" + +extern "C" +{ +#include "sai.h" +} + +namespace p4orch +{ +namespace test +{ +class AclManagerTest; +} // namespace test + +class AclRuleManager : public ObjectManagerInterface +{ + public: + explicit AclRuleManager(P4OidMapper *p4oidMapper, VRFOrch *vrfOrch, CoppOrch *coppOrch, + ResponsePublisherInterface *publisher) + : m_p4OidMapper(p4oidMapper), m_vrfOrch(vrfOrch), m_publisher(publisher), m_coppOrch(coppOrch), + m_countersDb(std::make_unique("COUNTERS_DB", 0)), + m_countersTable(std::make_unique( + m_countersDb.get(), std::string(COUNTERS_TABLE) + DEFAULT_KEY_SEPARATOR + APP_P4RT_TABLE_NAME)) + { + SWSS_LOG_ENTER(); + assert(m_p4OidMapper != nullptr); + } + virtual ~AclRuleManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + // Update counters stats for every rule in each ACL table in COUNTERS_DB, if + // counters are enabled in rules. + void doAclCounterStatsTask(); + + private: + // Deserializes an entry in a dynamically created ACL table. + ReturnCodeOr deserializeAclRuleAppDbEntry( + const std::string &acl_table_name, const std::string &key, + const std::vector &attributes); + + // Validate an ACL rule APP_DB entry. + ReturnCode validateAclRuleAppDbEntry(const P4AclRuleAppDbEntry &app_db_entry); + + // Get ACL rule by table name and rule key. Return nullptr if not found. + P4AclRule *getAclRule(const std::string &acl_table_name, const std::string &acl_rule_key); + + // Processes add operation for an ACL rule. + ReturnCode processAddRuleRequest(const std::string &acl_rule_key, const P4AclRuleAppDbEntry &app_db_entry); + + // Processes delete operation for an ACL rule. + ReturnCode processDeleteRuleRequest(const std::string &acl_table_name, const std::string &acl_rule_key); + + // Processes update operation for an ACL rule. + ReturnCode processUpdateRuleRequest(const P4AclRuleAppDbEntry &app_db_entry, const P4AclRule &old_acl_rule); + + // Set counters stats for an ACL rule in COUNTERS_DB. + ReturnCode setAclRuleCounterStats(const P4AclRule &acl_rule); + + // Create an ACL rule. + ReturnCode createAclRule(P4AclRule &acl_rule); + + // 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); + + // Create an ACL meter. + ReturnCode createAclMeter(const P4AclMeter &p4_acl_meter, const std::string &meter_key, sai_object_id_t *meter_oid); + + // Remove an ACL counter. + ReturnCode removeAclCounter(const std::string &acl_table_name, const std::string &counter_key); + + // Update ACL meter. + ReturnCode updateAclMeter(const P4AclMeter &new_acl_meter, const P4AclMeter &old_acl_meter); + + // Update ACL rule. + ReturnCode updateAclRule(const P4AclRule &new_acl_rule, const P4AclRule &old_acl_rule, + std::vector &acl_entry_attrs, + std::vector &rollback_attrs); + + // Remove an ACL meter. + ReturnCode removeAclMeter(const std::string &meter_key); + + // Remove the ACL rule by key in the given ACL table. + ReturnCode removeAclRule(const std::string &acl_table_name, const std::string &acl_rule_key); + + // Set Meter value in ACL rule. + ReturnCode setMeterValue(const P4AclTableDefinition *acl_table, const P4AclRuleAppDbEntry &app_db_entry, + P4AclMeter &acl_meter); + + // Validate and set all match attributes in an ACL rule. + ReturnCode setAllMatchFieldValues(const P4AclRuleAppDbEntry &app_db_entry, const P4AclTableDefinition *acl_table, + P4AclRule &acl_rules); + + // Validate and set all action attributes in an ACL rule. + ReturnCode setAllActionFieldValues(const P4AclRuleAppDbEntry &app_db_entry, const P4AclTableDefinition *acl_table, + P4AclRule &acl_rule); + + // Validate and set a match attribute in an ACL rule. + ReturnCode setMatchValue(const acl_entry_attr_union_t attr_name, const std::string &attr_value, + sai_attribute_value_t *value, P4AclRule *acl_rule, + const std::string &ip_type_bit_type = EMPTY_STRING); + + // Validate and set an action attribute in an ACL rule. + ReturnCode setActionValue(const acl_entry_attr_union_t attr_name, const std::string &attr_value, + sai_attribute_value_t *value, P4AclRule *acl_rule); + + // Get port object id by name for redirect action. + ReturnCode getRedirectActionPortOid(const std::string &target, sai_object_id_t *rediect_oid); + + // Get next hop object id by name for redirect action. + ReturnCode getRedirectActionNextHopOid(const std::string &target, sai_object_id_t *rediect_oid); + + // Create user defined trap for each cpu queue/trap group and program user + // defined traps in hostif. Save the user defined trap oids in m_p4OidMapper + // and default ref count is 1. + ReturnCode setUpUserDefinedTraps(); + + // Clean up user defined traps created for cpu queues. Callers need to make + // sure ref count on user defined traps in m_userDefinedTraps are ones before + // clean up. + ReturnCode cleanUpUserDefinedTraps(); + + P4OidMapper *m_p4OidMapper; + ResponsePublisherInterface *m_publisher; + P4AclRuleTables m_aclRuleTables; + VRFOrch *m_vrfOrch; + CoppOrch *m_coppOrch; + std::deque m_entries; + std::unique_ptr m_countersDb; + std::unique_ptr m_countersTable; + std::vector m_userDefinedTraps; + + friend class AclTableManager; + friend class p4orch::test::AclManagerTest; +}; + +} // namespace p4orch diff --git a/orchagent/p4orch/acl_table_manager.cpp b/orchagent/p4orch/acl_table_manager.cpp new file mode 100644 index 000000000000..456c2f04d253 --- /dev/null +++ b/orchagent/p4orch/acl_table_manager.cpp @@ -0,0 +1,901 @@ +#include "p4orch/acl_table_manager.h" + +#include +#include +#include + +#include "crmorch.h" +#include "json.hpp" +#include "logger.h" +#include "orch.h" +#include "p4orch.h" +#include "p4orch/p4orch_util.h" +#include "sai_serialize.h" +#include "switchorch.h" +#include "tokenize.h" +extern "C" +{ +#include "sai.h" +} + +extern sai_object_id_t gSwitchId; +extern sai_acl_api_t *sai_acl_api; +extern sai_udf_api_t *sai_udf_api; +extern sai_switch_api_t *sai_switch_api; +extern CrmOrch *gCrmOrch; +extern P4Orch *gP4Orch; +extern SwitchOrch *gSwitchOrch; +extern int gBatchSize; + +namespace p4orch +{ + +AclTableManager::AclTableManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + : m_p4OidMapper(p4oidMapper), m_publisher(publisher) +{ + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + // Create the default UDF match + auto status = createDefaultUdfMatch(); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL UDF default match : %s", status.message().c_str()); + } +} + +AclTableManager::~AclTableManager() +{ + auto status = removeDefaultUdfMatch(); + if (!status.ok()) + { + status.prepend("Failed to remove default UDF match: "); + SWSS_LOG_ERROR("%s", status.message().c_str()); + } +} + +void AclTableManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void AclTableManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string db_key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &db_key); + SWSS_LOG_NOTICE("P4AclTableManager drain tuple for table %s", QuotedVar(table_name).c_str()); + if (table_name != APP_P4RT_ACL_TABLE_DEFINITION_NAME) + { + ReturnCode status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid table " << QuotedVar(table_name); + 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); + continue; + } + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + ReturnCode status; + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto app_db_entry_or = deserializeAclTableDefinitionAppDbEntry(db_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 + ":" + db_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; + + status = validateAclTableDefinitionAppDbEntry(app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for ACL definition APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + db_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 *acl_table_definition = getAclTable(app_db_entry.acl_table_name); + if (acl_table_definition == nullptr) + { + SWSS_LOG_NOTICE("ACL table SET %s", app_db_entry.acl_table_name.c_str()); + status = processAddTableRequest(app_db_entry); + } + else + { + // All attributes in sai_acl_table_attr_t are CREATE_ONLY. + status = ReturnCode(StatusCode::SWSS_RC_UNIMPLEMENTED) + << "Unable to update ACL table definition in APP DB entry with key " + << QuotedVar(table_name + ":" + db_key) + << " : All attributes in sai_acl_table_attr_t are CREATE_ONLY."; + } + } + else if (operation == DEL_COMMAND) + { + status = processDeleteTableRequest(db_key); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Unknown operation type " << QuotedVar(operation); + } + if (!status.ok()) + { + SWSS_LOG_ERROR("Processed DEFINITION entry status: %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(); +} + +ReturnCodeOr AclTableManager::deserializeAclTableDefinitionAppDbEntry( + const std::string &key, const std::vector &attributes) +{ + SWSS_LOG_ENTER(); + + P4AclTableDefinitionAppDbEntry app_db_entry = {}; + app_db_entry.acl_table_name = key; + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + SWSS_LOG_INFO("ACL table definition attr string %s : %s\n", QuotedVar(field).c_str(), QuotedVar(value).c_str()); + if (field == kStage) + { + app_db_entry.stage = value; + continue; + } + else if (field == kPriority) + { + int priority = std::stoi(value); + if (priority < 0) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid ACL table priority " << QuotedVar(value); + } + app_db_entry.priority = static_cast(priority); + continue; + } + else if (field == kSize) + { + int size = std::stoi(value); + if (size < 0) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid ACL table size " << QuotedVar(value); + } + app_db_entry.size = static_cast(size); + continue; + } + else if (field == kMeterUnit) + { + app_db_entry.meter_unit = value; + continue; + } + else if (field == kCounterUnit) + { + app_db_entry.counter_unit = value; + continue; + } + std::vector tokenized_field = swss::tokenize(field, kFieldDelimiter); + if (tokenized_field.size() <= 1) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unknown ACL table definition field string " << QuotedVar(field); + } + const auto &p4_field = tokenized_field[1]; + if (tokenized_field[0] == kMatchPrefix) + { + app_db_entry.match_field_lookup[p4_field] = value; + } + else if (tokenized_field[0] == kAction) + { + if (!parseAclTableAppDbActionField(value, &app_db_entry.action_field_lookup[p4_field], + &app_db_entry.packet_action_color_lookup[p4_field])) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Error parsing ACL table definition action " << QuotedVar(field) << ":" << QuotedVar(value); + } + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unknown ACL table definition field string " << QuotedVar(field); + } + } + return app_db_entry; +} + +ReturnCode AclTableManager::validateAclTableDefinitionAppDbEntry(const P4AclTableDefinitionAppDbEntry &app_db_entry) +{ + // Perform generic APP DB entry validations. Operation specific + // validations will be done by the respective request process methods. + if (!app_db_entry.meter_unit.empty() && app_db_entry.meter_unit != P4_METER_UNIT_BYTES && + app_db_entry.meter_unit != P4_METER_UNIT_PACKETS) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table meter unit " << QuotedVar(app_db_entry.meter_unit) << " is invalid"; + } + if (!app_db_entry.counter_unit.empty() && app_db_entry.counter_unit != P4_COUNTER_UNIT_BYTES && + app_db_entry.counter_unit != P4_COUNTER_UNIT_PACKETS && app_db_entry.counter_unit != P4_COUNTER_UNIT_BOTH) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table counter unit " << QuotedVar(app_db_entry.counter_unit) << " is invalid"; + } + return ReturnCode(); +} + +P4AclTableDefinition *AclTableManager::getAclTable(const std::string &acl_table_name) +{ + SWSS_LOG_ENTER(); + if (m_aclTableDefinitions.find(acl_table_name) == m_aclTableDefinitions.end()) + return nullptr; + return &m_aclTableDefinitions[acl_table_name]; +} + +ReturnCode AclTableManager::processAddTableRequest(const P4AclTableDefinitionAppDbEntry &app_db_entry) +{ + SWSS_LOG_ENTER(); + + auto stage_it = aclStageLookup.find(app_db_entry.stage); + sai_acl_stage_t stage; + if (stage_it != aclStageLookup.end()) + { + stage = stage_it->second; + } + else + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table stage " << QuotedVar(app_db_entry.stage) << " is invalid"); + } + + if (gSwitchOrch->getAclGroupOidsBindingToSwitch().empty()) + { + // Create default ACL groups binding to switch + gSwitchOrch->initAclGroupsBindToSwitch(); + } + + 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()) + { + 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; + + auto build_match_rc = + buildAclTableDefinitionMatchFieldValues(app_db_entry.match_field_lookup, &acl_table_definition); + + LOG_AND_RETURN_IF_ERROR( + build_match_rc.prepend("Failed to build ACL table definition match fields with table name " + + QuotedVar(app_db_entry.acl_table_name) + ": ")); + + auto build_action_rc = buildAclTableDefinitionActionFieldValues(app_db_entry.action_field_lookup, + &acl_table_definition.rule_action_field_lookup); + + LOG_AND_RETURN_IF_ERROR( + build_action_rc.prepend("Failed to build ACL table definition action fields with table name " + + QuotedVar(app_db_entry.acl_table_name) + ": ")); + + if (gP4Orch->getAclRuleManager()->m_userDefinedTraps.empty() && + isSetUserTrapActionInAclTableDefinition(acl_table_definition.rule_action_field_lookup)) + { + // Set up User Defined Traps for QOS_QUEUE action + auto status = gP4Orch->getAclRuleManager()->setUpUserDefinedTraps(); + if (!status.ok()) + { + gP4Orch->getAclRuleManager()->cleanUpUserDefinedTraps(); + LOG_ERROR_AND_RETURN(status); + } + } + + auto build_action_color_rc = buildAclTableDefinitionActionColorFieldValues( + app_db_entry.packet_action_color_lookup, &acl_table_definition.rule_action_field_lookup, + &acl_table_definition.rule_packet_action_color_lookup); + LOG_AND_RETURN_IF_ERROR(build_action_color_rc.prepend("Failed to build ACL table definition " + "action color fields with table name " + + QuotedVar(app_db_entry.acl_table_name) + ": ")); + + if (!acl_table_definition.udf_fields_lookup.empty()) + { + LOG_AND_RETURN_IF_ERROR(createUdfGroupsAndUdfsForAclTable(acl_table_definition)); + } + + auto status = + createAclTable(acl_table_definition, &acl_table_definition.table_oid, &acl_table_definition.group_member_oid); + if (!status.ok()) + { + // Clean up newly created UDFs and UDF groups + for (auto &udf_fields : acl_table_definition.udf_fields_lookup) + { + for (auto &udf_field : fvValue(udf_fields)) + { + auto rc = removeUdf(udf_field.udf_id, udf_field.group_id); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to remove UDF %s: %s", QuotedVar(udf_field.udf_id).c_str(), + rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to remove UDF in recovery."); + } + rc = removeUdfGroup(udf_field.group_id); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to remove UDF group %s: %s", QuotedVar(udf_field.group_id).c_str(), + rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to remove UDF group in recovery."); + } + } + } + LOG_ERROR_AND_RETURN( + status.prepend("Failed to create ACL table with key " + QuotedVar(app_db_entry.acl_table_name))); + } + return status; +} + +ReturnCode AclTableManager::createDefaultUdfMatch() +{ + SWSS_LOG_ENTER(); + sai_object_id_t udf_match_oid; + std::vector udf_match_attrs; + CHECK_ERROR_AND_LOG_AND_RETURN(sai_udf_api->create_udf_match(&udf_match_oid, gSwitchId, 0, udf_match_attrs.data()), + "Failed to create default UDF match from SAI call " + "sai_udf_api->create_udf_match"); + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, udf_match_oid); + SWSS_LOG_INFO("Suceeded to create default UDF match %s with object ID %s ", QuotedVar(P4_UDF_MATCH_DEFAULT).c_str(), + sai_serialize_object_id(udf_match_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::removeDefaultUdfMatch() +{ + SWSS_LOG_ENTER(); + sai_object_id_t udf_match_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, &udf_match_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Default UDF match " << QuotedVar(P4_UDF_MATCH_DEFAULT) << " was not found"; + } + + // Check if there is anything referring to the UDF match before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT, &ref_count)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Default UDF match " << QuotedVar(P4_UDF_MATCH_DEFAULT) << " reference count was not found"; + } + if (ref_count > 0) + { + return ReturnCode(StatusCode::SWSS_RC_IN_USE) + << "Default UDF match " << QuotedVar(P4_UDF_MATCH_DEFAULT) + << " is referenced by other objects (ref_count = " << ref_count << ")"; + } + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_udf_api->remove_udf_match(udf_match_oid), + "Failed to remove default UDF match with id " << udf_match_oid); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT); + + SWSS_LOG_INFO("Suceeded to remove UDF match %s : %s", QuotedVar(P4_UDF_MATCH_DEFAULT).c_str(), + sai_serialize_object_id(udf_match_oid).c_str()); + return ReturnCode(); +} + +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); + + 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"); + 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()); + return ReturnCode(); +} + +ReturnCode AclTableManager::removeUdfGroup(const std::string &udf_group_id) +{ + SWSS_LOG_ENTER(); + sai_object_id_t group_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF_GROUP, udf_group_id, &group_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "UDF group " << QuotedVar(udf_group_id) << " was not found"; + } + + // Check if there is anything referring to the UDF group before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_UDF_GROUP, udf_group_id, &ref_count)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "UDF group " << QuotedVar(udf_group_id) << " reference count was not found"; + } + if (ref_count > 0) + { + return ReturnCode(StatusCode::SWSS_RC_IN_USE) + << "UDF group " << QuotedVar(udf_group_id) << " referenced by other objects (ref_count = " << ref_count + << ")"; + } + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_udf_api->remove_udf_group(group_oid), + "Failed to remove UDF group with id " << QuotedVar(udf_group_id)); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_UDF_GROUP, udf_group_id); + + SWSS_LOG_NOTICE("Suceeded to remove UDF group %s: %s", QuotedVar(udf_group_id).c_str(), + sai_serialize_object_id(group_oid).c_str()); + return ReturnCode(); +} + +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)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "UDF default match " << QuotedVar(P4_UDF_MATCH_DEFAULT) << " does not exist"; + } + 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); + + 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"); + 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); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_UDF_GROUP, udf_field.group_id); + SWSS_LOG_NOTICE("Suceeded to create UDF %s with object ID %s ", QuotedVar(udf_id).c_str(), + sai_serialize_object_id(udf_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::removeUdf(const std::string &udf_id, const std::string &udf_group_id) +{ + SWSS_LOG_ENTER(); + sai_object_id_t udf_oid; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_UDF, udf_id, &udf_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "UDF " << QuotedVar(udf_id) << " was not found"; + } + // Check if there is anything referring to the UDF before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_UDF, udf_id, &ref_count)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "UDF " << QuotedVar(udf_id) << " reference count was not found"; + } + if (ref_count > 0) + { + return ReturnCode(StatusCode::SWSS_RC_IN_USE) + << "UDF " << QuotedVar(udf_id) << " referenced by other objects (ref_count = " << ref_count << ")"; + } + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_udf_api->remove_udf(udf_oid), "Failed to remove UDF with id " + << udf_oid + << " from SAI call sai_udf_api->remove_udf"); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_UDF, udf_id); + // Decrease UDF group and match reference count + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_UDF_MATCH, P4_UDF_MATCH_DEFAULT); + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_UDF_GROUP, udf_group_id); + SWSS_LOG_NOTICE("Suceeded to remove UDF %s: %s", QuotedVar(udf_id).c_str(), + sai_serialize_object_id(udf_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::createUdfGroupsAndUdfsForAclTable(const P4AclTableDefinition &acl_table_definition) +{ + ReturnCode status; + // Cache newly created UDFs + std::vector created_udf_fields; + // Cache newly created UDF groups, + std::vector created_udf_group_ids; + // Create UDF groups and UDFs + for (auto &udf_fields : acl_table_definition.udf_fields_lookup) + { + for (auto &udf_field : fvValue(udf_fields)) + { + status = createUdfGroup(udf_field); + if (!status.ok()) + { + status.prepend("Failed to create ACL UDF group with group id " + QuotedVar(udf_field.group_id) + " : "); + break; + } + created_udf_group_ids.push_back(udf_field.group_id); + status = createUdf(udf_field); + if (!status.ok()) + { + status.prepend("Failed to create ACL UDF with id " + QuotedVar(udf_field.udf_id) + ": "); + break; + } + created_udf_fields.push_back(udf_field); + } + if (!status.ok()) + break; + } + // Clean up created UDFs and UDF groups if fails to create all. + if (!status.ok()) + { + for (const auto &udf_field : created_udf_fields) + { + auto rc = removeUdf(udf_field.udf_id, udf_field.group_id); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to remove UDF %s: %s", QuotedVar(udf_field.udf_id).c_str(), + rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to remove UDF in recovery."); + } + } + for (const auto &udf_group_id : created_udf_group_ids) + { + auto rc = removeUdfGroup(udf_group_id); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to remove UDF group %s: %s", QuotedVar(udf_group_id).c_str(), + rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to remove UDF group in recovery."); + } + } + LOG_ERROR_AND_RETURN(status); + } + return ReturnCode(); +} + +ReturnCode AclTableManager::createAclTable(P4AclTableDefinition &acl_table, sai_object_id_t *acl_table_oid, + 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); + + 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()), + "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); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL group member for table %s", QuotedVar(acl_table.acl_table_name).c_str()); + auto sai_status = sai_acl_api->remove_acl_table(*acl_table_oid); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove ACL table %s SAI_STATUS: %s", QuotedVar(acl_table.acl_table_name).c_str(), + sai_serialize_status(sai_status).c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to remove ACL table in recovery."); + } + return status; + } + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_ACL_TABLE, acl_table.acl_table_name, *acl_table_oid); + gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, (sai_acl_stage_t)acl_table.stage, + SAI_ACL_BIND_POINT_TYPE_SWITCH); + m_aclTablesByStage[acl_table.stage].push_back(acl_table.acl_table_name); + m_aclTableDefinitions[acl_table.acl_table_name] = acl_table; + // Add ACL table name to AclRuleManager mapping in p4orch + if (!gP4Orch->addAclTableToManagerMapping(acl_table.acl_table_name)) + { + SWSS_LOG_NOTICE("ACL table %s to AclRuleManager mapping already exists", + QuotedVar(acl_table.acl_table_name).c_str()); + } + for (const auto &udf_group_idx : acl_table.udf_group_attr_index_lookup) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_UDF_GROUP, fvField(udf_group_idx)); + } + + SWSS_LOG_NOTICE("ACL table %s was created successfully : %s", QuotedVar(acl_table.acl_table_name).c_str(), + sai_serialize_object_id(acl_table.table_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::removeAclTable(P4AclTableDefinition &acl_table) +{ + SWSS_LOG_ENTER(); + + auto status = removeAclGroupMember(acl_table.acl_table_name); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL table with key %s : failed to delete group " + "member %s.", + QuotedVar(acl_table.acl_table_name).c_str(), + sai_serialize_object_id(acl_table.group_member_oid).c_str()); + return status; + } + auto sai_status = sai_acl_api->remove_acl_table(acl_table.table_oid); + if (sai_status != SAI_STATUS_SUCCESS) + { + status = ReturnCode(sai_status) << "Failed to remove ACL table with key " << QuotedVar(acl_table.acl_table_name) + << " by calling sai_acl_api->remove_acl_table"; + SWSS_LOG_ERROR("%s", status.message().c_str()); + auto rc = createAclGroupMember(acl_table, &acl_table.group_member_oid); + if (!rc.ok()) + { + SWSS_LOG_ERROR("%s", rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to create ACL group member in recovery."); + } + return status; + } + for (const auto &udf_group_idx : acl_table.udf_group_attr_index_lookup) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_UDF_GROUP, fvField(udf_group_idx)); + } + + // Remove UDFs and UDF groups after ACL table deletion + std::vector removed_udf_fields; + std::vector removed_udf_group_ids; + for (const auto &udf_fields : acl_table.udf_fields_lookup) + { + for (const auto &udf_field : fvValue(udf_fields)) + { + status = removeUdf(udf_field.udf_id, udf_field.group_id); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL UDF with id %s : %s", QuotedVar(udf_field.udf_id).c_str(), + status.message().c_str()); + break; + } + removed_udf_fields.push_back(udf_field); + status = removeUdfGroup(udf_field.group_id); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove ACL UDF group with group id %s : %s", + QuotedVar(udf_field.group_id).c_str(), status.message().c_str()); + break; + } + removed_udf_group_ids.push_back(udf_field); + } + if (!status.ok()) + { + break; + } + } + if (!status.ok()) + { + for (const auto &udf_field : removed_udf_group_ids) + { + auto rc = createUdfGroup(udf_field); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to create UDF group %s: %s", QuotedVar(udf_field.group_id).c_str(), + rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to create UDF group in recovery."); + } + } + for (const auto &udf_field : removed_udf_fields) + { + auto rc = createUdf(udf_field); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to create UDF %s: %s", QuotedVar(udf_field.udf_id).c_str(), + rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to create UDF in recovery."); + } + } + } + + gCrmOrch->decCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, (sai_acl_stage_t)acl_table.stage, + SAI_ACL_BIND_POINT_TYPE_SWITCH, acl_table.table_oid); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ACL_TABLE, acl_table.acl_table_name); + // Remove ACL table name to AclRuleManager mapping in p4orch + if (!gP4Orch->removeAclTableToManagerMapping(acl_table.acl_table_name)) + { + SWSS_LOG_NOTICE("ACL table %s to AclRuleManager mapping does not exist", + QuotedVar(acl_table.acl_table_name).c_str()); + } + auto &table_keys = m_aclTablesByStage[acl_table.stage]; + auto position = std::find(table_keys.begin(), table_keys.end(), acl_table.acl_table_name); + if (position != table_keys.end()) + { + table_keys.erase(position); + } + P4AclTableDefinition rollback_acl_table = acl_table; + m_aclTableDefinitions.erase(acl_table.acl_table_name); + + if (!status.ok()) + { + auto rc = + createAclTable(rollback_acl_table, &rollback_acl_table.table_oid, &rollback_acl_table.group_member_oid); + if (!rc.ok()) + { + SWSS_LOG_ERROR("Failed to create ACL table: %s", rc.message().c_str()); + SWSS_RAISE_CRITICAL_STATE("Failed to create ACL table in recovery."); + } + return status; + } + SWSS_LOG_NOTICE("ACL table %s(%s) was removed successfully.", QuotedVar(rollback_acl_table.acl_table_name).c_str(), + sai_serialize_object_id(rollback_acl_table.table_oid).c_str()); + return ReturnCode(); +} + +ReturnCode AclTableManager::processDeleteTableRequest(const std::string &acl_table_name) +{ + SWSS_LOG_ENTER(); + + auto *acl_table = getAclTable(acl_table_name); + if (acl_table == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "ACL table with key " << QuotedVar(acl_table_name) << " does not exist"); + } + // Check if there is anything referring to the ACL table before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_ACL_TABLE, acl_table->acl_table_name, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count of ACL table " + << QuotedVar(acl_table->acl_table_name)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table " << QuotedVar(acl_table->acl_table_name) + << " referenced by other objects (ref_count = " << ref_count << ")"); + } + return removeAclTable(*acl_table); +} + +ReturnCode AclTableManager::createAclGroupMember(const P4AclTableDefinition &acl_table, + 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); + + 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()), + "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)); + 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) +{ + 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)) + { + 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."); + } + 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()); + 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()); + return ReturnCode(); +} + +} // namespace p4orch diff --git a/orchagent/p4orch/acl_table_manager.h b/orchagent/p4orch/acl_table_manager.h new file mode 100644 index 000000000000..6243c08cb4f8 --- /dev/null +++ b/orchagent/p4orch/acl_table_manager.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include + +#include "orch.h" +#include "p4orch/acl_util.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" + +extern "C" +{ +#include "sai.h" +} + +namespace p4orch +{ +namespace test +{ +class AclManagerTest; +} // namespace test + +class AclTableManager : public ObjectManagerInterface +{ + public: + explicit AclTableManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher); + virtual ~AclTableManager(); + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + // Get ACL table definition by table name in cache. Return nullptr if not + // found. + P4AclTableDefinition *getAclTable(const std::string &acl_table_name); + + private: + // Validate ACL table definition APP_DB entry. + ReturnCode validateAclTableDefinitionAppDbEntry(const P4AclTableDefinitionAppDbEntry &app_db_entry); + + // Deserializes an entry from table APP_P4RT_ACL_TABLE_DEFINITION_NAME. + ReturnCodeOr deserializeAclTableDefinitionAppDbEntry( + const std::string &key, const std::vector &attributes); + + // Create new ACL table definition. + ReturnCode createAclTable(P4AclTableDefinition &acl_table, sai_object_id_t *acl_table_oid, + sai_object_id_t *acl_group_member_oid); + + // Remove ACL table by table name. Caller should verify reference count is + // zero before calling the method. + ReturnCode removeAclTable(P4AclTableDefinition &acl_table); + + // Create UDF groups and UDFs for the ACL table. If any of the UDF and UDF + // group fails to create, then clean up all created ones + ReturnCode createUdfGroupsAndUdfsForAclTable(const P4AclTableDefinition &acl_table); + + // Create new ACL UDF group based on the UdfField. Callers should verify no + // UDF group with the same name exists + ReturnCode createUdfGroup(const P4UdfField &udf_field); + + // Remove ACL UDF group by group id, + ReturnCode removeUdfGroup(const std::string &udf_group_id); + + // Create the default UDF match with name P4_UDF_MATCH_DEFAULT. + // The attributes values for the UDF match are all wildcard matches. + ReturnCode createDefaultUdfMatch(); + + // Remove the default UDF match if no UDFs depend on it. + ReturnCode removeDefaultUdfMatch(); + + // Create UDF with group_oid, base and offset defined in udf_field and the + // default udf_match_oid. Callers should verify no UDF with the same name + // exists + ReturnCode createUdf(const P4UdfField &udf_fields); + + // Remove UDF by id string and group id string if no ACL rules depends on it + ReturnCode removeUdf(const std::string &udf_id, const std::string &udf_group_id); + + // Process add request on ACL table definition. If the table is + // created successfully, a new consumer will be added in + // p4orch to process requests for ACL rules for the table. + ReturnCode processAddTableRequest(const P4AclTableDefinitionAppDbEntry &app_db_entry); + + // Process delete request on ACL table definition. + ReturnCode processDeleteTableRequest(const std::string &acl_table_name); + + // Create ACL group member for given ACL table. + 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); + + P4OidMapper *m_p4OidMapper; + ResponsePublisherInterface *m_publisher; + P4AclTableDefinitions m_aclTableDefinitions; + std::deque m_entries; + std::map> m_aclTablesByStage; + + friend class p4orch::test::AclManagerTest; +}; + +} // namespace p4orch diff --git a/orchagent/p4orch/acl_util.cpp b/orchagent/p4orch/acl_util.cpp new file mode 100644 index 000000000000..6caf67cadeec --- /dev/null +++ b/orchagent/p4orch/acl_util.cpp @@ -0,0 +1,874 @@ +#include "p4orch/acl_util.h" + +#include "converter.h" +#include "json.hpp" +#include "logger.h" +#include "sai_serialize.h" +#include "table.h" +#include "tokenize.h" + +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) +{ + try + { + const auto &j = nlohmann::json::parse(aggr_actions_str); + if (!j.is_array()) + { + SWSS_LOG_ERROR("Invalid ACL table definition action %s, expecting an array.\n", aggr_actions_str.c_str()); + return false; + } + P4ActionParamName action_with_param; + for (auto &action_item : j) + { + auto sai_action_it = action_item.find(kAction); + if (sai_action_it == action_item.end()) + { + SWSS_LOG_ERROR("Invalid ACL table definition action %s, missing 'action':\n", aggr_actions_str.c_str()); + return false; + } + if (aclPacketActionLookup.find(sai_action_it.value()) == aclPacketActionLookup.end()) + { + action_with_param.sai_action = sai_action_it.value(); + auto action_param_it = action_item.find(kActionParamPrefix); + if (action_param_it != action_item.end() && !action_param_it.value().is_null()) + { + action_with_param.p4_param_name = action_param_it.value(); + } + action_list->push_back(action_with_param); + } + else + { + auto packet_color_it = action_item.find(kPacketColor); + P4PacketActionWithColor packet_action_with_color; + packet_action_with_color.packet_action = sai_action_it.value(); + if (packet_color_it != action_item.end() && !packet_color_it.value().is_null()) + { + packet_action_with_color.packet_color = packet_color_it.value(); + } + action_color_list->push_back(packet_action_with_color); + } + } + return true; + } + catch (std::exception &ex) + { + SWSS_LOG_ERROR("Failed to deserialize ACL table definition action fields: %s (%s)", aggr_actions_str.c_str(), + ex.what()); + return false; + } +} + +ReturnCode validateAndSetSaiMatchFieldJson(const nlohmann::json &match_json, const std::string &p4_match, + const std::string &aggr_match_str, + std::map *sai_match_field_lookup, + std::map *ip_type_bit_type_lookup) +{ + SaiMatchField sai_match_field; + auto format_str_it = match_json.find(kAclMatchFieldFormat); + if (format_str_it == match_json.end() || format_str_it.value().is_null() || !format_str_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value is required and should be a string"; + } + auto format_it = formatLookup.find(format_str_it.value()); + if (format_it == formatLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value is invalid, should be one of {" << P4_FORMAT_HEX_STRING << ", " << P4_FORMAT_MAC << ", " + << P4_FORMAT_IPV4 << ", " << P4_FORMAT_IPV6 << ", " << P4_FORMAT_STRING << "}"; + } + sai_match_field.format = format_it->second; + if (sai_match_field.format != Format::STRING) + { + // bitwidth is required if the format is not "STRING" + auto bitwidth_it = match_json.find(kAclMatchFieldBitwidth); + if (bitwidth_it == match_json.end() || bitwidth_it.value().is_null() || !bitwidth_it.value().is_number()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldBitwidth + << " value is required and should be a number"; + } + sai_match_field.bitwidth = bitwidth_it.value(); + } + + auto match_field_it = match_json.find(kAclMatchFieldSaiField); + if (match_field_it == match_json.end() || match_field_it.value().is_null() || !match_field_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldSaiField + << " value is required and should be a string"; + } + + std::vector tokenized_field = swss::tokenize(match_field_it.value(), kFieldDelimiter); + const auto &sai_match_field_str = tokenized_field[0]; + auto table_attr_it = aclMatchTableAttrLookup.find(sai_match_field_str); + auto rule_attr_it = aclMatchEntryAttrLookup.find(sai_match_field_str); + if (table_attr_it == aclMatchTableAttrLookup.end() || rule_attr_it == aclMatchEntryAttrLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << match_field_it.value() << " is not supported in P4Orch "; + } + const auto &expected_format_it = aclMatchTableAttrFormatLookup.find(table_attr_it->second); + if (expected_format_it == aclMatchTableAttrFormatLookup.end() || + sai_match_field.format != expected_format_it->second) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: format for field " << match_field_it.value() + << " is expected to be " << expected_format_it->second << ", but got " << format_it->first; + } + sai_match_field.table_attr = table_attr_it->second; + sai_match_field.entry_attr = rule_attr_it->second; + (*sai_match_field_lookup)[p4_match] = sai_match_field; + + if (rule_attr_it->second == SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE && tokenized_field.size() == 2) + { + // Get IP_TYPE suffix and save the bit mapping. + if (aclIpTypeBitSet.find(tokenized_field[1]) == aclIpTypeBitSet.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} has invalid IP_TYPE encode bit."; + } + (*ip_type_bit_type_lookup)[p4_match] = tokenized_field[1]; + } + SWSS_LOG_INFO("ACL table built match field %s with kind:sai_field", sai_match_field_str.c_str()); + + return ReturnCode(); +} + +ReturnCode validateAndSetCompositeElementSaiFieldJson( + const nlohmann::json &element_match_json, const std::string &p4_match, + std::map> *composite_sai_match_fields_lookup, const std::string &format_str) +{ + SaiMatchField sai_match_field; + const auto &element_str = element_match_json.dump(); + if (format_str != P4_FORMAT_IPV6) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field " << p4_match << " element: " << element_str + << " is an invalid ACL table attribute: '" << kAclMatchFieldFormat << "' should be " << P4_FORMAT_IPV6; + } + sai_match_field.format = Format::IPV6; + + auto bitwidth_it = element_match_json.find(kAclMatchFieldBitwidth); + if (bitwidth_it == element_match_json.end() || bitwidth_it.value().is_null() || !bitwidth_it.value().is_number()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field " << p4_match << "element: " << element_str + << " is an invalid ACL table attribute: " << kAclMatchFieldBitwidth + << " value is required and should be a number"; + } + sai_match_field.bitwidth = bitwidth_it.value(); + + auto match_field_it = element_match_json.find(kAclMatchFieldSaiField); + if (match_field_it == element_match_json.end() || match_field_it.value().is_null() || + !match_field_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field " << p4_match << " element: " << element_str + << " is an invalid ACL table attribute: " << kAclMatchFieldSaiField + << " value is required in composite elements and should be a string"; + } + const std::string &match_field_str = match_field_it.value(); + auto table_attr_it = aclCompositeMatchTableAttrLookup.find(match_field_str); + auto rule_attr_it = aclCompositeMatchEntryAttrLookup.find(match_field_str); + if (table_attr_it == aclCompositeMatchTableAttrLookup.end() || + rule_attr_it == aclCompositeMatchEntryAttrLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field " << p4_match << " element: " << element_str + << " is an invalid ACL table attribute: not supported in P4Orch " + "as an element in composite match fields"; + } + const uint32_t expected_bitwidth = BYTE_BITWIDTH * IPV6_SINGLE_WORD_BYTES_LENGTH; + if (sai_match_field.bitwidth != expected_bitwidth) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field " << p4_match << " element: " << element_str + << " is an invalid ACL table attribute: element.bitwidth is " + "expected to be " + << expected_bitwidth << " but got " << sai_match_field.bitwidth; + } + sai_match_field.table_attr = table_attr_it->second; + sai_match_field.entry_attr = rule_attr_it->second; + (*composite_sai_match_fields_lookup)[p4_match].push_back(sai_match_field); + SWSS_LOG_INFO("ACL table built composite match field element %s with kind:sai_field", match_field_str.c_str()); + return ReturnCode(); +} + +ReturnCode validateAndSetUdfFieldJson(const nlohmann::json &match_json, const std::string &p4_match, + const std::string &aggr_match_str, const std::string &acl_table_name, + std::map> *udf_fields_lookup, + std::map *udf_group_attr_index_lookup) +{ + P4UdfField udf_field; + // Parse UDF bitwitdth + auto bitwidth_json_it = match_json.find(kAclMatchFieldBitwidth); + if (bitwidth_json_it == match_json.end() || bitwidth_json_it.value().is_null() || + !bitwidth_json_it.value().is_number()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match composite UDF field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldBitwidth + << " value is required and should be a number"; + } + uint32_t bitwidth = bitwidth_json_it.value(); + if (bitwidth % BYTE_BITWIDTH != 0) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match composite UDF field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldBitwidth + << " value should be a multiple of 8."; + } + udf_field.length = (uint16_t)(bitwidth / BYTE_BITWIDTH); + + // Parse UDF offset + auto udf_offset_it = match_json.find(kAclUdfOffset); + if (udf_offset_it == match_json.end() || udf_offset_it.value().is_null() || !udf_offset_it.value().is_number()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match composite UDF field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclUdfOffset + << " value is required in composite elements and should be a number"; + } + udf_field.offset = udf_offset_it.value(); + + // Parse UDF base + auto udf_base_json_it = match_json.find(kAclUdfBase); + if (udf_base_json_it == match_json.end() || udf_base_json_it.value().is_null() || + !udf_base_json_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match composite UDF field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclUdfBase + << " value is required in composite elements and should be a string"; + } + const auto &udf_base_it = udfBaseLookup.find(udf_base_json_it.value()); + if (udf_base_it == udfBaseLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match composite UDF field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << udf_base_json_it.value() + << " is not supported in P4Orch " + "as a valid UDF base. Supported UDF bases are: " + << P4_UDF_BASE_L2 << ", " << P4_UDF_BASE_L3 << " and " << P4_UDF_BASE_L4; + } + udf_field.base = udf_base_it->second; + // Set UDF group id + udf_field.group_id = acl_table_name + "-" + p4_match + "-" + std::to_string((*udf_fields_lookup)[p4_match].size()); + udf_field.udf_id = + udf_field.group_id + "-base" + std::to_string(udf_field.base) + "-offset" + std::to_string(udf_field.offset); + (*udf_fields_lookup)[p4_match].push_back(udf_field); + // Assign UDF group to a new ACL entry attr index if it is a new group + uint16_t index = 0; + auto udf_group_attr_index_it = udf_group_attr_index_lookup->find(udf_field.group_id); + if (udf_group_attr_index_it != udf_group_attr_index_lookup->end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Error when building UDF field for ACL talbe: duplicated UDF " + "groups found for the same index."; + } + index = (uint16_t)udf_group_attr_index_lookup->size(); + (*udf_group_attr_index_lookup)[udf_field.group_id] = index; + SWSS_LOG_INFO("ACL table built composite match field elelment %s with kind:udf", udf_field.group_id.c_str()); + return ReturnCode(); +} + +ReturnCode validateAndSetCompositeMatchFieldJson( + const nlohmann::json &aggr_match_json, const std::string &p4_match, const std::string &aggr_match_str, + const std::string &acl_table_name, + std::map> *composite_sai_match_fields_lookup, + std::map> *udf_fields_lookup, + std::map *udf_group_attr_index_lookups) +{ + auto format_str_it = aggr_match_json.find(kAclMatchFieldFormat); + if (format_str_it == aggr_match_json.end() || format_str_it.value().is_null() || !format_str_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value is required and should be a string"; + } + auto format_it = formatLookup.find(format_str_it.value()); + if (format_it == formatLookup.end() || format_it->second == Format::STRING) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value is invalid, should be one of {" << P4_FORMAT_HEX_STRING << ", " << P4_FORMAT_IPV6 << "}"; + } + auto bitwidth_it = aggr_match_json.find(kAclMatchFieldBitwidth); + if (bitwidth_it == aggr_match_json.end() || bitwidth_it.value().is_null() || !bitwidth_it.value().is_number()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldBitwidth + << " value is required and should be a number"; + } + 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. + if (elements_it == aggr_match_json.end()) + { + (*udf_fields_lookup)[p4_match]; + return ReturnCode(); + } + if (elements_it.value().is_null() || !elements_it.value().is_array()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: 'elements' value is " + "required and should be an array"; + } + for (const auto &element : elements_it.value()) + { + if (element.is_null() || !element.is_object()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: 'elements' member " + "should be an json"; + } + const auto &element_kind_it = element.find(kAclMatchFieldKind); + if (element_kind_it == element.end() || element_kind_it.value().is_null() || + !element_kind_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: composite element " + "'kind' value is required and should be a string"; + } + ReturnCode rc; + if (element_kind_it.value() == kAclMatchFieldSaiField) + { + rc = validateAndSetCompositeElementSaiFieldJson(element, p4_match, composite_sai_match_fields_lookup, + format_str_it.value()); + } + else if (element_kind_it.value() == kAclMatchFieldKindUdf) + { + if (format_str_it.value() != P4_FORMAT_HEX_STRING) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value should be HEX_STRING for UDF field"; + } + rc = validateAndSetUdfFieldJson(element, p4_match, aggr_match_str, acl_table_name, udf_fields_lookup, + udf_group_attr_index_lookups); + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: composite element " + "'kind' should be either " + << kAclMatchFieldKindUdf << " or " << kAclMatchFieldSaiField; + } + if (!rc.ok()) + return rc; + } + // elements kind should be all sai_field or all udf. + auto sai_field_it = composite_sai_match_fields_lookup->find(p4_match); + auto udf_field_it = udf_fields_lookup->find(p4_match); + if (sai_field_it != composite_sai_match_fields_lookup->end() && udf_field_it != udf_fields_lookup->end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: composite element " + "'kind' should be consistent within all elements."; + } + // The sum of bitwidth of elements should equals overall bitwidth defined + // in composite fields + uint32_t total_bitwidth = 0; + if (sai_field_it != composite_sai_match_fields_lookup->end()) + { + // IPV6_64bit(IPV6_WORD3 and IPV6_WORD2 in elements, kind:sai_field, + // format:IPV6) + if (sai_field_it->second.size() != 2) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: composite match field " + "with sai_field in element kind should have 2 elements."; + } + if (!((sai_field_it->second[0].table_attr == SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD3 && + sai_field_it->second[1].table_attr == SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD2) || + (sai_field_it->second[0].table_attr == SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD3 && + sai_field_it->second[1].table_attr == SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2))) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: For composite match " + "field " + "with element.kind == sai_field, the SAI match field " + "in elements list should be either pair {" + << P4_MATCH_DST_IPV6_WORD3 << ", " << P4_MATCH_DST_IPV6_WORD2 << "} or pair {" + << P4_MATCH_SRC_IPV6_WORD3 << ", " << P4_MATCH_SRC_IPV6_WORD2 << "} with the correct sequence"; + } + total_bitwidth = sai_field_it->second[0].bitwidth + sai_field_it->second[1].bitwidth; + } + if (udf_field_it != udf_fields_lookup->end()) + { + for (const auto &udf_field : udf_field_it->second) + { + total_bitwidth += (uint32_t)udf_field.length * BYTE_BITWIDTH; + } + } + if (total_bitwidth != composite_bitwidth) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: composite bitwidth " + "does not equal with the sum of elements bitwidth."; + } + return ReturnCode(); +} + +ReturnCode buildAclTableDefinitionMatchFieldValues(const std::map &match_field_lookup, + P4AclTableDefinition *acl_table) +{ + for (const auto &raw_match_field : match_field_lookup) + { + const auto &p4_match = fvField(raw_match_field); + const auto &aggr_match_str = fvValue(raw_match_field); + try + { + const auto &aggr_match_json = nlohmann::json::parse(aggr_match_str); + if (!aggr_match_json.is_object()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: expecting an json"; + } + + const auto &kind_it = aggr_match_json.find(kAclMatchFieldKind); + if (kind_it == aggr_match_json.end() || kind_it.value().is_null() || !kind_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: 'kind' value is " + "required and should be a string"; + } + ReturnCode rc; + if (kind_it.value() == kAclMatchFieldSaiField) + { + rc = validateAndSetSaiMatchFieldJson(aggr_match_json, p4_match, aggr_match_str, + &acl_table->sai_match_field_lookup, + &acl_table->ip_type_bit_type_lookup); + } + else if (kind_it.value() == kAclMatchFieldKindComposite) + { + rc = validateAndSetCompositeMatchFieldJson( + aggr_match_json, p4_match, aggr_match_str, acl_table->acl_table_name, + &acl_table->composite_sai_match_fields_lookup, &acl_table->udf_fields_lookup, + &acl_table->udf_group_attr_index_lookup); + } + else if (kind_it.value() == kAclMatchFieldKindUdf) + { + auto format_str_it = aggr_match_json.find(kAclMatchFieldFormat); + if (format_str_it == aggr_match_json.end() || format_str_it.value().is_null() || + !format_str_it.value().is_string()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value is required and should be a string"; + } + if (format_str_it.value() != P4_FORMAT_HEX_STRING) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: " << kAclMatchFieldFormat + << " value should be HEX_STRING for UDF field"; + } + rc = validateAndSetUdfFieldJson(aggr_match_json, p4_match, aggr_match_str, acl_table->acl_table_name, + &acl_table->udf_fields_lookup, &acl_table->udf_group_attr_index_lookup); + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: 'kind' is expecting " + "one of {" + << kAclMatchFieldKindComposite << ", " << kAclMatchFieldSaiField << ", " << kAclMatchFieldKindUdf + << "}."; + } + if (!rc.ok()) + return rc; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table match field {" << p4_match << ": " << aggr_match_str + << "} is an invalid ACL table attribute: ex" << ex.what(); + } + } + return ReturnCode(); +} + +ReturnCode buildAclTableDefinitionActionFieldValues( + const std::map> &action_field_lookup, + std::map> *aggr_sai_actions_lookup) +{ + SaiActionWithParam action_with_param; + for (const auto &aggr_action_field : action_field_lookup) + { + auto &aggr_sai_actions = (*aggr_sai_actions_lookup)[fvField(aggr_action_field)]; + for (const auto &single_action : fvValue(aggr_action_field)) + { + auto rule_action_it = aclActionLookup.find(single_action.sai_action); + if (rule_action_it == aclActionLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table action is invalid: " << single_action.sai_action; + } + action_with_param.action = rule_action_it->second; + action_with_param.param_name = single_action.p4_param_name; + aggr_sai_actions.push_back(action_with_param); + } + } + return ReturnCode(); +} + +ReturnCode buildAclTableDefinitionActionColorFieldValues( + const std::map> &action_color_lookup, + std::map> *aggr_sai_actions_lookup, + std::map> *aggr_sai_action_color_lookup) +{ + for (const auto &aggr_action_color : action_color_lookup) + { + auto &aggr_sai_actions = (*aggr_sai_actions_lookup)[fvField(aggr_action_color)]; + auto &aggr_sai_action_color = (*aggr_sai_action_color_lookup)[fvField(aggr_action_color)]; + for (const auto &action_color : fvValue(aggr_action_color)) + { + auto packet_action_it = aclPacketActionLookup.find(action_color.packet_action); + if (packet_action_it == aclPacketActionLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table packet action is invalid: " << action_color.packet_action; + } + + if (action_color.packet_color.empty()) + { + // Handle packet action without packet color, set ACL entry attribute + SaiActionWithParam action_with_param; + action_with_param.action = SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION; + action_with_param.param_name = EMPTY_STRING; + action_with_param.param_value = action_color.packet_action; + aggr_sai_actions.push_back(action_with_param); + continue; + } + + // Handle packet action with packet color, set ACL policer attribute + auto packet_color_it = aclPacketColorPolicerAttrLookup.find(action_color.packet_color); + if (packet_color_it == aclPacketColorPolicerAttrLookup.end()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL table packet color is invalid: " << action_color.packet_color; + } + aggr_sai_action_color[packet_color_it->second] = packet_action_it->second; + } + } + return ReturnCode(); +} + +bool isSetUserTrapActionInAclTableDefinition( + const std::map> &aggr_sai_actions_lookup) +{ + for (const auto &aggr_action : aggr_sai_actions_lookup) + { + for (const auto &sai_action : fvValue(aggr_action)) + { + if (sai_action.action == SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID) + return true; + } + } + return false; +} + +bool setMatchFieldIpType(const std::string &attr_value, sai_attribute_value_t *value, + const std::string &ip_type_bit_type) +{ + SWSS_LOG_ENTER(); + if (ip_type_bit_type == EMPTY_STRING) + { + SWSS_LOG_ERROR("Invalid IP type %s, bit type is not defined.", attr_value.c_str()); + return false; + } + // go/p4-ip-type + const auto &ip_type_bit_data_mask = swss::tokenize(attr_value, kDataMaskDelimiter); + if (ip_type_bit_data_mask.size() == 2 && swss::to_uint(trim(ip_type_bit_data_mask[1])) == 0) + { + SWSS_LOG_ERROR("Invalid IP_TYPE mask %s for bit type %s: ip type bit mask " + "should not be zero.", + attr_value.c_str(), ip_type_bit_type.c_str()); + return false; + } + int ip_type_bit_data = std::stoi(ip_type_bit_data_mask[0], nullptr, 0); + value->aclfield.mask.u32 = 0xFFFFFFFF; + if (ip_type_bit_type == P4_IP_TYPE_BIT_IP) + { + if (ip_type_bit_data) + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_IP; + } + else + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_NON_IP; + } + } + else if (ip_type_bit_type == P4_IP_TYPE_BIT_IPV4ANY) + { + if (ip_type_bit_data) + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_IPV4ANY; + } + else + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_NON_IPV4; + } + } + else if (ip_type_bit_type == P4_IP_TYPE_BIT_IPV6ANY) + { + if (ip_type_bit_data) + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_IPV6ANY; + } + else + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_NON_IPV6; + } + } + else if (ip_type_bit_type == P4_IP_TYPE_BIT_ARP && ip_type_bit_data) + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_ARP; + } + else if (ip_type_bit_type == P4_IP_TYPE_BIT_ARP_REQUEST && ip_type_bit_data) + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_ARP_REQUEST; + } + else if (ip_type_bit_type == P4_IP_TYPE_BIT_ARP_REPLY && ip_type_bit_data) + { + value->aclfield.data.u32 = SAI_ACL_IP_TYPE_ARP_REPLY; + } + else + { + SWSS_LOG_ERROR("Invalid IP_TYPE bit data %s for ip type %s", attr_value.c_str(), ip_type_bit_type.c_str()); + return false; + } + return true; +} + +ReturnCode setCompositeSaiMatchValue(const acl_entry_attr_union_t attr_name, const std::string &attr_value, + sai_attribute_value_t *value) +{ + try + { + const auto &tokenized_ip = swss::tokenize(attr_value, kDataMaskDelimiter); + swss::IpAddress ip_data; + swss::IpAddress ip_mask; + if (tokenized_ip.size() == 2) + { + // data & mask + ip_data = swss::IpAddress(trim(tokenized_ip[0])); + if (ip_data.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP data type should be v6 type: " << attr_value; + } + ip_mask = swss::IpAddress(trim(tokenized_ip[1])); + if (ip_mask.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "IP mask type should be v6 type: " << attr_value; + } + } + else + { + // LPM annotated value + swss::IpPrefix ip_prefix(trim(attr_value)); + if (ip_prefix.isV4()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "IP type should be v6 type: " << attr_value; + } + ip_data = ip_prefix.getIp(); + ip_mask = ip_prefix.getMask(); + } + switch (attr_name) + { + case SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD3: + case SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD3: { + // IPv6 Address 127:96 32 bits + memcpy(&value->aclfield.data.ip6[0], &ip_data.getV6Addr()[0], IPV6_SINGLE_WORD_BYTES_LENGTH); + memcpy(&value->aclfield.mask.ip6[0], &ip_mask.getV6Addr()[0], IPV6_SINGLE_WORD_BYTES_LENGTH); + break; + } + case SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD2: + case SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2: { + // IPv6 Address 95:64 32 bits + memcpy(&value->aclfield.data.ip6[IPV6_SINGLE_WORD_BYTES_LENGTH], + &ip_data.getV6Addr()[IPV6_SINGLE_WORD_BYTES_LENGTH], IPV6_SINGLE_WORD_BYTES_LENGTH); + memcpy(&value->aclfield.mask.ip6[IPV6_SINGLE_WORD_BYTES_LENGTH], + &ip_mask.getV6Addr()[IPV6_SINGLE_WORD_BYTES_LENGTH], IPV6_SINGLE_WORD_BYTES_LENGTH); + break; + } + default: { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "ACL match field " << attr_name << " is not supported in composite match field sai_field type."; + } + } + } + catch (std::exception &e) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to parse match attribute " << attr_name + << " (value: " << attr_value << "). Error:" << e.what(); + } + value->aclfield.enable = true; + return ReturnCode(); +} + +ReturnCode setUdfMatchValue(const P4UdfField &udf_field, const std::string &attr_value, sai_attribute_value_t *value, + P4UdfDataMask *udf_data_mask, uint16_t bytes_offset) +{ + if (!udf_data_mask->data.empty() || !udf_data_mask->mask.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to set UDF match field " << udf_field.udf_id << " with value " << attr_value + << " in ACL rule: the UDF: duplicated UDF value found for the same " + "UDF field."; + } + try + { + // Extract UDF field values by length(in bytes) and offset(in bytes) + const std::vector &value_and_mask = swss::tokenize(attr_value, kDataMaskDelimiter); + uint32_t data_str_offset = bytes_offset * 2, mask_str_offset = bytes_offset * 2; + const auto &data = trim(value_and_mask[0]); + if (data.size() > 2 && data[0] == '0' && (data[1] == 'x' || data[1] == 'X')) + { + data_str_offset += 2; + } + std::string mask = EMPTY_STRING; + if (value_and_mask.size() > 1) + { + mask = trim(value_and_mask[1]); + if (mask.size() > 2 && mask[0] == '0' && (mask[1] == 'x' || mask[1] == 'X')) + { + mask_str_offset += 2; + } + } + for (uint16_t i = 0; i < udf_field.length; i++) + { + // Add to udf_data uint8_t list + udf_data_mask->data.push_back(std::stoul(data.substr(data_str_offset, 2), nullptr, 16) & 0xFF); + data_str_offset += 2; + if (value_and_mask.size() > 1) + { + // Add to udf_mask uint8_t list + udf_data_mask->mask.push_back((std::stoul(mask.substr(mask_str_offset, 2), nullptr, 16)) & 0xFF); + mask_str_offset += 2; + } + else + { + udf_data_mask->mask.push_back(0xFF); + } + } + value->aclfield.data.u8list.count = udf_field.length; + value->aclfield.data.u8list.list = udf_data_mask->data.data(); + value->aclfield.mask.u8list.count = udf_field.length; + value->aclfield.mask.u8list.list = udf_data_mask->mask.data(); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to set UDF match field " << udf_field.udf_id << " with value " << attr_value + << " in ACL rule: " << ex.what(); + } + value->aclfield.enable = true; + return ReturnCode(); +} + +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) +{ + switch (attr_name) + { + case SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_PACKET_COLOR: { + return value.aclaction.parameter.s32 != old_value.aclaction.parameter.s32; + } + case SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT: { + return value.aclaction.parameter.oid != old_value.aclaction.parameter.oid; + } + case SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IP: { + return value.aclaction.parameter.ip4 != old_value.aclaction.parameter.ip4; + } + case SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS: + case SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS: { + return acl_rule.action_mirror_sessions.at(attr_name).oid != + old_acl_rule.action_mirror_sessions.at(attr_name).oid; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_MAC: { + return memcmp(value.aclaction.parameter.mac, old_value.aclaction.parameter.mac, sizeof(sai_mac_t)); + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IPV6: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6: { + return memcmp(value.aclaction.parameter.ip6, old_value.aclaction.parameter.ip6, sizeof(sai_ip6_t)); + } + + case SAI_ACL_ENTRY_ATTR_ACTION_SET_TC: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DSCP: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_ECN: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_PRI: { + return value.aclaction.parameter.u8 != old_value.aclaction.parameter.u8; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID: { + return value.aclaction.parameter.u32 != old_value.aclaction.parameter.u32; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_ID: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_SRC_PORT: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_DST_PORT: { + return value.aclaction.parameter.u16 != old_value.aclaction.parameter.u16; + } + case SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID: { + return value.aclaction.parameter.oid != old_value.aclaction.parameter.oid; + } + case SAI_ACL_ENTRY_ATTR_ACTION_FLOOD: + case SAI_ACL_ENTRY_ATTR_ACTION_DECREMENT_TTL: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_DO_NOT_LEARN: { + // parameter is not needed + return false; + } + default: { + return false; + } + } +} + +} // namespace p4orch diff --git a/orchagent/p4orch/acl_util.h b/orchagent/p4orch/acl_util.h new file mode 100644 index 000000000000..c06849506b62 --- /dev/null +++ b/orchagent/p4orch/acl_util.h @@ -0,0 +1,710 @@ +#pragma once + +#include +#include +#include +#include + +#include "json.hpp" +#include "p4orch/p4orch_util.h" +#include "return_code.h" +extern "C" +{ +#include "sai.h" +#include "saiextensions.h" +} + +namespace p4orch +{ + +// sai_acl_entry_attr_t or sai_acl_entry_attr_extensions_t +using acl_entry_attr_union_t = int32_t; +// sai_acl_table_attr_t or sai_acl_table_attr_extensions_t +using acl_table_attr_union_t = int32_t; + +// Describes the format of a value. +enum Format +{ + // Hex string, e.g. 0x0a8b. All lowercase, and always of length + // ceil(num_bits/4)+2 (1 character for every 4 bits, zero-padded to be + // divisible by 4, and 2 characters for the '0x' prefix). + HEX_STRING = 0, + // MAC address, e.g. 00:11:ab:cd:ef:22. All lowercase, and always 17 + // characters long. + MAC = 1, + // IPv4 address, e.g. 10.0.0.2. + IPV4 = 2, + // IPv6 address, e.g. fe80::21a:11ff:fe17:5f80. All lowercase, formatted + // according to RFC5952. This can be used for any bitwidth of 128 or less. If + // the bitwidth n is less than 128, then by convention only the upper n bits + // can be set. + IPV6 = 3, + // String format, only printable characters. + STRING = 4, +}; + +struct P4AclCounter +{ + sai_object_id_t counter_oid; + bool bytes_enabled; + bool packets_enabled; + P4AclCounter() : bytes_enabled(false), packets_enabled(false), counter_oid(SAI_NULL_OBJECT_ID) + { + } +}; + +struct P4AclMeter +{ + sai_object_id_t meter_oid; + bool enabled; + sai_meter_type_t type; + sai_policer_mode_t mode; + sai_uint64_t cir; + sai_uint64_t cburst; + sai_uint64_t pir; + sai_uint64_t pburst; + + std::map packet_color_actions; + + P4AclMeter() + : enabled(false), meter_oid(SAI_NULL_OBJECT_ID), cir(0), cburst(0), pir(0), pburst(0), + type(SAI_METER_TYPE_PACKETS), mode(SAI_POLICER_MODE_TR_TCM) + { + } +}; + +struct P4AclMirrorSession +{ + std::string name; + std::string key; // KeyGenerator::generateMirrorSessionKey(name) + sai_object_id_t oid; +}; + +struct P4UdfDataMask +{ + std::vector data; + std::vector mask; +}; + +struct P4AclRule +{ + sai_object_id_t acl_table_oid; + sai_object_id_t acl_entry_oid; + std::string acl_table_name; + std::string acl_rule_key; + std::string db_key; + + sai_uint32_t priority; + std::string p4_action; + std::map match_fvs; + std::map action_fvs; + P4AclMeter meter; + P4AclCounter counter; + + sai_uint32_t action_qos_queue_num; + std::string action_redirect_nexthop_key; + // SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS and + // SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS are allowed as key + std::map action_mirror_sessions; + // Stores mapping from SAI_ACL_TABLE_ATTR_USER_DEFINED_FIELD_GROUP_{number} to + // udf data and masks pairs in two uin8_t list + std::map udf_data_masks; + std::vector in_ports; + std::vector out_ports; + std::vector in_ports_oids; + std::vector out_ports_oids; +}; + +struct SaiActionWithParam +{ + acl_entry_attr_union_t action; + std::string param_name; + std::string param_value; +}; + +struct SaiMatchField +{ + acl_entry_attr_union_t entry_attr; + acl_table_attr_union_t table_attr; + uint32_t bitwidth; + Format format; +}; + +struct P4UdfField +{ + uint16_t length; // in Bytes + std::string group_id; // {ACL_TABLE_NAME}-{P4_MATCH_FIELD}-{INDEX} + std::string udf_id; // {group_id}-base{base}-offset{offset} + uint16_t offset; // in Bytes + sai_udf_base_t base; +}; + +struct P4AclTableDefinition +{ + std::string acl_table_name; + sai_object_id_t table_oid; + sai_object_id_t group_oid; + sai_object_id_t group_member_oid; + + sai_acl_stage_t stage; + sai_uint32_t size; + sai_uint32_t priority; + 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 + std::map> composite_sai_match_fields_lookup; + // go/gpins-acl-udf + // p4_match string to a list of P4UdfFields mapping + std::map> udf_fields_lookup; + // UDF group id to ACL entry attribute index mapping + std::map udf_group_attr_index_lookup; + std::map sai_match_field_lookup; + std::map ip_type_bit_type_lookup; + std::map> rule_action_field_lookup; + std::map> rule_packet_action_color_lookup; + + P4AclTableDefinition() = default; + P4AclTableDefinition(const std::string &acl_table_name, const sai_acl_stage_t stage, const uint32_t priority, + const uint32_t size, const std::string &meter_unit, const std::string &counter_unit) + : acl_table_name(acl_table_name), stage(stage), priority(priority), size(size), meter_unit(meter_unit), + counter_unit(counter_unit){}; +}; + +struct P4UserDefinedTrapHostifTableEntry +{ + sai_object_id_t user_defined_trap; + sai_object_id_t hostif_table_entry; + P4UserDefinedTrapHostifTableEntry() + : user_defined_trap(SAI_NULL_OBJECT_ID), hostif_table_entry(SAI_NULL_OBJECT_ID){}; +}; + +using acl_rule_attr_lookup_t = std::map; +using acl_table_attr_lookup_t = std::map; +using acl_table_attr_format_lookup_t = std::map; +using acl_packet_action_lookup_t = std::map; +using acl_packet_color_lookup_t = std::map; +using acl_packet_color_policer_attr_lookup_t = std::map; +using acl_ip_type_lookup_t = std::map; +using acl_ip_frag_lookup_t = std::map; +using udf_base_lookup_t = std::map; +using acl_packet_vlan_lookup_t = std::map; +using P4AclTableDefinitions = std::map; +using P4AclRuleTables = std::map>; + +#define P4_FORMAT_HEX_STRING "HEX_STRING" +#define P4_FORMAT_MAC "MAC" +#define P4_FORMAT_IPV4 "IPV4" +#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" +#define P4_MATCH_OUT_PORTS "SAI_ACL_TABLE_ATTR_FIELD_OUT_PORTS" +#define P4_MATCH_SRC_IP "SAI_ACL_TABLE_ATTR_FIELD_SRC_IP" +#define P4_MATCH_DST_IP "SAI_ACL_TABLE_ATTR_FIELD_DST_IP" +#define P4_MATCH_INNER_SRC_IP "SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IP" +#define P4_MATCH_INNER_DST_IP "SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IP" +#define P4_MATCH_SRC_IPV6 "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6" +#define P4_MATCH_DST_IPV6 "SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6" +#define P4_MATCH_INNER_SRC_IPV6 "SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IPV6" +#define P4_MATCH_INNER_DST_IPV6 "SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IPV6" +#define P4_MATCH_SRC_MAC "SAI_ACL_TABLE_ATTR_FIELD_SRC_MAC" +#define P4_MATCH_DST_MAC "SAI_ACL_TABLE_ATTR_FIELD_DST_MAC" +#define P4_MATCH_OUTER_VLAN_ID "SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID" +#define P4_MATCH_OUTER_VLAN_PRI "SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_PRI" +#define P4_MATCH_OUTER_VLAN_CFI "SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_CFI" +#define P4_MATCH_INNER_VLAN_ID "SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_ID" +#define P4_MATCH_INNER_VLAN_PRI "SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_PRI" +#define P4_MATCH_INNER_VLAN_CFI "SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_CFI" +#define P4_MATCH_L4_SRC_PORT "SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT" +#define P4_MATCH_L4_DST_PORT "SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT" +#define P4_MATCH_INNER_L4_SRC_PORT "SAI_ACL_TABLE_ATTR_FIELD_INNER_L4_SRC_PORT" +#define P4_MATCH_INNER_L4_DST_PORT "SAI_ACL_TABLE_ATTR_FIELD_INNER_L4_DST_PORT" +#define P4_MATCH_ETHER_TYPE "SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE" +#define P4_MATCH_INNER_ETHER_TYPE "SAI_ACL_TABLE_ATTR_FIELD_INNER_ETHER_TYPE" +#define P4_MATCH_IP_PROTOCOL "SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL" +#define P4_MATCH_INNER_IP_PROTOCOL "SAI_ACL_TABLE_ATTR_FIELD_INNER_IP_PROTOCOL" +#define P4_MATCH_IP_ID "SAI_ACL_TABLE_ATTR_FIELD_IP_IDENTIFICATION" +#define P4_MATCH_DSCP "SAI_ACL_TABLE_ATTR_FIELD_DSCP" +#define P4_MATCH_ECN "SAI_ACL_TABLE_ATTR_FIELD_ECN" +#define P4_MATCH_TTL "SAI_ACL_TABLE_ATTR_FIELD_TTL" +#define P4_MATCH_TOS "SAI_ACL_TABLE_ATTR_FIELD_TOS" +#define P4_MATCH_IP_FLAGS "SAI_ACL_TABLE_ATTR_FIELD_IP_FLAGS" +#define P4_MATCH_TCP_FLAGS "SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS" +#define P4_MATCH_IP_TYPE "SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE" +#define P4_MATCH_IP_FRAG "SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_FRAG" +#define P4_MATCH_IPV6_FLOW_LABEL "SAI_ACL_TABLE_ATTR_FIELD_IPV6_FLOW_LABEL" +#define P4_MATCH_TRAFFIC_CLASS "SAI_ACL_TABLE_ATTR_FIELD_TC" +#define P4_MATCH_ICMP_TYPE "SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE" +#define P4_MATCH_ICMP_CODE "SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE" +#define P4_MATCH_ICMPV6_TYPE "SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE" +#define P4_MATCH_ICMPV6_CODE "SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE" +#define P4_MATCH_PACKET_VLAN "SAI_ACL_TABLE_ATTR_FIELD_PACKET_VLAN" +#define P4_MATCH_TUNNEL_VNI "SAI_ACL_TABLE_ATTR_FIELD_TUNNEL_VNI" +#define P4_MATCH_IPV6_NEXT_HEADER "SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER" +#define P4_MATCH_DST_IPV6_WORD3 "SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD3" +#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_ACTION_PACKET_ACTION "SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION" +#define P4_ACTION_REDIRECT "SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT" +// Tunnel Endpoint IP. mandatory and valid only when redirect action is to +// SAI_BRIDGE_PORT_TYPE_TUNNEL +#define P4_ACTION_ENDPOINT_IP "SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP" +#define P4_ACTION_MIRROR_INGRESS "SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS" +#define P4_ACTION_MIRROR_EGRESS "SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS" +#define P4_ACTION_FLOOD "SAI_ACL_ENTRY_ATTR_ACTION_FLOOD" +#define P4_ACTION_DECREMENT_TTL "SAI_ACL_ENTRY_ATTR_ACTION_DECREMENT_TTL" +#define P4_ACTION_SET_TRAFFIC_CLASS "SAI_ACL_ENTRY_ATTR_ACTION_SET_TC" +#define P4_ACTION_SET_PACKET_COLOR "SAI_ACL_ENTRY_ATTR_ACTION_SET_PACKET_COLOR" +#define P4_ACTION_SET_INNER_VLAN_ID "SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID" +#define P4_ACTION_SET_INNER_VLAN_PRIORITY "SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI" +#define P4_ACTION_SET_OUTER_VLAN_ID "SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_ID" +#define P4_ACTION_SET_OUTER_VLAN_PRIORITY "SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_PRI" +#define P4_ACTION_SET_SRC_MAC "SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC" +#define P4_ACTION_SET_DST_MAC "SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_MAC" +#define P4_ACTION_SET_SRC_IP "SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP" +#define P4_ACTION_SET_DST_IP "SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IP" +#define P4_ACTION_SET_SRC_IPV6 "SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IPV6" +#define P4_ACTION_SET_DST_IPV6 "SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6" +#define P4_ACTION_SET_DSCP "SAI_ACL_ENTRY_ATTR_ACTION_SET_DSCP" +#define P4_ACTION_SET_ECN "SAI_ACL_ENTRY_ATTR_ACTION_SET_ECN" +#define P4_ACTION_SET_L4_SRC_PORT "SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_SRC_PORT" +#define P4_ACTION_SET_L4_DST_PORT "SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_DST_PORT" +#define P4_ACTION_SET_DO_NOT_LEARN "SAI_ACL_ENTRY_ATTR_ACTION_SET_DO_NOT_LEARN" +#define P4_ACTION_SET_VRF "SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF" +#define P4_ACTION_SET_QOS_QUEUE "QOS_QUEUE" + +#define P4_PACKET_ACTION_FORWARD "SAI_PACKET_ACTION_FORWARD" +#define P4_PACKET_ACTION_DROP "SAI_PACKET_ACTION_DROP" +#define P4_PACKET_ACTION_COPY "SAI_PACKET_ACTION_COPY" +#define P4_PACKET_ACTION_PUNT "SAI_PACKET_ACTION_TRAP" +#define P4_PACKET_ACTION_LOG "SAI_PACKET_ACTION_LOG" + +#define P4_PACKET_ACTION_REDIRECT "REDIRECT" + +#define P4_PACKET_COLOR_GREEN "SAI_PACKET_COLOR_GREEN" +#define P4_PACKET_COLOR_YELLOW "SAI_PACKET_COLOR_YELLOW" +#define P4_PACKET_COLOR_RED "SAI_PACKET_COLOR_RED" + +#define P4_METER_UNIT_PACKETS "PACKETS" +#define P4_METER_UNIT_BYTES "BYTES" + +#define P4_COUNTER_UNIT_PACKETS "PACKETS" +#define P4_COUNTER_UNIT_BYTES "BYTES" +#define P4_COUNTER_UNIT_BOTH "BOTH" + +// IP_TYPE encode in p4. go/p4-ip-type +#define P4_IP_TYPE_BIT_IP "IP" +#define P4_IP_TYPE_BIT_IPV4ANY "IPV4ANY" +#define P4_IP_TYPE_BIT_IPV6ANY "IPV6ANY" +#define P4_IP_TYPE_BIT_ARP "ARP" +#define P4_IP_TYPE_BIT_ARP_REQUEST "ARP_REQUEST" +#define P4_IP_TYPE_BIT_ARP_REPLY "ARP_REPLY" + +#define P4_IP_TYPE_ANY "SAI_ACL_IP_TYPE_ANY" +#define P4_IP_TYPE_IP "SAI_ACL_IP_TYPE_IP" +#define P4_IP_TYPE_NON_IP "SAI_ACL_IP_TYPE_NON_IP" +#define P4_IP_TYPE_IPV4ANY "SAI_ACL_IP_TYPE_IPV4ANY" +#define P4_IP_TYPE_NON_IPV4 "SAI_ACL_IP_TYPE_NON_IPV4" +#define P4_IP_TYPE_IPV6ANY "SAI_ACL_IP_TYPE_IPV6ANY" +#define P4_IP_TYPE_NON_IPV6 "SAI_ACL_IP_TYPE_NON_IPV6" +#define P4_IP_TYPE_ARP "SAI_ACL_IP_TYPE_ARP" +#define P4_IP_TYPE_ARP_REQUEST "SAI_ACL_IP_TYPE_ARP_REQUEST" +#define P4_IP_TYPE_ARP_REPLY "SAI_ACL_IP_TYPE_ARP_REPLY" + +#define P4_IP_FRAG_ANY "SAI_ACL_IP_FRAG_ANY" +#define P4_IP_FRAG_NON_FRAG "SAI_ACL_IP_FRAG_NON_FRAG" +#define P4_IP_FRAG_NON_FRAG_OR_HEAD "SAI_ACL_IP_FRAG_NON_FRAG_OR_HEAD" +#define P4_IP_FRAG_HEAD "SAI_ACL_IP_FRAG_HEAD" +#define P4_IP_FRAG_NON_HEAD "SAI_ACL_IP_FRAG_NON_HEAD" + +#define P4_PACKET_VLAN_UNTAG "SAI_PACKET_VLAN_UNTAG" +#define P4_PACKET_VLAN_SINGLE_OUTER_TAG "SAI_PACKET_VLAN_SINGLE_OUTER_TAG" +#define P4_PACKET_VLAN_DOUBLE_TAG "SAI_PACKET_VLAN_DOUBLE_TAG" + +#define P4_UDF_MATCH_DEFAULT "acl_default_udf_match" + +// ACL counters update interval in the COUNTERS_DB +// Value is in seconds. Should not be less than 5 seconds +// (in worst case update of 1265 counters takes almost 5 sec) +#define P4_COUNTERS_READ_INTERVAL 10 + +#define P4_COUNTER_STATS_PACKETS "packets" +#define P4_COUNTER_STATS_BYTES "bytes" +#define P4_COUNTER_STATS_GREEN_PACKETS "green_packets" +#define P4_COUNTER_STATS_GREEN_BYTES "green_bytes" +#define P4_COUNTER_STATS_YELLOW_PACKETS "yellow_packets" +#define P4_COUNTER_STATS_YELLOW_BYTES "yellow_bytes" +#define P4_COUNTER_STATS_RED_PACKETS "red_packets" +#define P4_COUNTER_STATS_RED_BYTES "red_bytes" + +#define P4_UDF_BASE_L2 "SAI_UDF_BASE_L2" +#define P4_UDF_BASE_L3 "SAI_UDF_BASE_L3" +#define P4_UDF_BASE_L4 "SAI_UDF_BASE_L4" + +#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 +#define BYTE_BITWIDTH 8 + +static const std::map formatLookup = { + {P4_FORMAT_HEX_STRING, Format::HEX_STRING}, + {P4_FORMAT_MAC, Format::MAC}, + {P4_FORMAT_IPV4, Format::IPV4}, + {P4_FORMAT_IPV6, Format::IPV6}, + {P4_FORMAT_STRING, Format::STRING}, +}; + +static const acl_table_attr_lookup_t aclMatchTableAttrLookup = { + {P4_MATCH_IN_PORT, SAI_ACL_TABLE_ATTR_FIELD_IN_PORT}, + {P4_MATCH_OUT_PORT, SAI_ACL_TABLE_ATTR_FIELD_OUT_PORT}, + {P4_MATCH_IN_PORTS, SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS}, + {P4_MATCH_OUT_PORTS, SAI_ACL_TABLE_ATTR_FIELD_OUT_PORTS}, + {P4_MATCH_SRC_MAC, SAI_ACL_TABLE_ATTR_FIELD_SRC_MAC}, + {P4_MATCH_DST_MAC, SAI_ACL_TABLE_ATTR_FIELD_DST_MAC}, + {P4_MATCH_SRC_IP, SAI_ACL_TABLE_ATTR_FIELD_SRC_IP}, + {P4_MATCH_DST_IP, SAI_ACL_TABLE_ATTR_FIELD_DST_IP}, + {P4_MATCH_INNER_SRC_IP, SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IP}, + {P4_MATCH_INNER_DST_IP, SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IP}, + {P4_MATCH_SRC_IPV6, SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6}, + {P4_MATCH_DST_IPV6, SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6}, + {P4_MATCH_INNER_SRC_IPV6, SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IPV6}, + {P4_MATCH_INNER_DST_IPV6, SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IPV6}, + {P4_MATCH_OUTER_VLAN_ID, SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID}, + {P4_MATCH_OUTER_VLAN_PRI, SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_PRI}, + {P4_MATCH_OUTER_VLAN_CFI, SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_CFI}, + {P4_MATCH_INNER_VLAN_ID, SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_ID}, + {P4_MATCH_INNER_VLAN_PRI, SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_PRI}, + {P4_MATCH_INNER_VLAN_CFI, SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_CFI}, + {P4_MATCH_L4_SRC_PORT, SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT}, + {P4_MATCH_L4_DST_PORT, SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT}, + {P4_MATCH_INNER_L4_SRC_PORT, SAI_ACL_TABLE_ATTR_FIELD_INNER_L4_SRC_PORT}, + {P4_MATCH_INNER_L4_DST_PORT, SAI_ACL_TABLE_ATTR_FIELD_INNER_L4_DST_PORT}, + {P4_MATCH_ETHER_TYPE, SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE}, + {P4_MATCH_IP_PROTOCOL, SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL}, + {P4_MATCH_INNER_IP_PROTOCOL, SAI_ACL_TABLE_ATTR_FIELD_INNER_IP_PROTOCOL}, + {P4_MATCH_IP_ID, SAI_ACL_TABLE_ATTR_FIELD_IP_IDENTIFICATION}, + {P4_MATCH_DSCP, SAI_ACL_TABLE_ATTR_FIELD_DSCP}, + {P4_MATCH_ECN, SAI_ACL_TABLE_ATTR_FIELD_ECN}, + {P4_MATCH_TTL, SAI_ACL_TABLE_ATTR_FIELD_TTL}, + {P4_MATCH_TOS, SAI_ACL_TABLE_ATTR_FIELD_TOS}, + {P4_MATCH_IP_FLAGS, SAI_ACL_TABLE_ATTR_FIELD_IP_FLAGS}, + {P4_MATCH_TCP_FLAGS, SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS}, + {P4_MATCH_IP_TYPE, SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE}, + {P4_MATCH_IP_FRAG, SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_FRAG}, + {P4_MATCH_IPV6_FLOW_LABEL, SAI_ACL_TABLE_ATTR_FIELD_IPV6_FLOW_LABEL}, + {P4_MATCH_TRAFFIC_CLASS, SAI_ACL_TABLE_ATTR_FIELD_TC}, + {P4_MATCH_ICMP_TYPE, SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE}, + {P4_MATCH_ICMP_CODE, SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE}, + {P4_MATCH_ICMPV6_TYPE, SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE}, + {P4_MATCH_ICMPV6_CODE, SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE}, + {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}, +}; + +static const acl_table_attr_format_lookup_t aclMatchTableAttrFormatLookup = { + {SAI_ACL_TABLE_ATTR_FIELD_IN_PORT, Format::STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_OUT_PORT, Format::STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS, Format::STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_OUT_PORTS, Format::STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_SRC_MAC, Format::MAC}, + {SAI_ACL_TABLE_ATTR_FIELD_DST_MAC, Format::MAC}, + {SAI_ACL_TABLE_ATTR_FIELD_SRC_IP, Format::IPV4}, + {SAI_ACL_TABLE_ATTR_FIELD_DST_IP, Format::IPV4}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IP, Format::IPV4}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IP, Format::IPV4}, + {SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6, Format::IPV6}, + {SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6, Format::IPV6}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IPV6, Format::IPV6}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IPV6, Format::IPV6}, + {SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_PRI, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_CFI, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_ID, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_PRI, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_VLAN_CFI, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_L4_SRC_PORT, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_L4_DST_PORT, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_INNER_IP_PROTOCOL, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_IP_IDENTIFICATION, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_DSCP, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ECN, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_TTL, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_TOS, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_IP_FLAGS, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_FRAG, Format::STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_IPV6_FLOW_LABEL, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_TC, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE, Format::HEX_STRING}, + {SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE, Format::HEX_STRING}, + {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}, +}; + +static const acl_table_attr_lookup_t aclCompositeMatchTableAttrLookup = { + {P4_MATCH_DST_IPV6_WORD3, SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD3}, + {P4_MATCH_DST_IPV6_WORD2, SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6_WORD2}, + {P4_MATCH_SRC_IPV6_WORD3, SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD3}, + {P4_MATCH_SRC_IPV6_WORD2, SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2}, +}; + +static const acl_rule_attr_lookup_t aclMatchEntryAttrLookup = { + {P4_MATCH_IN_PORT, SAI_ACL_ENTRY_ATTR_FIELD_IN_PORT}, + {P4_MATCH_OUT_PORT, SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORT}, + {P4_MATCH_IN_PORTS, SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS}, + {P4_MATCH_OUT_PORTS, SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS}, + {P4_MATCH_SRC_MAC, SAI_ACL_ENTRY_ATTR_FIELD_SRC_MAC}, + {P4_MATCH_DST_MAC, SAI_ACL_ENTRY_ATTR_FIELD_DST_MAC}, + {P4_MATCH_SRC_IP, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP}, + {P4_MATCH_DST_IP, SAI_ACL_ENTRY_ATTR_FIELD_DST_IP}, + {P4_MATCH_INNER_SRC_IP, SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_IP}, + {P4_MATCH_INNER_DST_IP, SAI_ACL_ENTRY_ATTR_FIELD_INNER_DST_IP}, + {P4_MATCH_SRC_IPV6, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6}, + {P4_MATCH_DST_IPV6, SAI_ACL_ENTRY_ATTR_FIELD_DST_IPV6}, + {P4_MATCH_INNER_SRC_IPV6, SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_IPV6}, + {P4_MATCH_INNER_DST_IPV6, SAI_ACL_ENTRY_ATTR_FIELD_INNER_DST_IPV6}, + {P4_MATCH_OUTER_VLAN_ID, SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_ID}, + {P4_MATCH_OUTER_VLAN_PRI, SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_PRI}, + {P4_MATCH_OUTER_VLAN_CFI, SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_CFI}, + {P4_MATCH_INNER_VLAN_ID, SAI_ACL_ENTRY_ATTR_FIELD_INNER_VLAN_ID}, + {P4_MATCH_INNER_VLAN_PRI, SAI_ACL_ENTRY_ATTR_FIELD_INNER_VLAN_PRI}, + {P4_MATCH_INNER_VLAN_CFI, SAI_ACL_ENTRY_ATTR_FIELD_INNER_VLAN_CFI}, + {P4_MATCH_L4_SRC_PORT, SAI_ACL_ENTRY_ATTR_FIELD_L4_SRC_PORT}, + {P4_MATCH_L4_DST_PORT, SAI_ACL_ENTRY_ATTR_FIELD_L4_DST_PORT}, + {P4_MATCH_INNER_L4_SRC_PORT, SAI_ACL_ENTRY_ATTR_FIELD_INNER_L4_SRC_PORT}, + {P4_MATCH_INNER_L4_DST_PORT, SAI_ACL_ENTRY_ATTR_FIELD_INNER_L4_DST_PORT}, + {P4_MATCH_ETHER_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE}, + {P4_MATCH_IP_PROTOCOL, SAI_ACL_ENTRY_ATTR_FIELD_IP_PROTOCOL}, + {P4_MATCH_INNER_IP_PROTOCOL, SAI_ACL_ENTRY_ATTR_FIELD_INNER_IP_PROTOCOL}, + {P4_MATCH_IP_ID, SAI_ACL_ENTRY_ATTR_FIELD_IP_IDENTIFICATION}, + {P4_MATCH_DSCP, SAI_ACL_ENTRY_ATTR_FIELD_DSCP}, + {P4_MATCH_ECN, SAI_ACL_ENTRY_ATTR_FIELD_ECN}, + {P4_MATCH_TTL, SAI_ACL_ENTRY_ATTR_FIELD_TTL}, + {P4_MATCH_TOS, SAI_ACL_ENTRY_ATTR_FIELD_TOS}, + {P4_MATCH_IP_FLAGS, SAI_ACL_ENTRY_ATTR_FIELD_IP_FLAGS}, + {P4_MATCH_TCP_FLAGS, SAI_ACL_ENTRY_ATTR_FIELD_TCP_FLAGS}, + {P4_MATCH_IP_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE}, + {P4_MATCH_IP_FRAG, SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_FRAG}, + {P4_MATCH_IPV6_FLOW_LABEL, SAI_ACL_ENTRY_ATTR_FIELD_IPV6_FLOW_LABEL}, + {P4_MATCH_TRAFFIC_CLASS, SAI_ACL_ENTRY_ATTR_FIELD_TC}, + {P4_MATCH_ICMP_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_ICMP_TYPE}, + {P4_MATCH_ICMP_CODE, SAI_ACL_ENTRY_ATTR_FIELD_ICMP_CODE}, + {P4_MATCH_ICMPV6_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_ICMPV6_TYPE}, + {P4_MATCH_ICMPV6_CODE, SAI_ACL_ENTRY_ATTR_FIELD_ICMPV6_CODE}, + {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}, +}; + +static const acl_rule_attr_lookup_t aclCompositeMatchEntryAttrLookup = { + {P4_MATCH_DST_IPV6_WORD3, SAI_ACL_ENTRY_ATTR_FIELD_DST_IPV6_WORD3}, + {P4_MATCH_DST_IPV6_WORD2, SAI_ACL_ENTRY_ATTR_FIELD_DST_IPV6_WORD2}, + {P4_MATCH_SRC_IPV6_WORD3, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6_WORD3}, + {P4_MATCH_SRC_IPV6_WORD2, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6_WORD2}, +}; + +static const acl_packet_action_lookup_t aclPacketActionLookup = { + {P4_PACKET_ACTION_FORWARD, SAI_PACKET_ACTION_FORWARD}, {P4_PACKET_ACTION_DROP, SAI_PACKET_ACTION_DROP}, + {P4_PACKET_ACTION_COPY, SAI_PACKET_ACTION_COPY}, {P4_PACKET_ACTION_PUNT, SAI_PACKET_ACTION_TRAP}, + {P4_PACKET_ACTION_LOG, SAI_PACKET_ACTION_LOG}, +}; + +static const acl_rule_attr_lookup_t aclActionLookup = { + {P4_ACTION_PACKET_ACTION, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION}, + {P4_ACTION_REDIRECT, SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT}, + {P4_ACTION_ENDPOINT_IP, SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP}, + {P4_ACTION_FLOOD, SAI_ACL_ENTRY_ATTR_ACTION_FLOOD}, + {P4_ACTION_MIRROR_INGRESS, SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS}, + {P4_ACTION_MIRROR_EGRESS, SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS}, + {P4_ACTION_DECREMENT_TTL, SAI_ACL_ENTRY_ATTR_ACTION_DECREMENT_TTL}, + {P4_ACTION_SET_TRAFFIC_CLASS, SAI_ACL_ENTRY_ATTR_ACTION_SET_TC}, + {P4_ACTION_SET_PACKET_COLOR, SAI_ACL_ENTRY_ATTR_ACTION_SET_PACKET_COLOR}, + {P4_ACTION_SET_INNER_VLAN_ID, SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID}, + {P4_ACTION_SET_INNER_VLAN_PRIORITY, SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI}, + {P4_ACTION_SET_OUTER_VLAN_ID, SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_ID}, + {P4_ACTION_SET_OUTER_VLAN_PRIORITY, SAI_ACL_ENTRY_ATTR_ACTION_SET_OUTER_VLAN_PRI}, + {P4_ACTION_SET_SRC_MAC, SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC}, + {P4_ACTION_SET_DST_MAC, SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_MAC}, + {P4_ACTION_SET_SRC_IP, SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP}, + {P4_ACTION_SET_DST_IP, SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IP}, + {P4_ACTION_SET_SRC_IPV6, SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IPV6}, + {P4_ACTION_SET_DST_IPV6, SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6}, + {P4_ACTION_SET_DSCP, SAI_ACL_ENTRY_ATTR_ACTION_SET_DSCP}, + {P4_ACTION_SET_ECN, SAI_ACL_ENTRY_ATTR_ACTION_SET_ECN}, + {P4_ACTION_SET_L4_SRC_PORT, SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_SRC_PORT}, + {P4_ACTION_SET_L4_DST_PORT, SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_DST_PORT}, + {P4_ACTION_SET_QOS_QUEUE, SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID}, + {P4_ACTION_SET_DO_NOT_LEARN, SAI_ACL_ENTRY_ATTR_ACTION_SET_DO_NOT_LEARN}, + {P4_ACTION_SET_VRF, SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF}, +}; + +static const acl_packet_color_policer_attr_lookup_t aclPacketColorPolicerAttrLookup = { + {P4_PACKET_COLOR_GREEN, SAI_POLICER_ATTR_GREEN_PACKET_ACTION}, + {P4_PACKET_COLOR_YELLOW, SAI_POLICER_ATTR_YELLOW_PACKET_ACTION}, + {P4_PACKET_COLOR_RED, SAI_POLICER_ATTR_RED_PACKET_ACTION}, +}; + +static const acl_packet_color_lookup_t aclPacketColorLookup = { + {P4_PACKET_COLOR_GREEN, SAI_PACKET_COLOR_GREEN}, + {P4_PACKET_COLOR_YELLOW, SAI_PACKET_COLOR_YELLOW}, + {P4_PACKET_COLOR_RED, SAI_PACKET_COLOR_RED}, +}; + +static const std::set aclIpTypeBitSet = { + P4_IP_TYPE_BIT_IP, P4_IP_TYPE_BIT_IPV4ANY, P4_IP_TYPE_BIT_IPV6ANY, + P4_IP_TYPE_BIT_ARP, P4_IP_TYPE_BIT_ARP_REQUEST, P4_IP_TYPE_BIT_ARP_REPLY, +}; + +static const acl_ip_type_lookup_t aclIpTypeLookup = { + {P4_IP_TYPE_ANY, SAI_ACL_IP_TYPE_ANY}, + {P4_IP_TYPE_IP, SAI_ACL_IP_TYPE_IP}, + {P4_IP_TYPE_NON_IP, SAI_ACL_IP_TYPE_NON_IP}, + {P4_IP_TYPE_IPV4ANY, SAI_ACL_IP_TYPE_IPV4ANY}, + {P4_IP_TYPE_NON_IPV4, SAI_ACL_IP_TYPE_NON_IPV4}, + {P4_IP_TYPE_IPV6ANY, SAI_ACL_IP_TYPE_IPV6ANY}, + {P4_IP_TYPE_NON_IPV6, SAI_ACL_IP_TYPE_NON_IPV6}, + {P4_IP_TYPE_ARP, SAI_ACL_IP_TYPE_ARP}, + {P4_IP_TYPE_ARP_REQUEST, SAI_ACL_IP_TYPE_ARP_REQUEST}, + {P4_IP_TYPE_ARP_REPLY, SAI_ACL_IP_TYPE_ARP_REPLY}, +}; + +static const acl_ip_frag_lookup_t aclIpFragLookup = { + {P4_IP_FRAG_ANY, SAI_ACL_IP_FRAG_ANY}, + {P4_IP_FRAG_NON_FRAG, SAI_ACL_IP_FRAG_NON_FRAG}, + {P4_IP_FRAG_NON_FRAG_OR_HEAD, SAI_ACL_IP_FRAG_NON_FRAG_OR_HEAD}, + {P4_IP_FRAG_HEAD, SAI_ACL_IP_FRAG_HEAD}, + {P4_IP_FRAG_NON_HEAD, SAI_ACL_IP_FRAG_NON_HEAD}, +}; + +static const acl_packet_vlan_lookup_t aclPacketVlanLookup = { + {P4_PACKET_VLAN_UNTAG, SAI_PACKET_VLAN_UNTAG}, + {P4_PACKET_VLAN_SINGLE_OUTER_TAG, SAI_PACKET_VLAN_SINGLE_OUTER_TAG}, + {P4_PACKET_VLAN_DOUBLE_TAG, SAI_PACKET_VLAN_DOUBLE_TAG}, +}; + +static const udf_base_lookup_t udfBaseLookup = { + {P4_UDF_BASE_L2, SAI_UDF_BASE_L2}, + {P4_UDF_BASE_L3, SAI_UDF_BASE_L3}, + {P4_UDF_BASE_L4, SAI_UDF_BASE_L4}, +}; + +static std::map aclCounterColoredPacketsStatsIdMap = { + {SAI_POLICER_ATTR_GREEN_PACKET_ACTION, SAI_POLICER_STAT_GREEN_PACKETS}, + {SAI_POLICER_ATTR_YELLOW_PACKET_ACTION, SAI_POLICER_STAT_YELLOW_PACKETS}, + {SAI_POLICER_ATTR_RED_PACKET_ACTION, SAI_POLICER_STAT_RED_PACKETS}, +}; + +static std::map aclCounterColoredBytesStatsIdMap = { + {SAI_POLICER_ATTR_GREEN_PACKET_ACTION, SAI_POLICER_STAT_GREEN_BYTES}, + {SAI_POLICER_ATTR_YELLOW_PACKET_ACTION, SAI_POLICER_STAT_YELLOW_BYTES}, + {SAI_POLICER_ATTR_RED_PACKET_ACTION, SAI_POLICER_STAT_RED_BYTES}, +}; + +static std::map aclCounterStatsIdNameMap = { + {SAI_POLICER_STAT_GREEN_PACKETS, P4_COUNTER_STATS_GREEN_PACKETS}, + {SAI_POLICER_STAT_YELLOW_PACKETS, P4_COUNTER_STATS_YELLOW_PACKETS}, + {SAI_POLICER_STAT_RED_PACKETS, P4_COUNTER_STATS_RED_PACKETS}, + {SAI_POLICER_STAT_GREEN_BYTES, P4_COUNTER_STATS_GREEN_BYTES}, + {SAI_POLICER_STAT_YELLOW_BYTES, P4_COUNTER_STATS_YELLOW_BYTES}, + {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, + std::vector *action_color_list); + +// Validate and set match field with kind:sai_field. Caller methods are +// responsible to verify the kind before calling this method +ReturnCode validateAndSetSaiMatchFieldJson(const nlohmann::json &match_json, const std::string &p4_match, + const std::string &aggr_match_str, + 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) +ReturnCode validateAndSetCompositeElementSaiFieldJson( + const nlohmann::json &element_match_json, const std::string &p4_match, + std::map> *composite_sai_match_fields_lookup, + const std::string &format_str = EMPTY_STRING); + +// Validate and set UDF match field with kind:udf. Caller methods are +// responsible for verifying the kind and format before calling this method +ReturnCode validateAndSetUdfFieldJson(const nlohmann::json &match_json, const std::string &p4_match, + const std::string &aggr_match_str, const std::string &acl_table_name, + std::map> *udf_fields_lookup, + std::map *udf_group_attr_index_lookup); + +// Only two cases are allowed in composite fields: +// 1. IPV6_64bit(IPV6_WORD3 and IPV6_WORD2 in elements, kind:sai_field, +// format:IPV6) +// 2. Generic UDF(UDF in elements, kind:udf, format:HEX_STRING) +ReturnCode validateAndSetCompositeMatchFieldJson( + const nlohmann::json &aggr_match_json, const std::string &p4_match, const std::string &aggr_match_str, + const std::string &acl_table_name, + std::map> *composite_sai_match_fields_lookup, + std::map> *udf_fields_lookup, + std::map *udf_group_attr_index_lookup); + +ReturnCode buildAclTableDefinitionMatchFieldValues(const std::map &match_field_lookup, + P4AclTableDefinition *acl_table); + +// Build SaiActionWithParam action map for ACL table definition +// by P4ActionParamName action map +ReturnCode buildAclTableDefinitionActionFieldValues( + const std::map> &action_field_lookup, + std::map> *aggr_sai_actions_lookup); + +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 +ReturnCode buildAclTableDefinitionActionColorFieldValues( + const std::map> &action_color_lookup, + std::map> *aggr_sai_actions_lookup, + std::map> *aggr_sai_action_color_lookup); + +// Set IP_TYPE in match field +bool setMatchFieldIpType(const std::string &attr_value, sai_attribute_value_t *value, + const std::string &ip_type_bit_type); + +// Set composite match field with sai_field type. Currently only ACL entry +// attributes listed in aclCompositeMatchTableAttrLookup are supported +ReturnCode setCompositeSaiMatchValue(const acl_entry_attr_union_t attr_name, const std::string &attr_value, + sai_attribute_value_t *value); + +// Set composite match field with sai_field type. +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. +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); +} // namespace p4orch diff --git a/orchagent/p4orch/mirror_session_manager.cpp b/orchagent/p4orch/mirror_session_manager.cpp new file mode 100644 index 000000000000..067bc5aa1a8f --- /dev/null +++ b/orchagent/p4orch/mirror_session_manager.cpp @@ -0,0 +1,726 @@ +#include "p4orch/mirror_session_manager.h" + +#include "json.hpp" +#include "p4orch/p4orch_util.h" +#include "portsorch.h" +#include "swss/logger.h" +#include "swssnet.h" + +extern PortsOrch *gPortsOrch; +extern sai_mirror_api_t *sai_mirror_api; +extern sai_object_id_t gSwitchId; + +namespace p4orch +{ + +void MirrorSessionManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + SWSS_LOG_ENTER(); + m_entries.push_back(entry); +} + +void MirrorSessionManager::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 = deserializeP4MirrorSessionAppDbEntry(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 mirror_session_key = KeyGenerator::generateMirrorSessionKey(app_db_entry.mirror_session_id); + + // Fulfill the operation. + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *mirror_session_entry = getMirrorSessionEntry(mirror_session_key); + if (mirror_session_entry == nullptr) + { + // Create new mirror session. + status = processAddRequest(app_db_entry); + } + else + { + // Modify existing mirror session. + status = processUpdateRequest(app_db_entry, mirror_session_entry); + } + } + else if (operation == DEL_COMMAND) + { + // Delete mirror session. + status = processDeleteRequest(mirror_session_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(); +} + +ReturnCodeOr MirrorSessionManager::deserializeP4MirrorSessionAppDbEntry( + const std::string &key, const std::vector &attributes) +{ + SWSS_LOG_ENTER(); + + P4MirrorSessionAppDbEntry app_db_entry = {}; + + try + { + nlohmann::json j = nlohmann::json::parse(key); + app_db_entry.mirror_session_id = j[prependMatchField(p4orch::kMirrorSessionId)]; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize mirror session id"; + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == prependParamField(p4orch::kPort)) + { + swss::Port port; + if (!gPortsOrch->getPort(value, port)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Failed to get port info for port " << QuotedVar(value); + } + if (port.m_type != Port::Type::PHY) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Port " << QuotedVar(value) << "'s type " << port.m_type + << " is not physical and is invalid as destination port for " + "mirror packet."; + } + app_db_entry.port = value; + app_db_entry.has_port = true; + } + else if (field == prependParamField(p4orch::kSrcIp)) + { + try + { + app_db_entry.src_ip = swss::IpAddress(value); + app_db_entry.has_src_ip = true; + } + 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::kDstIp)) + { + try + { + app_db_entry.dst_ip = swss::IpAddress(value); + app_db_entry.has_dst_ip = true; + } + 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::kSrcMac)) + { + try + { + app_db_entry.src_mac = swss::MacAddress(value); + app_db_entry.has_src_mac = true; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid MAC address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + } + else if (field == prependParamField(p4orch::kDstMac)) + { + try + { + app_db_entry.dst_mac = swss::MacAddress(value); + app_db_entry.has_dst_mac = true; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid MAC address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + } + else if (field == prependParamField(p4orch::kTtl)) + { + try + { + app_db_entry.ttl = static_cast(std::stoul(value, 0, /*base=*/16)); + app_db_entry.has_ttl = true; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid TTL " << QuotedVar(value) << " of field " << QuotedVar(field); + } + } + else if (field == prependParamField(p4orch::kTos)) + { + try + { + app_db_entry.tos = static_cast(std::stoul(value, 0, /*base=*/16)); + app_db_entry.has_tos = true; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid TOS " << QuotedVar(value) << " of field " << QuotedVar(field); + } + } + else if (field == p4orch::kAction) + { + if (value != p4orch::kMirrorAsIpv4Erspan) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Action value " << QuotedVar(value) << " is not mirror_as_ipv4_erspan."; + } + } + else if (field != p4orch::kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in table entry"; + } + } + + return app_db_entry; +} + +P4MirrorSessionEntry *MirrorSessionManager::getMirrorSessionEntry(const std::string &mirror_session_key) +{ + auto it = m_mirrorSessionTable.find(mirror_session_key); + + if (it == m_mirrorSessionTable.end()) + { + return nullptr; + } + else + { + return &it->second; + } +} + +ReturnCode MirrorSessionManager::processAddRequest(const P4MirrorSessionAppDbEntry &app_db_entry) +{ + SWSS_LOG_ENTER(); + + ReturnCode status; + // Check if all required fields for add operation are given in APP DB entry. + if (app_db_entry.has_port && app_db_entry.has_src_ip && app_db_entry.has_dst_ip && app_db_entry.has_src_mac && + app_db_entry.has_dst_mac && app_db_entry.has_ttl && app_db_entry.has_tos) + { + P4MirrorSessionEntry mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(app_db_entry.mirror_session_id), + /*mirror_session_oid=*/0, app_db_entry.mirror_session_id, app_db_entry.port, app_db_entry.src_ip, + app_db_entry.dst_ip, app_db_entry.src_mac, app_db_entry.dst_mac, app_db_entry.ttl, app_db_entry.tos); + status = createMirrorSession(std::move(mirror_session_entry)); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Mirror session entry with mirror_session_id " << QuotedVar(app_db_entry.mirror_session_id) + << " doesn't specify all required fields for ADD operation."; + SWSS_LOG_ERROR("%s", status.message().c_str()); + } + + return status; +} + +ReturnCode MirrorSessionManager::createMirrorSession(P4MirrorSessionEntry mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + // Check the existence of the mirror session in centralized mapper. + if (m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_MIRROR_SESSION, mirror_session_entry.mirror_session_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Mirror session with key " + << 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); + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->create_mirror_session(&mirror_session_entry.mirror_session_oid, gSwitchId, + (uint32_t)attrs.size(), attrs.data()), + "Failed to create mirror session " << QuotedVar(mirror_session_entry.mirror_session_key)); + + // On successful creation, increment ref count. + gPortsOrch->increasePortRefCount(mirror_session_entry.port); + + // Add the key to OID map to centralized mapper. + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_MIRROR_SESSION, mirror_session_entry.mirror_session_key, + mirror_session_entry.mirror_session_oid); + + // Add created entry to internal table. + m_mirrorSessionTable.emplace(mirror_session_entry.mirror_session_key, mirror_session_entry); + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::processUpdateRequest(const P4MirrorSessionAppDbEntry &app_db_entry, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + // Check the existence of the mirror session in mirror manager and centralized + // mapper. + if (existing_mirror_session_entry == nullptr) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("existing_mirror_session_entry is nullptr"); + } + if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_MIRROR_SESSION, existing_mirror_session_entry->mirror_session_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Mirror session with key " + << QuotedVar(existing_mirror_session_entry->mirror_session_key) + << " doesn't exist in centralized mapper"); + } + + P4MirrorSessionEntry mirror_session_entry_before_update(*existing_mirror_session_entry); + + // Because SAI mirror set API sets attr one at a time, it is possible attr + // updates fail in the middle. Up on failure, all successful operations need + // to be undone. + ReturnCode ret; + bool update_fail_in_middle = false; + if (!update_fail_in_middle && app_db_entry.has_port) + { + ret = setPort(app_db_entry.port, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + if (!update_fail_in_middle && app_db_entry.has_src_ip) + { + ret = setSrcIp(app_db_entry.src_ip, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + if (!update_fail_in_middle && app_db_entry.has_dst_ip) + { + ret = setDstIp(app_db_entry.dst_ip, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + if (!update_fail_in_middle && app_db_entry.has_src_mac) + { + ret = setSrcMac(app_db_entry.src_mac, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + if (!update_fail_in_middle && app_db_entry.has_dst_mac) + { + ret = setDstMac(app_db_entry.dst_mac, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + if (!update_fail_in_middle && app_db_entry.has_ttl) + { + ret = setTtl(app_db_entry.ttl, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + if (!update_fail_in_middle && app_db_entry.has_tos) + { + ret = setTos(app_db_entry.tos, existing_mirror_session_entry); + if (!ret.ok()) + update_fail_in_middle = true; + } + + if (update_fail_in_middle) + { + ReturnCode status = setMirrorSessionEntry(mirror_session_entry_before_update, existing_mirror_session_entry); + if (!status.ok()) + { + ret << "Failed to recover mirror session entry to the state before " + "update operation."; + SWSS_RAISE_CRITICAL_STATE("Failed to recover mirror session entry to the state before update " + "operation."); + } + } + + return ret; +} + +ReturnCode MirrorSessionManager::setPort(const std::string &new_port_name, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_port_name == existing_mirror_session_entry->port) + { + return ReturnCode(); + } + + swss::Port new_port; + if (!gPortsOrch->getPort(new_port_name, new_port)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Failed to get port info for port " << QuotedVar(new_port_name)); + } + if (new_port.m_type != Port::Type::PHY) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Port " << QuotedVar(new_port.m_alias) << "'s type " << new_port.m_type + << " is not physical and is invalid as destination " + "port for mirror packet."); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_MONITOR_PORT; + attr.value.oid = new_port.m_port_id; + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new port " << QuotedVar(new_port.m_alias) << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update ref count. + gPortsOrch->decreasePortRefCount(existing_mirror_session_entry->port); + gPortsOrch->increasePortRefCount(new_port.m_alias); + + // Update the entry in table + existing_mirror_session_entry->port = new_port_name; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setSrcIp(const swss::IpAddress &new_src_ip, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_src_ip == existing_mirror_session_entry->src_ip) + { + return ReturnCode(); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS; + swss::copy(attr.value.ipaddr, new_src_ip); + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new src_ip " << QuotedVar(new_src_ip.to_string()) << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update the entry in table + existing_mirror_session_entry->src_ip = new_src_ip; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setDstIp(const swss::IpAddress &new_dst_ip, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_dst_ip == existing_mirror_session_entry->dst_ip) + { + return ReturnCode(); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS; + swss::copy(attr.value.ipaddr, new_dst_ip); + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new dst_ip " << QuotedVar(new_dst_ip.to_string()) << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update the entry in table + existing_mirror_session_entry->dst_ip = new_dst_ip; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setSrcMac(const swss::MacAddress &new_src_mac, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_src_mac == existing_mirror_session_entry->src_mac) + { + return ReturnCode(); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS; + memcpy(attr.value.mac, new_src_mac.getMac(), sizeof(sai_mac_t)); + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new src_mac " << QuotedVar(new_src_mac.to_string()) << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update the entry in table + existing_mirror_session_entry->src_mac = new_src_mac; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setDstMac(const swss::MacAddress &new_dst_mac, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_dst_mac == existing_mirror_session_entry->dst_mac) + { + return ReturnCode(); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS; + memcpy(attr.value.mac, new_dst_mac.getMac(), sizeof(sai_mac_t)); + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new dst_mac " << QuotedVar(new_dst_mac.to_string()) << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update the entry in table + existing_mirror_session_entry->dst_mac = new_dst_mac; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setTtl(uint8_t new_ttl, P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_ttl == existing_mirror_session_entry->ttl) + { + return ReturnCode(); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_TTL; + attr.value.u8 = new_ttl; + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new ttl " << new_ttl << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update the entry in table + existing_mirror_session_entry->ttl = new_ttl; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setTos(uint8_t new_tos, P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + if (new_tos == existing_mirror_session_entry->tos) + { + return ReturnCode(); + } + + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_TOS; + attr.value.u8 = new_tos; + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_mirror_api->set_mirror_session_attribute(existing_mirror_session_entry->mirror_session_oid, &attr), + "Failed to set new tos " << new_tos << " for mirror session " + << QuotedVar(existing_mirror_session_entry->mirror_session_key)); + + // Update the entry in table + existing_mirror_session_entry->tos = new_tos; + + return ReturnCode(); +} + +ReturnCode MirrorSessionManager::setMirrorSessionEntry(const P4MirrorSessionEntry &intent_mirror_session_entry, + P4MirrorSessionEntry *existing_mirror_session_entry) +{ + SWSS_LOG_ENTER(); + + ReturnCode status; + + if (intent_mirror_session_entry.port != existing_mirror_session_entry->port) + { + status = setPort(intent_mirror_session_entry.port, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + if (intent_mirror_session_entry.src_ip != existing_mirror_session_entry->src_ip) + { + status = setSrcIp(intent_mirror_session_entry.src_ip, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + if (intent_mirror_session_entry.dst_ip != existing_mirror_session_entry->dst_ip) + { + status = setDstIp(intent_mirror_session_entry.dst_ip, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + if (intent_mirror_session_entry.src_mac != existing_mirror_session_entry->src_mac) + { + status = setSrcMac(intent_mirror_session_entry.src_mac, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + if (intent_mirror_session_entry.dst_mac != existing_mirror_session_entry->dst_mac) + { + status = setDstMac(intent_mirror_session_entry.dst_mac, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + if (intent_mirror_session_entry.ttl != existing_mirror_session_entry->ttl) + { + status = setTtl(intent_mirror_session_entry.ttl, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + if (intent_mirror_session_entry.tos != existing_mirror_session_entry->tos) + { + status = setTos(intent_mirror_session_entry.tos, existing_mirror_session_entry); + if (!status.ok()) + return status; + } + + return status; +} + +ReturnCode MirrorSessionManager::processDeleteRequest(const std::string &mirror_session_key) +{ + SWSS_LOG_ENTER(); + + const P4MirrorSessionEntry *mirror_session_entry = getMirrorSessionEntry(mirror_session_key); + if (mirror_session_entry == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Mirror session with key " << QuotedVar(mirror_session_key) + << " does not exist in mirror session manager"); + } + + // Check if there is anything referring to the mirror session before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_MIRROR_SESSION, mirror_session_key, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count for mirror session " + << QuotedVar(mirror_session_key)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_IN_USE) + << "Mirror session " << QuotedVar(mirror_session_entry->mirror_session_key) + << " referenced by other objects (ref_count = " << ref_count); + } + + // Call SAI API. + CHECK_ERROR_AND_LOG_AND_RETURN(sai_mirror_api->remove_mirror_session(mirror_session_entry->mirror_session_oid), + "Failed to remove mirror session " + << QuotedVar(mirror_session_entry->mirror_session_key)); + + // On successful deletion, decrement ref count. + gPortsOrch->decreasePortRefCount(mirror_session_entry->port); + + // Delete the key to OID map from centralized mapper. + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_MIRROR_SESSION, mirror_session_entry->mirror_session_key); + + // Delete entry from internal table. + m_mirrorSessionTable.erase(mirror_session_entry->mirror_session_key); + + return ReturnCode(); +} + +} // namespace p4orch diff --git a/orchagent/p4orch/mirror_session_manager.h b/orchagent/p4orch/mirror_session_manager.h new file mode 100644 index 000000000000..c41dc07eb384 --- /dev/null +++ b/orchagent/p4orch/mirror_session_manager.h @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include + +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "p4orch/p4orch_util.h" +#include "response_publisher_interface.h" +#include "return_code.h" +#include "swss/ipaddress.h" +#include "swss/macaddress.h" +#include "swss/rediscommand.h" +extern "C" +{ +#include "sai.h" +} + +#define MIRROR_SESSION_DEFAULT_IP_HDR_VER 4 +#define GRE_PROTOCOL_ERSPAN 0x88be + +namespace p4orch +{ +namespace test +{ +class MirrorSessionManagerTest; +} // namespace test + +struct P4MirrorSessionEntry +{ + P4MirrorSessionEntry(const std::string &mirror_session_key, sai_object_id_t mirror_session_oid, + const std::string &mirror_session_id, const std::string &port, const swss::IpAddress &src_ip, + const swss::IpAddress &dst_ip, const swss::MacAddress &src_mac, + const swss::MacAddress &dst_mac, uint8_t ttl, uint8_t tos) + : mirror_session_key(mirror_session_key), mirror_session_oid(mirror_session_oid), + mirror_session_id(mirror_session_id), port(port), src_ip(src_ip), dst_ip(dst_ip), src_mac(src_mac), + dst_mac(dst_mac), ttl(ttl), tos(tos) + { + } + + P4MirrorSessionEntry(const P4MirrorSessionEntry &) = default; + + bool operator==(const P4MirrorSessionEntry &entry) const + { + return mirror_session_key == entry.mirror_session_key && mirror_session_oid == entry.mirror_session_oid && + mirror_session_id == entry.mirror_session_id && port == entry.port && src_ip == entry.src_ip && + dst_ip == entry.dst_ip && src_mac == entry.src_mac && dst_mac == entry.dst_mac && ttl == entry.ttl && + tos == entry.tos; + } + + std::string mirror_session_key; + + // SAI OID associated with this entry. + sai_object_id_t mirror_session_oid = 0; + + // Match field in table + std::string mirror_session_id; + // Action parameters + std::string port; + swss::IpAddress src_ip; + swss::IpAddress dst_ip; + swss::MacAddress src_mac; + swss::MacAddress dst_mac; + uint8_t ttl = 0; + uint8_t tos = 0; +}; + +// MirrorSessionManager is responsible for programming mirror session intents in +// APPL_DB:FIXED_MIRROR_SESSION_TABLE to ASIC_DB. +class MirrorSessionManager : public ObjectManagerInterface +{ + public: + MirrorSessionManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + + void drain() override; + + private: + ReturnCodeOr deserializeP4MirrorSessionAppDbEntry( + const std::string &key, const std::vector &attributes); + + P4MirrorSessionEntry *getMirrorSessionEntry(const std::string &mirror_session_key); + + ReturnCode processAddRequest(const P4MirrorSessionAppDbEntry &app_db_entry); + ReturnCode createMirrorSession(P4MirrorSessionEntry mirror_session_entry); + + ReturnCode processUpdateRequest(const P4MirrorSessionAppDbEntry &app_db_entry, + P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setPort(const std::string &new_port, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setSrcIp(const swss::IpAddress &new_src_ip, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setDstIp(const swss::IpAddress &new_dst_ip, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setSrcMac(const swss::MacAddress &new_src_mac, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setDstMac(const swss::MacAddress &new_dst_mac, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setTtl(uint8_t new_ttl, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setTos(uint8_t new_tos, P4MirrorSessionEntry *existing_mirror_session_entry); + ReturnCode setMirrorSessionEntry(const P4MirrorSessionEntry &intent_mirror_session_entry, + P4MirrorSessionEntry *existing_mirror_session_entry); + + ReturnCode processDeleteRequest(const std::string &mirror_session_key); + + std::unordered_map m_mirrorSessionTable; + + // Owners of pointers below must outlive this class's instance. + P4OidMapper *m_p4OidMapper; + ResponsePublisherInterface *m_publisher; + std::deque m_entries; + + // For test purpose only + friend class p4orch::test::MirrorSessionManagerTest; +}; + +} // namespace p4orch diff --git a/orchagent/p4orch/neighbor_manager.cpp b/orchagent/p4orch/neighbor_manager.cpp new file mode 100644 index 000000000000..059aa766988b --- /dev/null +++ b/orchagent/p4orch/neighbor_manager.cpp @@ -0,0 +1,376 @@ +#include "p4orch/neighbor_manager.h" + +#include +#include +#include + +#include "crmorch.h" +#include "json.hpp" +#include "logger.h" +#include "orch.h" +#include "p4orch/p4orch_util.h" +#include "swssnet.h" +extern "C" +{ +#include "sai.h" +} + +extern sai_object_id_t gSwitchId; + +extern sai_neighbor_api_t *sai_neighbor_api; + +extern CrmOrch *gCrmOrch; + +P4NeighborEntry::P4NeighborEntry(const std::string &router_interface_id, const swss::IpAddress &ip_address, + const swss::MacAddress &mac_address) +{ + SWSS_LOG_ENTER(); + + router_intf_id = router_interface_id; + neighbor_id = ip_address; + dst_mac_address = mac_address; + + router_intf_key = KeyGenerator::generateRouterInterfaceKey(router_intf_id); + neighbor_key = KeyGenerator::generateNeighborKey(router_intf_id, neighbor_id); +} + +ReturnCodeOr NeighborManager::deserializeNeighborEntry( + const std::string &key, const std::vector &attributes) +{ + SWSS_LOG_ENTER(); + + P4NeighborAppDbEntry app_db_entry = {}; + std::string ip_address; + try + { + nlohmann::json j = nlohmann::json::parse(key); + app_db_entry.router_intf_id = j[prependMatchField(p4orch::kRouterInterfaceId)]; + ip_address = j[prependMatchField(p4orch::kNeighborId)]; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize key"; + } + try + { + app_db_entry.neighbor_id = swss::IpAddress(ip_address); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid IP address " << QuotedVar(ip_address) << " of field " + << QuotedVar(prependMatchField(p4orch::kNeighborId)); + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == prependParamField(p4orch::kDstMac)) + { + try + { + app_db_entry.dst_mac_address = swss::MacAddress(value); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid MAC address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + app_db_entry.is_set_dst_mac = true; + } + else if (field != p4orch::kAction && field != p4orch::kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in table entry"; + } + } + + return app_db_entry; +} + +ReturnCode NeighborManager::validateNeighborAppDbEntry(const P4NeighborAppDbEntry &app_db_entry) +{ + SWSS_LOG_ENTER(); + // Perform generic APP DB entry validations. Operation specific validations + // will be done by the respective request process methods. + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_intf_id); + if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Router interface id " << QuotedVar(app_db_entry.router_intf_id) << " does not exist"; + } + + if ((app_db_entry.is_set_dst_mac) && (app_db_entry.dst_mac_address.to_string() == "00:00:00:00:00:00")) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid dst mac address " << QuotedVar(app_db_entry.dst_mac_address.to_string()); + } + + return ReturnCode(); +} + +P4NeighborEntry *NeighborManager::getNeighborEntry(const std::string &neighbor_key) +{ + SWSS_LOG_ENTER(); + + if (m_neighborTable.find(neighbor_key) == m_neighborTable.end()) + return nullptr; + + return &m_neighborTable[neighbor_key]; +} + +ReturnCode NeighborManager::createNeighbor(P4NeighborEntry &neighbor_entry) +{ + SWSS_LOG_ENTER(); + + const std::string &neighbor_key = neighbor_entry.neighbor_key; + if (getNeighborEntry(neighbor_key) != nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_EXISTS) + << "Neighbor entry with key " << QuotedVar(neighbor_key) << " already exists"); + } + + if (m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Neighbor entry with key " << QuotedVar(neighbor_key) + << " 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; + + 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()), + "Failed to create neighbor with key " << QuotedVar(neighbor_key)); + + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key); + if (neighbor_entry.neighbor_id.isV4()) + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEIGHBOR); + } + else + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEIGHBOR); + } + + m_neighborTable[neighbor_key] = neighbor_entry; + m_p4OidMapper->setDummyOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key); + return ReturnCode(); +} + +ReturnCode NeighborManager::removeNeighbor(const std::string &neighbor_key) +{ + SWSS_LOG_ENTER(); + + auto *neighbor_entry = getNeighborEntry(neighbor_key); + if (neighbor_entry == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Neighbor with key " << QuotedVar(neighbor_key) << " does not exist"); + } + + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count of neighbor with key " + << QuotedVar(neighbor_key)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Neighbor with key " << QuotedVar(neighbor_key) + << " referenced by other objects (ref_count = " << ref_count << ")"); + } + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_neighbor_api->remove_neighbor_entry(&neighbor_entry->neigh_entry), + "Failed to remove neighbor with key " << QuotedVar(neighbor_key)); + + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, neighbor_entry->router_intf_key); + if (neighbor_entry->neighbor_id.isV4()) + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEIGHBOR); + } + else + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEIGHBOR); + } + + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key); + m_neighborTable.erase(neighbor_key); + return ReturnCode(); +} + +ReturnCode NeighborManager::setDstMacAddress(P4NeighborEntry *neighbor_entry, const swss::MacAddress &mac_address) +{ + SWSS_LOG_ENTER(); + + if (neighbor_entry->dst_mac_address == mac_address) + return ReturnCode(); + + sai_attribute_t neigh_attr; + neigh_attr.id = SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS; + memcpy(neigh_attr.value.mac, mac_address.getMac(), sizeof(sai_mac_t)); + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_neighbor_api->set_neighbor_entry_attribute(&neighbor_entry->neigh_entry, &neigh_attr), + "Failed to set mac address " << QuotedVar(mac_address.to_string()) << " for neighbor with key " + << QuotedVar(neighbor_entry->neighbor_key)); + + neighbor_entry->dst_mac_address = mac_address; + return ReturnCode(); +} + +ReturnCode NeighborManager::processAddRequest(const P4NeighborAppDbEntry &app_db_entry, const std::string &neighbor_key) +{ + SWSS_LOG_ENTER(); + + // Perform operation specific validations. + if (!app_db_entry.is_set_dst_mac) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << p4orch::kDstMac + << " is mandatory to create neighbor entry. Failed to create " + "neighbor with key " + << QuotedVar(neighbor_key)); + } + + P4NeighborEntry neighbor_entry(app_db_entry.router_intf_id, app_db_entry.neighbor_id, app_db_entry.dst_mac_address); + auto status = createNeighbor(neighbor_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create neighbor with key %s", QuotedVar(neighbor_key).c_str()); + } + + return status; +} + +ReturnCode NeighborManager::processUpdateRequest(const P4NeighborAppDbEntry &app_db_entry, + P4NeighborEntry *neighbor_entry) +{ + SWSS_LOG_ENTER(); + + if (app_db_entry.is_set_dst_mac) + { + auto status = setDstMacAddress(neighbor_entry, app_db_entry.dst_mac_address); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to set destination mac address for neighbor with key %s", + QuotedVar(neighbor_entry->neighbor_key).c_str()); + return status; + } + } + + return ReturnCode(); +} + +ReturnCode NeighborManager::processDeleteRequest(const std::string &neighbor_key) +{ + SWSS_LOG_ENTER(); + + auto status = removeNeighbor(neighbor_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove neighbor with key %s", QuotedVar(neighbor_key).c_str()); + } + + return status; +} + +void NeighborManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void NeighborManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string db_key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &db_key); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + ReturnCode status; + auto app_db_entry_or = deserializeNeighborEntry(db_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 + ":" + db_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; + + status = validateNeighborAppDbEntry(app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for Neighbor APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + db_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; + } + + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(app_db_entry.router_intf_id, app_db_entry.neighbor_id); + + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *neighbor_entry = getNeighborEntry(neighbor_key); + if (neighbor_entry == nullptr) + { + // Create neighbor + status = processAddRequest(app_db_entry, neighbor_key); + } + else + { + // Modify existing neighbor + status = processUpdateRequest(app_db_entry, neighbor_entry); + } + } + else if (operation == DEL_COMMAND) + { + // Delete neighbor + status = processDeleteRequest(neighbor_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(); +} diff --git a/orchagent/p4orch/neighbor_manager.h b/orchagent/p4orch/neighbor_manager.h new file mode 100644 index 000000000000..2ede9de76378 --- /dev/null +++ b/orchagent/p4orch/neighbor_manager.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include + +#include "ipaddress.h" +#include "macaddress.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" +} + +struct P4NeighborEntry +{ + std::string router_intf_id; + swss::IpAddress neighbor_id; + swss::MacAddress dst_mac_address; + std::string router_intf_key; + std::string neighbor_key; + sai_neighbor_entry_t neigh_entry; + + P4NeighborEntry() = default; + P4NeighborEntry(const std::string &router_interface_id, const swss::IpAddress &ip_address, + const swss::MacAddress &mac_address); +}; + +// P4NeighborTable: Neighbor key string, P4NeighborEntry +typedef std::unordered_map P4NeighborTable; + +class NeighborManager : public ObjectManagerInterface +{ + public: + NeighborManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + virtual ~NeighborManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + private: + ReturnCodeOr deserializeNeighborEntry(const std::string &key, + const std::vector &attributes); + ReturnCode validateNeighborAppDbEntry(const P4NeighborAppDbEntry &app_db_entry); + P4NeighborEntry *getNeighborEntry(const std::string &neighbor_key); + ReturnCode createNeighbor(P4NeighborEntry &neighbor_entry); + ReturnCode removeNeighbor(const std::string &neighbor_key); + ReturnCode setDstMacAddress(P4NeighborEntry *neighbor_entry, const swss::MacAddress &mac_address); + 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); + + P4OidMapper *m_p4OidMapper; + P4NeighborTable m_neighborTable; + ResponsePublisherInterface *m_publisher; + std::deque m_entries; + + friend class NeighborManagerTest; +}; diff --git a/orchagent/p4orch/next_hop_manager.cpp b/orchagent/p4orch/next_hop_manager.cpp new file mode 100644 index 000000000000..3e2d9ff54866 --- /dev/null +++ b/orchagent/p4orch/next_hop_manager.cpp @@ -0,0 +1,333 @@ +#include "p4orch/next_hop_manager.h" + +#include +#include +#include + +#include "crmorch.h" +#include "ipaddress.h" +#include "json.hpp" +#include "logger.h" +#include "p4orch/p4orch_util.h" +#include "swssnet.h" +extern "C" +{ +#include "sai.h" +} + +extern sai_object_id_t gSwitchId; +extern sai_next_hop_api_t *sai_next_hop_api; +extern CrmOrch *gCrmOrch; + +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) +{ + SWSS_LOG_ENTER(); + next_hop_key = KeyGenerator::generateNextHopKey(next_hop_id); +} + +void NextHopManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void NextHopManager::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 = deserializeP4NextHopAppDbEntry(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 next_hop_key = KeyGenerator::generateNextHopKey(app_db_entry.next_hop_id); + + // Fulfill the operation. + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *next_hop_entry = getNextHopEntry(next_hop_key); + if (next_hop_entry == nullptr) + { + // Create new next hop. + status = processAddRequest(app_db_entry); + } + else + { + // Modify existing next hop. + status = processUpdateRequest(app_db_entry, next_hop_entry); + } + } + else if (operation == DEL_COMMAND) + { + // Delete next hop. + status = processDeleteRequest(next_hop_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(); +} + +P4NextHopEntry *NextHopManager::getNextHopEntry(const std::string &next_hop_key) +{ + SWSS_LOG_ENTER(); + + auto it = m_nextHopTable.find(next_hop_key); + + if (it == m_nextHopTable.end()) + { + return nullptr; + } + else + { + return &it->second; + } +} + +ReturnCodeOr NextHopManager::deserializeP4NextHopAppDbEntry( + const std::string &key, const std::vector &attributes) +{ + SWSS_LOG_ENTER(); + + P4NextHopAppDbEntry app_db_entry = {}; + + try + { + nlohmann::json j = nlohmann::json::parse(key); + app_db_entry.next_hop_id = j[prependMatchField(p4orch::kNexthopId)]; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize next hop 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; + app_db_entry.is_set_router_interface_id = true; + } + else if (field == prependParamField(p4orch::kNeighborId)) + { + try + { + app_db_entry.neighbor_id = swss::IpAddress(value); + } + catch (std::exception &ex) + { + 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) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in table entry"; + } + } + + return app_db_entry; +} + +ReturnCode NextHopManager::processAddRequest(const P4NextHopAppDbEntry &app_db_entry) +{ + SWSS_LOG_ENTER(); + + P4NextHopEntry next_hop_entry(app_db_entry.next_hop_id, app_db_entry.router_interface_id, app_db_entry.neighbor_id); + auto status = createNextHop(next_hop_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create next hop with key %s", QuotedVar(next_hop_entry.next_hop_key).c_str()); + } + return status; +} + +ReturnCode NextHopManager::createNextHop(P4NextHopEntry &next_hop_entry) +{ + SWSS_LOG_ENTER(); + + // Check the existence of the next hop in next hop manager and centralized + // mapper. + if (getNextHopEntry(next_hop_entry.next_hop_key) != nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_EXISTS) + << "Next hop with key " << QuotedVar(next_hop_entry.next_hop_key) + << " already exists in next hop manager"); + } + if (m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_entry.next_hop_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Next hop with key " << QuotedVar(next_hop_entry.next_hop_key) + << " 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)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Router intf " << QuotedVar(next_hop_entry.router_interface_id) << " does not exist"); + } + + // 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); + if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key)) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Neighbor with key " << QuotedVar(neighbor_key) + << " 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); + + // 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)); + + // 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()) + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEXTHOP); + } + else + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEXTHOP); + } + + // Add created entry to internal table. + m_nextHopTable.emplace(next_hop_entry.next_hop_key, next_hop_entry); + + // Add the key to OID map to centralized mapper. + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_entry.next_hop_key, next_hop_entry.next_hop_oid); + + return ReturnCode(); +} + +ReturnCode NextHopManager::processUpdateRequest(const P4NextHopAppDbEntry &app_db_entry, P4NextHopEntry *next_hop_entry) +{ + SWSS_LOG_ENTER(); + + ReturnCode status = ReturnCode(StatusCode::SWSS_RC_UNIMPLEMENTED) + << "Currently next hop doesn't support update. Next hop key " + << QuotedVar(next_hop_entry->next_hop_key); + SWSS_LOG_ERROR("%s", status.message().c_str()); + return status; +} + +ReturnCode NextHopManager::processDeleteRequest(const std::string &next_hop_key) +{ + SWSS_LOG_ENTER(); + + auto status = removeNextHop(next_hop_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove next hop with key %s", QuotedVar(next_hop_key).c_str()); + } + + return status; +} + +ReturnCode NextHopManager::removeNextHop(const std::string &next_hop_key) +{ + SWSS_LOG_ENTER(); + + // Check the existence of the next hop in next hop manager and centralized + // mapper. + auto *next_hop_entry = getNextHopEntry(next_hop_key); + if (next_hop_entry == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Next hop with key " << QuotedVar(next_hop_key) + << " does not exist in next hop manager"); + } + + // Check if there is anything referring to the next hop before deletion. + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count for next hop " + << QuotedVar(next_hop_key)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Next hop " << QuotedVar(next_hop_entry->next_hop_key) + << " referenced by other objects (ref_count = " << ref_count); + } + + // Call SAI API. + 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)); + m_p4OidMapper->decreaseRefCount( + SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, + KeyGenerator::generateNeighborKey(next_hop_entry->router_interface_id, next_hop_entry->neighbor_id)); + if (next_hop_entry->neighbor_id.isV4()) + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEXTHOP); + } + else + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEXTHOP); + } + + // Remove the key to OID map to centralized mapper. + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key); + + // Remove the entry from internal table. + m_nextHopTable.erase(next_hop_key); + + return ReturnCode(); +} diff --git a/orchagent/p4orch/next_hop_manager.h b/orchagent/p4orch/next_hop_manager.h new file mode 100644 index 000000000000..7b4a318f87a2 --- /dev/null +++ b/orchagent/p4orch/next_hop_manager.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include + +#include "ipaddress.h" +#include "orch.h" +#include "p4orch/neighbor_manager.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" +} + +// P4NextHopEntry holds NextHopManager's internal cache of P4 next hop entry. +struct P4NextHopEntry +{ + // Key of this entry, built from next_hop_id. + std::string next_hop_key; + + // Fields from P4 table. + // Match + std::string next_hop_id; + // Action + std::string router_interface_id; + swss::IpAddress neighbor_id; + + // SAI OID associated with this entry. + sai_object_id_t next_hop_oid = 0; + + P4NextHopEntry(const std::string &next_hop_id, const std::string &router_interface_id, + const swss::IpAddress &neighbor_id); +}; + +// NextHopManager listens to changes in table APP_P4RT_NEXTHOP_TABLE_NAME and +// creates/updates/deletes next hop SAI object accordingly. +class NextHopManager : public ObjectManagerInterface +{ + public: + NextHopManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + + virtual ~NextHopManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + private: + // Gets the internal cached next hop entry by its key. + // Return nullptr if corresponding next hop entry is not cached. + P4NextHopEntry *getNextHopEntry(const std::string &next_hop_key); + + // Deserializes an entry from table APP_P4RT_NEXTHOP_TABLE_NAME. + ReturnCodeOr deserializeP4NextHopAppDbEntry( + const std::string &key, const std::vector &attributes); + + // Processes add operation for an entry. + ReturnCode processAddRequest(const P4NextHopAppDbEntry &app_db_entry); + + // Creates an next hop in the next hop table. Return true on success. + ReturnCode createNextHop(P4NextHopEntry &next_hop_entry); + + // Processes update operation for an entry. + ReturnCode processUpdateRequest(const P4NextHopAppDbEntry &app_db_entry, P4NextHopEntry *next_hop_entry); + + // Processes delete operation for an entry. + ReturnCode processDeleteRequest(const std::string &next_hop_key); + + // Deletes an next hop in the next hop table. Return true on success. + ReturnCode removeNextHop(const std::string &next_hop_key); + + // m_nextHopTable: next_hop_key, P4NextHopEntry + std::unordered_map m_nextHopTable; + + // Owners of pointers below must outlive this class's instance. + P4OidMapper *m_p4OidMapper; + ResponsePublisherInterface *m_publisher; + std::deque m_entries; + + friend class NextHopManagerTest; +}; diff --git a/orchagent/p4orch/object_manager_interface.h b/orchagent/p4orch/object_manager_interface.h new file mode 100644 index 000000000000..ec9775f8e4d2 --- /dev/null +++ b/orchagent/p4orch/object_manager_interface.h @@ -0,0 +1,15 @@ +#pragma once + +#include "orch.h" + +class ObjectManagerInterface +{ + public: + virtual ~ObjectManagerInterface() = default; + + // Enqueues an entry into the manager + virtual void enqueue(const swss::KeyOpFieldsValuesTuple &entry) = 0; + + // Processes all entries in the queue + virtual void drain() = 0; +}; diff --git a/orchagent/p4orch/p4oidmapper.cpp b/orchagent/p4orch/p4oidmapper.cpp new file mode 100644 index 000000000000..f4ff6e34332b --- /dev/null +++ b/orchagent/p4orch/p4oidmapper.cpp @@ -0,0 +1,180 @@ +#include "p4oidmapper.h" + +#include +#include + +#include "logger.h" +#include "sai_serialize.h" + +extern "C" +{ +#include "sai.h" +} + +namespace +{ + +std::string convertToDBField(_In_ const sai_object_type_t object_type, _In_ const std::string &key) +{ + return sai_serialize_object_type(object_type) + ":" + key; +} + +} // namespace + +P4OidMapper::P4OidMapper() : m_db("APPL_STATE_DB", 0), m_table(&m_db, "P4RT_KEY_TO_OID") +{ +} + +bool P4OidMapper::setOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, _In_ sai_object_id_t oid, + _In_ uint32_t ref_count) +{ + SWSS_LOG_ENTER(); + + if (m_oidTables[object_type].find(key) != m_oidTables[object_type].end()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d already exists in centralized mapper", key.c_str(), object_type); + return false; + } + + m_oidTables[object_type][key] = {oid, ref_count}; + m_table.hset("", convertToDBField(object_type, key), sai_serialize_object_id(oid)); + return true; +} + +bool P4OidMapper::getOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, _Out_ sai_object_id_t *oid) +{ + SWSS_LOG_ENTER(); + + if (oid == nullptr) + { + SWSS_LOG_ERROR("nullptr input in centralized mapper"); + return false; + } + + if (m_oidTables[object_type].find(key) == m_oidTables[object_type].end()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d does not exist in centralized mapper", key.c_str(), object_type); + return false; + } + + *oid = m_oidTables[object_type][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) +{ + SWSS_LOG_ENTER(); + + if (ref_count == nullptr) + { + SWSS_LOG_ERROR("nullptr input in centralized mapper"); + return false; + } + + if (m_oidTables[object_type].find(key) == m_oidTables[object_type].end()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d does not exist in " + "centralized mapper", + key.c_str(), object_type); + return false; + } + + *ref_count = m_oidTables[object_type][key].ref_count; + return true; +} + +bool P4OidMapper::eraseOID(_In_ sai_object_type_t object_type, _In_ const std::string &key) +{ + SWSS_LOG_ENTER(); + + if (m_oidTables[object_type].find(key) == m_oidTables[object_type].end()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d does not exist in " + "centralized mapper", + key.c_str(), object_type); + return false; + } + + if (m_oidTables[object_type][key].ref_count != 0) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d has non-zero reference count in " + "centralized mapper", + key.c_str(), object_type); + return false; + } + + m_oidTables[object_type].erase(key); + m_table.hdel("", convertToDBField(object_type, key)); + return true; +} + +void P4OidMapper::eraseAllOIDs(_In_ sai_object_type_t object_type) +{ + SWSS_LOG_ENTER(); + + m_oidTables[object_type].clear(); + m_table.del(""); +} + +size_t P4OidMapper::getNumEntries(_In_ sai_object_type_t object_type) +{ + SWSS_LOG_ENTER(); + + return (m_oidTables[object_type].size()); +} + +bool P4OidMapper::existsOID(_In_ sai_object_type_t object_type, _In_ const std::string &key) +{ + SWSS_LOG_ENTER(); + + return m_oidTables[object_type].find(key) != m_oidTables[object_type].end(); +} + +bool P4OidMapper::increaseRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key) +{ + SWSS_LOG_ENTER(); + + if (m_oidTables[object_type].find(key) == m_oidTables[object_type].end()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d does not exist in " + "centralized mapper", + key.c_str(), object_type); + return false; + } + + if (m_oidTables[object_type][key].ref_count == std::numeric_limits::max()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d reached maximum ref_count %u in " + "centralized mapper", + key.c_str(), object_type, m_oidTables[object_type][key].ref_count); + return false; + } + + m_oidTables[object_type][key].ref_count++; + return true; +} + +bool P4OidMapper::decreaseRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key) +{ + SWSS_LOG_ENTER(); + + if (m_oidTables[object_type].find(key) == m_oidTables[object_type].end()) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d does not exist in " + "centralized mapper", + key.c_str(), object_type); + return false; + } + + if (m_oidTables[object_type][key].ref_count == 0) + { + SWSS_LOG_ERROR("Key %s with SAI object type %d reached zero ref_count in " + "centralized mapper", + key.c_str(), object_type); + return false; + } + + m_oidTables[object_type][key].ref_count--; + return true; +} diff --git a/orchagent/p4orch/p4oidmapper.h b/orchagent/p4orch/p4oidmapper.h new file mode 100644 index 000000000000..6f7b86ab8f4e --- /dev/null +++ b/orchagent/p4orch/p4oidmapper.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include + +#include "dbconnector.h" +#include "table.h" + +extern "C" +{ +#include "sai.h" +} + +// Interface for mapping P4 ID to SAI OID. +// This class is not thread safe. +class P4OidMapper +{ + public: + // This is a dummy value for non-oid based objects only. + static constexpr sai_object_id_t kDummyOid = 0xdeadf00ddeadf00d; + + P4OidMapper(); + ~P4OidMapper() = default; + + // Sets oid for the given key for the specific object_type. Returns false if + // the key already exists. + bool setOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, _In_ sai_object_id_t oid, + _In_ uint32_t ref_count = 0); + + // Sets dummy oid for the given key for the specific object_type. Should only + // be used for non-oid based object type. Returns false if the key + // already exists. + bool setDummyOID(_In_ sai_object_type_t object_type, _In_ const std::string &key, _In_ uint32_t ref_count = 0) + { + return setOID(object_type, key, /*oid=*/kDummyOid, ref_count); + } + + // 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); + + // 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); + + // Erases oid for the given key for the SAI object_type. + // This function checks if the reference count is zero or not before the + // operation. + // Returns true on success. + bool eraseOID(_In_ sai_object_type_t object_type, _In_ const std::string &key); + + // Erases all oids for the SAI object_type. + // This function will erase all oids regardless of the reference counts. + 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); + + // 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); + + // Increases the reference count for the given object. + // Returns true on success. + bool increaseRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key); + + // Decreases the reference count for the given object. + // Returns true on success. + bool decreaseRefCount(_In_ sai_object_type_t object_type, _In_ const std::string &key); + + private: + struct MapperEntry + { + sai_object_id_t sai_oid; + uint32_t ref_count; + }; + + // Buckets of map tables, one for every SAI object type. + std::unordered_map m_oidTables[SAI_OBJECT_TYPE_MAX]; + + swss::DBConnector m_db; + swss::Table m_table; +}; diff --git a/orchagent/p4orch/p4orch.cpp b/orchagent/p4orch/p4orch.cpp new file mode 100644 index 000000000000..ada1fa2c7782 --- /dev/null +++ b/orchagent/p4orch/p4orch.cpp @@ -0,0 +1,237 @@ +#include "p4orch.h" + +#include +#include +#include +#include + +#include "copporch.h" +#include "logger.h" +#include "orch.h" +#include "p4orch/acl_rule_manager.h" +#include "p4orch/acl_table_manager.h" +#include "p4orch/neighbor_manager.h" +#include "p4orch/next_hop_manager.h" +#include "p4orch/route_manager.h" +#include "p4orch/router_interface_manager.h" +#include "portsorch.h" +#include "return_code.h" +#include "sai_serialize.h" +#include "timer.h" + +extern PortsOrch *gPortsOrch; +#define P4_ACL_COUNTERS_STATS_POLL_TIMER_NAME "P4_ACL_COUNTERS_STATS_POLL_TIMER" + +P4Orch::P4Orch(swss::DBConnector *db, std::vector tableNames, VRFOrch *vrfOrch, CoppOrch *coppOrch) + : Orch(db, tableNames) +{ + SWSS_LOG_ENTER(); + + m_routerIntfManager = 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); + m_mirrorSessionManager = std::make_unique(&m_p4OidMapper, &m_publisher); + 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_p4TableToManagerMap[APP_P4RT_ROUTER_INTERFACE_TABLE_NAME] = m_routerIntfManager.get(); + m_p4TableToManagerMap[APP_P4RT_NEIGHBOR_TABLE_NAME] = m_neighborManager.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_p4ManagerPrecedence.push_back(m_routerIntfManager.get()); + m_p4ManagerPrecedence.push_back(m_neighborManager.get()); + m_p4ManagerPrecedence.push_back(m_nextHopManager.get()); + m_p4ManagerPrecedence.push_back(m_wcmpManager.get()); + m_p4ManagerPrecedence.push_back(m_routeManager.get()); + m_p4ManagerPrecedence.push_back(m_mirrorSessionManager.get()); + m_p4ManagerPrecedence.push_back(m_aclTableManager.get()); + m_p4ManagerPrecedence.push_back(m_aclRuleManager.get()); + + // Add timer executor to update ACL counters stats in COUNTERS_DB + auto interv = timespec{.tv_sec = P4_COUNTERS_READ_INTERVAL, .tv_nsec = 0}; + m_aclCounterStatsTimer = new swss::SelectableTimer(interv); + auto executor = new swss::ExecutableTimer(m_aclCounterStatsTimer, this, P4_ACL_COUNTERS_STATS_POLL_TIMER_NAME); + Orch::addExecutor(executor); + m_aclCounterStatsTimer->start(); + + // Add port state change notification handling support + swss::DBConnector notificationsDb("ASIC_DB", 0); + m_portStatusNotificationConsumer = new swss::NotificationConsumer(¬ificationsDb, "NOTIFICATIONS"); + auto portStatusNotifier = new Notifier(m_portStatusNotificationConsumer, this, "PORT_STATUS_NOTIFICATIONS"); + Orch::addExecutor(portStatusNotifier); +} + +void P4Orch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (!gPortsOrch->allPortsReady()) + { + return; + } + + const std::string table_name = consumer.getTableName(); + if (table_name != APP_P4RT_TABLE_NAME) + { + SWSS_LOG_ERROR("Incorrect table name %s (expected %s)", table_name.c_str(), APP_P4RT_TABLE_NAME); + return; + } + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + const swss::KeyOpFieldsValuesTuple key_op_fvs_tuple = it->second; + const std::string key = kfvKey(key_op_fvs_tuple); + it = consumer.m_toSync.erase(it); + std::string table_name; + std::string key_content; + parseP4RTKey(key, &table_name, &key_content); + if (table_name.empty()) + { + auto status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Table name cannot be empty, but was empty in key: " << key; + 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); + continue; + } + if (m_p4TableToManagerMap.find(table_name) == m_p4TableToManagerMap.end()) + { + auto status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to find P4Orch Manager for " << table_name << " P4RT DB table"; + 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); + continue; + } + m_p4TableToManagerMap[table_name]->enqueue(key_op_fvs_tuple); + } + + for (const auto &manager : m_p4ManagerPrecedence) + { + manager->drain(); + } +} + +void P4Orch::doTask(swss::SelectableTimer &timer) +{ + SWSS_LOG_ENTER(); + + if (!gPortsOrch->allPortsReady()) + { + return; + } + + if (&timer == m_aclCounterStatsTimer) + { + m_aclRuleManager->doAclCounterStatsTask(); + } + else + { + SWSS_LOG_NOTICE("Unrecognized timer passed in P4Orch::doTask(swss::SelectableTimer& " + "timer)"); + } +} + +void P4Orch::handlePortStatusChangeNotification(const std::string &op, const std::string &data) +{ + if (op == "port_state_change") + { + uint32_t count; + sai_port_oper_status_notification_t *port_oper_status = nullptr; + sai_deserialize_port_oper_status_ntf(data, count, &port_oper_status); + + for (uint32_t i = 0; i < count; i++) + { + sai_object_id_t id = port_oper_status[i].port_id; + sai_port_oper_status_t status = port_oper_status[i].port_state; + + Port port; + if (!gPortsOrch->getPort(id, port)) + { + SWSS_LOG_ERROR("Failed to get port object for port id 0x%" PRIx64, id); + continue; + } + + // Update port oper-status in local map + m_wcmpManager->updatePortOperStatusMap(port.m_alias, status); + + if (status == SAI_PORT_OPER_STATUS_UP) + { + m_wcmpManager->restorePrunedNextHops(port.m_alias); + } + else + { + m_wcmpManager->pruneNextHops(port.m_alias); + } + + sai_deserialize_free_port_oper_status_ntf(count, port_oper_status); + } + } +} + +void P4Orch::doTask(NotificationConsumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (!gPortsOrch->allPortsReady()) + { + return; + } + + std::string op, data; + std::vector values; + + consumer.pop(op, data, values); + + if (&consumer == m_portStatusNotificationConsumer) + { + handlePortStatusChangeNotification(op, data); + } +} + +bool P4Orch::addAclTableToManagerMapping(const std::string &acl_table_name) +{ + SWSS_LOG_ENTER(); + if (m_p4TableToManagerMap.find(acl_table_name) != m_p4TableToManagerMap.end()) + { + SWSS_LOG_NOTICE("Consumer for ACL table %s already exists in P4Orch", acl_table_name.c_str()); + return false; + } + m_p4TableToManagerMap[acl_table_name] = m_aclRuleManager.get(); + return true; +} + +bool P4Orch::removeAclTableToManagerMapping(const std::string &acl_table_name) +{ + SWSS_LOG_ENTER(); + if (m_p4TableToManagerMap.find(acl_table_name) == m_p4TableToManagerMap.end()) + { + SWSS_LOG_NOTICE("Consumer for ACL table %s does not exist in P4Orch", acl_table_name.c_str()); + return false; + } + m_p4TableToManagerMap.erase(acl_table_name); + return true; +} + +p4orch::AclTableManager *P4Orch::getAclTableManager() +{ + return m_aclTableManager.get(); +} + +p4orch::AclRuleManager *P4Orch::getAclRuleManager() +{ + return m_aclRuleManager.get(); +} + +p4orch::WcmpManager *P4Orch::getWcmpManager() +{ + return m_wcmpManager.get(); +} diff --git a/orchagent/p4orch/p4orch.h b/orchagent/p4orch/p4orch.h new file mode 100644 index 000000000000..42159f398196 --- /dev/null +++ b/orchagent/p4orch/p4orch.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "copporch.h" +#include "notificationconsumer.h" +#include "notifier.h" +#include "orch.h" +#include "p4orch/acl_rule_manager.h" +#include "p4orch/acl_table_manager.h" +#include "p4orch/mirror_session_manager.h" +#include "p4orch/neighbor_manager.h" +#include "p4orch/next_hop_manager.h" +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "p4orch/route_manager.h" +#include "p4orch/router_interface_manager.h" +#include "p4orch/wcmp_manager.h" +#include "response_publisher.h" +#include "vrforch.h" + +class P4Orch : public Orch +{ + public: + P4Orch(swss::DBConnector *db, std::vector tableNames, VRFOrch *vrfOrch, CoppOrch *coppOrch); + // Add ACL table to ACLRuleManager mapping in P4Orch. + bool addAclTableToManagerMapping(const std::string &acl_table_name); + // Remove the ACL table name to AclRuleManager mapping in P4Orch + bool removeAclTableToManagerMapping(const std::string &acl_table_name); + p4orch::AclTableManager *getAclTableManager(); + p4orch::AclRuleManager *getAclRuleManager(); + p4orch::WcmpManager *getWcmpManager(); + + private: + void doTask(Consumer &consumer); + void doTask(swss::SelectableTimer &timer); + void doTask(swss::NotificationConsumer &consumer); + void handlePortStatusChangeNotification(const std::string &op, const std::string &data); + + // m_p4TableToManagerMap: P4 APP DB table name, P4 Object Manager + std::unordered_map m_p4TableToManagerMap; + // P4 object manager request processing order. + std::vector m_p4ManagerPrecedence; + + swss::SelectableTimer *m_aclCounterStatsTimer; + P4OidMapper m_p4OidMapper; + std::unique_ptr m_routerIntfManager; + std::unique_ptr m_neighborManager; + std::unique_ptr m_nextHopManager; + std::unique_ptr m_routeManager; + std::unique_ptr m_mirrorSessionManager; + std::unique_ptr m_aclTableManager; + std::unique_ptr m_aclRuleManager; + std::unique_ptr m_wcmpManager; + + // Notification consumer for port state change + swss::NotificationConsumer *m_portStatusNotificationConsumer; + + friend class p4orch::test::WcmpManagerTest; +}; diff --git a/orchagent/p4orch/p4orch_util.cpp b/orchagent/p4orch/p4orch_util.cpp new file mode 100644 index 000000000000..e5d44794364f --- /dev/null +++ b/orchagent/p4orch/p4orch_util.cpp @@ -0,0 +1,103 @@ +#include "p4orch/p4orch_util.h" + +#include "schema.h" + +using ::p4orch::kTableKeyDelimiter; + +// Prepends "match/" to the input string str to construct a new string. +std::string prependMatchField(const std::string &str) +{ + return std::string(p4orch::kMatchPrefix) + p4orch::kFieldDelimiter + str; +} + +// Prepends "param/" to the input string str to construct a new string. +std::string prependParamField(const std::string &str) +{ + return std::string(p4orch::kActionParamPrefix) + p4orch::kFieldDelimiter + str; +} + +void parseP4RTKey(const std::string &key, std::string *table_name, std::string *key_content) +{ + auto pos = key.find_first_of(kTableKeyDelimiter); + if (pos == std::string::npos) + { + *table_name = ""; + *key_content = ""; + return; + } + *table_name = key.substr(0, pos); + *key_content = key.substr(pos + 1); +} + +std::string KeyGenerator::generateRouteKey(const std::string &vrf_id, const swss::IpPrefix &ip_prefix) +{ + std::map fv_map = { + {p4orch::kVrfId, vrf_id}, {ip_prefix.isV4() ? p4orch::kIpv4Dst : p4orch::kIpv6Dst, ip_prefix.to_string()}}; + return generateKey(fv_map); +} + +std::string KeyGenerator::generateRouterInterfaceKey(const std::string &router_intf_id) +{ + std::map fv_map = {{p4orch::kRouterInterfaceId, router_intf_id}}; + return generateKey(fv_map); +} + +std::string KeyGenerator::generateNeighborKey(const std::string &router_intf_id, const swss::IpAddress &neighbor_id) +{ + std::map fv_map = {{p4orch::kRouterInterfaceId, router_intf_id}, + {p4orch::kNeighborId, neighbor_id.to_string()}}; + return generateKey(fv_map); +} + +std::string KeyGenerator::generateNextHopKey(const std::string &next_hop_id) +{ + std::map fv_map = {{p4orch::kNexthopId, next_hop_id}}; + return generateKey(fv_map); +} + +std::string KeyGenerator::generateMirrorSessionKey(const std::string &mirror_session_id) +{ + std::map fv_map = {{p4orch::kMirrorSessionId, mirror_session_id}}; + return generateKey(fv_map); +} + +std::string KeyGenerator::generateWcmpGroupKey(const std::string &wcmp_group_id) +{ + std::map fv_map = {{p4orch::kWcmpGroupId, wcmp_group_id}}; + return generateKey(fv_map); +} + +std::string KeyGenerator::generateAclRuleKey(const std::map &match_fields, + const std::string &priority) +{ + std::map fv_map = {}; + for (const auto &match_field : match_fields) + { + fv_map.emplace(std::string(p4orch::kMatchPrefix) + p4orch::kFieldDelimiter + match_field.first, + match_field.second); + } + fv_map.emplace(p4orch::kPriority, priority); + return generateKey(fv_map); +} + +std::string KeyGenerator::generateKey(const std::map &fv_map) +{ + std::string key; + bool append_delimiter = false; + for (const auto &it : fv_map) + { + if (append_delimiter) + { + key.append(":"); + } + else + { + append_delimiter = true; + } + key.append(it.first); + key.append("="); + key.append(it.second); + } + + return key; +} diff --git a/orchagent/p4orch/p4orch_util.h b/orchagent/p4orch/p4orch_util.h new file mode 100644 index 000000000000..a3684a5fb86c --- /dev/null +++ b/orchagent/p4orch/p4orch_util.h @@ -0,0 +1,226 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "ipaddress.h" +#include "ipprefix.h" +#include "macaddress.h" + +namespace p4orch +{ + +// Field names in P4RT APP DB entry. +constexpr char *kRouterInterfaceId = "router_interface_id"; +constexpr char *kPort = "port"; +constexpr char *kSrcMac = "src_mac"; +constexpr char *kAction = "action"; +constexpr char *kActions = "actions"; +constexpr char *kWeight = "weight"; +constexpr char *kWatchPort = "watch_port"; +constexpr char *kNeighborId = "neighbor_id"; +constexpr char *kDstMac = "dst_mac"; +constexpr char *kNexthopId = "nexthop_id"; +constexpr char *kVrfId = "vrf_id"; +constexpr char *kIpv4Dst = "ipv4_dst"; +constexpr char *kIpv6Dst = "ipv6_dst"; +constexpr char *kWcmpGroupId = "wcmp_group_id"; +constexpr char *kSetNexthopId = "set_nexthop_id"; +constexpr char *kSetWcmpGroupId = "set_wcmp_group_id"; +constexpr char *kDrop = "drop"; +constexpr char *kStage = "stage"; +constexpr char *kSize = "size"; +constexpr char *kPriority = "priority"; +constexpr char *kPacketColor = "packet_color"; +constexpr char *kMeterUnit = "meter/unit"; +constexpr char *kCounterUnit = "counter/unit"; +constexpr char kFieldDelimiter = '/'; +constexpr char kTableKeyDelimiter = ':'; +constexpr char kDataMaskDelimiter = '&'; +constexpr char kPortsDelimiter = ','; +constexpr char *kMatchPrefix = "match"; +constexpr char *kActionParamPrefix = "param"; +constexpr char *kMeterPrefix = "meter"; +constexpr char *kMeterCir = "cir"; +constexpr char *kMeterCburst = "cburst"; +constexpr char *kMeterPir = "pir"; +constexpr char *kMeterPburst = "pburst"; +constexpr char *kControllerMetadata = "controller_metadata"; +constexpr char *kAclMatchFieldKind = "kind"; +constexpr char *kAclMatchFieldFormat = "format"; +constexpr char *kAclMatchFieldBitwidth = "bitwidth"; +constexpr char *kAclMatchFieldElements = "elements"; +constexpr char *kAclMatchFieldSaiField = "sai_field"; +constexpr char *kAclMatchFieldKindComposite = "composite"; +constexpr char *kAclMatchFieldKindUdf = "udf"; +constexpr char *kAclUdfBase = "base"; +constexpr char *kAclUdfOffset = "offset"; +constexpr char *kMirrorSessionId = "mirror_session_id"; +constexpr char *kSrcIp = "src_ip"; +constexpr char *kDstIp = "dst_ip"; +constexpr char *kTtl = "ttl"; +constexpr char *kTos = "tos"; +constexpr char *kMirrorAsIpv4Erspan = "mirror_as_ipv4_erspan"; +} // namespace p4orch + +// Prepends "match/" to the input string str to construct a new string. +std::string prependMatchField(const std::string &str); + +// Prepends "param/" to the input string str to construct a new string. +std::string prependParamField(const std::string &str); + +struct P4RouterInterfaceAppDbEntry +{ + std::string router_interface_id; + std::string port_name; + swss::MacAddress src_mac_address; + bool is_set_port_name = false; + bool is_set_src_mac = false; +}; + +struct P4NeighborAppDbEntry +{ + std::string router_intf_id; + swss::IpAddress neighbor_id; + swss::MacAddress dst_mac_address; + bool is_set_dst_mac = false; +}; + +// P4NextHopAppDbEntry holds entry deserialized from table +// APP_P4RT_NEXTHOP_TABLE_NAME. +struct P4NextHopAppDbEntry +{ + // Key + std::string next_hop_id; + // Fields + std::string router_interface_id; + swss::IpAddress neighbor_id; + bool is_set_router_interface_id = false; + bool is_set_neighbor_id = false; +}; + +struct P4MirrorSessionAppDbEntry +{ + // Key (match field) + std::string mirror_session_id; + + // fields (action parameters) + std::string port; + bool has_port = false; + + swss::IpAddress src_ip; + bool has_src_ip = false; + + swss::IpAddress dst_ip; + bool has_dst_ip = false; + + swss::MacAddress src_mac; + bool has_src_mac = false; + + swss::MacAddress dst_mac; + bool has_dst_mac = false; + + uint8_t ttl = 0; + bool has_ttl = false; + + uint8_t tos = 0; + bool has_tos = false; +}; + +struct P4ActionParamName +{ + std::string sai_action; + std::string p4_param_name; +}; + +struct P4PacketActionWithColor +{ + std::string packet_action; + std::string packet_color; +}; + +struct P4AclTableDefinitionAppDbEntry +{ + // Key + std::string acl_table_name; + // Fields + std::string stage; + uint32_t size; + uint32_t priority; + std::map match_field_lookup; + std::map> action_field_lookup; + std::map> packet_action_color_lookup; + std::string meter_unit; + std::string counter_unit; +}; + +struct P4AclMeterAppDb +{ + bool enabled; + uint64_t cir; + uint64_t cburst; + uint64_t pir; + uint64_t pburst; + + P4AclMeterAppDb() : enabled(false) + { + } +}; + +struct P4AclRuleAppDbEntry +{ + // Key + std::string acl_table_name; + std::map match_fvs; + uint32_t priority; + std::string db_key; + // Fields + std::string action; + std::map action_param_fvs; + P4AclMeterAppDb meter; +}; + +// Get the table name and key content from the given P4RT key. +// Outputs will be empty strings in case of error. +// Example: FIXED_NEIGHBOR_TABLE:{content} +// Table name: FIXED_NEIGHBOR_TABLE +// Key content: {content} +void parseP4RTKey(const std::string &key, std::string *table_name, std::string *key_content); + +// class KeyGenerator includes member functions to generate keys for entries +// stored in P4 Orch managers. +class KeyGenerator +{ + public: + static std::string generateRouteKey(const std::string &vrf_id, const swss::IpPrefix &ip_prefix); + + static std::string generateRouterInterfaceKey(const std::string &router_intf_id); + + static std::string generateNeighborKey(const std::string &router_intf_id, const swss::IpAddress &neighbor_id); + + static std::string generateNextHopKey(const std::string &next_hop_id); + + static std::string generateMirrorSessionKey(const std::string &mirror_session_id); + + static std::string generateWcmpGroupKey(const std::string &wcmp_group_id); + + static std::string generateAclRuleKey(const std::map &match_fields, + const std::string &priority); + + // 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... + static std::string generateKey(const std::map &fv_map); +}; + +// Inserts single quote for a variable name. +// Returns a string. +template std::string QuotedVar(T name) +{ + std::ostringstream ss; + ss << std::quoted(name, '\''); + return ss.str(); +} diff --git a/orchagent/p4orch/route_manager.cpp b/orchagent/p4orch/route_manager.cpp new file mode 100644 index 000000000000..7732a143e50c --- /dev/null +++ b/orchagent/p4orch/route_manager.cpp @@ -0,0 +1,579 @@ +#include "p4orch/route_manager.h" + +#include +#include +#include +#include +#include + +#include "crmorch.h" +#include "json.hpp" +#include "logger.h" +#include "p4orch/p4orch_util.h" +#include "swssnet.h" + +extern sai_object_id_t gSwitchId; +extern sai_object_id_t gVirtualRouterId; + +extern sai_route_api_t *sai_route_api; + +extern CrmOrch *gCrmOrch; + +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) +{ + SWSS_LOG_ENTER(); + // For drop action, we will update the action attribute first. + bool action_first = (new_action == SAI_PACKET_ACTION_DROP); + + // 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) + { + route_attr.value.s32 = new_action; + } + else + { + route_attr.value.oid = new_nexthop; + } + 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)); + + // Second attribute + route_attr.id = (action_first) ? SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID : SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + if (action_first) + { + route_attr.value.oid = new_nexthop; + } + else + { + route_attr.value.s32 = new_action; + } + ReturnCode status; + auto sai_status = sai_route_api->set_route_entry_attribute(rotue_entry, &route_attr); + if (sai_status == SAI_STATUS_SUCCESS) + { + return ReturnCode(); + } + 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()); + + // 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) + { + route_attr.value.s32 = old_action; + } + else + { + route_attr.value.oid = old_nexthop; + } + sai_status = sai_route_api->set_route_entry_attribute(rotue_entry, &route_attr); + 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()); + } + + return status; +} + +} // namespace + +bool RouteManager::mergeRouteEntry(const P4RouteEntry &dest, const P4RouteEntry &src, P4RouteEntry *ret) +{ + SWSS_LOG_ENTER(); + + *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) + { + return true; + } + return false; +} + +ReturnCodeOr RouteManager::deserializeRouteEntry(const std::string &key, + const std::vector &attributes, + const std::string &table_name) +{ + SWSS_LOG_ENTER(); + + P4RouteEntry route_entry = {}; + std::string route_prefix; + try + { + nlohmann::json j = nlohmann::json::parse(key); + route_entry.vrf_id = j[prependMatchField(p4orch::kVrfId)]; + if (table_name == APP_P4RT_IPV4_TABLE_NAME) + { + if (j.find(prependMatchField(p4orch::kIpv4Dst)) != j.end()) + { + route_prefix = j[prependMatchField(p4orch::kIpv4Dst)]; + } + else + { + route_prefix = "0.0.0.0/0"; + } + } + else + { + if (j.find(prependMatchField(p4orch::kIpv6Dst)) != j.end()) + { + route_prefix = j[prependMatchField(p4orch::kIpv6Dst)]; + } + else + { + route_prefix = "::/0"; + } + } + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize route key"; + } + try + { + route_entry.route_prefix = swss::IpPrefix(route_prefix); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid IP prefix " << QuotedVar(route_prefix); + } + + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == p4orch::kAction) + { + route_entry.action = value; + } + else if (field == prependParamField(p4orch::kNexthopId)) + { + route_entry.nexthop_id = value; + } + else if (field == prependParamField(p4orch::kWcmpGroupId)) + { + route_entry.wcmp_group = value; + } + else if (field != p4orch::kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in " << table_name; + } + } + + return route_entry; +} + +P4RouteEntry *RouteManager::getRouteEntry(const std::string &route_entry_key) +{ + SWSS_LOG_ENTER(); + + if (m_routeTable.find(route_entry_key) == m_routeTable.end()) + return nullptr; + + return &m_routeTable[route_entry_key]; +} + +ReturnCode RouteManager::validateRouteEntry(const P4RouteEntry &route_entry) +{ + SWSS_LOG_ENTER(); + + if (!route_entry.nexthop_id.empty()) + { + auto nexthop_key = KeyGenerator::generateNextHopKey(route_entry.nexthop_id); + if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_NEXT_HOP, nexthop_key)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Nexthop ID " << QuotedVar(route_entry.nexthop_id) << " does not exist"; + } + } + if (!route_entry.wcmp_group.empty()) + { + auto wcmp_group_key = KeyGenerator::generateWcmpGroupKey(route_entry.wcmp_group); + if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "WCMP group " << QuotedVar(route_entry.wcmp_group) << " does not exist"; + } + } + if (!route_entry.vrf_id.empty() && !m_vrfOrch->isVRFexists(route_entry.vrf_id)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "No VRF found with name " << QuotedVar(route_entry.vrf_id); + } + return ReturnCode(); +} + +ReturnCode RouteManager::validateSetRouteEntry(const P4RouteEntry &route_entry) +{ + auto *route_entry_ptr = getRouteEntry(route_entry.route_entry_key); + bool exist_in_mapper = m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); + if (route_entry_ptr == nullptr && exist_in_mapper) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Route entry does not exist in manager but exists in the " + "centralized map"; + } + if (route_entry_ptr != nullptr && !exist_in_mapper) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Route entry exists in manager but does not exist in the " + "centralized map"; + } + std::string action = route_entry.action; + // If action is empty, this could be an update. + if (action.empty()) + { + if (route_entry_ptr == nullptr) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Empty action for route"; + } + action = route_entry_ptr->action; + } + 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"; + } + } + 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"; + } + } + else if (action == p4orch::kDrop) + { + if (!route_entry.nexthop_id.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Non-empty nexthop_id for route with drop action"; + } + if (!route_entry.wcmp_group.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Non-empty wcmp_group_id for route with drop action"; + } + } + else + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid action " << QuotedVar(action); + } + return ReturnCode(); +} + +ReturnCode RouteManager::validateDelRouteEntry(const P4RouteEntry &route_entry) +{ + if (getRouteEntry(route_entry.route_entry_key) == nullptr) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) << "Route entry does not exist"; + } + if (!m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Route entry does not exist in the centralized map"); + } + if (!route_entry.action.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Non-empty action for Del route"; + } + if (!route_entry.nexthop_id.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Non-empty nexthop_id for Del route"; + } + if (!route_entry.wcmp_group.empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Non-empty wcmp_group for Del route"; + } + return ReturnCode(); +} + +ReturnCode RouteManager::createRouteEntry(const P4RouteEntry &route_entry) +{ + 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"); + } + + 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()) + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); + } + else + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + } + m_vrfOrch->increaseVrfRefCount(route_entry.vrf_id); + return ReturnCode(); +} + +ReturnCodeOr RouteManager::getNexthopOid(const P4RouteEntry &route_entry) +{ + sai_object_id_t oid = SAI_NULL_OBJECT_ID; + if (route_entry.action == p4orch::kSetNexthopId) + { + auto nexthop_key = KeyGenerator::generateNextHopKey(route_entry.nexthop_id); + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP, nexthop_key, &oid)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Nexthop " << QuotedVar(route_entry.nexthop_id) + << " does not exist"); + } + } + 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)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("WCMP group " << QuotedVar(route_entry.wcmp_group) + << " does not exist"); + } + } + return oid; +} + +ReturnCode RouteManager::updateRouteEntry(const P4RouteEntry &route_entry) +{ + 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)) + { + 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)); + } + if (new_route_entry.action == p4orch::kSetWcmpGroupId) + { + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, + KeyGenerator::generateWcmpGroupKey(new_route_entry.wcmp_group)); + } + + if (route_entry_ptr->action == p4orch::kSetNexthopId) + { + if (new_route_entry.action != p4orch::kSetNexthopId || + new_route_entry.nexthop_id != route_entry_ptr->nexthop_id) + { + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(route_entry_ptr->nexthop_id)); + } + } + if (route_entry_ptr->action == p4orch::kSetWcmpGroupId) + { + 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)); + } + } + m_routeTable[route_entry.route_entry_key] = new_route_entry; + return ReturnCode(); +} + +ReturnCode RouteManager::deleteRouteEntry(const P4RouteEntry &route_entry) +{ + 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)); + + 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) + { + 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); + return ReturnCode(); +} + +void RouteManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void RouteManager::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 route_entry_or = deserializeRouteEntry(key, attributes, table_name); + if (!route_entry_or.ok()) + { + status = route_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 &route_entry = *route_entry_or; + + status = validateRouteEntry(route_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for Route 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; + } + + 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) + { + status = createRouteEntry(route_entry); + } + else + { + status = updateRouteEntry(route_entry); + } + } + else if (operation == DEL_COMMAND) + { + 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); + } + } + 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(); +} diff --git a/orchagent/p4orch/route_manager.h b/orchagent/p4orch/route_manager.h new file mode 100644 index 000000000000..6e494709b34e --- /dev/null +++ b/orchagent/p4orch/route_manager.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include + +#include "ipprefix.h" +#include "orch.h" +#include "p4orch/next_hop_manager.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" +#include "vrforch.h" +extern "C" +{ +#include "sai.h" +} + +struct P4RouteEntry +{ + std::string route_entry_key; // Unique key of a route entry. + std::string vrf_id; + swss::IpPrefix route_prefix; + std::string action; + std::string nexthop_id; + std::string wcmp_group; + sai_route_entry_t sai_route_entry; +}; + +// P4RouteTable: Route ID, P4RouteEntry +typedef std::unordered_map P4RouteTable; + +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; + } + virtual ~RouteManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + private: + // Applies route entry updates from src to dest. The merged result will be + // stored in ret. + // The src should have passed all validation checks. + // Return true if there are updates, false otherwise. + bool mergeRouteEntry(const P4RouteEntry &dest, const P4RouteEntry &src, P4RouteEntry *ret); + + // Converts db table entry into P4RouteEntry. + ReturnCodeOr deserializeRouteEntry(const std::string &key, + const std::vector &attributes, + const std::string &table_name); + + // Gets the internal cached route entry by its key. + // 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 for SET command. + ReturnCode validateSetRouteEntry(const P4RouteEntry &route_entry); + + // 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); + + // Updates a route entry. + // Returns a SWSS status code. + ReturnCode updateRouteEntry(const P4RouteEntry &route_entry); + + // Deletes a route entry. + // Returns a SWSS status code. + ReturnCode deleteRouteEntry(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); + + P4RouteTable m_routeTable; + P4OidMapper *m_p4OidMapper; + VRFOrch *m_vrfOrch; + ResponsePublisherInterface *m_publisher; + std::deque m_entries; + + friend class RouteManagerTest; +}; diff --git a/orchagent/p4orch/router_interface_manager.cpp b/orchagent/p4orch/router_interface_manager.cpp new file mode 100644 index 000000000000..ea9abf083ac9 --- /dev/null +++ b/orchagent/p4orch/router_interface_manager.cpp @@ -0,0 +1,398 @@ +#include "p4orch/router_interface_manager.h" + +#include +#include +#include +#include +#include + +#include "directory.h" +#include "json.hpp" +#include "logger.h" +#include "orch.h" +#include "p4orch/p4orch_util.h" +#include "portsorch.h" +#include "vrforch.h" + +extern sai_object_id_t gSwitchId; +extern sai_object_id_t gVirtualRouterId; + +extern sai_router_interface_api_t *sai_router_intfs_api; + +extern PortsOrch *gPortsOrch; +extern Directory gDirectory; + +namespace +{ + +ReturnCode validateRouterInterfaceAppDbEntry(const P4RouterInterfaceAppDbEntry &app_db_entry) +{ + // Perform generic APP DB entry validations. Operation specific validations + // will be done by the respective request process methods. + + if (app_db_entry.is_set_port_name) + { + Port port; + if (!gPortsOrch->getPort(app_db_entry.port_name, port)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Port " << QuotedVar(app_db_entry.port_name) << " does not exist"; + } + } + + if ((app_db_entry.is_set_src_mac) && (app_db_entry.src_mac_address.to_string() == "00:00:00:00:00:00")) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid source mac address " << QuotedVar(app_db_entry.src_mac_address.to_string()); + } + + return ReturnCode(); +} + +} // namespace + +ReturnCodeOr RouterInterfaceManager::deserializeRouterIntfEntry( + const std::string &key, const std::vector &attributes) +{ + SWSS_LOG_ENTER(); + + P4RouterInterfaceAppDbEntry app_db_entry = {}; + try + { + nlohmann::json j = nlohmann::json::parse(key); + app_db_entry.router_interface_id = j[prependMatchField(p4orch::kRouterInterfaceId)]; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize router interface id"; + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == prependParamField(p4orch::kPort)) + { + app_db_entry.port_name = value; + app_db_entry.is_set_port_name = true; + } + else if (field == prependParamField(p4orch::kSrcMac)) + { + try + { + app_db_entry.src_mac_address = swss::MacAddress(value); + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid MAC address " << QuotedVar(value) << " of field " << QuotedVar(field); + } + app_db_entry.is_set_src_mac = true; + } + else if (field != p4orch::kAction && field != p4orch::kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in table entry"; + } + } + + return app_db_entry; +} + +P4RouterInterfaceEntry *RouterInterfaceManager::getRouterInterfaceEntry(const std::string &router_intf_key) +{ + SWSS_LOG_ENTER(); + + if (m_routerIntfTable.find(router_intf_key) == m_routerIntfTable.end()) + return nullptr; + + return &m_routerIntfTable[router_intf_key]; +} + +ReturnCode RouterInterfaceManager::createRouterInterface(const std::string &router_intf_key, + P4RouterInterfaceEntry &router_intf_entry) +{ + SWSS_LOG_ENTER(); + + if (getRouterInterfaceEntry(router_intf_key) != nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_EXISTS) + << "Router interface " << QuotedVar(router_intf_entry.router_interface_id) + << " already exists"); + } + + if (m_p4OidMapper->existsOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Router interface " << QuotedVar(router_intf_key) + << " 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); + + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_router_intfs_api->create_router_interface(&router_intf_entry.router_interface_oid, gSwitchId, + (uint32_t)attrs.size(), attrs.data()), + "Failed to create router interface " << QuotedVar(router_intf_entry.router_interface_id)); + + gPortsOrch->increasePortRefCount(router_intf_entry.port_name); + gDirectory.get()->increaseVrfRefCount(gVirtualRouterId); + + m_routerIntfTable[router_intf_key] = router_intf_entry; + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, router_intf_entry.router_interface_oid); + return ReturnCode(); +} + +ReturnCode RouterInterfaceManager::removeRouterInterface(const std::string &router_intf_key) +{ + SWSS_LOG_ENTER(); + + auto *router_intf_entry = getRouterInterfaceEntry(router_intf_key); + if (router_intf_entry == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Router interface entry with key " << QuotedVar(router_intf_key) << " does not exist"); + } + + uint32_t ref_count; + if (!m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, &ref_count)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get reference count for router interface " + << QuotedVar(router_intf_entry->router_interface_id)); + } + if (ref_count > 0) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Router interface " << QuotedVar(router_intf_entry->router_interface_id) + << " referenced by other objects (ref_count = " << ref_count << ")"); + } + + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_router_intfs_api->remove_router_interface(router_intf_entry->router_interface_oid), + "Failed to remove router interface " << QuotedVar(router_intf_entry->router_interface_id)); + + gPortsOrch->decreasePortRefCount(router_intf_entry->port_name); + gDirectory.get()->decreaseVrfRefCount(gVirtualRouterId); + + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key); + m_routerIntfTable.erase(router_intf_key); + return ReturnCode(); +} + +ReturnCode RouterInterfaceManager::setSourceMacAddress(P4RouterInterfaceEntry *router_intf_entry, + const swss::MacAddress &mac_address) +{ + SWSS_LOG_ENTER(); + + if (router_intf_entry->src_mac_address == mac_address) + return ReturnCode(); + + sai_attribute_t attr; + attr.id = SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS; + memcpy(attr.value.mac, mac_address.getMac(), sizeof(sai_mac_t)); + CHECK_ERROR_AND_LOG_AND_RETURN( + sai_router_intfs_api->set_router_interface_attribute(router_intf_entry->router_interface_oid, &attr), + "Failed to set mac address " << QuotedVar(mac_address.to_string()) << " on router interface " + << QuotedVar(router_intf_entry->router_interface_id)); + + router_intf_entry->src_mac_address = mac_address; + return ReturnCode(); +} + +ReturnCode RouterInterfaceManager::processAddRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, + const std::string &router_intf_key) +{ + SWSS_LOG_ENTER(); + + // Perform operation specific validations. + if (!app_db_entry.is_set_port_name) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << p4orch::kPort + << " is mandatory to create router interface. Failed to create " + "router interface " + << QuotedVar(app_db_entry.router_interface_id)); + } + + P4RouterInterfaceEntry router_intf_entry(app_db_entry.router_interface_id, app_db_entry.port_name, + app_db_entry.src_mac_address); + auto status = createRouterInterface(router_intf_key, router_intf_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create router interface with key %s", QuotedVar(router_intf_key).c_str()); + } + + return status; +} + +ReturnCode RouterInterfaceManager::processUpdateRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, + P4RouterInterfaceEntry *router_intf_entry) +{ + SWSS_LOG_ENTER(); + + // TODO: port_id is a create_only parameter in SAI. In order + // to update port name, current interface needs to be deleted and a new + // interface with updated parameters needs to be created. + if (app_db_entry.is_set_port_name && router_intf_entry->port_name != app_db_entry.port_name) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_UNIMPLEMENTED) + << "Updating port name for existing router interface is not " + "supported. Cannot update port name to " + << QuotedVar(app_db_entry.port_name) << " for router interface " + << QuotedVar(router_intf_entry->router_interface_id)); + } + + if (app_db_entry.is_set_src_mac) + { + auto status = setSourceMacAddress(router_intf_entry, app_db_entry.src_mac_address); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to update source mac address with key %s", + QuotedVar(router_intf_entry->router_interface_id).c_str()); + return status; + } + } + + return ReturnCode(); +} + +ReturnCode RouterInterfaceManager::processDeleteRequest(const std::string &router_intf_key) +{ + SWSS_LOG_ENTER(); + + auto status = removeRouterInterface(router_intf_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove router interface with key %s", QuotedVar(router_intf_key).c_str()); + } + + return status; +} + +void RouterInterfaceManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void RouterInterfaceManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string db_key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &db_key); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + ReturnCode status; + auto app_db_entry_or = deserializeRouterIntfEntry(db_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 + ":" + db_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; + + status = validateRouterInterfaceAppDbEntry(app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Validation failed for Router Interface APP DB entry with key %s: %s", + QuotedVar(table_name + ":" + db_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; + } + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); + + const std::string &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *router_intf_entry = getRouterInterfaceEntry(router_intf_key); + if (router_intf_entry == nullptr) + { + // Create router interface + status = processAddRequest(app_db_entry, router_intf_key); + } + else + { + // Modify existing router interface + status = processUpdateRequest(app_db_entry, router_intf_entry); + } + } + else if (operation == DEL_COMMAND) + { + // Delete router interface + status = processDeleteRequest(router_intf_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(); +} diff --git a/orchagent/p4orch/router_interface_manager.h b/orchagent/p4orch/router_interface_manager.h new file mode 100644 index 000000000000..a300b2a7a49a --- /dev/null +++ b/orchagent/p4orch/router_interface_manager.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include + +#include "macaddress.h" +#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" +extern "C" +{ +#include "sai.h" +} + +struct P4RouterInterfaceEntry +{ + std::string router_interface_id; + std::string port_name; + swss::MacAddress src_mac_address; + sai_object_id_t router_interface_oid = 0; + + P4RouterInterfaceEntry() = default; + P4RouterInterfaceEntry(const std::string &router_intf_id, const std::string &port, + const swss::MacAddress &mac_address) + : router_interface_id(router_intf_id), port_name(port), src_mac_address(mac_address) + { + } +}; + +// P4RouterInterfaceTable: Router Interface key, P4RouterInterfaceEntry +typedef std::unordered_map P4RouterInterfaceTable; + +class RouterInterfaceManager : public ObjectManagerInterface +{ + public: + RouterInterfaceManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + virtual ~RouterInterfaceManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + private: + ReturnCodeOr deserializeRouterIntfEntry( + const std::string &key, const std::vector &attributes); + P4RouterInterfaceEntry *getRouterInterfaceEntry(const std::string &router_intf_key); + ReturnCode createRouterInterface(const std::string &router_intf_key, P4RouterInterfaceEntry &router_intf_entry); + ReturnCode removeRouterInterface(const std::string &router_intf_key); + ReturnCode setSourceMacAddress(P4RouterInterfaceEntry *router_intf_entry, const swss::MacAddress &mac_address); + ReturnCode processAddRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, const std::string &router_intf_key); + ReturnCode processUpdateRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, + P4RouterInterfaceEntry *router_intf_entry); + ReturnCode processDeleteRequest(const std::string &router_intf_key); + + P4RouterInterfaceTable m_routerIntfTable; + P4OidMapper *m_p4OidMapper; + ResponsePublisherInterface *m_publisher; + std::deque m_entries; + + friend class RouterInterfaceManagerTest; +}; diff --git a/orchagent/p4orch/tests/Makefile.am b/orchagent/p4orch/tests/Makefile.am new file mode 100644 index 000000000000..489acd8f992c --- /dev/null +++ b/orchagent/p4orch/tests/Makefile.am @@ -0,0 +1,88 @@ +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 + +CFLAGS_SAI = -I /usr/include/sai + +bin_PROGRAMS = p4orch_tests p4orch_tests_asan p4orch_tests_tsan p4orch_tests_usan + +if DEBUG +DBGFLAGS = -ggdb -DDEBUG +else +DBGFLAGS = -g -DNDEBUG +endif + +CFLAGS_GTEST = +LDADD_GTEST = -lgtest -lgtest_main -lgmock -lgmock_main +CFLAGS_COVERAGE = --coverage -fprofile-arcs -ftest-coverage +LDADD_COVERAGE = -lgcov +CFLAGS_ASAN = -fsanitize=address +CFLAGS_TSAN = -fsanitize=thread +CFLAGS_USAN = -fsanitize=undefined + +p4orch_tests_SOURCES = $(ORCHAGENT_DIR)/orch.cpp \ + $(ORCHAGENT_DIR)/vrforch.cpp \ + $(ORCHAGENT_DIR)/vxlanorch.cpp \ + $(ORCHAGENT_DIR)/copporch.cpp \ + $(ORCHAGENT_DIR)/switchorch.cpp \ + $(ORCHAGENT_DIR)/request_parser.cpp \ + $(ORCHAGENT_DIR)/flex_counter/flex_counter_manager.cpp \ + $(P4ORCH_DIR)/p4oidmapper.cpp \ + $(P4ORCH_DIR)/p4orch.cpp \ + $(P4ORCH_DIR)/p4orch_util.cpp \ + $(P4ORCH_DIR)/router_interface_manager.cpp \ + $(P4ORCH_DIR)/neighbor_manager.cpp \ + $(P4ORCH_DIR)/next_hop_manager.cpp \ + $(P4ORCH_DIR)/route_manager.cpp \ + $(P4ORCH_DIR)/acl_util.cpp \ + $(P4ORCH_DIR)/acl_table_manager.cpp \ + $(P4ORCH_DIR)/acl_rule_manager.cpp \ + $(P4ORCH_DIR)/wcmp_manager.cpp \ + $(P4ORCH_DIR)/mirror_session_manager.cpp \ + $(top_srcdir)/tests/mock_tests/fake_response_publisher.cpp \ + fake_portorch.cpp \ + fake_crmorch.cpp \ + fake_dbconnector.cpp \ + fake_producertable.cpp \ + fake_consumerstatetable.cpp \ + fake_subscriberstatetable.cpp \ + fake_notificationconsumer.cpp \ + fake_table.cpp \ + p4oidmapper_test.cpp \ + p4orch_util_test.cpp \ + return_code_test.cpp \ + route_manager_test.cpp \ + next_hop_manager_test.cpp \ + wcmp_manager_test.cpp \ + acl_manager_test.cpp \ + router_interface_manager_test.cpp \ + neighbor_manager_test.cpp \ + mirror_session_manager_test.cpp \ + test_main.cpp \ + mock_sai_acl.cpp \ + mock_sai_hostif.cpp \ + mock_sai_serialize.cpp \ + mock_sai_switch.cpp \ + mock_sai_udf.cpp + +p4orch_tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_COVERAGE) $(CFLAGS_SAI) +p4orch_tests_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_COVERAGE) $(CFLAGS_SAI) +p4orch_tests_LDADD = $(LDADD_GTEST) $(LDADD_COVERAGE) -lpthread -lsairedis -lswsscommon -lsaimeta -lsaimetadata -lzmq + +p4orch_tests_asan_SOURCES = $(p4orch_tests_SOURCES) +p4orch_tests_asan_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_ASAN) $(CFLAGS_SAI) +p4orch_tests_asan_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_ASAN) $(CFLAGS_SAI) +p4orch_tests_asan_LDFLAGS = $(CFLAGS_ASAN) +p4orch_tests_asan_LDADD = $(LDADD_GTEST) -lpthread -lsairedis -lswsscommon -lsaimeta -lsaimetadata -lzmq + +p4orch_tests_tsan_SOURCES = $(p4orch_tests_SOURCES) +p4orch_tests_tsan_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_TSAN) $(CFLAGS_SAI) +p4orch_tests_tsan_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_TSAN) $(CFLAGS_SAI) +p4orch_tests_tsan_LDFLAGS = $(CFLAGS_TSAN) +p4orch_tests_tsan_LDADD = $(LDADD_GTEST) -lpthread -lsairedis -lswsscommon -lsaimeta -lsaimetadata -lzmq + +p4orch_tests_usan_SOURCES = $(p4orch_tests_SOURCES) +p4orch_tests_usan_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_USAN) $(CFLAGS_SAI) +p4orch_tests_usan_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_USAN) $(CFLAGS_SAI) +p4orch_tests_usan_LDFLAGS = $(CFLAGS_USAN) +p4orch_tests_usan_LDADD = $(LDADD_GTEST) -lpthread -lsairedis -lswsscommon -lsaimeta -lsaimetadata -lzmq diff --git a/orchagent/p4orch/tests/acl_manager_test.cpp b/orchagent/p4orch/tests/acl_manager_test.cpp new file mode 100644 index 000000000000..64ba37e5a3d3 --- /dev/null +++ b/orchagent/p4orch/tests/acl_manager_test.cpp @@ -0,0 +1,4253 @@ +#include +#include + +#include +#include +#include + +#include "acl_rule_manager.h" +#include "acl_table_manager.h" +#include "acl_util.h" +#include "acltable.h" +#include "json.hpp" +#include "mock_sai_acl.h" +#include "mock_sai_hostif.h" +#include "mock_sai_policer.h" +#include "mock_sai_serialize.h" +#include "mock_sai_switch.h" +#include "mock_sai_udf.h" +#include "p4orch.h" +#include "return_code.h" +#include "switchorch.h" +#include "table.h" +#include "tokenize.h" +#include "vrforch.h" + +extern swss::DBConnector *gAppDb; +extern swss::DBConnector *gStateDb; +extern swss::DBConnector *gCountersDb; +extern swss::DBConnector *gConfigDb; +extern sai_acl_api_t *sai_acl_api; +extern sai_policer_api_t *sai_policer_api; +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 int gBatchSize; +extern VRFOrch *gVrfOrch; +extern P4Orch *gP4Orch; +extern SwitchOrch *gSwitchOrch; +extern sai_object_id_t gSwitchId; +extern sai_object_id_t gVrfOid; +extern sai_object_id_t gTrapGroupStartOid; +extern sai_object_id_t gHostifStartOid; +extern sai_object_id_t gUserDefinedTrapStartOid; +extern char *gVrfName; +extern char *gMirrorSession1; +extern sai_object_id_t kMirrorSessionOid1; +extern char *gMirrorSession2; +extern sai_object_id_t kMirrorSessionOid2; +extern bool gIsNatSupported; + +namespace p4orch +{ +namespace test +{ + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Gt; +using ::testing::Invoke; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::Truly; + +namespace +{ +constexpr sai_object_id_t kAclGroupIngressOid = 0xb00000000058f; +constexpr sai_object_id_t kAclGroupEgressOid = 0xb000000000591; +constexpr sai_object_id_t kAclGroupLookupOid = 0xb000000000592; +constexpr sai_object_id_t kAclTableIngressOid = 0x7000000000606; +constexpr sai_object_id_t kAclGroupMemberIngressOid = 0xc000000000607; +constexpr sai_object_id_t kAclIngressRuleOid1 = 1001; +constexpr sai_object_id_t kAclIngressRuleOid2 = 1002; +constexpr sai_object_id_t kAclMeterOid1 = 2001; +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 char *kAclIngressTableName = "ACL_PUNT_TABLE"; + +// 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) +{ + if (attr_list == nullptr) + { + return false; + } + for (int i = 0; i < 3; ++i) + { + switch (attr_list[i].id) + { + case SAI_ACL_TABLE_GROUP_ATTR_ACL_STAGE: + if (attr_list[i].value.s32 != expected_stage) + { + return false; + } + break; + case SAI_ACL_TABLE_GROUP_ATTR_TYPE: + if (attr_list[i].value.s32 != SAI_ACL_TABLE_GROUP_TYPE_PARALLEL) + { + return false; + } + break; + case SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST: + if (attr_list[i].value.s32list.count != 1 || + attr_list[i].value.s32list.list[0] != SAI_ACL_BIND_POINT_TYPE_SWITCH) + { + return false; + } + break; + default: + return false; + } + } + return true; +} + +// Check the ACL stage sai_attribute_t list for ACL table +bool MatchSaiAttributeAclTableStage(const sai_acl_stage_t expected_stage, const sai_attribute_t *attr_list) +{ + if (attr_list == nullptr) + { + return false; + } + if (attr_list[0].id != SAI_ACL_TABLE_GROUP_ATTR_ACL_STAGE || attr_list[0].value.s32 != expected_stage) + { + return false; + } + + return true; +} + +bool MatchSaiSwitchAttrByAclStage(const sai_switch_attr_t expected_switch_attr, const sai_object_id_t group_oid, + const sai_attribute_t *attr) +{ + if (attr->id != expected_switch_attr || attr->value.oid != group_oid) + { + return false; + } + return true; +} + +std::string BuildMatchFieldJsonStrKindSaiField(std::string sai_field, std::string format = P4_FORMAT_HEX_STRING, + uint32_t bitwidth = 0) +{ + nlohmann::json match_json; + match_json[kAclMatchFieldKind] = kAclMatchFieldSaiField; + match_json[kAclMatchFieldSaiField] = sai_field; + match_json[kAclMatchFieldFormat] = format; + if (format != P4_FORMAT_STRING) + { + match_json[kAclMatchFieldBitwidth] = bitwidth; + } + return match_json.dump(); +} + +std::string BuildMatchFieldJsonStrKindComposite(std::vector elements, + std::string format = P4_FORMAT_HEX_STRING, uint32_t bitwidth = 0) +{ + nlohmann::json match_json; + match_json[kAclMatchFieldKind] = kAclMatchFieldKindComposite; + for (const auto element : elements) + { + match_json[kAclMatchFieldElements].push_back(element); + } + match_json[kAclMatchFieldFormat] = format; + match_json[kAclMatchFieldBitwidth] = bitwidth; + return match_json.dump(); +} + +std::string BuildMatchFieldJsonStrKindUdf(std::string base, uint32_t offset, std::string format = P4_FORMAT_HEX_STRING, + uint32_t bitwidth = 0) +{ + nlohmann::json match_json; + match_json[kAclMatchFieldKind] = kAclMatchFieldKindUdf; + match_json[kAclUdfBase] = base; + match_json[kAclUdfOffset] = offset; + match_json[kAclMatchFieldFormat] = format; + match_json[kAclMatchFieldBitwidth] = bitwidth; + return match_json.dump(); +} + +// Check if P4AclTableDefinitionAppDbEntry to P4AclTableDefinition mapping is as +// expected +void IsExpectedAclTableDefinitionMapping(const P4AclTableDefinition &acl_table_def, + const P4AclTableDefinitionAppDbEntry &app_db_entry) +{ + EXPECT_EQ(app_db_entry.acl_table_name, acl_table_def.acl_table_name); + EXPECT_EQ(app_db_entry.priority, acl_table_def.priority); + EXPECT_EQ(app_db_entry.size, acl_table_def.size); + EXPECT_EQ(app_db_entry.meter_unit, acl_table_def.meter_unit); + EXPECT_EQ(app_db_entry.counter_unit, acl_table_def.counter_unit); + for (const auto &raw_match_field : app_db_entry.match_field_lookup) + { + const auto &p4_match = fvField(raw_match_field); + const auto &aggr_match_str = fvValue(raw_match_field); + try + { + auto aggr_match_json = nlohmann::json::parse(aggr_match_str); + ASSERT_TRUE(aggr_match_json.is_object()); + auto kind = aggr_match_json[kAclMatchFieldKind]; + ASSERT_TRUE(!kind.is_null() && kind.is_string()); + if (kind == kAclMatchFieldKindComposite) + { + auto format_str = aggr_match_json[kAclMatchFieldFormat]; + ASSERT_FALSE(format_str.is_null() || !format_str.is_string()); + auto format_it = formatLookup.find(format_str); + ASSERT_NE(formatLookup.end(), format_it); + if (format_it->second != Format::STRING) + { + // bitwidth is required if the format is not "STRING" + auto bitwidth = aggr_match_json[kAclMatchFieldBitwidth]; + ASSERT_FALSE(bitwidth.is_null() || !bitwidth.is_number()); + } + auto elements = aggr_match_json[kAclMatchFieldElements]; + ASSERT_TRUE(!elements.is_null() && elements.is_array()); + if (elements[0][kAclMatchFieldKind] == kAclMatchFieldSaiField) + { + const auto &composite_sai_match_it = acl_table_def.composite_sai_match_fields_lookup.find(p4_match); + ASSERT_NE(acl_table_def.composite_sai_match_fields_lookup.end(), composite_sai_match_it); + for (const auto &element : composite_sai_match_it->second) + { + EXPECT_EQ(BYTE_BITWIDTH * IPV6_SINGLE_WORD_BYTES_LENGTH, element.bitwidth); + } + } + else if (elements[0][kAclMatchFieldKind] == kAclMatchFieldKindUdf) + { + const auto &composite_udf_match_it = acl_table_def.udf_fields_lookup.find(p4_match); + ASSERT_NE(acl_table_def.udf_fields_lookup.end(), composite_udf_match_it); + for (size_t i = 0; i < composite_udf_match_it->second.size(); i++) + { + EXPECT_EQ(elements[i][kAclMatchFieldBitwidth], + composite_udf_match_it->second[i].length * BYTE_BITWIDTH); + EXPECT_EQ(elements[i][kAclUdfOffset], composite_udf_match_it->second[i].offset); + EXPECT_EQ(app_db_entry.acl_table_name + "-" + p4_match + "-" + std::to_string(i), + composite_udf_match_it->second[i].group_id); + ASSERT_NE(udfBaseLookup.end(), udfBaseLookup.find(elements[i][kAclUdfBase])); + EXPECT_EQ(udfBaseLookup.find(elements[i][kAclUdfBase])->second, + composite_udf_match_it->second[i].base); + } + } + else + { + FAIL() << "Invalid kind for composite field element: " << elements[0][kAclMatchFieldKind]; + } + } + else if (kind == kAclMatchFieldKindUdf) + { + const auto &udf_match_it = acl_table_def.udf_fields_lookup.find(p4_match); + ASSERT_NE(acl_table_def.udf_fields_lookup.end(), udf_match_it); + EXPECT_EQ(1, udf_match_it->second.size()); + EXPECT_EQ(aggr_match_json[kAclMatchFieldBitwidth], udf_match_it->second[0].length * BYTE_BITWIDTH); + EXPECT_EQ(aggr_match_json[kAclUdfOffset], udf_match_it->second[0].offset); + EXPECT_EQ(app_db_entry.acl_table_name + "-" + p4_match + "-0", udf_match_it->second[0].group_id); + ASSERT_NE(udfBaseLookup.end(), udfBaseLookup.find(aggr_match_json[kAclUdfBase])); + EXPECT_EQ(udfBaseLookup.find(aggr_match_json[kAclUdfBase])->second, udf_match_it->second[0].base); + } + else + { + EXPECT_EQ(kAclMatchFieldSaiField, kind); + auto match_field = aggr_match_json[kAclMatchFieldSaiField]; + ASSERT_TRUE(!match_field.is_null() && match_field.is_string()); + auto field_suffix = swss::tokenize(match_field, kFieldDelimiter); + const auto &sai_field = field_suffix[0]; + ASSERT_NE(aclMatchEntryAttrLookup.end(), aclMatchEntryAttrLookup.find(sai_field)); + ASSERT_NE(aclMatchTableAttrLookup.end(), aclMatchTableAttrLookup.find(sai_field)); + EXPECT_EQ(aclMatchEntryAttrLookup.find(sai_field)->second, + acl_table_def.sai_match_field_lookup.find(p4_match)->second.entry_attr); + EXPECT_EQ(aclMatchTableAttrLookup.find(sai_field)->second, + acl_table_def.sai_match_field_lookup.find(p4_match)->second.table_attr); + } + } + catch (std::exception &ex) + { + FAIL() << "Exception when parsing match field. ex: " << ex.what(); + } + } + for (const auto &action_field : app_db_entry.action_field_lookup) + { + const auto &sai_action_param_it = acl_table_def.rule_action_field_lookup.find(fvField(action_field)); + ASSERT_NE(acl_table_def.rule_action_field_lookup.end(), sai_action_param_it); + + for (size_t i = 0; i < fvValue(action_field).size(); ++i) + { + ASSERT_NE(aclActionLookup.end(), aclActionLookup.find(fvValue(action_field)[i].sai_action)); + EXPECT_EQ(sai_action_param_it->second[i].action, + aclActionLookup.find(fvValue(action_field)[i].sai_action)->second); + } + } + + for (const auto &packet_action_color : app_db_entry.packet_action_color_lookup) + { + const auto &sai_action_color_it = + acl_table_def.rule_packet_action_color_lookup.find(fvField(packet_action_color)); + ASSERT_NE(acl_table_def.rule_packet_action_color_lookup.end(), sai_action_color_it); + for (size_t i = 0; i < fvValue(packet_action_color).size(); ++i) + { + if (fvValue(packet_action_color)[i].packet_color.empty()) + { + // Not a colored packet action, should be ACL entry attribute + // SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION instead of ACL policy + // attribute + const auto rule_action_it = acl_table_def.rule_action_field_lookup.find(fvField(packet_action_color)); + ASSERT_NE(acl_table_def.rule_action_field_lookup.end(), rule_action_it); + bool found_packet_action = false; + for (const auto &action_with_param : rule_action_it->second) + { + if (action_with_param.param_value == fvValue(packet_action_color)[i].packet_action) + { + // Only one packet action is allowed and no parameter should be + // added for SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION attribute. + // Return false if multiple packet actions are found or + // parameter name is not empty + EXPECT_FALSE(found_packet_action || !action_with_param.param_name.empty()); + found_packet_action = true; + } + } + // No packet action was found, return false. + EXPECT_TRUE(found_packet_action); + continue; + } + const auto &packet_color_policer_attr_it = + aclPacketColorPolicerAttrLookup.find(fvValue(packet_action_color)[i].packet_color); + const auto &packet_action_it = aclPacketActionLookup.find(fvValue(packet_action_color)[i].packet_action); + ASSERT_NE(aclPacketColorPolicerAttrLookup.end(), packet_color_policer_attr_it); + ASSERT_NE(aclPacketActionLookup.end(), packet_action_it); + + const auto &sai_packet_action_it = sai_action_color_it->second.find(packet_color_policer_attr_it->second); + ASSERT_NE(sai_action_color_it->second.end(), sai_packet_action_it); + EXPECT_EQ(sai_packet_action_it->second, packet_action_it->second); + } + } +} + +// Check if P4AclRuleAppDbEntry to P4AclRule field mapping is as +// expected given table definition. Specific match and action value +// validation should be done in caller method +void IsExpectedAclRuleMapping(const P4AclRule *acl_rule, const P4AclRuleAppDbEntry &app_db_entry, + const P4AclTableDefinition &table_def) +{ + // Check table name and priority + EXPECT_EQ(app_db_entry.acl_table_name, acl_rule->acl_table_name); + EXPECT_EQ(app_db_entry.priority, acl_rule->priority); + // Check match field + for (const auto &app_db_match_fv : app_db_entry.match_fvs) + { + // TODO: Fake UDF field until SAI supports it + if (table_def.udf_fields_lookup.find(fvField(app_db_match_fv)) != table_def.udf_fields_lookup.end()) + continue; + const auto &composite_sai_match_fields_it = + table_def.composite_sai_match_fields_lookup.find(fvField(app_db_match_fv)); + if (composite_sai_match_fields_it != table_def.composite_sai_match_fields_lookup.end()) + { + for (const auto &composite_sai_match_field : composite_sai_match_fields_it->second) + { + const auto &match_fv_it = acl_rule->match_fvs.find(composite_sai_match_field.entry_attr); + ASSERT_NE(acl_rule->match_fvs.end(), match_fv_it); + EXPECT_TRUE(match_fv_it->second.aclfield.enable); + } + continue; + } + const auto &match_field_it = table_def.sai_match_field_lookup.find(fvField(app_db_match_fv)); + ASSERT_NE(table_def.sai_match_field_lookup.end(), match_field_it); + const auto &match_fv_it = acl_rule->match_fvs.find(match_field_it->second.entry_attr); + ASSERT_NE(acl_rule->match_fvs.end(), match_fv_it); + EXPECT_TRUE(match_fv_it->second.aclfield.enable); + } + // Check action field + ASSERT_EQ(acl_rule->p4_action, app_db_entry.action); + const auto &actions_field_it = table_def.rule_action_field_lookup.find(app_db_entry.action); + const auto &packet_action_color_it = table_def.rule_packet_action_color_lookup.find(app_db_entry.action); + ASSERT_NE(table_def.rule_action_field_lookup.end(), actions_field_it); + ASSERT_NE(table_def.rule_packet_action_color_lookup.end(), packet_action_color_it); + if (actions_field_it != table_def.rule_action_field_lookup.end()) + { + for (const auto &action_field : actions_field_it->second) + { + ASSERT_NE(acl_rule->action_fvs.find(action_field.action), acl_rule->action_fvs.end()); + } + } + // Check meter field value + if (packet_action_color_it != table_def.rule_packet_action_color_lookup.end() && + !packet_action_color_it->second.empty()) + { + ASSERT_EQ(acl_rule->meter.packet_color_actions, packet_action_color_it->second); + } + if (!table_def.meter_unit.empty()) + { + EXPECT_TRUE(acl_rule->meter.enabled); + EXPECT_EQ(SAI_POLICER_MODE_TR_TCM, acl_rule->meter.mode); + EXPECT_EQ(app_db_entry.meter.cir, acl_rule->meter.cir); + EXPECT_EQ(app_db_entry.meter.cburst, acl_rule->meter.cburst); + EXPECT_EQ(app_db_entry.meter.pir, acl_rule->meter.pir); + EXPECT_EQ(app_db_entry.meter.pburst, acl_rule->meter.pburst); + if (table_def.meter_unit == P4_METER_UNIT_BYTES) + { + EXPECT_EQ(SAI_METER_TYPE_BYTES, acl_rule->meter.type); + } + if (table_def.meter_unit == P4_METER_UNIT_PACKETS) + { + EXPECT_EQ(SAI_METER_TYPE_PACKETS, acl_rule->meter.type); + } + } + // Check counter field value + if (table_def.counter_unit.empty()) + { + EXPECT_FALSE(acl_rule->counter.packets_enabled); + EXPECT_FALSE(acl_rule->counter.bytes_enabled); + return; + } + if (table_def.counter_unit == P4_COUNTER_UNIT_BOTH) + { + EXPECT_TRUE(acl_rule->counter.bytes_enabled && acl_rule->counter.packets_enabled); + } + else if (table_def.counter_unit == P4_COUNTER_UNIT_BYTES) + { + EXPECT_TRUE(acl_rule->counter.bytes_enabled && !acl_rule->counter.packets_enabled); + } + else + { + EXPECT_TRUE(table_def.counter_unit == P4_COUNTER_UNIT_PACKETS && !acl_rule->counter.bytes_enabled && + acl_rule->counter.packets_enabled); + } +} + +std::vector getDefaultTableDefFieldValueTuples() +{ + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{kStage, STAGE_INGRESS}); + attributes.push_back(swss::FieldValueTuple{kSize, "123"}); + attributes.push_back(swss::FieldValueTuple{kPriority, "234"}); + attributes.push_back(swss::FieldValueTuple{"meter/unit", P4_METER_UNIT_BYTES}); + attributes.push_back(swss::FieldValueTuple{"counter/unit", P4_COUNTER_UNIT_BOTH}); + attributes.push_back( + swss::FieldValueTuple{"match/ether_type", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ETHER_TYPE)}); + attributes.push_back( + swss::FieldValueTuple{"match/ether_dst", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_MAC, P4_FORMAT_MAC)}); + attributes.push_back( + swss::FieldValueTuple{"match/ipv6_dst", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_IPV6, P4_FORMAT_IPV6)}); + attributes.push_back(swss::FieldValueTuple{ + "match/is_ip", + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_IP)}); + attributes.push_back(swss::FieldValueTuple{ + "match/is_ipv4", + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_IPV4ANY)}); + attributes.push_back(swss::FieldValueTuple{ + "match/is_ipv6", + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_IPV6ANY)}); + attributes.push_back(swss::FieldValueTuple{ + "match/is_arp", + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_ARP)}); + attributes.push_back(swss::FieldValueTuple{ + "match/is_arp_request", BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + + P4_IP_TYPE_BIT_ARP_REQUEST)}); + attributes.push_back(swss::FieldValueTuple{ + "match/is_arp_reply", BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + + P4_IP_TYPE_BIT_ARP_REPLY)}); + attributes.push_back( + swss::FieldValueTuple{"match/ipv6_next_header", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IPV6_NEXT_HEADER)}); + attributes.push_back(swss::FieldValueTuple{"match/ttl", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TTL)}); + attributes.push_back( + 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{"action/copy_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_COPY\",\"packet_color\":\"SAI_" + "PACKET_" + "COLOR_GREEN\"},{\"action\":\"SAI_ACL_ENTRY_ATTR_ACTION_SET_TC\"," + "\"param\":\"traffic_class\"}]"}); + attributes.push_back(swss::FieldValueTuple{"action/punt_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_TRAP\"},{\"action\":\"SAI_ACL_" + "ENTRY_" + "ATTR_ACTION_SET_TC\",\"param\":\"traffic_class\"}]"}); + return attributes; +} + +P4AclTableDefinitionAppDbEntry getDefaultAclTableDefAppDbEntry() +{ + P4AclTableDefinitionAppDbEntry app_db_entry; + app_db_entry.acl_table_name = kAclIngressTableName; + app_db_entry.size = 123; + 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; + // 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); + app_db_entry.match_field_lookup["ether_src"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_SRC_MAC, P4_FORMAT_MAC); + app_db_entry.match_field_lookup["ipv6_dst"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_IPV6, P4_FORMAT_IPV6); + app_db_entry.match_field_lookup["ipv6_next_header"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IPV6_NEXT_HEADER); + app_db_entry.match_field_lookup["ttl"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TTL); + app_db_entry.match_field_lookup["in_ports"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IN_PORTS, P4_FORMAT_STRING); + app_db_entry.match_field_lookup["in_port"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IN_PORT, P4_FORMAT_STRING); + app_db_entry.match_field_lookup["out_ports"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_OUT_PORTS, P4_FORMAT_STRING); + app_db_entry.match_field_lookup["out_port"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_OUT_PORT, P4_FORMAT_STRING); + app_db_entry.match_field_lookup["is_ip"] = + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_IP); + app_db_entry.match_field_lookup["is_ipv4"] = + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_IPV4ANY); + app_db_entry.match_field_lookup["is_ipv6"] = + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_IPV6ANY); + app_db_entry.match_field_lookup["is_arp"] = + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_ARP); + app_db_entry.match_field_lookup["is_arp_request"] = BuildMatchFieldJsonStrKindSaiField( + std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_ARP_REQUEST); + app_db_entry.match_field_lookup["is_arp_reply"] = + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + P4_IP_TYPE_BIT_ARP_REPLY); + app_db_entry.match_field_lookup["tcp_flags"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TCP_FLAGS); + app_db_entry.match_field_lookup["ip_flags"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IP_FLAGS); + app_db_entry.match_field_lookup["l4_src_port"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_L4_SRC_PORT); + app_db_entry.match_field_lookup["l4_dst_port"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_L4_DST_PORT); + app_db_entry.match_field_lookup["ip_id"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IP_ID); + app_db_entry.match_field_lookup["inner_l4_src_port"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_L4_SRC_PORT); + app_db_entry.match_field_lookup["inner_l4_dst_port"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_L4_DST_PORT); + app_db_entry.match_field_lookup["dscp"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DSCP); + app_db_entry.match_field_lookup["inner_ip_src"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_SRC_IP, P4_FORMAT_IPV4); + app_db_entry.match_field_lookup["inner_ip_dst"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_DST_IP, P4_FORMAT_IPV4); + app_db_entry.match_field_lookup["inner_ipv6_src"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_SRC_IPV6, P4_FORMAT_IPV6); + app_db_entry.match_field_lookup["inner_ipv6_dst"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_DST_IPV6, P4_FORMAT_IPV6); + app_db_entry.match_field_lookup["ip_src"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_SRC_IP, P4_FORMAT_IPV4); + app_db_entry.match_field_lookup["ip_dst"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_IP, P4_FORMAT_IPV4); + app_db_entry.match_field_lookup["ipv6_src"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_SRC_IPV6, P4_FORMAT_IPV6); + app_db_entry.match_field_lookup["tc"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TRAFFIC_CLASS); + app_db_entry.match_field_lookup["icmp_type"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMP_TYPE); + app_db_entry.match_field_lookup["icmp_code"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMP_CODE); + app_db_entry.match_field_lookup["tos"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TOS); + app_db_entry.match_field_lookup["icmpv6_type"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMPV6_TYPE); + app_db_entry.match_field_lookup["icmpv6_code"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMPV6_CODE); + app_db_entry.match_field_lookup["ecn"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ECN); + app_db_entry.match_field_lookup["inner_ip_protocol"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_INNER_IP_PROTOCOL); + app_db_entry.match_field_lookup["ip_protocol"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IP_PROTOCOL); + app_db_entry.match_field_lookup["ipv6_flow_label"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IPV6_FLOW_LABEL); + app_db_entry.match_field_lookup["tunnel_vni"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TUNNEL_VNI); + app_db_entry.match_field_lookup["ip_frag"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IP_FRAG, P4_FORMAT_STRING); + app_db_entry.match_field_lookup["packet_vlan"] = + BuildMatchFieldJsonStrKindSaiField(P4_MATCH_PACKET_VLAN, P4_FORMAT_STRING); + app_db_entry.match_field_lookup["outer_vlan_pri"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_OUTER_VLAN_PRI); + app_db_entry.match_field_lookup["outer_vlan_id"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_OUTER_VLAN_ID); + app_db_entry.match_field_lookup["outer_vlan_cfi"] = BuildMatchFieldJsonStrKindSaiField(P4_MATCH_OUTER_VLAN_CFI); + 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["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))}, + P4_FORMAT_IPV6, 64); + app_db_entry.match_field_lookup["arp_tpa"] = BuildMatchFieldJsonStrKindComposite( + {nlohmann::json::parse(BuildMatchFieldJsonStrKindUdf("SAI_UDF_BASE_L3", 24, P4_FORMAT_HEX_STRING, 16)), + nlohmann::json::parse(BuildMatchFieldJsonStrKindUdf("SAI_UDF_BASE_L3", 26, P4_FORMAT_HEX_STRING, 16))}, + P4_FORMAT_HEX_STRING, 32); + app_db_entry.match_field_lookup["udf2"] = + BuildMatchFieldJsonStrKindUdf("SAI_UDF_BASE_L3", 56, P4_FORMAT_HEX_STRING, 16); + + // Action field mapping, from P4 action to SAI action + app_db_entry.action_field_lookup["set_packet_action"].push_back( + {.sai_action = P4_ACTION_PACKET_ACTION, .p4_param_name = "packet_action"}); + app_db_entry.action_field_lookup["copy_and_set_tc"].push_back( + {.sai_action = P4_ACTION_SET_TRAFFIC_CLASS, .p4_param_name = "traffic_class"}); + app_db_entry.action_field_lookup["punt_and_set_tc"].push_back( + {.sai_action = P4_ACTION_SET_TRAFFIC_CLASS, .p4_param_name = "traffic_class"}); + app_db_entry.packet_action_color_lookup["copy_and_set_tc"].push_back( + {.packet_action = P4_PACKET_ACTION_COPY, .packet_color = P4_PACKET_COLOR_GREEN}); + app_db_entry.packet_action_color_lookup["punt_and_set_tc"].push_back( + {.packet_action = P4_PACKET_ACTION_PUNT, .packet_color = EMPTY_STRING}); + app_db_entry.packet_action_color_lookup["punt_non_green_pk"].push_back( + {.packet_action = P4_PACKET_ACTION_PUNT, .packet_color = P4_PACKET_COLOR_YELLOW}); + app_db_entry.packet_action_color_lookup["punt_non_green_pk"].push_back( + {.packet_action = P4_PACKET_ACTION_PUNT, .packet_color = P4_PACKET_COLOR_RED}); + app_db_entry.action_field_lookup["redirect"].push_back( + {.sai_action = P4_ACTION_REDIRECT, .p4_param_name = "target"}); + app_db_entry.action_field_lookup["endpoint_ip"].push_back( + {.sai_action = P4_ACTION_ENDPOINT_IP, .p4_param_name = "ip_address"}); + app_db_entry.action_field_lookup["mirror_ingress"].push_back( + {.sai_action = P4_ACTION_MIRROR_INGRESS, .p4_param_name = "target"}); + app_db_entry.action_field_lookup["mirror_egress"].push_back( + {.sai_action = P4_ACTION_MIRROR_EGRESS, .p4_param_name = "target"}); + app_db_entry.action_field_lookup["set_packet_color"].push_back( + {.sai_action = P4_ACTION_SET_PACKET_COLOR, .p4_param_name = "packet_color"}); + app_db_entry.action_field_lookup["set_src_mac"].push_back( + {.sai_action = P4_ACTION_SET_SRC_MAC, .p4_param_name = "mac_address"}); + app_db_entry.action_field_lookup["set_dst_mac"].push_back( + {.sai_action = P4_ACTION_SET_DST_MAC, .p4_param_name = "mac_address"}); + app_db_entry.action_field_lookup["set_src_ip"].push_back( + {.sai_action = P4_ACTION_SET_SRC_IP, .p4_param_name = "ip_address"}); + app_db_entry.action_field_lookup["set_dst_ip"].push_back( + {.sai_action = P4_ACTION_SET_DST_IP, .p4_param_name = "ip_address"}); + app_db_entry.action_field_lookup["set_src_ipv6"].push_back( + {.sai_action = P4_ACTION_SET_SRC_IPV6, .p4_param_name = "ip_address"}); + app_db_entry.action_field_lookup["set_dst_ipv6"].push_back( + {.sai_action = P4_ACTION_SET_DST_IPV6, .p4_param_name = "ip_address"}); + app_db_entry.action_field_lookup["set_dscp_and_ecn"].push_back( + {.sai_action = P4_ACTION_SET_DSCP, .p4_param_name = "dscp"}); + app_db_entry.action_field_lookup["set_dscp_and_ecn"].push_back( + {.sai_action = P4_ACTION_SET_ECN, .p4_param_name = "ecn"}); + app_db_entry.action_field_lookup["set_inner_vlan"].push_back( + {.sai_action = P4_ACTION_SET_INNER_VLAN_PRIORITY, .p4_param_name = "vlan_pri"}); + app_db_entry.action_field_lookup["set_inner_vlan"].push_back( + {.sai_action = P4_ACTION_SET_INNER_VLAN_ID, .p4_param_name = "vlan_id"}); + app_db_entry.action_field_lookup["set_outer_vlan"].push_back( + {.sai_action = P4_ACTION_SET_OUTER_VLAN_PRIORITY, .p4_param_name = "vlan_pri"}); + app_db_entry.action_field_lookup["set_outer_vlan"].push_back( + {.sai_action = P4_ACTION_SET_OUTER_VLAN_ID, .p4_param_name = "vlan_id"}); + app_db_entry.action_field_lookup["set_l4_src_port"].push_back( + {.sai_action = P4_ACTION_SET_L4_SRC_PORT, .p4_param_name = "port"}); + app_db_entry.action_field_lookup["set_l4_dst_port"].push_back( + {.sai_action = P4_ACTION_SET_L4_DST_PORT, .p4_param_name = "port"}); + app_db_entry.action_field_lookup["flood"].push_back({.sai_action = P4_ACTION_FLOOD, .p4_param_name = EMPTY_STRING}); + app_db_entry.action_field_lookup["decrement_ttl"].push_back( + {.sai_action = P4_ACTION_DECREMENT_TTL, .p4_param_name = EMPTY_STRING}); + app_db_entry.action_field_lookup["do_not_learn"].push_back( + {.sai_action = P4_ACTION_SET_DO_NOT_LEARN, .p4_param_name = EMPTY_STRING}); + 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"}); + return app_db_entry; +} + +std::vector getDefaultRuleFieldValueTuples() +{ + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{kAction, "copy_and_set_tc"}); + attributes.push_back(swss::FieldValueTuple{"param/traffic_class", "0x20"}); + 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", "..."}); + return attributes; +} + +P4AclRuleAppDbEntry getDefaultAclRuleAppDbEntryWithoutAction() +{ + 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\": \"0xff11223\",\"match/udf2\": " + "\"0x9876 & 0xAAAA\",\"priority\":100}"; + // ACL meter fields + app_db_entry.meter.enabled = true; + app_db_entry.meter.cir = 80; + app_db_entry.meter.cburst = 80; + app_db_entry.meter.pir = 200; + app_db_entry.meter.pburst = 200; + return app_db_entry; +} + +const std::string concatTableNameAndRuleKey(const std::string &table_name, const std::string &rule_key) +{ + return table_name + kTableKeyDelimiter + rule_key; +} + +} // namespace + +class AclManagerTest : public ::testing::Test +{ + protected: + AclManagerTest() + { + setUpMockApi(); + setUpCoppOrch(); + setUpSwitchOrch(); + setUpP4Orch(); + // const auto& acl_groups = gSwitchOrch->getAclGroupOidsBindingToSwitch(); + // 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_NE(acl_groups.end(), acl_groups.find(SAI_ACL_STAGE_EGRESS)); + // EXPECT_EQ(kAclGroupEgressOid, acl_groups.at(SAI_ACL_STAGE_EGRESS)); + // EXPECT_NE(acl_groups.end(), acl_groups.find(SAI_ACL_STAGE_PRE_INGRESS)); + // EXPECT_EQ(kAclGroupLookupOid, acl_groups.at(SAI_ACL_STAGE_PRE_INGRESS)); + 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), + kMirrorSessionOid2); + } + + ~AclManagerTest() + { + cleanupAclManagerTest(); + } + + void cleanupAclManagerTest() + { + EXPECT_CALL(mock_sai_udf_, remove_udf_match(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + delete gP4Orch; + delete copp_orch_; + delete gSwitchOrch; + } + + void setUpMockApi() + { + mock_sai_acl = &mock_sai_acl_; + mock_sai_serialize = &mock_sai_serialize_; + mock_sai_policer = &mock_sai_policer_; + mock_sai_hostif = &mock_sai_hostif_; + mock_sai_switch = &mock_sai_switch_; + mock_sai_udf = &mock_sai_udf_; + sai_acl_api->create_acl_table = create_acl_table; + sai_acl_api->remove_acl_table = remove_acl_table; + sai_acl_api->create_acl_table_group = create_acl_table_group; + sai_acl_api->remove_acl_table_group = remove_acl_table_group; + sai_acl_api->create_acl_table_group_member = create_acl_table_group_member; + sai_acl_api->remove_acl_table_group_member = remove_acl_table_group_member; + sai_acl_api->get_acl_counter_attribute = get_acl_counter_attribute; + sai_acl_api->create_acl_entry = create_acl_entry; + sai_acl_api->remove_acl_entry = remove_acl_entry; + sai_acl_api->set_acl_entry_attribute = set_acl_entry_attribute; + sai_acl_api->create_acl_counter = create_acl_counter; + sai_acl_api->remove_acl_counter = remove_acl_counter; + sai_policer_api->create_policer = create_policer; + sai_policer_api->remove_policer = remove_policer; + sai_policer_api->get_policer_stats = get_policer_stats; + sai_policer_api->set_policer_attribute = set_policer_attribute; + sai_hostif_api->create_hostif_table_entry = mock_create_hostif_table_entry; + sai_hostif_api->remove_hostif_table_entry = mock_remove_hostif_table_entry; + sai_hostif_api->create_hostif_trap_group = mock_create_hostif_trap_group; + sai_hostif_api->create_hostif_trap = mock_create_hostif_trap; + sai_hostif_api->create_hostif = mock_create_hostif; + sai_hostif_api->remove_hostif = mock_remove_hostif; + sai_hostif_api->create_hostif_user_defined_trap = mock_create_hostif_user_defined_trap; + sai_hostif_api->remove_hostif_user_defined_trap = mock_remove_hostif_user_defined_trap; + sai_switch_api->get_switch_attribute = mock_get_switch_attribute; + sai_switch_api->set_switch_attribute = mock_set_switch_attribute; + sai_udf_api->remove_udf = remove_udf; + sai_udf_api->create_udf = create_udf; + sai_udf_api->remove_udf_group = remove_udf_group; + sai_udf_api->create_udf_group = create_udf_group; + sai_udf_api->remove_udf_match = remove_udf_match; + sai_udf_api->create_udf_match = create_udf_match; + } + + void setUpCoppOrch() + { + // init copp orch + 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); + // add trap group and genetlink for each CPU queue + swss::Table app_copp_table(gAppDb, APP_COPP_TABLE_NAME); + for (uint64_t queue_num = 1; queue_num <= P4_CPU_QUEUE_MAX_NUM; queue_num++) + { + std::vector attrs; + attrs.push_back({"queue", std::to_string(queue_num)}); + attrs.push_back({"genetlink_name", "genl_packet"}); + attrs.push_back({"genetlink_mcgrp_name", "packets"}); + app_copp_table.set(GENL_PACKET_TRAP_GROUP_NAME_PREFIX + std::to_string(queue_num), attrs); + } + sai_object_id_t trap_group_oid = gTrapGroupStartOid; + sai_object_id_t hostif_oid = gHostifStartOid; + EXPECT_CALL(mock_sai_hostif_, create_hostif_trap_group(_, _, _, _)) + .Times(P4_CPU_QUEUE_MAX_NUM) + .WillRepeatedly( + DoAll(Invoke([&trap_group_oid](sai_object_id_t *oid, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) { *oid = ++trap_group_oid; }), + Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_hostif_, create_hostif(_, _, _, _)) + .Times(P4_CPU_QUEUE_MAX_NUM) + .WillRepeatedly( + DoAll(Invoke([&hostif_oid](sai_object_id_t *oid, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) { *oid = ++hostif_oid; }), + Return(SAI_STATUS_SUCCESS))); + + copp_orch_->addExistingData(&app_copp_table); + static_cast(copp_orch_)->doTask(); + } + + void setUpSwitchOrch() + { + EXPECT_CALL(mock_sai_serialize_, sai_serialize_object_id(_)).WillRepeatedly(Return(EMPTY_STRING)); + TableConnector stateDbSwitchTable(gStateDb, "SWITCH_CAPABILITY"); + TableConnector app_switch_table(gAppDb, APP_SWITCH_TABLE_NAME); + TableConnector conf_asic_sensors(gConfigDb, CFG_ASIC_SENSORS_TABLE_NAME); + std::vector switch_tables = {conf_asic_sensors, app_switch_table}; + gSwitchOrch = new SwitchOrch(gAppDb, switch_tables, stateDbSwitchTable); + } + + void setUpP4Orch() + { + EXPECT_CALL(mock_sai_serialize_, sai_serialize_object_id(_)).WillRepeatedly(Return(EMPTY_STRING)); + EXPECT_CALL(mock_sai_acl_, + create_acl_table_group( + _, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiAttributeAclGroupStage, SAI_ACL_STAGE_INGRESS, std::placeholders::_1)))) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclGroupIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, + create_acl_table_group( + _, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiAttributeAclGroupStage, SAI_ACL_STAGE_EGRESS, std::placeholders::_1)))) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclGroupEgressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, + create_acl_table_group(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiAttributeAclGroupStage, SAI_ACL_STAGE_PRE_INGRESS, + std::placeholders::_1)))) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclGroupLookupOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_switch_, + set_switch_attribute(Eq(gSwitchId), + Truly(std::bind(MatchSaiSwitchAttrByAclStage, SAI_SWITCH_ATTR_INGRESS_ACL, + kAclGroupIngressOid, std::placeholders::_1)))) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_switch_, + set_switch_attribute(Eq(gSwitchId), + Truly(std::bind(MatchSaiSwitchAttrByAclStage, SAI_SWITCH_ATTR_EGRESS_ACL, + kAclGroupEgressOid, std::placeholders::_1)))) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_switch_, + set_switch_attribute(Eq(gSwitchId), + Truly(std::bind(MatchSaiSwitchAttrByAclStage, SAI_SWITCH_ATTR_PRE_INGRESS_ACL, + kAclGroupLookupOid, std::placeholders::_1)))) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf_match(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); + std::vector p4_tables; + gP4Orch = new P4Orch(gAppDb, p4_tables, gVrfOrch, copp_orch_); + acl_table_manager_ = gP4Orch->getAclTableManager(); + acl_rule_manager_ = gP4Orch->getAclRuleManager(); + p4_oid_mapper_ = acl_table_manager_->m_p4OidMapper; + } + + void AddDefaultUserTrapsSaiCalls(sai_object_id_t *user_defined_trap_oid) + { + EXPECT_CALL(mock_sai_hostif_, create_hostif_user_defined_trap(_, _, _, _)) + .Times(P4_CPU_QUEUE_MAX_NUM) + .WillRepeatedly(DoAll(Invoke([user_defined_trap_oid]( + sai_object_id_t *oid, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) { *oid = ++(*user_defined_trap_oid); }), + Return(SAI_STATUS_SUCCESS))); + } + + void AddDefaultIngressTable() + { + const auto &app_db_entry = getDefaultAclTableDefAppDbEntry(); + 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(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + 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)); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + ASSERT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddTableRequest(app_db_entry)); + ASSERT_NO_FATAL_FAILURE( + IsExpectedAclTableDefinitionMapping(*GetAclTable(app_db_entry.acl_table_name), app_db_entry)); + } + + void DrainTableTuples() + { + acl_table_manager_->drain(); + } + void EnqueueTableTuple(const swss::KeyOpFieldsValuesTuple &entry) + { + acl_table_manager_->enqueue(entry); + } + + void DrainRuleTuples() + { + acl_rule_manager_->drain(); + } + void EnqueueRuleTuple(const swss::KeyOpFieldsValuesTuple &entry) + { + acl_rule_manager_->enqueue(entry); + } + + ReturnCodeOr DeserializeAclTableDefinitionAppDbEntry( + const std::string &key, const std::vector &attributes) + { + return acl_table_manager_->deserializeAclTableDefinitionAppDbEntry(key, attributes); + } + + ReturnCodeOr DeserializeAclRuleAppDbEntry(const std::string &acl_table_name, + const std::string &key, + const std::vector &attributes) + { + return acl_rule_manager_->deserializeAclRuleAppDbEntry(acl_table_name, key, attributes); + } + + P4AclTableDefinition *GetAclTable(const std::string &acl_table_name) + { + return acl_table_manager_->getAclTable(acl_table_name); + } + + P4AclRule *GetAclRule(const std::string &acl_table_name, const std::string &acl_rule_key) + { + return acl_rule_manager_->getAclRule(acl_table_name, acl_rule_key); + } + + ReturnCode ProcessAddTableRequest(const P4AclTableDefinitionAppDbEntry &app_db_entry) + { + return acl_table_manager_->processAddTableRequest(app_db_entry); + } + + ReturnCode ProcessDeleteTableRequest(const std::string &acl_table_name) + { + return acl_table_manager_->processDeleteTableRequest(acl_table_name); + } + + ReturnCode ProcessAddRuleRequest(const std::string &acl_rule_key, const P4AclRuleAppDbEntry &app_db_entry) + { + return acl_rule_manager_->processAddRuleRequest(acl_rule_key, app_db_entry); + } + + ReturnCode ProcessUpdateRuleRequest(const P4AclRuleAppDbEntry &app_db_entry, const P4AclRule &old_acl_rule) + { + return acl_rule_manager_->processUpdateRuleRequest(app_db_entry, old_acl_rule); + } + + ReturnCode ProcessDeleteRuleRequest(const std::string &acl_table_name, const std::string &acl_rule_key) + { + return acl_rule_manager_->processDeleteRuleRequest(acl_table_name, acl_rule_key); + } + + void DoAclCounterStatsTask() + { + acl_rule_manager_->doAclCounterStatsTask(); + } + + StrictMock mock_sai_acl_; + StrictMock mock_sai_serialize_; + StrictMock mock_sai_policer_; + StrictMock mock_sai_hostif_; + StrictMock mock_sai_switch_; + StrictMock mock_sai_udf_; + CoppOrch *copp_orch_; + P4OidMapper *p4_oid_mapper_; + p4orch::AclTableManager *acl_table_manager_; + p4orch::AclRuleManager *acl_rule_manager_; +}; + +TEST_F(AclManagerTest, DrainTableTuplesToProcessSetDelRequestSucceeds) +{ + const auto &p4rtAclTableName = + std::string(APP_P4RT_ACL_TABLE_DEFINITION_NAME) + kTableKeyDelimiter + kAclIngressTableName; + EnqueueTableTuple( + swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, getDefaultTableDefFieldValueTuples()})); + + // Drain table tuples to process SET request + 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))); + DrainTableTuples(); + EXPECT_NE(nullptr, GetAclTable(kAclIngressTableName)); + + // Drain table tuples to process DEL request + 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)); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, DEL_COMMAND, {}})); + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, DrainTableTuplesToProcessUpdateRequestExpectFails) +{ + const auto &p4rtAclTableName = + std::string(APP_P4RT_ACL_TABLE_DEFINITION_NAME) + kTableKeyDelimiter + kAclIngressTableName; + auto attributes = getDefaultTableDefFieldValueTuples(); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + + // Drain table tuples to process SET request, create a table with priority + // 234 + 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))); + DrainTableTuples(); + EXPECT_NE(nullptr, GetAclTable(kAclIngressTableName)); + + // Drain table tuples to process SET request, try to update table priority + // to 100: should fail to update. + attributes.push_back(swss::FieldValueTuple{kPriority, "100"}); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + DrainTableTuples(); + EXPECT_EQ(234, GetAclTable(kAclIngressTableName)->priority); +} + +TEST_F(AclManagerTest, DrainTableTuplesWithInvalidTableNameOpsFails) +{ + auto p4rtAclTableName = std::string("UNDEFINED") + kTableKeyDelimiter + kAclIngressTableName; + EnqueueTableTuple( + swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, getDefaultTableDefFieldValueTuples()})); + // Drain table tuples to process SET request on invalid ACL definition table + // name: "UNDEFINED" + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); + + p4rtAclTableName = std::string(APP_P4RT_ACL_TABLE_DEFINITION_NAME) + kTableKeyDelimiter + kAclIngressTableName; + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, "UPDATE", getDefaultTableDefFieldValueTuples()})); + // Drain table tuples to process invalid operation: "UPDATE" + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, DrainTableTuplesWithInvalidFieldFails) +{ + auto attributes = getDefaultTableDefFieldValueTuples(); + const auto &p4rtAclTableName = + std::string(APP_P4RT_ACL_TABLE_DEFINITION_NAME) + kTableKeyDelimiter + kAclIngressTableName; + + // Invalid attribute field + attributes.push_back(swss::FieldValueTuple{"undefined", "undefined"}); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + // Drain table tuples to process SET request + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); + + // Invalid attribute field + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"undefined/undefined", "undefined"}); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + // Drain table tuples to process SET request + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); + + // Invalid meter unit value + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"meter/unit", "undefined"}); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + // Drain table tuples to process SET request + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); + + // Invalid counter unit value + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"counter/unit", "undefined"}); + EnqueueTableTuple(swss::KeyOpFieldsValuesTuple({p4rtAclTableName, SET_COMMAND, attributes})); + // Drain table tuples to process SET request + DrainTableTuples(); + EXPECT_EQ(nullptr, GetAclTable(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableSucceeds) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto acl_table = GetAclTable(kAclIngressTableName); + EXPECT_NE(nullptr, acl_table); +} + +TEST_F(AclManagerTest, CreatePuntTableFailsWhenUserTrapsSaiCallFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + EXPECT_CALL(mock_sai_hostif_, create_hostif_user_defined_trap(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gUserDefinedTrapStartOid + 1), Return(SAI_STATUS_SUCCESS))) + .WillOnce(Return(SAI_STATUS_INSUFFICIENT_RESOURCES)); + EXPECT_CALL(mock_sai_hostif_, create_hostif_table_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gHostifStartOid + 1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_hostif_, remove_hostif_table_entry(Eq(gHostifStartOid + 1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_hostif_, remove_hostif_user_defined_trap(Eq(gUserDefinedTrapStartOid + 1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + // The user defined traps fail to create + EXPECT_EQ(StatusCode::SWSS_RC_FULL, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + sai_object_id_t oid; + EXPECT_FALSE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_HOSTIF_USER_DEFINED_TRAP, + std::to_string(gUserDefinedTrapStartOid + 1), &oid)); + + EXPECT_CALL(mock_sai_hostif_, create_hostif_user_defined_trap(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gUserDefinedTrapStartOid + 1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_hostif_, create_hostif_table_entry(_, _, _, _)) + .WillOnce(Return(SAI_STATUS_INSUFFICIENT_RESOURCES)); + EXPECT_CALL(mock_sai_hostif_, remove_hostif_user_defined_trap(Eq(gUserDefinedTrapStartOid + 1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + // The hostif table entry fails to create + EXPECT_EQ(StatusCode::SWSS_RC_FULL, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + EXPECT_CALL(mock_sai_hostif_, create_hostif_user_defined_trap(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gUserDefinedTrapStartOid + 1), Return(SAI_STATUS_SUCCESS))) + .WillOnce(Return(SAI_STATUS_INSUFFICIENT_RESOURCES)); + EXPECT_CALL(mock_sai_hostif_, create_hostif_table_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gHostifStartOid + 1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_hostif_, remove_hostif_table_entry(Eq(gHostifStartOid + 1))) + .WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + // The 2nd user defined trap fails to create, the 1st hostif table entry fails + // to remove + EXPECT_EQ(StatusCode::SWSS_RC_FULL, ProcessAddTableRequest(app_db_entry)); +} + +TEST_F(AclManagerTest, DISABLED_CreatePuntTableFailsWhenUserTrapGroupOrHostifNotFound) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + const auto skip_cpu_queue = 1; + // init copp orch + 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)); + swss::Table app_copp_table(gAppDb, APP_COPP_TABLE_NAME); + // Clean up APP_COPP_TABLE_NAME table entries + for (int queue_num = 1; queue_num <= P4_CPU_QUEUE_MAX_NUM; queue_num++) + { + app_copp_table.del(GENL_PACKET_TRAP_GROUP_NAME_PREFIX + std::to_string(queue_num)); + } + cleanupAclManagerTest(); + copp_orch_ = new CoppOrch(gAppDb, APP_COPP_TABLE_NAME); + setUpSwitchOrch(); + // Update p4orch to use new copp orch + setUpP4Orch(); + // Fail to create ACL table because the trap group is absent + EXPECT_EQ("Trap group was not found given trap group name: " + std::string(GENL_PACKET_TRAP_GROUP_NAME_PREFIX) + + std::to_string(skip_cpu_queue), + ProcessAddTableRequest(app_db_entry).message()); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Create the trap group for CPU queue 1 without host interface(genl + // attributes) + std::vector attrs; + attrs.push_back({"queue", std::to_string(skip_cpu_queue)}); + // Add one COPP_TABLE entry with trap group info, without hostif info + app_copp_table.set(GENL_PACKET_TRAP_GROUP_NAME_PREFIX + std::to_string(skip_cpu_queue), attrs); + copp_orch_->addExistingData(&app_copp_table); + EXPECT_CALL(mock_sai_hostif_, create_hostif_trap_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gTrapGroupStartOid + skip_cpu_queue), Return(SAI_STATUS_SUCCESS))); + static_cast(copp_orch_)->doTask(); + // Fail to create ACL table because the host interface is absent + EXPECT_EQ("Hostif object id was not found given trap group - " + std::string(GENL_PACKET_TRAP_GROUP_NAME_PREFIX) + + std::to_string(skip_cpu_queue), + ProcessAddTableRequest(app_db_entry).message()); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableFailsWhenCapabilityExceeds) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + 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_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)); + EXPECT_EQ(StatusCode::SWSS_RC_FULL, ProcessAddTableRequest(app_db_entry)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableFailsWhenFailedToCreateTableGroupMember) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + 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_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_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).Times(3).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableRaisesCriticalStateWhenAclTableRecoveryFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + 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_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. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableRaisesCriticalStateWhenUdfGroupRecoveryFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + 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_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. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableRaisesCriticalStateWhenUdfRecoveryFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + 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_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)); + // 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. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddTableRequest(app_db_entry)); +} + +TEST_F(AclManagerTest, CreateIngressPuntTableFailsWhenFailedToCreateUdf) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + // Fail to create the first UDF, and success to remove the first UDF + // group + EXPECT_CALL(mock_sai_udf_, create_udf_group(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + 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")); + EXPECT_FALSE( + p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF_GROUP, std::string(kAclIngressTableName) + "-arp_tpa-0")); + + // Fail to create the second UDF group, and success to remove the first UDF + // group and UDF + 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_, 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)); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF, + std::string(kAclIngressTableName) + "-arp_tpa-0-base1-offset24")); + EXPECT_FALSE( + p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF_GROUP, std::string(kAclIngressTableName) + "-arp_tpa-0")); + + // Fail to create the second UDF group, and fail to remove the first UDF + // group + 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_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, remove_udf(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + // (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")); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF_GROUP, std::string(kAclIngressTableName) + "-arp_tpa-0")); + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_UDF_GROUP, std::string(kAclIngressTableName) + "-arp_tpa-0"); + + // Fail to create the second UDF group, and fail to remove the first UDF and + // UDF group + 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_, remove_udf(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + // (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")); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_UDF_GROUP, std::string(kAclIngressTableName) + "-arp_tpa-0")); +} + +TEST_F(AclManagerTest, CreatePuntTableWithInvalidStageFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + // Invalid stage + app_db_entry.stage = "RANDOM"; + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, CreatePuntTableWithInvalidSaiMatchFieldFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + // Invalid SAI match field + app_db_entry.match_field_lookup["random"] = BuildMatchFieldJsonStrKindSaiField("RANDOM"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + app_db_entry.match_field_lookup.erase("random"); + + // Invalid match field str - should be JSON str + app_db_entry.match_field_lookup["ether_type"] = P4_MATCH_ETHER_TYPE; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - should be object instead of array + app_db_entry.match_field_lookup["ether_type"] = "[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_" + "ETHER_TYPE\",\"format\":\"HEX_STRING\",\"bitwidth\":8}]"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing kind + app_db_entry.match_field_lookup["ether_type"] = "{\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE\",\"format\":" + "\"HEX_STRING\",\"bitwidth\":8}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid kind + app_db_entry.match_field_lookup["ether_type"] = + "{\"kind\":\"field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_ETHER_" + "TYPE\",\"format\":\"HEX_STRING\",\"bitwidth\":8}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing format + app_db_entry.match_field_lookup["ether_type"] = "{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_" + "ETHER_TYPE\",\"bitwidth\":8}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid format + app_db_entry.match_field_lookup["ether_type"] = "{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_" + "ETHER_TYPE\",\"format\":\"INVALID_TYPE\",\"bitwidth\":8}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - not expected format for the field + app_db_entry.match_field_lookup["ether_type"] = "{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_" + "ETHER_TYPE\",\"format\":\"IPV4\",\"bitwidth\":8}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing bitwidth + app_db_entry.match_field_lookup["ether_type"] = "{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_" + "ETHER_TYPE\",\"format\":\"HEX_STRING\"}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing sai_field + app_db_entry.match_field_lookup["ether_type"] = "{\"kind\":\"sai_field\",\"format\":\"HEX_STRING\",\"bitwidth\":8}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Unsupported IP_TYPE bit type + app_db_entry.match_field_lookup.erase("ether_type"); + app_db_entry.match_field_lookup["is_non_ip"] = + BuildMatchFieldJsonStrKindSaiField(std::string(P4_MATCH_IP_TYPE) + kFieldDelimiter + "NONIP"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, CreatePuntTableWithInvalidCompositeSaiMatchFieldFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + + // Invalid match field str - missing format + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid format + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV4\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid total bitwidth + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":65," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing element.bitwidth + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\"},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid element.bitwidth + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":63," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":31},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid element kind + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing element.sai_field + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"bitwidth\":32},{\"kind\":\"sai_" + "field\",\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\"," + "\"bitwidth\":32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid element.sai_field + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid elements length + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid first element.sai_field + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD2\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid second element.sai_field + app_db_entry.match_field_lookup["src_ipv6_64bit"] = + "{\"kind\":\"composite\",\"format\":\"IPV6\",\"bitwidth\":64," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32},{\"kind\":\"sai_field\"," + "\"sai_field\":\"SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":" + "32}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, CreatePuntTableWithInvalidCompositeUdfMatchFieldFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + + // Invalid match field str - missing format + app_db_entry.match_field_lookup["arp_tpa"] = + "{\"kind\":\"composite\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"format\":" + "\"IPV4\",\"bitwidth\":16,\"offset\":24},{\"kind\":\"udf\",\"base\":" + "\"SAI_UDF_BASE_L3\",\"format\":\"IPV4\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid format + app_db_entry.match_field_lookup["arp_tpa"] = + "{\"kind\":\"composite\",\"format\":\"IP\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"format\":" + "\"IPV6\",\"bitwidth\":16,\"offset\":24},{\"kind\":\"udf\",\"base\":" + "\"SAI_UDF_BASE_L3\",\"format\":\"IPV4\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid total bitwidth + app_db_entry.match_field_lookup["arp_tpa"] = + "{\"kind\":\"composite\",\"format\":\"IPV4\",\"bitwidth\":33," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"format\":" + "\"IPV4\",\"bitwidth\":16,\"offset\":24},{\"kind\":" + "\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"format\":\"IPV4\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing element.kind + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"IPV4\",\"bitwidth\":32," + "\"elements\":[{\"base\":\"SAI_UDF_BASE_L3\",\"format\":" + "\"IPV4\",\"bitwidth\":15,\"offset\":24},{\"kind\":\"udf\",\"base\":" + "\"SAI_UDF_BASE_L3\",\"format\":\"IPV4\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - inconsistent element.kind + app_db_entry.match_field_lookup["arp_tpa"] = + "{\"kind\":\"composite\",\"format\":\"IPV4\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"sai_field\",\"sai_field\":\"SAI_ACL_TABLE_" + "ATTR_FIELD_SRC_IPV6_WORD3\",\"bitwidth\":32},{\"kind\":\"udf\",\"base\":" + "\"SAI_UDF_BASE_L3\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing element.bitwidth + app_db_entry.match_field_lookup["arp_tpa"] = + "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"offset\":" + "24},{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid element.bitwidth + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":31," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\"," + "\"bitwidth\":15,\"offset\":24},{\"kind\":\"udf\",\"base\":\"SAI_UDF_" + "BASE_L3\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid element.base + app_db_entry.match_field_lookup["arp_tpa"] = + "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE\",\"bitwidth\":" + "16,\"offset\":24},{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\"," + "\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing element.base + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"udf\",\"bitwidth\":16,\"offset\":24},{" + "\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing element.offset + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":32," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\"," + "\"bitwidth\":16},{\"kind\":" + "\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - elements is empty + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":32," + "\"elements\":[]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - invalid element, should be an object + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"HEX_STRING\",\"bitwidth\":32," + "\"elements\":[\"group-1\"]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid match field str - missing bitwidth + app_db_entry.match_field_lookup["arp_tpa"] = "{\"kind\":\"composite\",\"format\":\"HEX_STRING\"," + "\"elements\":[{\"kind\":\"udf\",\"base\":\"SAI_UDF_BASE_L3\"," + "\"bitwidth\":16,\"offset\":24},{\"kind\":" + "\"udf\",\"base\":\"SAI_UDF_BASE_L3\",\"bitwidth\":" + "16,\"offset\":26}]}"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, CreatePuntTableWithInvalidActionFieldFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + + // Invalid action field + app_db_entry.action_field_lookup["random_action"].push_back( + {.sai_action = "RANDOM_ACTION", .p4_param_name = "DUMMY"}); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, CreatePuntTableWithInvalidPacketColorFieldFails) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + + // Invalid packet_color field + app_db_entry.packet_action_color_lookup["invalid_packet_color"].push_back( + {.packet_action = P4_PACKET_ACTION_PUNT, .packet_color = "DUMMY"}); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); + + // Invalid packet_action field + app_db_entry.packet_action_color_lookup.erase("invalid_packet_color"); + app_db_entry.packet_action_color_lookup["invalid_packet_action"].push_back( + {.packet_action = "PUNT", .packet_color = P4_PACKET_COLOR_GREEN}); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddTableRequest(app_db_entry)); + EXPECT_EQ(nullptr, GetAclTable(app_db_entry.acl_table_name)); +} + +TEST_F(AclManagerTest, DeserializeValidAclTableDefAppDbSucceeds) +{ + auto app_db_entry_or = + DeserializeAclTableDefinitionAppDbEntry(kAclIngressTableName, getDefaultTableDefFieldValueTuples()); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(kAclIngressTableName, app_db_entry.acl_table_name); + EXPECT_EQ(123, app_db_entry.size); + EXPECT_EQ(STAGE_INGRESS, app_db_entry.stage); + EXPECT_EQ(234, app_db_entry.priority); + EXPECT_EQ(P4_METER_UNIT_BYTES, app_db_entry.meter_unit); + EXPECT_EQ(P4_COUNTER_UNIT_BOTH, app_db_entry.counter_unit); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ETHER_TYPE), + app_db_entry.match_field_lookup.find("ether_type")->second); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_MAC, P4_FORMAT_MAC), + app_db_entry.match_field_lookup.find("ether_dst")->second); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_IPV6, P4_FORMAT_IPV6), + app_db_entry.match_field_lookup.find("ipv6_dst")->second); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IPV6_NEXT_HEADER), + app_db_entry.match_field_lookup.find("ipv6_next_header")->second); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TTL), app_db_entry.match_field_lookup.find("ttl")->second); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ICMP_TYPE), + app_db_entry.match_field_lookup.find("icmp_type")->second); + EXPECT_EQ(BuildMatchFieldJsonStrKindSaiField(P4_MATCH_L4_DST_PORT), + app_db_entry.match_field_lookup.find("l4_dst_port")->second); + EXPECT_EQ(P4_ACTION_SET_TRAFFIC_CLASS, + app_db_entry.action_field_lookup.find("copy_and_set_tc")->second[0].sai_action); + EXPECT_EQ("traffic_class", app_db_entry.action_field_lookup.find("copy_and_set_tc")->second[0].p4_param_name); + EXPECT_EQ(P4_ACTION_SET_TRAFFIC_CLASS, + app_db_entry.action_field_lookup.find("punt_and_set_tc")->second[0].sai_action); + EXPECT_EQ("traffic_class", app_db_entry.action_field_lookup.find("punt_and_set_tc")->second[0].p4_param_name); + EXPECT_EQ(P4_PACKET_ACTION_COPY, + app_db_entry.packet_action_color_lookup.find("copy_and_set_tc")->second[0].packet_action); + EXPECT_EQ(P4_PACKET_COLOR_GREEN, + app_db_entry.packet_action_color_lookup.find("copy_and_set_tc")->second[0].packet_color); + EXPECT_EQ(P4_PACKET_ACTION_PUNT, + app_db_entry.packet_action_color_lookup.find("punt_and_set_tc")->second[0].packet_action); + EXPECT_EQ(EMPTY_STRING, app_db_entry.packet_action_color_lookup.find("punt_and_set_tc")->second[0].packet_color); +} + +TEST_F(AclManagerTest, DeserializeAclTableDefAppDbWithInvalidJsonFails) +{ + std::string acl_table_name = kAclIngressTableName; + auto attributes = getDefaultTableDefFieldValueTuples(); + + // Invalid action JSON + attributes.push_back(swss::FieldValueTuple{"action/drop_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_COPY\";\"packet_color\":\"SAI_PACKET_" + "COLOR_GREEN\"};{\"action\":\"SAI_ACL_ENTRY_ATTR_ACTION_SET_TC\";" + "\"param\":\"traffic_class\"}]"}); + EXPECT_FALSE(DeserializeAclTableDefinitionAppDbEntry(acl_table_name, attributes).ok()); + + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"action/drop_and_set_tc", + "[{\"action\"-\"SAI_PACKET_ACTION_COPY\",\"packet_color\"-\"SAI_PACKET_" + "COLOR_GREEN\"},{\"action\"-\"SAI_ACL_ENTRY_ATTR_ACTION_SET_TC\"," + "\"param\"-\"traffic_class\"}]"}); + EXPECT_FALSE(DeserializeAclTableDefinitionAppDbEntry(acl_table_name, attributes).ok()); + + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"action/drop_and_set_tc", "[\"action\":\"SAI_PACKET_ACTION_COPY\"]"}); + EXPECT_FALSE(DeserializeAclTableDefinitionAppDbEntry(acl_table_name, attributes).ok()); +} + +TEST_F(AclManagerTest, DeserializeAclTableDefAppDbWithInvalidSizeFails) +{ + std::string acl_table_name = kAclIngressTableName; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{kStage, STAGE_INGRESS}); + attributes.push_back(swss::FieldValueTuple{kPriority, "234"}); + attributes.push_back(swss::FieldValueTuple{"meter/unit", P4_METER_UNIT_BYTES}); + attributes.push_back(swss::FieldValueTuple{"counter/unit", P4_COUNTER_UNIT_BOTH}); + attributes.push_back( + swss::FieldValueTuple{"match/ether_type", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ETHER_TYPE)}); + attributes.push_back( + swss::FieldValueTuple{"match/ether_dst", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_MAC)}); + attributes.push_back( + swss::FieldValueTuple{"match/ipv6_dst", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_IPV6)}); + attributes.push_back( + swss::FieldValueTuple{"match/ipv6_next_header", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IPV6_NEXT_HEADER)}); + attributes.push_back(swss::FieldValueTuple{"match/ttl", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TTL)}); + attributes.push_back( + 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{"action/copy_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_COPY\",\"packet_color\":\"SAI_PACKET_" + "COLOR_GREEN\"},{\"action\":\"SAI_ACL_ENTRY_ATTR_ACTION_SET_TC\"," + "\"param\":\"traffic_class\"}]"}); + attributes.push_back(swss::FieldValueTuple{"action/punt_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_TRAP\"},{\"action\":\"SAI_ACL_ENTRY_" + "ATTR_ACTION_SET_TC\",\"param\":\"traffic_class\"}]"}); + + // Invalid table size + attributes.push_back(swss::FieldValueTuple{kSize, "-123"}); + EXPECT_FALSE(DeserializeAclTableDefinitionAppDbEntry(acl_table_name, attributes).ok()); +} + +TEST_F(AclManagerTest, DeserializeAclTableDefAppDbWithInvalidPriorityFails) +{ + std::string acl_table_name = kAclIngressTableName; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{kStage, STAGE_INGRESS}); + attributes.push_back(swss::FieldValueTuple{kSize, "234"}); + attributes.push_back(swss::FieldValueTuple{"meter/unit", P4_METER_UNIT_BYTES}); + attributes.push_back(swss::FieldValueTuple{"counter/unit", P4_COUNTER_UNIT_BOTH}); + attributes.push_back( + swss::FieldValueTuple{"match/ether_type", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_ETHER_TYPE)}); + attributes.push_back( + swss::FieldValueTuple{"match/ether_dst", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_MAC)}); + attributes.push_back( + swss::FieldValueTuple{"match/ipv6_dst", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_DST_IPV6)}); + attributes.push_back( + swss::FieldValueTuple{"match/ipv6_next_header", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_IPV6_NEXT_HEADER)}); + attributes.push_back(swss::FieldValueTuple{"match/ttl", BuildMatchFieldJsonStrKindSaiField(P4_MATCH_TTL)}); + attributes.push_back( + 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{"action/copy_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_COPY\",\"packet_color\":\"SAI_PACKET_" + "COLOR_GREEN\"},{\"action\":\"SAI_ACL_ENTRY_ATTR_ACTION_SET_TC\"," + "\"param\":\"traffic_class\"}]"}); + attributes.push_back(swss::FieldValueTuple{"action/punt_and_set_tc", + "[{\"action\":\"SAI_PACKET_ACTION_TRAP\"},{\"action\":\"SAI_ACL_ENTRY_" + "ATTR_ACTION_SET_TC\",\"param\":\"traffic_class\"}]"}); + + // Invalid table priority + attributes.push_back(swss::FieldValueTuple{kPriority, "-123"}); + EXPECT_FALSE(DeserializeAclTableDefinitionAppDbEntry(acl_table_name, attributes).ok()); +} + +TEST_F(AclManagerTest, RemoveIngressPuntTableSucceeds) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + 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)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveIngressPuntRuleFails) +{ + // Create ACL table + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + // Insert the first ACL rule + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + 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))); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Fails to remove ACL rule when sai_acl_api->remove_acl_entry() fails + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(_)).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + 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. + 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); + + // Fails to remove ACL rule when reference count > 0 + p4_oid_mapper_->increaseRefCount(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + p4_oid_mapper_->decreaseRefCount(SAI_OBJECT_TYPE_ACL_ENTRY, table_name_and_rule_key); + + // Fails to remove ACL rule when sai_policer_api->remove_policer() fails + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + 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_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Fails to remove ACL rule when sai_policer_api->remove_policer() fails and + // recovery fails + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + 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. + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Fails to remove ACL rule when the counter does not exist. + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + p4_oid_mapper_->decreaseRefCount(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key); + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_ACL_COUNTER, table_name_and_rule_key); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .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. + 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); + + // Fails to remove ACL rule when sai_acl_api->remove_acl_counter() fails + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .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))); + 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 + // ACL rule recovery fails. + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .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. + 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 + // meter recovery fails + 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. + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Fails to remove ACL rule when the meter does not exist. + // The previous test fails to recover the ACL meter, and hence the meter does + // not exist. + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + // (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); +} + +TEST_F(AclManagerTest, RemoveNonExistingPuntTableFails) +{ + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenAclRuleExists) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + // Insert the first ACL rule + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Fails to remove ACL table when the table is nonempty + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenTableDoesNotExist) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_ACL_TABLE, kAclIngressTableName); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenTableRefCountIsNotZero) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + p4_oid_mapper_->increaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, kAclIngressTableName); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + 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(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableRaisesCriticalStateWhenAclGroupMemberRecoveryFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + 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. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenAclTableGroupMemberDoesNotExist) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_ACL_TABLE_GROUP_MEMBER, kAclIngressTableName); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenRemoveAclTableGroupMemberSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenRemoveUdfSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + 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_FAILURE)); + 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(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenRemoveUdfGroupSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + 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_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(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(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsRaisesCriticalStateWhenUdfRecoveryFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + 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_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, create_udf(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + 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(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), Return(SAI_STATUS_SUCCESS))); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsRaisesCriticalStateWhenUdfGroupRecoveryFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + 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)).WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_udf_, remove_udf_group(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + // 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. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenUdfHasNonZeroRefCount) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + 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_acl_, create_acl_table(_, _, _, _)) + .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))); + p4_oid_mapper_->increaseRefCount(SAI_OBJECT_TYPE_UDF, + std::string(kAclIngressTableName) + "-arp_tpa-0-base1-offset24"); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclTableFailsWhenUdfGroupHasNonZeroRefCount) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + 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_acl_, create_acl_table(_, _, _, _)) + .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))); + p4_oid_mapper_->increaseRefCount(SAI_OBJECT_TYPE_UDF_GROUP, std::string(kAclIngressTableName) + "-arp_tpa-0"); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, RemoveAclGroupsSucceedsAfterCleanup) +{ + // Create ACL table + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + // Insert the first ACL rule + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + auto acl_rule_key1 = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + 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(_, _, _, _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key1, app_db_entry)); + // Insert the second ACL rule + app_db_entry.match_fvs["tc"] = "1"; + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclIngressRuleOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid2), Return(SAI_STATUS_SUCCESS))); + auto acl_rule_key2 = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key2, app_db_entry)); + // There are 3 groups created, only group in INGRESS stage is nonempty. + // Other groups can be deleted in below RemoveAllGroups() + EXPECT_CALL(mock_sai_switch_, set_switch_attribute(Eq(gSwitchId), _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + + // Remove ACL groups + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid2))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid2))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group_member(_)).WillOnce(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)); + // Remove rules + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key1)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key2)); + // Remove table + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteTableRequest(kAclIngressTableName)); +} + +TEST_F(AclManagerTest, DrainRuleTuplesToProcessSetRequestSucceeds) +{ + auto app_db_entry = getDefaultAclTableDefAppDbEntry(); + app_db_entry.counter_unit = P4_COUNTER_UNIT_PACKETS; + app_db_entry.meter_unit = P4_METER_UNIT_PACKETS; + 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(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), 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)); + sai_object_id_t user_defined_trap_oid = gUserDefinedTrapStartOid; + AddDefaultUserTrapsSaiCalls(&user_defined_trap_oid); + ASSERT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddTableRequest(app_db_entry)); + ASSERT_NO_FATAL_FAILURE( + IsExpectedAclTableDefinitionMapping(*GetAclTable(app_db_entry.acl_table_name), app_db_entry)); + 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, getDefaultRuleFieldValueTuples()})); + + // Update request on exact rule without change will not need SAI call + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, SET_COMMAND, getDefaultRuleFieldValueTuples()})); + + // Drain rule tuples 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_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + DrainRuleTuples(); + + const auto &acl_rule_key = "match/ether_type=0x0800:match/ipv6_dst=fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53:priority=15"; + const auto *acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + EXPECT_EQ(kAclIngressRuleOid1, acl_rule->acl_entry_oid); +} + +TEST_F(AclManagerTest, DrainRuleTuplesToProcessSetDelRequestSucceeds) +{ + 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})); + + // 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_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + DrainRuleTuples(); + // Populate counter stats + EXPECT_CALL(mock_sai_policer_, get_policer_stats(Eq(kAclMeterOid1), _, _, _)) + .WillOnce(DoAll(Invoke([](sai_object_id_t policer_id, uint32_t number_of_counters, + const sai_stat_id_t *counter_ids, uint64_t *counters) { + counters[0] = 100; // green_bytes + }), + Return(SAI_STATUS_SUCCESS))); + DoAclCounterStatsTask(); + auto counters_table = std::make_unique(gCountersDb, std::string(COUNTERS_TABLE) + + DEFAULT_KEY_SEPARATOR + APP_P4RT_TABLE_NAME); + std::vector values; + EXPECT_TRUE(counters_table->get(rule_tuple_key, values)); + + const auto &acl_rule_key = "match/ether_type=0x0800:match/ipv6_dst=fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53:priority=15"; + const auto *acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + EXPECT_EQ(kAclIngressRuleOid1, acl_rule->acl_entry_oid); + EXPECT_EQ(rule_tuple_key, acl_rule->db_key); + + // Drain ACL rule tuple to process DEL request + attributes.clear(); + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, DEL_COMMAND, attributes})); + + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + DrainRuleTuples(); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, DrainRuleTuplesToProcessSetRequestInvalidTableNameRuleKeyFails) +{ + auto attributes = getDefaultRuleFieldValueTuples(); + auto acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\",\"priority\":15}"; + auto rule_tuple_key = std::string("INVALID_TABLE_NAME") + kTableKeyDelimiter + acl_rule_json_key; + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, SET_COMMAND, attributes})); + // Drain rule tuple to process SET request with invalid ACL table name: + // "INVALID_TABLE_NAME" + DrainRuleTuples(); + + auto acl_rule_key = "match/ether_type=0x0800:match/ipv6_dst=fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53:priority=15"; + EXPECT_EQ(nullptr, GetAclRule("INVALID_TABLE_NAME", acl_rule_key)); + + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\"}"; + rule_tuple_key = std::string(kAclIngressTableName) + kTableKeyDelimiter + acl_rule_json_key; + acl_rule_key = "match/ether_type=0x0800:match/ipv6_dst=fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53"; + EnqueueRuleTuple(swss::KeyOpFieldsValuesTuple({rule_tuple_key, SET_COMMAND, attributes})); + // Drain rule tuple to process SET request without priority field in rule + // JSON key + DrainRuleTuples(); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, DeserializeAclRuleAppDbWithInvalidPriorityFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto attributes = getDefaultRuleFieldValueTuples(); + + // ACL rule json key has invalid priority + const auto &acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\",\"priority\":-15}"; + + EXPECT_FALSE(DeserializeAclRuleAppDbEntry(kAclIngressTableName, acl_rule_json_key, attributes).ok()); +} + +TEST_F(AclManagerTest, DeserializeAclRuleAppDbWithInvalidMeterFieldFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + std::string acl_table_name = kAclIngressTableName; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{kAction, "copy_and_set_tc"}); + attributes.push_back(swss::FieldValueTuple{"param/traffic_class", "0x20"}); + attributes.push_back(swss::FieldValueTuple{"meter/cburst", "80"}); + attributes.push_back(swss::FieldValueTuple{"meter/pir", "200"}); + attributes.push_back(swss::FieldValueTuple{"meter/pburst", "200"}); + const auto &acl_rule_json_key = "{\"match/ether_type\":\"0x0800\",\"match/" + "ipv6_dst\":\"fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53\",\"priority\":15}"; + + // ACL rule has invalid cir value in meter field + attributes.push_back(swss::FieldValueTuple{"meter/cir", "-80"}); + EXPECT_FALSE(DeserializeAclRuleAppDbEntry(acl_table_name, acl_rule_json_key, attributes).ok()); + + // ACL rule has invalid meter field + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"meter/undefined", "80"}); + EXPECT_FALSE(DeserializeAclRuleAppDbEntry(acl_table_name, acl_rule_json_key, attributes).ok()); + + // ACL rule has invalid field + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"undefined/undefined", "80"}); + EXPECT_FALSE(DeserializeAclRuleAppDbEntry(acl_table_name, acl_rule_json_key, attributes).ok()); + + // ACL rule has invalid meter field + attributes.pop_back(); + attributes.push_back(swss::FieldValueTuple{"undefined", "80"}); + EXPECT_FALSE(DeserializeAclRuleAppDbEntry(acl_table_name, acl_rule_json_key, attributes).ok()); +} + +TEST_F(AclManagerTest, DrainRuleTuplesWithInvalidCommand) +{ + 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, "INVALID_COMMAND", attributes})); + DrainRuleTuples(); + const auto &acl_rule_key = "match/ether_type=0x0800:match/ipv6_dst=fdf8:f53b:82e4::53 & " + "fdf8:f53b:82e4::53:priority=15"; + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, DeserializeAclRuleAppDbWithInvalidMatchFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + // ACL rule json key has invalid match field + auto acl_rule_json_key = "{\"undefined/undefined\":\"0x0800\",\"priority\":15}"; + EXPECT_FALSE( + DeserializeAclRuleAppDbEntry(kAclIngressTableName, acl_rule_json_key, getDefaultRuleFieldValueTuples()).ok()); + // ACL rule json key is missing match prefix + acl_rule_json_key = "{\"ipv6_dst\":\"0x0800\",\"priority\":15}"; + EXPECT_FALSE( + DeserializeAclRuleAppDbEntry(kAclIngressTableName, acl_rule_json_key, getDefaultRuleFieldValueTuples()).ok()); +} + +TEST_F(AclManagerTest, DeserializeAclRuleAppDbWithInvalidJsonFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto attributes = getDefaultRuleFieldValueTuples(); + // ACL rule json key is an invalid JSON + EXPECT_FALSE(DeserializeAclRuleAppDbEntry(kAclIngressTableName, "{\"undefined\"}", attributes).ok()); + EXPECT_FALSE( + DeserializeAclRuleAppDbEntry(kAclIngressTableName, "[{\"ipv6_dst\":\"0x0800\",\"priority\":15}]", attributes) + .ok()); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidSaiMatchFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + // ACL rule has invalid in/out port(s) + app_db_entry.match_fvs["in_port"] = "Eth0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("in_port"); + app_db_entry.match_fvs["out_port"] = "Eth0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("out_port"); + app_db_entry.match_fvs["in_ports"] = "Eth0,Eth1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["in_ports"] = ""; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("in_ports"); + app_db_entry.match_fvs["out_ports"] = ""; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["out_ports"] = "Eth0,Eth1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("out_ports"); + + // ACL rule has invalid ipv6_dst + app_db_entry.match_fvs["ipv6_dst"] = "10.0.0.2"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ipv6_dst"] = "10.0.0.2 & 255.255.255.0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ipv6_dst"] = "fdf8:f53b:82e4::53 & 255.255.255.0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ipv6_dst"] = "null"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ipv6_dst"] = "fdf8:f53b:82e4::53"; + + // ACL rule has invalid ip_src + app_db_entry.match_fvs["ip_src"] = "fdf8:f53b:82e4::53"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ip_src"] = "fdf8:f53b:82e4::53 & ffff:ffff:ffff::"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ip_src"] = "10.0.0.2 & ffff:ffff:ffff::"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ip_src"] = "null"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ip_src"] = "10.0.0.2"; + + // ACL rule has invalid ether_type + app_db_entry.match_fvs["ether_type"] = "0x88800"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["ether_type"] = "0x0800"; + + // ACL rule has invalid ip_frag + app_db_entry.match_fvs["ip_frag"] = "invalid"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("ip_frag"); + + // ACL rule has invalid packet_vlan + app_db_entry.match_fvs["packet_vlan"] = "invalid"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("packet_vlan"); + + // ACL rule has invalid UDF field: should be HEX_STRING + app_db_entry.match_fvs["arp_tpa"] = "invalid"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // ACL rule has invalid UDF field: invalid HEX_STRING length + app_db_entry.match_fvs["arp_tpa"] = "0xff"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // ACL table misses UDF group definition + const auto &acl_table_acl_tableapp_db_entry = getDefaultAclTableDefAppDbEntry(); + auto *acl_table = GetAclTable(acl_table_acl_tableapp_db_entry.acl_table_name); + std::map saved_udf_group_attr_index_lookup = acl_table->udf_group_attr_index_lookup; + 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. + 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; + + // ACL rule has undefined match field + app_db_entry.match_fvs["undefined"] = "1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs.erase("undefined"); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidCompositeSaiMatchFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + // ACL rule has invalid src_ipv6_64bit(composite SAI field) - should be ipv6 + // address + app_db_entry.match_fvs["src_ipv6_64bit"] = "Eth0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["src_ipv6_64bit"] = "10.0.0.1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["src_ipv6_64bit"] = "10.0.0.1 & ffff:ffff::"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.match_fvs["src_ipv6_64bit"] = "fdf8:f53b:82e4:: & 255.255.255.255"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); +} + +TEST_F(AclManagerTest, AclRuleWithValidMatchFields) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + + // Match fields registered in table definition + app_db_entry.match_fvs["ether_dst"] = "AA:BB:CC:DD:EE:FF & FF:FF:FF:FF:FF:FF"; + app_db_entry.match_fvs["ttl"] = "0x2 & 0xFF"; + app_db_entry.match_fvs["in_ports"] = "Ethernet1,Ethernet2"; + app_db_entry.match_fvs["in_port"] = "Ethernet3"; + app_db_entry.match_fvs["out_ports"] = "Ethernet4,Ethernet5"; + app_db_entry.match_fvs["out_port"] = "Ethernet6"; + app_db_entry.match_fvs["tcp_flags"] = " 0x2 & 0x3F "; + app_db_entry.match_fvs["ip_flags"] = "0x2"; + app_db_entry.match_fvs["l4_src_port"] = "0x2e90 & 0xFFF0"; + app_db_entry.match_fvs["l4_dst_port"] = "0x2e98"; + app_db_entry.match_fvs["ip_id"] = "2"; + app_db_entry.match_fvs["inner_l4_src_port"] = "1212"; + app_db_entry.match_fvs["inner_l4_dst_port"] = "1212"; + app_db_entry.match_fvs["dscp"] = "8"; + app_db_entry.match_fvs["inner_ip_src"] = "192.50.128.0"; + app_db_entry.match_fvs["inner_ip_dst"] = "192.50.128.0/17"; + app_db_entry.match_fvs["inner_ipv6_src"] = "1234:5678::"; + app_db_entry.match_fvs["inner_ipv6_dst"] = "2001:db8:3c4d:15::/64"; + app_db_entry.match_fvs["ip_src"] = " 192.50.128.0 & 255.255.255.0 "; + app_db_entry.match_fvs["ip_dst"] = "192.50.128.0/17"; + app_db_entry.match_fvs["ipv6_src"] = "2001:db8:3c4d:15::/64"; + app_db_entry.match_fvs["tc"] = "1"; + app_db_entry.match_fvs["icmp_type"] = "9"; // RA + app_db_entry.match_fvs["icmp_code"] = "0"; // Normal RA + app_db_entry.match_fvs["tos"] = "32"; + app_db_entry.match_fvs["icmpv6_type"] = "134"; // RA + app_db_entry.match_fvs["icmpv6_code"] = "0"; // Normal RA + app_db_entry.match_fvs["ecn"] = "0"; + app_db_entry.match_fvs["inner_ip_protocol"] = "0x01"; // ICMP + app_db_entry.match_fvs["ip_protocol"] = "0x6"; // TCP + app_db_entry.match_fvs["ipv6_flow_label"] = "0x88 & 0xFFFFFFFF"; + app_db_entry.match_fvs["tunnel_vni"] = "88"; + app_db_entry.match_fvs["ip_frag"] = P4_IP_FRAG_HEAD; + app_db_entry.match_fvs["packet_vlan"] = P4_PACKET_VLAN_SINGLE_OUTER_TAG; + app_db_entry.match_fvs["outer_vlan_pri"] = "100"; + app_db_entry.match_fvs["outer_vlan_id"] = "100"; + app_db_entry.match_fvs["outer_vlan_cfi"] = "100"; + app_db_entry.match_fvs["inner_vlan_pri"] = "200"; + app_db_entry.match_fvs["inner_vlan_id"] = "200"; + app_db_entry.match_fvs["inner_vlan_cfi"] = "200"; + + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + 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(_, _, _, _)) + .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); + auto *acl_table = GetAclTable(kAclIngressTableName); + ASSERT_NE(nullptr, acl_rule); + ASSERT_NE(nullptr, acl_table); + EXPECT_NO_FATAL_FAILURE(IsExpectedAclRuleMapping(acl_rule, app_db_entry, *acl_table)); + EXPECT_TRUE(acl_rule->meter.packet_color_actions.empty()); + + // Check match field value + EXPECT_EQ(2, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS].aclfield.data.objlist.count); + EXPECT_EQ(0x112233, acl_rule->in_ports_oids[0]); + EXPECT_EQ(0x1fed3, acl_rule->in_ports_oids[1]); + 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]); + 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); + EXPECT_EQ(0x3F, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_TCP_FLAGS].aclfield.mask.u8); + EXPECT_EQ(0x2, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IP_FLAGS].aclfield.data.u8); + EXPECT_EQ(0x3F, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IP_FLAGS].aclfield.mask.u8); + EXPECT_EQ(8, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_DSCP].aclfield.data.u8); + EXPECT_EQ(0x3F, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_DSCP].aclfield.mask.u8); + EXPECT_EQ(0x0800, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE].aclfield.data.u16); + EXPECT_EQ(0xFFFF, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE].aclfield.mask.u16); + EXPECT_EQ(0x2e90, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_L4_SRC_PORT].aclfield.data.u16); + EXPECT_EQ(0xFFF0, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_L4_SRC_PORT].aclfield.mask.u16); + EXPECT_EQ(2, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IP_IDENTIFICATION].aclfield.data.u16); + EXPECT_EQ(0xFFFF, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IP_IDENTIFICATION].aclfield.mask.u16); + EXPECT_EQ(100, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_ID].aclfield.data.u16); + EXPECT_EQ(0xFFFF, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_OUTER_VLAN_ID].aclfield.mask.u16); + // 192.50.128.0 + EXPECT_EQ(swss::IpPrefix("192.50.128.0").getIp().getV4Addr(), + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_IP].aclfield.data.ip4); + EXPECT_EQ(swss::IpPrefix("192.50.128.0").getMask().getV4Addr(), + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_INNER_SRC_IP].aclfield.mask.ip4); + // 192.50.128.0 & 255.255.255.0 + EXPECT_EQ(swss::IpAddress("192.50.128.0").getV4Addr(), + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP].aclfield.data.ip4); + EXPECT_EQ(swss::IpAddress("255.255.255.0").getV4Addr(), + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP].aclfield.mask.ip4); + EXPECT_EQ(0, memcmp(acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6].aclfield.data.ip6, + swss::IpPrefix("2001:db8:3c4d:15::/64").getIp().getV6Addr(), 16)); + EXPECT_EQ(0, memcmp(acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6].aclfield.mask.ip6, + swss::IpPrefix("2001:db8:3c4d:15::/64").getMask().getV6Addr(), 16)); + + EXPECT_EQ(0, memcmp(acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_SRC_MAC].aclfield.data.mac, + swss::MacAddress("AA:BB:CC:DD:EE:FF").getMac(), 6)); + EXPECT_EQ(0, memcmp(acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_DST_MAC].aclfield.data.mac, + swss::MacAddress("AA:BB:CC:DD:EE:FF").getMac(), 6)); + EXPECT_EQ(0, memcmp(acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_DST_MAC].aclfield.mask.mac, + swss::MacAddress("FF:FF:FF:FF:FF:FF").getMac(), 6)); + EXPECT_EQ(1, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_TC].aclfield.data.u8); + EXPECT_EQ(0xFF, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_TC].aclfield.mask.u8); + EXPECT_EQ(0x88, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IPV6_FLOW_LABEL].aclfield.data.u32); + EXPECT_EQ(0xFFFFFFFF, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_IPV6_FLOW_LABEL].aclfield.mask.u32); + EXPECT_EQ(SAI_ACL_IP_FRAG_HEAD, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_FRAG].aclfield.data.u32); + EXPECT_EQ(SAI_PACKET_VLAN_SINGLE_OUTER_TAG, + acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_PACKET_VLAN].aclfield.data.u32); + + // Check action field value + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_EQ(0x20, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_TC].aclaction.parameter.u8); +} + +TEST_F(AclManagerTest, AclRuleWithValidAction) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + // Redirect action + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = "Ethernet1"; + // 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(_, _, _, _)) + .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(/*port_oid=*/0x112233, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT].aclaction.parameter.oid); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + + // Redirect action + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = "Ethernet7"; + // 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(/*port_oid=*/0x1234, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT].aclaction.parameter.oid); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + + // Set up an next hop mapping + const std::string next_hop_id = "ju1u32m1.atl11:qe-3/7"; + const auto &next_hop_key = KeyGenerator::generateNextHopKey(next_hop_id); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, + /*next_hop_oid=*/1); + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = next_hop_id; + // 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(/*next_hop_oid=*/1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT].aclaction.parameter.oid); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + + // Set endpoint Ip action + app_db_entry.action = "endpoint_ip"; + app_db_entry.action_param_fvs["ip_address"] = "127.0.0.1"; + 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(swss::IpAddress("127.0.0.1").getV4Addr(), + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP].aclaction.parameter.ip4); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + // Install rule + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP].aclaction.parameter.ip6, + swss::IpAddress("fdf8:f53b:82e4::53").getV6Addr(), 16)); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Mirror ingress action + app_db_entry.action = "mirror_ingress"; + app_db_entry.action_param_fvs["target"] = gMirrorSession1; + // 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + + // Check action field value + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].aclaction.enable); + EXPECT_EQ(kMirrorSessionOid1, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].oid); + EXPECT_EQ(gMirrorSession1, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].name); + + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Mirror egress action + app_db_entry.action = "mirror_egress"; + app_db_entry.action_param_fvs["target"] = gMirrorSession2; + // 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + + // Check action field value + EXPECT_EQ(acl_rule->action_fvs.end(), acl_rule->action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS)); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS].aclaction.enable); + EXPECT_EQ(kMirrorSessionOid2, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS].oid); + EXPECT_EQ(gMirrorSession2, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS].name); + + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set Packet Color + app_db_entry.action = "set_packet_color"; + app_db_entry.action_param_fvs["packet_color"] = "SAI_PACKET_COLOR_YELLOW"; + // 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(SAI_PACKET_COLOR_YELLOW, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_PACKET_COLOR].aclaction.parameter.s32); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set Src Mac + app_db_entry.action = "set_src_mac"; + app_db_entry.action_param_fvs["mac_address"] = "AA:BB:CC:DD:EE:FF"; + + 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC].aclaction.parameter.mac, + swss::MacAddress("AA:BB:CC:DD:EE:FF").getMac(), 6)); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set src IP + app_db_entry.action = "set_src_ip"; + app_db_entry.action_param_fvs["ip_address"] = "10.0.0.1"; + + 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(swss::IpAddress("10.0.0.1").getV4Addr(), + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP].aclaction.parameter.ip4); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set IPv6 Dst + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + + 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6].aclaction.parameter.ip6, + swss::IpAddress("fdf8:f53b:82e4::53").getV6Addr(), 16)); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set DSCP and ECN + app_db_entry.action = "set_dscp_and_ecn"; + app_db_entry.action_param_fvs["dscp"] = "8"; + app_db_entry.action_param_fvs["ecn"] = "0"; + + 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(8, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_DSCP].aclaction.parameter.u8); + EXPECT_EQ(0, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_ECN].aclaction.parameter.u8); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set Inner VLAN + app_db_entry.action = "set_inner_vlan"; + app_db_entry.action_param_fvs["vlan_pri"] = "100"; + app_db_entry.action_param_fvs["vlan_id"] = "100"; + + 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(100, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID].aclaction.parameter.u32); + EXPECT_EQ(100, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI].aclaction.parameter.u8); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set L4Src Port + app_db_entry.action = "set_l4_src_port"; + app_db_entry.action_param_fvs["port"] = "1212"; + + 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1212, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_L4_SRC_PORT].aclaction.parameter.u16); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Flood action + app_db_entry.action = "flood"; + + 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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_FLOOD].aclaction.enable); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Set user defined trap for QOS_QUEUE + int queue_num = 2; + app_db_entry.action = "qos_queue"; + app_db_entry.action_param_fvs["cpu_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(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + 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); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, AclRuleWithVrfAction) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + // Set vrf + app_db_entry.action = "set_vrf"; + app_db_entry.action_param_fvs["vrf"] = gVrfName; + // 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(_, _, _, _)) + .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(gVrfOid, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF].aclaction.parameter.oid); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, AclRuleWithIpTypeBitEncoding) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + // Set src IP + app_db_entry.action = "set_src_ip"; + app_db_entry.action_param_fvs["ip_address"] = "10.0.0.1"; + + // Successful cases + // Wildcard match on IP_TYPE: SAI_ACL_IP_TYPE_ANY + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillRepeatedly(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 match field IP_TYPE + EXPECT_NE(acl_rule->match_fvs.end(), acl_rule->match_fvs.find(SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE)); + EXPECT_EQ(SAI_ACL_IP_TYPE_ANY, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_ip { value: 0x1 mask: 0x1 } = SAI_ACL_IP_TYPE_IP + app_db_entry.match_fvs["is_ip"] = "0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_IP, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_ip { value: 0x0 mask: 0x1 } = SAI_ACL_IP_TYPE_NON_IP + app_db_entry.match_fvs["is_ip"] = "0x0 & 0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_NON_IP, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_ipv4 { value: 0x1 mask: 0x1 } = SAI_ACL_IP_TYPE_IPV4ANY + app_db_entry.match_fvs.erase("is_ip"); + app_db_entry.match_fvs["is_ipv4"] = "0x1 & 0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_IPV4ANY, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_ipv4 { value: 0x0 mask: 0x1 } = SAI_ACL_IP_TYPE_NON_IPV4 + app_db_entry.match_fvs["is_ipv4"] = "0x0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_NON_IPV4, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_ipv6 { value: 0x1 mask: 0x1 } = SAI_ACL_IP_TYPE_IPV6ANY + app_db_entry.match_fvs.erase("is_ipv4"); + app_db_entry.match_fvs["is_ipv6"] = "0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_IPV6ANY, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_ipv6 { value: 0x0 mask: 0x1 } = SAI_ACL_IP_TYPE_NON_IPV6 + app_db_entry.match_fvs["is_ipv6"] = "0x0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_NON_IPV6, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_arp { value: 0x1 mask: 0x1 } = SAI_ACL_IP_TYPE_ARP + app_db_entry.match_fvs.erase("is_ipv6"); + app_db_entry.match_fvs["is_arp"] = "0x1 & 0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_ARP, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_arp_request { value: 0x1 mask: 0x1 } = SAI_ACL_IP_TYPE_ARP_REQUEST + app_db_entry.match_fvs.erase("is_arp"); + app_db_entry.match_fvs["is_arp_request"] = "0x1 & 0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_ARP_REQUEST, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // is_arp_reply { value: 0x1 mask: 0x1 } = SAI_ACL_IP_TYPE_ARP_REPLY + app_db_entry.match_fvs.erase("is_arp_request"); + app_db_entry.match_fvs["is_arp_reply"] = "0x1 & 0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check match field IP_TYPE + EXPECT_EQ(SAI_ACL_IP_TYPE_ARP_REPLY, acl_rule->match_fvs[SAI_ACL_ENTRY_ATTR_FIELD_ACL_IP_TYPE].aclfield.data.u32); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + + // Failed cases + // is_arp_reply { value: 0x0 mask: 0x1 } = N/A + app_db_entry.match_fvs["is_arp_reply"] = "0x0 & 0x1"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_EQ(nullptr, acl_rule); + + // is_arp_request { value: 0x0 mask: 0x1 } = N/A + app_db_entry.match_fvs.erase("is_arp_reply"); + app_db_entry.match_fvs["is_arp_request"] = "0x0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_EQ(nullptr, acl_rule); + + // is_arp { value: 0x0 mask: 0x1 } = N/A + app_db_entry.match_fvs.erase("is_arp_request"); + app_db_entry.match_fvs["is_arp"] = "0x0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_EQ(nullptr, acl_rule); + + // is_ip { value: 0x1 mask: 0x0 } = N/A + app_db_entry.match_fvs.erase("is_arp"); + app_db_entry.match_fvs["is_ip"] = "0x1 & 0x0"; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_EQ(nullptr, acl_rule); +} + +TEST_F(AclManagerTest, UpdateAclRuleWithActionMeterChange) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + sai_object_id_t meter_oid; + uint32_t ref_cnt; + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(kAclIngressTableName, acl_rule_key); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "1"; + // 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(_, _, _, _)) + .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(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, 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_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update action parameter value + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + // Update rule + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + 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(acl_rule->meter.packet_color_actions.empty()); + + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update packet action: Copy green packet + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + app_db_entry.meter.cburst = 500; + app_db_entry.meter.cir = 500; + app_db_entry.meter.pburst = 600; + app_db_entry.meter.pir = 600; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .Times(5) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(acl_rule->action_fvs.end(), acl_rule->action_fvs.find(SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION)); + 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_FALSE(acl_rule->meter.packet_color_actions.empty()); + EXPECT_NE(acl_rule->meter.packet_color_actions.find(SAI_POLICER_ATTR_GREEN_PACKET_ACTION), + acl_rule->meter.packet_color_actions.end()); + EXPECT_EQ(SAI_PACKET_ACTION_COPY, acl_rule->meter.packet_color_actions[SAI_POLICER_ATTR_GREEN_PACKET_ACTION]); + EXPECT_EQ(500, acl_rule->meter.cburst); + EXPECT_EQ(500, acl_rule->meter.cir); + EXPECT_EQ(600, acl_rule->meter.pburst); + EXPECT_EQ(600, acl_rule->meter.pir); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update ACL rule : disable rate limiting, packet action is still existing. + app_db_entry.meter.enabled = false; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .Times(4) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_FALSE(acl_rule->meter.packet_color_actions.empty()); + EXPECT_NE(acl_rule->meter.packet_color_actions.find(SAI_POLICER_ATTR_GREEN_PACKET_ACTION), + acl_rule->meter.packet_color_actions.end()); + EXPECT_EQ(SAI_PACKET_ACTION_COPY, acl_rule->meter.packet_color_actions[SAI_POLICER_ATTR_GREEN_PACKET_ACTION]); + 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); + + // Update meter: enable rate limiting and reset green packet action + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.meter.enabled = true; + // Update meter and rule: reset color packet action and update entry + // attribute + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .Times(5) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + 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(acl_rule->meter.packet_color_actions.empty()); + EXPECT_EQ(500, acl_rule->meter.cburst); + EXPECT_EQ(500, acl_rule->meter.cir); + EXPECT_EQ(600, acl_rule->meter.pburst); + EXPECT_EQ(600, acl_rule->meter.pir); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update ACL rule : disable meter + app_db_entry.meter.enabled = false; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + 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_FALSE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + 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.packet_color_actions.empty()); + EXPECT_FALSE(acl_rule->meter.enabled); + + // Update ACL rule : enable meter + app_db_entry.meter.enabled = true; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(2, acl_rule->action_fvs.size()); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + 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_EQ(kAclMeterOid2, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + EXPECT_TRUE(acl_rule->meter.packet_color_actions.empty()); + EXPECT_TRUE(acl_rule->meter.enabled); + + // Redirect action + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = "Ethernet1"; + // Update rule + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(3) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ( + /*port_oid=*/0x112233, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT].aclaction.parameter.oid); + + // Set up an next hop mapping + const std::string next_hop_id = "ju1u32m1.atl11:qe-3/7"; + const auto &next_hop_key = KeyGenerator::generateNextHopKey(next_hop_id); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, + /*next_hop_oid=*/1); + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = next_hop_id; + // Update rule + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(/*next_hop_oid=*/1, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT].aclaction.parameter.oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + EXPECT_EQ(next_hop_key, acl_rule->action_redirect_nexthop_key); + + // Set endpoint Ip action + app_db_entry.action = "endpoint_ip"; + app_db_entry.action_param_fvs["ip_address"] = "127.0.0.1"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(swss::IpAddress("127.0.0.1").getV4Addr(), + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP].aclaction.parameter.ip4); + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(1) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_ENDPOINT_IP].aclaction.parameter.ip6, + swss::IpAddress("fdf8:f53b:82e4::53").getV6Addr(), 16)); + + // Set src Mac + app_db_entry.action = "set_src_mac"; + app_db_entry.action_param_fvs["mac_address"] = "AA:BB:CC:DD:EE:FF"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC].aclaction.parameter.mac, + swss::MacAddress("AA:BB:CC:DD:EE:FF").getMac(), 6)); + app_db_entry.action_param_fvs["mac_address"] = "11:22:33:44:55:66"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_MAC].aclaction.parameter.mac, + swss::MacAddress("11:22:33:44:55:66").getMac(), 6)); + + // Set Inner VLAN + app_db_entry.action = "set_inner_vlan"; + app_db_entry.action_param_fvs["vlan_pri"] = "100"; + app_db_entry.action_param_fvs["vlan_id"] = "100"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(3) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(2, acl_rule->action_fvs.size()); + EXPECT_EQ(100, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID].aclaction.parameter.u32); + EXPECT_EQ(100, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI].aclaction.parameter.u8); + app_db_entry.action_param_fvs["vlan_pri"] = "150"; + app_db_entry.action_param_fvs["vlan_id"] = "150"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(2, acl_rule->action_fvs.size()); + EXPECT_EQ(150, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_ID].aclaction.parameter.u32); + EXPECT_EQ(150, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_INNER_VLAN_PRI].aclaction.parameter.u8); + + // Set src IP + app_db_entry.action = "set_src_ip"; + app_db_entry.action_param_fvs["ip_address"] = "10.0.0.1"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(3) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(swss::IpAddress("10.0.0.1").getV4Addr(), + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP].aclaction.parameter.ip4); + app_db_entry.action_param_fvs["ip_address"] = "10.10.10.1"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(1) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(swss::IpAddress("10.10.10.1").getV4Addr(), + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_SRC_IP].aclaction.parameter.ip4); + + // Set IPv6 Dst + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6].aclaction.parameter.ip6, + swss::IpAddress("fdf8:f53b:82e4::53").getV6Addr(), 16)); + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b::53"; + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(1) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(0, memcmp(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_DST_IPV6].aclaction.parameter.ip6, + swss::IpAddress("fdf8:f53b::53").getV6Addr(), 16)); + + // Mirror ingress action + app_db_entry.action = "mirror_ingress"; + app_db_entry.action_param_fvs["target"] = gMirrorSession1; + // Update rule + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(3) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].aclaction.enable); + EXPECT_EQ(kMirrorSessionOid1, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].oid); + EXPECT_EQ(gMirrorSession1, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].name); + + app_db_entry.action_param_fvs["target"] = gMirrorSession2; + + // Update rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].aclaction.enable); + EXPECT_EQ(kMirrorSessionOid2, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].oid); + EXPECT_EQ(gMirrorSession2, acl_rule->action_mirror_sessions[SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS].name); + + // Flood action + app_db_entry.action = "flood"; + + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_FLOOD].aclaction.enable); + + // QOS_QUEUE action to set user defined trap for CPU queue number 3 + int queue_num = 3; + app_db_entry.action = "qos_queue"; + app_db_entry.action_param_fvs["cpu_queue"] = std::to_string(queue_num); + + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID].aclaction.enable); + EXPECT_EQ(gUserDefinedTrapStartOid + queue_num, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID].aclaction.parameter.oid); + + // QOS_QUEUE action to set user defined trap CPU queue number 4 + queue_num = 4; + app_db_entry.action_param_fvs["cpu_queue"] = std::to_string(queue_num); + + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID].aclaction.enable); + EXPECT_EQ(gUserDefinedTrapStartOid + queue_num, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_USER_TRAP_ID].aclaction.parameter.oid); +} + +TEST_F(AclManagerTest, UpdateAclRuleWithVrfActionChange) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(kAclIngressTableName, acl_rule_key); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "1"; + // 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(_, _, _, _)) + .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); + + // Set VRF + app_db_entry.action = "set_vrf"; + app_db_entry.action_param_fvs["vrf"] = gVrfName; + // Update rule + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .Times(3) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(gVrfOid, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF].aclaction.parameter.oid); + app_db_entry.action_param_fvs["vrf"] = ""; + // Update rule + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ(1, acl_rule->action_fvs.size()); + EXPECT_EQ(gVirtualRouterId, acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_SET_VRF].aclaction.parameter.oid); +} + +TEST_F(AclManagerTest, UpdateAclRuleFailsWhenSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + + sai_object_id_t meter_oid; + uint32_t ref_cnt; + const auto &table_name_and_rule_key = concatTableNameAndRuleKey(kAclIngressTableName, acl_rule_key); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "1"; + // 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(_, _, _, _)) + .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); + + // Update packet action: Copy green packet. Fails when update meter + // attribute fails + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ("punt_and_set_tc", acl_rule->p4_action); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, 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_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update packet action and rate limiting. Fails when update meter attribute + // fails plue meter attribute recovery fails. + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + app_db_entry.meter.cburst = 500; + app_db_entry.meter.cir = 500; + app_db_entry.meter.pburst = 600; + app_db_entry.meter.pir = 600; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .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, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ("punt_and_set_tc", acl_rule->p4_action); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, 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_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update packet action: Copy green packet. Fails when action param is + // missing + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs.erase("traffic_class"); + app_db_entry.meter.cburst = 80; + app_db_entry.meter.cir = 80; + app_db_entry.meter.pburst = 200; + app_db_entry.meter.pir = 200; + // Update meter attribute for green packet action + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ("punt_and_set_tc", acl_rule->p4_action); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, 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_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update packet action: Copy green packet. Fails when updating ACL rule + // fails + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .Times(2) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ("punt_and_set_tc", acl_rule->p4_action); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, 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_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update packet action: Copy green packet. Fails when updating ACL rule + // fails and meter recovery fails + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + // Update meter attribute for green packet action + EXPECT_CALL(mock_sai_policer_, set_policer_attribute(Eq(kAclMeterOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .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. + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ("punt_and_set_tc", acl_rule->p4_action); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, 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_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Remove meter in ACL rule: fails when deleting meter fails plus ACL rule + // recovery fails. + app_db_entry.meter.enabled = false; + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "2"; + // Remove meter + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)); + // (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); + // Check action field value + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, 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(acl_rule->meter.packet_color_actions.empty()); + EXPECT_TRUE(acl_rule->meter.enabled); + EXPECT_TRUE(p4_oid_mapper_->getOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &meter_oid)); + EXPECT_EQ(kAclMeterOid1, meter_oid); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Successfully remove meter + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "1"; + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, set_acl_entry_attribute(Eq(kAclIngressRuleOid1), _)) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRuleRequest(app_db_entry, *acl_rule)); + acl_rule = GetAclRule(kAclIngressTableName, acl_rule_key); + ASSERT_NE(nullptr, acl_rule); + // Check action field value + EXPECT_EQ("punt_and_set_tc", acl_rule->p4_action); + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, 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(acl_rule->meter.packet_color_actions.empty()); + EXPECT_FALSE(acl_rule->meter.enabled); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_POLICER, table_name_and_rule_key)); + + // Add meter in ACL rule with packet color action + app_db_entry.meter.enabled = true; + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid2), Return(SAI_STATUS_SUCCESS))); + 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_SUCCESS)); + 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); + // Check action and meter + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, 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(acl_rule->meter.packet_color_actions.empty()); + EXPECT_FALSE(acl_rule->meter.enabled); + + // Add meter in ACL rule with packet color action. Fails when updating ACL + // rule fails and meter recovery fails + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid2), Return(SAI_STATUS_SUCCESS))); + 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. + 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); + // Check action and meter + EXPECT_EQ(SAI_PACKET_ACTION_TRAP, + acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.parameter.s32); + EXPECT_TRUE(acl_rule->action_fvs[SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION].aclaction.enable); + EXPECT_EQ(1, 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(acl_rule->meter.packet_color_actions.empty()); + EXPECT_FALSE(acl_rule->meter.enabled); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidActionFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + + // ACL rule has redirect action with invalid next_hop_id + const std::string next_hop_id = "ju1u32m1.atl11:qe-3/7"; + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = next_hop_id; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("target"); + // ACL rule has redirect action with wrong port type + app_db_entry.action_param_fvs["target"] = "Ethernet8"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("target"); + // ACL rule has invalid action + app_db_entry.action = "set_tc"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + // ACL rule has invalid IP address + app_db_entry.action = "endpoint_ip"; + app_db_entry.action_param_fvs["ip_address"] = "invalid"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("ip_address"); + // ACL rule is missing action parameter + app_db_entry.action = "set_src_ip"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + // ACL rule with invalid action parameter field "ipv4", should be "ip" + app_db_entry.action = "set_src_ip"; + app_db_entry.action_param_fvs["ipv4"] = "10.0.0.1"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("ipv4"); + // ACL rule has invalid MAC address + app_db_entry.action = "set_src_mac"; + app_db_entry.action_param_fvs["mac_address"] = "invalid"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("mac_address"); + // ACL rule with invalid packet action value + app_db_entry.action = "set_packet_action"; + app_db_entry.action_param_fvs["packet_action"] = "PUNT"; // Invalid packet action str + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("packet_action"); + // ACL rule with invalid packet color value + app_db_entry.action = "set_packet_color"; + app_db_entry.action_param_fvs["packet_color"] = "YELLOW"; // Invalid color str + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("packet_color"); + // Invalid Mirror ingress session + app_db_entry.action = "mirror_ingress"; + app_db_entry.action_param_fvs["target"] = "Session"; + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("target"); + // Invalid cpu queue number + app_db_entry.action = "qos_queue"; + app_db_entry.action_param_fvs["cpu_queue"] = "10"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs["cpu_queue"] = "invalid"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("cpu_queue"); + // ACL rule has invalid dscp + app_db_entry.action = "set_dscp_and_ecn"; + app_db_entry.action_param_fvs["dscp"] = "invalid"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("dscp"); + // ACL rule has invalid vlan id + app_db_entry.action = "set_inner_vlan"; + app_db_entry.action_param_fvs["vlan_id"] = "invalid"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("vlan_id"); + // ACL rule has invalid port number + app_db_entry.action = "set_l4_src_port"; + app_db_entry.action_param_fvs["port"] = "invalid"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + app_db_entry.action_param_fvs.erase("port"); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidVrfActionFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + + // ACL rule with invalid VRF name + app_db_entry.action = "set_vrf"; + app_db_entry.action_param_fvs["vrf"] = "Vrf-yellow"; + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidUnitsInTableFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto *acl_table = GetAclTable(kAclIngressTableName); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + + // Invalid meter unit + acl_table->meter_unit = "INVALID"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + acl_table->meter_unit = P4_METER_UNIT_BYTES; + + // 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; +} + +TEST_F(AclManagerTest, CreateAclRuleFailsWhenSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + auto acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + + // Set up an next hop mapping + const std::string next_hop_id = "ju1u32m1.atl11:qe-1/7"; + const auto &next_hop_key = KeyGenerator::generateNextHopKey(next_hop_id); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, + /*next_hop_oid=*/1); + app_db_entry.action = "redirect"; + app_db_entry.action_param_fvs["target"] = next_hop_id; + + // Fails to create ACL rule when sai_acl_api->create_acl_entry() fails + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + 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_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + uint32_t ref_count; + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key, &ref_count)); + EXPECT_EQ(0, ref_count); + + // Set VRF action + app_db_entry.action = "set_vrf"; + app_db_entry.action_param_fvs["vrf"] = gVrfName; + acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + + // Fails to create ACL rule when sai_acl_api->create_acl_entry() fails + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + 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_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Fails to create ACL rule when sai_acl_api->create_acl_entry() fails plus + // meter and counter recovery fails + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + 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. + 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 + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)).WillOnce(Return(SAI_STATUS_NOT_IMPLEMENTED)); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Fails to create ACL rule when sai_acl_api->create_acl_counter() fails + 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_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Fails to create ACL rule when sai_acl_api->create_acl_counter() fails and + // meter recovery fails + 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. + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidSetSrcIpActionFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + app_db_entry.action = "set_src_ip"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + app_db_entry.action_param_fvs["ip_address"] = "null"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); +} + +TEST_F(AclManagerTest, CreateAclRuleWithInvalidSetDstIpv6ActionFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "100"); + + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "10.0.0.2"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + app_db_entry.action_param_fvs["ip_address"] = "null"; + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); +} + +TEST_F(AclManagerTest, DeleteAclRuleWhenTableDoesNotExistFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + app_db_entry.action = "punt_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + const auto &acl_rule_key = KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, "15"); + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .Times(2) + .WillRepeatedly(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(_, _, _, _)) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + p4_oid_mapper_->decreaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, kAclIngressTableName); + p4_oid_mapper_->decreaseRefCount(SAI_OBJECT_TYPE_ACL_TABLE, kAclIngressTableName); + 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. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, DoAclCounterStatsTaskSucceeds) +{ + auto app_db_def_entry = getDefaultAclTableDefAppDbEntry(); + app_db_def_entry.counter_unit = P4_COUNTER_UNIT_BOTH; + 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(DoAll(SetArgPointee<0>(kAclGroupMemberIngressOid), 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)); + 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)); + auto counters_table = std::make_unique(gCountersDb, std::string(COUNTERS_TABLE) + + DEFAULT_KEY_SEPARATOR + APP_P4RT_TABLE_NAME); + + // Insert the first ACL rule + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = + KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, std::to_string(app_db_entry.priority)); + const auto &counter_stats_key = app_db_entry.db_key; + std::vector values; + std::string stats; + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclCounterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Populate counter stats in COUNTERS_DB + 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 packets and bytes are populated in COUNTERS_DB + 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); + 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_RED_PACKETS, stats)); + 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)); + + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(Eq(kAclCounterOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + EXPECT_FALSE(counters_table->get(counter_stats_key, values)); + + // Install rule with packet color GREEN + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + // Populate counter stats in COUNTERS_DB + EXPECT_CALL(mock_sai_policer_, get_policer_stats(Eq(kAclMeterOid1), _, _, _)) + .WillOnce(DoAll(Invoke([](sai_object_id_t policer_id, uint32_t number_of_counters, + const sai_stat_id_t *counter_ids, uint64_t *counters) { + counters[0] = 10; // green_packets + counters[1] = 100; // green_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)); + EXPECT_EQ("10", stats); + + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_GREEN_BYTES, stats)); + EXPECT_EQ("100", stats); + EXPECT_FALSE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_RED_PACKETS, stats)); + 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)); + + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + EXPECT_FALSE(counters_table->get(counter_stats_key, values)); + + // Install rule with packet color YELLOW and RED + app_db_entry.action = "punt_non_green_pk"; + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + // Populate counter stats in COUNTERS_DB + EXPECT_CALL(mock_sai_policer_, get_policer_stats(Eq(kAclMeterOid1), _, _, _)) + .WillOnce(DoAll(Invoke([](sai_object_id_t policer_id, uint32_t number_of_counters, + const sai_stat_id_t *counter_ids, uint64_t *counters) { + counters[0] = 20; // yellow_packets + counters[1] = 200; // yellow_bytes + counters[2] = 30; // red_packets + counters[3] = 300; // red_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)); + EXPECT_EQ("20", stats); + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_YELLOW_BYTES, stats)); + EXPECT_EQ("200", stats); + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_RED_PACKETS, stats)); + EXPECT_EQ("30", stats); + EXPECT_TRUE(counters_table->hget(counter_stats_key, P4_COUNTER_STATS_RED_BYTES, stats)); + 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)); + // Remove rule + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + EXPECT_FALSE(counters_table->get(counter_stats_key, values)); +} + +TEST_F(AclManagerTest, DoAclCounterStatsTaskFailsWhenSaiCallFails) +{ + ASSERT_NO_FATAL_FAILURE(AddDefaultIngressTable()); + auto counters_table = std::make_unique(gCountersDb, std::string(COUNTERS_TABLE) + + DEFAULT_KEY_SEPARATOR + APP_P4RT_TABLE_NAME); + + // Insert the ACL rule + auto app_db_entry = getDefaultAclRuleAppDbEntryWithoutAction(); + const auto &acl_rule_key = + KeyGenerator::generateAclRuleKey(app_db_entry.match_fvs, std::to_string(app_db_entry.priority)); + const auto &counter_stats_key = app_db_entry.db_key; + std::vector values; + std::string stats; + app_db_entry.action = "set_dst_ipv6"; + app_db_entry.action_param_fvs["ip_address"] = "fdf8:f53b:82e4::53"; + EXPECT_CALL(mock_sai_acl_, create_acl_entry(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclIngressRuleOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_acl_, create_acl_counter(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclCounterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_policer_, create_policer(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAclMeterOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + + // Populate counter stats in COUNTERS_DB + EXPECT_CALL(mock_sai_acl_, get_acl_counter_attribute(Eq(kAclCounterOid1), _, _)) + .WillOnce(Return(SAI_STATUS_NOT_IMPLEMENTED)); + DoAclCounterStatsTask(); + EXPECT_FALSE(counters_table->get(counter_stats_key, values)); + + // Remove rule + EXPECT_CALL(mock_sai_acl_, remove_acl_entry(Eq(kAclIngressRuleOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_counter(Eq(kAclCounterOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_policer_, remove_policer(Eq(kAclMeterOid1))).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); + + // Install rule with packet color GREEN + app_db_entry.action = "copy_and_set_tc"; + app_db_entry.action_param_fvs["traffic_class"] = "0x20"; + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRuleRequest(acl_rule_key, app_db_entry)); + // Fails when get_policer_stats() is not implemented + EXPECT_CALL(mock_sai_policer_, get_policer_stats(Eq(kAclMeterOid1), _, _, _)) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + DoAclCounterStatsTask(); + EXPECT_FALSE(counters_table->get(counter_stats_key, values)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRuleRequest(kAclIngressTableName, acl_rule_key)); + EXPECT_EQ(nullptr, GetAclRule(kAclIngressTableName, acl_rule_key)); +} + +TEST_F(AclManagerTest, DISABLED_InitCreateGroupFails) +{ + // Failed to create ACL groups + EXPECT_CALL(mock_sai_serialize_, sai_serialize_object_id(_)).WillRepeatedly(Return(EMPTY_STRING)); + EXPECT_CALL(mock_sai_acl_, create_acl_table_group(_, Eq(gSwitchId), Eq(3), _)) + .WillOnce(Return(SAI_STATUS_TABLE_FULL)); + TableConnector stateDbSwitchTable(gStateDb, "SWITCH_CAPABILITY"); + TableConnector app_switch_table(gAppDb, APP_SWITCH_TABLE_NAME); + TableConnector conf_asic_sensors(gConfigDb, CFG_ASIC_SENSORS_TABLE_NAME); + std::vector switch_tables = {conf_asic_sensors, app_switch_table}; + EXPECT_THROW(new SwitchOrch(gAppDb, switch_tables, stateDbSwitchTable), std::runtime_error); +} + +TEST_F(AclManagerTest, DISABLED_InitBindGroupToSwitchFails) +{ + EXPECT_CALL(mock_sai_serialize_, sai_serialize_object_id(_)).WillRepeatedly(Return(EMPTY_STRING)); + // Failed to bind ACL group to switch attribute. + EXPECT_CALL(mock_sai_acl_, create_acl_table_group(_, Eq(gSwitchId), Eq(3), _)) + .WillOnce(DoAll(SetArgPointee<0>(kAclGroupIngressOid), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_switch_, set_switch_attribute(Eq(gSwitchId), _)).WillOnce(Return(SAI_STATUS_FAILURE)); + TableConnector stateDbSwitchTable(gStateDb, "SWITCH_CAPABILITY"); + TableConnector app_switch_table(gAppDb, APP_SWITCH_TABLE_NAME); + TableConnector conf_asic_sensors(gConfigDb, CFG_ASIC_SENSORS_TABLE_NAME); + std::vector switch_tables = {conf_asic_sensors, app_switch_table}; + EXPECT_THROW(new SwitchOrch(gAppDb, switch_tables, stateDbSwitchTable), std::runtime_error); +} + +} // namespace test +} // namespace p4orch diff --git a/orchagent/p4orch/tests/fake_consumerstatetable.cpp b/orchagent/p4orch/tests/fake_consumerstatetable.cpp new file mode 100644 index 000000000000..045950abfd08 --- /dev/null +++ b/orchagent/p4orch/tests/fake_consumerstatetable.cpp @@ -0,0 +1,11 @@ +#include "consumerstatetable.h" + +namespace swss +{ + +ConsumerStateTable::ConsumerStateTable(DBConnector *db, const std::string &tableName, int popBatchSize, int pri) + : ConsumerTableBase(db, tableName, popBatchSize, pri), TableName_KeySet(tableName) +{ +} + +} // namespace swss diff --git a/orchagent/p4orch/tests/fake_crmorch.cpp b/orchagent/p4orch/tests/fake_crmorch.cpp new file mode 100644 index 000000000000..03f15c28aca4 --- /dev/null +++ b/orchagent/p4orch/tests/fake_crmorch.cpp @@ -0,0 +1,67 @@ +#include +#include + +#include "crmorch.h" + +CrmOrch::CrmOrch(swss::DBConnector *db, std::string tableName) : Orch(db, std::vector{}) +{ +} + +void CrmOrch::incCrmResUsedCounter(CrmResourceType resource) +{ +} + +void CrmOrch::decCrmResUsedCounter(CrmResourceType resource) +{ +} + +void CrmOrch::incCrmAclUsedCounter(CrmResourceType resource, sai_acl_stage_t stage, sai_acl_bind_point_type_t point) +{ +} + +void CrmOrch::decCrmAclUsedCounter(CrmResourceType resource, sai_acl_stage_t stage, sai_acl_bind_point_type_t point, + sai_object_id_t oid) +{ +} + +void CrmOrch::incCrmAclTableUsedCounter(CrmResourceType resource, sai_object_id_t tableId) +{ +} + +void CrmOrch::decCrmAclTableUsedCounter(CrmResourceType resource, sai_object_id_t tableId) +{ +} + +void CrmOrch::doTask(Consumer &consumer) +{ +} + +void CrmOrch::handleSetCommand(const std::string &key, const std::vector &data) +{ +} + +void CrmOrch::doTask(swss::SelectableTimer &timer) +{ +} + +void CrmOrch::getResAvailableCounters() +{ +} + +void CrmOrch::updateCrmCountersTable() +{ +} + +void CrmOrch::checkCrmThresholds() +{ +} + +std::string CrmOrch::getCrmAclKey(sai_acl_stage_t stage, sai_acl_bind_point_type_t bindPoint) +{ + return ""; +} + +std::string CrmOrch::getCrmAclTableKey(sai_object_id_t id) +{ + return ""; +} diff --git a/orchagent/p4orch/tests/fake_dbconnector.cpp b/orchagent/p4orch/tests/fake_dbconnector.cpp new file mode 100644 index 000000000000..1709d9d9772b --- /dev/null +++ b/orchagent/p4orch/tests/fake_dbconnector.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include "dbconnector.h" + +namespace swss +{ + +static std::map dbNameIdMap = { + {"APPL_DB", 0}, {"ASIC_DB", 1}, {"COUNTERS_DB", 2}, {"CONFIG_DB", 4}, {"FLEX_COUNTER_DB", 5}, {"STATE_DB", 6}, +}; + +RedisContext::RedisContext() +{ +} + +RedisContext::~RedisContext() +{ +} + +DBConnector::DBConnector(int dbId, const std::string &hostname, int port, unsigned int timeout) : m_dbId(dbId) +{ +} + +DBConnector::DBConnector(const std::string &dbName, unsigned int timeout, bool isTcpConn) +{ + if (dbNameIdMap.find(dbName) != dbNameIdMap.end()) + { + m_dbId = dbNameIdMap[dbName]; + } + else + { + m_dbId = -1; + } +} + +DBConnector::DBConnector(int dbId, const std::string &unixPath, unsigned int timeout) : m_dbId(dbId) +{ +} + +int DBConnector::getDbId() const +{ + return m_dbId; +} + +} // namespace swss diff --git a/orchagent/p4orch/tests/fake_notificationconsumer.cpp b/orchagent/p4orch/tests/fake_notificationconsumer.cpp new file mode 100644 index 000000000000..b903b336aba4 --- /dev/null +++ b/orchagent/p4orch/tests/fake_notificationconsumer.cpp @@ -0,0 +1,13 @@ +#include "notificationconsumer.h" + +namespace swss +{ + +NotificationConsumer::NotificationConsumer(swss::DBConnector *db, const std::string &channel, int pri, + size_t popBatchSize) + : Selectable(pri), POP_BATCH_SIZE(popBatchSize), m_db(db), m_subscribe(NULL), m_channel(channel) +{ + SWSS_LOG_ENTER(); +} + +} // namespace swss diff --git a/orchagent/p4orch/tests/fake_portorch.cpp b/orchagent/p4orch/tests/fake_portorch.cpp new file mode 100644 index 000000000000..aaf766e1aa74 --- /dev/null +++ b/orchagent/p4orch/tests/fake_portorch.cpp @@ -0,0 +1,691 @@ +extern "C" +{ +#include "sai.h" +} + +#include +#include + +#include "portsorch.h" + +#define PORT_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS 1000 +#define PORT_BUFFER_DROP_STAT_POLLING_INTERVAL_MS 60000 +#define QUEUE_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS 10000 + +PortsOrch::PortsOrch(DBConnector *db, DBConnector *stateDb, vector &tableNames, + DBConnector *chassisAppDb) + : Orch(db, tableNames), m_portStateTable(stateDb, STATE_PORT_TABLE_NAME), + port_stat_manager(PORT_STAT_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, + PORT_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS, true), + port_buffer_drop_stat_manager(PORT_BUFFER_DROP_STAT_FLEX_COUNTER_GROUP, StatsMode::READ, + PORT_BUFFER_DROP_STAT_POLLING_INTERVAL_MS, true), + queue_stat_manager(QUEUE_STAT_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, + QUEUE_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS, true) +{ +} + +bool PortsOrch::allPortsReady() +{ + return true; +} + +bool PortsOrch::isInitDone() +{ + return true; +} + +bool PortsOrch::isConfigDone() +{ + return true; +} + +bool PortsOrch::isPortAdminUp(const string &alias) +{ + return true; +} + +std::map &PortsOrch::getAllPorts() +{ + return m_portList; +} + +bool PortsOrch::bake() +{ + return true; +} + +void PortsOrch::cleanPortTable(const vector &keys) +{ +} + +bool PortsOrch::getBridgePort(sai_object_id_t id, Port &port) +{ + return true; +} + +bool PortsOrch::setBridgePortLearningFDB(Port &port, sai_bridge_port_fdb_learning_mode_t mode) +{ + return true; +} + +bool PortsOrch::getPort(string alias, Port &port) +{ + if (m_portList.find(alias) == m_portList.end()) + { + return false; + } + port = m_portList[alias]; + return true; +} + +bool PortsOrch::getPort(sai_object_id_t id, Port &port) +{ + for (const auto &p : m_portList) + { + if (p.second.m_port_id == id) + { + port = p.second; + return true; + } + } + return false; +} + +void PortsOrch::increasePortRefCount(const string &alias) +{ +} + +void PortsOrch::decreasePortRefCount(const string &alias) +{ +} + +bool PortsOrch::getPortByBridgePortId(sai_object_id_t bridge_port_id, Port &port) +{ + return true; +} + +void PortsOrch::setPort(string alias, Port port) +{ + m_portList[alias] = port; +} + +void PortsOrch::getCpuPort(Port &port) +{ +} + +bool PortsOrch::getInbandPort(Port &port) +{ + return true; +} + +bool PortsOrch::getVlanByVlanId(sai_vlan_id_t vlan_id, Port &vlan) +{ + return true; +} + +bool PortsOrch::setHostIntfsOperStatus(const Port &port, bool up) const +{ + return true; +} + +void PortsOrch::updateDbPortOperStatus(const Port &port, sai_port_oper_status_t status) const +{ +} + +bool PortsOrch::createVlanHostIntf(Port &vl, string hostif_name) +{ + return true; +} + +bool PortsOrch::removeVlanHostIntf(Port vl) +{ + return true; +} + +bool PortsOrch::createBindAclTableGroup(sai_object_id_t port_oid, sai_object_id_t acl_table_oid, + sai_object_id_t &group_oid, acl_stage_type_t acl_stage) +{ + return true; +} + +bool PortsOrch::unbindRemoveAclTableGroup(sai_object_id_t port_oid, sai_object_id_t acl_table_oid, + acl_stage_type_t acl_stage) +{ + return true; +} + +bool PortsOrch::bindAclTable(sai_object_id_t id, sai_object_id_t table_oid, sai_object_id_t &group_member_oid, + acl_stage_type_t acl_stage) +{ + return true; +} + +bool PortsOrch::unbindAclTable(sai_object_id_t port_oid, sai_object_id_t acl_table_oid, + sai_object_id_t acl_group_member_oid, acl_stage_type_t acl_stage) +{ + return true; +} + +bool PortsOrch::bindUnbindAclTableGroup(Port &port, bool ingress, bool bind) +{ + return true; +} + +bool PortsOrch::getPortPfc(sai_object_id_t portId, uint8_t *pfc_bitmask) +{ + return true; +} + +bool PortsOrch::setPortPfc(sai_object_id_t portId, uint8_t pfc_bitmask) +{ + return true; +} + +void PortsOrch::generateQueueMap() +{ +} + +void PortsOrch::generatePriorityGroupMap() +{ +} + +void PortsOrch::generatePortCounterMap() +{ +} + +void PortsOrch::generatePortBufferDropCounterMap() +{ +} + +void PortsOrch::refreshPortStatus() +{ +} + +bool PortsOrch::removeAclTableGroup(const Port &p) +{ + return true; +} + +bool PortsOrch::addSubPort(Port &port, const string &alias, const bool &adminUp, const uint32_t &mtu) +{ + return true; +} + +bool PortsOrch::removeSubPort(const string &alias) +{ + return true; +} + +bool PortsOrch::updateL3VniStatus(uint16_t vlan_id, bool status) +{ + return true; +} + +void PortsOrch::getLagMember(Port &lag, vector &portv) +{ +} + +void PortsOrch::updateChildPortsMtu(const Port &p, const uint32_t mtu) +{ +} + +bool PortsOrch::addTunnel(string tunnel, sai_object_id_t, bool learning) +{ + return true; +} + +bool PortsOrch::removeTunnel(Port tunnel) +{ + return true; +} + +bool PortsOrch::addBridgePort(Port &port) +{ + return true; +} + +bool PortsOrch::removeBridgePort(Port &port) +{ + return true; +} + +bool PortsOrch::addVlanMember(Port &vlan, Port &port, string &tagging_mode, string end_point_ip) +{ + return true; +} + +bool PortsOrch::removeVlanMember(Port &vlan, Port &port, string end_point_ip) +{ + return true; +} + +bool PortsOrch::isVlanMember(Port &vlan, Port &port, string end_point_ip) +{ + return true; +} + +bool PortsOrch::addVlanFloodGroups(Port &vlan, Port &port, string end_point_ip) +{ + return true; +} + +bool PortsOrch::removeVlanEndPointIp(Port &vlan, Port &port, string end_point_ip) +{ + return true; +} + +void PortsOrch::increaseBridgePortRefCount(Port &port) +{ +} + +void PortsOrch::decreaseBridgePortRefCount(Port &port) +{ +} + +bool PortsOrch::getBridgePortReferenceCount(Port &port) +{ + return true; +} + +bool PortsOrch::isInbandPort(const string &alias) +{ + return true; +} + +bool PortsOrch::setVoqInbandIntf(string &alias, string &type) +{ + return true; +} + +bool PortsOrch::getRecircPort(Port &p, string role) +{ + return true; +} + +const gearbox_phy_t *PortsOrch::getGearboxPhy(const Port &port) +{ + return nullptr; +} + +bool PortsOrch::getPortIPG(sai_object_id_t port_id, uint32_t &ipg) +{ + return true; +} + +bool PortsOrch::setPortIPG(sai_object_id_t port_id, uint32_t ipg) +{ + return true; +} + +bool PortsOrch::getPortOperStatus(const Port &port, sai_port_oper_status_t &status) const +{ + status = port.m_oper_status; + return true; +} + +std::string PortsOrch::getQueueWatermarkFlexCounterTableKey(std::string s) +{ + return ""; +} + +std::string PortsOrch::getPriorityGroupWatermarkFlexCounterTableKey(std::string s) +{ + return ""; +} + +std::string PortsOrch::getPriorityGroupDropPacketsFlexCounterTableKey(std::string s) +{ + return ""; +} + +std::string PortsOrch::getPortRateFlexCounterTableKey(std::string s) +{ + return ""; +} + +void PortsOrch::doTask() +{ +} + +void PortsOrch::doTask(Consumer &consumer) +{ +} + +void PortsOrch::doPortTask(Consumer &consumer) +{ +} + +void PortsOrch::doVlanTask(Consumer &consumer) +{ +} + +void PortsOrch::doVlanMemberTask(Consumer &consumer) +{ +} + +void PortsOrch::doLagTask(Consumer &consumer) +{ +} + +void PortsOrch::doLagMemberTask(Consumer &consumer) +{ +} + +void PortsOrch::doTask(NotificationConsumer &consumer) +{ +} + +void PortsOrch::removePortFromLanesMap(string alias) +{ +} + +void PortsOrch::removePortFromPortListMap(sai_object_id_t port_id) +{ +} + +void PortsOrch::removeDefaultVlanMembers() +{ +} + +void PortsOrch::removeDefaultBridgePorts() +{ +} + +bool PortsOrch::initializePort(Port &port) +{ + return true; +} + +void PortsOrch::initializePriorityGroups(Port &port) +{ +} + +void PortsOrch::initializePortMaximumHeadroom(Port &port) +{ +} + +void PortsOrch::initializeQueues(Port &port) +{ +} + +bool PortsOrch::addHostIntfs(Port &port, string alias, sai_object_id_t &host_intfs_id) +{ + return true; +} + +bool PortsOrch::setHostIntfsStripTag(Port &port, sai_hostif_vlan_tag_t strip) +{ + return true; +} + +bool PortsOrch::setBridgePortLearnMode(Port &port, string learn_mode) +{ + return true; +} + +bool PortsOrch::addVlan(string vlan) +{ + return true; +} + +bool PortsOrch::removeVlan(Port vlan) +{ + return true; +} + +bool PortsOrch::addLag(string lag, uint32_t spa_id, int32_t switch_id) +{ + return true; +} + +bool PortsOrch::removeLag(Port lag) +{ + return true; +} + +bool PortsOrch::setLagTpid(sai_object_id_t id, sai_uint16_t tpid) +{ + return true; +} + +bool PortsOrch::addLagMember(Port &lag, Port &port, bool enableForwarding) +{ + return true; +} + +bool PortsOrch::removeLagMember(Port &lag, Port &port) +{ + return true; +} + +bool PortsOrch::setCollectionOnLagMember(Port &lagMember, bool enableCollection) +{ + return true; +} + +bool PortsOrch::setDistributionOnLagMember(Port &lagMember, bool enableDistribution) +{ + return true; +} + +bool PortsOrch::addPort(const set &lane_set, uint32_t speed, int an, string fec) +{ + return true; +} + +sai_status_t PortsOrch::removePort(sai_object_id_t port_id) +{ + return SAI_STATUS_SUCCESS; +} + +bool PortsOrch::initPort(const string &alias, const string &role, const int index, const set &lane_set) +{ + return true; +} + +void PortsOrch::deInitPort(string alias, sai_object_id_t port_id) +{ +} + +bool PortsOrch::setPortAdminStatus(Port &port, bool up) +{ + return true; +} + +bool PortsOrch::getPortAdminStatus(sai_object_id_t id, bool &up) +{ + return true; +} + +bool PortsOrch::setPortMtu(sai_object_id_t id, sai_uint32_t mtu) +{ + return true; +} + +bool PortsOrch::setPortTpid(sai_object_id_t id, sai_uint16_t tpid) +{ + return true; +} + +bool PortsOrch::setPortPvid(Port &port, sai_uint32_t pvid) +{ + return true; +} + +bool PortsOrch::getPortPvid(Port &port, sai_uint32_t &pvid) +{ + return true; +} + +bool PortsOrch::setPortFec(Port &port, sai_port_fec_mode_t mode) +{ + return true; +} + +bool PortsOrch::setPortPfcAsym(Port &port, string pfc_asym) +{ + return true; +} + +bool PortsOrch::getDestPortId(sai_object_id_t src_port_id, dest_port_type_t port_type, sai_object_id_t &des_port_id) +{ + return true; +} + +bool PortsOrch::setBridgePortAdminStatus(sai_object_id_t id, bool up) +{ + return true; +} + +bool PortsOrch::isSpeedSupported(const std::string &alias, sai_object_id_t port_id, sai_uint32_t speed) +{ + return true; +} + +void PortsOrch::getPortSupportedSpeeds(const std::string &alias, sai_object_id_t port_id, + PortSupportedSpeeds &supported_speeds) +{ +} + +void PortsOrch::initPortSupportedSpeeds(const std::string &alias, sai_object_id_t port_id) +{ +} + +task_process_status PortsOrch::setPortSpeed(Port &port, sai_uint32_t speed) +{ + return task_success; +} + +bool PortsOrch::getPortSpeed(sai_object_id_t port_id, sai_uint32_t &speed) +{ + return true; +} + +bool PortsOrch::setGearboxPortsAttr(Port &port, sai_port_attr_t id, void *value) +{ + return true; +} + +bool PortsOrch::setGearboxPortAttr(Port &port, dest_port_type_t port_type, sai_port_attr_t id, void *value) +{ + return true; +} + +task_process_status PortsOrch::setPortAdvSpeeds(sai_object_id_t port_id, std::vector &speed_list) +{ + return task_success; +} + +bool PortsOrch::getQueueTypeAndIndex(sai_object_id_t queue_id, string &type, uint8_t &index) +{ + return true; +} + +void PortsOrch::generateQueueMapPerPort(const Port &port) +{ +} + +void PortsOrch::generatePriorityGroupMapPerPort(const Port &port) +{ +} + +task_process_status PortsOrch::setPortAutoNeg(sai_object_id_t id, int an) +{ + return task_success; +} + +bool PortsOrch::setPortFecMode(sai_object_id_t id, int fec) +{ + return true; +} + +task_process_status PortsOrch::setPortInterfaceType(sai_object_id_t id, sai_port_interface_type_t interface_type) +{ + return task_success; +} + +task_process_status PortsOrch::setPortAdvInterfaceTypes(sai_object_id_t id, std::vector &interface_types) +{ + return task_success; +} + +void PortsOrch::updatePortOperStatus(Port &port, sai_port_oper_status_t status) +{ +} + +bool PortsOrch::getPortOperSpeed(const Port &port, sai_uint32_t &speed) const +{ + return true; +} + +void PortsOrch::updateDbPortOperSpeed(Port &port, sai_uint32_t speed) +{ +} + +void PortsOrch::getPortSerdesVal(const std::string &s, std::vector &lane_values) +{ +} + +bool PortsOrch::getPortAdvSpeedsVal(const std::string &s, std::vector &speed_values) +{ + return true; +} + +bool PortsOrch::getPortInterfaceTypeVal(const std::string &s, sai_port_interface_type_t &interface_type) +{ + return true; +} + +bool PortsOrch::getPortAdvInterfaceTypesVal(const std::string &s, std::vector &type_values) +{ + return true; +} + +void PortsOrch::removePortSerdesAttribute(sai_object_id_t port_id) +{ +} + +bool PortsOrch::getSaiAclBindPointType(Port::Type type, sai_acl_bind_point_type_t &sai_acl_bind_type) +{ + return true; +} + +void PortsOrch::initGearbox() +{ +} + +bool PortsOrch::initGearboxPort(Port &port) +{ + return true; +} + +bool PortsOrch::getSystemPorts() +{ + return true; +} + +bool PortsOrch::addSystemPorts() +{ + return true; +} + +void PortsOrch::voqSyncAddLag(Port &lag) +{ +} + +void PortsOrch::voqSyncDelLag(Port &lag) +{ +} + +void PortsOrch::voqSyncAddLagMember(Port &lag, Port &port) +{ +} + +void PortsOrch::voqSyncDelLagMember(Port &lag, Port &port) +{ +} + +std::unordered_set PortsOrch::generateCounterStats(const string &type) +{ + return {}; +} \ No newline at end of file diff --git a/orchagent/p4orch/tests/fake_producertable.cpp b/orchagent/p4orch/tests/fake_producertable.cpp new file mode 100644 index 000000000000..703be025ebe5 --- /dev/null +++ b/orchagent/p4orch/tests/fake_producertable.cpp @@ -0,0 +1,27 @@ +#include +#include + +#include "producertable.h" + +namespace swss +{ + +ProducerTable::ProducerTable(DBConnector *db, const std::string &tableName) + : TableBase(tableName, ":"), TableName_KeyValueOpQueues(tableName) +{ +} + +ProducerTable::~ProducerTable() +{ +} + +void ProducerTable::set(const std::string &key, const std::vector &values, const std::string &op, + const std::string &prefix) +{ +} + +void ProducerTable::del(const std::string &key, const std::string &op, const std::string &prefix) +{ +} + +} // namespace swss diff --git a/orchagent/p4orch/tests/fake_subscriberstatetable.cpp b/orchagent/p4orch/tests/fake_subscriberstatetable.cpp new file mode 100644 index 000000000000..729fbcefae98 --- /dev/null +++ b/orchagent/p4orch/tests/fake_subscriberstatetable.cpp @@ -0,0 +1,11 @@ +#include "subscriberstatetable.h" + +namespace swss +{ + +SubscriberStateTable::SubscriberStateTable(DBConnector *db, const std::string &tableName, int popBatchSize, int pri) + : ConsumerTableBase(db, tableName, popBatchSize, pri), m_table(db, tableName) +{ +} + +} // namespace swss diff --git a/orchagent/p4orch/tests/fake_table.cpp b/orchagent/p4orch/tests/fake_table.cpp new file mode 100644 index 000000000000..e1f785f048c1 --- /dev/null +++ b/orchagent/p4orch/tests/fake_table.cpp @@ -0,0 +1,97 @@ +#include + +#include "table.h" + +namespace swss +{ + +using TableDataT = std::map>; +using TablesT = std::map; + +namespace fake_db +{ + +TablesT gTables; + +} // namespace fake_db + +using namespace fake_db; + +Table::Table(const DBConnector *db, const std::string &tableName) : TableBase(tableName, ":") +{ +} + +Table::~Table() +{ +} + +void Table::hset(const std::string &key, const std::string &field, const std::string &value, const std::string & /*op*/, + const std::string & /*prefix*/) +{ + gTables[getTableName()][key][field] = value; +} + +void Table::set(const std::string &key, const std::vector &values, const std::string & /*op*/, + const std::string & /*prefix*/) +{ + auto &fvs = gTables[getTableName()][key]; + for (const auto &fv : values) + { + fvs[fv.first] = fv.second; + } +} + +bool Table::hget(const std::string &key, const std::string &field, std::string &value) +{ + const auto &table_data = gTables[getTableName()]; + const auto &key_it = table_data.find(key); + if (key_it == table_data.end()) + { + return false; + } + const auto &field_it = key_it->second.find(field); + if (field_it == key_it->second.end()) + { + return false; + } + value = field_it->second; + return true; +} + +bool Table::get(const std::string &key, std::vector &ovalues) +{ + const auto &table_data = gTables[getTableName()]; + if (table_data.find(key) == table_data.end()) + { + return false; + } + + for (const auto &fv : table_data.at(key)) + { + ovalues.push_back({fv.first, fv.second}); + } + return true; +} + +void Table::del(const std::string &key, const std::string & /*op*/, const std::string & /*prefix*/) +{ + gTables[getTableName()].erase(key); +} + +void Table::hdel(const std::string &key, const std::string &field, const std::string & /*op*/, + const std::string & /*prefix*/) +{ + gTables[getTableName()][key].erase(field); +} + +void Table::getKeys(std::vector &keys) +{ + keys.clear(); + auto table = gTables[getTableName()]; + for (const auto &it : table) + { + keys.push_back(it.first); + } +} + +} // namespace swss diff --git a/orchagent/p4orch/tests/mirror_session_manager_test.cpp b/orchagent/p4orch/tests/mirror_session_manager_test.cpp new file mode 100644 index 000000000000..c45a0d9bcd17 --- /dev/null +++ b/orchagent/p4orch/tests/mirror_session_manager_test.cpp @@ -0,0 +1,1011 @@ +#include "p4orch/mirror_session_manager.h" + +#include +#include + +#include +#include + +#include "json.hpp" +#include "mock_response_publisher.h" +#include "mock_sai_mirror.h" +#include "p4oidmapper.h" +#include "p4orch_util.h" +#include "portsorch.h" +#include "swss/ipaddress.h" +#include "swss/macaddress.h" +#include "swssnet.h" +extern "C" +{ +#include "sai.h" +} + +extern sai_mirror_api_t *sai_mirror_api; +extern sai_object_id_t gSwitchId; +extern PortsOrch *gPortsOrch; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::Truly; + +namespace p4orch +{ +namespace test +{ +namespace +{ + +constexpr char *kMirrorSessionId = "mirror_session1"; +constexpr sai_object_id_t kMirrorSessionOid = 0x445566; +// A physical port set up in test_main.cpp +constexpr char *kPort1 = "Ethernet1"; +constexpr sai_object_id_t kPort1Oid = 0x112233; +// A management port set up in test_main.cpp +constexpr char *kPort2 = "Ethernet8"; +// A physical port set up in test_main.cpp +constexpr char *kPort3 = "Ethernet3"; +constexpr sai_object_id_t kPort3Oid = 0xaabbccdd; +constexpr char *kSrcIp1 = "10.206.196.31"; +constexpr char *kSrcIp2 = "10.206.196.32"; +constexpr char *kDstIp1 = "172.20.0.203"; +constexpr char *kDstIp2 = "172.20.0.204"; +constexpr char *kSrcMac1 = "00:02:03:04:05:06"; +constexpr char *kSrcMac2 = "00:02:03:04:05:07"; +constexpr char *kDstMac1 = "00:1a:11:17:5f:80"; +constexpr char *kDstMac2 = "00:1a:11:17:5f:81"; +constexpr char *kTtl1 = "0x40"; +constexpr char *kTtl2 = "0x41"; +constexpr uint8_t kTtl1Num = 0x40; +constexpr uint8_t kTtl2Num = 0x41; +constexpr char *kTos1 = "0x00"; +constexpr char *kTos2 = "0x01"; +constexpr uint8_t kTos1Num = 0x00; +constexpr uint8_t kTos2Num = 0x01; + +// Generates attribute list for create_mirror_session(). +std::vector GenerateAttrListForCreate(sai_object_id_t port_oid, uint8_t ttl, uint8_t tos, + const swss::IpAddress &src_ip, const swss::IpAddress &dst_ip, + const swss::MacAddress &src_mac, const swss::MacAddress &dst_mac) +{ + std::vector attrs; + sai_attribute_t attr; + + attr.id = SAI_MIRROR_SESSION_ATTR_MONITOR_PORT; + attr.value.oid = port_oid; + 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 = tos; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_TTL; + attr.value.u8 = ttl; + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS; + swss::copy(attr.value.ipaddr, src_ip); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS; + swss::copy(attr.value.ipaddr, dst_ip); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS; + memcpy(attr.value.mac, src_mac.getMac(), sizeof(sai_mac_t)); + attrs.push_back(attr); + + attr.id = SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS; + memcpy(attr.value.mac, 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; +} + +// Matcher for attribute list in SAI mirror call. +// Returns true if attribute lists have the same values in the same order. +bool MatchSaiCallAttrList(const sai_attribute_t *attr_list, const std::vector &expected_attr_list) +{ + if (attr_list == nullptr) + { + return false; + } + + for (uint i = 0; i < expected_attr_list.size(); ++i) + { + switch (attr_list[i].id) + { + case SAI_MIRROR_SESSION_ATTR_MONITOR_PORT: + if (attr_list[i].value.oid != expected_attr_list[i].value.oid) + { + return false; + } + break; + + case SAI_MIRROR_SESSION_ATTR_TYPE: + case SAI_MIRROR_SESSION_ATTR_ERSPAN_ENCAPSULATION_TYPE: + if (attr_list[i].value.s32 != expected_attr_list[i].value.s32) + { + return false; + } + break; + + case SAI_MIRROR_SESSION_ATTR_IPHDR_VERSION: + case SAI_MIRROR_SESSION_ATTR_TOS: + case SAI_MIRROR_SESSION_ATTR_TTL: + if (attr_list[i].value.u8 != expected_attr_list[i].value.u8) + { + return false; + } + break; + + case SAI_MIRROR_SESSION_ATTR_GRE_PROTOCOL_TYPE: + if (attr_list[i].value.u16 != expected_attr_list[i].value.u16) + { + return false; + } + break; + + case SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS: + case SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS: + if (attr_list[i].value.ipaddr.addr_family != expected_attr_list[i].value.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[i].value.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[i].value.ipaddr.addr.ip6, + sizeof(sai_ip6_t)) != 0)) + { + return false; + } + break; + + case SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS: + case SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS: + if (memcmp(&attr_list[i].value.mac, &expected_attr_list[i].value.mac, sizeof(sai_mac_t)) != 0) + { + return false; + } + break; + + default: + return false; + } + } + + return true; +} + +} // namespace + +class MirrorSessionManagerTest : public ::testing::Test +{ + protected: + MirrorSessionManagerTest() : mirror_session_manager_(&p4_oid_mapper_, &publisher_) + { + } + + void SetUp() override + { + // Set up mock stuff for SAI mirror API structure. + mock_sai_mirror = &mock_sai_mirror_; + sai_mirror_api->create_mirror_session = mock_create_mirror_session; + sai_mirror_api->remove_mirror_session = mock_remove_mirror_session; + sai_mirror_api->set_mirror_session_attribute = mock_set_mirror_session_attribute; + sai_mirror_api->get_mirror_session_attribute = mock_get_mirror_session_attribute; + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + return mirror_session_manager_.enqueue(entry); + } + + void Drain() + { + return mirror_session_manager_.drain(); + } + + ReturnCodeOr DeserializeP4MirrorSessionAppDbEntry( + const std::string &key, const std::vector &attributes) + { + return mirror_session_manager_.deserializeP4MirrorSessionAppDbEntry(key, attributes); + } + + p4orch::P4MirrorSessionEntry *GetMirrorSessionEntry(const std::string &mirror_session_key) + { + return mirror_session_manager_.getMirrorSessionEntry(mirror_session_key); + } + + ReturnCode ProcessAddRequest(const P4MirrorSessionAppDbEntry &app_db_entry) + { + return mirror_session_manager_.processAddRequest(app_db_entry); + } + + ReturnCode CreateMirrorSession(p4orch::P4MirrorSessionEntry mirror_session_entry) + { + return mirror_session_manager_.createMirrorSession(mirror_session_entry); + } + + ReturnCode ProcessUpdateRequest(const P4MirrorSessionAppDbEntry &app_db_entry, + p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.processUpdateRequest(app_db_entry, existing_mirror_session_entry); + } + + ReturnCode SetPort(const std::string &new_port, p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setPort(new_port, existing_mirror_session_entry); + } + + ReturnCode SetSrcIp(const swss::IpAddress &new_src_ip, p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setSrcIp(new_src_ip, existing_mirror_session_entry); + } + + ReturnCode SetDstIp(const swss::IpAddress &new_dst_ip, p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setDstIp(new_dst_ip, existing_mirror_session_entry); + } + + ReturnCode SetSrcMac(const swss::MacAddress &new_src_mac, + p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setSrcMac(new_src_mac, existing_mirror_session_entry); + } + + ReturnCode SetDstMac(const swss::MacAddress &new_dst_mac, + p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setDstMac(new_dst_mac, existing_mirror_session_entry); + } + + ReturnCode SetTtl(uint8_t new_ttl, p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setTtl(new_ttl, existing_mirror_session_entry); + } + + ReturnCode SetTos(uint8_t new_tos, p4orch::P4MirrorSessionEntry *existing_mirror_session_entry) + { + return mirror_session_manager_.setTos(new_tos, existing_mirror_session_entry); + } + + ReturnCode ProcessDeleteRequest(const std::string &mirror_session_key) + { + return mirror_session_manager_.processDeleteRequest(mirror_session_key); + } + + void AddDefaultMirrorSection() + { + P4MirrorSessionAppDbEntry app_db_entry; + app_db_entry.mirror_session_id = kMirrorSessionId; + app_db_entry.has_port = true; + app_db_entry.port = kPort1; + app_db_entry.has_src_ip = true; + app_db_entry.src_ip = swss::IpAddress(kSrcIp1); + app_db_entry.has_dst_ip = true; + app_db_entry.dst_ip = swss::IpAddress(kDstIp1); + app_db_entry.has_src_mac = true; + app_db_entry.src_mac = swss::MacAddress(kSrcMac1); + app_db_entry.has_dst_mac = true; + app_db_entry.dst_mac = swss::MacAddress(kDstMac1); + app_db_entry.has_ttl = true; + app_db_entry.ttl = kTtl1Num; + app_db_entry.has_tos = true; + app_db_entry.tos = kTos1Num; + EXPECT_CALL(mock_sai_mirror_, + create_mirror_session(::testing::NotNull(), Eq(gSwitchId), Eq(11), + Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + GenerateAttrListForCreate( + kPort1Oid, kTtl1Num, kTos1Num, swss::IpAddress(kSrcIp1), + swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), + swss::MacAddress(kDstMac1)))))) + .WillOnce(DoAll(SetArgPointee<0>(kMirrorSessionOid), Return(SAI_STATUS_SUCCESS))); + ASSERT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(app_db_entry)); + } + + StrictMock mock_sai_mirror_; + MockResponsePublisher publisher_; + P4OidMapper p4_oid_mapper_; + p4orch::MirrorSessionManager mirror_session_manager_; +}; + +// Do add, update and delete serially. +TEST_F(MirrorSessionManagerTest, SuccessfulEnqueueAndDrain) +{ + // 1. Add a new entry. + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + // Set up mock call. + EXPECT_CALL(mock_sai_mirror_, + create_mirror_session( + ::testing::NotNull(), Eq(gSwitchId), Eq(11), + Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + GenerateAttrListForCreate(kPort1Oid, kTtl1Num, kTos1Num, swss::IpAddress(kSrcIp1), + swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), + swss::MacAddress(kDstMac1)))))) + .WillOnce(DoAll(SetArgPointee<0>(kMirrorSessionOid), Return(SAI_STATUS_SUCCESS))); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); + p4orch::P4MirrorSessionEntry expected_mirror_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_EQ(*mirror_entry, expected_mirror_entry); + + sai_object_id_t oid_in_mapper = 0; + EXPECT_TRUE(p4_oid_mapper_.getOID(SAI_OBJECT_TYPE_MIRROR_SESSION, + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), &oid_in_mapper)); + EXPECT_EQ(kMirrorSessionOid, oid_in_mapper); + + // 2. Update the added entry. + fvs = {{p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort3}, + {prependParamField(p4orch::kSrcIp), kSrcIp2}, {prependParamField(p4orch::kDstIp), kDstIp2}, + {prependParamField(p4orch::kSrcMac), kSrcMac2}, {prependParamField(p4orch::kDstMac), kDstMac2}, + {prependParamField(p4orch::kTtl), kTtl2}, {prependParamField(p4orch::kTos), kTos2}}; + + app_db_entry = {std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + // Set up mock call. + sai_attribute_t attr; + attr.id = SAI_MIRROR_SESSION_ATTR_MONITOR_PORT; + attr.value.oid = kPort3Oid; + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_IP_ADDRESS; + swss::copy(attr.value.ipaddr, swss::IpAddress(kSrcIp2)); + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + attr.id = SAI_MIRROR_SESSION_ATTR_DST_IP_ADDRESS; + swss::copy(attr.value.ipaddr, swss::IpAddress(kDstIp2)); + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + attr.id = SAI_MIRROR_SESSION_ATTR_SRC_MAC_ADDRESS; + memcpy(attr.value.mac, swss::MacAddress(kSrcMac2).getMac(), sizeof(sai_mac_t)); + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + attr.id = SAI_MIRROR_SESSION_ATTR_DST_MAC_ADDRESS; + memcpy(attr.value.mac, swss::MacAddress(kDstMac2).getMac(), sizeof(sai_mac_t)); + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + attr.id = SAI_MIRROR_SESSION_ATTR_TTL; + attr.value.u8 = kTtl2Num; + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + attr.id = SAI_MIRROR_SESSION_ATTR_TOS; + attr.value.u8 = kTos2Num; + EXPECT_CALL( + mock_sai_mirror_, + set_mirror_session_attribute(Eq(kMirrorSessionOid), Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + std::vector({attr}))))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + Drain(); + + mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); + expected_mirror_entry.port = kPort3; + expected_mirror_entry.src_ip = swss::IpAddress(kSrcIp2); + expected_mirror_entry.dst_ip = swss::IpAddress(kDstIp2); + expected_mirror_entry.src_mac = swss::MacAddress(kSrcMac2); + expected_mirror_entry.dst_mac = swss::MacAddress(kDstMac2); + expected_mirror_entry.ttl = kTtl2Num; + expected_mirror_entry.tos = kTos2Num; + EXPECT_EQ(*mirror_entry, expected_mirror_entry); + + // 3. Delete the entry. + fvs = {}; + app_db_entry = {std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), DEL_COMMAND, fvs}; + + Enqueue(app_db_entry); + // Set up mock call. + EXPECT_CALL(mock_sai_mirror_, remove_mirror_session(Eq(kMirrorSessionOid))).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + + mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); + + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_MIRROR_SESSION, + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId))); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailForInvalidAppDbEntryMatchFiled) +{ + nlohmann::json j; + j[prependMatchField("invalid_match_field")] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailForUnknownOp) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), "unknown_op", fvs); + + Enqueue(app_db_entry); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailForInvalidAppDbEntryFieldValue) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), "0123456789"}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailForUnknownAppDbEntryFieldValue) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{{p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, + {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, + {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, + {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, + {prependParamField(p4orch::kTos), kTos1}, + {"unknown_field", "unknown_value"}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailForIncompleteAppDbEntry) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs_missing_tos{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs_missing_tos); + + Enqueue(app_db_entry); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailForUnknownPort) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), "unknown_port"}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailWhenCreateSaiCallFails) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + EXPECT_CALL(mock_sai_mirror_, + create_mirror_session( + ::testing::NotNull(), Eq(gSwitchId), Eq(11), + Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + GenerateAttrListForCreate(kPort1Oid, kTtl1Num, kTos1Num, swss::IpAddress(kSrcIp1), + swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), + swss::MacAddress(kDstMac1)))))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + EXPECT_EQ(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DrainShouldFailWhenDeleteSaiCallFails) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + EXPECT_CALL(mock_sai_mirror_, + create_mirror_session( + ::testing::NotNull(), Eq(gSwitchId), Eq(11), + Truly(std::bind(MatchSaiCallAttrList, std::placeholders::_1, + GenerateAttrListForCreate(kPort1Oid, kTtl1Num, kTos1Num, swss::IpAddress(kSrcIp1), + swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), + swss::MacAddress(kDstMac1)))))) + .WillOnce(DoAll(SetArgPointee<0>(kMirrorSessionOid), Return(SAI_STATUS_SUCCESS))); + Drain(); + + fvs = {}; + app_db_entry = {std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), DEL_COMMAND, fvs}; + + Enqueue(app_db_entry); + EXPECT_CALL(mock_sai_mirror_, remove_mirror_session(Eq(kMirrorSessionOid))).WillOnce(Return(SAI_STATUS_FAILURE)); + Drain(); + + // Check entry still exists. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DeserializeInvalidValueShouldFail) +{ + constexpr char *kInalidKey = R"({"invalid_key"})"; + std::vector fvs{{p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kInalidKey, fvs).ok()); + + constexpr char *kValidKey = R"({"match/mirror_session_id":"mirror_session1"})"; + + std::vector invalid_src_ip_value = {{prependParamField(p4orch::kSrcIp), "0123456789"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kInalidKey, invalid_src_ip_value).ok()); + + std::vector invalid_dst_ip_value = {{prependParamField(p4orch::kDstIp), "0123456789"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, invalid_dst_ip_value).ok()); + + std::vector invalid_src_mac_value = {{prependParamField(p4orch::kSrcMac), "0123456789"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, invalid_src_mac_value).ok()); + + std::vector invalid_dst_mac_value = {{prependParamField(p4orch::kDstMac), "0123456789"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, invalid_dst_mac_value).ok()); + + std::vector invalid_ttl_value = {{prependParamField(p4orch::kTtl), "gpins"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, invalid_ttl_value).ok()); + + std::vector invalid_tos_value = {{prependParamField(p4orch::kTos), "xyz"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, invalid_tos_value).ok()); + + std::vector unsupported_port = {{prependParamField(p4orch::kPort), kPort2}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, unsupported_port).ok()); + + std::vector invalid_action_value = {{p4orch::kAction, "abc"}}; + EXPECT_FALSE(DeserializeP4MirrorSessionAppDbEntry(kValidKey, invalid_action_value).ok()); +} + +TEST_F(MirrorSessionManagerTest, CreateExistingMirrorSessionInMapperShouldFail) +{ + p4orch::P4MirrorSessionEntry mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + + // Add this mirror session's oid to centralized mapper. + 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. + EXPECT_FALSE(CreateMirrorSession(mirror_session_entry).ok()); +} + +TEST_F(MirrorSessionManagerTest, CreateMirrorSessionWithInvalidPortShouldFail) +{ + // Non-existing port. + p4orch::P4MirrorSessionEntry mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, + "Non-existing Port", swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), + swss::MacAddress(kDstMac1), kTtl1Num, kTos1Num); + + EXPECT_FALSE(CreateMirrorSession(mirror_session_entry).ok()); + + // Unsupported management port. + mirror_session_entry.port = kPort2; + EXPECT_FALSE(CreateMirrorSession(mirror_session_entry).ok()); +} + +TEST_F(MirrorSessionManagerTest, UpdatingNonexistingMirrorSessionShouldFail) +{ + P4MirrorSessionAppDbEntry app_db_entry; + // Fail because existing_mirror_session_entry is nullptr. + // (TODO): Expect critical state. + EXPECT_FALSE(ProcessUpdateRequest(app_db_entry, + /*existing_mirror_session_entry=*/nullptr) + .ok()); + + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + + // Fail because the mirror session is not added into centralized mapper. + // (TODO): Expect critical state. + EXPECT_FALSE(ProcessUpdateRequest(app_db_entry, &existing_mirror_session_entry).ok()); +} + +TEST_F(MirrorSessionManagerTest, UpdatingPortFailureCases) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + // Case 1: non-existing port. + EXPECT_FALSE(SetPort("invalid_port", &existing_mirror_session_entry).ok()); + + // Case 2: kPort2 is an unsupported management port. + EXPECT_FALSE(SetPort(kPort2, &existing_mirror_session_entry).ok()); + + // Case 3: SAI call failure. + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetPort(kPort3, &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +TEST_F(MirrorSessionManagerTest, UpdatingSrcIpSaiFailure) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetSrcIp(swss::IpAddress(kSrcIp2), &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +TEST_F(MirrorSessionManagerTest, UpdatingDstIpSaiFailure) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetDstIp(swss::IpAddress(kDstIp2), &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +TEST_F(MirrorSessionManagerTest, UpdatingSrcMacSaiFailure) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetSrcMac(swss::MacAddress(kSrcMac2), &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +TEST_F(MirrorSessionManagerTest, UpdatingDstMacSaiFailure) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetDstMac(swss::MacAddress(kDstMac2), &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +TEST_F(MirrorSessionManagerTest, UpdatingTtlSaiFailure) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetTtl(kTtl2Num, &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +TEST_F(MirrorSessionManagerTest, UpdatingTosSaiFailure) +{ + p4orch::P4MirrorSessionEntry existing_mirror_session_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + p4orch::P4MirrorSessionEntry existing_mirror_session_entry_before_update(existing_mirror_session_entry); + EXPECT_FALSE(SetTos(kTos2Num, &existing_mirror_session_entry).ok()); + EXPECT_EQ(existing_mirror_session_entry, existing_mirror_session_entry_before_update); +} + +// The update operation should be atomic -- it either succeeds or fails without +// changing anything. This test case verifies that failed update operation +// doesn't change existing entry. +TEST_F(MirrorSessionManagerTest, UpdateFailureShouldNotChangeExistingEntry) +{ + // 1. Add a new entry. + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + // Set up mock call. + EXPECT_CALL(mock_sai_mirror_, create_mirror_session(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kMirrorSessionOid), Return(SAI_STATUS_SUCCESS))); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); + p4orch::P4MirrorSessionEntry expected_mirror_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_EQ(*mirror_entry, expected_mirror_entry); + + sai_object_id_t oid_in_mapper = 0; + EXPECT_TRUE(p4_oid_mapper_.getOID(SAI_OBJECT_TYPE_MIRROR_SESSION, + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), &oid_in_mapper)); + EXPECT_EQ(kMirrorSessionOid, oid_in_mapper); + + // 2. Update the added entry. + fvs = {{p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort3}, + {prependParamField(p4orch::kSrcIp), kSrcIp2}, {prependParamField(p4orch::kDstIp), kDstIp2}, + {prependParamField(p4orch::kSrcMac), kSrcMac2}, {prependParamField(p4orch::kDstMac), kDstMac2}, + {prependParamField(p4orch::kTtl), kTtl2}, {prependParamField(p4orch::kTos), kTos2}}; + + app_db_entry = {std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + // Set up mock calls. Update entry will trigger 7 attribute updates and each + // attribute update requires a seperate SAI call. Let's pass the first 6 SAI + // calls and fail the last one. When update fails in the middle, 6 successful + // attribute updates will be reverted one by one. So the set SAI call wil be + // called 13 times and actions are 6 successes, 1 failure, 6successes. + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)) + .Times(13) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + + Drain(); + + mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); + EXPECT_EQ(*mirror_entry, expected_mirror_entry); +} + +TEST_F(MirrorSessionManagerTest, UpdateRecoveryFailureShouldRaiseCriticalState) +{ + // 1. Add a new entry. + nlohmann::json j; + j[prependMatchField(p4orch::kMirrorSessionId)] = kMirrorSessionId; + + std::vector fvs{ + {p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort1}, + {prependParamField(p4orch::kSrcIp), kSrcIp1}, {prependParamField(p4orch::kDstIp), kDstIp1}, + {prependParamField(p4orch::kSrcMac), kSrcMac1}, {prependParamField(p4orch::kDstMac), kDstMac1}, + {prependParamField(p4orch::kTtl), kTtl1}, {prependParamField(p4orch::kTos), kTos1}}; + + swss::KeyOpFieldsValuesTuple app_db_entry( + std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs); + + Enqueue(app_db_entry); + // Set up mock call. + EXPECT_CALL(mock_sai_mirror_, create_mirror_session(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kMirrorSessionOid), Return(SAI_STATUS_SUCCESS))); + Drain(); + + // Check the added entry. + auto mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); + p4orch::P4MirrorSessionEntry expected_mirror_entry( + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), kMirrorSessionOid, kMirrorSessionId, kPort1, + swss::IpAddress(kSrcIp1), swss::IpAddress(kDstIp1), swss::MacAddress(kSrcMac1), swss::MacAddress(kDstMac1), + kTtl1Num, kTos1Num); + EXPECT_EQ(*mirror_entry, expected_mirror_entry); + + sai_object_id_t oid_in_mapper = 0; + EXPECT_TRUE(p4_oid_mapper_.getOID(SAI_OBJECT_TYPE_MIRROR_SESSION, + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId), &oid_in_mapper)); + EXPECT_EQ(kMirrorSessionOid, oid_in_mapper); + + // 2. Update the added entry. + fvs = {{p4orch::kAction, p4orch::kMirrorAsIpv4Erspan}, {prependParamField(p4orch::kPort), kPort3}, + {prependParamField(p4orch::kSrcIp), kSrcIp2}, {prependParamField(p4orch::kDstIp), kDstIp2}, + {prependParamField(p4orch::kSrcMac), kSrcMac2}, {prependParamField(p4orch::kDstMac), kDstMac2}, + {prependParamField(p4orch::kTtl), kTtl2}, {prependParamField(p4orch::kTos), kTos2}}; + + app_db_entry = {std::string(APP_P4RT_MIRROR_SESSION_TABLE_NAME) + kTableKeyDelimiter + j.dump(), SET_COMMAND, fvs}; + + Enqueue(app_db_entry); + // Set up mock calls. Update entry will trigger 7 attribute updates and each + // attribute update requires a seperate SAI call. Let's pass the first 6 SAI + // calls and fail the last one. When update fails in the middle, 6 successful + // attribute updates will be reverted one by one. We will fail the recovery by + // failing the last revert. + EXPECT_CALL(mock_sai_mirror_, set_mirror_session_attribute(_, _)) + .Times(13) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_SUCCESS)) + .WillOnce(Return(SAI_STATUS_FAILURE)); + // (TODO): Expect critical state. + + Drain(); + + mirror_entry = GetMirrorSessionEntry(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_NE(mirror_entry, nullptr); +} + +TEST_F(MirrorSessionManagerTest, DeleteNonExistingMirrorSessionShouldFail) +{ + ASSERT_EQ(StatusCode::SWSS_RC_NOT_FOUND, + ProcessDeleteRequest(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId))); +} + +TEST_F(MirrorSessionManagerTest, DeleteMirrorSessionWithNonZeroRefShouldFail) +{ + AddDefaultMirrorSection(); + p4_oid_mapper_.increaseRefCount(SAI_OBJECT_TYPE_MIRROR_SESSION, + KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + ASSERT_EQ(StatusCode::SWSS_RC_IN_USE, + ProcessDeleteRequest(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId))); +} + +TEST_F(MirrorSessionManagerTest, DeleteMirrorSessionNotInMapperShouldFail) +{ + AddDefaultMirrorSection(); + p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_MIRROR_SESSION, KeyGenerator::generateMirrorSessionKey(kMirrorSessionId)); + // (TODO): Expect critical state. + ASSERT_EQ(StatusCode::SWSS_RC_INTERNAL, + ProcessDeleteRequest(KeyGenerator::generateMirrorSessionKey(kMirrorSessionId))); +} + +} // namespace test +} // namespace p4orch diff --git a/orchagent/p4orch/tests/mock_response_publisher.h b/orchagent/p4orch/tests/mock_response_publisher.h new file mode 100644 index 000000000000..d163333bd07c --- /dev/null +++ b/orchagent/p4orch/tests/mock_response_publisher.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "response_publisher_interface.h" + +class MockResponsePublisher : public ResponsePublisherInterface +{ + public: + MOCK_METHOD6(publish, void(const std::string &table, const std::string &key, + const std::vector &intent_attrs, const ReturnCode &status, + const std::vector &state_attrs, bool replace)); + MOCK_METHOD5(publish, + void(const std::string &table, const std::string &key, + const std::vector &intent_attrs, const ReturnCode &status, bool replace)); + MOCK_METHOD5(writeToDB, + void(const std::string &table, const std::string &key, + const std::vector &values, const std::string &op, bool replace)); +}; diff --git a/orchagent/p4orch/tests/mock_sai_acl.cpp b/orchagent/p4orch/tests/mock_sai_acl.cpp new file mode 100644 index 000000000000..531a71b3a5b4 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_acl.cpp @@ -0,0 +1,68 @@ +#include "mock_sai_acl.h" + +MockSaiAcl *mock_sai_acl; + +sai_status_t create_acl_table(sai_object_id_t *acl_table_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_acl->create_acl_table(acl_table_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_acl_table(sai_object_id_t acl_table_id) +{ + return mock_sai_acl->remove_acl_table(acl_table_id); +} + +sai_status_t create_acl_table_group(sai_object_id_t *acl_table_group_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_acl->create_acl_table_group(acl_table_group_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_acl_table_group(sai_object_id_t acl_table_group_id) +{ + return mock_sai_acl->remove_acl_table_group(acl_table_group_id); +} + +sai_status_t create_acl_table_group_member(sai_object_id_t *acl_table_group_member_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list) +{ + return mock_sai_acl->create_acl_table_group_member(acl_table_group_member_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_acl_table_group_member(sai_object_id_t acl_table_group_member_id) +{ + return mock_sai_acl->remove_acl_table_group_member(acl_table_group_member_id); +} + +sai_status_t create_acl_entry(sai_object_id_t *acl_entry_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_acl->create_acl_entry(acl_entry_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_acl_entry(sai_object_id_t acl_entry_id) +{ + return mock_sai_acl->remove_acl_entry(acl_entry_id); +} + +sai_status_t create_acl_counter(sai_object_id_t *acl_counter_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_acl->create_acl_counter(acl_counter_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_acl_counter(sai_object_id_t acl_counter_id) +{ + return mock_sai_acl->remove_acl_counter(acl_counter_id); +} + +sai_status_t get_acl_counter_attribute(sai_object_id_t acl_counter_id, uint32_t attr_count, sai_attribute_t *attr_list) +{ + return mock_sai_acl->get_acl_counter_attribute(acl_counter_id, attr_count, attr_list); +} + +sai_status_t set_acl_entry_attribute(sai_object_id_t acl_entry_id, const sai_attribute_t *attr) +{ + return mock_sai_acl->set_acl_entry_attribute(acl_entry_id, attr); +} diff --git a/orchagent/p4orch/tests/mock_sai_acl.h b/orchagent/p4orch/tests/mock_sai_acl.h new file mode 100644 index 000000000000..252eb1768a89 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_acl.h @@ -0,0 +1,87 @@ +#include + +extern "C" +{ +#include "sai.h" +#include "saiacl.h" +} + +class SaiAclInterface +{ + public: + virtual sai_status_t create_acl_table(sai_object_id_t *acl_table_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_acl_table(sai_object_id_t acl_table_id) = 0; + virtual sai_status_t create_acl_table_group(sai_object_id_t *acl_table_group_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_acl_table_group(sai_object_id_t acl_table_group_id) = 0; + virtual sai_status_t create_acl_table_group_member(sai_object_id_t *acl_table_group_member_id, + sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_acl_table_group_member(sai_object_id_t acl_table_group_member_id) = 0; + + virtual sai_status_t create_acl_entry(sai_object_id_t *acl_entry_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_acl_entry(sai_object_id_t acl_entry_id) = 0; + virtual sai_status_t create_acl_counter(sai_object_id_t *acl_counter_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_acl_counter(sai_object_id_t acl_counter_id) = 0; + virtual sai_status_t get_acl_counter_attribute(sai_object_id_t acl_counter_id, uint32_t attr_count, + sai_attribute_t *attr_list) = 0; + virtual sai_status_t set_acl_entry_attribute(sai_object_id_t acl_entry_id, const sai_attribute_t *attr) = 0; +}; + +class MockSaiAcl : public SaiAclInterface +{ + public: + MOCK_METHOD4(create_acl_table, sai_status_t(sai_object_id_t *acl_table_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_acl_table, sai_status_t(sai_object_id_t acl_table_id)); + MOCK_METHOD4(create_acl_table_group, sai_status_t(sai_object_id_t *acl_table_group_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_acl_table_group, sai_status_t(sai_object_id_t acl_table_group_id)); + MOCK_METHOD4(create_acl_table_group_member, + sai_status_t(sai_object_id_t *acl_table_group_member_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_acl_table_group_member, sai_status_t(sai_object_id_t acl_table_group_member_id)); + MOCK_METHOD4(create_acl_entry, sai_status_t(sai_object_id_t *acl_entry_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_acl_entry, sai_status_t(sai_object_id_t acl_entry_id)); + MOCK_METHOD4(create_acl_counter, sai_status_t(sai_object_id_t *acl_counter_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_acl_counter, sai_status_t(sai_object_id_t acl_counter_id)); + MOCK_METHOD3(get_acl_counter_attribute, + sai_status_t(sai_object_id_t acl_counter_id, uint32_t attr_count, sai_attribute_t *attr_list)); + MOCK_METHOD2(set_acl_entry_attribute, sai_status_t(sai_object_id_t acl_entry_id, const sai_attribute_t *attr)); +}; + +extern MockSaiAcl *mock_sai_acl; + +sai_status_t create_acl_table(_Out_ sai_object_id_t *acl_table_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list); + +sai_status_t remove_acl_table(sai_object_id_t acl_table_id); + +sai_status_t create_acl_table_group(sai_object_id_t *acl_table_group_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list); + +sai_status_t remove_acl_table_group(sai_object_id_t acl_table_group_id); + +sai_status_t create_acl_table_group_member(sai_object_id_t *acl_table_group_member_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list); + +sai_status_t remove_acl_table_group_member(sai_object_id_t acl_table_group_member_id); + +sai_status_t create_acl_entry(sai_object_id_t *acl_entry_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list); + +sai_status_t remove_acl_entry(sai_object_id_t acl_entry_id); + +sai_status_t create_acl_counter(sai_object_id_t *acl_counter_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list); + +sai_status_t remove_acl_counter(sai_object_id_t acl_counter_id); + +sai_status_t get_acl_counter_attribute(sai_object_id_t acl_counter_id, uint32_t attr_count, sai_attribute_t *attr_list); + +sai_status_t set_acl_entry_attribute(sai_object_id_t acl_entry_id, const sai_attribute_t *attr); diff --git a/orchagent/p4orch/tests/mock_sai_hostif.cpp b/orchagent/p4orch/tests/mock_sai_hostif.cpp new file mode 100644 index 000000000000..7dcc0f70c2ab --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_hostif.cpp @@ -0,0 +1,67 @@ +#include "mock_sai_hostif.h" + +MockSaiHostif *mock_sai_hostif; + +sai_status_t mock_create_hostif_trap_group(_Out_ sai_object_id_t *hostif_trap_group_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_hostif->create_hostif_trap_group(hostif_trap_group_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_set_hostif_trap_group_attribute(_In_ sai_object_id_t hostif_trap_group_id, + _In_ const sai_attribute_t *attr) +{ + return mock_sai_hostif->set_hostif_trap_group_attribute(hostif_trap_group_id, attr); +} + +sai_status_t mock_remove_hostif_trap_group(_In_ const sai_object_id_t hostif_trap_group_id) +{ + return mock_sai_hostif->remove_hostif_trap_group(hostif_trap_group_id); +} + +sai_status_t mock_create_hostif_table_entry(_Out_ sai_object_id_t *hostif_table_entry_id, + _In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_hostif->create_hostif_table_entry(hostif_table_entry_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_hostif_table_entry(_In_ const sai_object_id_t hostif_table_entry_id) +{ + return mock_sai_hostif->remove_hostif_table_entry(hostif_table_entry_id); +} + +sai_status_t mock_create_hostif_user_defined_trap(_Out_ sai_object_id_t *hostif_user_defined_trap_id, + _In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_hostif->create_hostif_user_defined_trap(hostif_user_defined_trap_id, switch_id, attr_count, + attr_list); +} + +sai_status_t mock_remove_hostif_user_defined_trap(_In_ const sai_object_id_t hostif_user_defined_trap_id) +{ + return mock_sai_hostif->remove_hostif_user_defined_trap(hostif_user_defined_trap_id); +} + +sai_status_t mock_create_hostif_trap(_Out_ sai_object_id_t *hostif_trap_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_hostif->create_hostif_trap(hostif_trap_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_hostif_trap(_In_ const sai_object_id_t hostif_trap_id) +{ + return mock_sai_hostif->remove_hostif_trap(hostif_trap_id); +} + +sai_status_t mock_create_hostif(_Out_ sai_object_id_t *hostif_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_hostif->create_hostif(hostif_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_hostif(_In_ const sai_object_id_t hostif_id) +{ + return mock_sai_hostif->remove_hostif(hostif_id); +} diff --git a/orchagent/p4orch/tests/mock_sai_hostif.h b/orchagent/p4orch/tests/mock_sai_hostif.h new file mode 100644 index 000000000000..0e758aeebdb4 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_hostif.h @@ -0,0 +1,76 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock Class mapping methods to host interface object SAI APIs. +class MockSaiHostif +{ + public: + MOCK_METHOD4(create_hostif_trap_group, + sai_status_t(_Out_ sai_object_id_t *hostif_trap_group_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD2(set_hostif_trap_group_attribute, + sai_status_t(_In_ sai_object_id_t hostif_trap_group_id, _In_ const sai_attribute_t *attr)); + + MOCK_METHOD1(remove_hostif_trap_group, sai_status_t(_In_ sai_object_id_t hostif_trap_group_id)); + + MOCK_METHOD4(create_hostif_table_entry, + sai_status_t(_Out_ sai_object_id_t *hostif_table_entry_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_hostif_table_entry, sai_status_t(_In_ sai_object_id_t hostif_table_entry_id)); + + MOCK_METHOD4(create_hostif_user_defined_trap, + sai_status_t(_Out_ sai_object_id_t *hostif_user_defined_trap_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_hostif_user_defined_trap, sai_status_t(_In_ sai_object_id_t hostif_user_defined_trap_id)); + + MOCK_METHOD4(create_hostif_trap, sai_status_t(_Out_ sai_object_id_t *hostif_trap_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_hostif_trap, sai_status_t(_In_ sai_object_id_t hostif_trap_id)); + + MOCK_METHOD4(create_hostif, sai_status_t(_Out_ sai_object_id_t *hostif_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_hostif, sai_status_t(_In_ sai_object_id_t hostif_id)); +}; + +extern MockSaiHostif *mock_sai_hostif; + +sai_status_t mock_create_hostif_trap_group(_Out_ sai_object_id_t *hostif_trap_group_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list); + +sai_status_t mock_set_hostif_trap_group_attribute(_In_ sai_object_id_t hostif_trap_group_id, + _In_ const sai_attribute_t *attr); + +sai_status_t mock_remove_hostif_trap_group(_In_ const sai_object_id_t hostif_trap_group_id); + +sai_status_t mock_create_hostif_table_entry(_Out_ sai_object_id_t *hostif_table_entry_id, + _In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list); + +sai_status_t mock_remove_hostif_table_entry(_In_ const sai_object_id_t hostif_table_entry_id); + +sai_status_t mock_create_hostif_user_defined_trap(_Out_ sai_object_id_t *hostif_user_defined_trap_id, + _In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list); + +sai_status_t mock_remove_hostif_user_defined_trap(_In_ const sai_object_id_t hostif_user_defined_trap_id); + +sai_status_t mock_create_hostif_trap(_Out_ sai_object_id_t *hostif_trap_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list); + +sai_status_t mock_remove_hostif_trap(_In_ const sai_object_id_t hostif_trap_id); + +sai_status_t mock_create_hostif(_Out_ sai_object_id_t *hostif_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list); + +sai_status_t mock_remove_hostif(_In_ const sai_object_id_t hostif_id); diff --git a/orchagent/p4orch/tests/mock_sai_mirror.h b/orchagent/p4orch/tests/mock_sai_mirror.h new file mode 100644 index 000000000000..39991d583e8c --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_mirror.h @@ -0,0 +1,53 @@ +// Define classes and functions to mock SAI mirror functions. +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock class including mock functions mapping to SAI mirror's functions. +class MockSaiMirror +{ + public: + MOCK_METHOD4(create_mirror_session, + sai_status_t(_Out_ sai_object_id_t *mirror_session_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_mirror_session, sai_status_t(_In_ sai_object_id_t mirror_session_id)); + + MOCK_METHOD2(set_mirror_session_attribute, + sai_status_t(_In_ sai_object_id_t mirror_session_id, _In_ const sai_attribute_t *attr)); + + MOCK_METHOD3(get_mirror_session_attribute, + sai_status_t(_In_ sai_object_id_t mirror_session_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list)); +}; + +// Note that before mock functions below are used, mock_sai_mirror must be +// initialized to point to an instance of MockSaiMirror. +MockSaiMirror *mock_sai_mirror; + +sai_status_t mock_create_mirror_session(_Out_ sai_object_id_t *mirror_session_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_mirror->create_mirror_session(mirror_session_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_mirror_session(_In_ sai_object_id_t mirror_session_id) +{ + return mock_sai_mirror->remove_mirror_session(mirror_session_id); +} + +sai_status_t mock_set_mirror_session_attribute(_In_ sai_object_id_t mirror_session_id, _In_ const sai_attribute_t *attr) +{ + return mock_sai_mirror->set_mirror_session_attribute(mirror_session_id, attr); +} + +sai_status_t mock_get_mirror_session_attribute(_In_ sai_object_id_t mirror_session_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_mirror->get_mirror_session_attribute(mirror_session_id, attr_count, attr_list); +} diff --git a/orchagent/p4orch/tests/mock_sai_neighbor.h b/orchagent/p4orch/tests/mock_sai_neighbor.h new file mode 100644 index 000000000000..cd8f2aa0a9de --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_neighbor.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock Class mapping methods to neighbor object SAI APIs. +class MockSaiNeighbor +{ + public: + MOCK_METHOD3(create_neighbor_entry, sai_status_t(_In_ const sai_neighbor_entry_t *neighbor_entry, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_neighbor_entry, sai_status_t(_In_ const sai_neighbor_entry_t *neighbor_entry)); + + MOCK_METHOD2(set_neighbor_entry_attribute, + sai_status_t(_In_ const sai_neighbor_entry_t *neighbor_entry, _In_ const sai_attribute_t *attr)); + + MOCK_METHOD3(get_neighbor_entry_attribute, + sai_status_t(_In_ const sai_neighbor_entry_t *neighbor_entry, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list)); +}; + +MockSaiNeighbor *mock_sai_neighbor; + +sai_status_t mock_create_neighbor_entry(_In_ const sai_neighbor_entry_t *neighbor_entry, _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_neighbor->create_neighbor_entry(neighbor_entry, attr_count, attr_list); +} + +sai_status_t mock_remove_neighbor_entry(_In_ const sai_neighbor_entry_t *neighbor_entry) +{ + return mock_sai_neighbor->remove_neighbor_entry(neighbor_entry); +} + +sai_status_t mock_set_neighbor_entry_attribute(_In_ const sai_neighbor_entry_t *neighbor_entry, + _In_ const sai_attribute_t *attr) +{ + return mock_sai_neighbor->set_neighbor_entry_attribute(neighbor_entry, attr); +} + +sai_status_t mock_get_neighbor_entry_attribute(_In_ const sai_neighbor_entry_t *neighbor_entry, + _In_ uint32_t attr_count, _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_neighbor->get_neighbor_entry_attribute(neighbor_entry, attr_count, attr_list); +} diff --git a/orchagent/p4orch/tests/mock_sai_next_hop.h b/orchagent/p4orch/tests/mock_sai_next_hop.h new file mode 100644 index 000000000000..83e6e7d506b8 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_next_hop.h @@ -0,0 +1,51 @@ +// Define classes and functions to mock SAI next hop functions. +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock class including mock functions mapping to SAI next hop's functions. +class MockSaiNextHop +{ + public: + MOCK_METHOD4(create_next_hop, sai_status_t(_Out_ sai_object_id_t *next_hop_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_next_hop, sai_status_t(_In_ sai_object_id_t next_hop_id)); + + MOCK_METHOD2(set_next_hop_attribute, + sai_status_t(_In_ sai_object_id_t next_hop_id, _In_ const sai_attribute_t *attr)); + + MOCK_METHOD3(get_next_hop_attribute, sai_status_t(_In_ sai_object_id_t next_hop_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list)); +}; + +// Note that before mock functions below are used, mock_sai_next_hop must be +// initialized to point to an instance of MockSaiNextHop. +MockSaiNextHop *mock_sai_next_hop; + +sai_status_t mock_create_next_hop(_Out_ sai_object_id_t *next_hop_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_next_hop->create_next_hop(next_hop_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_next_hop(_In_ sai_object_id_t next_hop_id) +{ + return mock_sai_next_hop->remove_next_hop(next_hop_id); +} + +sai_status_t mock_set_next_hop_attribute(_In_ sai_object_id_t next_hop_id, _In_ const sai_attribute_t *attr) +{ + return mock_sai_next_hop->set_next_hop_attribute(next_hop_id, attr); +} + +sai_status_t mock_get_next_hop_attribute(_In_ sai_object_id_t next_hop_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_next_hop->get_next_hop_attribute(next_hop_id, attr_count, attr_list); +} diff --git a/orchagent/p4orch/tests/mock_sai_next_hop_group.h b/orchagent/p4orch/tests/mock_sai_next_hop_group.h new file mode 100644 index 000000000000..5398ec5a7076 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_next_hop_group.h @@ -0,0 +1,64 @@ +// Define classes and functions to mock SAI next hop group functions. +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock class including mock functions mapping to SAI next hop group's +// functions. +class MockSaiNextHopGroup +{ + public: + MOCK_METHOD4(create_next_hop_group, + sai_status_t(_Out_ sai_object_id_t *next_hop_group_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_next_hop_group, sai_status_t(_In_ sai_object_id_t next_hop_group_id)); + + MOCK_METHOD4(create_next_hop_group_member, + sai_status_t(_Out_ sai_object_id_t *next_hop_group_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_next_hop_group_member, sai_status_t(_In_ sai_object_id_t next_hop_group_member_id)); + + MOCK_METHOD2(set_next_hop_group_member_attribute, + sai_status_t(_In_ sai_object_id_t next_hop_group_member_id, _In_ const sai_attribute_t *attr)); +}; + +// Note that before mock functions below are used, mock_sai_next_hop_group must +// be initialized to point to an instance of MockSaiNextHopGroup. +MockSaiNextHopGroup *mock_sai_next_hop_group; + +sai_status_t create_next_hop_group(_Out_ sai_object_id_t *next_hop_group_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_next_hop_group->create_next_hop_group(next_hop_group_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_next_hop_group(_In_ sai_object_id_t next_hop_group_id) +{ + return mock_sai_next_hop_group->remove_next_hop_group(next_hop_group_id); +} + +sai_status_t create_next_hop_group_member(_Out_ sai_object_id_t *next_hop_group_member_id, + _In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_next_hop_group->create_next_hop_group_member(next_hop_group_member_id, switch_id, attr_count, + attr_list); +} + +sai_status_t remove_next_hop_group_member(_In_ sai_object_id_t next_hop_group_member_id) +{ + return mock_sai_next_hop_group->remove_next_hop_group_member(next_hop_group_member_id); +} + +sai_status_t set_next_hop_group_member_attribute(_In_ sai_object_id_t next_hop_group_member_id, + _In_ const sai_attribute_t *attr) +{ + return mock_sai_next_hop_group->set_next_hop_group_member_attribute(next_hop_group_member_id, attr); +} diff --git a/orchagent/p4orch/tests/mock_sai_policer.h b/orchagent/p4orch/tests/mock_sai_policer.h new file mode 100644 index 000000000000..351cca14c020 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_policer.h @@ -0,0 +1,53 @@ +#pragma once + +extern "C" +{ +#include "sai.h" +#include "saipolicer.h" +} + +class SaiPolicerInterface +{ + public: + virtual sai_status_t create_policer(sai_object_id_t *policer_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_policer(sai_object_id_t policer_id) = 0; + virtual sai_status_t get_policer_stats(sai_object_id_t policer_id, uint32_t number_of_counters, + const sai_stat_id_t *counter_ids, uint64_t *counters) = 0; + virtual sai_status_t set_policer_attribute(sai_object_id_t policer_id, const sai_attribute_t *attr) = 0; +}; + +class MockSaiPolicer : public SaiPolicerInterface +{ + public: + MOCK_METHOD4(create_policer, sai_status_t(sai_object_id_t *policer_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_policer, sai_status_t(sai_object_id_t policer_id)); + MOCK_METHOD4(get_policer_stats, sai_status_t(sai_object_id_t policer_id, uint32_t number_of_counters, + const sai_stat_id_t *counter_ids, uint64_t *counters)); + MOCK_METHOD2(set_policer_attribute, sai_status_t(sai_object_id_t policer_id, const sai_attribute_t *attr)); +}; + +MockSaiPolicer *mock_sai_policer; + +sai_status_t create_policer(sai_object_id_t *policer_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_policer->create_policer(policer_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_policer(sai_object_id_t policer_id) +{ + return mock_sai_policer->remove_policer(policer_id); +} + +sai_status_t get_policer_stats(sai_object_id_t policer_id, uint32_t number_of_counters, + const sai_stat_id_t *counter_ids, uint64_t *counters) +{ + return mock_sai_policer->get_policer_stats(policer_id, number_of_counters, counter_ids, counters); +} + +sai_status_t set_policer_attribute(sai_object_id_t policer_id, const sai_attribute_t *attr) +{ + return mock_sai_policer->set_policer_attribute(policer_id, attr); +} diff --git a/orchagent/p4orch/tests/mock_sai_route.h b/orchagent/p4orch/tests/mock_sai_route.h new file mode 100644 index 000000000000..b40cf6605eee --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_route.h @@ -0,0 +1,108 @@ +#pragma once + +extern "C" +{ +#include "sai.h" +#include "sairoute.h" +} + +class SaiRouteInterface +{ + public: + virtual sai_status_t create_route_entry(const sai_route_entry_t *route_entry, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_route_entry(const sai_route_entry_t *route_entry) = 0; + virtual sai_status_t set_route_entry_attribute(const sai_route_entry_t *route_entry, + const sai_attribute_t *attr) = 0; + virtual sai_status_t get_route_entry_attribute(const sai_route_entry_t *route_entry, uint32_t attr_count, + sai_attribute_t *attr_list) = 0; + virtual sai_status_t create_route_entries(uint32_t object_count, const sai_route_entry_t *route_entry, + const uint32_t *attr_count, const sai_attribute_t **attr_list, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) = 0; + virtual sai_status_t remove_route_entries(uint32_t object_count, const sai_route_entry_t *route_entry, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) = 0; + virtual sai_status_t set_route_entries_attribute(uint32_t object_count, const sai_route_entry_t *route_entry, + const sai_attribute_t *attr_list, sai_bulk_op_error_mode_t mode, + sai_status_t *object_statuses) = 0; + virtual sai_status_t get_route_entries_attribute(uint32_t object_count, const sai_route_entry_t *route_entry, + const uint32_t *attr_count, sai_attribute_t **attr_list, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) = 0; +}; + +class MockSaiRoute : public SaiRouteInterface +{ + public: + MOCK_METHOD3(create_route_entry, sai_status_t(const sai_route_entry_t *route_entry, uint32_t attr_count, + const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_route_entry, sai_status_t(const sai_route_entry_t *route_entry)); + MOCK_METHOD2(set_route_entry_attribute, + sai_status_t(const sai_route_entry_t *route_entry, const sai_attribute_t *attr)); + MOCK_METHOD3(get_route_entry_attribute, + sai_status_t(const sai_route_entry_t *route_entry, uint32_t attr_count, sai_attribute_t *attr_list)); + MOCK_METHOD6(create_route_entries, sai_status_t(uint32_t object_count, const sai_route_entry_t *route_entry, + const uint32_t *attr_count, const sai_attribute_t **attr_list, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses)); + MOCK_METHOD4(remove_route_entries, sai_status_t(uint32_t object_count, const sai_route_entry_t *route_entry, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses)); + MOCK_METHOD5(set_route_entries_attribute, + sai_status_t(uint32_t object_count, const sai_route_entry_t *route_entry, + const sai_attribute_t *attr_list, sai_bulk_op_error_mode_t mode, + sai_status_t *object_statuses)); + MOCK_METHOD6(get_route_entries_attribute, + sai_status_t(uint32_t object_count, const sai_route_entry_t *route_entry, const uint32_t *attr_count, + sai_attribute_t **attr_list, sai_bulk_op_error_mode_t mode, + sai_status_t *object_statuses)); +}; + +MockSaiRoute *mock_sai_route; + +sai_status_t create_route_entry(const sai_route_entry_t *route_entry, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_route->create_route_entry(route_entry, attr_count, attr_list); +} + +sai_status_t remove_route_entry(const sai_route_entry_t *route_entry) +{ + return mock_sai_route->remove_route_entry(route_entry); +} + +sai_status_t set_route_entry_attribute(const sai_route_entry_t *route_entry, const sai_attribute_t *attr) +{ + return mock_sai_route->set_route_entry_attribute(route_entry, attr); +} + +sai_status_t get_route_entry_attribute(const sai_route_entry_t *route_entry, uint32_t attr_count, + sai_attribute_t *attr_list) +{ + return mock_sai_route->get_route_entry_attribute(route_entry, attr_count, attr_list); +} + +sai_status_t create_route_entries(uint32_t object_count, const sai_route_entry_t *route_entry, + const uint32_t *attr_count, const sai_attribute_t **attr_list, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) +{ + return mock_sai_route->create_route_entries(object_count, route_entry, attr_count, attr_list, mode, + object_statuses); +} + +sai_status_t remove_route_entries(uint32_t object_count, const sai_route_entry_t *route_entry, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) +{ + return mock_sai_route->remove_route_entries(object_count, route_entry, mode, object_statuses); +} + +sai_status_t set_route_entries_attribute(uint32_t object_count, const sai_route_entry_t *route_entry, + const sai_attribute_t *attr_list, sai_bulk_op_error_mode_t mode, + sai_status_t *object_statuses) +{ + return mock_sai_route->set_route_entries_attribute(object_count, route_entry, attr_list, mode, object_statuses); +} + +sai_status_t get_route_entries_attribute(uint32_t object_count, const sai_route_entry_t *route_entry, + const uint32_t *attr_count, sai_attribute_t **attr_list, + sai_bulk_op_error_mode_t mode, sai_status_t *object_statuses) +{ + return mock_sai_route->get_route_entries_attribute(object_count, route_entry, attr_count, attr_list, mode, + object_statuses); +} diff --git a/orchagent/p4orch/tests/mock_sai_router_interface.h b/orchagent/p4orch/tests/mock_sai_router_interface.h new file mode 100644 index 000000000000..9c0caa300440 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_router_interface.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock Class mapping methods to router interface SAI APIs. +class MockSaiRouterInterface +{ + public: + MOCK_METHOD4(create_router_interface, + sai_status_t(_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)); + + MOCK_METHOD1(remove_router_interface, sai_status_t(_In_ sai_object_id_t router_interface_id)); + + MOCK_METHOD2(set_router_interface_attribute, + sai_status_t(_In_ sai_object_id_t router_interface_id, _In_ const sai_attribute_t *attr)); + + MOCK_METHOD3(get_router_interface_attribute, + sai_status_t(_In_ sai_object_id_t router_interface_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list)); +}; + +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); +} diff --git a/orchagent/p4orch/tests/mock_sai_serialize.cpp b/orchagent/p4orch/tests/mock_sai_serialize.cpp new file mode 100644 index 000000000000..ada42acf021f --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_serialize.cpp @@ -0,0 +1,13 @@ +#include "mock_sai_serialize.h" + +MockSaiSerialize *mock_sai_serialize; + +inline std::string sai_serialize_object_id(sai_object_id_t oid) +{ + return mock_sai_serialize->sai_serialize_object_id(oid); +} + +inline std::string sai_serialize_object_type(sai_object_type_t object_type) +{ + return mock_sai_serialize->sai_serialize_object_type(object_type); +} diff --git a/orchagent/p4orch/tests/mock_sai_serialize.h b/orchagent/p4orch/tests/mock_sai_serialize.h new file mode 100644 index 000000000000..4e4ae5057394 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_serialize.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "sai_serialize.h" + +class SaiSerializeInterface +{ + public: + virtual std::string sai_serialize_object_id(sai_object_id_t oid) = 0; + virtual std::string sai_serialize_object_type(sai_object_type_t object_type) = 0; +}; + +class MockSaiSerialize : public SaiSerializeInterface +{ + public: + MOCK_METHOD1(sai_serialize_object_id, std::string(sai_object_id_t oid)); + MOCK_METHOD1(sai_serialize_object_type, std::string(sai_object_type_t object_type)); +}; + +extern MockSaiSerialize *mock_sai_serialize; + +std::string sai_serialize_object_id(sai_object_id_t oid); + +std::string sai_serialize_object_type(sai_object_type_t object_type); diff --git a/orchagent/p4orch/tests/mock_sai_switch.cpp b/orchagent/p4orch/tests/mock_sai_switch.cpp new file mode 100644 index 000000000000..180de2d6f93a --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_switch.cpp @@ -0,0 +1,14 @@ +#include "mock_sai_switch.h" + +MockSaiSwitch *mock_sai_switch; + +sai_status_t mock_get_switch_attribute(_In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_switch->get_switch_attribute(switch_id, attr_count, attr_list); +} + +sai_status_t mock_set_switch_attribute(_In_ sai_object_id_t switch_id, _In_ const sai_attribute_t *attr) +{ + return mock_sai_switch->set_switch_attribute(switch_id, attr); +} diff --git a/orchagent/p4orch/tests/mock_sai_switch.h b/orchagent/p4orch/tests/mock_sai_switch.h new file mode 100644 index 000000000000..2883178ea34f --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_switch.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock Class mapping methods to switch object SAI APIs. +class MockSaiSwitch +{ + public: + MOCK_METHOD3(get_switch_attribute, sai_status_t(_In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list)); + MOCK_METHOD2(set_switch_attribute, sai_status_t(_In_ sai_object_id_t switch_id, _In_ const sai_attribute_t *attr)); +}; + +extern MockSaiSwitch *mock_sai_switch; + +sai_status_t mock_get_switch_attribute(_In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list); + +sai_status_t mock_set_switch_attribute(_In_ sai_object_id_t switch_id, _In_ const sai_attribute_t *attr); diff --git a/orchagent/p4orch/tests/mock_sai_udf.cpp b/orchagent/p4orch/tests/mock_sai_udf.cpp new file mode 100644 index 000000000000..4133988c8b05 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_udf.cpp @@ -0,0 +1,36 @@ +#include "mock_sai_udf.h" + +MockSaiUdf *mock_sai_udf; + +sai_status_t create_udf(sai_object_id_t *udf_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_udf->create_udf(udf_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_udf(sai_object_id_t udf_id) +{ + return mock_sai_udf->remove_udf(udf_id); +} + +sai_status_t create_udf_group(sai_object_id_t *udf_group_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_udf->create_udf_group(udf_group_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_udf_group(sai_object_id_t udf_group_id) +{ + return mock_sai_udf->remove_udf_group(udf_group_id); +} + +sai_status_t create_udf_match(sai_object_id_t *udf_match_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) +{ + return mock_sai_udf->create_udf_match(udf_match_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_udf_match(sai_object_id_t udf_match_id) +{ + return mock_sai_udf->remove_udf_match(udf_match_id); +} diff --git a/orchagent/p4orch/tests/mock_sai_udf.h b/orchagent/p4orch/tests/mock_sai_udf.h new file mode 100644 index 000000000000..251f8c2b1a51 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_udf.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +#include "saiudf.h" +} + +class SaiUdfInterface +{ + public: + virtual sai_status_t create_udf(sai_object_id_t *udf_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_udf(sai_object_id_t udf_id) = 0; + virtual sai_status_t create_udf_group(sai_object_id_t *udf_group_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_udf_group(sai_object_id_t udf_group_id) = 0; + virtual sai_status_t create_udf_match(sai_object_id_t *udf_match_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list) = 0; + virtual sai_status_t remove_udf_match(sai_object_id_t udf_match_id) = 0; +}; + +class MockSaiUdf : public SaiUdfInterface +{ + public: + MOCK_METHOD4(create_udf, sai_status_t(sai_object_id_t *udf_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_udf, sai_status_t(sai_object_id_t udf_id)); + MOCK_METHOD4(create_udf_group, sai_status_t(sai_object_id_t *udf_group_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_udf_group, sai_status_t(sai_object_id_t udf_group_id)); + MOCK_METHOD4(create_udf_match, sai_status_t(sai_object_id_t *udf_match_id, sai_object_id_t switch_id, + uint32_t attr_count, const sai_attribute_t *attr_list)); + MOCK_METHOD1(remove_udf_match, sai_status_t(sai_object_id_t udf_match_id)); +}; + +extern MockSaiUdf *mock_sai_udf; + +sai_status_t create_udf(sai_object_id_t *udf_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list); + +sai_status_t remove_udf(sai_object_id_t udf_id); + +sai_status_t create_udf_group(sai_object_id_t *udf_group_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list); + +sai_status_t remove_udf_group(sai_object_id_t udf_group_id); + +sai_status_t create_udf_match(sai_object_id_t *udf_match_id, sai_object_id_t switch_id, uint32_t attr_count, + const sai_attribute_t *attr_list); + +sai_status_t remove_udf_match(sai_object_id_t udf_match_id); diff --git a/orchagent/p4orch/tests/mock_sai_virtual_router.h b/orchagent/p4orch/tests/mock_sai_virtual_router.h new file mode 100644 index 000000000000..943b9b4828c2 --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_virtual_router.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +extern "C" +{ +#include "sai.h" +} + +// Mock Class mapping methods to router interface SAI APIs. +class MockSaiVirtualRouter +{ + public: + MOCK_METHOD4(create_virtual_router, + sai_status_t(_Out_ sai_object_id_t *virtual_router_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list)); + + MOCK_METHOD1(remove_virtual_router, sai_status_t(_In_ sai_object_id_t virtual_router_id)); + + MOCK_METHOD2(set_virtual_router_attribute, + sai_status_t(_In_ sai_object_id_t virtual_router_id, _In_ const sai_attribute_t *attr)); + + MOCK_METHOD3(get_virtual_router_attribute, + sai_status_t(_In_ sai_object_id_t virtual_router_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list)); +}; + +MockSaiVirtualRouter *mock_sai_virtual_router; + +sai_status_t create_virtual_router(_Out_ sai_object_id_t *virtual_router_id, _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_virtual_router->create_virtual_router(virtual_router_id, switch_id, attr_count, attr_list); +} + +sai_status_t remove_virtual_router(_In_ sai_object_id_t virtual_router_id) +{ + return mock_sai_virtual_router->remove_virtual_router(virtual_router_id); +} + +sai_status_t set_virtual_router_attribute(_In_ sai_object_id_t virtual_router_id, _In_ const sai_attribute_t *attr) +{ + return mock_sai_virtual_router->set_virtual_router_attribute(virtual_router_id, attr); +} + +sai_status_t get_virtual_router_attribute(_In_ sai_object_id_t virtual_router_id, _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_virtual_router->get_virtual_router_attribute(virtual_router_id, attr_count, attr_list); +} diff --git a/orchagent/p4orch/tests/neighbor_manager_test.cpp b/orchagent/p4orch/tests/neighbor_manager_test.cpp new file mode 100644 index 000000000000..e3986ef701ae --- /dev/null +++ b/orchagent/p4orch/tests/neighbor_manager_test.cpp @@ -0,0 +1,822 @@ +#include "neighbor_manager.h" + +#include +#include + +#include +#include + +#include "json.hpp" +#include "mock_response_publisher.h" +#include "mock_sai_neighbor.h" +#include "p4orch.h" +#include "p4orch/p4orch_util.h" +#include "return_code.h" +#include "swssnet.h" + +using ::p4orch::kTableKeyDelimiter; + +using ::testing::_; +using ::testing::Eq; +using ::testing::Return; +using ::testing::StrictMock; +using ::testing::Truly; + +extern sai_object_id_t gSwitchId; +extern sai_neighbor_api_t *sai_neighbor_api; + +namespace +{ + +constexpr char *kRouterInterfaceId1 = "intf-3/4"; +constexpr sai_object_id_t kRouterInterfaceOid1 = 0x295100; + +constexpr char *kRouterInterfaceId2 = "Ethernet20"; +constexpr sai_object_id_t kRouterInterfaceOid2 = 0x51411; + +const swss::IpAddress kNeighborId1("10.0.0.22"); +const swss::MacAddress kMacAddress1("00:01:02:03:04:05"); + +const swss::IpAddress kNeighborId2("fe80::21a:11ff:fe17:5f80"); +const swss::MacAddress kMacAddress2("00:ff:ee:dd:cc:bb"); + +bool MatchNeighborEntry(const sai_neighbor_entry_t *neigh_entry, const sai_neighbor_entry_t &expected_neigh_entry) +{ + if (neigh_entry == nullptr) + return false; + + if ((neigh_entry->switch_id != expected_neigh_entry.switch_id) || + (neigh_entry->rif_id != expected_neigh_entry.rif_id) || + (neigh_entry->ip_address.addr_family != expected_neigh_entry.ip_address.addr_family)) + return false; + + if ((neigh_entry->ip_address.addr_family == SAI_IP_ADDR_FAMILY_IPV4) && + (neigh_entry->ip_address.addr.ip4 != expected_neigh_entry.ip_address.addr.ip4)) + { + return false; + } + else if ((neigh_entry->ip_address.addr_family == SAI_IP_ADDR_FAMILY_IPV6) && + (memcmp(neigh_entry->ip_address.addr.ip6, expected_neigh_entry.ip_address.addr.ip6, 16))) + { + return false; + } + + return true; +} + +bool MatchNeighborCreateAttributeList(const sai_attribute_t *attr_list, const swss::MacAddress &dst_mac_address) +{ + if (attr_list == nullptr) + return false; + + std::unordered_set attrs; + + for (int i = 0; i < 2; ++i) + { + if (attrs.count(attr_list[i].id) != 0) + { + // Repeated attribute. + return false; + } + switch (attr_list[i].id) + { + case SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS: + if (memcmp(attr_list[i].value.mac, dst_mac_address.getMac(), sizeof(sai_mac_t)) != 0) + { + return false; + } + break; + case SAI_NEIGHBOR_ENTRY_ATTR_NO_HOST_ROUTE: + if (!attr_list[i].value.booldata) + { + return false; + } + break; + default: + return false; + } + attrs.insert(attr_list[i].id); + } + + return true; +} + +bool MatchNeighborSetAttributeList(const sai_attribute_t *attr_list, const swss::MacAddress &dst_mac_address) +{ + if (attr_list == nullptr) + return false; + + return (attr_list[0].id == SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS && + memcmp(attr_list[0].value.mac, dst_mac_address.getMac(), sizeof(sai_mac_t)) == 0); +} + +} // namespace + +class NeighborManagerTest : public ::testing::Test +{ + protected: + NeighborManagerTest() : neighbor_manager_(&p4_oid_mapper_, &publisher_) + { + } + + void SetUp() override + { + mock_sai_neighbor = &mock_sai_neighbor_; + sai_neighbor_api->create_neighbor_entry = mock_create_neighbor_entry; + sai_neighbor_api->remove_neighbor_entry = mock_remove_neighbor_entry; + sai_neighbor_api->set_neighbor_entry_attribute = mock_set_neighbor_entry_attribute; + sai_neighbor_api->get_neighbor_entry_attribute = mock_get_neighbor_entry_attribute; + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + neighbor_manager_.enqueue(entry); + } + + void Drain() + { + neighbor_manager_.drain(); + } + + ReturnCodeOr DeserializeNeighborEntry(const std::string &key, + const std::vector &attributes) + { + return neighbor_manager_.deserializeNeighborEntry(key, attributes); + } + + ReturnCode ValidateNeighborAppDbEntry(const P4NeighborAppDbEntry &app_db_entry) + { + return neighbor_manager_.validateNeighborAppDbEntry(app_db_entry); + } + + ReturnCode CreateNeighbor(P4NeighborEntry &neighbor_entry) + { + return neighbor_manager_.createNeighbor(neighbor_entry); + } + + ReturnCode RemoveNeighbor(const std::string &neighbor_key) + { + return neighbor_manager_.removeNeighbor(neighbor_key); + } + + ReturnCode SetDstMacAddress(P4NeighborEntry *neighbor_entry, const swss::MacAddress &mac_address) + { + return neighbor_manager_.setDstMacAddress(neighbor_entry, mac_address); + } + + ReturnCode ProcessAddRequest(const P4NeighborAppDbEntry &app_db_entry, const std::string &neighbor_key) + { + return neighbor_manager_.processAddRequest(app_db_entry, neighbor_key); + } + + ReturnCode ProcessUpdateRequest(const P4NeighborAppDbEntry &app_db_entry, P4NeighborEntry *neighbor_entry) + { + return neighbor_manager_.processUpdateRequest(app_db_entry, neighbor_entry); + } + + ReturnCode ProcessDeleteRequest(const std::string &neighbor_key) + { + return neighbor_manager_.processDeleteRequest(neighbor_key); + } + + P4NeighborEntry *GetNeighborEntry(const std::string &neighbor_key) + { + return neighbor_manager_.getNeighborEntry(neighbor_key); + } + + void ValidateNeighborEntry(const P4NeighborEntry &expected_entry, const uint32_t router_intf_ref_count) + { + auto neighbor_entry = GetNeighborEntry(expected_entry.neighbor_key); + + EXPECT_NE(nullptr, neighbor_entry); + EXPECT_EQ(expected_entry.router_intf_id, neighbor_entry->router_intf_id); + EXPECT_EQ(expected_entry.neighbor_id, neighbor_entry->neighbor_id); + EXPECT_EQ(expected_entry.dst_mac_address, neighbor_entry->dst_mac_address); + EXPECT_EQ(expected_entry.router_intf_key, neighbor_entry->router_intf_key); + EXPECT_EQ(expected_entry.neighbor_key, neighbor_entry->neighbor_key); + + EXPECT_TRUE(MatchNeighborEntry(&neighbor_entry->neigh_entry, expected_entry.neigh_entry)); + + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, expected_entry.neighbor_key)); + + uint32_t ref_count; + ASSERT_TRUE( + p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, expected_entry.router_intf_key, &ref_count)); + EXPECT_EQ(router_intf_ref_count, ref_count); + } + + void ValidateNeighborEntryNotPresent(const P4NeighborEntry &neighbor_entry, bool check_ref_count, + const uint32_t router_intf_ref_count = 0) + { + auto current_entry = GetNeighborEntry(neighbor_entry.neighbor_key); + EXPECT_EQ(current_entry, nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_entry.neighbor_key)); + + if (check_ref_count) + { + uint32_t ref_count; + ASSERT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, neighbor_entry.router_intf_key, + &ref_count)); + EXPECT_EQ(ref_count, router_intf_ref_count); + } + } + + void AddNeighborEntry(P4NeighborEntry &neighbor_entry, const sai_object_id_t router_intf_oid) + { + 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; + + EXPECT_CALL(mock_sai_neighbor_, + create_neighbor_entry(Truly(std::bind(MatchNeighborEntry, std::placeholders::_1, neigh_entry)), + Eq(2), + Truly(std::bind(MatchNeighborCreateAttributeList, std::placeholders::_1, + neighbor_entry.dst_mac_address)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + ASSERT_TRUE( + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, neighbor_entry.router_intf_key, router_intf_oid)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateNeighbor(neighbor_entry)); + } + + std::string CreateNeighborAppDbKey(const std::string router_interface_id, const swss::IpAddress neighbor_id) + { + nlohmann::json j; + j[prependMatchField(p4orch::kRouterInterfaceId)] = router_interface_id; + j[prependMatchField(p4orch::kNeighborId)] = neighbor_id.to_string(); + return j.dump(); + } + + StrictMock mock_sai_neighbor_; + MockResponsePublisher publisher_; + P4OidMapper p4_oid_mapper_; + NeighborManager neighbor_manager_; +}; + +TEST_F(NeighborManagerTest, CreateNeighborValidAttributes) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid1); + + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, CreateNeighborEntryExistsInManager) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid1); + + // Same neighbor key with different destination mac address. + P4NeighborEntry new_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress2); + EXPECT_EQ(StatusCode::SWSS_RC_EXISTS, CreateNeighbor(new_entry)); + + // Validate that entry in Manager has not changed. + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +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. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, CreateNeighbor(neighbor_entry)); + + auto current_entry = GetNeighborEntry(neighbor_entry.neighbor_key); + EXPECT_EQ(current_entry, nullptr); + + // Validate that dummyOID still exists in Centralized Mapper. + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_entry.neighbor_key)); +} + +TEST_F(NeighborManagerTest, CreateNeighborNonExistentRouterIntf) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, CreateNeighbor(neighbor_entry)); + + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/false); +} + +TEST_F(NeighborManagerTest, CreateNeighborSaiApiFails) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + + ASSERT_TRUE( + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, neighbor_entry.router_intf_key, kRouterInterfaceOid1)); + EXPECT_CALL(mock_sai_neighbor_, create_neighbor_entry(_, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, CreateNeighbor(neighbor_entry)); + + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); +} + +TEST_F(NeighborManagerTest, RemoveNeighborExistingNeighborEntry) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid2); + + sai_neighbor_entry_t neigh_entry; + neigh_entry.switch_id = gSwitchId; + copy(neigh_entry.ip_address, neighbor_entry.neighbor_id); + neigh_entry.rif_id = kRouterInterfaceOid2; + + EXPECT_CALL(mock_sai_neighbor_, + remove_neighbor_entry(Truly(std::bind(MatchNeighborEntry, std::placeholders::_1, neigh_entry)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, RemoveNeighbor(neighbor_entry.neighbor_key)); + + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); +} + +TEST_F(NeighborManagerTest, RemoveNeighborNonExistingNeighborEntry) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, RemoveNeighbor(neighbor_entry.neighbor_key)); +} + +TEST_F(NeighborManagerTest, RemoveNeighborNotExistInMapper) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid2); + + ASSERT_TRUE(p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_entry.neighbor_key)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, RemoveNeighbor(neighbor_entry.neighbor_key)); +} + +TEST_F(NeighborManagerTest, RemoveNeighborNonZeroRefCount) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid2); + + ASSERT_TRUE(p4_oid_mapper_.increaseRefCount(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_entry.neighbor_key)); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, RemoveNeighbor(neighbor_entry.neighbor_key)); + + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, RemoveNeighborSaiApiFails) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid2); + + EXPECT_CALL(mock_sai_neighbor_, remove_neighbor_entry(_)).WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, RemoveNeighbor(neighbor_entry.neighbor_key)); + + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, SetDstMacAddressModifyMacAddress) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid2); + + sai_neighbor_entry_t neigh_entry; + neigh_entry.switch_id = gSwitchId; + copy(neigh_entry.ip_address, neighbor_entry.neighbor_id); + neigh_entry.rif_id = kRouterInterfaceOid2; + + EXPECT_CALL(mock_sai_neighbor_, + set_neighbor_entry_attribute( + Truly(std::bind(MatchNeighborEntry, std::placeholders::_1, neigh_entry)), + Truly(std::bind(MatchNeighborSetAttributeList, std::placeholders::_1, kMacAddress1)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, SetDstMacAddress(&neighbor_entry, kMacAddress1)); + EXPECT_EQ(neighbor_entry.dst_mac_address, kMacAddress1); +} + +TEST_F(NeighborManagerTest, SetDstMacAddressIdempotent) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + + // SAI API not being called makes the operation idempotent. + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, SetDstMacAddress(&neighbor_entry, kMacAddress2)); + EXPECT_EQ(neighbor_entry.dst_mac_address, kMacAddress2); +} + +TEST_F(NeighborManagerTest, SetDstMacAddressSaiApiFails) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId2, kNeighborId2, kMacAddress2); + + EXPECT_CALL(mock_sai_neighbor_, set_neighbor_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, SetDstMacAddress(&neighbor_entry, kMacAddress1)); + EXPECT_EQ(neighbor_entry.dst_mac_address, kMacAddress2); +} + +TEST_F(NeighborManagerTest, ProcessAddRequestValidAppDbParams) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = kMacAddress1, + .is_set_dst_mac = true}; + + P4NeighborEntry neighbor_entry(app_db_entry.router_intf_id, app_db_entry.neighbor_id, app_db_entry.dst_mac_address); + neighbor_entry.neigh_entry.switch_id = gSwitchId; + copy(neighbor_entry.neigh_entry.ip_address, app_db_entry.neighbor_id); + neighbor_entry.neigh_entry.rif_id = kRouterInterfaceOid1; + + EXPECT_CALL( + mock_sai_neighbor_, + create_neighbor_entry( + Truly(std::bind(MatchNeighborEntry, std::placeholders::_1, neighbor_entry.neigh_entry)), Eq(2), + Truly(std::bind(MatchNeighborCreateAttributeList, std::placeholders::_1, app_db_entry.dst_mac_address)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_intf_id), + neighbor_entry.neigh_entry.rif_id)); + + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(app_db_entry.router_intf_id, app_db_entry.neighbor_id); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(app_db_entry, neighbor_key)); + + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, ProcessAddRequesDstMacAddressNotSet) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = swss::MacAddress(), + .is_set_dst_mac = false}; + + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(app_db_entry.router_intf_id, app_db_entry.neighbor_id); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRequest(app_db_entry, neighbor_key)); + + P4NeighborEntry neighbor_entry(app_db_entry.router_intf_id, app_db_entry.neighbor_id, app_db_entry.dst_mac_address); + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/false); +} + +TEST_F(NeighborManagerTest, ProcessAddRequestInvalidRouterInterface) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = kMacAddress1, + .is_set_dst_mac = true}; + + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(app_db_entry.router_intf_id, app_db_entry.neighbor_id); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(app_db_entry, neighbor_key)); + + P4NeighborEntry neighbor_entry(app_db_entry.router_intf_id, app_db_entry.neighbor_id, app_db_entry.dst_mac_address); + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/false); +} + +TEST_F(NeighborManagerTest, ProcessUpdateRequestSetDstMacAddress) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid1); + + neighbor_entry.neigh_entry.switch_id = gSwitchId; + copy(neighbor_entry.neigh_entry.ip_address, neighbor_entry.neighbor_id); + neighbor_entry.neigh_entry.rif_id = kRouterInterfaceOid1; + + EXPECT_CALL(mock_sai_neighbor_, + set_neighbor_entry_attribute( + Truly(std::bind(MatchNeighborEntry, std::placeholders::_1, neighbor_entry.neigh_entry)), + Truly(std::bind(MatchNeighborSetAttributeList, std::placeholders::_1, kMacAddress2)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = kMacAddress2, + .is_set_dst_mac = true}; + + // Update neighbor entry present in the Manager. + auto current_entry = GetNeighborEntry(neighbor_entry.neighbor_key); + ASSERT_NE(current_entry, nullptr); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(app_db_entry, current_entry)); + + // Validate that neighbor entry present in the Manager has the updated + // MacAddress. + neighbor_entry.dst_mac_address = kMacAddress2; + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, ProcessUpdateRequestSetDstMacAddressFails) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid1); + + EXPECT_CALL(mock_sai_neighbor_, set_neighbor_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = kMacAddress2, + .is_set_dst_mac = true}; + + // Update neighbor entry present in the Manager. + auto current_entry = GetNeighborEntry(neighbor_entry.neighbor_key); + ASSERT_NE(current_entry, nullptr); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessUpdateRequest(app_db_entry, current_entry)); + + // Validate that neighbor entry present in the Manager has not changed. + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); +} + +TEST_F(NeighborManagerTest, ProcessDeleteRequestExistingNeighborEntry) +{ + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + AddNeighborEntry(neighbor_entry, kRouterInterfaceOid1); + + neighbor_entry.neigh_entry.switch_id = gSwitchId; + copy(neighbor_entry.neigh_entry.ip_address, neighbor_entry.neighbor_id); + neighbor_entry.neigh_entry.rif_id = kRouterInterfaceOid1; + + EXPECT_CALL(mock_sai_neighbor_, remove_neighbor_entry(Truly(std::bind(MatchNeighborEntry, std::placeholders::_1, + neighbor_entry.neigh_entry)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRequest(neighbor_entry.neighbor_key)); + + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); +} + +TEST_F(NeighborManagerTest, ProcessDeleteRequestNonExistingNeighborEntry) +{ + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, + ProcessDeleteRequest(KeyGenerator::generateNeighborKey(kRouterInterfaceId1, kNeighborId1))); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryValidAttributes) +{ + const std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_dst_mac"), + swss::FieldValueTuple(prependParamField(p4orch::kDstMac), kMacAddress2.to_string()), + }; + + auto app_db_entry_or = + DeserializeNeighborEntry(CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId2), attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_intf_id, kRouterInterfaceId2); + EXPECT_EQ(app_db_entry.neighbor_id, kNeighborId2); + EXPECT_EQ(app_db_entry.dst_mac_address, kMacAddress2); + EXPECT_TRUE(app_db_entry.is_set_dst_mac); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryInvalidKeyFormat) +{ + const std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_dst_mac"), + swss::FieldValueTuple(prependParamField(p4orch::kDstMac), kMacAddress2.to_string()), + }; + + // Ensure that the following key is valid. It shall be modified to construct + // invalid key in rest of the test case. + std::string valid_key = R"({"match/router_interface_id":"intf-3/4","match/neighbor_id":"10.0.0.1"})"; + EXPECT_TRUE(DeserializeNeighborEntry(valid_key, attributes).ok()); + + // Invalid json format. + std::string invalid_key = R"({"match/router_interface_id:intf-3/4,match/neighbor_id:10.0.0.1"})"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); + + // Invalid json format. + invalid_key = R"([{"match/router_interface_id":"intf-3/4","match/neighbor_id":"10.0.0.1"}])"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); + + // Invalid json format. + invalid_key = R"(["match/router_interface_id","intf-3/4","match/neighbor_id","10.0.0.1"])"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); + + // Invalid json format. + invalid_key = R"({"match/router_interface_id":"intf-3/4","match/neighbor_id:10.0.0.1"})"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); + + // Invalid router interface id field name. + invalid_key = R"({"match/router_interface":"intf-3/4","match/neighbor_id":"10.0.0.1"})"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); + + // Invalid neighbor id field name. + invalid_key = R"({"match/router_interface_id":"intf-3/4","match/neighbor":"10.0.0.1"})"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryMissingAction) +{ + const std::vector attributes = { + swss::FieldValueTuple(prependParamField(p4orch::kDstMac), kMacAddress2.to_string()), + }; + + auto app_db_entry_or = + DeserializeNeighborEntry(CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId2), attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_intf_id, kRouterInterfaceId2); + EXPECT_EQ(app_db_entry.neighbor_id, kNeighborId2); + EXPECT_EQ(app_db_entry.dst_mac_address, kMacAddress2); + EXPECT_TRUE(app_db_entry.is_set_dst_mac); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryNoAttributes) +{ + const std::vector attributes; + + auto app_db_entry_or = + DeserializeNeighborEntry(CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId2), attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_intf_id, kRouterInterfaceId2); + EXPECT_EQ(app_db_entry.neighbor_id, kNeighborId2); + EXPECT_EQ(app_db_entry.dst_mac_address, swss::MacAddress()); + EXPECT_FALSE(app_db_entry.is_set_dst_mac); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryInvalidField) +{ + const std::vector attributes = {swss::FieldValueTuple("invalid_field", "invalid_value")}; + + EXPECT_FALSE(DeserializeNeighborEntry(CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId2), attributes).ok()); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryInvalidIpAddrValue) +{ + const std::vector attributes; + + // Invalid IPv4 address. + std::string invalid_key = R"({"match/router_interface_id":"intf-3/4","match/neighbor_id":"10.0.0.x"})"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); + + // Invalid IPv6 address. + invalid_key = R"({"match/router_interface_id":"intf-3/4","match/neighbor_id":"fe80::fe17:5f8g"})"; + EXPECT_FALSE(DeserializeNeighborEntry(invalid_key, attributes).ok()); +} + +TEST_F(NeighborManagerTest, DeserializeNeighborEntryInvalidMacAddrValue) +{ + const std::vector attributes = { + swss::FieldValueTuple(prependParamField(p4orch::kDstMac), "11:22:33:44:55")}; + + EXPECT_FALSE(DeserializeNeighborEntry(CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId2), attributes).ok()); +} + +TEST_F(NeighborManagerTest, ValidateNeighborAppDbEntryValidEntry) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = kMacAddress1, + .is_set_dst_mac = true}; + + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_intf_id), + kRouterInterfaceOid1)); + + EXPECT_TRUE(ValidateNeighborAppDbEntry(app_db_entry).ok()); +} + +TEST_F(NeighborManagerTest, ValidateNeighborAppDbEntryNonExistentRouterInterface) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = kMacAddress1, + .is_set_dst_mac = true}; + + EXPECT_FALSE(ValidateNeighborAppDbEntry(app_db_entry).ok()); +} + +TEST_F(NeighborManagerTest, ValidateNeighborAppDbEntryZeroMacAddress) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = swss::MacAddress(), + .is_set_dst_mac = true}; + + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_intf_id), + kRouterInterfaceOid1)); + + EXPECT_FALSE(ValidateNeighborAppDbEntry(app_db_entry).ok()); +} + +TEST_F(NeighborManagerTest, ValidateNeighborAppDbEntryMacAddressNotPresent) +{ + const P4NeighborAppDbEntry app_db_entry = {.router_intf_id = kRouterInterfaceId1, + .neighbor_id = kNeighborId1, + .dst_mac_address = swss::MacAddress(), + .is_set_dst_mac = false}; + + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_intf_id), + kRouterInterfaceOid1)); + + EXPECT_TRUE(ValidateNeighborAppDbEntry(app_db_entry).ok()); +} + +TEST_F(NeighborManagerTest, DrainValidAttributes) +{ + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + + const std::string appl_db_key = std::string(APP_P4RT_NEIGHBOR_TABLE_NAME) + kTableKeyDelimiter + + CreateNeighborAppDbKey(kRouterInterfaceId1, kNeighborId1); + + // Enqueue entry for create operation. + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kDstMac), kMacAddress1.to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_neighbor_, create_neighbor_entry(_, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + neighbor_entry.neigh_entry.switch_id = gSwitchId; + copy(neighbor_entry.neigh_entry.ip_address, neighbor_entry.neighbor_id); + neighbor_entry.neigh_entry.rif_id = kRouterInterfaceOid1; + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); + + // Enqueue entry for update operation. + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kDstMac), kMacAddress2.to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_neighbor_, set_neighbor_entry_attribute(_, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + + neighbor_entry.dst_mac_address = kMacAddress2; + ValidateNeighborEntry(neighbor_entry, /*router_intf_ref_count=*/1); + + // Enqueue entry for delete operation. + attributes.clear(); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, DEL_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_neighbor_, remove_neighbor_entry(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); +} + +TEST_F(NeighborManagerTest, DrainInvalidAppDbEntryKey) +{ + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + + // create invalid neighbor key with router interface id as kRouterInterfaceId1 + // and neighbor id as kNeighborId1 + const std::string invalid_neighbor_key = R"({"match/router_interface_id:intf-3/4,match/neighbor_id:10.0.0.22"})"; + const std::string appl_db_key = + std::string(APP_P4RT_NEIGHBOR_TABLE_NAME) + kTableKeyDelimiter + invalid_neighbor_key; + + // Enqueue entry for create operation. + std::vector attributes; + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + Drain(); + + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); +} + +TEST_F(NeighborManagerTest, DrainInvalidAppDbEntryAttributes) +{ + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + + // Non-existent router interface id in neighbor key. + std::string appl_db_key = std::string(APP_P4RT_NEIGHBOR_TABLE_NAME) + kTableKeyDelimiter + + CreateNeighborAppDbKey(kRouterInterfaceId2, kNeighborId1); + + std::vector attributes; + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + appl_db_key = std::string(APP_P4RT_NEIGHBOR_TABLE_NAME) + kTableKeyDelimiter + + CreateNeighborAppDbKey(kRouterInterfaceId1, kNeighborId1); + // Invalid destination mac address attribute. + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{p4orch::kDstMac, swss::MacAddress().to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + Drain(); + + // Validate that first create operation did not create a neighbor entry. + P4NeighborEntry neighbor_entry1(kRouterInterfaceId2, kNeighborId1, kMacAddress1); + ValidateNeighborEntryNotPresent(neighbor_entry1, /*check_ref_count=*/false); + + // Validate that second create operation did not create a neighbor entry. + P4NeighborEntry neighbor_entry2(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + ValidateNeighborEntryNotPresent(neighbor_entry2, /*check_ref_count=*/true); +} + +TEST_F(NeighborManagerTest, DrainInvalidOperation) +{ + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1), + kRouterInterfaceOid1)); + + const std::string appl_db_key = std::string(APP_P4RT_NEIGHBOR_TABLE_NAME) + kTableKeyDelimiter + + CreateNeighborAppDbKey(kRouterInterfaceId1, kNeighborId1); + + std::vector attributes; + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, "INVALID", attributes)); + Drain(); + + P4NeighborEntry neighbor_entry(kRouterInterfaceId1, kNeighborId1, kMacAddress1); + ValidateNeighborEntryNotPresent(neighbor_entry, /*check_ref_count=*/true); +} diff --git a/orchagent/p4orch/tests/next_hop_manager_test.cpp b/orchagent/p4orch/tests/next_hop_manager_test.cpp new file mode 100644 index 000000000000..a78310cc8d35 --- /dev/null +++ b/orchagent/p4orch/tests/next_hop_manager_test.cpp @@ -0,0 +1,709 @@ +#include "next_hop_manager.h" + +#include +#include + +#include +#include +#include + +#include "ipaddress.h" +#include "json.hpp" +#include "mock_response_publisher.h" +#include "mock_sai_next_hop.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_next_hop_api_t *sai_next_hop_api; +extern MockSaiNextHop *mock_sai_next_hop; + +namespace +{ + +constexpr char *kNextHopId = "8"; +constexpr char *kNextHopP4AppDbKey = R"({"match/nexthop_id":"8"})"; +constexpr sai_object_id_t kNextHopOid = 1; +constexpr char *kRouterInterfaceId1 = "16"; +constexpr char *kRouterInterfaceId2 = "17"; +constexpr sai_object_id_t kRouterInterfaceOid1 = 1; +constexpr sai_object_id_t kRouterInterfaceOid2 = 2; +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, + /*neighbor_id=*/swss::IpAddress(kNeighborId1), + /*is_set_router_interface_id=*/true, /*is_set_neighbor_id=*/true}; + +const P4NextHopAppDbEntry kP4NextHopAppDbEntry2{/*next_hop_id=*/kNextHopId, /*router_interface_id=*/kRouterInterfaceId2, + /*neighbor_id=*/swss::IpAddress(kNeighborId2), + /*is_set_router_interface_id=*/true, /*is_set_neighbor_id=*/true}; + +// APP DB entries for Delete request. +const P4NextHopAppDbEntry kP4NextHopAppDbEntry3{/*next_hop_id=*/kNextHopId, /*router_interface_id=*/"", + /*neighbor_id=*/swss::IpAddress(), + /*is_set_router_interface_id=*/false, /*is_set_neighbor_id=*/false}; + +std::unordered_map CreateAttributeListForNextHopObject( + const P4NextHopAppDbEntry &app_entry, const sai_object_id_t &rif_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}); + + 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; +} + +// Verifies whether the attribute list is the same as expected for SAI next +// hop's create_next_hop(). +// Returns true if they match; otherwise, false. +bool MatchCreateNextHopArgAttrList(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_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) + { + return false; + } + + for (int i = 0; i < 3; ++i) + { + switch (attr_list[i].id) + { + case SAI_NEXT_HOP_ATTR_TYPE: + if (attr_list[i].value.s32 != expected_attr_list.at(SAI_NEXT_HOP_ATTR_TYPE).s32) + return false; + break; + case SAI_NEXT_HOP_ATTR_IP: { + auto construct_ip_addr = [](const sai_ip_address_t &sai_ip_address) -> swss::IpAddress { + swss::ip_addr_t ipaddr; + if (sai_ip_address.addr_family == SAI_IP_ADDR_FAMILY_IPV4) + { + ipaddr.family = AF_INET; + ipaddr.ip_addr.ipv4_addr = sai_ip_address.addr.ip4; + } + else + { + ipaddr.family = AF_INET6; + memcpy(&ipaddr.ip_addr.ipv6_addr, &sai_ip_address.addr.ip6, sizeof(ipaddr.ip_addr.ipv6_addr)); + } + + return swss::IpAddress(ipaddr); + }; + + auto ipaddr = construct_ip_addr(attr_list[i].value.ipaddr); + auto expected_ipaddr = construct_ip_addr(expected_attr_list.at(SAI_NEXT_HOP_ATTR_IP).ipaddr); + if (ipaddr != expected_ipaddr) + { + return false; + } + break; + } + case SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID: + if (attr_list[i].value.oid != expected_attr_list.at(SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID).oid) + { + return false; + } + break; + default: + // Invalid attribute ID in next hop's attribute list. + return false; + } + } + + return true; +} + +} // namespace + +class NextHopManagerTest : public ::testing::Test +{ + protected: + NextHopManagerTest() : next_hop_manager_(&p4_oid_mapper_, &publisher_) + { + } + + void SetUp() override + { + // Set up mock stuff for SAI next hop API structure. + mock_sai_next_hop = &mock_sai_next_hop_; + sai_next_hop_api->create_next_hop = mock_create_next_hop; + sai_next_hop_api->remove_next_hop = mock_remove_next_hop; + sai_next_hop_api->set_next_hop_attribute = mock_set_next_hop_attribute; + sai_next_hop_api->get_next_hop_attribute = mock_get_next_hop_attribute; + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + next_hop_manager_.enqueue(entry); + } + + void Drain() + { + next_hop_manager_.drain(); + } + + ReturnCode ProcessAddRequest(const P4NextHopAppDbEntry &app_db_entry) + { + return next_hop_manager_.processAddRequest(app_db_entry); + } + + ReturnCode ProcessUpdateRequest(const P4NextHopAppDbEntry &app_db_entry, P4NextHopEntry *next_hop_entry) + { + return next_hop_manager_.processUpdateRequest(app_db_entry, next_hop_entry); + } + + ReturnCode ProcessDeleteRequest(const std::string &next_hop_key) + { + return next_hop_manager_.processDeleteRequest(next_hop_key); + } + + P4NextHopEntry *GetNextHopEntry(const std::string &next_hop_key) + { + return next_hop_manager_.getNextHopEntry(next_hop_key); + } + + ReturnCodeOr DeserializeP4NextHopAppDbEntry( + const std::string &key, const std::vector &attributes) + { + return next_hop_manager_.deserializeP4NextHopAppDbEntry(key, attributes); + } + + // Resolves the dependency of a next hop entry by adding depended router + // interface and neighbor into centralized mapper. + // Returns true on succuess. + bool ResolveNextHopEntryDependency(const P4NextHopAppDbEntry &app_db_entry, const sai_object_id_t &rif_oid); + + // Adds the next hop entry -- kP4NextHopAppDbEntry1, via next hop manager's + // ProcessAddRequest (). This function also takes care of all the dependencies + // of the next hop entry. + // Returns a valid pointer to next hop entry on success. + P4NextHopEntry *AddNextHopEntry1(); + + // Validates that a P4 App next hop entry is correctly added in next hop + // manager and centralized mapper. Returns true on success. + bool ValidateNextHopEntryAdd(const P4NextHopAppDbEntry &app_db_entry, const sai_object_id_t &expected_next_hop_oid); + + // 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_next_hop_; + MockResponsePublisher publisher_; + P4OidMapper p4_oid_mapper_; + NextHopManager next_hop_manager_; +}; + +bool NextHopManagerTest::ResolveNextHopEntryDependency(const P4NextHopAppDbEntry &app_db_entry, + const sai_object_id_t &rif_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)) + { + return false; + } + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(app_db_entry.router_interface_id, app_db_entry.neighbor_id); + if (!p4_oid_mapper_.setDummyOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key)) + { + return false; + } + return true; +} + +P4NextHopEntry *NextHopManagerTest::AddNextHopEntry1() +{ + if (!ResolveNextHopEntryDependency(kP4NextHopAppDbEntry1, kRouterInterfaceOid1)) + { + return nullptr; + } + + // 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(kP4NextHopAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(DoAll(SetArgPointee<0>(kNextHopOid), Return(SAI_STATUS_SUCCESS))); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + return GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)); +} + +bool NextHopManagerTest::ValidateNextHopEntryAdd(const P4NextHopAppDbEntry &app_db_entry, + const sai_object_id_t &expected_next_hop_oid) +{ + 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; + + 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) || + next_hop_oid != expected_next_hop_oid) + { + return false; + } + + return true; +} + +TEST_F(NextHopManagerTest, ProcessAddRequestShouldSucceedAddingNewNextHop) +{ + ASSERT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry1, kRouterInterfaceOid1)); + + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + uint32_t original_rif_ref_count; + ASSERT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, &original_rif_ref_count)); + uint32_t original_neighbor_ref_count; + ASSERT_TRUE(p4_oid_mapper_.getRefCount(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, &original_neighbor_ref_count)); + + // 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(kP4NextHopAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(DoAll(SetArgPointee<0>(kNextHopOid), Return(SAI_STATUS_SUCCESS))); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + EXPECT_TRUE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry1, kNextHopOid)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, original_rif_ref_count + 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, original_neighbor_ref_count + 1)); +} + +TEST_F(NextHopManagerTest, ProcessAddRequestShouldFailWhenNextHopExistInCentralMapper) +{ + 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. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, ProcessAddRequest(kP4NextHopAppDbEntry1)); +} + +TEST_F(NextHopManagerTest, ProcessAddRequestShouldFailWhenDependingRifIsAbsentInCentralMapper) +{ + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + ASSERT_TRUE(p4_oid_mapper_.setDummyOID(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key)); + + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + EXPECT_EQ(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)), nullptr); +} + +TEST_F(NextHopManagerTest, ProcessAddRequestShouldFailWhenDependingNeigherIsAbsentInCentralMapper) +{ + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + ASSERT_TRUE(p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, kRouterInterfaceOid1)); + + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + EXPECT_EQ(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)), nullptr); +} + +TEST_F(NextHopManagerTest, ProcessAddRequestShouldFailWhenSaiCallFails) +{ + ASSERT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry1, kRouterInterfaceOid1)); + + // 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(kP4NextHopAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + // The add request failed for the next hop entry. + EXPECT_EQ(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)), nullptr); +} + +TEST_F(NextHopManagerTest, ProcessAddRequestShouldDoNoOpForDuplicateAddRequest) +{ + ASSERT_NE(AddNextHopEntry1(), nullptr); + + // Add the same next hop entry again. + EXPECT_EQ(StatusCode::SWSS_RC_EXISTS, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + // Adding the same next hop entry multiple times should have the same outcome + // as adding it once. + EXPECT_TRUE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry1, kNextHopOid)); + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + +TEST_F(NextHopManagerTest, ProcessUpdateRequestShouldFailAsItIsUnsupported) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRequest(kP4NextHopAppDbEntry2, p4_next_hop_entry)); + + // Expect that the update call will fail, so next hop entry's fields stay the + // same. + EXPECT_TRUE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry1, kNextHopOid)); + + // Validate ref count stay the same. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + +TEST_F(NextHopManagerTest, ProcessDeleteRequestShouldSucceedForExistingNextHop) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + // Set up mock call. + EXPECT_CALL(mock_sai_next_hop_, remove_next_hop(Eq(p4_next_hop_entry->next_hop_oid))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessDeleteRequest(p4_next_hop_entry->next_hop_key)); + + // Validate the next hop entry has been deleted in both P4 next hop manager + // and centralized mapper. + p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)); + EXPECT_EQ(p4_next_hop_entry, nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id))); + + // Validate ref count decrement. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + 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, ProcessDeleteRequestShouldFailForNonExistingNextHop) +{ + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, + ProcessDeleteRequest(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id))); +} + +TEST_F(NextHopManagerTest, ProcessDeleteRequestShouldFailIfNextHopEntryIsAbsentInCentralMapper) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + ASSERT_TRUE(p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_NEXT_HOP, p4_next_hop_entry->next_hop_key)); + + // (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. + p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)); + ASSERT_NE(p4_next_hop_entry, nullptr); + + // Validate ref count remains the same. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + +TEST_F(NextHopManagerTest, ProcessDeleteRequestShouldFailIfNextHopEntryIsStillReferenced) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + ASSERT_TRUE(p4_oid_mapper_.increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, p4_next_hop_entry->next_hop_key)); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessDeleteRequest(p4_next_hop_entry->next_hop_key)); + + // Validate the next hop entry is not deleted in either P4 next hop manager or + // central mapper. + p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)); + ASSERT_NE(p4_next_hop_entry, nullptr); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEXT_HOP, p4_next_hop_entry->next_hop_key, 1)); + + // Validate ref count remains the same. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + +TEST_F(NextHopManagerTest, ProcessDeleteRequestShouldFailIfSaiCallFails) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + // Set up mock call. + EXPECT_CALL(mock_sai_next_hop_, remove_next_hop(Eq(p4_next_hop_entry->next_hop_oid))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessDeleteRequest(p4_next_hop_entry->next_hop_key)); + + // Validate the next hop entry is not deleted in either P4 next hop manager or + // central mapper. + p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)); + ASSERT_NE(p4_next_hop_entry, nullptr); + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id))); + + // Validate ref count remains the same. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + +TEST_F(NextHopManagerTest, GetNextHopEntryShouldReturnValidPointerForAddedNextHop) +{ + ASSERT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry1, kRouterInterfaceOid1)); + + // 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(kP4NextHopAppDbEntry1, kRouterInterfaceOid1))))) + .WillOnce(DoAll(SetArgPointee<0>(kNextHopOid), Return(SAI_STATUS_SUCCESS))); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(kP4NextHopAppDbEntry1)); + + EXPECT_NE(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)), nullptr); +} + +TEST_F(NextHopManagerTest, GetNextHopEntryShouldReturnNullPointerForNonexistingNextHop) +{ + EXPECT_EQ(GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)), nullptr); +} + +TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldSucceedForValidNextHopSetEntry) +{ + std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_nexthop"), + swss::FieldValueTuple(prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId1), + swss::FieldValueTuple(prependParamField(p4orch::kNeighborId), kNeighborId1)}; + + auto app_db_entry_or = DeserializeP4NextHopAppDbEntry(kNextHopP4AppDbKey, attributes); + 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_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); + EXPECT_TRUE(app_db_entry.is_set_neighbor_id); + EXPECT_EQ(app_db_entry.neighbor_id, swss::IpAddress(kNeighborId1)); +} + +TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldSucceedForValidNextHopDeleteEntry) +{ + auto app_db_entry_or = DeserializeP4NextHopAppDbEntry(kNextHopP4AppDbKey, std::vector()); + 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); +} + +TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldReturnNullPointerWhenFailToDeserializeNextHopId) +{ + // Incorrect format of P4 App next hop entry key + std::string key = R"({"nexthop":"8"})"; + std::vector attributes; + + EXPECT_FALSE(DeserializeP4NextHopAppDbEntry(key, attributes).ok()); +} + +TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldReturnNullPointerForInvalidIpAddr) +{ + std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_nexthop"), + swss::FieldValueTuple(prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId1), + swss::FieldValueTuple(prependParamField(p4orch::kNeighborId), "0.0.0.0.0.0")}; // Invalid IP address. + + EXPECT_FALSE(DeserializeP4NextHopAppDbEntry(kNextHopP4AppDbKey, attributes).ok()); +} + +TEST_F(NextHopManagerTest, DeserializeP4NextHopAppDbEntryShouldReturnNullPointerDueToUnexpectedField) +{ + std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_nexthop"), + swss::FieldValueTuple(p4orch::kRouterInterfaceId, kRouterInterfaceId1), + swss::FieldValueTuple("unexpected_field", "unexpected_value")}; + + EXPECT_FALSE(DeserializeP4NextHopAppDbEntry(kNextHopP4AppDbKey, attributes).ok()); +} + +TEST_F(NextHopManagerTest, DrainValidAppEntryShouldSucceed) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; + + std::vector fvs{{prependParamField(p4orch::kNeighborId), kNeighborId2}, + {prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId2}}; + + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + SET_COMMAND, fvs); + + Enqueue(app_db_entry); + + EXPECT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry2, kRouterInterfaceOid2)); + EXPECT_CALL(mock_sai_next_hop_, create_next_hop(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kNextHopOid), Return(SAI_STATUS_SUCCESS))); + + Drain(); + + EXPECT_TRUE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry2, kNextHopOid)); +} + +TEST_F(NextHopManagerTest, DrainAppEntryWithInvalidOpShouldBeNoOp) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; + + std::vector fvs{{prependParamField(p4orch::kNeighborId), kNeighborId2}, + {prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId2}}; + + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + "INVALID_OP", fvs); + + Enqueue(app_db_entry); + + EXPECT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry2, kRouterInterfaceOid2)); + + Drain(); + + EXPECT_FALSE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry2, kNextHopOid)); +} + +TEST_F(NextHopManagerTest, DrainAppEntryWithInvalidFieldShouldBeNoOp) +{ + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; + + std::vector fvs{{prependParamField(p4orch::kNeighborId), kNeighborId2}, + {prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId2}, + {"unexpected_field", "unexpected_value"}}; + + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + "INVALID_OP", fvs); + + Enqueue(app_db_entry); + + EXPECT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry2, kRouterInterfaceOid2)); + + Drain(); + + EXPECT_FALSE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry2, kNextHopOid)); +} + +TEST_F(NextHopManagerTest, DrainUpdateRequestShouldBeUnsupported) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; + std::vector fvs{{prependParamField(p4orch::kNeighborId), kNeighborId2}, + {prependParamField(p4orch::kRouterInterfaceId), kRouterInterfaceId2}}; + swss::KeyOpFieldsValuesTuple app_db_entry(std::string(APP_P4RT_NEXTHOP_TABLE_NAME) + kTableKeyDelimiter + j.dump(), + SET_COMMAND, fvs); + + Enqueue(app_db_entry); + EXPECT_TRUE(ResolveNextHopEntryDependency(kP4NextHopAppDbEntry2, kRouterInterfaceOid2)); + Drain(); + + // Expect that the update call will fail, so next hop entry's fields stay the + // same. + EXPECT_TRUE(ValidateNextHopEntryAdd(kP4NextHopAppDbEntry1, kNextHopOid)); + + // Validate ref count stay the same. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 1)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 1)); +} + +TEST_F(NextHopManagerTest, DrainDeleteRequestShouldSucceedForExistingNextHop) +{ + auto *p4_next_hop_entry = AddNextHopEntry1(); + ASSERT_NE(p4_next_hop_entry, nullptr); + + nlohmann::json j; + j[prependMatchField(p4orch::kNexthopId)] = kNextHopId; + 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(p4_next_hop_entry->next_hop_oid))) + .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. + p4_next_hop_entry = GetNextHopEntry(KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id)); + EXPECT_EQ(p4_next_hop_entry, nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(kP4NextHopAppDbEntry1.next_hop_id))); + + // Validate ref count decrement. + const std::string rif_key = KeyGenerator::generateRouterInterfaceKey(kP4NextHopAppDbEntry1.router_interface_id); + const std::string neighbor_key = + KeyGenerator::generateNeighborKey(kP4NextHopAppDbEntry1.router_interface_id, kP4NextHopAppDbEntry1.neighbor_id); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_key, 0)); + EXPECT_TRUE(ValidateRefCnt(SAI_OBJECT_TYPE_NEIGHBOR_ENTRY, neighbor_key, 0)); +} diff --git a/orchagent/p4orch/tests/p4oidmapper_test.cpp b/orchagent/p4orch/tests/p4oidmapper_test.cpp new file mode 100644 index 000000000000..131ee7aedb55 --- /dev/null +++ b/orchagent/p4orch/tests/p4oidmapper_test.cpp @@ -0,0 +1,122 @@ +#include "p4oidmapper.h" + +#include + +#include + +extern "C" +{ +#include "saitypes.h" +} + +namespace +{ + +constexpr char *kNextHopObject1 = "NextHop1"; +constexpr char *kNextHopObject2 = "NextHop2"; +constexpr char *kRouteObject1 = "Route1"; +constexpr char *kRouteObject2 = "Route2"; +constexpr sai_object_id_t kOid1 = 1; +constexpr sai_object_id_t kOid2 = 2; + +TEST(P4OidMapperTest, MapperTest) +{ + P4OidMapper mapper; + 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.setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); + EXPECT_TRUE(mapper.setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2, + /*ref_count=*/200)); + + EXPECT_EQ(2, mapper.getNumEntries(SAI_OBJECT_TYPE_NEXT_HOP)); + EXPECT_EQ(2, mapper.getNumEntries(SAI_OBJECT_TYPE_ROUTE_ENTRY)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + + sai_object_id_t oid; + EXPECT_TRUE(mapper.getOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, &oid)); + EXPECT_EQ(kOid1, oid); + EXPECT_TRUE(mapper.getOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, &oid)); + EXPECT_EQ(kOid2, oid); + + uint32_t ref_count; + EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, &ref_count)); + EXPECT_EQ(0, ref_count); + EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, &ref_count)); + EXPECT_EQ(100, ref_count); + EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1, &ref_count)); + EXPECT_EQ(0, ref_count); + EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2, &ref_count)); + EXPECT_EQ(200, ref_count); + EXPECT_TRUE(mapper.increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, &ref_count)); + EXPECT_EQ(1, ref_count); + EXPECT_TRUE(mapper.decreaseRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + EXPECT_TRUE(mapper.getRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2, &ref_count)); + EXPECT_EQ(199, ref_count); + + EXPECT_TRUE(mapper.decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + EXPECT_TRUE(mapper.eraseOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + EXPECT_EQ(1, mapper.getNumEntries(SAI_OBJECT_TYPE_NEXT_HOP)); + EXPECT_EQ(2, mapper.getNumEntries(SAI_OBJECT_TYPE_ROUTE_ENTRY)); + EXPECT_FALSE(mapper.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); + EXPECT_TRUE(mapper.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + + mapper.eraseAllOIDs(SAI_OBJECT_TYPE_ROUTE_ENTRY); + EXPECT_EQ(1, mapper.getNumEntries(SAI_OBJECT_TYPE_NEXT_HOP)); + EXPECT_EQ(0, mapper.getNumEntries(SAI_OBJECT_TYPE_ROUTE_ENTRY)); + EXPECT_FALSE(mapper.existsOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + 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)); +} + +TEST(P4OidMapperTest, ErrorTest) +{ + P4OidMapper mapper; + EXPECT_TRUE(mapper.setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid1)); + EXPECT_TRUE(mapper.setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2, std::numeric_limits::max())); + + // Set existing OID should fail. + EXPECT_FALSE(mapper.setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, kOid2)); + EXPECT_FALSE(mapper.setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + + // Get non-existing OID should fail. + sai_object_id_t oid; + EXPECT_FALSE(mapper.getOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2, &oid)); + + // Get OID with nullptr should fail. + EXPECT_FALSE(mapper.getOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1, nullptr)); + + // Get non-existing ref count should fail. + uint32_t ref_count; + EXPECT_FALSE(mapper.getRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1, &ref_count)); + + // Get ref count with nullptr should fail. + EXPECT_FALSE(mapper.getRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2, nullptr)); + + // Erase non-existing OID should fail. + EXPECT_FALSE(mapper.eraseOID(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject2)); + + // Erase OID with non-zero ref count should fail. + EXPECT_FALSE(mapper.eraseOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + + // Increase max ref count should fail. + EXPECT_FALSE(mapper.increaseRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject2)); + + // Increase non-existing ref count should fail. + EXPECT_FALSE(mapper.increaseRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); + + // Decrease zero ref count should fail. + EXPECT_FALSE(mapper.decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNextHopObject1)); + + // Decrease non-existing ref count should fail. + EXPECT_FALSE(mapper.decreaseRefCount(SAI_OBJECT_TYPE_ROUTE_ENTRY, kRouteObject1)); +} + +} // namespace diff --git a/orchagent/p4orch/tests/p4orch_util_test.cpp b/orchagent/p4orch/tests/p4orch_util_test.cpp new file mode 100644 index 000000000000..8c171b1e6e4b --- /dev/null +++ b/orchagent/p4orch/tests/p4orch_util_test.cpp @@ -0,0 +1,80 @@ +#include "p4orch_util.h" + +#include + +#include + +#include "ipprefix.h" +#include "swssnet.h" + +namespace +{ + +TEST(P4OrchUtilTest, KeyGeneratorTest) +{ + std::string intf_key = KeyGenerator::generateRouterInterfaceKey("intf-qe-3/7"); + EXPECT_EQ("router_interface_id=intf-qe-3/7", intf_key); + std::string neighbor_key = KeyGenerator::generateNeighborKey("intf-qe-3/7", swss::IpAddress("10.0.0.22")); + EXPECT_EQ("neighbor_id=10.0.0.22:router_interface_id=intf-qe-3/7", neighbor_key); + std::string nexthop_key = KeyGenerator::generateNextHopKey("ju1u32m1.atl11:qe-3/7"); + EXPECT_EQ("nexthop_id=ju1u32m1.atl11:qe-3/7", nexthop_key); + std::string wcmp_group_key = KeyGenerator::generateWcmpGroupKey("group-1"); + EXPECT_EQ("wcmp_group_id=group-1", wcmp_group_key); + std::string ipv4_route_key = KeyGenerator::generateRouteKey("b4-traffic", swss::IpPrefix("10.11.12.0/24")); + EXPECT_EQ("ipv4_dst=10.11.12.0/24:vrf_id=b4-traffic", ipv4_route_key); + ipv4_route_key = KeyGenerator::generateRouteKey("b4-traffic", swss::IpPrefix("0.0.0.0/0")); + EXPECT_EQ("ipv4_dst=0.0.0.0/0:vrf_id=b4-traffic", ipv4_route_key); + std::string ipv6_route_key = KeyGenerator::generateRouteKey("b4-traffic", swss::IpPrefix("2001:db8:1::/32")); + EXPECT_EQ("ipv6_dst=2001:db8:1::/32:vrf_id=b4-traffic", ipv6_route_key); + ipv6_route_key = KeyGenerator::generateRouteKey("b4-traffic", swss::IpPrefix("::/0")); + EXPECT_EQ("ipv6_dst=::/0:vrf_id=b4-traffic", ipv6_route_key); + + // Test with special characters. + neighbor_key = KeyGenerator::generateNeighborKey("::===::", swss::IpAddress("::1")); + EXPECT_EQ("neighbor_id=::1:router_interface_id=::===::", neighbor_key); + + std::map match_fvs; + match_fvs["ether_type"] = "0x0800"; + match_fvs["ipv6_dst"] = "fdf8:f53b:82e4::53 & fdf8:f53b:82e4::53"; + auto acl_rule_key = KeyGenerator::generateAclRuleKey(match_fvs, "15"); + EXPECT_EQ("match/ether_type=0x0800:match/" + "ipv6_dst=fdf8:f53b:82e4::53 & fdf8:f53b:82e4::53:priority=15", + acl_rule_key); +} + +TEST(P4OrchUtilTest, ParseP4RTKeyTest) +{ + std::string table; + std::string key; + parseP4RTKey("table:key", &table, &key); + EXPECT_EQ("table", table); + EXPECT_EQ("key", key); + parseP4RTKey("|||::::", &table, &key); + EXPECT_EQ("|||", table); + EXPECT_EQ(":::", key); + parseP4RTKey("invalid", &table, &key); + EXPECT_TRUE(table.empty()); + EXPECT_TRUE(key.empty()); +} + +TEST(P4OrchUtilTest, PrependMatchFieldShouldSucceed) +{ + EXPECT_EQ(prependMatchField("str"), "match/str"); +} + +TEST(P4OrchUtilTest, PrependParamFieldShouldSucceed) +{ + EXPECT_EQ(prependParamField("str"), "param/str"); +} + +TEST(P4OrchUtilTest, QuotedVarTest) +{ + std::string foo("Hello World"); + std::string bar("a string has 'quote'"); + EXPECT_EQ(QuotedVar(foo), "'Hello World'"); + EXPECT_EQ(QuotedVar(foo.c_str()), "'Hello World'"); + EXPECT_EQ(QuotedVar(bar), "'a string has \\\'quote\\\''"); + EXPECT_EQ(QuotedVar(bar.c_str()), "'a string has \\\'quote\\\''"); +} + +} // namespace diff --git a/orchagent/p4orch/tests/return_code_test.cpp b/orchagent/p4orch/tests/return_code_test.cpp new file mode 100644 index 000000000000..7a866827d7ce --- /dev/null +++ b/orchagent/p4orch/tests/return_code_test.cpp @@ -0,0 +1,176 @@ +#include "return_code.h" + +#include + +#include +#include + +extern "C" +{ +#include "sai.h" +} + +namespace +{ + +TEST(ReturnCodeTest, SuccessCode) +{ + auto return_code = ReturnCode(); + EXPECT_TRUE(return_code.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code.code()); + EXPECT_EQ("SWSS_RC_SUCCESS", return_code.codeStr()); + EXPECT_EQ("SWSS_RC_SUCCESS", return_code.message()); + EXPECT_EQ("SWSS_RC_SUCCESS:SWSS_RC_SUCCESS", return_code.toString()); + EXPECT_FALSE(return_code.isSai()); +} + +TEST(ReturnCodeTest, SuccessSaiCode) +{ + auto return_code = ReturnCode(SAI_STATUS_SUCCESS); + EXPECT_TRUE(return_code.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code.code()); + EXPECT_EQ("SWSS_RC_SUCCESS", return_code.codeStr()); + EXPECT_EQ("SWSS_RC_SUCCESS", return_code.message()); + EXPECT_EQ("SWSS_RC_SUCCESS:SWSS_RC_SUCCESS", return_code.toString()); + EXPECT_TRUE(return_code.isSai()); +} + +TEST(ReturnCodeTest, ErrorStatusCode) +{ + auto return_code = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid arguments."; + EXPECT_FALSE(return_code.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, return_code); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, return_code.code()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM", return_code.codeStr()); + EXPECT_EQ("Invalid arguments.", return_code.message()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM:Invalid arguments.", return_code.toString()); + EXPECT_FALSE(return_code.isSai()); +} + +TEST(ReturnCodeTest, ErrorSaiCode) +{ + sai_status_t sai_statue = SAI_STATUS_NOT_IMPLEMENTED; + auto return_code = ReturnCode(sai_statue) << "SAI error: " << sai_statue; + EXPECT_FALSE(return_code.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, return_code); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, return_code.code()); + EXPECT_EQ("SWSS_RC_UNIMPLEMENTED", return_code.codeStr()); + EXPECT_EQ("SAI error: -15", return_code.message()); + EXPECT_EQ("SWSS_RC_UNIMPLEMENTED:SAI error: -15", return_code.toString()); + EXPECT_TRUE(return_code.isSai()); +} + +TEST(ReturnCodeTest, ReturnCodeCopy) +{ + ReturnCode return_code_1 = ReturnCode() << "SUCCESS"; + ReturnCode return_code_2 = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid arguments."; + return_code_1 = return_code_2; + EXPECT_FALSE(return_code_1.ok()); + EXPECT_EQ(return_code_1, StatusCode::SWSS_RC_INVALID_PARAM); + EXPECT_EQ(return_code_1.code(), StatusCode::SWSS_RC_INVALID_PARAM); + EXPECT_EQ("SWSS_RC_INVALID_PARAM", return_code_1.codeStr()); + EXPECT_EQ("Invalid arguments.", return_code_1.message()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM:Invalid arguments.", return_code_1.toString()); + EXPECT_EQ(return_code_1, return_code_2); + EXPECT_FALSE(return_code_1 != return_code_2); +} + +TEST(ReturnCodeTest, PrependStringInMsg) +{ + auto return_code = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Detailed reasons."; + return_code.prepend("General statement - "); + EXPECT_FALSE(return_code.ok()); + EXPECT_FALSE(StatusCode::SWSS_RC_INVALID_PARAM != return_code); + EXPECT_FALSE(return_code != StatusCode::SWSS_RC_INVALID_PARAM); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, return_code.code()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM", return_code.codeStr()); + EXPECT_EQ("General statement - Detailed reasons.", return_code.message()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM:General statement - Detailed reasons.", return_code.toString()); +} + +TEST(ReturnCodeTest, CopyAndAppendStringInMsg) +{ + ReturnCode return_code; + return_code = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Detailed reasons."; + EXPECT_EQ("Detailed reasons.", return_code.message()); + return_code << " More details."; + EXPECT_FALSE(return_code.ok()); + EXPECT_FALSE(StatusCode::SWSS_RC_INVALID_PARAM != return_code); + EXPECT_FALSE(return_code != StatusCode::SWSS_RC_INVALID_PARAM); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, return_code.code()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM", return_code.codeStr()); + EXPECT_EQ("Detailed reasons. More details.", return_code.message()); + EXPECT_EQ("SWSS_RC_INVALID_PARAM:Detailed reasons. More details.", return_code.toString()); +} + +TEST(ReturnCodeTest, ReturnCodeOrHasInt) +{ + ReturnCodeOr return_code_or = 42; + EXPECT_TRUE(return_code_or.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code_or.status()); + EXPECT_EQ(42, return_code_or.value()); + EXPECT_EQ(42, *return_code_or); +} + +TEST(ReturnCodeTest, ReturnCodeOrHasCopyableObject) +{ + class TestObj + { + public: + TestObj(int value) : value_(value) + { + } + int GetValue() + { + return value_; + } + + private: + int value_ = 0; + }; + + ReturnCodeOr return_code_or = TestObj(42); + EXPECT_TRUE(return_code_or.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code_or.status()); + EXPECT_EQ(42, return_code_or.value().GetValue()); + EXPECT_EQ(42, (*return_code_or).GetValue()); + EXPECT_EQ(42, return_code_or->GetValue()); +} + +TEST(ReturnCodeTest, ReturnCodeOrHasMoveableObject) +{ + class TestObj + { + public: + TestObj(int value) : value_(value) + { + } + int GetValue() + { + return value_; + } + + private: + int value_ = 0; + }; + + ReturnCodeOr> return_code_or = std::make_unique(42); + EXPECT_TRUE(return_code_or.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, return_code_or.status()); + EXPECT_EQ(42, return_code_or.value()->GetValue()); + EXPECT_EQ(42, (*return_code_or)->GetValue()); + EXPECT_EQ(42, return_code_or->get()->GetValue()); + std::unique_ptr test_obj = std::move(*return_code_or); + EXPECT_EQ(42, test_obj->GetValue()); +} + +TEST(ReturnCodeTest, ReturnCodeOrHasReturnCode) +{ + ReturnCodeOr return_code_or = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM); + EXPECT_FALSE(return_code_or.ok()); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, return_code_or.status()); +} + +} // namespace diff --git a/orchagent/p4orch/tests/route_manager_test.cpp b/orchagent/p4orch/tests/route_manager_test.cpp new file mode 100644 index 000000000000..de1238761b5a --- /dev/null +++ b/orchagent/p4orch/tests/route_manager_test.cpp @@ -0,0 +1,1558 @@ +#include "route_manager.h" + +#include +#include + +#include +#include +#include + +#include "ipprefix.h" +#include "json.hpp" +#include "mock_response_publisher.h" +#include "mock_sai_route.h" +#include "p4orch.h" +#include "p4orch/p4orch_util.h" +#include "return_code.h" +#include "swssnet.h" +#include "vrforch.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_object_id_t gVirtualRouterId; +extern sai_object_id_t gVrfOid; +extern char *gVrfName; +extern sai_route_api_t *sai_route_api; +extern VRFOrch *gVrfOrch; + +namespace +{ + +constexpr char *kIpv4Prefix = "10.11.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; +constexpr char *kNexthopId2 = "ju1u32m2.atl11:qe-3/7"; +constexpr sai_object_id_t kNexthopOid2 = 2; +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; + +// Returns true if the two prefixes are equal. False otherwise. +// Arguments must be non-nullptr. +bool PrefixCmp(const sai_ip_prefix_t *x, const sai_ip_prefix_t *y) +{ + if (x->addr_family != y->addr_family) + { + return false; + } + if (x->addr_family == SAI_IP_ADDR_FAMILY_IPV4) + { + return memcmp(&x->addr.ip4, &y->addr.ip4, sizeof(sai_ip4_t)) == 0 && + memcmp(&x->mask.ip4, &y->mask.ip4, sizeof(sai_ip4_t)) == 0; + } + return memcmp(&x->addr.ip6, &y->addr.ip6, sizeof(sai_ip6_t)) == 0 && + 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) +{ + if (route_entry == nullptr) + { + return false; + } + if (route_entry->vr_id != expected_vrf_oid) + { + return false; + } + if (route_entry->switch_id != gSwitchId) + { + return false; + } + if (!PrefixCmp(&route_entry->destination, &expected_prefix)) + { + 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) +{ + if (attr == nullptr) + { + return false; + } + if (attr->id != SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION) + { + return false; + } + if (attr->value.s32 != expected_action) + { + 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) +{ + if (attr == nullptr) + { + return false; + } + if (attr->id != SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID) + { + return false; + } + if (attr->value.oid != expected_oid) + { + return false; + } + return true; +} + +} // namespace + +class RouteManagerTest : public ::testing::Test +{ + protected: + RouteManagerTest() : route_manager_(&p4_oid_mapper_, gVrfOrch, &publisher_) + { + } + + void SetUp() override + { + mock_sai_route = &mock_sai_route_; + sai_route_api->create_route_entry = create_route_entry; + sai_route_api->remove_route_entry = remove_route_entry; + sai_route_api->set_route_entry_attribute = set_route_entry_attribute; + sai_route_api->get_route_entry_attribute = get_route_entry_attribute; + sai_route_api->create_route_entries = create_route_entries; + sai_route_api->remove_route_entries = remove_route_entries; + sai_route_api->set_route_entries_attribute = set_route_entries_attribute; + sai_route_api->get_route_entries_attribute = get_route_entries_attribute; + } + + bool MergeRouteEntry(const P4RouteEntry &dest, const P4RouteEntry &src, P4RouteEntry *ret) + { + return route_manager_.mergeRouteEntry(dest, src, ret); + } + + ReturnCodeOr DeserializeRouteEntry(const std::string &key, + const std::vector &attributes, + const std::string &table_name) + { + return route_manager_.deserializeRouteEntry(key, attributes, table_name); + } + + P4RouteEntry *GetRouteEntry(const std::string &route_entry_key) + { + return route_manager_.getRouteEntry(route_entry_key); + } + + ReturnCode ValidateRouteEntry(const P4RouteEntry &route_entry) + { + return route_manager_.validateRouteEntry(route_entry); + } + + ReturnCode ValidateSetRouteEntry(const P4RouteEntry &route_entry) + { + return route_manager_.validateSetRouteEntry(route_entry); + } + + ReturnCode ValidateDelRouteEntry(const P4RouteEntry &route_entry) + { + return route_manager_.validateDelRouteEntry(route_entry); + } + + ReturnCode CreateRouteEntry(const P4RouteEntry &route_entry) + { + return route_manager_.createRouteEntry(route_entry); + } + + ReturnCode UpdateRouteEntry(const P4RouteEntry &route_entry) + { + return route_manager_.updateRouteEntry(route_entry); + } + + ReturnCode DeleteRouteEntry(const P4RouteEntry &route_entry) + { + return route_manager_.deleteRouteEntry(route_entry); + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + route_manager_.enqueue(entry); + } + + void Drain() + { + route_manager_.drain(); + } + + // 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) + { + 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_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + 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)); + } + + // 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) + { + 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); + 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)); + } + + // Verifies the two given route entries are identical. + void VerifyRouteEntriesEq(const P4RouteEntry &x, const P4RouteEntry &y) + { + EXPECT_EQ(x.route_entry_key, y.route_entry_key); + EXPECT_EQ(x.vrf_id, y.vrf_id); + EXPECT_EQ(x.route_prefix, y.route_prefix); + 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.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)); + } + + // Verifies the given route entry exists and matches. + void VerifyRouteEntry(const P4RouteEntry &route_entry, const sai_ip_prefix_t &sai_route_prefix, + const sai_object_id_t vrf_oid) + { + auto *route_entry_ptr = GetRouteEntry(route_entry.route_entry_key); + P4RouteEntry expect_entry = route_entry; + expect_entry.sai_route_entry.vr_id = vrf_oid; + expect_entry.sai_route_entry.switch_id = gSwitchId; + expect_entry.sai_route_entry.destination = sai_route_prefix; + VerifyRouteEntriesEq(expect_entry, *route_entry_ptr); + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key)); + } + + StrictMock mock_sai_route_; + MockResponsePublisher publisher_; + P4OidMapper p4_oid_mapper_; + RouteManager route_manager_; +}; + +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); + 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); + 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); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + P4RouteEntry expect_entry = dest; + expect_entry.nexthop_id = kNexthopId2; + 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); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.nexthop_id = ""; + expect_entry.action = p4orch::kSetWcmpGroupId; + expect_entry.wcmp_group = kWcmpGroup1; + 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); + 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, 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); + 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); + 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); + 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); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.wcmp_group = ""; + expect_entry.action = p4orch::kSetNexthopId; + expect_entry.nexthop_id = kNexthopId1; + 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); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.wcmp_group = ""; + expect_entry.action = p4orch::kDrop; + VerifyRouteEntriesEq(expect_entry, ret); +} + +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); + 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); + 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); + 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); + EXPECT_TRUE(MergeRouteEntry(dest, src, &ret)); + expect_entry = dest; + expect_entry.action = p4orch::kSetWcmpGroupId; + expect_entry.wcmp_group = kWcmpGroup1; + VerifyRouteEntriesEq(expect_entry, ret); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithNexthopIdActionTest) +{ + 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::kSetNexthopId}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kNexthopId), kNexthopId1}); + 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); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithWcmpGroupActionTest) +{ + 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::kSetWcmpGroupId}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kWcmpGroupId), kWcmpGroup1}); + 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); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithDropActionTest) +{ + 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::kDrop}); + 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); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithInvalidKeyShouldFail) +{ + std::string key = "{{{{{{{{{{{{"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kDrop}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV6_TABLE_NAME); + EXPECT_FALSE(route_entry_or.ok()); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithInvalidFieldShouldFail) +{ + std::string key = R"({"match/vrf_id":"b4-traffic","match/ipv6_dst":"2001:db8:1::/32"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{"invalid", "invalid"}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV6_TABLE_NAME); + EXPECT_FALSE(route_entry_or.ok()); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithInvalidRouteShouldFail) +{ + std::string key = R"({"match/vrf_id":"b4-traffic","match/ipv6_dst":"invalid"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kDrop}); + auto route_entry_or = DeserializeRouteEntry(key, attributes, APP_P4RT_IPV6_TABLE_NAME); + EXPECT_FALSE(route_entry_or.ok()); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithoutIpv4WildcardLpmMatchShouldSucceed) +{ + std::string key = R"({"match/vrf_id":"b4-traffic"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kDrop}); + 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); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryWithoutIpv6WildcardLpmMatchShouldSucceed) +{ + std::string key = R"({"match/vrf_id":"b4-traffic"})"; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{p4orch::kAction, p4orch::kDrop}); + 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); + VerifyRouteEntriesEq(expect_entry, route_entry); +} + +TEST_F(RouteManagerTest, ValidateRouteEntryTest) +{ + auto swss_ipv4_route_prefix = swss::IpPrefix(kIpv4Prefix); + + // 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)); + 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)); +} + +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)); +} + +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); + 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)); +} + +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)); +} + +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); + p4_oid_mapper_.setDummyOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateSetRouteEntry(route_entry)); +} + +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); + p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_ROUTE_ENTRY, route_entry.route_entry_key); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ValidateSetRouteEntry(route_entry)); +} + +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)); +} + +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; + 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)); +} + +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)); +} + +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; + 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)); +} + +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; + 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)); +} + +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; + 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)); +} + +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; + 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)); +} + +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)); +} + +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)); +} + +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); + 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)); +} + +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)); +} + +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)); +} + +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)); +} + +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)); +} + +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); + + EXPECT_CALL(mock_sai_route_, create_route_entry(_, _, _)).Times(3).WillRepeatedly(Return(SAI_STATUS_FAILURE)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, CreateRouteEntry(route_entry)); + + route_entry.action = p4orch::kSetNexthopId; + route_entry.nexthop_id = kNexthopId1; + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, CreateRouteEntry(route_entry)); + + route_entry.action = p4orch::kSetWcmpGroupId; + route_entry.wcmp_group = kWcmpGroup1; + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, CreateRouteEntry(route_entry)); +} + +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); + 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)); + 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, 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); + 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)); + VerifyRouteEntry(route_entry, 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); +} + +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); + + 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)); + VerifyRouteEntry(route_entry, sai_ipv4_route_prefix, gVrfOid); +} + +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); + + 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)); + VerifyRouteEntry(route_entry, sai_ipv6_route_prefix, gVrfOid); +} + +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); + 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)); + 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(1, ref_cnt); +} + +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); + 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)); + 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, UpdateRouteEntryWcmpWithSaiErrorShouldFail) +{ + 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); + 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)); +} + +TEST_F(RouteManagerTest, UpdateRouteEntryWcmpNotExistInMapperShouldFail) +{ + 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)); +} + +TEST_F(RouteManagerTest, UpdateRouteFromSetWcmpToSetNextHopSucceeds) +{ + 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::kSetNexthopId; + route_entry.nexthop_id = kNexthopId2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + 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)); + 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, UpdateRouteFromSetNexthopIdToSetWcmpSucceeds) +{ + 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::kSetWcmpGroupId; + route_entry.wcmp_group = kWcmpGroup2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + 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; + 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); + 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)); +} + +TEST_F(RouteManagerTest, UpdateRouteEntryNexthopIdNotExistInMapperShouldFail) +{ + 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)); +} + +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)); +} + +TEST_F(RouteManagerTest, UpdateRouteWithDifferentNexthopIdsSucceeds) +{ + 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.nexthop_id = kNexthopId2; + route_entry.route_entry_key = KeyGenerator::generateRouteKey(route_entry.vrf_id, route_entry.route_prefix); + 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)); + route_entry.action = p4orch::kSetNexthopId; + 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); + 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::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)); + 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); + 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); + 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)); + 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(kNexthopId2), &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +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); + 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); + 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; + 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); + + 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)); + 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) +{ + 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)); +} + +TEST_F(RouteManagerTest, DeleteRouteEntryWithSaiErrorShouldFail) +{ + 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)); +} + +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); + + 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)); + 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); + + 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)); + 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) +{ + 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)); + + 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)); + + 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); + 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); + 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, RouteCreateAndDeleteInDrainSucceeds) +{ + 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}); + 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)); + + 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)); + Drain(); + std::string key = KeyGenerator::generateRouteKey(gVrfName, swss::IpPrefix(kIpv4Prefix)); + auto *route_entry_ptr = GetRouteEntry(key); + EXPECT_EQ(nullptr, route_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_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, 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)); + + 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)); + + Drain(); + std::string key = KeyGenerator::generateRouteKey(kDefaultVrfName, swss::IpPrefix(kIpv4Prefix)); + auto *route_entry_ptr = GetRouteEntry(key); + EXPECT_NE(nullptr, route_entry_ptr); + EXPECT_TRUE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_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); +} + +TEST_F(RouteManagerTest, DeserializeRouteEntryInDrainFails) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_IPV4_TABLE_NAME) + kTableKeyDelimiter; + Enqueue( + swss::KeyOpFieldsValuesTuple(kKeyPrefix + "{{{{{{{{{{{{", SET_COMMAND, std::vector{})); + 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)); + 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)); + Drain(); +} + +TEST_F(RouteManagerTest, ValidateSetRouteEntryInDrainFails) +{ + 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)); + Drain(); +} + +TEST_F(RouteManagerTest, ValidateDelRouteEntryInDrainFails) +{ + 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; + // 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)); + 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); + 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(), "INVALID_COMMAND", attributes)); + Drain(); +} diff --git a/orchagent/p4orch/tests/router_interface_manager_test.cpp b/orchagent/p4orch/tests/router_interface_manager_test.cpp new file mode 100644 index 000000000000..661fe33efa20 --- /dev/null +++ b/orchagent/p4orch/tests/router_interface_manager_test.cpp @@ -0,0 +1,843 @@ +#include "router_interface_manager.h" + +#include +#include + +#include +#include + +#include "mock_response_publisher.h" +#include "mock_sai_router_interface.h" +#include "p4orch.h" +#include "p4orch/p4orch_util.h" +#include "portsorch.h" +#include "return_code.h" +#include "swssnet.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 PortsOrch *gPortsOrch; + +extern sai_object_id_t gSwitchId; +extern sai_object_id_t gVirtualRouterId; +extern sai_router_interface_api_t *sai_router_intfs_api; + +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 *kRouterInterfaceId1 = "intf-3/4"; +constexpr sai_object_id_t kRouterInterfaceOid1 = 0x295100; +const swss::MacAddress kMacAddress1("00:01:02:03:04:05"); + +constexpr char *kRouterInterfaceId2 = "Ethernet20"; +constexpr sai_object_id_t kRouterInterfaceOid2 = 0x51411; +const swss::MacAddress kMacAddress2("00:ff:ee:dd:cc:bb"); + +const swss::MacAddress kZeroMacAddress("00:00:00:00:00:00"); + +constexpr char *kRouterIntfAppDbKey = R"({"match/router_interface_id":"intf-3/4"})"; + +std::unordered_map CreateRouterInterfaceAttributeList( + const sai_object_id_t &virtual_router_oid, const swss::MacAddress mac_address, const sai_object_id_t &port_oid, + const uint32_t mtu) +{ + std::unordered_map attr_list; + sai_attribute_value_t attr_value; + + attr_value.oid = virtual_router_oid; + attr_list[SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID] = attr_value; + + if (mac_address != kZeroMacAddress) + { + memcpy(attr_value.mac, mac_address.getMac(), sizeof(sai_mac_t)); + attr_list[SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS] = attr_value; + } + + attr_value.s32 = SAI_ROUTER_INTERFACE_TYPE_PORT; + attr_list[SAI_ROUTER_INTERFACE_ATTR_TYPE] = attr_value; + + attr_value.oid = port_oid; + attr_list[SAI_ROUTER_INTERFACE_ATTR_PORT_ID] = attr_value; + + attr_value.u32 = mtu; + attr_list[SAI_ROUTER_INTERFACE_ATTR_MTU] = attr_value; + + return attr_list; +} + +bool MatchCreateRouterInterfaceAttributeList( + const sai_attribute_t *attr_list, + const std::unordered_map &expected_attr_list) +{ + if (attr_list == nullptr) + return false; + + int matched_attr_num = 0; + const int attr_list_length = (int)expected_attr_list.size(); + for (int i = 0; i < attr_list_length; i++) + { + switch (attr_list[i].id) + { + case SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID: + if (attr_list[i].value.oid != expected_attr_list.at(SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID).oid) + { + return false; + } + matched_attr_num++; + break; + + case SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS: + if (memcmp(attr_list[i].value.mac, expected_attr_list.at(SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS).mac, + sizeof(sai_mac_t))) + { + return false; + } + matched_attr_num++; + break; + + case SAI_ROUTER_INTERFACE_ATTR_TYPE: + if (attr_list[i].value.s32 != expected_attr_list.at(SAI_ROUTER_INTERFACE_ATTR_TYPE).s32) + { + return false; + } + matched_attr_num++; + break; + + case SAI_ROUTER_INTERFACE_ATTR_PORT_ID: + if (attr_list[i].value.oid != expected_attr_list.at(SAI_ROUTER_INTERFACE_ATTR_PORT_ID).oid) + { + return false; + } + matched_attr_num++; + break; + + case SAI_ROUTER_INTERFACE_ATTR_MTU: + if (attr_list[i].value.u32 != expected_attr_list.at(SAI_ROUTER_INTERFACE_ATTR_MTU).u32) + { + return false; + } + matched_attr_num++; + break; + + default: + // Unexpected attribute present in attribute list + return false; + } + } + + return (matched_attr_num == attr_list_length); +} + +} // namespace + +class RouterInterfaceManagerTest : public ::testing::Test +{ + protected: + RouterInterfaceManagerTest() : router_intf_manager_(&p4_oid_mapper_, &publisher_) + { + } + + void SetUp() override + { + 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; + sai_router_intfs_api->set_router_interface_attribute = mock_set_router_interface_attribute; + sai_router_intfs_api->get_router_interface_attribute = mock_get_router_interface_attribute; + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + router_intf_manager_.enqueue(entry); + } + + void Drain() + { + router_intf_manager_.drain(); + } + + ReturnCodeOr DeserializeRouterIntfEntry( + const std::string &key, const std::vector &attributes) + { + return router_intf_manager_.deserializeRouterIntfEntry(key, attributes); + } + + ReturnCode CreateRouterInterface(const std::string &router_intf_key, P4RouterInterfaceEntry &router_intf_entry) + { + return router_intf_manager_.createRouterInterface(router_intf_key, router_intf_entry); + } + + ReturnCode RemoveRouterInterface(const std::string &router_intf_key) + { + return router_intf_manager_.removeRouterInterface(router_intf_key); + } + + ReturnCode SetSourceMacAddress(P4RouterInterfaceEntry *router_intf_entry, const swss::MacAddress &mac_address) + { + return router_intf_manager_.setSourceMacAddress(router_intf_entry, mac_address); + } + + ReturnCode ProcessAddRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, const std::string &router_intf_key) + { + return router_intf_manager_.processAddRequest(app_db_entry, router_intf_key); + } + + ReturnCode ProcessUpdateRequest(const P4RouterInterfaceAppDbEntry &app_db_entry, + P4RouterInterfaceEntry *router_intf_entry) + { + return router_intf_manager_.processUpdateRequest(app_db_entry, router_intf_entry); + } + + ReturnCode ProcessDeleteRequest(const std::string &router_intf_key) + { + return router_intf_manager_.processDeleteRequest(router_intf_key); + } + + P4RouterInterfaceEntry *GetRouterInterfaceEntry(const std::string &router_intf_key) + { + return router_intf_manager_.getRouterInterfaceEntry(router_intf_key); + } + + void ValidateRouterInterfaceEntry(const P4RouterInterfaceEntry &expected_entry) + { + const std::string router_intf_key = + KeyGenerator::generateRouterInterfaceKey(expected_entry.router_interface_id); + auto router_intf_entry = GetRouterInterfaceEntry(router_intf_key); + + EXPECT_NE(nullptr, router_intf_entry); + EXPECT_EQ(expected_entry.router_interface_id, router_intf_entry->router_interface_id); + EXPECT_EQ(expected_entry.port_name, router_intf_entry->port_name); + EXPECT_EQ(expected_entry.src_mac_address, router_intf_entry->src_mac_address); + EXPECT_EQ(expected_entry.router_interface_oid, router_intf_entry->router_interface_oid); + + sai_object_id_t p4_mapper_oid; + ASSERT_TRUE(p4_oid_mapper_.getOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, &p4_mapper_oid)); + EXPECT_EQ(expected_entry.router_interface_oid, p4_mapper_oid); + } + + void ValidateRouterInterfaceEntryNotPresent(const std::string router_interface_id) + { + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(router_interface_id); + auto current_entry = GetRouterInterfaceEntry(router_intf_key); + EXPECT_EQ(current_entry, nullptr); + EXPECT_FALSE(p4_oid_mapper_.existsOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key)); + } + + void AddRouterInterfaceEntry(P4RouterInterfaceEntry &router_intf_entry, const sai_object_id_t port_oid, + const uint32_t mtu) + { + EXPECT_CALL(mock_sai_router_intf_, + create_router_interface( + ::testing::NotNull(), Eq(gSwitchId), Eq(5), + Truly(std::bind(MatchCreateRouterInterfaceAttributeList, std::placeholders::_1, + CreateRouterInterfaceAttributeList( + gVirtualRouterId, router_intf_entry.src_mac_address, port_oid, mtu))))) + .WillOnce(DoAll(SetArgPointee<0>(router_intf_entry.router_interface_oid), Return(SAI_STATUS_SUCCESS))); + + const std::string router_intf_key = + KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouterInterface(router_intf_key, router_intf_entry)); + } + + StrictMock mock_sai_router_intf_; + MockResponsePublisher publisher_; + P4OidMapper p4_oid_mapper_; + RouterInterfaceManager router_intf_manager_; +}; + +TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceValidAttributes) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceEntryExistsInManager) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + // Same router interface key with different attributes + P4RouterInterfaceEntry new_entry(router_intf_entry.router_interface_id, kPortName2, kMacAddress2); + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id); + EXPECT_EQ(StatusCode::SWSS_RC_EXISTS, CreateRouterInterface(router_intf_key, new_entry)); + + // Validate that entry in Manager and Centralized Mapper has not changed + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceEntryExistsInP4OidMapper) +{ + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId2); + p4_oid_mapper_.setOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, kRouterInterfaceOid2); + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId2, kPortName2, kMacAddress2); + + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, CreateRouterInterface(router_intf_key, router_intf_entry)); + + auto current_entry = GetRouterInterfaceEntry(router_intf_key); + EXPECT_EQ(current_entry, nullptr); + + // Validate that OID doesn't change in Centralized Mapper + sai_object_id_t mapper_oid; + ASSERT_TRUE(p4_oid_mapper_.getOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key, &mapper_oid)); + EXPECT_EQ(mapper_oid, kRouterInterfaceOid2); +} + +TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceInvalidPort) +{ + const std::string invalid_port_name = "xyz"; + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId2, invalid_port_name, kMacAddress2); + + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, + CreateRouterInterface(KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId2), router_intf_entry)); + + ValidateRouterInterfaceEntryNotPresent(kRouterInterfaceId2); +} + +TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceNoMacAddress) +{ + P4RouterInterfaceEntry router_intf_entry; + router_intf_entry.router_interface_id = kRouterInterfaceId1; + router_intf_entry.port_name = kPortName1; + + EXPECT_CALL(mock_sai_router_intf_, + create_router_interface(::testing::NotNull(), Eq(gSwitchId), Eq(4), + Truly(std::bind(MatchCreateRouterInterfaceAttributeList, std::placeholders::_1, + CreateRouterInterfaceAttributeList( + gVirtualRouterId, kZeroMacAddress, kPortOid1, kMtu1))))) + .WillOnce(DoAll(SetArgPointee<0>(kRouterInterfaceOid1), Return(SAI_STATUS_SUCCESS))); + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, CreateRouterInterface(router_intf_key, router_intf_entry)); + + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, CreateRouterInterfaceSaiApiFails) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + EXPECT_CALL(mock_sai_router_intf_, create_router_interface(_, _, _, _)).WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, + CreateRouterInterface(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id), + router_intf_entry)); + + ValidateRouterInterfaceEntryNotPresent(router_intf_entry.router_interface_id); +} + +TEST_F(RouterInterfaceManagerTest, RemoveRouterInterfaceExistingInterface) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId2, kPortName2, kMacAddress2); + router_intf_entry.router_interface_oid = kRouterInterfaceOid2; + AddRouterInterfaceEntry(router_intf_entry, kPortOid2, kMtu2); + + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(Eq(router_intf_entry.router_interface_oid))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, + RemoveRouterInterface(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id))); + + ValidateRouterInterfaceEntryNotPresent(router_intf_entry.router_interface_id); +} + +TEST_F(RouterInterfaceManagerTest, RemoveRouterInterfaceNonExistingInterface) +{ + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, + RemoveRouterInterface(KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId2))); +} + +TEST_F(RouterInterfaceManagerTest, RemoveRouterInterfaceNonZeroRefCount) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId2, kPortName2, kMacAddress2); + router_intf_entry.router_interface_oid = kRouterInterfaceOid2; + AddRouterInterfaceEntry(router_intf_entry, kPortOid2, kMtu2); + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id); + ASSERT_TRUE(p4_oid_mapper_.increaseRefCount(SAI_OBJECT_TYPE_ROUTER_INTERFACE, router_intf_key)); + + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, RemoveRouterInterface(router_intf_key)); + + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, RemoveRouterInterfaceSaiApiFails) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId2, kPortName2, kMacAddress2); + router_intf_entry.router_interface_oid = kRouterInterfaceOid2; + AddRouterInterfaceEntry(router_intf_entry, kPortOid2, kMtu2); + + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(Eq(router_intf_entry.router_interface_oid))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, + RemoveRouterInterface(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id))); + + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, SetSourceMacAddressModifyMacAddress) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + + sai_attribute_value_t attr_value; + memcpy(attr_value.mac, kMacAddress2.getMac(), sizeof(sai_mac_t)); + std::unordered_map attr_list = { + {SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS, attr_value}}; + EXPECT_CALL(mock_sai_router_intf_, + set_router_interface_attribute( + Eq(router_intf_entry.router_interface_oid), + Truly(std::bind(MatchCreateRouterInterfaceAttributeList, std::placeholders::_1, attr_list)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, SetSourceMacAddress(&router_intf_entry, kMacAddress2)); + EXPECT_EQ(router_intf_entry.src_mac_address, kMacAddress2); +} + +TEST_F(RouterInterfaceManagerTest, SetSourceMacAddressIdempotent) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + + // SAI API not being called makes the operation idempotent. + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, SetSourceMacAddress(&router_intf_entry, kMacAddress1)); + EXPECT_EQ(router_intf_entry.src_mac_address, kMacAddress1); +} + +TEST_F(RouterInterfaceManagerTest, SetSourceMacAddressSaiApiFails) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + + sai_attribute_value_t attr_value; + memcpy(attr_value.mac, kMacAddress2.getMac(), sizeof(sai_mac_t)); + std::unordered_map attr_list = { + {SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS, attr_value}}; + EXPECT_CALL(mock_sai_router_intf_, + set_router_interface_attribute( + Eq(router_intf_entry.router_interface_oid), + Truly(std::bind(MatchCreateRouterInterfaceAttributeList, std::placeholders::_1, attr_list)))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, SetSourceMacAddress(&router_intf_entry, kMacAddress2)); + EXPECT_EQ(router_intf_entry.src_mac_address, kMacAddress1); +} + +TEST_F(RouterInterfaceManagerTest, ProcessAddRequestValidAppDbParams) +{ + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = kRouterInterfaceId1, + .port_name = kPortName1, + .src_mac_address = kMacAddress1, + .is_set_port_name = true, + .is_set_src_mac = true}; + + EXPECT_CALL(mock_sai_router_intf_, + create_router_interface(::testing::NotNull(), Eq(gSwitchId), Eq(5), + Truly(std::bind(MatchCreateRouterInterfaceAttributeList, std::placeholders::_1, + CreateRouterInterfaceAttributeList( + gVirtualRouterId, kMacAddress1, kPortOid1, kMtu1))))) + .WillOnce(DoAll(SetArgPointee<0>(kRouterInterfaceOid1), Return(SAI_STATUS_SUCCESS))); + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(app_db_entry, router_intf_key)); + + P4RouterInterfaceEntry router_intf_entry(app_db_entry.router_interface_id, app_db_entry.port_name, + app_db_entry.src_mac_address); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, ProcessAddRequestPortNameMissing) +{ + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = kRouterInterfaceId1, + .port_name = "", + .src_mac_address = kMacAddress1, + .is_set_port_name = false, + .is_set_src_mac = true}; + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRequest(app_db_entry, router_intf_key)); +} + +TEST_F(RouterInterfaceManagerTest, ProcessAddRequestInvalidPortName) +{ + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = kRouterInterfaceId1, + .port_name = "", + .src_mac_address = kMacAddress1, + .is_set_port_name = true, + .is_set_src_mac = true}; + + const std::string router_intf_key = KeyGenerator::generateRouterInterfaceKey(app_db_entry.router_interface_id); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(app_db_entry, router_intf_key)); +} + +TEST_F(RouterInterfaceManagerTest, ProcessUpdateRequestSetSourceMacAddress) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + sai_attribute_value_t attr_value; + memcpy(attr_value.mac, kMacAddress2.getMac(), sizeof(sai_mac_t)); + std::unordered_map attr_list = { + {SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS, attr_value}}; + EXPECT_CALL(mock_sai_router_intf_, + set_router_interface_attribute( + Eq(router_intf_entry.router_interface_oid), + Truly(std::bind(MatchCreateRouterInterfaceAttributeList, std::placeholders::_1, attr_list)))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = router_intf_entry.router_interface_id, + .port_name = "", + .src_mac_address = kMacAddress2, + .is_set_port_name = false, + .is_set_src_mac = true}; + + // Update router interface entry present in the Manager. + auto current_entry = + GetRouterInterfaceEntry(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id)); + ASSERT_NE(current_entry, nullptr); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(app_db_entry, current_entry)); + + // Validate that router interface entry present in the Manager has the updated + // MacAddress. + router_intf_entry.src_mac_address = kMacAddress2; + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, ProcessUpdateRequestSetPortNameIdempotent) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = router_intf_entry.router_interface_id, + .port_name = kPortName1, + .src_mac_address = swss::MacAddress(), + .is_set_port_name = true, + .is_set_src_mac = false}; + + // Update router interface entry present in the Manager. + auto current_entry = + GetRouterInterfaceEntry(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id)); + ASSERT_NE(current_entry, nullptr); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(app_db_entry, current_entry)); + + // Validate that router interface entry present in the Manager has not + // changed. + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, ProcessUpdateRequestSetPortName) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = router_intf_entry.router_interface_id, + .port_name = kPortName2, + .src_mac_address = swss::MacAddress(), + .is_set_port_name = true, + .is_set_src_mac = false}; + + // Update router interface entry present in the Manager. + auto current_entry = + GetRouterInterfaceEntry(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id)); + ASSERT_NE(current_entry, nullptr); + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRequest(app_db_entry, current_entry)); + + // Validate that router interface entry present in the Manager has not + // changed. + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, ProcessUpdateRequestMacAddrAndPort) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + const P4RouterInterfaceAppDbEntry app_db_entry = {.router_interface_id = router_intf_entry.router_interface_id, + .port_name = kPortName2, + .src_mac_address = kMacAddress2, + .is_set_port_name = true, + .is_set_src_mac = true}; + + // Update router interface entry present in the Manager. + auto current_entry = + GetRouterInterfaceEntry(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id)); + ASSERT_NE(current_entry, nullptr); + // Update port name not supported, hence ProcessUpdateRequest should fail. + EXPECT_EQ(StatusCode::SWSS_RC_UNIMPLEMENTED, ProcessUpdateRequest(app_db_entry, current_entry)); + + // Validate that router interface entry present in the Manager does not + // changed. + ValidateRouterInterfaceEntry(router_intf_entry); +} + +TEST_F(RouterInterfaceManagerTest, ProcessDeleteRequestExistingInterface) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(Eq(router_intf_entry.router_interface_oid))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, + ProcessDeleteRequest(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id))); + + ValidateRouterInterfaceEntryNotPresent(router_intf_entry.router_interface_id); +} + +TEST_F(RouterInterfaceManagerTest, ProcessDeleteRequestNonExistingInterface) +{ + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, + ProcessDeleteRequest(KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1))); +} + +TEST_F(RouterInterfaceManagerTest, ProcessDeleteRequestInterfaceNotExistInMapper) +{ + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + AddRouterInterfaceEntry(router_intf_entry, kPortOid1, kMtu1); + + p4_oid_mapper_.eraseOID(SAI_OBJECT_TYPE_ROUTER_INTERFACE, + KeyGenerator::generateRouterInterfaceKey(kRouterInterfaceId1)); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_INTERNAL, + ProcessDeleteRequest(KeyGenerator::generateRouterInterfaceKey(router_intf_entry.router_interface_id))); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryValidAttributes) +{ + const std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_port_and_src_mac"), + swss::FieldValueTuple(prependParamField(p4orch::kPort), kPortName1), + swss::FieldValueTuple(prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()), + }; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); + EXPECT_EQ(app_db_entry.port_name, kPortName1); + EXPECT_EQ(app_db_entry.src_mac_address, kMacAddress1); + EXPECT_TRUE(app_db_entry.is_set_port_name); + EXPECT_TRUE(app_db_entry.is_set_src_mac); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryInvalidKeyFormat) +{ + const std::vector attributes = { + swss::FieldValueTuple(p4orch::kAction, "set_port_and_src_mac"), + swss::FieldValueTuple(prependParamField(p4orch::kPort), kPortName1), + swss::FieldValueTuple(prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()), + }; + + // Invalid json format. + std::string invalid_key = R"({"match/router_interface_id:intf-3/4"})"; + auto app_db_entry_or = DeserializeRouterIntfEntry(invalid_key, attributes); + EXPECT_FALSE(app_db_entry_or.ok()); + + // Invalid json format. + invalid_key = R"([{"match/router_interface_id":"intf-3/4"}])"; + app_db_entry_or = DeserializeRouterIntfEntry(invalid_key, attributes); + EXPECT_FALSE(app_db_entry_or.ok()); + + // Invalid json format. + invalid_key = R"(["match/router_interface_id","intf-3/4"])"; + app_db_entry_or = DeserializeRouterIntfEntry(invalid_key, attributes); + EXPECT_FALSE(app_db_entry_or.ok()); + + // Invalid field name. + invalid_key = R"({"router_interface_id":"intf-3/4"})"; + app_db_entry_or = DeserializeRouterIntfEntry(invalid_key, attributes); + EXPECT_FALSE(app_db_entry_or.ok()); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryMissingAction) +{ + const std::vector attributes = { + swss::FieldValueTuple(prependParamField(p4orch::kPort), kPortName1), + swss::FieldValueTuple(prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()), + }; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); + EXPECT_EQ(app_db_entry.port_name, kPortName1); + EXPECT_EQ(app_db_entry.src_mac_address, kMacAddress1); + EXPECT_TRUE(app_db_entry.is_set_port_name); + EXPECT_TRUE(app_db_entry.is_set_src_mac); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryOnlyPortNameAttribute) +{ + const std::vector attributes = { + swss::FieldValueTuple(prependParamField(p4orch::kPort), kPortName1)}; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); + EXPECT_EQ(app_db_entry.port_name, kPortName1); + EXPECT_EQ(app_db_entry.src_mac_address, kZeroMacAddress); + EXPECT_TRUE(app_db_entry.is_set_port_name); + EXPECT_FALSE(app_db_entry.is_set_src_mac); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryOnlyMacAddrAttribute) +{ + const std::vector attributes = { + swss::FieldValueTuple(prependParamField(p4orch::kSrcMac), kMacAddress1.to_string())}; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); + EXPECT_EQ(app_db_entry.port_name, ""); + EXPECT_EQ(app_db_entry.src_mac_address, kMacAddress1); + EXPECT_FALSE(app_db_entry.is_set_port_name); + EXPECT_TRUE(app_db_entry.is_set_src_mac); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryNoAttributes) +{ + const std::vector attributes; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_TRUE(app_db_entry_or.ok()); + auto &app_db_entry = *app_db_entry_or; + EXPECT_EQ(app_db_entry.router_interface_id, kRouterInterfaceId1); + EXPECT_EQ(app_db_entry.port_name, ""); + EXPECT_EQ(app_db_entry.src_mac_address, kZeroMacAddress); + EXPECT_FALSE(app_db_entry.is_set_port_name); + EXPECT_FALSE(app_db_entry.is_set_src_mac); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryInvalidField) +{ + const std::vector attributes = {swss::FieldValueTuple("invalid_field", "invalid_value")}; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_FALSE(app_db_entry_or.ok()); +} + +TEST_F(RouterInterfaceManagerTest, DeserializeRouterIntfEntryInvalidMacAddrValue) +{ + const std::vector attributes = { + swss::FieldValueTuple(prependParamField(p4orch::kSrcMac), "00:11:22:33:44")}; + + auto app_db_entry_or = DeserializeRouterIntfEntry(kRouterIntfAppDbKey, attributes); + EXPECT_FALSE(app_db_entry_or.ok()); +} + +TEST_F(RouterInterfaceManagerTest, DrainValidAttributes) +{ + const std::string appl_db_key = + std::string(APP_P4RT_ROUTER_INTERFACE_TABLE_NAME) + kTableKeyDelimiter + std::string(kRouterIntfAppDbKey); + + // Enqueue entry for create operation. + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), kPortName1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_router_intf_, create_router_interface(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kRouterInterfaceOid1), Return(SAI_STATUS_SUCCESS))); + Drain(); + + P4RouterInterfaceEntry router_intf_entry(kRouterInterfaceId1, kPortName1, kMacAddress1); + router_intf_entry.router_interface_oid = kRouterInterfaceOid1; + ValidateRouterInterfaceEntry(router_intf_entry); + + // Enqueue entry for update operation. + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kMacAddress2.to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_router_intf_, set_router_interface_attribute(_, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + + router_intf_entry.src_mac_address = kMacAddress2; + ValidateRouterInterfaceEntry(router_intf_entry); + + // Enqueue entry for delete operation. + attributes.clear(); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, DEL_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_router_intf_, remove_router_interface(_)).WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + + ValidateRouterInterfaceEntryNotPresent(router_intf_entry.router_interface_id); +} + +TEST_F(RouterInterfaceManagerTest, DrainInvalidAppDbEntryKey) +{ + // Create invalid json key with router interface id as kRouterInterfaceId1. + const std::string invalid_router_intf_key = R"({"match/router_interface_id:intf-3/4"})"; + const std::string appl_db_key = + std::string(APP_P4RT_ROUTER_INTERFACE_TABLE_NAME) + kTableKeyDelimiter + invalid_router_intf_key; + + // Enqueue entry for create operation. + std::vector attributes; + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + Drain(); + + ValidateRouterInterfaceEntryNotPresent(kRouterInterfaceId1); +} + +TEST_F(RouterInterfaceManagerTest, DrainInvalidAppDbEntryAttributes) +{ + const std::string appl_db_key = + std::string(APP_P4RT_ROUTER_INTERFACE_TABLE_NAME) + kTableKeyDelimiter + std::string(kRouterIntfAppDbKey); + + // Invalid port attribute. + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), "xyz"}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + // Zero mac address attribute. + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), kPortName1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kZeroMacAddress.to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, SET_COMMAND, attributes)); + + Drain(); + ValidateRouterInterfaceEntryNotPresent(kRouterInterfaceId1); +} + +TEST_F(RouterInterfaceManagerTest, DrainInvalidOperation) +{ + const std::string appl_db_key = + std::string(APP_P4RT_ROUTER_INTERFACE_TABLE_NAME) + kTableKeyDelimiter + std::string(kRouterIntfAppDbKey); + + // Enqueue entry for invalid operation. + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kPort), kPortName1}); + attributes.push_back(swss::FieldValueTuple{prependParamField(p4orch::kSrcMac), kMacAddress1.to_string()}); + Enqueue(swss::KeyOpFieldsValuesTuple(appl_db_key, "INVALID", attributes)); + Drain(); + + ValidateRouterInterfaceEntryNotPresent(kRouterInterfaceId1); +} diff --git a/orchagent/p4orch/tests/test_main.cpp b/orchagent/p4orch/tests/test_main.cpp new file mode 100644 index 000000000000..23cf37d8e130 --- /dev/null +++ b/orchagent/p4orch/tests/test_main.cpp @@ -0,0 +1,201 @@ +extern "C" +{ +#include "sai.h" +} + +#include + +#include + +#include "copporch.h" +#include "crmorch.h" +#include "dbconnector.h" +#include "directory.h" +#include "mock_sai_virtual_router.h" +#include "p4orch.h" +#include "portsorch.h" +#include "sai_serialize.h" +#include "switchorch.h" +#include "vrforch.h" +#include "gtest/gtest.h" + +using ::testing::StrictMock; + +/* Global variables */ +sai_object_id_t gVirtualRouterId = SAI_NULL_OBJECT_ID; +sai_object_id_t gSwitchId = SAI_NULL_OBJECT_ID; +sai_object_id_t gVrfOid = 111; +sai_object_id_t gTrapGroupStartOid = 20; +sai_object_id_t gHostifStartOid = 30; +sai_object_id_t gUserDefinedTrapStartOid = 40; +char *gVrfName = "b4-traffic"; +char *gMirrorSession1 = "mirror-session-1"; +sai_object_id_t kMirrorSessionOid1 = 9001; +char *gMirrorSession2 = "mirror-session-2"; +sai_object_id_t kMirrorSessionOid2 = 9002; +sai_object_id_t gUnderlayIfId; + +#define DEFAULT_BATCH_SIZE 128 +int gBatchSize = DEFAULT_BATCH_SIZE; +bool gSairedisRecord = true; +bool gSwssRecord = true; +bool gLogRotate = false; +bool gSaiRedisLogRotate = false; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +bool gSyncMode = false; +bool gIsNatSupported = false; + +PortsOrch *gPortsOrch; +CrmOrch *gCrmOrch; +P4Orch *gP4Orch; +VRFOrch *gVrfOrch; +SwitchOrch *gSwitchOrch; +Directory gDirectory; +ofstream gRecordOfs; +string gRecordFile; +swss::DBConnector *gAppDb; +swss::DBConnector *gStateDb; +swss::DBConnector *gConfigDb; +swss::DBConnector *gCountersDb; +MacAddress gVxlanMacAddress; + +sai_router_interface_api_t *sai_router_intfs_api; +sai_neighbor_api_t *sai_neighbor_api; +sai_next_hop_api_t *sai_next_hop_api; +sai_next_hop_group_api_t *sai_next_hop_group_api; +sai_route_api_t *sai_route_api; +sai_acl_api_t *sai_acl_api; +sai_policer_api_t *sai_policer_api; +sai_virtual_router_api_t *sai_virtual_router_api; +sai_hostif_api_t *sai_hostif_api; +sai_switch_api_t *sai_switch_api; +sai_mirror_api_t *sai_mirror_api; +sai_udf_api_t *sai_udf_api; +sai_tunnel_api_t *sai_tunnel_api; + +namespace +{ + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; + +void CreatePort(const std::string port_name, const uint32_t speed, const uint32_t mtu, const sai_object_id_t port_oid, + Port::Type port_type = Port::PHY, const sai_port_oper_status_t oper_status = SAI_PORT_OPER_STATUS_DOWN, + const sai_object_id_t vrouter_id = gVirtualRouterId, const bool admin_state_up = true) +{ + Port port(port_name, port_type); + port.m_speed = speed; + port.m_mtu = mtu; + if (port_type == Port::LAG) + { + port.m_lag_id = port_oid; + } + else + { + port.m_port_id = port_oid; + } + port.m_vr_id = vrouter_id; + port.m_admin_state_up = admin_state_up; + port.m_oper_status = oper_status; + + gPortsOrch->setPort(port_name, port); +} + +void SetupPorts() +{ + CreatePort(/*port_name=*/"Ethernet1", /*speed=*/100000, + /*mtu=*/1500, /*port_oid=*/0x112233); + CreatePort(/*port_name=*/"Ethernet2", /*speed=*/400000, + /*mtu=*/4500, /*port_oid=*/0x1fed3); + CreatePort(/*port_name=*/"Ethernet3", /*speed=*/50000, + /*mtu=*/9100, /*port_oid=*/0xaabbccdd); + CreatePort(/*port_name=*/"Ethernet4", /*speed=*/100000, + /*mtu=*/1500, /*port_oid=*/0x9988); + CreatePort(/*port_name=*/"Ethernet5", /*speed=*/400000, + /*mtu=*/4500, /*port_oid=*/0x56789abcdef); + CreatePort(/*port_name=*/"Ethernet6", /*speed=*/50000, + /*mtu=*/9100, /*port_oid=*/0x56789abcdff, Port::PHY, SAI_PORT_OPER_STATUS_UP); + CreatePort(/*port_name=*/"Ethernet7", /*speed=*/100000, + /*mtu=*/9100, /*port_oid=*/0x1234, /*port_type*/ Port::LAG); + CreatePort(/*port_name=*/"Ethernet8", /*speed=*/100000, + /*mtu=*/9100, /*port_oid=*/0x5678, /*port_type*/ Port::MGMT); + CreatePort(/*port_name=*/"Ethernet9", /*speed=*/50000, + /*mtu=*/9100, /*port_oid=*/0x56789abcfff, Port::PHY, SAI_PORT_OPER_STATUS_UNKNOWN); +} + +void AddVrf() +{ + Table app_vrf_table(gAppDb, APP_VRF_TABLE_NAME); + std::vector attributes; + app_vrf_table.set(gVrfName, attributes); + + StrictMock mock_sai_virtual_router_; + mock_sai_virtual_router = &mock_sai_virtual_router_; + sai_virtual_router_api->create_virtual_router = create_virtual_router; + sai_virtual_router_api->remove_virtual_router = remove_virtual_router; + sai_virtual_router_api->set_virtual_router_attribute = set_virtual_router_attribute; + EXPECT_CALL(mock_sai_virtual_router_, create_virtual_router(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(gVrfOid), Return(SAI_STATUS_SUCCESS))); + gVrfOrch->addExistingData(&app_vrf_table); + static_cast(gVrfOrch)->doTask(); +} + +} // namespace + +int main(int argc, char *argv[]) +{ + testing::InitGoogleTest(&argc, argv); + + sai_router_interface_api_t router_intfs_api; + sai_neighbor_api_t neighbor_api; + sai_next_hop_api_t next_hop_api; + sai_next_hop_group_api_t next_hop_group_api; + sai_route_api_t route_api; + sai_acl_api_t acl_api; + sai_policer_api_t policer_api; + sai_virtual_router_api_t virtual_router_api; + sai_hostif_api_t hostif_api; + sai_switch_api_t switch_api; + sai_mirror_api_t mirror_api; + sai_udf_api_t udf_api; + sai_router_intfs_api = &router_intfs_api; + sai_neighbor_api = &neighbor_api; + sai_next_hop_api = &next_hop_api; + sai_next_hop_group_api = &next_hop_group_api; + sai_route_api = &route_api; + sai_acl_api = &acl_api; + sai_policer_api = &policer_api; + sai_virtual_router_api = &virtual_router_api; + sai_hostif_api = &hostif_api; + sai_switch_api = &switch_api; + sai_mirror_api = &mirror_api; + sai_udf_api = &udf_api; + + swss::DBConnector appl_db("APPL_DB", 0); + swss::DBConnector state_db("STATE_DB", 0); + swss::DBConnector config_db("CONFIG_DB", 0); + swss::DBConnector counters_db("COUNTERS_DB", 0); + gAppDb = &appl_db; + gStateDb = &state_db; + gConfigDb = &config_db; + gCountersDb = &counters_db; + std::vector ports_tables; + PortsOrch ports_orch(gAppDb, gStateDb, ports_tables, gAppDb); + gPortsOrch = &ports_orch; + CrmOrch crm_orch(gConfigDb, CFG_CRM_TABLE_NAME); + + gCrmOrch = &crm_orch; + VRFOrch vrf_orch(gAppDb, APP_VRF_TABLE_NAME, gStateDb, STATE_VRF_OBJECT_TABLE_NAME); + gVrfOrch = &vrf_orch; + gDirectory.set(static_cast(&vrf_orch)); + + // Setup ports for all tests. + SetupPorts(); + AddVrf(); + + return RUN_ALL_TESTS(); +} diff --git a/orchagent/p4orch/tests/wcmp_manager_test.cpp b/orchagent/p4orch/tests/wcmp_manager_test.cpp new file mode 100644 index 000000000000..73cf34be25fb --- /dev/null +++ b/orchagent/p4orch/tests/wcmp_manager_test.cpp @@ -0,0 +1,1852 @@ +#include "wcmp_manager.h" + +#include +#include + +#include + +#include "json.hpp" +#include "mock_response_publisher.h" +#include "mock_sai_acl.h" +#include "mock_sai_hostif.h" +#include "mock_sai_next_hop_group.h" +#include "mock_sai_serialize.h" +#include "mock_sai_switch.h" +#include "mock_sai_udf.h" +#include "p4oidmapper.h" +#include "p4orch.h" +#include "p4orch/p4orch_util.h" +#include "p4orch_util.h" +#include "return_code.h" +#include "sai_serialize.h" +extern "C" +{ +#include "sai.h" +} + +extern P4Orch *gP4Orch; +extern VRFOrch *gVrfOrch; +extern swss::DBConnector *gAppDb; +extern sai_object_id_t gSwitchId; +extern sai_next_hop_group_api_t *sai_next_hop_group_api; +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_object_id_t gSwitchId; +extern sai_acl_api_t *sai_acl_api; + +namespace p4orch +{ +namespace test +{ + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::Truly; + +namespace +{ + +constexpr char *kWcmpGroupId1 = "group-1"; +constexpr sai_object_id_t kWcmpGroupOid1 = 10; +constexpr char *kNexthopId1 = "ju1u32m1.atl11:qe-3/7"; +constexpr sai_object_id_t kNexthopOid1 = 1; +constexpr sai_object_id_t kWcmpGroupMemberOid1 = 11; +constexpr char *kNexthopId2 = "ju1u32m2.atl11:qe-3/7"; +constexpr sai_object_id_t kNexthopOid2 = 2; +constexpr sai_object_id_t kWcmpGroupMemberOid2 = 12; +constexpr char *kNexthopId3 = "ju1u32m3.atl11:qe-3/7"; +constexpr sai_object_id_t kNexthopOid3 = 3; +constexpr sai_object_id_t kWcmpGroupMemberOid3 = 13; +constexpr sai_object_id_t kWcmpGroupMemberOid4 = 14; +constexpr sai_object_id_t kWcmpGroupMemberOid5 = 15; +const std::string kWcmpGroupKey1 = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); +const std::string kNexthopKey1 = KeyGenerator::generateNextHopKey(kNexthopId1); +const std::string kNexthopKey2 = KeyGenerator::generateNextHopKey(kNexthopId2); +const std::string kNexthopKey3 = KeyGenerator::generateNextHopKey(kNexthopId3); +constexpr sai_object_id_t kUdfMatchOid1 = 5001; + +// Matches the next hop group type sai_attribute_t argument. +bool MatchSaiNextHopGroupAttribute(const sai_attribute_t *attr) +{ + if (attr == nullptr || attr->id != SAI_NEXT_HOP_GROUP_ATTR_TYPE || attr->value.s32 != SAI_NEXT_HOP_GROUP_TYPE_ECMP) + { + return false; + } + return true; +} + +// Matches the action type sai_attribute_t argument. +bool MatchSaiNextHopGroupMemberAttribute(const sai_object_id_t expected_next_hop_oid, const int expected_weight, + const sai_object_id_t expected_wcmp_group_oid, + const sai_attribute_t *attr_list) +{ + if (attr_list == nullptr) + { + return false; + } + for (int i = 0; i < 3; ++i) + { + switch (attr_list[i].id) + { + case SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID: + if (attr_list[i].value.oid != expected_wcmp_group_oid) + { + return false; + } + break; + case SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID: + if (attr_list[i].value.oid != expected_next_hop_oid) + { + return false; + } + break; + case SAI_NEXT_HOP_GROUP_MEMBER_ATTR_WEIGHT: + if (attr_list[i].value.u32 != (uint32_t)expected_weight) + { + return false; + } + break; + default: + break; + } + } + return true; +} + +void VerifyWcmpGroupMemberEntry(const std::string &expected_next_hop_id, const int expected_weight, + std::shared_ptr wcmp_gm_entry) +{ + EXPECT_EQ(expected_next_hop_id, wcmp_gm_entry->next_hop_id); + EXPECT_EQ(expected_weight, (int)wcmp_gm_entry->weight); +} + +void VerifyWcmpGroupEntry(const P4WcmpGroupEntry &expect_entry, const P4WcmpGroupEntry &wcmp_entry) +{ + EXPECT_EQ(expect_entry.wcmp_group_id, wcmp_entry.wcmp_group_id); + ASSERT_EQ(expect_entry.wcmp_group_members.size(), wcmp_entry.wcmp_group_members.size()); + for (size_t i = 0; i < expect_entry.wcmp_group_members.size(); i++) + { + ASSERT_LE(i, wcmp_entry.wcmp_group_members.size()); + auto gm = expect_entry.wcmp_group_members[i]; + VerifyWcmpGroupMemberEntry(gm->next_hop_id, gm->weight, wcmp_entry.wcmp_group_members[i]); + } +} +} // namespace + +class WcmpManagerTest : public ::testing::Test +{ + protected: + WcmpManagerTest() + { + setUpMockApi(); + setUpP4Orch(); + wcmp_group_manager_ = gP4Orch->getWcmpManager(); + p4_oid_mapper_ = wcmp_group_manager_->m_p4OidMapper; + } + + ~WcmpManagerTest() + { + EXPECT_CALL(mock_sai_switch_, set_switch_attribute(Eq(gSwitchId), _)) + .WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_acl_, remove_acl_table_group(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_udf_, remove_udf_match(_)).WillRepeatedly(Return(SAI_STATUS_SUCCESS)); + delete gP4Orch; + delete copp_orch_; + } + + void setUpMockApi() + { + // Set up mock stuff for SAI next hop group API structure. + mock_sai_next_hop_group = &mock_sai_next_hop_group_; + mock_sai_switch = &mock_sai_switch_; + mock_sai_hostif = &mock_sai_hostif_; + mock_sai_serialize = &mock_sai_serialize_; + mock_sai_acl = &mock_sai_acl_; + mock_sai_udf = &mock_sai_udf_; + + sai_next_hop_group_api->create_next_hop_group = create_next_hop_group; + sai_next_hop_group_api->remove_next_hop_group = remove_next_hop_group; + sai_next_hop_group_api->create_next_hop_group_member = create_next_hop_group_member; + sai_next_hop_group_api->remove_next_hop_group_member = remove_next_hop_group_member; + sai_next_hop_group_api->set_next_hop_group_member_attribute = set_next_hop_group_member_attribute; + + sai_hostif_api->create_hostif_table_entry = mock_create_hostif_table_entry; + sai_hostif_api->create_hostif_trap = mock_create_hostif_trap; + sai_switch_api->get_switch_attribute = mock_get_switch_attribute; + sai_switch_api->set_switch_attribute = mock_set_switch_attribute; + sai_acl_api->create_acl_table_group = create_acl_table_group; + sai_acl_api->remove_acl_table_group = remove_acl_table_group; + sai_udf_api->create_udf_match = create_udf_match; + sai_udf_api->remove_udf_match = remove_udf_match; + } + + void setUpP4Orch() + { + // init copp orch + 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(_, _, _)).WillOnce(Return(SAI_STATUS_SUCCESS)); + copp_orch_ = new CoppOrch(gAppDb, APP_COPP_TABLE_NAME); + + // init P4 orch + EXPECT_CALL(mock_sai_udf_, create_udf_match(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kUdfMatchOid1), Return(SAI_STATUS_SUCCESS))); + + std::vector p4_tables; + gP4Orch = new P4Orch(gAppDb, p4_tables, gVrfOrch, copp_orch_); + } + + void Enqueue(const swss::KeyOpFieldsValuesTuple &entry) + { + wcmp_group_manager_->enqueue(entry); + } + + void Drain() + { + wcmp_group_manager_->drain(); + } + + ReturnCode ProcessAddRequest(P4WcmpGroupEntry *app_db_entry) + { + return wcmp_group_manager_->processAddRequest(app_db_entry); + } + + void HandlePortStatusChangeNotification(const std::string &op, const std::string &data) + { + gP4Orch->handlePortStatusChangeNotification(op, data); + } + + void PruneNextHops(const std::string &port) + { + wcmp_group_manager_->pruneNextHops(port); + } + + void RestorePrunedNextHops(const std::string &port) + { + wcmp_group_manager_->restorePrunedNextHops(port); + } + + bool VerifyWcmpGroupMemberInPrunedSet(std::shared_ptr gm, bool expected_member_present, + long unsigned int expected_set_size) + { + if (wcmp_group_manager_->pruned_wcmp_members_set.size() != expected_set_size) + return false; + + return expected_member_present ? (wcmp_group_manager_->pruned_wcmp_members_set.find(gm) != + wcmp_group_manager_->pruned_wcmp_members_set.end()) + : (wcmp_group_manager_->pruned_wcmp_members_set.find(gm) == + wcmp_group_manager_->pruned_wcmp_members_set.end()); + } + + bool VerifyWcmpGroupMemberInPortMap(std::shared_ptr gm, bool expected_member_present, + long unsigned int expected_set_size) + { + auto it = wcmp_group_manager_->port_name_to_wcmp_group_member_map.find(gm->watch_port); + if (it != wcmp_group_manager_->port_name_to_wcmp_group_member_map.end()) + { + auto &s = wcmp_group_manager_->port_name_to_wcmp_group_member_map[gm->watch_port]; + if (s.size() != expected_set_size) + return false; + return expected_member_present ? (s.count(gm) > 0) : (s.count(gm) == 0); + } + else + { + return !expected_member_present; + } + return false; + } + + ReturnCode ProcessUpdateRequest(P4WcmpGroupEntry *app_db_entry) + { + return wcmp_group_manager_->processUpdateRequest(app_db_entry); + } + + ReturnCode RemoveWcmpGroup(const std::string &wcmp_group_id) + { + return wcmp_group_manager_->removeWcmpGroup(wcmp_group_id); + } + + P4WcmpGroupEntry *GetWcmpGroupEntry(const std::string &wcmp_group_id) + { + return wcmp_group_manager_->getWcmpGroupEntry(wcmp_group_id); + } + + ReturnCodeOr DeserializeP4WcmpGroupAppDbEntry( + const std::string &key, const std::vector &attributes) + { + return wcmp_group_manager_->deserializeP4WcmpGroupAppDbEntry(key, attributes); + } + + // Adds the WCMP group entry via WcmpManager::ProcessAddRequest(). This + // function also takes care of all the dependencies of the WCMP group entry. + // Returns a valid pointer to WCMP group entry on success. + P4WcmpGroupEntry AddWcmpGroupEntry1(); + P4WcmpGroupEntry AddWcmpGroupEntryWithWatchport(const std::string &port, const bool oper_up = false); + P4WcmpGroupEntry getDefaultWcmpGroupEntryForTest(); + std::shared_ptr createWcmpGroupMemberEntry(const std::string &next_hop_id, + const int weight); + std::shared_ptr createWcmpGroupMemberEntryWithWatchport(const std::string &next_hop_id, + const int weight, + const std::string &watch_port, + const std::string &wcmp_group_id, + const sai_object_id_t next_hop_oid); + + StrictMock mock_sai_next_hop_group_; + StrictMock mock_sai_switch_; + StrictMock mock_sai_hostif_; + StrictMock mock_sai_serialize_; + StrictMock mock_sai_acl_; + StrictMock mock_sai_udf_; + P4OidMapper *p4_oid_mapper_; + WcmpManager *wcmp_group_manager_; + CoppOrch *copp_orch_; +}; + +P4WcmpGroupEntry WcmpManagerTest::getDefaultWcmpGroupEntryForTest() +{ + P4WcmpGroupEntry app_db_entry; + app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr gm1 = std::make_shared(); + 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->next_hop_id = kNexthopId2; + gm2->weight = 1; + app_db_entry.wcmp_group_members.push_back(gm2); + return app_db_entry; +} + +P4WcmpGroupEntry WcmpManagerTest::AddWcmpGroupEntryWithWatchport(const std::string &port, const bool oper_up) +{ + P4WcmpGroupEntry app_db_entry; + app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr gm1 = std::make_shared(); + gm1->next_hop_id = kNexthopId1; + gm1->weight = 2; + gm1->watch_port = port; + gm1->wcmp_group_id = kWcmpGroupId1; + app_db_entry.wcmp_group_members.push_back(gm1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group(_, Eq(gSwitchId), Eq(1), + Truly(std::bind(MatchSaiNextHopGroupAttribute, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + // For members with non empty watchport field, member creation in SAI happens + // for operationally up ports only.. + if (oper_up) + { + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + } + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(&app_db_entry)); + EXPECT_NE(nullptr, GetWcmpGroupEntry(kWcmpGroupId1)); + return app_db_entry; +} + +P4WcmpGroupEntry WcmpManagerTest::AddWcmpGroupEntry1() +{ + P4WcmpGroupEntry app_db_entry = getDefaultWcmpGroupEntryForTest(); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, kNexthopOid3); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group(_, Eq(gSwitchId), Eq(1), + Truly(std::bind(MatchSaiNextHopGroupAttribute, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessAddRequest(&app_db_entry)); + EXPECT_NE(nullptr, GetWcmpGroupEntry(kWcmpGroupId1)); + return app_db_entry; +} + +// Create a WCMP group member with the requested attributes +std::shared_ptr WcmpManagerTest::createWcmpGroupMemberEntry(const std::string &next_hop_id, + const int weight) +{ + std::shared_ptr gm = std::make_shared(); + gm->next_hop_id = next_hop_id; + gm->weight = weight; + return gm; +} + +// Create a WCMP group member that uses a watchport with the requested +// attributes +std::shared_ptr WcmpManagerTest::createWcmpGroupMemberEntryWithWatchport( + const std::string &next_hop_id, const int weight, const std::string &watch_port, const std::string &wcmp_group_id, + const sai_object_id_t next_hop_oid) +{ + std::shared_ptr gm = std::make_shared(); + gm->next_hop_id = next_hop_id; + gm->weight = weight; + gm->watch_port = watch_port; + gm->wcmp_group_id = wcmp_group_id; + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, KeyGenerator::generateNextHopKey(next_hop_id), next_hop_oid); + return gm; +} + +TEST_F(WcmpManagerTest, CreateWcmpGroup) +{ + AddWcmpGroupEntry1(); + P4WcmpGroupEntry expect_entry = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm_entry1 = createWcmpGroupMemberEntry(kNexthopId1, 2); + expect_entry.wcmp_group_members.push_back(gm_entry1); + std::shared_ptr gm_entry2 = createWcmpGroupMemberEntry(kNexthopId2, 1); + expect_entry.wcmp_group_members.push_back(gm_entry2); + VerifyWcmpGroupEntry(expect_entry, *GetWcmpGroupEntry(kWcmpGroupId1)); +} + +TEST_F(WcmpManagerTest, CreateWcmpGroupFailsWhenCreateGroupMemberSaiCallFails) +{ + P4WcmpGroupEntry app_db_entry = getDefaultWcmpGroupEntryForTest(); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + // WCMP group creation fails when one of the group member creation fails + EXPECT_CALL(mock_sai_next_hop_group_, create_next_hop_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_ITEM_NOT_FOUND)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(&app_db_entry)); + std::string key = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_EQ(nullptr, wcmp_group_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(WcmpManagerTest, CreateWcmpGroupFailsWhenCreateGroupMemberSaiCallFailsPlusGroupMemberRecoveryFails) +{ + P4WcmpGroupEntry app_db_entry = getDefaultWcmpGroupEntryForTest(); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + // WCMP group creation fails when one of the group member creation fails + EXPECT_CALL(mock_sai_next_hop_group_, create_next_hop_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_ITEM_NOT_FOUND)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(&app_db_entry)); +} + +TEST_F(WcmpManagerTest, CreateWcmpGroupFailsWhenCreateGroupMemberSaiCallFailsPlusGroupRecoveryFails) +{ + P4WcmpGroupEntry app_db_entry = getDefaultWcmpGroupEntryForTest(); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + // WCMP group creation fails when one of the group member creation fails + EXPECT_CALL(mock_sai_next_hop_group_, create_next_hop_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_ITEM_NOT_FOUND)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(&app_db_entry)); +} + +TEST_F(WcmpManagerTest, CreateWcmpGroupFailsWhenCreateGroupSaiCallFails) +{ + P4WcmpGroupEntry app_db_entry = getDefaultWcmpGroupEntryForTest(); + app_db_entry.wcmp_group_members.pop_back(); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + // WCMP group creation fails when one of the group member creation fails + EXPECT_CALL(mock_sai_next_hop_group_, create_next_hop_group(_, _, _, _)).WillOnce(Return(SAI_STATUS_TABLE_FULL)); + + EXPECT_EQ(StatusCode::SWSS_RC_FULL, ProcessAddRequest(&app_db_entry)); + std::string key = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_EQ(nullptr, wcmp_group_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupFailsWhenRefcountIsGtThanZero) +{ + AddWcmpGroupEntry1(); + p4_oid_mapper_->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1)); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, RemoveWcmpGroup(kWcmpGroupId1)); + EXPECT_NE(nullptr, GetWcmpGroupEntry(kWcmpGroupId1)); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupFailsWhenNotExist) +{ + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, RemoveWcmpGroup(kWcmpGroupId1)); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupFailsWhenSaiCallFails) +{ + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntry1(); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, RemoveWcmpGroup(kWcmpGroupId1)); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupFailsWhenMemberRemovalFails) +{ + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntry1(); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, RemoveWcmpGroup(kWcmpGroupId1)); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupFailsWhenMemberRemovalFailsPlusRecoveryFails) +{ + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntry1(); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_FAILURE))); + // (TODO): Expect critical state. + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, RemoveWcmpGroup(kWcmpGroupId1)); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupMembersSucceed) +{ + AddWcmpGroupEntry1(); + // Update WCMP group member with nexthop_id=kNexthopId1 weight to 3, + // nexthop_id=kNexthopId2 weight to 15. + P4WcmpGroupEntry wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm1 = createWcmpGroupMemberEntry(kNexthopId1, 3); + std::shared_ptr gm2 = createWcmpGroupMemberEntry(kNexthopId2, 15); + wcmp_group.wcmp_group_members.push_back(gm1); + wcmp_group.wcmp_group_members.push_back(gm2); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 3, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 15, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_TRUE(ProcessUpdateRequest(&wcmp_group).ok()); + VerifyWcmpGroupEntry(wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + uint32_t wcmp_group_refcount = 0; + uint32_t nexthop_refcount = 0; + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(2, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + // Remove group member with nexthop_id=kNexthopId1 + wcmp_group.wcmp_group_members.clear(); + gm2 = createWcmpGroupMemberEntry(kNexthopId2, 15); + wcmp_group.wcmp_group_members.push_back(gm2); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid4))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid5))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 15, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_TRUE(ProcessUpdateRequest(&wcmp_group).ok()); + VerifyWcmpGroupEntry(wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(1, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + // Add group member with nexthop_id=kNexthopId1 and weight=20 + wcmp_group.wcmp_group_members.clear(); + std::shared_ptr updated_gm2 = createWcmpGroupMemberEntry(kNexthopId2, 15); + std::shared_ptr updated_gm1 = createWcmpGroupMemberEntry(kNexthopId1, 20); + wcmp_group.wcmp_group_members.push_back(updated_gm1); + wcmp_group.wcmp_group_members.push_back(updated_gm2); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 20, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 15, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_TRUE(ProcessUpdateRequest(&wcmp_group).ok()); + VerifyWcmpGroupEntry(wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(2, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + + // Update WCMP without group members + wcmp_group.wcmp_group_members.clear(); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid5))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_TRUE(ProcessUpdateRequest(&wcmp_group).ok()); + VerifyWcmpGroupEntry(wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(0, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupFailsWhenRemoveGroupMemberSaiCallFails) +{ + AddWcmpGroupEntry1(); + // Add WCMP group member with nexthop_id=kNexthopId1, weight=3 and + // nexthop_id=kNexthopId3, weight=30, update nexthop_id=kNexthopId2 + // weight to 10. + P4WcmpGroupEntry wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm1 = createWcmpGroupMemberEntry(kNexthopId1, 3); + std::shared_ptr gm2 = createWcmpGroupMemberEntry(kNexthopId2, 10); + std::shared_ptr gm3 = createWcmpGroupMemberEntry(kNexthopId3, 30); + + wcmp_group.wcmp_group_members.push_back(gm1); + wcmp_group.wcmp_group_members.push_back(gm2); + wcmp_group.wcmp_group_members.push_back(gm3); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 3, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid3, 30, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid3), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_TRUE(ProcessUpdateRequest(&wcmp_group).ok()); + VerifyWcmpGroupEntry(wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + uint32_t wcmp_group_refcount = 0; + uint32_t nexthop_refcount = 0; + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(3, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + // Remove WCMP group member with nexthop_id=kNexthopId1 and + // nexthop_id=kNexthopId3(fail) - succeed to clean up + wcmp_group.wcmp_group_members.clear(); + wcmp_group.wcmp_group_members.push_back(gm1); + wcmp_group.wcmp_group_members.push_back(gm3); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid5))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid3))) + .WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + // Clean up - revert deletions -success + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_IN_USE, ProcessUpdateRequest(&wcmp_group)); + P4WcmpGroupEntry expected_wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + expected_wcmp_group.wcmp_group_members.push_back(gm1); + expected_wcmp_group.wcmp_group_members.push_back(gm2); + expected_wcmp_group.wcmp_group_members.push_back(gm3); + // WCMP group remains as the old one + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(3, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + + // Remove WCMP group member with nexthop_id=kNexthopId1 and + // nexthop_id=kNexthopId3(fail) - fail to clean up + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid5))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid3))) + .WillOnce(Return(SAI_STATUS_OBJECT_IN_USE)); + // Clean up - revert deletions -failure + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_TABLE_FULL)); + // (TODO): Expect critical state. + EXPECT_EQ("Failed to remove WCMP group member with nexthop id " + "'ju1u32m3.atl11:qe-3/7'", + ProcessUpdateRequest(&wcmp_group).message()); + // WCMP group is as expected, but refcounts are not + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(2, wcmp_group_refcount); + // WCMP group is corrupt due to clean up failure + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupFailsWhenCreateNewGroupMemberSaiCallFails) +{ + AddWcmpGroupEntry1(); + P4WcmpGroupEntry wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + + // Remove group member with nexthop_id=kNexthopId1 + wcmp_group.wcmp_group_members.clear(); + std::shared_ptr gm = createWcmpGroupMemberEntry(kNexthopId2, 15); + wcmp_group.wcmp_group_members.push_back(gm); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 15, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_TRUE(ProcessUpdateRequest(&wcmp_group).ok()); + VerifyWcmpGroupEntry(wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + uint32_t wcmp_group_refcount = 0; + uint32_t nexthop_refcount = 0; + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(1, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + // Add WCMP group member with nexthop_id=kNexthopId1, weight=3 and + // nexthop_id=kNexthopId3, weight=30(fail), update nexthop_id=kNexthopId2 + // weight to 10. + P4WcmpGroupEntry updated_wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr updated_gm1 = createWcmpGroupMemberEntry(kNexthopId1, 3); + std::shared_ptr updated_gm2 = createWcmpGroupMemberEntry(kNexthopId2, 20); + std::shared_ptr updated_gm3 = createWcmpGroupMemberEntry(kNexthopId3, 30); + updated_wcmp_group.wcmp_group_members.push_back(updated_gm1); + updated_wcmp_group.wcmp_group_members.push_back(updated_gm2); + updated_wcmp_group.wcmp_group_members.push_back(updated_gm3); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid5))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 3, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 20, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid3, 30, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_TABLE_FULL)); + // Clean up - success + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 15, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid5), Return(SAI_STATUS_SUCCESS))); + EXPECT_FALSE(ProcessUpdateRequest(&updated_wcmp_group).ok()); + P4WcmpGroupEntry expected_wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr expected_gm = createWcmpGroupMemberEntry(kNexthopId2, 15); + expected_wcmp_group.wcmp_group_members.push_back(expected_gm); + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(1, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + + // Try again, but this time clean up failed to remove created group member + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid5))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 3, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 20, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid3, 30, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_TABLE_FULL)); + // Clean up - revert creation - failure + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 15, + kWcmpGroupOid1, std::placeholders::_1)))) + .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. + 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 + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(2, wcmp_group_refcount); // Corrupt status due to clean up failure + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); // Corrupt status due to clean up failure + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(2, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey3, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupFailsWhenReduceGroupMemberWeightSaiCallFails) +{ + AddWcmpGroupEntry1(); + P4WcmpGroupEntry wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + // Update WCMP group member to nexthop_id=kNexthopId1, weight=1(reduce) and + // nexthop_id=kNexthopId2, weight=10(increase), update nexthop_id=kNexthopId1 + // weight=1(fail). + std::shared_ptr gm1 = createWcmpGroupMemberEntry(kNexthopId1, 1); + std::shared_ptr gm2 = createWcmpGroupMemberEntry(kNexthopId2, 10); + wcmp_group.wcmp_group_members.push_back(gm1); + wcmp_group.wcmp_group_members.push_back(gm2); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_FALSE(ProcessUpdateRequest(&wcmp_group).ok()); + P4WcmpGroupEntry expected_wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr expected_gm1 = createWcmpGroupMemberEntry(kNexthopId1, 2); + std::shared_ptr expected_gm2 = createWcmpGroupMemberEntry(kNexthopId2, 1); + expected_wcmp_group.wcmp_group_members.push_back(expected_gm1); + expected_wcmp_group.wcmp_group_members.push_back(expected_gm2); + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + uint32_t wcmp_group_refcount = 0; + uint32_t nexthop_refcount = 0; + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(2, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupFailsWhenIncreaseGroupMemberWeightSaiCallFails) +{ + AddWcmpGroupEntry1(); + P4WcmpGroupEntry wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + // Update WCMP group member to nexthop_id=kNexthopId1, weight=1(reduce) and + // nexthop_id=kNexthopId2, weight=10(increase), update nexthop_id=kNexthopId2 + // weight=10(fail). + std::shared_ptr gm1 = createWcmpGroupMemberEntry(kNexthopId1, 1); + std::shared_ptr gm2 = createWcmpGroupMemberEntry(kNexthopId2, 10); + wcmp_group.wcmp_group_members.push_back(gm1); + wcmp_group.wcmp_group_members.push_back(gm2); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + // Clean up modified members - success + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid4))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_FALSE(ProcessUpdateRequest(&wcmp_group).ok()); + P4WcmpGroupEntry expected_wcmp_group = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr expected_gm1 = createWcmpGroupMemberEntry(kNexthopId1, 2); + std::shared_ptr expected_gm2 = createWcmpGroupMemberEntry(kNexthopId2, 1); + expected_wcmp_group.wcmp_group_members.push_back(expected_gm1); + expected_wcmp_group.wcmp_group_members.push_back(expected_gm2); + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + uint32_t wcmp_group_refcount = 0; + uint32_t nexthop_refcount = 0; + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(2, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); + // Try again, the same error happens when update and new error during clean up + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_NOT_SUPPORTED)); + // Clean up modified members - failure + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid4))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_NOT_SUPPORTED))); + + // (TODO): Expect critical state. + EXPECT_EQ("Failed to create next hop group member " + "'ju1u32m2.atl11:qe-3/7'", + ProcessUpdateRequest(&wcmp_group).message()); + // weight of wcmp_group_members[kNexthopId1] unable to revert + // SAI object in ASIC DB: missing group member with + // next_hop_id=kNexthopId1 + expected_gm1->weight = 2; + VerifyWcmpGroupEntry(expected_wcmp_group, *GetWcmpGroupEntry(kWcmpGroupId1)); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, kWcmpGroupKey1, &wcmp_group_refcount)); + EXPECT_EQ(1, wcmp_group_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &nexthop_refcount)); + EXPECT_EQ(0, nexthop_refcount); + ASSERT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &nexthop_refcount)); + EXPECT_EQ(1, nexthop_refcount); +} + +TEST_F(WcmpManagerTest, ValidateWcmpGroupEntryFailsWhenNextHopDoesNotExist) +{ + P4WcmpGroupEntry app_db_entry = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm = createWcmpGroupMemberEntry("Unregistered-Nexthop", 1); + app_db_entry.wcmp_group_members.push_back(gm); + EXPECT_EQ(StatusCode::SWSS_RC_NOT_FOUND, ProcessAddRequest(&app_db_entry)); +} + +TEST_F(WcmpManagerTest, ValidateWcmpGroupEntryFailsWhenWeightLessThanOne) +{ + P4WcmpGroupEntry app_db_entry = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm = createWcmpGroupMemberEntry(kNexthopId1, 0); + app_db_entry.wcmp_group_members.push_back(gm); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRequest(&app_db_entry)); +} + +TEST_F(WcmpManagerTest, WcmpGroupInvalidOperationInDrainFails) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_WCMP_GROUP_TABLE_NAME) + kTableKeyDelimiter; + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + nlohmann::json j; + j[prependMatchField(p4orch::kWcmpGroupId)] = kWcmpGroupId1; + std::vector attributes; + // If weight is omitted in the action, then it is set to 1 by default(ECMP) + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + action[prependParamField(p4orch::kNexthopId)] = kNexthopId2; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + + // Invalid Operation string. Only SET and DEL are allowed + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), "Update", attributes)); + Drain(); + std::string key = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_EQ(nullptr, wcmp_group_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(WcmpManagerTest, WcmpGroupUndefinedAttributesInDrainFails) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_WCMP_GROUP_TABLE_NAME) + kTableKeyDelimiter; + nlohmann::json j; + j[prependMatchField(p4orch::kWcmpGroupId)] = kWcmpGroupId1; + std::vector attributes; + attributes.push_back(swss::FieldValueTuple{"Undefined", "Invalid"}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + Drain(); + std::string key = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_EQ(nullptr, wcmp_group_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); +} + +TEST_F(WcmpManagerTest, WcmpGroupCreateAndDeleteInDrainSucceeds) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_WCMP_GROUP_TABLE_NAME) + kTableKeyDelimiter; + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + nlohmann::json j; + j[prependMatchField(p4orch::kWcmpGroupId)] = kWcmpGroupId1; + std::vector attributes; + // If weight is omitted in the action, then it is set to 1 by default(ECMP) + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + action[prependParamField(p4orch::kNexthopId)] = kNexthopId2; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + + EXPECT_CALL(mock_sai_next_hop_group_, create_next_hop_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + Drain(); + std::string key = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_NE(nullptr, wcmp_group_entry_ptr); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + attributes.clear(); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), DEL_COMMAND, attributes)); + Drain(); + wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_EQ(nullptr, wcmp_group_entry_ptr); + EXPECT_FALSE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); +} + +TEST_F(WcmpManagerTest, WcmpGroupCreateAndUpdateInDrainSucceeds) +{ + const std::string kKeyPrefix = std::string(APP_P4RT_WCMP_GROUP_TABLE_NAME) + kTableKeyDelimiter; + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, kNexthopOid1); + p4_oid_mapper_->setOID(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, kNexthopOid2); + nlohmann::json j; + j[prependMatchField(p4orch::kWcmpGroupId)] = kWcmpGroupId1; + std::vector attributes; + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[p4orch::kWeight] = 1; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + // Create WCMP group with member {next_hop_id=kNexthopId1, weight=1} + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + EXPECT_CALL(mock_sai_next_hop_group_, create_next_hop_group(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + Drain(); + std::string key = KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1); + auto *wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_NE(nullptr, wcmp_group_entry_ptr); + EXPECT_EQ(1, wcmp_group_entry_ptr->wcmp_group_members.size()); + VerifyWcmpGroupMemberEntry(kNexthopId1, 1, wcmp_group_entry_ptr->wcmp_group_members[0]); + EXPECT_EQ(kWcmpGroupMemberOid1, wcmp_group_entry_ptr->wcmp_group_members[0]->member_oid); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + uint32_t ref_cnt; + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update WCMP group with exact same members, the same entry will be removed + // and created again + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid3), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_NE(nullptr, wcmp_group_entry_ptr); + EXPECT_EQ(1, wcmp_group_entry_ptr->wcmp_group_members.size()); + VerifyWcmpGroupMemberEntry(kNexthopId1, 1, wcmp_group_entry_ptr->wcmp_group_members[0]); + EXPECT_EQ(kWcmpGroupMemberOid3, wcmp_group_entry_ptr->wcmp_group_members[0]->member_oid); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + + // Update WCMP group with member {next_hop_id=kNexthopId2, weight=1} + actions.clear(); + action[prependParamField(p4orch::kNexthopId)] = kNexthopId2; + actions.push_back(action); + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid3))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_NE(nullptr, wcmp_group_entry_ptr); + EXPECT_EQ(1, wcmp_group_entry_ptr->wcmp_group_members.size()); + VerifyWcmpGroupMemberEntry(kNexthopId2, 1, wcmp_group_entry_ptr->wcmp_group_members[0]); + EXPECT_EQ(kWcmpGroupMemberOid2, wcmp_group_entry_ptr->wcmp_group_members[0]->member_oid); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey1, &ref_cnt)); + EXPECT_EQ(0, ref_cnt); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); + // Update WCMP group with member {next_hop_id=kNexthopId2, weight=2} + actions.clear(); + action[p4orch::kWeight] = 2; + actions.push_back(action); + attributes.clear(); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + Enqueue(swss::KeyOpFieldsValuesTuple(kKeyPrefix + j.dump(), SET_COMMAND, attributes)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid2))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + Drain(); + wcmp_group_entry_ptr = GetWcmpGroupEntry(kWcmpGroupId1); + EXPECT_NE(nullptr, wcmp_group_entry_ptr); + EXPECT_EQ(1, wcmp_group_entry_ptr->wcmp_group_members.size()); + VerifyWcmpGroupMemberEntry(kNexthopId2, 2, wcmp_group_entry_ptr->wcmp_group_members[0]); + EXPECT_EQ(kWcmpGroupMemberOid4, wcmp_group_entry_ptr->wcmp_group_members[0]->member_oid); + EXPECT_TRUE(p4_oid_mapper_->existsOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, key)); + EXPECT_TRUE(p4_oid_mapper_->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP, kNexthopKey2, &ref_cnt)); + EXPECT_EQ(1, ref_cnt); +} + +TEST_F(WcmpManagerTest, DeserializeWcmpGroup) +{ + std::string key = R"({"match/wcmp_group_id":"group-a"})"; + std::vector attributes; + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[p4orch::kWeight] = 2; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + action[p4orch::kWeight] = 1; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId2; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + auto wcmp_group_entry_or = DeserializeP4WcmpGroupAppDbEntry(key, attributes); + EXPECT_TRUE(wcmp_group_entry_or.ok()); + auto &wcmp_group_entry = *wcmp_group_entry_or; + P4WcmpGroupEntry expect_entry = {}; + expect_entry.wcmp_group_id = "group-a"; + std::shared_ptr gm_entry1 = createWcmpGroupMemberEntry(kNexthopId1, 2); + expect_entry.wcmp_group_members.push_back(gm_entry1); + std::shared_ptr gm_entry2 = createWcmpGroupMemberEntry(kNexthopId2, 1); + expect_entry.wcmp_group_members.push_back(gm_entry2); + VerifyWcmpGroupEntry(expect_entry, wcmp_group_entry); +} + +TEST_F(WcmpManagerTest, DeserializeWcmpGroupDuplicateGroupMembers) +{ + std::string key = R"({"match/wcmp_group_id":"group-a"})"; + std::vector attributes; + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[p4orch::kWeight] = 1; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + action[prependParamField(p4orch::kNexthopId)] = kNexthopId2; + actions.push_back(action); + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + auto return_code_or = DeserializeP4WcmpGroupAppDbEntry(key, attributes); + EXPECT_TRUE(return_code_or.ok()); +} + +TEST_F(WcmpManagerTest, DeserializeWcmpGroupFailsWhenGroupKeyIsInvalidJson) +{ + std::vector attributes; + nlohmann::json actions; + nlohmann::json action; + action[p4orch::kAction] = p4orch::kSetNexthopId; + action[p4orch::kWeight] = 1; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + // Invalid JSON + std::string key = R"("match/wcmp_group_id":"group-a"})"; + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); + // Is string not JSON + key = R"("group-a")"; + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); +} + +TEST_F(WcmpManagerTest, DeserializeWcmpGroupFailsWhenActionsStringIsInvalid) +{ + std::string key = R"({"match/wcmp_group_id":"group-a"})"; + std::vector attributes; + // Actions field is an invalid JSON + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, "Undefied"}); + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); + + attributes.clear(); + nlohmann::json action; + action[p4orch::kAction] = kSetNexthopId; + action[p4orch::kWeight] = 1; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + // Actions field is not an array + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, action.dump()}); + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); + + attributes.clear(); + nlohmann::json actions; + action[p4orch::kAction] = "Undefined"; + actions.push_back(action); + // Actions field has undefiend action + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); + + attributes.clear(); + actions.clear(); + action.clear(); + action[p4orch::kAction] = kSetNexthopId; + action[p4orch::kWeight] = 1; + actions.push_back(action); + // Actions field has the group member without next_hop_id field + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); + attributes.clear(); + actions.clear(); + action[p4orch::kAction] = kSetNexthopId; + action[p4orch::kWeight] = 1; + action[prependParamField(p4orch::kNexthopId)] = kNexthopId1; + actions.push_back(action); + actions.push_back(action); + // Actions field has multiple group members have the same next_hop_id + attributes.push_back(swss::FieldValueTuple{p4orch::kActions, actions.dump()}); + EXPECT_TRUE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); +} + +TEST_F(WcmpManagerTest, DeserializeWcmpGroupFailsWithUndefinedAttributes) +{ + std::string key = R"({"match/wcmp_group_id":"group-a"})"; + std::vector attributes; + // Undefined field in attribute list + attributes.push_back(swss::FieldValueTuple{"Undefined", "Undefined"}); + EXPECT_FALSE(DeserializeP4WcmpGroupAppDbEntry(key, attributes).ok()); +} + +TEST_F(WcmpManagerTest, ValidateWcmpGroupEntryWithInvalidWatchportAttributeFails) +{ + P4WcmpGroupEntry app_db_entry = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm = + createWcmpGroupMemberEntryWithWatchport(kNexthopId1, 1, "EthernetXX", kWcmpGroupId1, kNexthopOid1); + app_db_entry.wcmp_group_members.push_back(gm); + EXPECT_EQ(StatusCode::SWSS_RC_INVALID_PARAM, ProcessAddRequest(&app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(gm, false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(gm, false, 0)); +} + +TEST_F(WcmpManagerTest, PruneNextHopSucceeds) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + // Prune next hops associated with port + PruneNextHops(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)); +} + +TEST_F(WcmpManagerTest, PruneNextHopFailsWithNextHopRemovalFailure) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + // Prune next hops associated with port (fails) + PruneNextHops(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, RestorePrunedNextHopSucceeds) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(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)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + + // Restore next hops associated with port + 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], false, 0)); +} + +TEST_F(WcmpManagerTest, RestorePrunedNextHopFailsWithNoOidMappingForWcmpGroup) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(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)); + p4_oid_mapper_->eraseOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, KeyGenerator::generateWcmpGroupKey(kWcmpGroupId1)); + // (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)); +} + +TEST_F(WcmpManagerTest, RestorePrunedNextHopFailsWithNextHopCreationFailure) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(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)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_FAILURE))); + // (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)); +} + +TEST_F(WcmpManagerTest, CreateGroupWithWatchportFailsWithNextHopCreationFailure) +{ + // Add member with operationally up watch port + // Create WCMP group with members kNexthopId1 and kNexthopId2 (fails) + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = {.wcmp_group_id = kWcmpGroupId1, .wcmp_group_members = {}}; + std::shared_ptr gm1 = + createWcmpGroupMemberEntryWithWatchport(kNexthopId1, 1, port_name, kWcmpGroupId1, kNexthopOid1); + app_db_entry.wcmp_group_members.push_back(gm1); + std::shared_ptr gm2 = + createWcmpGroupMemberEntryWithWatchport(kNexthopId2, 1, port_name, kWcmpGroupId1, kNexthopOid2); + app_db_entry.wcmp_group_members.push_back(gm2); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group(_, Eq(gSwitchId), Eq(1), + Truly(std::bind(MatchSaiNextHopGroupAttribute, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_FAILURE)); + // Clean up created members + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_UNKNOWN, ProcessAddRequest(&app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(gm1, false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(gm2, false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(gm1, false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(gm2, false, 0)); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupAfterPruningSucceeds) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + PruneNextHops(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)); + + // Remove Wcmp group. No SAI call for member removal is expected as it is + // already pruned. + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, RemoveWcmpGroup(kWcmpGroupId1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, RemoveWcmpGroupWithOperationallyDownWatchportSucceeds) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport("Ethernet1"); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); + + // Remove Wcmp group. No SAI call for member removal is expected as it is + // already pruned. + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, RemoveWcmpGroup(kWcmpGroupId1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupWithOperationallyUpWatchportMemberSucceeds) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + + // Update WCMP group to remove kNexthopId1 and add kNexthopId2 + P4WcmpGroupEntry updated_app_db_entry; + updated_app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr updated_gm = + createWcmpGroupMemberEntryWithWatchport(kNexthopId2, 1, port_name, kWcmpGroupId1, kNexthopOid2); + updated_app_db_entry.wcmp_group_members.push_back(updated_gm); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid2), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(&updated_app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_gm, true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_gm, false, 0)); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupWithOperationallyDownWatchportMemberSucceeds) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(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)); + + // Update WCMP group to remove kNexthopId1 and add kNexthopId2. No SAI calls + // are expected as the associated watch port is operationally down. + P4WcmpGroupEntry updated_app_db_entry; + updated_app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr updated_gm = + createWcmpGroupMemberEntryWithWatchport(kNexthopId2, 1, port_name, kWcmpGroupId1, kNexthopOid2); + updated_app_db_entry.wcmp_group_members.push_back(updated_gm); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(&updated_app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_gm, true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_gm, true, 1)); +} + +TEST_F(WcmpManagerTest, PruneAfterWcmpGroupUpdateSucceeds) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + + // Update WCMP group to modify weight of kNexthopId1. + P4WcmpGroupEntry updated_app_db_entry; + updated_app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr updated_gm = + createWcmpGroupMemberEntryWithWatchport(kNexthopId1, 10, port_name, kWcmpGroupId1, kNexthopOid1); + updated_app_db_entry.wcmp_group_members.push_back(updated_gm); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(&updated_app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_app_db_entry.wcmp_group_members[0], false, 0)); + + // Prune members associated with port. + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + PruneNextHops(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_app_db_entry.wcmp_group_members[0], true, 1)); + + // Remove Wcmp group. No SAI call for member removal is expected as it is + // already pruned. + // RemoveWcmpGroupWithOperationallyDownWatchportSucceeds verfies that SAI call + // for pruned member is not made on group removal. Hence, the member must be + // removed from SAI during prune. + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group(Eq(kWcmpGroupOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, RemoveWcmpGroup(kWcmpGroupId1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, PrunedMemberUpdateOnRestoreSucceeds) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(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)); + + // Update WCMP group to modify weight of kNexthopId1. + P4WcmpGroupEntry updated_app_db_entry; + updated_app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr updated_gm = + createWcmpGroupMemberEntryWithWatchport(kNexthopId1, 10, port_name, kWcmpGroupId1, kNexthopOid1); + updated_app_db_entry.wcmp_group_members.push_back(updated_gm); + EXPECT_EQ(StatusCode::SWSS_RC_SUCCESS, ProcessUpdateRequest(&updated_app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_app_db_entry.wcmp_group_members[0], true, 1)); + + // Restore members associated with port. + // Verify that the weight of the restored member is updated. + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + RestorePrunedNextHops(port_name); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, UpdateWcmpGroupWithOperationallyUpWatchportMemberFailsWithMemberRemovalFailure) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + + // Update WCMP group to remove kNexthopId1(fails) and add kNexthopId2 + P4WcmpGroupEntry updated_app_db_entry; + updated_app_db_entry.wcmp_group_id = kWcmpGroupId1; + std::shared_ptr updated_gm2 = + createWcmpGroupMemberEntryWithWatchport(kNexthopId2, 10, port_name, kWcmpGroupId1, kNexthopOid2); + std::shared_ptr updated_gm1 = + createWcmpGroupMemberEntryWithWatchport(kNexthopId1, 1, port_name, kWcmpGroupId1, kNexthopOid1); + updated_app_db_entry.wcmp_group_members.push_back(updated_gm1); + updated_app_db_entry.wcmp_group_members.push_back(updated_gm2); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_INSUFFICIENT_RESOURCES)); + // Clean up created member-succeeds + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid4))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_EQ(StatusCode::SWSS_RC_FULL, ProcessUpdateRequest(&updated_app_db_entry)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_gm2, false, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_gm2, false, 0)); + + // Update again, this time clean up fails + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 1, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid4), Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid2, 10, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(Return(SAI_STATUS_INSUFFICIENT_RESOURCES)); + // Clean up created member(fails) + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid4))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_INSUFFICIENT_RESOURCES))); + // (TODO): Expect critical state. + EXPECT_EQ("Failed to create next hop group member " + "'ju1u32m2.atl11:qe-3/7'", + ProcessUpdateRequest(&updated_app_db_entry).message()); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(updated_gm2, false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(updated_gm2, false, 0)); +} + +TEST_F(WcmpManagerTest, WatchportStateChangetoOperDownSucceeds) +{ + // Add member with operationally up watch port + std::string port_name = "Ethernet6"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(port_name, true); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); + + // Send port down signal + // Verify that the next hop member associated with the port is pruned. + std::string op = "port_state_change"; + std::string data = "[{\"port_id\":\"oid:0x56789abcdff\",\"port_state\":\"SAI_PORT_OPER_" + "STATUS_DOWN\"}]"; + EXPECT_CALL(mock_sai_next_hop_group_, remove_next_hop_group_member(Eq(kWcmpGroupMemberOid1))) + .WillOnce(Return(SAI_STATUS_SUCCESS)); + HandlePortStatusChangeNotification(op, data); + 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, WatchportStateChangeToOperUpSucceeds) +{ + // Add member with operationally down watch port. Since associated watchport + // is operationally down, member will not be created in SAI but will be + // directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(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)); + + // Send port up signal. + // Verify that the pruned next hop member associated with the port is + // restored. + std::string op = "port_state_change"; + std::string data = "[{\"port_id\":\"oid:0x112233\",\"port_state\":\"SAI_PORT_OPER_" + "STATUS_UP\"}]"; + EXPECT_CALL(mock_sai_next_hop_group_, + create_next_hop_group_member(_, Eq(gSwitchId), Eq(3), + Truly(std::bind(MatchSaiNextHopGroupMemberAttribute, kNexthopOid1, 2, + kWcmpGroupOid1, std::placeholders::_1)))) + .WillOnce(DoAll(SetArgPointee<0>(kWcmpGroupMemberOid1), Return(SAI_STATUS_SUCCESS))); + HandlePortStatusChangeNotification(op, data); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], false, 0)); +} + +TEST_F(WcmpManagerTest, WatchportStateChangeFromOperUnknownToDownPrunesMemberOnlyOnceSuceeds) +{ + // Add member with operationally unknown watch port. Since associated + // watchport is not operationally up, member will not be created in SAI but + // will be directly added to the pruned set of WCMP group members. + std::string port_name = "Ethernet1"; + P4WcmpGroupEntry app_db_entry = AddWcmpGroupEntryWithWatchport(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)); + + // Send port down signal. + // Verify that the pruned next hop member is not pruned again. + std::string op = "port_state_change"; + std::string data = "[{\"port_id\":\"oid:0x56789abcfff\",\"port_state\":\"SAI_PORT_OPER_" + "STATUS_DOWN\"}]"; + HandlePortStatusChangeNotification(op, data); + EXPECT_TRUE(VerifyWcmpGroupMemberInPortMap(app_db_entry.wcmp_group_members[0], true, 1)); + EXPECT_TRUE(VerifyWcmpGroupMemberInPrunedSet(app_db_entry.wcmp_group_members[0], true, 1)); +} +} // namespace test +} // namespace p4orch diff --git a/orchagent/p4orch/wcmp_manager.cpp b/orchagent/p4orch/wcmp_manager.cpp new file mode 100644 index 000000000000..6078f9222195 --- /dev/null +++ b/orchagent/p4orch/wcmp_manager.cpp @@ -0,0 +1,760 @@ +#include "p4orch/wcmp_manager.h" + +#include +#include +#include + +#include "crmorch.h" +#include "json.hpp" +#include "logger.h" +#include "p4orch/p4orch_util.h" +#include "portsorch.h" +#include "sai_serialize.h" +extern "C" +{ +#include "sai.h" +} + +extern sai_object_id_t gSwitchId; +extern sai_next_hop_group_api_t *sai_next_hop_group_api; +extern CrmOrch *gCrmOrch; +extern PortsOrch *gPortsOrch; + +namespace p4orch +{ + +namespace +{ + +std::string getWcmpGroupMemberKey(const std::string &wcmp_group_key, const sai_object_id_t wcmp_member_oid) +{ + return wcmp_group_key + kTableKeyDelimiter + sai_serialize_object_id(wcmp_member_oid); +} + +} // namespace + +ReturnCode WcmpManager::validateWcmpGroupEntry(const P4WcmpGroupEntry &app_db_entry) +{ + for (auto &wcmp_group_member : app_db_entry.wcmp_group_members) + { + if (wcmp_group_member->weight <= 0) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid WCMP group member weight " << wcmp_group_member->weight << ": should be greater than 0."; + } + sai_object_id_t nexthop_oid = SAI_NULL_OBJECT_ID; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP, + KeyGenerator::generateNextHopKey(wcmp_group_member->next_hop_id), &nexthop_oid)) + { + return ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "Nexthop id " << QuotedVar(wcmp_group_member->next_hop_id) << " does not exist for WCMP group " + << QuotedVar(app_db_entry.wcmp_group_id); + } + if (!wcmp_group_member->watch_port.empty()) + { + Port port; + if (!gPortsOrch->getPort(wcmp_group_member->watch_port, port)) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid watch_port field " << wcmp_group_member->watch_port + << ": should be a valid port name."; + } + } + } + return ReturnCode(); +} + +ReturnCodeOr WcmpManager::deserializeP4WcmpGroupAppDbEntry( + const std::string &key, const std::vector &attributes) +{ + P4WcmpGroupEntry app_db_entry = {}; + try + { + nlohmann::json j = nlohmann::json::parse(key); + if (!j.is_object()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Invalid WCMP group key: should be a JSON object."; + } + app_db_entry.wcmp_group_id = j[prependMatchField(kWcmpGroupId)]; + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) << "Failed to deserialize WCMP group key"; + } + + for (const auto &it : attributes) + { + const auto &field = fvField(it); + const auto &value = fvValue(it); + if (field == kActions) + { + try + { + nlohmann::json j = nlohmann::json::parse(value); + if (!j.is_array()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Invalid WCMP group actions " << QuotedVar(value) << ", expecting an array."; + } + for (auto &action_item : j) + { + std::shared_ptr wcmp_group_member = + std::make_shared(); + std::string action = action_item[kAction]; + if (action != kSetNexthopId) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected action " << QuotedVar(action) << " in WCMP group entry"; + } + if (action_item[prependParamField(kNexthopId)].empty()) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Next hop id was not found in entry member for WCMP " + "group " + << QuotedVar(app_db_entry.wcmp_group_id); + } + wcmp_group_member->next_hop_id = action_item[prependParamField(kNexthopId)]; + if (!action_item[kWeight].empty()) + { + wcmp_group_member->weight = action_item[kWeight]; + } + if (!action_item[kWatchPort].empty()) + { + wcmp_group_member->watch_port = action_item[kWatchPort]; + } + wcmp_group_member->wcmp_group_id = app_db_entry.wcmp_group_id; + app_db_entry.wcmp_group_members.push_back(wcmp_group_member); + } + } + catch (std::exception &ex) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Failed to deserialize WCMP group actions fields: " << QuotedVar(value); + } + } + else if (field != kControllerMetadata) + { + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unexpected field " << QuotedVar(field) << " in table entry"; + } + } + + return app_db_entry; +} + +P4WcmpGroupEntry *WcmpManager::getWcmpGroupEntry(const std::string &wcmp_group_id) +{ + SWSS_LOG_ENTER(); + const auto &wcmp_group_it = m_wcmpGroupTable.find(wcmp_group_id); + if (wcmp_group_it == m_wcmpGroupTable.end()) + return nullptr; + return &wcmp_group_it->second; +} + +ReturnCode WcmpManager::processAddRequest(P4WcmpGroupEntry *app_db_entry) +{ + SWSS_LOG_ENTER(); + auto status = validateWcmpGroupEntry(*app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Invalid WCMP group with id %s: %s", QuotedVar(app_db_entry->wcmp_group_id).c_str(), + status.message().c_str()); + return status; + } + status = createWcmpGroup(app_db_entry); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create WCMP group with id %s: %s", QuotedVar(app_db_entry->wcmp_group_id).c_str(), + status.message().c_str()); + } + return status; +} + +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); + + 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()), + "Failed to create next hop group member " << QuotedVar(wcmp_group_member->next_hop_id)); + + // Update reference count + const auto &next_hop_key = KeyGenerator::generateNextHopKey(wcmp_group_member->next_hop_id); + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER, + getWcmpGroupMemberKey(wcmp_group_key, wcmp_group_member->member_oid), + wcmp_group_member->member_oid); + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key); + m_p4OidMapper->increaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key); + + return ReturnCode(); +} + +void WcmpManager::insertMemberInPortNameToWcmpGroupMemberMap(std::shared_ptr member) +{ + port_name_to_wcmp_group_member_map[member->watch_port].insert(member); +} + +void WcmpManager::removeMemberFromPortNameToWcmpGroupMemberMap(std::shared_ptr member) +{ + if (port_name_to_wcmp_group_member_map.find(member->watch_port) != port_name_to_wcmp_group_member_map.end()) + { + auto &s = port_name_to_wcmp_group_member_map[member->watch_port]; + auto it = s.find(member); + if (it != s.end()) + { + s.erase(it); + } + } +} + +ReturnCode WcmpManager::fetchPortOperStatus(const std::string &port_name, sai_port_oper_status_t *oper_status) +{ + if (!getPortOperStatusFromMap(port_name, oper_status)) + { + // Get port object for associated watch port + Port port; + if (!gPortsOrch->getPort(port_name, port)) + { + SWSS_LOG_ERROR("Failed to get port object for port %s", port_name.c_str()); + return ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM); + } + // Get the oper-status of the port from hardware. In case of warm reboot, + // this ensures that actual state of the port oper-status is used to + // determine whether member associated with watch_port is to be created in + // SAI. + if (!gPortsOrch->getPortOperStatus(port, *oper_status)) + { + RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL("Failed to get port oper-status for port " << port.m_alias); + } + // Update port oper-status in local map + updatePortOperStatusMap(port.m_alias, *oper_status); + } + return ReturnCode(); +} + +ReturnCode WcmpManager::createWcmpGroupMemberWithWatchport(P4WcmpGroupEntry *wcmp_group, + std::shared_ptr member, + const std::string &wcmp_group_key) +{ + // Create member in SAI only for operationally up ports + sai_port_oper_status_t oper_status = SAI_PORT_OPER_STATUS_DOWN; + auto status = fetchPortOperStatus(member->watch_port, &oper_status); + if (!status.ok()) + { + return status; + } + + if (oper_status == SAI_PORT_OPER_STATUS_UP) + { + auto status = createWcmpGroupMember(member, wcmp_group->wcmp_group_oid, wcmp_group_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create next hop member %s with watch_port %s", member->next_hop_id.c_str(), + member->watch_port.c_str()); + return status; + } + } + else + { + pruned_wcmp_members_set.emplace(member); + SWSS_LOG_NOTICE("Member %s in group %s not created in asic as the associated watchport " + "(%s) is not operationally up", + member->next_hop_id.c_str(), member->wcmp_group_id.c_str(), member->watch_port.c_str()); + } + // Add member to port_name_to_wcmp_group_member_map + insertMemberInPortNameToWcmpGroupMemberMap(member); + return ReturnCode(); +} + +ReturnCode WcmpManager::processWcmpGroupMemberAddition(std::shared_ptr member, + P4WcmpGroupEntry *wcmp_group, const std::string &wcmp_group_key) +{ + ReturnCode status = ReturnCode(); + if (!member->watch_port.empty()) + { + status = createWcmpGroupMemberWithWatchport(wcmp_group, member, wcmp_group_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create WCMP group member %s with watch_port %s", member->next_hop_id.c_str(), + member->watch_port.c_str()); + } + } + else + { + status = createWcmpGroupMember(member, wcmp_group->wcmp_group_oid, wcmp_group_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to create WCMP group member %s", member->next_hop_id.c_str()); + } + } + return status; +} + +ReturnCode WcmpManager::processWcmpGroupMemberRemoval(std::shared_ptr member, + const std::string &wcmp_group_key) +{ + // If member exists in pruned_wcmp_members_set, remove from set. Else, remove + // member using SAI. + auto it = pruned_wcmp_members_set.find(member); + if (it != pruned_wcmp_members_set.end()) + { + pruned_wcmp_members_set.erase(it); + SWSS_LOG_NOTICE("Removed pruned member %s from group %s", member->next_hop_id.c_str(), + member->wcmp_group_id.c_str()); + } + else + { + auto status = removeWcmpGroupMember(member, wcmp_group_key); + if (!status.ok()) + { + return status; + } + } + // Remove member from port_name_to_wcmp_group_member_map + removeMemberFromPortNameToWcmpGroupMemberMap(member); + return ReturnCode(); +} + +ReturnCode WcmpManager::createWcmpGroup(P4WcmpGroupEntry *wcmp_group) +{ + SWSS_LOG_ENTER(); + // Create SAI next hop group + sai_attribute_t nhg_attr; + std::vector 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); + + 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()), + "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); + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP); + m_p4OidMapper->setOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key, wcmp_group->wcmp_group_oid); + + // Create next hop group members + std::vector> created_wcmp_group_members; + ReturnCode status; + for (auto &wcmp_group_member : wcmp_group->wcmp_group_members) + { + status = processWcmpGroupMemberAddition(wcmp_group_member, wcmp_group, wcmp_group_key); + if (!status.ok()) + { + break; + } + created_wcmp_group_members.push_back(wcmp_group_member); + } + if (!status.ok()) + { + // Clean up created group members and the group + recoverGroupMembers(wcmp_group, wcmp_group_key, created_wcmp_group_members, {}); + auto sai_status = sai_next_hop_group_api->remove_next_hop_group(wcmp_group->wcmp_group_oid); + if (sai_status != SAI_STATUS_SUCCESS) + { + std::stringstream ss; + ss << "Failed to delete WCMP group with id " << QuotedVar(wcmp_group->wcmp_group_id); + SWSS_LOG_ERROR("%s SAI_STATUS: %s", ss.str().c_str(), sai_serialize_status(sai_status).c_str()); + SWSS_RAISE_CRITICAL_STATE(ss.str()); + } + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key); + return status; + } + m_wcmpGroupTable[wcmp_group->wcmp_group_id] = *wcmp_group; + return ReturnCode(); +} + +void WcmpManager::recoverGroupMembers( + p4orch::P4WcmpGroupEntry *wcmp_group_entry, const std::string &wcmp_group_key, + const std::vector> &created_wcmp_group_members, + const std::vector> &removed_wcmp_group_members) +{ + // Keep track of recovery status during clean up + ReturnCode recovery_status; + // Clean up created group members - remove created new members + for (const auto &new_member : created_wcmp_group_members) + { + auto status = processWcmpGroupMemberRemoval(new_member, wcmp_group_key); + if (!status.ok()) + { + SWSS_LOG_ERROR("Failed to remove created next hop group member %s in " + "processUpdateRequest().", + QuotedVar(new_member->next_hop_id).c_str()); + recovery_status.ok() ? recovery_status = status.prepend("Error during recovery: ") + : recovery_status << "; Error during recovery: " << status.message(); + } + } + // Clean up removed group members - create removed old members + for (auto &old_member : removed_wcmp_group_members) + { + auto status = processWcmpGroupMemberAddition(old_member, wcmp_group_entry, wcmp_group_key); + if (!status.ok()) + { + recovery_status.ok() ? recovery_status = status.prepend("Error during recovery: ") + : recovery_status << "; Error during recovery: " << status.message(); + } + } + if (!recovery_status.ok()) + SWSS_RAISE_CRITICAL_STATE(recovery_status.message()); +} + +ReturnCode WcmpManager::processUpdateRequest(P4WcmpGroupEntry *wcmp_group_entry) +{ + SWSS_LOG_ENTER(); + auto *old_wcmp = getWcmpGroupEntry(wcmp_group_entry->wcmp_group_id); + wcmp_group_entry->wcmp_group_oid = old_wcmp->wcmp_group_oid; + const auto &wcmp_group_key = KeyGenerator::generateWcmpGroupKey(wcmp_group_entry->wcmp_group_id); + // Keep record of created next hop group members + std::vector> created_wcmp_group_members; + // Keep record of removed next hop group members + std::vector> removed_wcmp_group_members; + + // Update group members steps: + // 1. Find the old member in the list with the smallest weight + // 2. Find the new member in the list with the smallest weight + // 3. Make SAI calls to remove old members except the reserved member with the + // smallest weight + // 4. Make SAI call to create the new member with the smallest weight + // 5. Make SAI call to remove the reserved old member + // 6. Make SAI calls to create remaining new members + ReturnCode update_request_status; + auto find_smallest_index = [&](p4orch::P4WcmpGroupEntry *wcmp) { + if (wcmp->wcmp_group_members.empty()) + return -1; + int reserved_idx = 0; + for (int i = 1; i < (int)wcmp->wcmp_group_members.size(); i++) + { + if (wcmp->wcmp_group_members[i]->weight < wcmp->wcmp_group_members[reserved_idx]->weight) + { + reserved_idx = i; + } + } + return reserved_idx; + }; + // Find the old member who has the smallest weight, -1 if the member list is + // empty + int reserved_old_member_index = find_smallest_index(old_wcmp); + // Find the new member who has the smallest weight, -1 if the member list is + // empty + int reserved_new_member_index = find_smallest_index(wcmp_group_entry); + + // Remove stale group members except the member with the smallest weight + for (int i = 0; i < (int)old_wcmp->wcmp_group_members.size(); i++) + { + // Reserve the old member with smallest weight + if (i == reserved_old_member_index) + continue; + auto &stale_member = old_wcmp->wcmp_group_members[i]; + update_request_status = processWcmpGroupMemberRemoval(stale_member, wcmp_group_key); + if (!update_request_status.ok()) + { + SWSS_LOG_ERROR("Failed to remove stale next hop group member %s in " + "processUpdateRequest().", + QuotedVar(sai_serialize_object_id(stale_member->member_oid)).c_str()); + recoverGroupMembers(wcmp_group_entry, wcmp_group_key, created_wcmp_group_members, + removed_wcmp_group_members); + return update_request_status; + } + removed_wcmp_group_members.push_back(stale_member); + } + + // Create the new member with the smallest weight if member list is nonempty + if (!wcmp_group_entry->wcmp_group_members.empty()) + { + auto &member = wcmp_group_entry->wcmp_group_members[reserved_new_member_index]; + update_request_status = processWcmpGroupMemberAddition(member, wcmp_group_entry, wcmp_group_key); + if (!update_request_status.ok()) + { + recoverGroupMembers(wcmp_group_entry, wcmp_group_key, created_wcmp_group_members, + removed_wcmp_group_members); + return update_request_status; + } + created_wcmp_group_members.push_back(member); + } + + // Remove the old member with the smallest weight if member list is nonempty + if (!old_wcmp->wcmp_group_members.empty()) + { + auto &stale_member = old_wcmp->wcmp_group_members[reserved_old_member_index]; + update_request_status = processWcmpGroupMemberRemoval(stale_member, wcmp_group_key); + if (!update_request_status.ok()) + { + SWSS_LOG_ERROR("Failed to remove stale next hop group member %s in " + "processUpdateRequest().", + QuotedVar(sai_serialize_object_id(stale_member->member_oid)).c_str()); + recoverGroupMembers(wcmp_group_entry, wcmp_group_key, created_wcmp_group_members, + removed_wcmp_group_members); + return update_request_status; + } + removed_wcmp_group_members.push_back(stale_member); + } + + // Create new group members + for (int i = 0; i < (int)wcmp_group_entry->wcmp_group_members.size(); i++) + { + // Skip the new member with the lowest weight as it is already created + if (i == reserved_new_member_index) + continue; + auto &member = wcmp_group_entry->wcmp_group_members[i]; + // Create new group member + update_request_status = processWcmpGroupMemberAddition(member, wcmp_group_entry, wcmp_group_key); + if (!update_request_status.ok()) + { + recoverGroupMembers(wcmp_group_entry, wcmp_group_key, created_wcmp_group_members, + removed_wcmp_group_members); + return update_request_status; + } + created_wcmp_group_members.push_back(member); + } + + m_wcmpGroupTable[wcmp_group_entry->wcmp_group_id] = *wcmp_group_entry; + return update_request_status; +} + +ReturnCode WcmpManager::removeWcmpGroupMember(const std::shared_ptr wcmp_group_member, + const std::string &wcmp_group_key) +{ + SWSS_LOG_ENTER(); + const std::string &next_hop_key = KeyGenerator::generateNextHopKey(wcmp_group_member->next_hop_id); + + CHECK_ERROR_AND_LOG_AND_RETURN(sai_next_hop_group_api->remove_next_hop_group_member(wcmp_group_member->member_oid), + "Failed to remove WCMP group member with nexthop id " + << QuotedVar(wcmp_group_member->next_hop_id)); + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER, + getWcmpGroupMemberKey(wcmp_group_key, wcmp_group_member->member_oid)); + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_key); + m_p4OidMapper->decreaseRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key); + return ReturnCode(); +} + +ReturnCode WcmpManager::removeWcmpGroup(const std::string &wcmp_group_id) +{ + SWSS_LOG_ENTER(); + auto *wcmp_group = getWcmpGroupEntry(wcmp_group_id); + if (wcmp_group == nullptr) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_NOT_FOUND) + << "WCMP group with id " << QuotedVar(wcmp_group_id) << " was not found."); + } + // Check refcount before deleting group members + uint32_t expected_refcount = (uint32_t)wcmp_group->wcmp_group_members.size(); + uint32_t wcmp_group_refcount = 0; + const auto &wcmp_group_key = KeyGenerator::generateWcmpGroupKey(wcmp_group_id); + m_p4OidMapper->getRefCount(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key, &wcmp_group_refcount); + if (wcmp_group_refcount > expected_refcount) + { + LOG_ERROR_AND_RETURN(ReturnCode(StatusCode::SWSS_RC_IN_USE) + << "Failed to remove WCMP group with id " << QuotedVar(wcmp_group_id) << ", as it has " + << wcmp_group_refcount - expected_refcount << " more objects than its group members (size=" + << expected_refcount << ") referencing it."); + } + std::vector> removed_wcmp_group_members; + ReturnCode status; + // Delete group members + for (const auto &member : wcmp_group->wcmp_group_members) + { + status = processWcmpGroupMemberRemoval(member, wcmp_group_key); + if (!status.ok()) + { + break; + } + removed_wcmp_group_members.push_back(member); + } + // Delete group + if (status.ok()) + { + auto sai_status = sai_next_hop_group_api->remove_next_hop_group(wcmp_group->wcmp_group_oid); + if (sai_status == SAI_STATUS_SUCCESS) + { + m_p4OidMapper->eraseOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key); + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP); + m_wcmpGroupTable.erase(wcmp_group->wcmp_group_id); + return ReturnCode(); + } + status = ReturnCode(sai_status) << "Failed to delete WCMP group with id " + << QuotedVar(wcmp_group->wcmp_group_id); + SWSS_LOG_ERROR("%s SAI_STATUS: %s", status.message().c_str(), sai_serialize_status(sai_status).c_str()); + } + // Recover group members. + recoverGroupMembers(wcmp_group, wcmp_group_key, {}, removed_wcmp_group_members); + return status; +} + +void WcmpManager::pruneNextHops(const std::string &port) +{ + SWSS_LOG_ENTER(); + + // Get list of WCMP group members associated with the watch_port + if (port_name_to_wcmp_group_member_map.find(port) != port_name_to_wcmp_group_member_map.end()) + { + for (const auto &member : port_name_to_wcmp_group_member_map[port]) + { + auto it = pruned_wcmp_members_set.find(member); + // Prune a member if it is not already pruned. + if (it == pruned_wcmp_members_set.end()) + { + const auto &wcmp_group_key = KeyGenerator::generateWcmpGroupKey(member->wcmp_group_id); + auto status = removeWcmpGroupMember(member, wcmp_group_key); + if (!status.ok()) + { + SWSS_LOG_NOTICE("Failed to remove member %s from group %s, rv: %s", member->next_hop_id.c_str(), + member->wcmp_group_id.c_str(), status.message().c_str()); + } + else + { + // Add pruned member to pruned set + pruned_wcmp_members_set.emplace(member); + SWSS_LOG_NOTICE("Pruned member %s from group %s", member->next_hop_id.c_str(), + member->wcmp_group_id.c_str()); + } + } + } + } +} + +void WcmpManager::restorePrunedNextHops(const std::string &port) +{ + SWSS_LOG_ENTER(); + + // Get list of WCMP group members associated with the watch_port that were + // pruned + if (port_name_to_wcmp_group_member_map.find(port) != port_name_to_wcmp_group_member_map.end()) + { + ReturnCode status; + for (auto member : port_name_to_wcmp_group_member_map[port]) + { + auto it = pruned_wcmp_members_set.find(member); + if (it != pruned_wcmp_members_set.end()) + { + const auto &wcmp_group_key = KeyGenerator::generateWcmpGroupKey(member->wcmp_group_id); + sai_object_id_t wcmp_group_oid = SAI_NULL_OBJECT_ID; + if (!m_p4OidMapper->getOID(SAI_OBJECT_TYPE_NEXT_HOP_GROUP, wcmp_group_key, &wcmp_group_oid)) + { + status = ReturnCode(StatusCode::SWSS_RC_INTERNAL) + << "Error during restoring pruned next hop: Failed to get " + "WCMP group OID for group " + << member->wcmp_group_id; + SWSS_LOG_ERROR("%s", status.message().c_str()); + SWSS_RAISE_CRITICAL_STATE(status.message()); + return; + } + status = createWcmpGroupMember(member, wcmp_group_oid, wcmp_group_key); + if (!status.ok()) + { + status.prepend("Error during restoring pruned next hop: "); + SWSS_LOG_ERROR("%s", status.message().c_str()); + SWSS_RAISE_CRITICAL_STATE(status.message()); + return; + } + pruned_wcmp_members_set.erase(it); + SWSS_LOG_NOTICE("Restored pruned member %s in group %s", member->next_hop_id.c_str(), + member->wcmp_group_id.c_str()); + } + } + } +} + +bool WcmpManager::getPortOperStatusFromMap(const std::string &port, sai_port_oper_status_t *oper_status) +{ + if (port_oper_status_map.find(port) != port_oper_status_map.end()) + { + *oper_status = port_oper_status_map[port]; + return true; + } + return false; +} + +void WcmpManager::updatePortOperStatusMap(const std::string &port, const sai_port_oper_status_t &status) +{ + port_oper_status_map[port] = status; +} + +void WcmpManager::enqueue(const swss::KeyOpFieldsValuesTuple &entry) +{ + m_entries.push_back(entry); +} + +void WcmpManager::drain() +{ + SWSS_LOG_ENTER(); + + for (const auto &key_op_fvs_tuple : m_entries) + { + std::string table_name; + std::string db_key; + parseP4RTKey(kfvKey(key_op_fvs_tuple), &table_name, &db_key); + const std::vector &attributes = kfvFieldsValues(key_op_fvs_tuple); + + ReturnCode status; + auto app_db_entry_or = deserializeP4WcmpGroupAppDbEntry(db_key, attributes); + if (!app_db_entry_or.ok()) + { + status = app_db_entry_or.status(); + SWSS_LOG_ERROR("Unable to deserialize APP DB WCMP group entry with key %s: %s", + QuotedVar(table_name + ":" + db_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 &operation = kfvOp(key_op_fvs_tuple); + if (operation == SET_COMMAND) + { + auto *wcmp_group_entry = getWcmpGroupEntry(app_db_entry.wcmp_group_id); + if (wcmp_group_entry == nullptr) + { + // Create WCMP group + status = processAddRequest(&app_db_entry); + } + else + { + // Modify existing WCMP group + status = processUpdateRequest(&app_db_entry); + } + } + else if (operation == DEL_COMMAND) + { + // Delete WCMP group + status = removeWcmpGroup(app_db_entry.wcmp_group_id); + } + else + { + status = ReturnCode(StatusCode::SWSS_RC_INVALID_PARAM) + << "Unknown operation type: " << QuotedVar(operation) << " for WCMP group entry with key " + << QuotedVar(table_name) << ":" << QuotedVar(db_key) + << "; only SET and DEL operations are allowed."; + SWSS_LOG_ERROR("Unknown operation type %s\n", QuotedVar(operation).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(); +} + +} // namespace p4orch diff --git a/orchagent/p4orch/wcmp_manager.h b/orchagent/p4orch/wcmp_manager.h new file mode 100644 index 000000000000..4c6629a39817 --- /dev/null +++ b/orchagent/p4orch/wcmp_manager.h @@ -0,0 +1,178 @@ +#pragma once + +#include +#include +#include + +#include "notificationconsumer.h" +#include "orch.h" +#include "p4orch/object_manager_interface.h" +#include "p4orch/p4oidmapper.h" +#include "response_publisher_interface.h" +#include "return_code.h" +extern "C" +{ +#include "sai.h" +} + +namespace p4orch +{ +namespace test +{ +class WcmpManagerTest; +} // namespace test + +struct P4WcmpGroupMemberEntry +{ + std::string next_hop_id; + // Default ECMP(weight=1) + int weight = 1; + std::string watch_port; + sai_object_id_t member_oid = SAI_NULL_OBJECT_ID; + std::string wcmp_group_id; +}; + +struct P4WcmpGroupEntry +{ + std::string wcmp_group_id; + // next_hop_id: P4WcmpGroupMemberEntry + std::vector> wcmp_group_members; + sai_object_id_t wcmp_group_oid = SAI_NULL_OBJECT_ID; +}; + +// WcmpManager listens to changes in table APP_P4RT_WCMP_GROUP_TABLE_NAME and +// creates/updates/deletes next hop group SAI object accordingly. Below is +// an example WCMP group table entry in APPL_DB. +// +// P4RT_TABLE:FIXED_WCMP_GROUP_TABLE:{"match/wcmp_group_id":"group-1"} +// "actions" =[ +// { +// "action": "set_nexthop_id", +// "param/nexthop_id": "node-1234:eth-1/2/3", +// "weight": 3, +// "watch_port": "Ethernet0", +// }, +// { +// "action": "set_nexthop_id", +// "param/nexthop_id": "node-2345:eth-1/2/3", +// "weight": 4, +// "watch_port": "Ethernet8", +// }, +// ] +// "controller_metadata" = "..." +class WcmpManager : public ObjectManagerInterface +{ + public: + WcmpManager(P4OidMapper *p4oidMapper, ResponsePublisherInterface *publisher) + { + SWSS_LOG_ENTER(); + + assert(p4oidMapper != nullptr); + m_p4OidMapper = p4oidMapper; + assert(publisher != nullptr); + m_publisher = publisher; + } + + virtual ~WcmpManager() = default; + + void enqueue(const swss::KeyOpFieldsValuesTuple &entry) override; + void drain() override; + + // Prunes next hop members egressing through the given port. + void pruneNextHops(const std::string &port); + + // Restores pruned next hop members on link up. Returns an SWSS status code. + void restorePrunedNextHops(const std::string &port); + + // Inserts into/updates port_oper_status_map + void updatePortOperStatusMap(const std::string &port, const sai_port_oper_status_t &status); + + private: + // Gets the internal cached WCMP group entry by its key. + // Return nullptr if corresponding WCMP group entry is not cached. + P4WcmpGroupEntry *getWcmpGroupEntry(const std::string &wcmp_group_id); + + // Deserializes an entry from table APP_P4RT_WCMP_GROUP_TABLE_NAME. + ReturnCodeOr deserializeP4WcmpGroupAppDbEntry( + const std::string &key, const std::vector &attributes); + + // Perform validation on WCMP group entry. Return a SWSS status code + ReturnCode validateWcmpGroupEntry(const P4WcmpGroupEntry &app_db_entry); + + // Processes add operation for an entry. + ReturnCode processAddRequest(P4WcmpGroupEntry *app_db_entry); + + // Creates an WCMP group in the WCMP group table. + // validateWcmpGroupEntry() is required in caller function before + // createWcmpGroup() is called + ReturnCode createWcmpGroup(P4WcmpGroupEntry *wcmp_group_entry); + + // Creates WCMP group member in the WCMP group. + ReturnCode createWcmpGroupMember(std::shared_ptr wcmp_group_member, + const sai_object_id_t group_oid, const std::string &wcmp_group_key); + + // Creates WCMP group member with an associated watch_port. + ReturnCode createWcmpGroupMemberWithWatchport(P4WcmpGroupEntry *wcmp_group, + std::shared_ptr member, + const std::string &wcmp_group_key); + + // Performs watchport related addition operations and creates WCMP group + // member. + ReturnCode processWcmpGroupMemberAddition(std::shared_ptr member, + P4WcmpGroupEntry *wcmp_group, const std::string &wcmp_group_key); + + // Performs watchport related removal operations and removes WCMP group + // member. + ReturnCode processWcmpGroupMemberRemoval(std::shared_ptr member, + const std::string &wcmp_group_key); + + // Processes update operation for a WCMP group entry. + ReturnCode processUpdateRequest(P4WcmpGroupEntry *wcmp_group_entry); + + // Clean up group members when request fails + void recoverGroupMembers( + p4orch::P4WcmpGroupEntry *wcmp_group, const std::string &wcmp_group_key, + const std::vector> &created_wcmp_group_members, + const std::vector> &removed_wcmp_group_members); + + // Deletes a WCMP group in the WCMP group table. + ReturnCode removeWcmpGroup(const std::string &wcmp_group_id); + + // Deletes a WCMP group member in the WCMP group table. + ReturnCode removeWcmpGroupMember(const std::shared_ptr wcmp_group_member, + const std::string &wcmp_group_id); + + // Fetches oper-status of port using port_oper_status_map or SAI. + ReturnCode fetchPortOperStatus(const std::string &port, sai_port_oper_status_t *oper_status); + + // Inserts a next hop member in port_name_to_wcmp_group_member_map + void insertMemberInPortNameToWcmpGroupMemberMap(std::shared_ptr member); + + // Removes a next hop member from port_name_to_wcmp_group_member_map + void removeMemberFromPortNameToWcmpGroupMemberMap(std::shared_ptr member); + + // Gets port oper-status from port_oper_status_map if present + bool getPortOperStatusFromMap(const std::string &port, sai_port_oper_status_t *status); + + // Maps wcmp_group_id to P4WcmpGroupEntry + std::unordered_map m_wcmpGroupTable; + + // Maps port name to P4WcmpGroupMemberEntry + std::unordered_map>> + port_name_to_wcmp_group_member_map; + + // Set of pruned P4WcmpGroupMemberEntry entries + std::unordered_set> pruned_wcmp_members_set; + + // Maps port name to oper-status + std::unordered_map port_oper_status_map; + + // Owners of pointers below must outlive this class's instance. + P4OidMapper *m_p4OidMapper; + std::deque m_entries; + ResponsePublisherInterface *m_publisher; + + friend class p4orch::test::WcmpManagerTest; +}; + +} // namespace p4orch