From da21172e06dda30fba21cc84e73376b8a03239f0 Mon Sep 17 00:00:00 2001 From: Stepan Blyshchak <38952541+stepanblyschak@users.noreply.github.com> Date: Mon, 22 Nov 2021 18:27:25 +0200 Subject: [PATCH 01/14] [aclorch] add generic AclOrch::updateAclRule() method (#1993) - What I did I added generic AclOrch::updateAclRule() implementation. This implementation takes a newly constructed shared_ptr provided by the user, calculates a difference between priority, matches and actions and calls sai_acl_api->set_acl_entry_attribute() for those attributes that were changed. The derivatives of AclRule can customize the behaviour of AclRule::update(const AclRule& updatedRule) by polymorphic override, although in that case the derivative needs dynamic_cast the updatedRule to a concrete derivative type. Added unit test to cover update scenario. Currently this new API is not used by any clients nor handling ACL_RULE table updates. This API will be used by PBH ACL rule update flow. - Why I did it To support the scenario for updating PBH ACL rule in the future. - How I verified it Temporary changed the ACL_RULE table update to leverage this new API and tested updates through CONFIG_DB and unit test coverage. Signed-off-by: Stepan Blyshchak --- orchagent/Makefile.am | 1 + orchagent/aclorch.cpp | 647 ++++++++++++++++++++++++-------- orchagent/aclorch.h | 58 ++- orchagent/pbh/pbhrule.cpp | 31 +- orchagent/pbh/pbhrule.h | 3 +- orchagent/saiattr.cpp | 91 +++++ orchagent/saiattr.h | 41 ++ tests/mock_tests/Makefile.am | 1 + tests/mock_tests/aclorch_ut.cpp | 117 +++++- tests/mock_tests/portal.h | 4 +- 10 files changed, 783 insertions(+), 211 deletions(-) create mode 100644 orchagent/saiattr.cpp create mode 100644 orchagent/saiattr.h diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 364fff4853..e0106f00ce 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -64,6 +64,7 @@ orchagent_SOURCES = \ pbh/pbhrule.cpp \ pbhorch.cpp \ saihelper.cpp \ + saiattr.cpp \ switchorch.cpp \ pfcwdorch.cpp \ pfcactionhandler.cpp \ diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index d9dc26e99e..e3b279a482 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -60,8 +60,8 @@ acl_rule_attr_lookup_t aclMatchLookup = { MATCH_ICMP_CODE, SAI_ACL_ENTRY_ATTR_FIELD_ICMP_CODE }, { MATCH_ICMPV6_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_ICMPV6_TYPE }, { MATCH_ICMPV6_CODE, SAI_ACL_ENTRY_ATTR_FIELD_ICMPV6_CODE }, - { MATCH_L4_SRC_PORT_RANGE, (sai_acl_entry_attr_t)SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE }, - { MATCH_L4_DST_PORT_RANGE, (sai_acl_entry_attr_t)SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE }, + { MATCH_L4_SRC_PORT_RANGE, SAI_ACL_ENTRY_ATTR_FIELD_ACL_RANGE_TYPE }, + { MATCH_L4_DST_PORT_RANGE, SAI_ACL_ENTRY_ATTR_FIELD_ACL_RANGE_TYPE }, { MATCH_TUNNEL_VNI, SAI_ACL_ENTRY_ATTR_FIELD_TUNNEL_VNI }, { MATCH_INNER_ETHER_TYPE, SAI_ACL_ENTRY_ATTR_FIELD_INNER_ETHER_TYPE }, { MATCH_INNER_IP_PROTOCOL, SAI_ACL_ENTRY_ATTR_FIELD_INNER_IP_PROTOCOL }, @@ -69,6 +69,12 @@ acl_rule_attr_lookup_t aclMatchLookup = { MATCH_INNER_L4_DST_PORT, SAI_ACL_ENTRY_ATTR_FIELD_INNER_L4_DST_PORT } }; +static acl_range_type_lookup_t aclRangeTypeLookup = +{ + { MATCH_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE }, + { MATCH_L4_DST_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE }, +}; + static acl_rule_attr_lookup_t aclL3ActionLookup = { { ACTION_PACKET_ACTION, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION }, @@ -164,6 +170,16 @@ static map aclCounterLookup = {SAI_ACL_COUNTER_ATTR_ENABLE_PACKET_COUNT, SAI_ACL_COUNTER_ATTR_PACKETS}, }; +static string getAttributeIdName(sai_object_type_t objectType, sai_attr_id_t attr) +{ + const auto* meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_ENTRY, attr); + if (!meta) + { + SWSS_LOG_THROW("Metadata null pointer returned by sai_metadata_get_attr_metadata"); + } + return meta->attridname; +} + AclRule::AclRule(AclOrch *pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : m_pAclOrch(pAclOrch), m_id(rule), @@ -186,12 +202,11 @@ bool AclRule::validateAddPriority(string attr_name, string attr_value) { char *endp = NULL; errno = 0; - m_priority = (uint32_t)strtol(attr_value.c_str(), &endp, 0); + auto priority = (uint32_t)strtol(attr_value.c_str(), &endp, 0); // check conversion was successful and the value is within the allowed range status = (errno == 0) && (endp == attr_value.c_str() + attr_value.size()) && - (m_priority >= m_minPriority) && - (m_priority <= m_maxPriority); + setPriority(priority); } return status; @@ -201,7 +216,11 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) { SWSS_LOG_ENTER(); - sai_attribute_value_t value; + sai_acl_field_data_t matchData{}; + vector inPorts; + vector outPorts; + + matchData.enable = true; try { @@ -218,7 +237,6 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) return false; } - m_inPorts.clear(); for (auto alias : ports) { Port port; @@ -234,11 +252,11 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) return false; } - m_inPorts.push_back(port.m_port_id); + inPorts.push_back(port.m_port_id); } - value.aclfield.data.objlist.count = static_cast(m_inPorts.size()); - value.aclfield.data.objlist.list = m_inPorts.data(); + matchData.data.objlist.count = static_cast(inPorts.size()); + matchData.data.objlist.list = inPorts.data(); } else if (attr_name == MATCH_OUT_PORTS) { @@ -249,7 +267,6 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) return false; } - m_outPorts.clear(); for (auto alias : ports) { Port port; @@ -265,49 +282,49 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) return false; } - m_outPorts.push_back(port.m_port_id); + outPorts.push_back(port.m_port_id); } - value.aclfield.data.objlist.count = static_cast(m_outPorts.size()); - value.aclfield.data.objlist.list = m_outPorts.data(); + matchData.data.objlist.count = static_cast(outPorts.size()); + matchData.data.objlist.list = outPorts.data(); } else if (attr_name == MATCH_IP_TYPE) { - if (!processIpType(attr_value, value.aclfield.data.u32)) + if (!processIpType(attr_value, matchData.data.u32)) { SWSS_LOG_ERROR("Invalid IP type %s", attr_value.c_str()); return false; } - value.aclfield.mask.u32 = 0xFFFFFFFF; + matchData.mask.u32 = 0xFFFFFFFF; } else if (attr_name == MATCH_TCP_FLAGS) { // Support both exact value match and value/mask match auto flag_data = tokenize(attr_value, '/'); - value.aclfield.data.u8 = to_uint(flag_data[0], 0, 0x3F); + matchData.data.u8 = to_uint(flag_data[0], 0, 0x3F); if (flag_data.size() == 2) { - value.aclfield.mask.u8 = to_uint(flag_data[1], 0, 0x3F); + matchData.mask.u8 = to_uint(flag_data[1], 0, 0x3F); } else { - value.aclfield.mask.u8 = 0x3F; + matchData.mask.u8 = 0x3F; } } else if (attr_name == MATCH_ETHER_TYPE || attr_name == MATCH_L4_SRC_PORT || attr_name == MATCH_L4_DST_PORT) { - value.aclfield.data.u16 = to_uint(attr_value); - value.aclfield.mask.u16 = 0xFFFF; + matchData.data.u16 = to_uint(attr_value); + matchData.mask.u16 = 0xFFFF; } else if (attr_name == MATCH_VLAN_ID) { - value.aclfield.data.u16 = to_uint(attr_value); - value.aclfield.mask.u16 = 0xFFF; + matchData.data.u16 = to_uint(attr_value); + matchData.mask.u16 = 0xFFF; - if (value.aclfield.data.u16 < MIN_VLAN_ID || value.aclfield.data.u16 > MAX_VLAN_ID) + if (matchData.data.u16 < MIN_VLAN_ID || matchData.data.u16 > MAX_VLAN_ID) { SWSS_LOG_ERROR("Invalid VLAN ID: %s", attr_value.c_str()); return false; @@ -318,21 +335,21 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) /* Support both exact value match and value/mask match */ auto dscp_data = tokenize(attr_value, '/'); - value.aclfield.data.u8 = to_uint(dscp_data[0], 0, 0x3F); + matchData.data.u8 = to_uint(dscp_data[0], 0, 0x3F); if (dscp_data.size() == 2) { - value.aclfield.mask.u8 = to_uint(dscp_data[1], 0, 0x3F); + matchData.mask.u8 = to_uint(dscp_data[1], 0, 0x3F); } else { - value.aclfield.mask.u8 = 0x3F; + matchData.mask.u8 = 0x3F; } } else if (attr_name == MATCH_IP_PROTOCOL || attr_name == MATCH_NEXT_HEADER) { - value.aclfield.data.u8 = to_uint(attr_value); - value.aclfield.mask.u8 = 0xFF; + matchData.data.u8 = to_uint(attr_value); + matchData.mask.u8 = 0xFF; } else if (attr_name == MATCH_SRC_IP || attr_name == MATCH_DST_IP) { @@ -343,8 +360,8 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) SWSS_LOG_ERROR("IP type is not v4 type"); return false; } - value.aclfield.data.ip4 = ip.getIp().getV4Addr(); - value.aclfield.mask.ip4 = ip.getMask().getV4Addr(); + matchData.data.ip4 = ip.getIp().getV4Addr(); + matchData.mask.ip4 = ip.getMask().getV4Addr(); } else if (attr_name == MATCH_SRC_IPV6 || attr_name == MATCH_DST_IPV6) { @@ -354,52 +371,59 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) SWSS_LOG_ERROR("IP type is not v6 type"); return false; } - memcpy(value.aclfield.data.ip6, ip.getIp().getV6Addr(), 16); - memcpy(value.aclfield.mask.ip6, ip.getMask().getV6Addr(), 16); + memcpy(matchData.data.ip6, ip.getIp().getV6Addr(), 16); + memcpy(matchData.mask.ip6, ip.getMask().getV6Addr(), 16); } else if ((attr_name == MATCH_L4_SRC_PORT_RANGE) || (attr_name == MATCH_L4_DST_PORT_RANGE)) { - if (sscanf(attr_value.c_str(), "%d-%d", &value.u32range.min, &value.u32range.max) != 2) + AclRangeConfig rangeConfig{}; + if (sscanf(attr_value.c_str(), "%d-%d", &rangeConfig.min, &rangeConfig.max) != 2) { SWSS_LOG_ERROR("Range parse error. Attribute: %s, value: %s", attr_name.c_str(), attr_value.c_str()); return false; } + rangeConfig.rangeType = aclRangeTypeLookup[attr_name]; + // check boundaries - if ((value.u32range.min > USHRT_MAX) || - (value.u32range.max > USHRT_MAX) || - (value.u32range.min > value.u32range.max)) + if ((rangeConfig.min > USHRT_MAX) || + (rangeConfig.max > USHRT_MAX) || + (rangeConfig.min > rangeConfig.max)) { SWSS_LOG_ERROR("Range parse error. Invalid range value. Attribute: %s, value: %s", attr_name.c_str(), attr_value.c_str()); return false; } + + m_rangeConfig.push_back(rangeConfig); + + return true; } else if (attr_name == MATCH_TC) { - value.aclfield.data.u8 = to_uint(attr_value); - value.aclfield.mask.u8 = 0xFF; + matchData.data.u8 = to_uint(attr_value); + matchData.mask.u8 = 0xFF; } else if (attr_name == MATCH_ICMP_TYPE || attr_name == MATCH_ICMP_CODE || attr_name == MATCH_ICMPV6_TYPE || attr_name == MATCH_ICMPV6_CODE) { - value.aclfield.data.u8 = to_uint(attr_value); - value.aclfield.mask.u8 = 0xFF; + matchData.data.u8 = to_uint(attr_value); + matchData.mask.u8 = 0xFF; } else if (attr_name == MATCH_TUNNEL_VNI) { - value.aclfield.data.u32 = to_uint(attr_value); - value.aclfield.mask.u32 = 0xFFFFFFFF; + matchData.data.u32 = to_uint(attr_value); + matchData.mask.u32 = 0xFFFFFFFF; } else if (attr_name == MATCH_INNER_ETHER_TYPE || attr_name == MATCH_INNER_L4_SRC_PORT || attr_name == MATCH_INNER_L4_DST_PORT) { - value.aclfield.data.u16 = to_uint(attr_value); - value.aclfield.mask.u16 = 0xFFFF; + matchData.data.u16 = to_uint(attr_value); + matchData.mask.u16 = 0xFFFF; } else if (attr_name == MATCH_INNER_IP_PROTOCOL) { - value.aclfield.data.u8 = to_uint(attr_value); - value.aclfield.mask.u8 = 0xFF; + matchData.data.u8 = to_uint(attr_value); + matchData.mask.u8 = 0xFF; } } catch (exception &e) @@ -422,42 +446,7 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) attr_name = MATCH_NEXT_HEADER; } - m_matches[aclMatchLookup[attr_name]] = value; - - return true; -} - -bool AclRule::validateAddAction(string attr_name, string attr_value) -{ - for (const auto& it: m_actions) - { - if (!AclRule::isActionSupported(it.first)) - { - SWSS_LOG_ERROR("Action %s:%s is not supported by ASIC", - attr_name.c_str(), attr_value.c_str()); - return false; - } - - // check if ACL action attribute entry parameter is an enum value - const auto* meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_ENTRY, it.first); - if (meta == nullptr) - { - SWSS_LOG_THROW("Metadata null pointer returned by sai_metadata_get_attr_metadata for action %s", - attr_name.c_str()); - } - if (meta->isenum) - { - // if ACL action attribute requires enum value check if value is supported by the ASIC - if (!m_pAclOrch->isAclActionEnumValueSupported(AclOrch::getAclActionFromAclEntry(it.first), - it.second.aclaction.parameter)) - { - SWSS_LOG_ERROR("Action %s:%s is not supported by ASIC", - attr_name.c_str(), attr_value.c_str()); - return false; - } - } - } - return true; + return setMatch(aclMatchLookup[attr_name], matchData); } bool AclRule::processIpType(string type, sai_uint32_t &ip_type) @@ -526,50 +515,41 @@ bool AclRule::createRule() rule_attrs.push_back(attr); } - // store matches - for (auto it : m_matches) + if (!m_rangeConfig.empty()) { - // collect ranges and add them later as a list - if (((sai_acl_range_type_t)it.first == SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE) || - ((sai_acl_range_type_t)it.first == SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE)) + for (const auto& rangeConfig: m_rangeConfig) { - SWSS_LOG_INFO("Creating range object %u..%u", it.second.u32range.min, it.second.u32range.max); + SWSS_LOG_INFO("Creating range object %u..%u", rangeConfig.min, rangeConfig.max); - AclRange *range = AclRange::create((sai_acl_range_type_t)it.first, it.second.u32range.min, it.second.u32range.max); + AclRange *range = AclRange::create(rangeConfig.rangeType, rangeConfig.min, rangeConfig.max); if (!range) { // release already created range if any AclRange::remove(range_objects, range_object_list.count); return false; } - else - { - range_objects[range_object_list.count++] = range->getOid(); - } - } - else - { - attr.id = it.first; - attr.value = it.second; - attr.value.aclfield.enable = true; - rule_attrs.push_back(attr); + + m_ranges.push_back(range); + range_objects[range_object_list.count++] = range->getOid(); } - } - // store ranges if any - if (range_object_list.count > 0) - { attr.id = SAI_ACL_ENTRY_ATTR_FIELD_ACL_RANGE_TYPE; attr.value.aclfield.enable = true; attr.value.aclfield.data.objlist = range_object_list; rule_attrs.push_back(attr); } + // store matches + for (auto& it : m_matches) + { + attr = it.second.getSaiAttr(); + rule_attrs.push_back(attr); + } + // store actions - for (auto it : m_actions) + for (auto& it : m_actions) { - attr.id = it.first; - attr.value = it.second; + attr = it.second.getSaiAttr(); rule_attrs.push_back(attr); } @@ -671,11 +651,9 @@ bool AclRule::remove() void AclRule::updateInPorts() { SWSS_LOG_ENTER(); - sai_attribute_t attr; sai_status_t status; - attr.id = SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS; - attr.value = m_matches[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS]; + auto attr = m_matches[SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS].getSaiAttr(); attr.value.aclfield.enable = true; status = sai_acl_api->set_acl_entry_attribute(m_ruleOid, &attr); @@ -685,6 +663,279 @@ void AclRule::updateInPorts() } } +bool AclRule::update(const AclRule& updatedRule) +{ + SWSS_LOG_ENTER(); + + if (!m_rangeConfig.empty() || !updatedRule.m_rangeConfig.empty()) + { + SWSS_LOG_ERROR("Updating range matches is currently not implemented"); + return false; + } + + if (!updateCounter(updatedRule)) + { + return false; + } + + if (!updatePriority(updatedRule)) + { + return false; + } + + if (!updateMatches(updatedRule)) + { + return false; + } + + if (!updateActions(updatedRule)) + { + return false; + } + + return true; +} + +bool AclRule::updateCounter(const AclRule& updatedRule) +{ + if (updatedRule.m_createCounter) + { + if (!enableCounter()) + { + return false; + } + } + else + { + if (!disableCounter()) + { + return false; + } + } + + m_createCounter = updatedRule.m_createCounter; + + return true; +} + +bool AclRule::updatePriority(const AclRule& updatedRule) +{ + if (m_priority == updatedRule.m_priority) + { + return true; + } + + sai_attribute_t attr {}; + attr.id = SAI_ACL_ENTRY_ATTR_PRIORITY; + attr.value.s32 = updatedRule.m_priority; + if (!setAttribute(attr)) + { + return false; + } + + m_priority = updatedRule.m_priority; + + return true; +} + +bool AclRule::updateMatches(const AclRule& updatedRule) +{ + vector> matchesUpdated; + vector> matchesDisabled; + + // Diff by value to get new matches and updated matches + // in a single set_difference pass. + set_difference( + updatedRule.m_matches.begin(), + updatedRule.m_matches.end(), + m_matches.begin(), m_matches.end(), + back_inserter(matchesUpdated) + ); + + // Diff by key only to get delete matches. Assuming that + // deleted matches mean setting a match attribute to disabled state. + set_difference( + m_matches.begin(), m_matches.end(), + updatedRule.m_matches.begin(), + updatedRule.m_matches.end(), + back_inserter(matchesDisabled), + [](auto& oldMatch, auto& newMatch) + { + return oldMatch.first < newMatch.first; + } + ); + + for (const auto& attrPair: matchesDisabled) + { + auto attr = attrPair.second.getSaiAttr(); + attr.value.aclfield.enable = false; + if (!setAttribute(attr)) + { + return false; + } + m_matches.erase(attrPair.first); + } + + for (const auto& attrPair: matchesUpdated) + { + auto attr = attrPair.second.getSaiAttr(); + if (!setAttribute(attr)) + { + return false; + } + setMatch(attrPair.first, attr.value.aclfield); + } + + return true; +} + +bool AclRule::updateActions(const AclRule& updatedRule) +{ + vector> actionsUpdated; + vector> actionsDisabled; + + // Diff by value to get new action and updated actions + // in a single set_difference pass. + set_difference( + updatedRule.m_actions.begin(), + updatedRule.m_actions.end(), + m_actions.begin(), m_actions.end(), + back_inserter(actionsUpdated) + ); + + // Diff by key only to get delete actions. Assuming that + // action matches mean setting a action attribute to disabled state. + set_difference( + m_actions.begin(), m_actions.end(), + updatedRule.m_actions.begin(), + updatedRule.m_actions.end(), + back_inserter(actionsDisabled), + [](auto& oldAction, auto& newAction) + { + return oldAction.first < newAction.first; + } + ); + + for (const auto& attrPair: actionsDisabled) + { + auto attr = attrPair.second.getSaiAttr(); + attr.value.aclaction.enable = false; + if (!setAttribute(attr)) + { + return false; + } + m_actions.erase(attrPair.first); + } + + for (const auto& attrPair: actionsUpdated) + { + auto attr = attrPair.second.getSaiAttr(); + if (!setAttribute(attr)) + { + return false; + } + setAction(attrPair.first, attr.value.aclaction); + } + + return true; +} + +bool AclRule::setPriority(const sai_uint32_t &value) +{ + if (!(value >= m_minPriority && value <= m_maxPriority)) + { + SWSS_LOG_ERROR("Priority value %d is out of supported priority range %d-%d", + value, m_minPriority, m_maxPriority); + return false; + } + m_priority = value; + return true; +} + +bool AclRule::setAction(sai_acl_entry_attr_t actionId, sai_acl_action_data_t actionData) +{ + if (!isActionSupported(actionId)) + { + SWSS_LOG_ERROR("Action attribute %s is not supported", + getAttributeIdName(SAI_OBJECT_TYPE_ACL_ENTRY, actionId).c_str()); + return false; + } + + // check if ACL action attribute entry parameter is an enum value + const auto* meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_ENTRY, actionId); + if (meta == nullptr) + { + SWSS_LOG_THROW("Metadata null pointer returned by sai_metadata_get_attr_metadata for action %d", actionId); + } + if (meta->isenum) + { + // if ACL action attribute requires enum value check if value is supported by the ASIC + if (!m_pAclOrch->isAclActionEnumValueSupported(AclOrch::getAclActionFromAclEntry(actionId), actionData.parameter)) + { + SWSS_LOG_ERROR("Action %s is not supported by ASIC", + getAttributeIdName(SAI_OBJECT_TYPE_ACL_ENTRY, actionId).c_str()); + return false; + } + } + + sai_attribute_t attr; + attr.id = actionId; + attr.value.aclaction = actionData; + + m_actions[actionId] = SaiAttrWrapper(SAI_OBJECT_TYPE_ACL_ENTRY, attr); + + return true; +} + +bool AclRule::setMatch(sai_acl_entry_attr_t matchId, sai_acl_field_data_t matchData) +{ + sai_attribute_t attr; + attr.id = matchId; + attr.value.aclfield = matchData; + + m_matches[matchId] = SaiAttrWrapper(SAI_OBJECT_TYPE_ACL_ENTRY, attr); + + return true; +} + +bool AclRule::setAttribute(sai_attribute_t attr) +{ + auto meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_ENTRY, attr.id); + if (!meta) + { + SWSS_LOG_THROW("Failed to get metadata for attribute id %d", attr.id); + } + auto status = sai_acl_api->set_acl_entry_attribute(m_ruleOid, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to update attribute %s on ACL rule %s in ACL table %s: %s", + meta->attridname, getId().c_str(), getTableId().c_str(), + sai_serialize_status(status).c_str()); + return false; + } + SWSS_LOG_INFO("Successfully updated action attribute %s on ACL rule %s in ACL table %s", + meta->attridname, getId().c_str(), getTableId().c_str()); + return true; +} + +vector AclRule::getInPorts() const +{ + vector inPorts; + auto it = m_matches.find(SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS); + if (it == m_matches.end()) + { + return inPorts; + } + auto attr = it->second.getSaiAttr(); + if (!attr.value.aclfield.enable) + { + return inPorts; + } + auto objlist = attr.value.aclfield.data.objlist; + inPorts = vector(objlist.list, objlist.list + objlist.count); + return inPorts; +} + shared_ptr AclRule::makeShared(acl_table_type_t type, AclOrch *acl, MirrorOrch *mirror, DTelOrch *dtel, const string& rule, const string& table, const KeyOpFieldsValuesTuple& data) { string action; @@ -887,12 +1138,11 @@ bool AclRule::createCounter() bool AclRule::removeRanges() { SWSS_LOG_ENTER(); - for (auto it : m_matches) + for (const auto& rangeConfig: m_rangeConfig) { - if (((sai_acl_range_type_t)it.first == SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE) || - ((sai_acl_range_type_t)it.first == SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE)) + if (!AclRange::remove(rangeConfig.rangeType, rangeConfig.min, rangeConfig.max)) { - return AclRange::remove((sai_acl_range_type_t)it.first, it.second.u32range.min, it.second.u32range.max); + return false; } } return true; @@ -932,7 +1182,7 @@ bool AclRuleL3::validateAddAction(string attr_name, string _attr_value) SWSS_LOG_ENTER(); string attr_value = to_upper(_attr_value); - sai_attribute_value_t value; + sai_acl_action_data_t actionData; auto action_str = attr_name; @@ -941,7 +1191,7 @@ bool AclRuleL3::validateAddAction(string attr_name, string _attr_value) const auto it = aclPacketActionLookup.find(attr_value); if (it != aclPacketActionLookup.cend()) { - value.aclaction.parameter.s32 = it->second; + actionData.parameter.s32 = it->second; } // handle PACKET_ACTION_REDIRECT in ACTION_PACKET_ACTION for backward compatibility else if (attr_value.find(PACKET_ACTION_REDIRECT) != string::npos) @@ -968,14 +1218,14 @@ bool AclRuleL3::validateAddAction(string attr_name, string _attr_value) { return false; } - value.aclaction.parameter.oid = param_id; + actionData.parameter.oid = param_id; action_str = ACTION_REDIRECT_ACTION; } // handle PACKET_ACTION_DO_NOT_NAT in ACTION_PACKET_ACTION else if (attr_value == PACKET_ACTION_DO_NOT_NAT) { - value.aclaction.parameter.booldata = true; + actionData.parameter.booldata = true; action_str = ACTION_DO_NOT_NAT_ACTION; } else @@ -990,18 +1240,16 @@ bool AclRuleL3::validateAddAction(string attr_name, string _attr_value) { return false; } - value.aclaction.parameter.oid = param_id; + actionData.parameter.oid = param_id; } else { return false; } - value.aclaction.enable = true; - - m_actions[aclL3ActionLookup[action_str]] = value; + actionData.enable = true; - return AclRule::validateAddAction(attr_name, attr_value); + return setAction(aclL3ActionLookup[action_str], actionData); } // This method should return sai attribute id of the redirect destination @@ -1111,7 +1359,7 @@ bool AclRuleL3::validate() { SWSS_LOG_ENTER(); - if (m_matches.size() == 0 || m_actions.size() != 1) + if ((m_rangeConfig.empty() && m_matches.empty()) || m_actions.size() != 1) { return false; } @@ -1119,7 +1367,7 @@ bool AclRuleL3::validate() return true; } -void AclRuleL3::update(SubjectType, void *) +void AclRuleL3::onUpdate(SubjectType, void *) { // Do nothing } @@ -1214,9 +1462,7 @@ bool AclRuleMirror::validateAddAction(string attr_name, string attr_value) m_sessionName = attr_value; // insert placeholder value, we'll set the session oid in AclRuleMirror::create() - m_actions[action] = sai_attribute_value_t{}; - - return AclRule::validateAddAction(attr_name, attr_value); + return setAction(action, sai_acl_action_data_t{}); } bool AclRuleMirror::validateAddMatch(string attr_name, string attr_value) @@ -1287,7 +1533,7 @@ bool AclRuleMirror::validate() { SWSS_LOG_ENTER(); - if (m_matches.size() == 0 || m_sessionName.empty()) + if ((m_rangeConfig.empty() && m_matches.empty()) || m_sessionName.empty()) { return false; } @@ -1337,9 +1583,11 @@ bool AclRuleMirror::activate() for (auto& it: m_actions) { - it.second.aclaction.enable = true; - it.second.aclaction.parameter.objlist.list = &oid; - it.second.aclaction.parameter.objlist.count = 1; + auto attr = it.second.getSaiAttr(); + attr.value.aclaction.enable = true; + attr.value.aclaction.parameter.objlist.list = &oid; + attr.value.aclaction.parameter.objlist.count = 1; + setAction(it.first, attr.value.aclaction); } if (!AclRule::createRule()) @@ -1382,7 +1630,7 @@ bool AclRuleMirror::deactivate() return true; } -void AclRuleMirror::update(SubjectType type, void *cntx) +void AclRuleMirror::onUpdate(SubjectType type, void *cntx) { if (type != SUBJECT_TYPE_MIRROR_SESSION_CHANGE) { @@ -1408,6 +1656,19 @@ void AclRuleMirror::update(SubjectType type, void *cntx) } } +bool AclRuleMirror::update(const AclRule& rule) +{ + auto mirrorRule = dynamic_cast(&rule); + if (!mirrorRule) + { + SWSS_LOG_ERROR("Cannot update mirror rule with a rule of a different type"); + return false; + } + + SWSS_LOG_ERROR("Updating mirror rule is currently not implemented"); + return false; +} + AclRuleMclag::AclRuleMclag(AclOrch *aclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : AclRuleL3(aclOrch, rule, table, type, createCounter) { @@ -1828,7 +2089,7 @@ bool AclTable::create() return status == SAI_STATUS_SUCCESS; } -void AclTable::update(SubjectType type, void *cntx) +void AclTable::onUpdate(SubjectType type, void *cntx) { SWSS_LOG_ENTER(); @@ -2016,6 +2277,36 @@ bool AclTable::remove(string rule_id) } } +bool AclTable::updateRule(shared_ptr updatedRule) +{ + SWSS_LOG_ENTER(); + + if (!updatedRule) + { + return false; + } + + auto ruleId = updatedRule->getId(); + auto ruleIter = rules.find(ruleId); + if (ruleIter == rules.end()) + { + SWSS_LOG_ERROR("Failed to update ACL rule %s as it does not exist in %s", + ruleId.c_str(), id.c_str()); + return false; + } + + if (!ruleIter->second->update(*updatedRule)) + { + SWSS_LOG_ERROR("Failed to update ACL rule %s in table %s", + ruleId.c_str(), id.c_str()); + return false; + } + + SWSS_LOG_NOTICE("Successfully updated ACL rule %s in table %s", + ruleId.c_str(), id.c_str()); + return true; +} + bool AclTable::clear() { SWSS_LOG_ENTER(); @@ -2045,7 +2336,7 @@ bool AclRuleDTelFlowWatchListEntry::validateAddAction(string attr_name, string a { SWSS_LOG_ENTER(); - sai_attribute_value_t value; + sai_acl_action_data_t actionData; string attr_value = to_upper(attr_val); sai_object_id_t session_oid; @@ -2069,7 +2360,7 @@ bool AclRuleDTelFlowWatchListEntry::validateAddAction(string attr_name, string a return false; } - value.aclaction.parameter.s32 = it->second; + actionData.parameter.s32 = it->second; if (attr_value == DTEL_FLOW_OP_INT) { @@ -2088,7 +2379,7 @@ bool AclRuleDTelFlowWatchListEntry::validateAddAction(string attr_name, string a bool ret = m_pDTelOrch->getINTSessionOid(attr_value, session_oid); if (ret) { - value.aclaction.parameter.oid = session_oid; + actionData.parameter.oid = session_oid; // Increase session reference count regardless of state to deny // attempt to remove INT session with attached ACL rules. @@ -2107,22 +2398,20 @@ bool AclRuleDTelFlowWatchListEntry::validateAddAction(string attr_name, string a if (attr_name == ACTION_DTEL_FLOW_SAMPLE_PERCENT) { - value.aclaction.parameter.u8 = to_uint(attr_value); + actionData.parameter.u8 = to_uint(attr_value); } - value.aclaction.enable = true; + actionData.enable = true; if (attr_name == ACTION_DTEL_REPORT_ALL_PACKETS || attr_name == ACTION_DTEL_DROP_REPORT_ENABLE || attr_name == ACTION_DTEL_TAIL_DROP_REPORT_ENABLE) { - value.aclaction.parameter.booldata = (attr_value == DTEL_ENABLED) ? true : false; - value.aclaction.enable = (attr_value == DTEL_ENABLED) ? true : false; + actionData.parameter.booldata = (attr_value == DTEL_ENABLED) ? true : false; + actionData.enable = (attr_value == DTEL_ENABLED) ? true : false; } - m_actions[aclDTelActionLookup[attr_name]] = value; - - return AclRule::validateAddAction(attr_name, attr_value); + return setAction(aclDTelActionLookup[attr_name], actionData); } bool AclRuleDTelFlowWatchListEntry::validate() @@ -2134,7 +2423,7 @@ bool AclRuleDTelFlowWatchListEntry::validate() return false; } - if (m_matches.size() == 0 || m_actions.size() == 0) + if ((m_rangeConfig.empty() && m_matches.empty()) || m_actions.size() == 0) { return false; } @@ -2202,9 +2491,9 @@ bool AclRuleDTelFlowWatchListEntry::deactivate() return true; } -void AclRuleDTelFlowWatchListEntry::update(SubjectType type, void *cntx) +void AclRuleDTelFlowWatchListEntry::onUpdate(SubjectType type, void *cntx) { - sai_attribute_value_t value; + sai_acl_action_data_t actionData; sai_object_id_t session_oid = SAI_NULL_OBJECT_ID; if (!m_pDTelOrch) @@ -2235,8 +2524,8 @@ void AclRuleDTelFlowWatchListEntry::update(SubjectType type, void *cntx) return; } - value.aclaction.enable = true; - value.aclaction.parameter.oid = session_oid; + actionData.enable = true; + actionData.parameter.oid = session_oid; // Increase session reference count regardless of state to deny // attempt to remove INT session with attached ACL rules. @@ -2245,7 +2534,11 @@ void AclRuleDTelFlowWatchListEntry::update(SubjectType type, void *cntx) throw runtime_error("Failed to increase INT session reference count"); } - m_actions[SAI_ACL_ENTRY_ATTR_ACTION_DTEL_INT_SESSION] = value; + if (!setAction(SAI_ACL_ENTRY_ATTR_ACTION_DTEL_INT_SESSION, actionData)) + { + SWSS_LOG_ERROR("Failed to set action SAI_ACL_ENTRY_ATTR_ACTION_DTEL_INT_SESSION"); + return; + } INT_session_valid = true; @@ -2259,6 +2552,19 @@ void AclRuleDTelFlowWatchListEntry::update(SubjectType type, void *cntx) } } +bool AclRuleDTelFlowWatchListEntry::update(const AclRule& rule) +{ + auto dtelDropWathcListRule = dynamic_cast(&rule); + if (!dtelDropWathcListRule) + { + SWSS_LOG_ERROR("Cannot update DTEL flow watch list rule with a rule of a different type"); + return false; + } + + SWSS_LOG_ERROR("Updating DTEL flow watch list rule is currently not implemented"); + return false; +} + AclRuleDTelDropWatchListEntry::AclRuleDTelDropWatchListEntry(AclOrch *aclOrch, DTelOrch *dtel, string rule, string table, acl_table_type_t type) : AclRule(aclOrch, rule, table, type), m_pDTelOrch(dtel) @@ -2274,7 +2580,7 @@ bool AclRuleDTelDropWatchListEntry::validateAddAction(string attr_name, string a return false; } - sai_attribute_value_t value; + sai_acl_action_data_t actionData; string attr_value = to_upper(attr_val); if (attr_name != ACTION_DTEL_DROP_REPORT_ENABLE && @@ -2284,13 +2590,10 @@ bool AclRuleDTelDropWatchListEntry::validateAddAction(string attr_name, string a return false; } + actionData.parameter.booldata = (attr_value == DTEL_ENABLED) ? true : false; + actionData.enable = (attr_value == DTEL_ENABLED) ? true : false; - value.aclaction.parameter.booldata = (attr_value == DTEL_ENABLED) ? true : false; - value.aclaction.enable = (attr_value == DTEL_ENABLED) ? true : false; - - m_actions[aclDTelActionLookup[attr_name]] = value; - - return AclRule::validateAddAction(attr_name, attr_value); + return setAction(aclDTelActionLookup[attr_name], actionData); } bool AclRuleDTelDropWatchListEntry::validate() @@ -2302,7 +2605,7 @@ bool AclRuleDTelDropWatchListEntry::validate() return false; } - if (m_matches.size() == 0 || m_actions.size() == 0) + if ((m_rangeConfig.empty() && m_matches.empty()) || m_actions.size() == 0) { return false; } @@ -2310,7 +2613,7 @@ bool AclRuleDTelDropWatchListEntry::validate() return true; } -void AclRuleDTelDropWatchListEntry::update(SubjectType, void *) +void AclRuleDTelDropWatchListEntry::onUpdate(SubjectType, void *) { // Do nothing } @@ -2816,13 +3119,13 @@ void AclOrch::update(SubjectType type, void *cntx) { if (type == SUBJECT_TYPE_PORT_CHANGE) { - table.second.update(type, cntx); + table.second.onUpdate(type, cntx); } else { for (auto& rule : table.second.rules) { - rule.second->update(type, cntx); + rule.second->onUpdate(type, cntx); } } } @@ -3355,6 +3658,26 @@ bool AclOrch::updateAclRule(string table_id, string rule_id, bool enableCounter) return true; } +bool AclOrch::updateAclRule(shared_ptr updatedRule) +{ + SWSS_LOG_ENTER(); + + auto tableId = updatedRule->getTableId(); + sai_object_id_t tableOid = getTableById(tableId); + if (tableOid == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("Failed to add ACL rule in ACL table %s. Table doesn't exist", tableId.c_str()); + return false; + } + + if (!m_AclTables[tableOid].updateRule(updatedRule)) + { + return false; + } + + return true; +} + bool AclOrch::isCombinedMirrorV6Table() { return m_isCombinedMirrorV6Table; diff --git a/orchagent/aclorch.h b/orchagent/aclorch.h index a34d06846e..9b1df18570 100644 --- a/orchagent/aclorch.h +++ b/orchagent/aclorch.h @@ -19,6 +19,8 @@ #include "acltable.h" +#include "saiattr.h" + #define RULE_PRIORITY "PRIORITY" #define MATCH_IN_PORTS "IN_PORTS" #define MATCH_OUT_PORTS "OUT_PORTS" @@ -93,6 +95,7 @@ #define ACL_COUNTER_FLEX_COUNTER_GROUP "ACL_STAT_COUNTER" typedef map acl_rule_attr_lookup_t; +typedef map acl_range_type_lookup_t; typedef map acl_ip_type_lookup_t; typedef map acl_dtel_flow_op_type_lookup_t; typedef map acl_packet_action_lookup_t; @@ -102,6 +105,13 @@ typedef map> acl_action_enum_values_capabili class AclOrch; +struct AclRangeConfig +{ + sai_acl_range_type_t rangeType; + uint32_t min; + uint32_t max; +}; + class AclRange { public: @@ -130,7 +140,7 @@ class AclRule AclRule(AclOrch *pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = true); virtual bool validateAddPriority(string attr_name, string attr_value); virtual bool validateAddMatch(string attr_name, string attr_value); - virtual bool validateAddAction(string attr_name, string attr_value); + virtual bool validateAddAction(string attr_name, string attr_value) = 0; virtual bool validate() = 0; bool processIpType(string type, sai_uint32_t &ip_type); inline static void setRulePriorities(sai_uint32_t min, sai_uint32_t max) @@ -140,8 +150,9 @@ class AclRule } virtual bool create(); + virtual bool update(const AclRule& updatedRule); virtual bool remove(); - virtual void update(SubjectType, void *) = 0; + virtual void onUpdate(SubjectType, void *) = 0; virtual void updateInPorts(); virtual bool enableCounter(); @@ -167,16 +178,13 @@ class AclRule return m_counterOid; } + vector getInPorts() const; + bool hasCounter() const { return getCounterOid() != SAI_NULL_OBJECT_ID; } - vector getInPorts() - { - return m_inPorts; - } - static shared_ptr makeShared(acl_table_type_t type, AclOrch *acl, MirrorOrch *mirror, DTelOrch *dtel, const string& rule, const string& table, const KeyOpFieldsValuesTuple&); virtual ~AclRule() {} @@ -187,6 +195,17 @@ class AclRule virtual bool removeRanges(); virtual bool removeRule(); + virtual bool updatePriority(const AclRule& updatedRule); + virtual bool updateMatches(const AclRule& updatedRule); + virtual bool updateActions(const AclRule& updatedRule); + virtual bool updateCounter(const AclRule& updatedRule); + + virtual bool setPriority(const sai_uint32_t &value); + virtual bool setAction(sai_acl_entry_attr_t actionId, sai_acl_action_data_t actionData); + virtual bool setMatch(sai_acl_entry_attr_t matchId, sai_acl_field_data_t matchData); + + virtual bool setAttribute(sai_attribute_t attr); + void decreaseNextHopRefCount(); bool isActionSupported(sai_acl_entry_attr_t) const; @@ -201,13 +220,13 @@ class AclRule sai_object_id_t m_ruleOid; sai_object_id_t m_counterOid; uint32_t m_priority; - map m_matches; - map m_actions; + map m_actions; + map m_matches; string m_redirect_target_next_hop; string m_redirect_target_next_hop_group; - vector m_inPorts; - vector m_outPorts; + vector m_rangeConfig; + vector m_ranges; private: bool m_createCounter; @@ -221,7 +240,8 @@ class AclRuleL3: public AclRule bool validateAddAction(string attr_name, string attr_value); bool validateAddMatch(string attr_name, string attr_value); bool validate(); - void update(SubjectType, void *); + void onUpdate(SubjectType, void *) override; + protected: sai_object_id_t getRedirectObjectId(const string& redirect_param); }; @@ -256,11 +276,12 @@ class AclRuleMirror: public AclRule bool validate(); bool createRule(); bool removeRule(); - void update(SubjectType, void *); + void onUpdate(SubjectType, void *) override; bool activate(); bool deactivate(); + bool update(const AclRule& updatedRule) override; protected: bool m_state {false}; string m_sessionName; @@ -275,11 +296,12 @@ class AclRuleDTelFlowWatchListEntry: public AclRule bool validate(); bool createRule(); bool removeRule(); - void update(SubjectType, void *); + void onUpdate(SubjectType, void *) override; bool activate(); bool deactivate(); + bool update(const AclRule& updatedRule) override; protected: DTelOrch *m_pDTelOrch; string m_intSessionId; @@ -293,8 +315,7 @@ class AclRuleDTelDropWatchListEntry: public AclRule AclRuleDTelDropWatchListEntry(AclOrch *m_pAclOrch, DTelOrch *m_pDTelOrch, string rule, string table, acl_table_type_t type); bool validateAddAction(string attr_name, string attr_value); bool validate(); - void update(SubjectType, void *); - + void onUpdate(SubjectType, void *) override; protected: DTelOrch *m_pDTelOrch; }; @@ -342,12 +363,14 @@ class AclTable void unlink(sai_object_id_t portOid); // Add or overwrite a rule into the ACL table bool add(shared_ptr newRule); + // Update existing ACL rule + bool updateRule(shared_ptr updatedRule); // Remove a rule from the ACL table bool remove(string rule_id); // Remove all rules from the ACL table bool clear(); // Update table subject to changes - void update(SubjectType, void *); + void onUpdate(SubjectType, void *); public: string id; @@ -403,6 +426,7 @@ class AclOrch : public Orch, public Observer bool updateAclTable(string table_id, AclTable &table); bool addAclRule(shared_ptr aclRule, string table_id); bool removeAclRule(string table_id, string rule_id); + bool updateAclRule(shared_ptr updatedAclRule); bool updateAclRule(string table_id, string rule_id, string attr_name, void *data, bool oper); bool updateAclRule(string table_id, string rule_id, bool enableCounter); AclRule* getAclRule(string table_id, string rule_id); diff --git a/orchagent/pbh/pbhrule.cpp b/orchagent/pbh/pbhrule.cpp index 52812e35b6..d4e2218cf0 100644 --- a/orchagent/pbh/pbhrule.cpp +++ b/orchagent/pbh/pbhrule.cpp @@ -11,15 +11,7 @@ bool AclRulePbh::validateAddPriority(const sai_uint32_t &value) { SWSS_LOG_ENTER(); - if ((value < m_minPriority) || (value > m_maxPriority)) - { - SWSS_LOG_ERROR("Failed to validate priority: invalid value %d", value); - return false; - } - - m_priority = value; - - return true; + return setPriority(value); } bool AclRulePbh::validateAddMatch(const sai_attribute_t &attr) @@ -52,9 +44,7 @@ bool AclRulePbh::validateAddMatch(const sai_attribute_t &attr) return false; } - m_matches[attrId] = attr.value; - - return true; + return setMatch(attrId, attr.value.aclfield); } bool AclRulePbh::validateAddAction(const sai_attribute_t &attr) @@ -83,15 +73,7 @@ bool AclRulePbh::validateAddAction(const sai_attribute_t &attr) return false; } - if (!AclRule::isActionSupported(attrId)) - { - SWSS_LOG_ERROR("Action %s is not supported by ASIC", attrName.c_str()); - return false; - } - - m_actions[attrId] = attr.value; - - return true; + return setAction(attrId, attr.value.aclaction); } bool AclRulePbh::validate() @@ -107,7 +89,12 @@ bool AclRulePbh::validate() return true; } -void AclRulePbh::update(SubjectType, void *) +void AclRulePbh::onUpdate(SubjectType, void *) { // Do nothing } + +bool AclRulePbh::validateAddAction(string attr_name, string attr_value) +{ + SWSS_LOG_THROW("This API should not be used on PbhRule"); +} diff --git a/orchagent/pbh/pbhrule.h b/orchagent/pbh/pbhrule.h index 3e753a0f03..9e661761c4 100644 --- a/orchagent/pbh/pbhrule.h +++ b/orchagent/pbh/pbhrule.h @@ -11,5 +11,6 @@ class AclRulePbh: public AclRule bool validateAddMatch(const sai_attribute_t &attr); bool validateAddAction(const sai_attribute_t &attr); bool validate() override; - void update(SubjectType, void *) override; + void onUpdate(SubjectType, void *) override; + bool validateAddAction(string attr_name, string attr_value) override; }; diff --git a/orchagent/saiattr.cpp b/orchagent/saiattr.cpp new file mode 100644 index 0000000000..1c24489ed5 --- /dev/null +++ b/orchagent/saiattr.cpp @@ -0,0 +1,91 @@ +#include "saiattr.h" + +#include +#include + +SaiAttrWrapper::SaiAttrWrapper(sai_object_type_t objectType, const sai_attribute_t& attr) +{ + auto meta = sai_metadata_get_attr_metadata(objectType, attr.id); + if (!meta) + { + SWSS_LOG_THROW("Failed to get attribute %d metadata", attr.id); + } + + init(objectType, *meta, attr); +} + +SaiAttrWrapper::~SaiAttrWrapper() +{ + if (m_meta) + { + sai_deserialize_free_attribute_value(m_meta->attrvaluetype, m_attr); + } +} + +SaiAttrWrapper::SaiAttrWrapper(const SaiAttrWrapper& other) +{ + init(other.m_objectType, *other.m_meta, other.m_attr); +} + +SaiAttrWrapper& SaiAttrWrapper::operator=(const SaiAttrWrapper& other) +{ + init(other.m_objectType, *other.m_meta, other.m_attr); + return *this; +} + +SaiAttrWrapper::SaiAttrWrapper(SaiAttrWrapper&& other) +{ + swap(std::move(other)); +} + +SaiAttrWrapper& SaiAttrWrapper::operator=(SaiAttrWrapper&& other) +{ + swap(std::move(other)); + return *this; +} + +bool SaiAttrWrapper::operator<(const SaiAttrWrapper& other) const +{ + return m_serializedAttr < other.m_serializedAttr; +} + +const sai_attribute_t& SaiAttrWrapper::getSaiAttr() const +{ + return m_attr; +} + +std::string SaiAttrWrapper::toString() const +{ + return m_serializedAttr; +} + +sai_attr_id_t SaiAttrWrapper::getAttrId() const +{ + return m_attr.id; +} + +void SaiAttrWrapper::swap(SaiAttrWrapper&& other) +{ + m_objectType = other.m_objectType; + m_meta = other.m_meta; + m_attr = other.m_attr; + m_serializedAttr = other.m_serializedAttr; + other.m_attr = sai_attribute_t{}; + other.m_serializedAttr.clear(); +} + +void SaiAttrWrapper::init( + sai_object_type_t objectType, + const sai_attr_metadata_t& meta, + const sai_attribute_t& attr) +{ + m_objectType = objectType; + m_attr.id = attr.id; + m_meta = &meta; + + m_serializedAttr = sai_serialize_attr_value(*m_meta, attr); + + // deserialize to actually preform a deep copy of attr + // and attribute value's dynamically allocated lists. + sai_deserialize_attr_value(m_serializedAttr, *m_meta, m_attr); +} diff --git a/orchagent/saiattr.h b/orchagent/saiattr.h new file mode 100644 index 0000000000..c4fef0ba0d --- /dev/null +++ b/orchagent/saiattr.h @@ -0,0 +1,41 @@ +#pragma once + +extern "C" +{ +#include +#include +} + +#include + +class SaiAttrWrapper +{ +public: + SaiAttrWrapper() = default; + + SaiAttrWrapper(sai_object_type_t objectType, const sai_attribute_t& attr); + SaiAttrWrapper(const SaiAttrWrapper& other); + SaiAttrWrapper(SaiAttrWrapper&& other); + SaiAttrWrapper& operator=(const SaiAttrWrapper& other); + SaiAttrWrapper& operator=(SaiAttrWrapper&& other); + virtual ~SaiAttrWrapper(); + + bool operator<(const SaiAttrWrapper& other) const; + + const sai_attribute_t& getSaiAttr() const; + std::string toString() const; + sai_attr_id_t getAttrId() const; + +private: + + void init( + sai_object_type_t objectType, + const sai_attr_metadata_t& meta, + const sai_attribute_t& attr); + void swap(SaiAttrWrapper&& other); + + sai_object_type_t m_objectType {SAI_OBJECT_TYPE_NULL}; + const sai_attr_metadata_t* m_meta {nullptr}; + sai_attribute_t m_attr {}; + std::string m_serializedAttr; +}; diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 51bcf30547..7f31379658 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -59,6 +59,7 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/pbh/pbhrule.cpp \ $(top_srcdir)/orchagent/pbhorch.cpp \ $(top_srcdir)/orchagent/saihelper.cpp \ + $(top_srcdir)/orchagent/saiattr.cpp \ $(top_srcdir)/orchagent/switchorch.cpp \ $(top_srcdir)/orchagent/pfcwdorch.cpp \ $(top_srcdir)/orchagent/pfcactionhandler.cpp \ diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index d95ae2f87e..4ffee617ad 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -827,21 +827,21 @@ namespace aclorch_test return false; } - if (it->second.aclaction.enable != true) + if (it->second.getSaiAttr().value.aclaction.enable != true) { return false; } if (attr_value == PACKET_ACTION_FORWARD) { - if (it->second.aclaction.parameter.s32 != SAI_PACKET_ACTION_FORWARD) + if (it->second.getSaiAttr().value.aclaction.parameter.s32 != SAI_PACKET_ACTION_FORWARD) { return false; } } else if (attr_value == PACKET_ACTION_DROP) { - if (it->second.aclaction.parameter.s32 != SAI_PACKET_ACTION_DROP) + if (it->second.getSaiAttr().value.aclaction.parameter.s32 != SAI_PACKET_ACTION_DROP) { return false; } @@ -874,14 +874,14 @@ namespace aclorch_test } char addr[20]; - sai_serialize_ip4(addr, it_field->second.aclfield.data.ip4); + sai_serialize_ip4(addr, it_field->second.getSaiAttr().value.aclfield.data.ip4); if (attr_value != addr) { return false; } char mask[20]; - sai_serialize_ip4(mask, it_field->second.aclfield.mask.ip4); + sai_serialize_ip4(mask, it_field->second.getSaiAttr().value.aclfield.mask.ip4); if (string(mask) != "255.255.255.255") { return false; @@ -896,14 +896,14 @@ namespace aclorch_test } char addr[46]; - sai_serialize_ip6(addr, it_field->second.aclfield.data.ip6); + sai_serialize_ip6(addr, it_field->second.getSaiAttr().value.aclfield.data.ip6); if (attr_value != addr) { return false; } char mask[46]; - sai_serialize_ip6(mask, it_field->second.aclfield.mask.ip6); + sai_serialize_ip6(mask, it_field->second.getSaiAttr().value.aclfield.mask.ip6); if (string(mask) != "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") { return false; @@ -976,6 +976,25 @@ namespace aclorch_test return !aclEnable && aclOid == SAI_NULL_OBJECT_ID; } + + string getAclRuleSaiAttribute(const AclRule& rule, sai_acl_entry_attr_t attrId) + { + sai_attribute_t attr{}; + attr.id = attrId; + auto meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_ENTRY, attrId); + if (!meta) + { + SWSS_LOG_THROW("SAI BUG: Failed to get attribute metadata for SAI_OBJECT_TYPE_ACL_ENTRY attribute id %d", attrId); + } + + auto status = sai_acl_api->get_acl_entry_attribute(rule.m_ruleOid, 1, &attr); + EXPECT_TRUE(status == SAI_STATUS_SUCCESS); + + auto actualSaiValue = sai_serialize_attr_value(*meta, attr); + + return actualSaiValue; + } + }; map AclOrchTest::gProfileMap; @@ -1317,4 +1336,88 @@ namespace aclorch_test ASSERT_EQ(tableIt, orch->getAclTables().end()); } + TEST_F(AclOrchTest, AclRuleUpdate) + { + string acl_table_id = "acl_table_1"; + string acl_rule_id = "acl_rule_1"; + + auto orch = createAclOrch(); + + auto kvfAclTable = deque( + { { acl_table_id, + SET_COMMAND, + { { ACL_TABLE_DESCRIPTION, "TEST" }, + { ACL_TABLE_TYPE, TABLE_TYPE_L3 }, + { ACL_TABLE_STAGE, STAGE_INGRESS }, + { ACL_TABLE_PORTS, "1,2" } } } }); + + orch->doAclTableTask(kvfAclTable); + + // validate acl table ... + + auto acl_table_oid = orch->getTableById(acl_table_id); + ASSERT_NE(acl_table_oid, SAI_NULL_OBJECT_ID); + + const auto &acl_tables = orch->getAclTables(); + auto it_table = acl_tables.find(acl_table_oid); + ASSERT_NE(it_table, acl_tables.end()); + + class AclRuleTest : public AclRuleL3 + { + public: + AclRuleTest(AclOrch* orch, string rule, string table): + AclRuleL3(orch, rule, table, ACL_TABLE_L3, true) + {} + + void setCounterEnabled(bool enabled) + { + m_createCounter = enabled; + } + + void disableMatch(sai_acl_entry_attr_t attr) + { + m_matches.erase(attr); + } + }; + + auto rule = make_shared(orch->m_aclOrch, acl_rule_id, acl_table_id); + ASSERT_TRUE(rule->validateAddPriority(RULE_PRIORITY, "800")); + ASSERT_TRUE(rule->validateAddMatch(MATCH_SRC_IP, "1.1.1.1/32")); + ASSERT_TRUE(rule->validateAddAction(ACTION_PACKET_ACTION, PACKET_ACTION_FORWARD)); + + ASSERT_TRUE(orch->m_aclOrch->addAclRule(rule, acl_table_id)); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_PRIORITY), "800"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP), "1.1.1.1&mask:255.255.255.255"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION), "SAI_PACKET_ACTION_FORWARD"); + + auto updatedRule = make_shared(*rule); + ASSERT_TRUE(updatedRule->validateAddPriority(RULE_PRIORITY, "900")); + ASSERT_TRUE(updatedRule->validateAddMatch(MATCH_SRC_IP, "2.2.2.2/24")); + ASSERT_TRUE(updatedRule->validateAddMatch(MATCH_DST_IP, "3.3.3.3/24")); + ASSERT_TRUE(updatedRule->validateAddAction(ACTION_PACKET_ACTION, PACKET_ACTION_DROP)); + + ASSERT_TRUE(orch->m_aclOrch->updateAclRule(updatedRule)); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_PRIORITY), "900"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP), "2.2.2.2&mask:255.255.255.0"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_FIELD_DST_IP), "3.3.3.3&mask:255.255.255.0"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION), "SAI_PACKET_ACTION_DROP"); + + auto updatedRule2 = make_shared(*updatedRule); + updatedRule2->setCounterEnabled(false); + updatedRule2->disableMatch(SAI_ACL_ENTRY_ATTR_FIELD_DST_IP); + ASSERT_TRUE(orch->m_aclOrch->updateAclRule(updatedRule2)); + ASSERT_TRUE(validateAclRuleCounter(*orch->m_aclOrch->getAclRule(acl_table_id, acl_rule_id), false)); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_PRIORITY), "900"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP), "2.2.2.2&mask:255.255.255.0"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_FIELD_DST_IP), "disabled"); + ASSERT_EQ(getAclRuleSaiAttribute(*rule, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION), "SAI_PACKET_ACTION_DROP"); + + auto updatedRule3 = make_shared(*updatedRule2); + updatedRule3->setCounterEnabled(true); + ASSERT_TRUE(orch->m_aclOrch->updateAclRule(updatedRule3)); + ASSERT_TRUE(validateAclRuleCounter(*orch->m_aclOrch->getAclRule(acl_table_id, acl_rule_id), true)); + + ASSERT_TRUE(orch->m_aclOrch->removeAclRule(rule->getTableId(), rule->getId())); + } + } // namespace nsAclOrchTest diff --git a/tests/mock_tests/portal.h b/tests/mock_tests/portal.h index 7dab40036b..c2438e8e1c 100644 --- a/tests/mock_tests/portal.h +++ b/tests/mock_tests/portal.h @@ -18,12 +18,12 @@ struct Portal return aclRule->m_ruleOid; } - static const map &getMatches(const AclRule *aclRule) + static const map &getMatches(const AclRule *aclRule) { return aclRule->m_matches; } - static const map &getActions(const AclRule *aclRule) + static const map &getActions(const AclRule *aclRule) { return aclRule->m_actions; } From 59cab5d939d8701362515fbb877a1e36dccf07fd Mon Sep 17 00:00:00 2001 From: vmittal-msft <46945843+vmittal-msft@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:47:45 -0800 Subject: [PATCH 02/14] Support for setting switch level DSCP to TC QoS map (#2023) Signed-off-by: Vineet Mittal --- orchagent/qosorch.cpp | 34 +++++++++++++++++++++++++++++++++- orchagent/qosorch.h | 3 +++ orchagent/switchorch.cpp | 28 ++++++++++++++++++++++++++++ orchagent/switchorch.h | 1 + 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/orchagent/qosorch.cpp b/orchagent/qosorch.cpp index 0431b369d3..edd5db3443 100644 --- a/orchagent/qosorch.cpp +++ b/orchagent/qosorch.cpp @@ -14,15 +14,16 @@ using namespace std; +extern sai_switch_api_t *sai_switch_api; extern sai_port_api_t *sai_port_api; extern sai_queue_api_t *sai_queue_api; extern sai_scheduler_api_t *sai_scheduler_api; extern sai_wred_api_t *sai_wred_api; extern sai_qos_map_api_t *sai_qos_map_api; extern sai_scheduler_group_api_t *sai_scheduler_group_api; -extern sai_switch_api_t *sai_switch_api; extern sai_acl_api_t* sai_acl_api; +extern SwitchOrch *gSwitchOrch; extern PortsOrch *gPortsOrch; extern sai_object_id_t gSwitchId; extern CrmOrch *gCrmOrch; @@ -217,6 +218,34 @@ bool DscpToTcMapHandler::convertFieldValuesToAttributes(KeyOpFieldsValuesTuple & return true; } +void DscpToTcMapHandler::applyDscpToTcMapToSwitch(sai_attr_id_t attr_id, sai_object_id_t map_id) +{ + SWSS_LOG_ENTER(); + bool rv = true; + + /* Query DSCP_TO_TC QoS map at switch capability */ + rv = gSwitchOrch->querySwitchDscpToTcCapability(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_QOS_DSCP_TO_TC_MAP); + if (rv == false) + { + SWSS_LOG_ERROR("Switch level DSCP to TC QoS map configuration is not supported"); + return; + } + + /* Apply DSCP_TO_TC QoS map at switch */ + sai_attribute_t attr; + attr.id = attr_id; + attr.value.oid = map_id; + + sai_status_t status = sai_switch_api->set_switch_attribute(gSwitchId, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to apply DSCP_TO_TC QoS map to switch rv:%d", status); + return; + } + + SWSS_LOG_NOTICE("Applied DSCP_TO_TC QoS map to switch successfully"); +} + sai_object_id_t DscpToTcMapHandler::addQosItem(const vector &attributes) { SWSS_LOG_ENTER(); @@ -241,6 +270,9 @@ sai_object_id_t DscpToTcMapHandler::addQosItem(const vector &at return SAI_NULL_OBJECT_ID; } SWSS_LOG_DEBUG("created QosMap object:%" PRIx64, sai_object); + + applyDscpToTcMapToSwitch(SAI_SWITCH_ATTR_QOS_DSCP_TO_TC_MAP, sai_object); + return sai_object; } diff --git a/orchagent/qosorch.h b/orchagent/qosorch.h index 69e7cef70b..cd265d59ec 100644 --- a/orchagent/qosorch.h +++ b/orchagent/qosorch.h @@ -5,6 +5,7 @@ #include #include #include "orch.h" +#include "switchorch.h" #include "portsorch.h" const string dscp_to_tc_field_name = "dscp_to_tc_map"; @@ -71,6 +72,8 @@ class DscpToTcMapHandler : public QosMapHandler public: bool convertFieldValuesToAttributes(KeyOpFieldsValuesTuple &tuple, vector &attributes) override; sai_object_id_t addQosItem(const vector &attributes) override; +protected: + void applyDscpToTcMapToSwitch(sai_attr_id_t attr_id, sai_object_id_t sai_dscp_to_tc_map); }; class MplsTcToTcMapHandler : public QosMapHandler diff --git a/orchagent/switchorch.cpp b/orchagent/switchorch.cpp index 05a36ec87c..daeace8b08 100644 --- a/orchagent/switchorch.cpp +++ b/orchagent/switchorch.cpp @@ -613,3 +613,31 @@ void SwitchOrch::querySwitchTpidCapability() set_switch_capability(fvVector); } } + +bool SwitchOrch::querySwitchDscpToTcCapability(sai_object_type_t sai_object, sai_attr_id_t attr_id) +{ + SWSS_LOG_ENTER(); + + /* Check if SAI is capable of handling Switch level DSCP to TC QoS map */ + vector fvVector; + sai_status_t status = SAI_STATUS_SUCCESS; + sai_attr_capability_t capability; + + status = sai_query_attribute_capability(gSwitchId, sai_object, attr_id, &capability); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Could not query switch level DSCP to TC map %d", status); + return false; + } + else + { + if (capability.set_implemented) + { + return true; + } + else + { + return false; + } + } +} diff --git a/orchagent/switchorch.h b/orchagent/switchorch.h index 86181e19df..46d165bd48 100644 --- a/orchagent/switchorch.h +++ b/orchagent/switchorch.h @@ -28,6 +28,7 @@ class SwitchOrch : public Orch void restartCheckReply(const std::string &op, const std::string &data, std::vector &values); bool setAgingFDB(uint32_t sec); void set_switch_capability(const std::vector& values); + bool querySwitchDscpToTcCapability(sai_object_type_t sai_object, sai_attr_id_t attr_id); private: void doTask(Consumer &consumer); void doTask(swss::SelectableTimer &timer); From bb0733aa67ffc4e430e70bcf2db2dc6316172b32 Mon Sep 17 00:00:00 2001 From: Stepan Blyshchak <38952541+stepanblyschak@users.noreply.github.com> Date: Tue, 23 Nov 2021 04:32:49 +0200 Subject: [PATCH 03/14] [aclorch] Add ACL_TABLE_TYPE configuration (#1982) * [aclorch] Add ACL_TABLE_TYPE configuration Added an API to create a table with configurable ACL table type (matches, bpoints, actions). Implemented a handler for new ACL_TABLE_TYPE CONFIG DB table. Implemented UT for the above. HLD: Azure/SONiC#867 DEPENDS ON: Azure/sonic-swss-common#546 Azure/sonic-sairedis#957 I implemented ACL table type concept. Till this change, there are predefined ACL table types orchagent knows about (L3, L3V6, etc.) and if other orch requires a custom table a new table type needs to be defined in aclorch. This PR addresses this limitation by introducing AclTableType which can be constructed from a set of matches, actions and bpoint types user needs. There is also a new handler for ACL_TABLE_TYPE table which is used for user to define table types. Currently, some of built-in ACL table types that requires special handling are distinguished from others by their names (TABLE_TYPE_MIRROR, TABLE_TYPE_MIRRORV6) and a special handling is performed by an AclOrch. Signed-off-by: Stepan Blyshchak --- doc/Configuration.md | 40 + doc/swss-schema.md | 24 +- orchagent/aclorch.cpp | 1874 ++++++++++++++-------------- orchagent/aclorch.h | 211 ++-- orchagent/acltable.h | 24 +- orchagent/muxorch.cpp | 18 +- orchagent/muxorch.h | 2 +- orchagent/orchdaemon.cpp | 10 +- orchagent/pbh/pbhrule.cpp | 2 +- orchagent/pbhorch.cpp | 13 +- orchagent/pfcactionhandler.cpp | 25 +- orchagent/pfcactionhandler.h | 2 +- orchagent/saihelper.h | 3 + tests/dvslib/dvs_acl.py | 29 + tests/mock_tests/aclorch_ut.cpp | 423 ++++++- tests/test_acl.py | 89 +- tests/test_acl_egress_table.py | 19 +- tests/test_mirror_ipv6_combined.py | 79 -- 18 files changed, 1673 insertions(+), 1214 deletions(-) diff --git a/doc/Configuration.md b/doc/Configuration.md index 039e749d37..40865366f6 100644 --- a/doc/Configuration.md +++ b/doc/Configuration.md @@ -286,6 +286,46 @@ and migration plan ``` +***ACL table type configuration example*** +``` +{ + "ACL_TABLE_TYPE": { + "CUSTOM_L3": { + "MATCHES": [ + "IN_PORTS", + "OUT_PORTS", + "SRC_IP" + ], + "ACTIONS": [ + "PACKET_ACTION", + "MIRROR_INGRESS_ACTION" + ], + "BIND_POINTS": [ + "PORT", + "LAG" + ] + } + }, + "ACL_TABLE": { + "DATAACL": { + "STAGE": "INGRESS", + "TYPE": "CUSTOM_L3", + "PORTS": [ + "Ethernet0", + "PortChannel1" + ] + } + }, + "ACL_RULE": { + "DATAACL|RULE0": { + "PRIORITY": "999", + "PACKET_ACTION": "DROP", + "SRC_IP": "1.1.1.1/32", + } + } +} +``` + ### BGP Sessions BGP session configuration is defined in **BGP_NEIGHBOR** table. BGP diff --git a/doc/swss-schema.md b/doc/swss-schema.md index 1fe9ef9b2c..ec28eb6c0f 100644 --- a/doc/swss-schema.md +++ b/doc/swss-schema.md @@ -569,15 +569,37 @@ It's possible to create separate configuration files for different ASIC platform ---------------------------------------------- +### ACL\_TABLE\_TYPE +Stores a definition of table - set of matches, actions and bind point types. ACL_TABLE references a key inside this table in "type" field. + +``` +key: ACL_TABLE_TYPE:name ; key of the ACL table type entry. The name is arbitary name user chooses. +; field = value +matches = match-list ; list of matches for this table, matches are same as in ACL_RULE table. +actions = action-list ; list of actions for this table, actions are same as in ACL_RULE table. +bind_points = bind-points-list ; list of bind point types for this table. + +; values annotation +match = 1*64VCHAR +match-list = [1-max-matches]*match +action = 1*64VCHAR +action-list = [1-max-actions]*action +bind-point = port/lag +bind-points-list = [1-max-bind-points]*bind-point +``` + ### ACL\_TABLE Stores information about ACL tables on the switch. Port names are defined in [port_config.ini](../portsyncd/port_config.ini). key = ACL_TABLE:name ; acl_table_name must be unique ;field = value policy_desc = 1*255VCHAR ; name of the ACL policy table description - type = "mirror"/"l3"/"l3v6" ; type of acl table, every type of + type = 1*255VCHAR ; type of acl table, every type of ; table defines the match/action a ; specific set of match and actions. + ; There are pre-defined table types like + ; "MIRROR", "MIRRORV6", "MIRROR_DSCP", + ; "L3", "L3V6", "MCLAG", "PFCWD", "DROP". ports = [0-max_ports]*port_name ; the ports to which this ACL ; table is applied, can be emtry ; value annotations diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index e3b279a482..900299b3d5 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -32,7 +32,10 @@ extern CrmOrch *gCrmOrch; #define MIN_VLAN_ID 1 // 0 is a reserved VLAN ID #define MAX_VLAN_ID 4095 // 4096 is a reserved VLAN ID +#define STATE_DB_ACL_ACTION_FIELD_IS_ACTION_LIST_MANDATORY "is_action_list_mandatory" +#define STATE_DB_ACL_ACTION_FIELD_ACTION_LIST "action_list" #define COUNTERS_ACL_COUNTER_RULE_MAP "ACL_COUNTER_RULE_MAP" + #define ACL_COUNTER_DEFAULT_POLLING_INTERVAL_MS 10000 // ms #define ACL_COUNTER_DEFAULT_ENABLED_STATE false @@ -75,6 +78,12 @@ static acl_range_type_lookup_t aclRangeTypeLookup = { MATCH_L4_DST_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE }, }; +static acl_bind_point_type_lookup_t aclBindPointTypeLookup = +{ + { BIND_POINT_TYPE_PORT, SAI_ACL_BIND_POINT_TYPE_PORT }, + { BIND_POINT_TYPE_PORTCHANNEL, SAI_ACL_BIND_POINT_TYPE_LAG }, +}; + static acl_rule_attr_lookup_t aclL3ActionLookup = { { ACTION_PACKET_ACTION, SAI_ACL_ENTRY_ATTR_ACTION_PACKET_ACTION }, @@ -112,20 +121,6 @@ static acl_dtel_flow_op_type_lookup_t aclDTelFlowOpTypeLookup = { DTEL_FLOW_OP_IOAM, SAI_ACL_DTEL_FLOW_OP_IOAM } }; -static acl_table_type_lookup_t aclTableTypeLookUp = -{ - { TABLE_TYPE_L3, ACL_TABLE_L3 }, - { TABLE_TYPE_L3V6, ACL_TABLE_L3V6 }, - { TABLE_TYPE_MIRROR, ACL_TABLE_MIRROR }, - { TABLE_TYPE_MIRRORV6, ACL_TABLE_MIRRORV6 }, - { TABLE_TYPE_MIRROR_DSCP, ACL_TABLE_MIRROR_DSCP }, - { TABLE_TYPE_CTRLPLANE, ACL_TABLE_CTRLPLANE }, - { TABLE_TYPE_DTEL_FLOW_WATCHLIST, ACL_TABLE_DTEL_FLOW_WATCHLIST }, - { TABLE_TYPE_DTEL_DROP_WATCHLIST, ACL_TABLE_DTEL_DROP_WATCHLIST }, - { TABLE_TYPE_MCLAG, ACL_TABLE_MCLAG }, - { TABLE_TYPE_DROP, ACL_TABLE_DROP } -}; - static acl_stage_type_lookup_t aclStageLookUp = { {STAGE_INGRESS, ACL_STAGE_INGRESS }, @@ -136,16 +131,24 @@ static const acl_capabilities_t defaultAclActionsSupported = { { ACL_STAGE_INGRESS, + AclActionCapabilities { - SAI_ACL_ACTION_TYPE_PACKET_ACTION, - SAI_ACL_ACTION_TYPE_MIRROR_INGRESS, - SAI_ACL_ACTION_TYPE_NO_NAT + { + SAI_ACL_ACTION_TYPE_PACKET_ACTION, + SAI_ACL_ACTION_TYPE_MIRROR_INGRESS, + SAI_ACL_ACTION_TYPE_NO_NAT + }, + false } }, { ACL_STAGE_EGRESS, + AclActionCapabilities { - SAI_ACL_ACTION_TYPE_PACKET_ACTION + { + SAI_ACL_ACTION_TYPE_PACKET_ACTION + }, + false } } }; @@ -170,6 +173,24 @@ static map aclCounterLookup = {SAI_ACL_COUNTER_ATTR_ENABLE_PACKET_COUNT, SAI_ACL_COUNTER_ATTR_PACKETS}, }; +static sai_acl_table_attr_t AclEntryFieldToAclTableField(sai_acl_entry_attr_t attr) +{ + if (!IS_ATTR_ID_IN_RANGE(attr, ACL_ENTRY, FIELD)) + { + SWSS_LOG_THROW("ACL entry attribute is not a in a range of SAI_ACL_ENTRY_ATTR_FIELD_* attribute: %d", attr); + } + return static_cast(SAI_ACL_TABLE_ATTR_FIELD_START + (attr - SAI_ACL_ENTRY_ATTR_FIELD_START)); +} + +static sai_acl_action_type_t AclEntryActionToAclAction(sai_acl_entry_attr_t attr) +{ + if (!IS_ATTR_ID_IN_RANGE(attr, ACL_ENTRY, ACTION)) + { + SWSS_LOG_THROW("ACL entry attribute is not a in a range of SAI_ACL_ENTRY_ATTR_ACTION_* attribute: %d", attr); + } + return static_cast(attr - SAI_ACL_ENTRY_ATTR_ACTION_START); +} + static string getAttributeIdName(sai_object_type_t objectType, sai_attr_id_t attr) { const auto* meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_ENTRY, attr); @@ -180,18 +201,284 @@ static string getAttributeIdName(sai_object_type_t objectType, sai_attr_id_t att return meta->attridname; } -AclRule::AclRule(AclOrch *pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : +AclTableMatchInterface::AclTableMatchInterface(sai_acl_table_attr_t matchField): + m_matchField(matchField) +{ + if (!IS_ATTR_ID_IN_RANGE(m_matchField, ACL_TABLE, FIELD)) + { + SWSS_LOG_THROW("Invalid match table attribute %d", m_matchField); + } +} + +sai_acl_table_attr_t AclTableMatchInterface::getId() const +{ + return m_matchField; +} + +AclTableMatch::AclTableMatch(sai_acl_table_attr_t matchField): + AclTableMatchInterface(matchField) +{ + const auto meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_ACL_TABLE, getId()); + if (!meta) + { + SWSS_LOG_THROW("Failed to get metadata for attribute for SAI_OBJECT_TYPE_ACL_TABLE: attribute %d", getId()); + } + + if (meta->attrvaluetype != SAI_ATTR_VALUE_TYPE_BOOL) + { + SWSS_LOG_THROW("This API does not allow to set match with a non boolean value type"); + } +} + +sai_attribute_t AclTableMatch::toSaiAttribute() +{ + return sai_attribute_t{ + .id = getId(), + .value = { + .booldata = true, + }, + }; +} + +bool AclTableMatch::validateAclRuleMatch(const AclRule& rule) const +{ + // no need to validate rule configuration + return true; +} + +AclTableRangeMatch::AclTableRangeMatch(set rangeTypes): + AclTableMatchInterface(SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE), + m_rangeList(rangeTypes.begin(), rangeTypes.end()) +{ +} + +sai_attribute_t AclTableRangeMatch::toSaiAttribute() +{ + return sai_attribute_t{ + .id = getId(), + .value = { + .s32list = { + .count = static_cast(m_rangeList.size()), + .list = m_rangeList.data(), + }, + }, + }; +} + +bool AclTableRangeMatch::validateAclRuleMatch(const AclRule& rule) const +{ + const auto& rangeConfig = rule.getRangeConfig(); + for (const auto& range: rangeConfig) + { + if (find(m_rangeList.begin(), m_rangeList.end(), range.rangeType) == m_rangeList.end()) + { + SWSS_LOG_ERROR("Range match %s is not supported on table %s", + sai_metadata_get_acl_range_type_name(range.rangeType), rule.getTableId().c_str()); + return false; + } + } + + return true; +} + +string AclTableType::getName() const +{ + return m_name; +} + +const set& AclTableType::getBindPointTypes() const +{ + return m_bpointTypes; +} + +const map>& AclTableType::getMatches() const +{ + return m_matches; +} + +const set& AclTableType::getActions() const +{ + return m_aclAcitons; +} + +AclTableTypeBuilder& AclTableTypeBuilder::withName(string name) +{ + m_tableType.m_name = name; + return *this; +} + +AclTableTypeBuilder& AclTableTypeBuilder::withBindPointType(sai_acl_bind_point_type_t bpointType) +{ + m_tableType.m_bpointTypes.insert(bpointType); + return *this; +} + +AclTableTypeBuilder& AclTableTypeBuilder::withMatch(shared_ptr match) +{ + m_tableType.m_matches.emplace(match->getId(), match); + return *this; +} + +AclTableTypeBuilder& AclTableTypeBuilder::withAction(sai_acl_action_type_t action) +{ + m_tableType.m_aclAcitons.insert(action); + return *this; +} + +AclTableType AclTableTypeBuilder::build() +{ + auto tableType = m_tableType; + m_tableType = AclTableType(); + return tableType; +} + +bool AclTableTypeParser::parse(const std::string& key, + const vector& fieldValues, + AclTableTypeBuilder& builder) +{ + builder.withName(key); + + for (const auto& fieldValue: fieldValues) + { + auto field = to_upper(fvField(fieldValue)); + auto value = to_upper(fvValue(fieldValue)); + + SWSS_LOG_DEBUG("field %s, value %s", field.c_str(), value.c_str()); + + if (field == ACL_TABLE_TYPE_MATCHES) + { + if (!parseAclTableTypeMatches(value, builder)) + { + return false; + } + } + else if (field == ACL_TABLE_TYPE_ACTIONS) + { + if (!parseAclTableTypeActions(value, builder)) + { + return false; + } + } + else if (field == ACL_TABLE_TYPE_BPOINT_TYPES) + { + if (!parseAclTableTypeBindPointTypes(value, builder)) + { + return false; + } + } + else + { + SWSS_LOG_ERROR("Unknown field %s: value %s", field.c_str(), value.c_str()); + return false; + } + } + + return true; +} + +bool AclTableTypeParser::parseAclTableTypeMatches(const std::string& value, AclTableTypeBuilder& builder) +{ + auto matches = tokenize(value, comma); + set saiRangeTypes; + + for (const auto& match: matches) + { + auto matchIt = aclMatchLookup.find(match); + auto rangeMatchIt = aclRangeTypeLookup.find(match); + + if (rangeMatchIt != aclRangeTypeLookup.end()) + { + auto rangeType = rangeMatchIt->second; + saiRangeTypes.insert(rangeType); + } + else if (matchIt != aclMatchLookup.end()) + { + auto saiMatch = AclEntryFieldToAclTableField(matchIt->second); + builder.withMatch(make_unique(saiMatch)); + } + else + { + SWSS_LOG_ERROR("Unknown match %s", match.c_str()); + return false; + } + } + + if (!saiRangeTypes.empty()) + { + builder.withMatch(make_unique(saiRangeTypes)); + } + + return true; +} + +bool AclTableTypeParser::parseAclTableTypeActions(const std::string& value, AclTableTypeBuilder& builder) +{ + auto actions = tokenize(value, comma); + for (const auto& action: actions) + { + sai_acl_entry_attr_t saiActionAttr = SAI_ACL_ENTRY_ATTR_ACTION_END; + + auto l3Action = aclL3ActionLookup.find(action); + auto mirrorAction = aclMirrorStageLookup.find(action); + auto dtelAction = aclDTelActionLookup.find(action); + + if (l3Action != aclL3ActionLookup.end()) + { + saiActionAttr = l3Action->second; + } + else if (mirrorAction != aclMirrorStageLookup.end()) + { + saiActionAttr = mirrorAction->second; + } + else if (dtelAction != aclDTelActionLookup.end()) + { + saiActionAttr = dtelAction->second; + } + else + { + SWSS_LOG_ERROR("Unknown action %s", action.c_str()); + return false; + } + + builder.withAction(AclEntryActionToAclAction(saiActionAttr)); + } + + return true; +} + +bool AclTableTypeParser::parseAclTableTypeBindPointTypes(const std::string& value, AclTableTypeBuilder& builder) +{ + auto bpointTypes = tokenize(value, comma); + for (const auto& bpointType: bpointTypes) + { + auto bpointIt = aclBindPointTypeLookup.find(bpointType); + if (bpointIt == aclBindPointTypeLookup.end()) + { + SWSS_LOG_ERROR("Unknown bind point %s", bpointType.c_str()); + return false; + } + + auto saiBpointType = bpointIt->second; + builder.withBindPointType(saiBpointType); + } + + return true; +} + +AclRule::AclRule(AclOrch *pAclOrch, string rule, string table, bool createCounter) : m_pAclOrch(pAclOrch), m_id(rule), - m_tableId(table), - m_tableType(type), - m_tableOid(SAI_NULL_OBJECT_ID), m_ruleOid(SAI_NULL_OBJECT_ID), m_counterOid(SAI_NULL_OBJECT_ID), m_priority(0), m_createCounter(createCounter) { - m_tableOid = pAclOrch->getTableById(m_tableId); + auto tableOid = pAclOrch->getTableById(table); + m_pTable = pAclOrch->getTableByOid(tableOid); + if (!m_pTable) + { + SWSS_LOG_THROW("Failed to find ACL table %s. ACL table must exist at the time of creating AclRule", table.c_str()); + } } bool AclRule::validateAddPriority(string attr_name, string attr_value) @@ -396,7 +683,7 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) m_rangeConfig.push_back(rangeConfig); - return true; + return m_pTable->validateAclRuleMatch(SAI_ACL_ENTRY_ATTR_FIELD_ACL_RANGE_TYPE, *this); } else if (attr_name == MATCH_TC) { @@ -439,7 +726,7 @@ bool AclRule::validateAddMatch(string attr_name, string attr_value) // TODO: For backwards compatibility, users can substitute IP_PROTOCOL for NEXT_HEADER. // This should be removed in a future release. - if ((m_tableType == ACL_TABLE_MIRRORV6 || m_tableType == ACL_TABLE_L3V6) + if ((m_pTable->type.getName() == TABLE_TYPE_MIRRORV6 || m_pTable->type.getName() == TABLE_TYPE_L3V6) && attr_name == MATCH_IP_PROTOCOL) { SWSS_LOG_WARN("Support for IP protocol on IPv6 tables will be removed in a future release, please switch to using NEXT_HEADER instead!"); @@ -485,7 +772,6 @@ bool AclRule::createRule() { SWSS_LOG_ENTER(); - sai_object_id_t table_oid = m_pAclOrch->getTableById(m_tableId); vector rule_attrs; sai_object_id_t range_objects[2]; sai_object_list_t range_object_list = {0, range_objects}; @@ -495,7 +781,7 @@ bool AclRule::createRule() // store table oid this rule belongs to attr.id = SAI_ACL_ENTRY_ATTR_TABLE_ID; - attr.value.oid = table_oid; + attr.value.oid = m_pTable->getOid(); rule_attrs.push_back(attr); attr.id = SAI_ACL_ENTRY_ATTR_PRIORITY; @@ -562,7 +848,7 @@ bool AclRule::createRule() decreaseNextHopRefCount(); } - gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, m_tableOid); + gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, m_pTable->getOid()); return (status == SAI_STATUS_SUCCESS); } @@ -599,13 +885,7 @@ void AclRule::decreaseNextHopRefCount() bool AclRule::isActionSupported(sai_acl_entry_attr_t action) const { - auto action_type = AclOrch::getAclActionFromAclEntry(action); - const auto* pTable = m_pAclOrch->getTableByOid(m_tableOid); - if (pTable == nullptr) - { - SWSS_LOG_THROW("ACL table does not exist for oid %" PRIu64, m_tableOid); - } - return m_pAclOrch->isAclActionSupported(pTable->stage, action_type); + return m_pAclOrch->isAclActionSupported(m_pTable->stage, AclEntryActionToAclAction(action)); } bool AclRule::removeRule() @@ -624,7 +904,7 @@ bool AclRule::removeRule() return false; } - gCrmOrch->decCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, m_tableOid); + gCrmOrch->decCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_ENTRY, m_pTable->getOid()); m_ruleOid = SAI_NULL_OBJECT_ID; @@ -870,7 +1150,7 @@ bool AclRule::setAction(sai_acl_entry_attr_t actionId, sai_acl_action_data_t act if (meta->isenum) { // if ACL action attribute requires enum value check if value is supported by the ASIC - if (!m_pAclOrch->isAclActionEnumValueSupported(AclOrch::getAclActionFromAclEntry(actionId), actionData.parameter)) + if (!m_pAclOrch->isAclActionEnumValueSupported(AclEntryActionToAclAction(actionId), actionData.parameter)) { SWSS_LOG_ERROR("Action %s is not supported by ASIC", getAttributeIdName(SAI_OBJECT_TYPE_ACL_ENTRY, actionId).c_str()); @@ -884,6 +1164,11 @@ bool AclRule::setAction(sai_acl_entry_attr_t actionId, sai_acl_action_data_t act m_actions[actionId] = SaiAttrWrapper(SAI_OBJECT_TYPE_ACL_ENTRY, attr); + if (!m_pTable->validateAclRuleAction(actionId, *this)) + { + return false; + } + return true; } @@ -895,6 +1180,11 @@ bool AclRule::setMatch(sai_acl_entry_attr_t matchId, sai_acl_field_data_t matchD m_matches[matchId] = SaiAttrWrapper(SAI_OBJECT_TYPE_ACL_ENTRY, attr); + if (!m_pTable->validateAclRuleMatch(matchId, *this)) + { + return false; + } + return true; } @@ -936,94 +1226,78 @@ vector AclRule::getInPorts() const return inPorts; } -shared_ptr AclRule::makeShared(acl_table_type_t type, AclOrch *acl, MirrorOrch *mirror, DTelOrch *dtel, const string& rule, const string& table, const KeyOpFieldsValuesTuple& data) +string AclRule::getId() const { - string action; - bool action_found = false; - /* Find action configured by user. Based on action type create rule. */ - for (const auto& itr : kfvFieldsValues(data)) - { - string attr_name = to_upper(fvField(itr)); - string attr_value = fvValue(itr); - if (aclL3ActionLookup.find(attr_name) != aclL3ActionLookup.cend() || - aclMirrorStageLookup.find(attr_name) != aclMirrorStageLookup.cend() || - /* handle "MIRROR_ACTION" key without mirror stage specified for backward compatibility */ - attr_name == ACTION_MIRROR_ACTION || - aclDTelActionLookup.find(attr_name) != aclDTelActionLookup.cend()) - { - action_found = true; - action = attr_name; - break; - } - } + return m_id; +} - if (!action_found) - { - throw runtime_error("ACL rule action is not found in rule " + rule); - } +string AclRule::getTableId() const +{ + return m_pTable->getId(); +} - if (type != ACL_TABLE_L3 && - type != ACL_TABLE_L3V6 && - type != ACL_TABLE_MIRROR && - type != ACL_TABLE_MIRRORV6 && - type != ACL_TABLE_MIRROR_DSCP && - type != ACL_TABLE_DTEL_FLOW_WATCHLIST && - type != ACL_TABLE_DTEL_DROP_WATCHLIST && - type != ACL_TABLE_MCLAG && - type != ACL_TABLE_DROP) - { - throw runtime_error("Unknown table type"); - } +sai_object_id_t AclRule::getOid() const +{ + return m_ruleOid; +} - /* Mirror rules can exist in both tables */ - if (aclMirrorStageLookup.find(action) != aclMirrorStageLookup.cend() || - action == ACTION_MIRROR_ACTION /* implicitly ingress in old schema */) - { - return make_shared(acl, mirror, rule, table, type); - } - /* L3 rules can exist only in L3 table */ - else if (type == ACL_TABLE_L3) - { - return make_shared(acl, rule, table, type); - } - /* L3V6 rules can exist only in L3V6 table */ - else if (type == ACL_TABLE_L3V6) - { - return make_shared(acl, rule, table, type); - } - /* Pfcwd rules can exist only in PFCWD table */ - else if (type == ACL_TABLE_PFCWD) - { - return make_shared(acl, rule, table, type); - } - else if (type == ACL_TABLE_DTEL_FLOW_WATCHLIST) +sai_object_id_t AclRule::getCounterOid() const +{ + return m_counterOid; +} + +bool AclRule::hasCounter() const +{ + return getCounterOid() != SAI_NULL_OBJECT_ID; +} + +const vector& AclRule::getRangeConfig() const +{ + return m_rangeConfig; +} + +shared_ptr AclRule::makeShared(AclOrch *acl, MirrorOrch *mirror, DTelOrch *dtel, const string& rule, const string& table, const KeyOpFieldsValuesTuple& data) +{ + shared_ptr aclRule; + + for (const auto& itr: kfvFieldsValues(data)) { - if (dtel) + auto action = to_upper(fvField(itr)); + if (aclMirrorStageLookup.find(action) != aclMirrorStageLookup.cend() || + action == ACTION_MIRROR_ACTION /* implicitly ingress in old schema */) { - return make_shared(acl, dtel, rule, table, type); - } else { - throw runtime_error("DTel feature is not enabled. Watchlists cannot be configured"); + return make_shared(acl, mirror, rule, table); } - } - else if (type == ACL_TABLE_DTEL_DROP_WATCHLIST) - { - if (dtel) + else if (aclL3ActionLookup.find(action) != aclL3ActionLookup.cend()) { - return make_shared(acl, dtel, rule, table, type); - } else { - throw runtime_error("DTel feature is not enabled. Watchlists cannot be configured"); + return make_shared(acl, rule, table); + } + else if (aclDTelFlowOpTypeLookup.find(action) != aclDTelFlowOpTypeLookup.cend()) + { + if (!dtel) + { + throw runtime_error("DTel feature is not enabled. Watchlists cannot be configured"); + } + + if (action == ACTION_DTEL_DROP_REPORT_ENABLE || + action == ACTION_DTEL_TAIL_DROP_REPORT_ENABLE || + action == ACTION_DTEL_REPORT_ALL_PACKETS) + { + return make_shared(acl, dtel, rule, table); + } + else + { + return make_shared(acl, dtel, rule, table); + } } } - else if (type == ACL_TABLE_MCLAG) - { - return make_shared(acl, rule, table, type); - } - else if (type == ACL_TABLE_DROP) + + if (!aclRule) { - return make_shared(acl, rule, table, type); + throw runtime_error("ACL rule action is not found in rule " + rule); } - throw runtime_error("Wrong combination of table type and action in rule " + rule); + return aclRule; } bool AclRule::enableCounter() @@ -1037,7 +1311,7 @@ bool AclRule::enableCounter() if (m_ruleOid == SAI_NULL_OBJECT_ID) { - SWSS_LOG_ERROR("ACL rule %s doesn't exist in ACL table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_ERROR("ACL rule %s doesn't exist in ACL table %s", m_id.c_str(), m_pTable->getId().c_str()); return false; } @@ -1055,7 +1329,7 @@ bool AclRule::enableCounter() sai_status_t status = sai_acl_api->set_acl_entry_attribute(m_ruleOid, &attr); if (status != SAI_STATUS_SUCCESS) { - SWSS_LOG_ERROR("Failed to enable counter for ACL rule %s in ACL table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_ERROR("Failed to enable counter for ACL rule %s in ACL table %s", m_id.c_str(), m_pTable->getId().c_str()); removeCounter(); return false; } @@ -1074,7 +1348,7 @@ bool AclRule::disableCounter() if (m_ruleOid == SAI_NULL_OBJECT_ID) { - SWSS_LOG_ERROR("ACL rule %s doesn't exist in ACL table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_ERROR("ACL rule %s doesn't exist in ACL table %s", m_id.c_str(), m_pTable->getId().c_str()); return false; } @@ -1087,7 +1361,7 @@ bool AclRule::disableCounter() sai_status_t status = sai_acl_api->set_acl_entry_attribute(m_ruleOid, &attr); if (status != SAI_STATUS_SUCCESS) { - SWSS_LOG_ERROR("Failed to disable counter for ACL rule %s in ACL table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_ERROR("Failed to disable counter for ACL rule %s in ACL table %s", m_id.c_str(), m_pTable->getId().c_str()); return false; } @@ -1112,7 +1386,7 @@ bool AclRule::createCounter() } attr.id = SAI_ACL_COUNTER_ATTR_TABLE_ID; - attr.value.oid = m_tableOid; + attr.value.oid = m_pTable->getOid(); counter_attrs.push_back(attr); for (const auto& counterAttrPair: aclCounterLookup) @@ -1124,13 +1398,13 @@ bool AclRule::createCounter() if (sai_acl_api->create_acl_counter(&m_counterOid, gSwitchId, (uint32_t)counter_attrs.size(), counter_attrs.data()) != SAI_STATUS_SUCCESS) { - SWSS_LOG_ERROR("Failed to create counter for the rule %s in table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_ERROR("Failed to create counter for the rule %s in table %s", m_id.c_str(), m_pTable->getId().c_str()); return false; } - gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, m_tableOid); + gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, m_pTable->getOid()); - SWSS_LOG_INFO("Created counter for the rule %s in table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_INFO("Created counter for the rule %s in table %s", m_id.c_str(), m_pTable->getId().c_str()); return true; } @@ -1159,25 +1433,25 @@ bool AclRule::removeCounter() if (sai_acl_api->remove_acl_counter(m_counterOid) != SAI_STATUS_SUCCESS) { - SWSS_LOG_ERROR("Failed to remove ACL counter for rule %s in table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_ERROR("Failed to remove ACL counter for rule %s in table %s", m_id.c_str(), m_pTable->getId().c_str()); return false; } - gCrmOrch->decCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, m_tableOid); + gCrmOrch->decCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, m_pTable->getOid()); m_counterOid = SAI_NULL_OBJECT_ID; - SWSS_LOG_INFO("Removed counter for the rule %s in table %s", m_id.c_str(), m_tableId.c_str()); + SWSS_LOG_INFO("Removed counter for the rule %s in table %s", m_id.c_str(), m_pTable->getId().c_str()); return true; } -AclRuleL3::AclRuleL3(AclOrch *aclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : - AclRule(aclOrch, rule, table, type, createCounter) +AclRulePacket::AclRulePacket(AclOrch *aclOrch, string rule, string table, bool createCounter) : + AclRule(aclOrch, rule, table, createCounter) { } -bool AclRuleL3::validateAddAction(string attr_name, string _attr_value) +bool AclRulePacket::validateAddAction(string attr_name, string _attr_value) { SWSS_LOG_ENTER(); @@ -1253,7 +1527,7 @@ bool AclRuleL3::validateAddAction(string attr_name, string _attr_value) } // This method should return sai attribute id of the redirect destination -sai_object_id_t AclRuleL3::getRedirectObjectId(const string& redirect_value) +sai_object_id_t AclRulePacket::getRedirectObjectId(const string& redirect_value) { string target = redirect_value; @@ -1326,36 +1600,7 @@ sai_object_id_t AclRuleL3::getRedirectObjectId(const string& redirect_value) return SAI_NULL_OBJECT_ID; } -bool AclRuleL3::validateAddMatch(string attr_name, string attr_value) -{ - if (attr_name == MATCH_DSCP) - { - SWSS_LOG_ERROR("DSCP match is not supported for table type L3"); - return false; - } - - if (attr_name == MATCH_SRC_IPV6 || attr_name == MATCH_DST_IPV6) - { - SWSS_LOG_ERROR("IPv6 address match is not supported for table type L3"); - return false; - } - - if (attr_name == MATCH_ICMPV6_TYPE || attr_name == MATCH_ICMPV6_CODE) - { - SWSS_LOG_ERROR("ICMPv6 match is not supported for table type L3"); - return false; - } - - if (attr_name == MATCH_NEXT_HEADER) - { - SWSS_LOG_ERROR("IPv6 Next Header match is not supported for table type L3"); - return false; - } - - return AclRule::validateAddMatch(attr_name, attr_value); -} - -bool AclRuleL3::validate() +bool AclRulePacket::validate() { SWSS_LOG_ENTER(); @@ -1367,80 +1612,21 @@ bool AclRuleL3::validate() return true; } -void AclRuleL3::onUpdate(SubjectType, void *) +void AclRulePacket::onUpdate(SubjectType, void *) { // Do nothing } -AclRulePfcwd::AclRulePfcwd(AclOrch *aclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : - AclRuleL3(aclOrch, rule, table, type, createCounter) +AclRuleMirror::AclRuleMirror(AclOrch *aclOrch, MirrorOrch *mirror, string rule, string table) : + AclRule(aclOrch, rule, table), + m_state(false), + m_pMirrorOrch(mirror) { } -bool AclRulePfcwd::validateAddMatch(string attr_name, string attr_value) +bool AclRuleMirror::validateAddAction(string attr_name, string attr_value) { - return AclRule::validateAddMatch(attr_name, attr_value); -} - -AclRuleMux::AclRuleMux(AclOrch *aclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : - AclRuleL3(aclOrch, rule, table, type, createCounter) -{ -} - -bool AclRuleMux::validateAddMatch(string attr_name, string attr_value) -{ - return AclRule::validateAddMatch(attr_name, attr_value); -} - -AclRuleL3V6::AclRuleL3V6(AclOrch *aclOrch, string rule, string table, acl_table_type_t type) : - AclRuleL3(aclOrch, rule, table, type) -{ -} - - -bool AclRuleL3V6::validateAddMatch(string attr_name, string attr_value) -{ - if (attr_name == MATCH_DSCP) - { - SWSS_LOG_ERROR("DSCP match is not supported for table type L3V6"); - return false; - } - - if (attr_name == MATCH_SRC_IP || attr_name == MATCH_DST_IP) - { - SWSS_LOG_ERROR("IPv4 address match is not supported for table type L3V6"); - return false; - } - - if (attr_name == MATCH_ICMP_TYPE || attr_name == MATCH_ICMP_CODE) - { - SWSS_LOG_ERROR("ICMPv4 match is not supported for table type L3V6"); - return false; - } - - if (attr_name == MATCH_ETHER_TYPE) - { - SWSS_LOG_ERROR("Ethertype match is not supported for table type L3V6"); - return false; - } - - // TODO: For backwards compatibility, users can substitute IP_PROTOCOL for NEXT_HEADER. - // Should add a check for IP_PROTOCOL in a future release. - - return AclRule::validateAddMatch(attr_name, attr_value); -} - - -AclRuleMirror::AclRuleMirror(AclOrch *aclOrch, MirrorOrch *mirror, string rule, string table, acl_table_type_t type) : - AclRule(aclOrch, rule, table, type), - m_state(false), - m_pMirrorOrch(mirror) -{ -} - -bool AclRuleMirror::validateAddAction(string attr_name, string attr_value) -{ - SWSS_LOG_ENTER(); + SWSS_LOG_ENTER(); sai_acl_entry_attr_t action; @@ -1465,70 +1651,6 @@ bool AclRuleMirror::validateAddAction(string attr_name, string attr_value) return setAction(action, sai_acl_action_data_t{}); } -bool AclRuleMirror::validateAddMatch(string attr_name, string attr_value) -{ - if ((m_tableType == ACL_TABLE_L3 || m_tableType == ACL_TABLE_L3V6) - && attr_name == MATCH_DSCP) - { - SWSS_LOG_ERROR("DSCP match is not supported for the table of type L3"); - return false; - } - - if ((m_tableType == ACL_TABLE_MIRROR_DSCP && - aclMatchLookup.find(attr_name) != aclMatchLookup.end() && - attr_name != MATCH_DSCP)) - { - SWSS_LOG_ERROR("%s match is not supported for the table of type MIRROR_DSCP", - attr_name.c_str()); - return false; - } - - /* - * Type of Tables and Supported Match Types (Configuration) - * |---------------------------------------------------| - * | Match Type | TABLE_MIRROR | TABLE_MIRRORV6 | - * |---------------------------------------------------| - * | MATCH_SRC_IP | √ | | - * | MATCH_DST_IP | √ | | - * |---------------------------------------------------| - * | MATCH_ICMP_TYPE | √ | | - * | MATCH_ICMP_CODE | √ | | - * |---------------------------------------------------| - * | MATCH_ICMPV6_TYPE | | √ | - * | MATCH_ICMPV6_CODE | | √ | - * |---------------------------------------------------| - * | MATCH_SRC_IPV6 | | √ | - * | MATCH_DST_IPV6 | | √ | - * |---------------------------------------------------| - * | MARTCH_ETHERTYPE | √ | | - * |---------------------------------------------------| - */ - - if (m_tableType == ACL_TABLE_MIRROR && - (attr_name == MATCH_SRC_IPV6 || attr_name == MATCH_DST_IPV6 || - attr_name == MATCH_ICMPV6_TYPE || attr_name == MATCH_ICMPV6_CODE || - attr_name == MATCH_NEXT_HEADER)) - { - SWSS_LOG_ERROR("%s match is not supported for the table of type MIRROR", - attr_name.c_str()); - return false; - } - - // TODO: For backwards compatibility, users can substitute IP_PROTOCOL for NEXT_HEADER. - // This check should be expanded to include IP_PROTOCOL in a future release. - if (m_tableType == ACL_TABLE_MIRRORV6 && - (attr_name == MATCH_SRC_IP || attr_name == MATCH_DST_IP || - attr_name == MATCH_ICMP_TYPE || attr_name == MATCH_ICMP_CODE || - attr_name == MATCH_ETHER_TYPE)) - { - SWSS_LOG_ERROR("%s match is not supported for the table of type MIRRORv6", - attr_name.c_str()); - return false; - } - - return AclRule::validateAddMatch(attr_name, attr_value); -} - bool AclRuleMirror::validate() { SWSS_LOG_ENTER(); @@ -1630,6 +1752,19 @@ bool AclRuleMirror::deactivate() return true; } +bool AclRuleMirror::update(const AclRule& rule) +{ + auto mirrorRule = dynamic_cast(&rule); + if (!mirrorRule) + { + SWSS_LOG_ERROR("Cannot update mirror rule with a rule of a different type"); + return false; + } + + SWSS_LOG_ERROR("Updating mirror rule is currently not implemented"); + return false; +} + void AclRuleMirror::onUpdate(SubjectType type, void *cntx) { if (type != SUBJECT_TYPE_MIRROR_SESSION_CHANGE) @@ -1656,46 +1791,6 @@ void AclRuleMirror::onUpdate(SubjectType type, void *cntx) } } -bool AclRuleMirror::update(const AclRule& rule) -{ - auto mirrorRule = dynamic_cast(&rule); - if (!mirrorRule) - { - SWSS_LOG_ERROR("Cannot update mirror rule with a rule of a different type"); - return false; - } - - SWSS_LOG_ERROR("Updating mirror rule is currently not implemented"); - return false; -} - -AclRuleMclag::AclRuleMclag(AclOrch *aclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : - AclRuleL3(aclOrch, rule, table, type, createCounter) -{ -} - -bool AclRuleMclag::validateAddMatch(string attr_name, string attr_value) -{ - if (attr_name != MATCH_IP_TYPE && attr_name != MATCH_OUT_PORTS) - { - return false; - } - - return AclRule::validateAddMatch(attr_name, attr_value); -} - -bool AclRuleMclag::validate() -{ - SWSS_LOG_ENTER(); - - if (m_matches.size() == 0) - { - return false; - } - - return true; -} - AclTable::AclTable(AclOrch *pAclOrch, string id) noexcept : m_pAclOrch(pAclOrch), id(id) { @@ -1706,20 +1801,11 @@ AclTable::AclTable(AclOrch *pAclOrch) noexcept : m_pAclOrch(pAclOrch) } -bool AclTable::validateAddType(const acl_table_type_t &value) +bool AclTable::validateAddType(const AclTableType &tableType) { SWSS_LOG_ENTER(); - if (value == ACL_TABLE_MIRROR || value == ACL_TABLE_MIRRORV6) - { - if (!m_pAclOrch->isAclMirrorTableSupported(value)) - { - SWSS_LOG_ERROR("Failed to validate type: mirror table is not supported"); - return false; - } - } - - type = value; + type = tableType; return true; } @@ -1775,318 +1861,125 @@ bool AclTable::validateAddPorts(const unordered_set &value) bool AclTable::validate() { - if (type == ACL_TABLE_CTRLPLANE) - return true; - - if (type == ACL_TABLE_UNKNOWN || stage == ACL_STAGE_UNKNOWN) - return false; - - return true; -} - -bool AclTable::create() -{ - SWSS_LOG_ENTER(); - - sai_attribute_t attr; - vector table_attrs; - vector bpoint_list; - - // PFC watch dog ACLs are only applied to port - if ((type == ACL_TABLE_PFCWD) || (type == ACL_TABLE_DROP)) + if (type.getName() == TABLE_TYPE_CTRLPLANE) { - bpoint_list = { SAI_ACL_BIND_POINT_TYPE_PORT }; + return true; } - else + + if (stage == ACL_STAGE_UNKNOWN) { - bpoint_list = { SAI_ACL_BIND_POINT_TYPE_PORT, SAI_ACL_BIND_POINT_TYPE_LAG }; + return false; } - attr.id = SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST; - attr.value.s32list.count = static_cast(bpoint_list.size()); - attr.value.s32list.list = bpoint_list.data(); - table_attrs.push_back(attr); - - if (type == ACL_TABLE_PBH) + if (m_pAclOrch->isAclActionListMandatoryOnTableCreation(stage)) { - attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; - attr.value.s32 = SAI_ACL_STAGE_INGRESS; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_GRE_KEY; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_INNER_ETHER_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - sai_status_t status = sai_acl_api->create_acl_table(&m_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); - - if (status == SAI_STATUS_SUCCESS) + if (type.getActions().empty()) { - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_PORT); - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_LAG); + SWSS_LOG_ERROR("Action list for table %s is mandatory", id.c_str()); + return false; } - - return status == SAI_STATUS_SUCCESS; } - if ((type == ACL_TABLE_PFCWD) || (type == ACL_TABLE_DROP)) + for (const auto& action: type.getActions()) { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_TC; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; - attr.value.s32 = (stage == ACL_STAGE_INGRESS) ? SAI_ACL_STAGE_INGRESS : SAI_ACL_STAGE_EGRESS; - table_attrs.push_back(attr); - - if (stage == ACL_STAGE_INGRESS) - { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS; - attr.value.booldata = true; - table_attrs.push_back(attr); - } - - sai_status_t status = sai_acl_api->create_acl_table(&m_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); - - if (status == SAI_STATUS_SUCCESS) + if (!m_pAclOrch->isAclActionSupported(stage, action)) { - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, (sai_acl_stage_t) attr.value.s32, SAI_ACL_BIND_POINT_TYPE_PORT); + SWSS_LOG_ERROR("Action %s is not supported on table %s", + sai_metadata_get_acl_action_type_name(action), id.c_str()); + return false; } - - return status == SAI_STATUS_SUCCESS; } - if (type == ACL_TABLE_MIRROR_DSCP) - { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DSCP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; - attr.value.s32 = (stage == ACL_STAGE_INGRESS) ? - SAI_ACL_STAGE_INGRESS : SAI_ACL_STAGE_EGRESS; - table_attrs.push_back(attr); - - sai_status_t status = sai_acl_api->create_acl_table( - &m_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); - - if (status == SAI_STATUS_SUCCESS) - { - gCrmOrch->incCrmAclUsedCounter( - CrmResourceType::CRM_ACL_TABLE, (sai_acl_stage_t)attr.value.s32, SAI_ACL_BIND_POINT_TYPE_PORT); - gCrmOrch->incCrmAclUsedCounter( - CrmResourceType::CRM_ACL_TABLE, (sai_acl_stage_t)attr.value.s32, SAI_ACL_BIND_POINT_TYPE_LAG); - } + return true; +} - return status == SAI_STATUS_SUCCESS; +bool AclTable::validateAclRuleMatch(sai_acl_entry_attr_t matchId, const AclRule& rule) const +{ + const auto& tableMatches = type.getMatches(); + const auto tableMatchId = AclEntryFieldToAclTableField(matchId); + const auto tableMatchIt = tableMatches.find(tableMatchId); + if (tableMatchIt == tableMatches.end()) + { + SWSS_LOG_ERROR("Match %s in rule %s is not supported by table %s", + getAttributeIdName(SAI_OBJECT_TYPE_ACL_ENTRY, matchId).c_str(), + rule.getId().c_str(), id.c_str()); + return false; } - if (type != ACL_TABLE_MIRRORV6 && type != ACL_TABLE_L3V6) + const auto& tableMatchAttrObject = tableMatchIt->second; + if (!tableMatchAttrObject->validateAclRuleMatch(rule)) { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); + SWSS_LOG_ERROR("Match %s in rule configuration %s is invalid %s", + getAttributeIdName(SAI_OBJECT_TYPE_ACL_ENTRY, matchId).c_str(), + rule.getId().c_str(), id.c_str()); + return false; } - attr.id = SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - /* - * Type of Tables and Supported Match Types (ASIC database) - * |------------------------------------------------------------------| - * | | TABLE_MIRROR | TABLE_MIRROR | TABLE_MIRRORV6 | - * | Match Type |----------------------------------------------| - * | | combined | separated | - * |------------------------------------------------------------------| - * | MATCH_SRC_IP | √ | √ | | - * | MATCH_DST_IP | √ | √ | | - * |------------------------------------------------------------------| - * | MATCH_ICMP_TYPE | √ | √ | | - * | MATCH_ICMP_CODE | √ | √ | | - * |------------------------------------------------------------------| - * | MATCH_SRC_IPV6 | √ | | √ | - * | MATCH_DST_IPV6 | √ | | √ | - * |------------------------------------------------------------------| - * | MATCH_ICMPV6_TYPE | √ | | √ | - * | MATCH_ICMPV6_CODE | √ | | √ | - * |------------------------------------------------------------------| - * | MATCH_IP_PROTOCOL | √ | √ | | - * | MATCH_NEXT_HEADER | √ | | √ | - * | -----------------------------------------------------------------| - * | MATCH_ETHERTYPE | √ | √ | | - * |------------------------------------------------------------------| - * | MATCH_IN_PORTS | √ | √ | | - * |------------------------------------------------------------------| - */ + return true; +} - // FIXME: This section has become hard to maintain and should be refactored. - if (type == ACL_TABLE_MIRROR) +bool AclTable::validateAclRuleAction(sai_acl_entry_attr_t actionId, const AclRule& rule) const +{ + // This means ACL table can hold rules with any action. + if (!type.getActions().empty()) { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_SRC_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS; - attr.value.booldata = true; - table_attrs.push_back(attr); - - // If the switch supports v6 and requires one single table - if (m_pAclOrch->m_mirrorTableCapabilities[ACL_TABLE_MIRRORV6] && - m_pAclOrch->m_isCombinedMirrorV6Table) + auto action = AclEntryActionToAclAction(actionId); + if (!type.getActions().count(action)) { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER; - attr.value.booldata = true; - table_attrs.push_back(attr); + SWSS_LOG_ERROR("Action %s is not supported on table %s", + sai_metadata_get_acl_action_type_name(action), id.c_str()); + return false; } } - else if (type == ACL_TABLE_L3V6 || type == ACL_TABLE_MIRRORV6) // v6 only - { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6; - attr.value.booldata = true; - table_attrs.push_back(attr); - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE; - attr.value.booldata = true; - table_attrs.push_back(attr); + return true; +} - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER; - attr.value.booldata = true; - table_attrs.push_back(attr); - } - else // v4 only - { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_SRC_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); +bool AclTable::create() +{ + SWSS_LOG_ENTER(); - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); + sai_attribute_t attr; + vector table_attrs; + vector action_types_list {type.getActions().begin(), type.getActions().end()}; + vector bpoint_list {type.getBindPointTypes().begin(), type.getBindPointTypes().end()}; - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); + attr.id = SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST; + attr.value.s32list.count = static_cast(bpoint_list.size()); + attr.value.s32list.list = bpoint_list.data(); + table_attrs.push_back(attr); - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE; - attr.value.booldata = true; - table_attrs.push_back(attr); + for (const auto& matchPair: type.getMatches()) + { + table_attrs.push_back(matchPair.second->toSaiAttribute()); + } - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL; - attr.value.booldata = true; + if (!action_types_list.empty()) + { + attr.id= SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST; + attr.value.s32list.count = static_cast(action_types_list.size()); + attr.value.s32list.list = action_types_list.data(); table_attrs.push_back(attr); } - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS; - attr.value.booldata = true; - table_attrs.push_back(attr); - - int32_t range_types_list[] = { SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE }; - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE; - attr.value.s32list.count = (uint32_t)(sizeof(range_types_list) / sizeof(range_types_list[0])); - attr.value.s32list.list = range_types_list; - table_attrs.push_back(attr); - sai_acl_stage_t acl_stage; attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; acl_stage = (stage == ACL_STAGE_INGRESS) ? SAI_ACL_STAGE_INGRESS : SAI_ACL_STAGE_EGRESS; attr.value.s32 = acl_stage; table_attrs.push_back(attr); - if (type == ACL_TABLE_MIRROR || type == ACL_TABLE_MIRRORV6) - { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DSCP; - attr.value.booldata = true; - table_attrs.push_back(attr); - } - - if (type == ACL_TABLE_MCLAG) + sai_status_t status = sai_acl_api->create_acl_table(&m_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); + if (status != SAI_STATUS_SUCCESS) { - attr.id = SAI_ACL_TABLE_ATTR_FIELD_OUT_PORTS; - attr.value.booldata = true; - table_attrs.push_back(attr); + return false; } - sai_status_t status = sai_acl_api->create_acl_table(&m_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); - - if (status == SAI_STATUS_SUCCESS) + for (const auto& bpointType: type.getBindPointTypes()) { - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, acl_stage, SAI_ACL_BIND_POINT_TYPE_PORT); - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, acl_stage, SAI_ACL_BIND_POINT_TYPE_LAG); + gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, acl_stage, bpointType); } - return status == SAI_STATUS_SUCCESS; + return true; } void AclTable::onUpdate(SubjectType type, void *cntx) @@ -2326,8 +2219,8 @@ bool AclTable::clear() return true; } -AclRuleDTelFlowWatchListEntry::AclRuleDTelFlowWatchListEntry(AclOrch *aclOrch, DTelOrch *dtel, string rule, string table, acl_table_type_t type) : - AclRule(aclOrch, rule, table, type), +AclRuleDTelFlowWatchListEntry::AclRuleDTelFlowWatchListEntry(AclOrch *aclOrch, DTelOrch *dtel, string rule, string table) : + AclRule(aclOrch, rule, table), m_pDTelOrch(dtel) { } @@ -2565,8 +2458,8 @@ bool AclRuleDTelFlowWatchListEntry::update(const AclRule& rule) return false; } -AclRuleDTelDropWatchListEntry::AclRuleDTelDropWatchListEntry(AclOrch *aclOrch, DTelOrch *dtel, string rule, string table, acl_table_type_t type) : - AclRule(aclOrch, rule, table, type), +AclRuleDTelDropWatchListEntry::AclRuleDTelDropWatchListEntry(AclOrch *aclOrch, DTelOrch *dtel, string rule, string table) : + AclRule(aclOrch, rule, table), m_pDTelOrch(dtel) { } @@ -2762,24 +2655,24 @@ void AclOrch::init(vector& connectors, PortsOrch *portOrch, Mirr { m_mirrorTableCapabilities = { - { ACL_TABLE_MIRROR, true }, - { ACL_TABLE_MIRRORV6, true }, + { TABLE_TYPE_MIRROR, true }, + { TABLE_TYPE_MIRRORV6, true }, }; } else { m_mirrorTableCapabilities = { - { ACL_TABLE_MIRROR, true }, - { ACL_TABLE_MIRRORV6, false }, + { TABLE_TYPE_MIRROR, true }, + { TABLE_TYPE_MIRRORV6, false }, }; } SWSS_LOG_NOTICE("%s switch capability:", platform.c_str()); - SWSS_LOG_NOTICE(" ACL_TABLE_MIRROR: %s", - m_mirrorTableCapabilities[ACL_TABLE_MIRROR] ? "yes" : "no"); - SWSS_LOG_NOTICE(" ACL_TABLE_MIRRORV6: %s", - m_mirrorTableCapabilities[ACL_TABLE_MIRRORV6] ? "yes" : "no"); + SWSS_LOG_NOTICE(" TABLE_TYPE_MIRROR: %s", + m_mirrorTableCapabilities[TABLE_TYPE_MIRROR] ? "yes" : "no"); + SWSS_LOG_NOTICE(" TABLE_TYPE_MIRRORV6: %s", + m_mirrorTableCapabilities[TABLE_TYPE_MIRRORV6] ? "yes" : "no"); // In Mellanox platform, V4 and V6 rules are stored in different tables if (platform == MLNX_PLATFORM_SUBSTRING || @@ -2793,57 +2686,239 @@ void AclOrch::init(vector& connectors, PortsOrch *portOrch, Mirr } - // Store the capabilities in state database - // TODO: Move this part of the code into syncd - vector fvVector; - for (auto const& it : m_mirrorTableCapabilities) + // Store the capabilities in state database + // TODO: Move this part of the code into syncd + vector fvVector; + for (auto const& it : m_mirrorTableCapabilities) + { + string value = it.second ? "true" : "false"; + if (it.first == TABLE_TYPE_MIRROR) + { + fvVector.emplace_back(TABLE_TYPE_MIRROR, value); + } + else if (it.first == TABLE_TYPE_MIRRORV6) + { + fvVector.emplace_back(TABLE_TYPE_MIRRORV6, value); + } + else + { + // ignore + } + } + m_switchOrch->set_switch_capability(fvVector); + + sai_attribute_t attrs[2]; + attrs[0].id = SAI_SWITCH_ATTR_ACL_ENTRY_MINIMUM_PRIORITY; + attrs[1].id = SAI_SWITCH_ATTR_ACL_ENTRY_MAXIMUM_PRIORITY; + + sai_status_t status = sai_switch_api->get_switch_attribute(gSwitchId, 2, attrs); + if (status == SAI_STATUS_SUCCESS) + { + SWSS_LOG_NOTICE("Get ACL entry priority values, min: %u, max: %u", attrs[0].value.u32, attrs[1].value.u32); + AclRule::setRulePriorities(attrs[0].value.u32, attrs[1].value.u32); + } + else + { + SWSS_LOG_ERROR("Failed to get ACL entry priority min/max values, rv:%d", status); + task_process_status handle_status = handleSaiGetStatus(SAI_API_SWITCH, status); + if (handle_status != task_process_status::task_success) + { + throw "AclOrch initialization failure"; + } + } + + queryAclActionCapability(); + + for (auto stage: {ACL_STAGE_INGRESS, ACL_STAGE_EGRESS}) + { + m_mirrorTableId[stage] = ""; + m_mirrorV6TableId[stage] = ""; + } + + initDefaultTableTypes(); + + // Attach observers + m_mirrorOrch->attach(this); + gPortsOrch->attach(this); +} + +void AclOrch::initDefaultTableTypes() +{ + SWSS_LOG_ENTER(); + + AclTableTypeBuilder builder; + + addAclTableType( + builder.withName(TABLE_TYPE_L3) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS)) + .withMatch(make_shared(set{ + {SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}})) + .build() + ); + + addAclTableType( + builder.withName(TABLE_TYPE_L3V6) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS)) + .withMatch(make_shared(set{ + {SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}})) + .build() + ); + + addAclTableType( + builder.withName(TABLE_TYPE_MCLAG) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUT_PORTS)) + .withMatch(make_shared(set{ + {SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}})) + .build() + ); + + addAclTableType( + builder.withName(TABLE_TYPE_PFCWD) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TC)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS)) + .build() + ); + + addAclTableType( + builder.withName(TABLE_TYPE_DROP) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TC)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS)) + .build() + ); + + + /* + * Type of Tables and Supported Match Types (ASIC database) + * |------------------------------------------------------------------| + * | | TABLE_MIRROR | TABLE_MIRROR | TABLE_MIRRORV6 | + * | Match Type |----------------------------------------------| + * | | combined | separated | + * |------------------------------------------------------------------| + * | MATCH_SRC_IP | √ | √ | | + * | MATCH_DST_IP | √ | √ | | + * |------------------------------------------------------------------| + * | MATCH_ICMP_TYPE | √ | √ | | + * | MATCH_ICMP_CODE | √ | √ | | + * |------------------------------------------------------------------| + * | MATCH_SRC_IPV6 | √ | | √ | + * | MATCH_DST_IPV6 | √ | | √ | + * |------------------------------------------------------------------| + * | MATCH_ICMPV6_TYPE | √ | | √ | + * | MATCH_ICMPV6_CODE | √ | | √ | + * |------------------------------------------------------------------| + * | MATCH_IP_PROTOCOL | √ | √ | | + * | MATCH_NEXT_HEADER | √ | | √ | + * | -----------------------------------------------------------------| + * | MATCH_ETHERTYPE | √ | √ | | + * |------------------------------------------------------------------| + * | MATCH_IN_PORTS | √ | √ | | + * |------------------------------------------------------------------| + */ + + if (isAclMirrorV4Supported()) { - string value = it.second ? "true" : "false"; - switch (it.first) - { - case ACL_TABLE_MIRROR: - fvVector.emplace_back(TABLE_TYPE_MIRROR, value); - break; - case ACL_TABLE_MIRRORV6: - fvVector.emplace_back(TABLE_TYPE_MIRRORV6, value); - break; - default: - break; - } - } - m_switchOrch->set_switch_capability(fvVector); - - sai_attribute_t attrs[2]; - attrs[0].id = SAI_SWITCH_ATTR_ACL_ENTRY_MINIMUM_PRIORITY; - attrs[1].id = SAI_SWITCH_ATTR_ACL_ENTRY_MAXIMUM_PRIORITY; + addAclTableType( + builder.withName(TABLE_TYPE_MIRROR_DSCP) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DSCP)) + .build() + ); - sai_status_t status = sai_switch_api->get_switch_attribute(gSwitchId, 2, attrs); - if (status == SAI_STATUS_SUCCESS) - { - SWSS_LOG_NOTICE("Get ACL entry priority values, min: %u, max: %u", attrs[0].value.u32, attrs[1].value.u32); - AclRule::setRulePriorities(attrs[0].value.u32, attrs[1].value.u32); - } - else - { - SWSS_LOG_ERROR("Failed to get ACL entry priority min/max values, rv:%d", status); - task_process_status handle_status = handleSaiGetStatus(SAI_API_SWITCH, status); - if (handle_status != task_process_status::task_success) + builder.withName(TABLE_TYPE_MIRROR) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IN_PORTS)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DSCP)) + .withMatch(make_shared(set{ + {SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}})); + + if (isAclMirrorV6Supported() && isCombinedMirrorV6Table()) { - throw "AclOrch initialization failure"; + builder + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER)); } + addAclTableType(builder.build()); + } + + if (isAclMirrorV6Supported()) + { + addAclTableType( + builder.withName(TABLE_TYPE_MIRRORV6) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMPV6_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DSCP)) + .withMatch(make_shared(set{ + {SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}})) + .build() + ); } - queryAclActionCapability(); - - for (auto stage: {ACL_STAGE_INGRESS, ACL_STAGE_EGRESS}) - { - m_mirrorTableId[stage] = ""; - m_mirrorV6TableId[stage] = ""; - } - - // Attach observers - m_mirrorOrch->attach(this); - gPortsOrch->attach(this); + // Placeholder for control plane tables + addAclTableType(builder.withName(TABLE_TYPE_CTRLPLANE).build()); } void AclOrch::queryAclActionCapability() @@ -2877,12 +2952,15 @@ void AclOrch::queryAclActionCapability() SWSS_LOG_INFO("Supported %s action count %d:", stage_str, attr.value.aclcapability.action_list.count); + auto& capabilities = m_aclCapabilities[stage]; + for (size_t i = 0; i < static_cast(attr.value.aclcapability.action_list.count); i++) { auto action = static_cast(action_list[i]); - m_aclCapabilities[stage].insert(action); + capabilities.actionList.insert(action); SWSS_LOG_INFO(" %s", sai_serialize_enum(action, &sai_metadata_enum_sai_acl_action_type_t).c_str()); } + capabilities.isActionListMandatoryOnTableCreation = attr.value.aclcapability.is_action_list_mandatory; } else { @@ -2930,16 +3008,18 @@ void AclOrch::putAclActionCapabilityInDB(acl_stage_type_t stage) auto stage_str = (stage == ACL_STAGE_INGRESS ? STAGE_INGRESS : STAGE_EGRESS); auto field = std::string("ACL_ACTIONS") + '|' + stage_str; - auto& acl_action_set = m_aclCapabilities[stage]; + auto& capabilities = m_aclCapabilities[stage]; + auto& acl_action_set = capabilities.actionList; string delimiter; ostringstream acl_action_value_stream; + ostringstream is_action_list_mandatory_stream; for (const auto& action_map: {aclL3ActionLookup, aclMirrorStageLookup, aclDTelActionLookup}) { for (const auto& it: action_map) { - auto saiAction = getAclActionFromAclEntry(it.second); + auto saiAction = AclEntryActionToAclAction(it.second); if (acl_action_set.find(saiAction) != acl_action_set.cend()) { acl_action_value_stream << delimiter << it.first; @@ -2948,8 +3028,11 @@ void AclOrch::putAclActionCapabilityInDB(acl_stage_type_t stage) } } - fvVector.emplace_back(field, acl_action_value_stream.str()); - m_switchOrch->set_switch_capability(fvVector); + is_action_list_mandatory_stream << boolalpha << capabilities.isActionListMandatoryOnTableCreation; + + fvVector.emplace_back(STATE_DB_ACL_ACTION_FIELD_IS_ACTION_LIST_MANDATORY, is_action_list_mandatory_stream.str()); + fvVector.emplace_back(STATE_DB_ACL_ACTION_FIELD_ACTION_LIST, acl_action_value_stream.str()); + m_aclStageCapabilityTable.set(stage_str, fvVector); } void AclOrch::initDefaultAclActionCapabilities(acl_stage_type_t stage) @@ -2958,9 +3041,9 @@ void AclOrch::initDefaultAclActionCapabilities(acl_stage_type_t stage) SWSS_LOG_INFO("Assumed %s %zu actions to be supported:", stage == ACL_STAGE_INGRESS ? STAGE_INGRESS : STAGE_EGRESS, - m_aclCapabilities[stage].size()); + m_aclCapabilities[stage].actionList.size()); - for (auto action: m_aclCapabilities[stage]) + for (auto action: m_aclCapabilities[stage].actionList) { SWSS_LOG_INFO(" %s", sai_serialize_enum(action, &sai_metadata_enum_sai_acl_action_type_t).c_str()); } @@ -2975,7 +3058,7 @@ void AclOrch::queryAclActionAttrEnumValues(const string &action_name, { vector fvVector; auto acl_attr = ruleAttrLookupMap.at(action_name); - auto acl_action = getAclActionFromAclEntry(acl_attr); + auto acl_action = AclEntryActionToAclAction(acl_attr); /* if the action is not supported then no need to do secondary query for * supported values @@ -3054,19 +3137,10 @@ void AclOrch::queryAclActionAttrEnumValues(const string &action_name, m_switchOrch->set_switch_capability(fvVector); } -sai_acl_action_type_t AclOrch::getAclActionFromAclEntry(sai_acl_entry_attr_t attr) -{ - if (attr < SAI_ACL_ENTRY_ATTR_ACTION_START || attr > SAI_ACL_ENTRY_ATTR_ACTION_END) - { - SWSS_LOG_THROW("Invalid ACL entry attribute passed in: %d", attr); - } - - return static_cast(attr - SAI_ACL_ENTRY_ATTR_ACTION_START); -}; - -AclOrch::AclOrch(vector& connectors, SwitchOrch *switchOrch, +AclOrch::AclOrch(vector& connectors, DBConnector* stateDb, SwitchOrch *switchOrch, PortsOrch *portOrch, MirrorOrch *mirrorOrch, NeighOrch *neighOrch, RouteOrch *routeOrch, DTelOrch *dtelOrch) : Orch(connectors), + m_aclStageCapabilityTable(stateDb, STATE_ACL_STAGE_CAPABILITY_TABLE_NAME), m_switchOrch(switchOrch), m_mirrorOrch(mirrorOrch), m_neighOrch(neighOrch), @@ -3150,6 +3224,10 @@ void AclOrch::doTask(Consumer &consumer) { doAclRuleTask(consumer); } + else if (table_name == CFG_ACL_TABLE_TYPE_TABLE_NAME || table_name == APP_ACL_TABLE_TYPE_TABLE_NAME) + { + doAclTableTypeTask(consumer); + } else { SWSS_LOG_ERROR("Invalid table %s", table_name.c_str()); @@ -3304,7 +3382,7 @@ bool AclOrch::addAclTable(AclTable &newTable) SWSS_LOG_ENTER(); string table_id = newTable.id; - if (newTable.type == ACL_TABLE_CTRLPLANE) + if (newTable.type.getName() == TABLE_TYPE_CTRLPLANE) { m_ctrlAclTables.emplace(table_id, newTable); SWSS_LOG_NOTICE("Created control plane ACL table %s", newTable.id.c_str()); @@ -3329,15 +3407,15 @@ bool AclOrch::addAclTable(AclTable &newTable) // If ACL table is new, check for the existence of current mirror tables // Note: only one table per mirror type can be created auto table_type = newTable.type; - if (table_type == ACL_TABLE_MIRROR || table_type == ACL_TABLE_MIRRORV6) + if (table_type.getName() == TABLE_TYPE_MIRROR || table_type.getName() == TABLE_TYPE_MIRRORV6) { string mirror_type; - if (table_type == ACL_TABLE_MIRROR && !m_mirrorTableId[table_stage].empty()) + if (table_type.getName() == TABLE_TYPE_MIRROR && !m_mirrorTableId[table_stage].empty()) { mirror_type = TABLE_TYPE_MIRROR; } - if (table_type == ACL_TABLE_MIRRORV6 && !m_mirrorV6TableId[table_stage].empty()) + if (table_type.getName() == TABLE_TYPE_MIRRORV6 && !m_mirrorV6TableId[table_stage].empty()) { mirror_type = TABLE_TYPE_MIRRORV6; } @@ -3355,7 +3433,7 @@ bool AclOrch::addAclTable(AclTable &newTable) } // Check if a separate mirror table is needed or not based on the platform - if (newTable.type == ACL_TABLE_MIRROR || newTable.type == ACL_TABLE_MIRRORV6) + if (newTable.type.getName() == TABLE_TYPE_MIRROR || newTable.type.getName() == TABLE_TYPE_MIRRORV6) { if (m_isCombinedMirrorV6Table && (!m_mirrorTableId[table_stage].empty() || @@ -3389,11 +3467,11 @@ bool AclOrch::addAclTable(AclTable &newTable) newTable.id.c_str(), table_oid); // Mark the existence of the mirror table - if (newTable.type == ACL_TABLE_MIRROR) + if (newTable.type.getName() == TABLE_TYPE_MIRROR) { m_mirrorTableId[table_stage] = table_id; } - else if (newTable.type == ACL_TABLE_MIRRORV6) + else if (newTable.type.getName() == TABLE_TYPE_MIRRORV6) { m_mirrorV6TableId[table_stage] = table_id; } @@ -3428,11 +3506,9 @@ bool AclOrch::removeAclTable(string table_id) auto type = m_AclTables[table_oid].type; sai_acl_stage_t sai_stage = (stage == ACL_STAGE_INGRESS) ? SAI_ACL_STAGE_INGRESS : SAI_ACL_STAGE_EGRESS; - gCrmOrch->decCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, sai_stage, SAI_ACL_BIND_POINT_TYPE_PORT, table_oid); - - if (type != ACL_TABLE_PFCWD) + for (const auto& bpointType: type.getBindPointTypes()) { - gCrmOrch->decCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, sai_stage, SAI_ACL_BIND_POINT_TYPE_LAG, table_oid); + gCrmOrch->decCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, sai_stage, bpointType, table_oid); } SWSS_LOG_NOTICE("Successfully deleted ACL table %s", table_id.c_str()); @@ -3467,6 +3543,44 @@ bool AclOrch::removeAclTable(string table_id) } } +bool AclOrch::addAclTableType(const AclTableType& tableType) +{ + SWSS_LOG_ENTER(); + + if (tableType.getName().empty()) + { + SWSS_LOG_ERROR("Received table type without a name"); + return false; + } + + if (m_AclTableTypes.find(tableType.getName()) != m_AclTableTypes.end()) + { + SWSS_LOG_ERROR("Table type %s already exists", tableType.getName().c_str()); + return false; + } + + m_AclTableTypes.emplace(tableType.getName(), tableType); + return true; +} + +bool AclOrch::removeAclTableType(const string& tableTypeName) +{ + // It is Ok to remove table type that is in use by AclTable. + // AclTable holds a copy of AclTableType and there is no + // SAI object associated with AclTableType. + // So it is no harm to remove it without validation. + // The upper layer can although ensure that + // user does not remove table type that is referenced + // by an ACL table. + if (!m_AclTableTypes.erase(tableTypeName)) + { + SWSS_LOG_ERROR("Unknown table type %s", tableTypeName.c_str()); + return false; + } + + return true; +} + bool AclOrch::addAclRule(shared_ptr newRule, string table_id) { sai_object_id_t table_oid = getTableById(table_id); @@ -3683,7 +3797,7 @@ bool AclOrch::isCombinedMirrorV6Table() return m_isCombinedMirrorV6Table; } -bool AclOrch::isAclMirrorTableSupported(acl_table_type_t type) const +bool AclOrch::isAclMirrorTableSupported(string type) const { const auto &cit = m_mirrorTableCapabilities.find(type); if (cit == m_mirrorTableCapabilities.cend()) @@ -3694,6 +3808,26 @@ bool AclOrch::isAclMirrorTableSupported(acl_table_type_t type) const return cit->second; } +bool AclOrch::isAclMirrorV4Supported() const +{ + return isAclMirrorTableSupported(TABLE_TYPE_MIRROR); +} + +bool AclOrch::isAclMirrorV6Supported() const +{ + return isAclMirrorTableSupported(TABLE_TYPE_MIRRORV6); +} + +bool AclOrch::isAclActionListMandatoryOnTableCreation(acl_stage_type_t stage) const +{ + const auto& it = m_aclCapabilities.find(stage); + if (it == m_aclCapabilities.cend()) + { + return false; + } + return it->second.isActionListMandatoryOnTableCreation; +} + bool AclOrch::isAclActionSupported(acl_stage_type_t stage, sai_acl_action_type_t action) const { const auto& it = m_aclCapabilities.find(stage); @@ -3701,7 +3835,8 @@ bool AclOrch::isAclActionSupported(acl_stage_type_t stage, sai_acl_action_type_t { return false; } - return it->second.find(action) != it->second.cend(); + const auto& actionCapability = it->second; + return actionCapability.actionList.find(action) != actionCapability.actionList.cend(); } bool AclOrch::isAclActionEnumValueSupported(sai_acl_action_type_t action, sai_acl_action_parameter_t param) const @@ -3732,6 +3867,7 @@ void AclOrch::doAclTableTask(Consumer &consumer) if (op == SET_COMMAND) { AclTable newTable(this); + string tableTypeName; bool bAllAttributesOk = true; newTable.id = table_id; @@ -3749,7 +3885,7 @@ void AclOrch::doAclTableTask(Consumer &consumer) } else if (attr_name == ACL_TABLE_TYPE) { - if (!processAclTableType(attr_value, newTable.type)) + if (!processAclTableType(attr_value, tableTypeName)) { SWSS_LOG_ERROR("Failed to process ACL table %s type", table_id.c_str()); @@ -3790,6 +3926,15 @@ void AclOrch::doAclTableTask(Consumer &consumer) } } + auto tableType = getAclTableType(tableTypeName); + if (!tableType) + { + it++; + continue; + } + + newTable.validateAddType(*tableType); + // validate and create/update ACL Table if (bAllAttributesOk && newTable.validate()) { @@ -3799,7 +3944,7 @@ void AclOrch::doAclTableTask(Consumer &consumer) sai_object_id_t table_oid = getTableById(table_id); if (table_oid != SAI_NULL_OBJECT_ID && - !isAclTableTypeUpdated(newTable.type, + !isAclTableTypeUpdated(newTable.type.getName(), m_AclTables[table_oid]) && !isAclTableStageUpdated(newTable.stage, m_AclTables[table_oid])) @@ -3880,8 +4025,7 @@ void AclOrch::doAclRuleTask(Consumer &consumer) sai_object_id_t table_oid = getTableById(table_id); /* ACL table is not yet created or ACL table is a control plane table */ - /* TODO: Remove ACL_TABLE_UNKNOWN as a table with this type cannot be successfully created */ - if (table_oid == SAI_NULL_OBJECT_ID || m_AclTables[table_oid].type == ACL_TABLE_UNKNOWN) + if (table_oid == SAI_NULL_OBJECT_ID) { /* Skip the control plane rules */ @@ -3897,17 +4041,17 @@ void AclOrch::doAclRuleTask(Consumer &consumer) continue; } - auto type = m_AclTables[table_oid].type; + auto type = m_AclTables[table_oid].type.getName(); auto stage = m_AclTables[table_oid].stage; - if (type == ACL_TABLE_MIRROR || type == ACL_TABLE_MIRRORV6) + if (type == TABLE_TYPE_MIRROR || type == TABLE_TYPE_MIRRORV6) { - type = table_id == m_mirrorTableId[stage] ? ACL_TABLE_MIRROR : ACL_TABLE_MIRRORV6; + type = table_id == m_mirrorTableId[stage] ? TABLE_TYPE_MIRROR : TABLE_TYPE_MIRRORV6; } try { - newRule = AclRule::makeShared(type, this, m_mirrorOrch, m_dTelOrch, rule_id, table_id, t); + newRule = AclRule::makeShared(this, m_mirrorOrch, m_dTelOrch, rule_id, table_id, t); } catch (exception &e) { @@ -3955,7 +4099,7 @@ void AclOrch::doAclRuleTask(Consumer &consumer) if (bHasTCPFlag && !bHasIPProtocol) { string attr_name; - if (type == ACL_TABLE_MIRRORV6 || type == ACL_TABLE_L3V6) + if (type == TABLE_TYPE_MIRRORV6 || type == TABLE_TYPE_L3V6) { attr_name = MATCH_NEXT_HEADER; } @@ -4004,6 +4148,42 @@ void AclOrch::doAclRuleTask(Consumer &consumer) } } +void AclOrch::doAclTableTypeTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + auto keyOpFieldValues = it->second; + auto key = kfvKey(keyOpFieldValues); + auto op = kfvOp(keyOpFieldValues); + + if (op == SET_COMMAND) + { + AclTableTypeBuilder builder; + if (!AclTableTypeParser().parse(key, kfvFieldsValues(keyOpFieldValues), builder)) + { + SWSS_LOG_ERROR("Failed to parse ACL table type configuration %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + addAclTableType(builder.build()); + } + else if (op == DEL_COMMAND) + { + removeAclTableType(key); + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + } + + it = consumer.m_toSync.erase(it); + } +} + bool AclOrch::processAclTablePorts(string portList, AclTable &aclTable) { SWSS_LOG_ENTER(); @@ -4037,39 +4217,26 @@ bool AclOrch::processAclTablePorts(string portList, AclTable &aclTable) return true; } -bool AclOrch::isAclTableTypeUpdated(acl_table_type_t table_type, AclTable &t) +bool AclOrch::isAclTableTypeUpdated(string table_type, AclTable &t) { - if (m_isCombinedMirrorV6Table && (table_type == ACL_TABLE_MIRROR || table_type == ACL_TABLE_MIRRORV6)) + if (m_isCombinedMirrorV6Table && (table_type == TABLE_TYPE_MIRROR || table_type == TABLE_TYPE_MIRRORV6)) { - // ACL_TABLE_MIRRORV6 and ACL_TABLE_MIRROR should be treated as same type in combined scenario - return !(t.type == ACL_TABLE_MIRROR || t.type == ACL_TABLE_MIRRORV6); + // TABLE_TYPE_MIRRORV6 and TABLE_TYPE_MIRROR should be treated as same type in combined scenario + return !(t.type.getName() == TABLE_TYPE_MIRROR || t.type.getName() == TABLE_TYPE_MIRRORV6); } - return (table_type != t.type); + return (table_type != t.type.getName()); } -bool AclOrch::processAclTableType(string type, acl_table_type_t &table_type) +bool AclOrch::processAclTableType(string type, string &out_table_type) { SWSS_LOG_ENTER(); - auto iter = aclTableTypeLookUp.find(to_upper(type)); - - if (iter == aclTableTypeLookUp.end()) + if (type.empty()) { return false; } - table_type = iter->second; - - // Mirror table check procedure - if (table_type == ACL_TABLE_MIRROR || table_type == ACL_TABLE_MIRRORV6) - { - // Check the switch capability - if (!m_mirrorTableCapabilities[table_type]) - { - SWSS_LOG_ERROR("Mirror table type %s is not supported", type.c_str()); - return false; - } - } + out_table_type = type; return true; } @@ -4096,6 +4263,18 @@ bool AclOrch::processAclTableStage(string stage, acl_stage_type_t &acl_stage) return true; } +const AclTableType* AclOrch::getAclTableType(const string& tableTypeName) const +{ + auto it = m_AclTableTypes.find(to_upper(tableTypeName)); + if (it == m_AclTableTypes.end()) + { + SWSS_LOG_INFO("Failed to find ACL table type %s", tableTypeName.c_str()); + return nullptr; + } + + return &it->second; +} + sai_object_id_t AclOrch::getTableById(string table_id) { SWSS_LOG_ENTER(); @@ -4197,220 +4376,61 @@ sai_status_t AclOrch::bindAclTable(AclTable &aclTable, bool bind) return status; } -sai_status_t AclOrch::createDTelWatchListTables() +void AclOrch::createDTelWatchListTables() { SWSS_LOG_ENTER(); - AclTable flowWLTable, dropWLTable; - sai_object_id_t table_oid; - - sai_status_t status; - sai_attribute_t attr; - vector table_attrs; - - /* Create Flow watchlist ACL table */ - - flowWLTable.id = TABLE_TYPE_DTEL_FLOW_WATCHLIST; - flowWLTable.type = ACL_TABLE_DTEL_FLOW_WATCHLIST; - flowWLTable.description = "Dataplane Telemetry Flow Watchlist table"; - - attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; - attr.value.s32 = SAI_ACL_STAGE_INGRESS; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST; - vector bpoint_list; - bpoint_list.push_back(SAI_ACL_BIND_POINT_TYPE_SWITCH); - attr.value.s32list.count = 1; - attr.value.s32list.list = bpoint_list.data(); - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_SRC_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_TUNNEL_VNI; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_INNER_ETHER_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST; - int32_t acl_action_list[4]; - acl_action_list[0] = SAI_ACL_ACTION_TYPE_ACL_DTEL_FLOW_OP; - acl_action_list[1] = SAI_ACL_ACTION_TYPE_DTEL_INT_SESSION; - acl_action_list[2] = SAI_ACL_ACTION_TYPE_DTEL_REPORT_ALL_PACKETS; - acl_action_list[3] = SAI_ACL_ACTION_TYPE_DTEL_FLOW_SAMPLE_PERCENT; - attr.value.s32list.count = 4; - attr.value.s32list.list = acl_action_list; - table_attrs.push_back(attr); - - status = sai_acl_api->create_acl_table(&table_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to create table %s", flowWLTable.description.c_str()); - if (handleSaiCreateStatus(SAI_API_ACL, status) != task_success) - { - return status; - } - } - - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_SWITCH); - m_AclTables[table_oid] = flowWLTable; - SWSS_LOG_INFO("Successfully created ACL table %s, oid: %" PRIx64, flowWLTable.description.c_str(), table_oid); - - /* Create Drop watchlist ACL table */ - - table_attrs.clear(); - - dropWLTable.id = TABLE_TYPE_DTEL_DROP_WATCHLIST; - dropWLTable.type = ACL_TABLE_DTEL_DROP_WATCHLIST; - dropWLTable.description = "Dataplane Telemetry Drop Watchlist table"; - - attr.id = SAI_ACL_TABLE_ATTR_ACL_STAGE; - attr.value.s32 = SAI_ACL_STAGE_INGRESS; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST; - bpoint_list.clear(); - bpoint_list.push_back(SAI_ACL_BIND_POINT_TYPE_SWITCH); - attr.value.s32list.count = 1; - attr.value.s32list.list = bpoint_list.data(); - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_SRC_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_DST_IP; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL; - attr.value.booldata = true; - table_attrs.push_back(attr); - - attr.id = SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST; - acl_action_list[0] = SAI_ACL_ACTION_TYPE_DTEL_DROP_REPORT_ENABLE; - acl_action_list[1] = SAI_ACL_ACTION_TYPE_DTEL_TAIL_DROP_REPORT_ENABLE; - attr.value.s32list.count = 2; - attr.value.s32list.list = acl_action_list; - table_attrs.push_back(attr); - - status = sai_acl_api->create_acl_table(&table_oid, gSwitchId, (uint32_t)table_attrs.size(), table_attrs.data()); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to create table %s", dropWLTable.description.c_str()); - if (handleSaiCreateStatus(SAI_API_ACL, status) != task_success) - { - return status; - } - } - - gCrmOrch->incCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_SWITCH); - m_AclTables[table_oid] = dropWLTable; - SWSS_LOG_INFO("Successfully created ACL table %s, oid: %" PRIx64, dropWLTable.description.c_str(), table_oid); + AclTableTypeBuilder builder; + + AclTable flowWLTable(this, TABLE_TYPE_DTEL_FLOW_WATCHLIST); + AclTable dropWLTable(this, TABLE_TYPE_DTEL_DROP_WATCHLIST); + + flowWLTable.validateAddStage(ACL_STAGE_INGRESS); + flowWLTable.validateAddType(builder + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_SWITCH) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TUNNEL_VNI)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_INNER_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_INNER_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_INNER_DST_IP)) + .withAction(SAI_ACL_ACTION_TYPE_ACL_DTEL_FLOW_OP) + .withAction(SAI_ACL_ACTION_TYPE_DTEL_INT_SESSION) + .withAction(SAI_ACL_ACTION_TYPE_DTEL_REPORT_ALL_PACKETS) + .withAction(SAI_ACL_ACTION_TYPE_DTEL_FLOW_SAMPLE_PERCENT) + .build() + ); + flowWLTable.setDescription("Dataplane Telemetry Flow Watchlist table"); + + dropWLTable.validateAddStage(ACL_STAGE_INGRESS); + dropWLTable.validateAddType(builder + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_SWITCH) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withAction(SAI_ACL_ACTION_TYPE_DTEL_DROP_REPORT_ENABLE) + .withAction(SAI_ACL_ACTION_TYPE_DTEL_TAIL_DROP_REPORT_ENABLE) + .build() + ); + dropWLTable.setDescription("Dataplane Telemetry Drop Watchlist table"); - return SAI_STATUS_SUCCESS; + addAclTable(flowWLTable); + addAclTable(dropWLTable); } -sai_status_t AclOrch::deleteDTelWatchListTables() +void AclOrch::deleteDTelWatchListTables() { SWSS_LOG_ENTER(); - AclTable flowWLTable(this), dropWLTable(this); - sai_object_id_t table_oid; - string table_id = TABLE_TYPE_DTEL_FLOW_WATCHLIST; - - sai_status_t status; - - table_oid = getTableById(table_id); - - if (table_oid == SAI_NULL_OBJECT_ID) - { - SWSS_LOG_INFO("Failed to find ACL table %s", table_id.c_str()); - return SAI_STATUS_FAILURE; - } - - status = sai_acl_api->remove_acl_table(table_oid); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to delete table %s", table_id.c_str()); - if (handleSaiRemoveStatus(SAI_API_ACL, status) != task_success) - { - return status; - } - } - - gCrmOrch->decCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_SWITCH, table_oid); - m_AclTables.erase(table_oid); - - table_id = TABLE_TYPE_DTEL_DROP_WATCHLIST; - - table_oid = getTableById(table_id); - - if (table_oid == SAI_NULL_OBJECT_ID) - { - SWSS_LOG_INFO("Failed to find ACL table %s", table_id.c_str()); - return SAI_STATUS_FAILURE; - } - - status = sai_acl_api->remove_acl_table(table_oid); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to delete table %s", table_id.c_str()); - if (handleSaiRemoveStatus(SAI_API_ACL, status) != task_success) - { - return status; - } - } - - gCrmOrch->decCrmAclUsedCounter(CrmResourceType::CRM_ACL_TABLE, SAI_ACL_STAGE_INGRESS, SAI_ACL_BIND_POINT_TYPE_SWITCH, table_oid); - m_AclTables.erase(table_oid); - - return SAI_STATUS_SUCCESS; + removeAclTable(TABLE_TYPE_DTEL_FLOW_WATCHLIST); + removeAclTable(TABLE_TYPE_DTEL_DROP_WATCHLIST); } void AclOrch::registerFlexCounter(const AclRule& rule) diff --git a/orchagent/aclorch.h b/orchagent/aclorch.h index 9b1df18570..9e6db3919c 100644 --- a/orchagent/aclorch.h +++ b/orchagent/aclorch.h @@ -50,6 +50,9 @@ #define MATCH_INNER_L4_SRC_PORT "INNER_L4_SRC_PORT" #define MATCH_INNER_L4_DST_PORT "INNER_L4_DST_PORT" +#define BIND_POINT_TYPE_PORT "PORT" +#define BIND_POINT_TYPE_PORTCHANNEL "PORTCHANNEL" + #define ACTION_PACKET_ACTION "PACKET_ACTION" #define ACTION_REDIRECT_ACTION "REDIRECT_ACTION" #define ACTION_DO_NOT_NAT_ACTION "DO_NOT_NAT_ACTION" @@ -94,15 +97,100 @@ #define ACL_COUNTER_FLEX_COUNTER_GROUP "ACL_STAT_COUNTER" +struct AclActionCapabilities +{ + set actionList; + bool isActionListMandatoryOnTableCreation {false}; +}; + typedef map acl_rule_attr_lookup_t; typedef map acl_range_type_lookup_t; +typedef map acl_bind_point_type_lookup_t; typedef map acl_ip_type_lookup_t; typedef map acl_dtel_flow_op_type_lookup_t; typedef map acl_packet_action_lookup_t; typedef tuple acl_range_properties_t; -typedef map> acl_capabilities_t; +typedef map acl_capabilities_t; typedef map> acl_action_enum_values_capabilities_t; +class AclRule; + +class AclTableMatchInterface +{ +public: + AclTableMatchInterface(sai_acl_table_attr_t matchField); + + sai_acl_table_attr_t getId() const; + virtual sai_attribute_t toSaiAttribute() = 0; + virtual bool validateAclRuleMatch(const AclRule& rule) const = 0; +private: + sai_acl_table_attr_t m_matchField; +}; + +class AclTableMatch: public AclTableMatchInterface +{ +public: + AclTableMatch(sai_acl_table_attr_t matchField); + + sai_attribute_t toSaiAttribute() override; + bool validateAclRuleMatch(const AclRule& rule) const override; +}; + +class AclTableRangeMatch: public AclTableMatchInterface +{ +public: + AclTableRangeMatch(set rangeTypes); + + sai_attribute_t toSaiAttribute() override; + bool validateAclRuleMatch(const AclRule& rule) const override; + +private: + vector m_rangeList; +}; +class AclTableType +{ +public: + string getName() const; + const set& getBindPointTypes() const; + const map>& getMatches() const; + const set& getRangeTypes() const; + const set& getActions() const; + +private: + friend class AclTableTypeBuilder; + + string m_name; + set m_bpointTypes; + map> m_matches; + set m_aclAcitons; +}; + +class AclTableTypeBuilder +{ +public: + AclTableTypeBuilder& withName(string name); + AclTableTypeBuilder& withBindPointType(sai_acl_bind_point_type_t bpointType); + AclTableTypeBuilder& withMatch(shared_ptr match); + AclTableTypeBuilder& withAction(sai_acl_action_type_t action); + AclTableType build(); + +private: + AclTableType m_tableType; +}; + +class AclTableTypeParser +{ +public: + bool parse( + const string& key, + const vector& fieldValues, + AclTableTypeBuilder& builder); +private: + bool parseAclTableTypeMatches(const string& value, AclTableTypeBuilder& builder); + bool parseAclTableTypeActions(const string& value, AclTableTypeBuilder& builder); + bool parseAclTableTypeBindPointTypes(const string& value, AclTableTypeBuilder& builder); +}; + class AclOrch; struct AclRangeConfig @@ -134,10 +222,12 @@ class AclRange static map m_ranges; }; +class AclTable; + class AclRule { public: - AclRule(AclOrch *pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = true); + AclRule(AclOrch *pAclOrch, string rule, string table, bool createCounter = true); virtual bool validateAddPriority(string attr_name, string attr_value); virtual bool validateAddMatch(string attr_name, string attr_value); virtual bool validateAddAction(string attr_name, string attr_value) = 0; @@ -158,34 +248,15 @@ class AclRule virtual bool enableCounter(); virtual bool disableCounter(); - sai_object_id_t getOid() const - { - return m_ruleOid; - } - - string getId() const - { - return m_id; - } - - string getTableId() const - { - return m_tableId; - } - - sai_object_id_t getCounterOid() const - { - return m_counterOid; - } - + string getId() const; + string getTableId() const; + sai_object_id_t getOid() const; + sai_object_id_t getCounterOid() const; + bool hasCounter() const; vector getInPorts() const; - bool hasCounter() const - { - return getCounterOid() != SAI_NULL_OBJECT_ID; - } - - static shared_ptr makeShared(acl_table_type_t type, AclOrch *acl, MirrorOrch *mirror, DTelOrch *dtel, const string& rule, const string& table, const KeyOpFieldsValuesTuple&); + const vector& getRangeConfig() const; + static shared_ptr makeShared(AclOrch *acl, MirrorOrch *mirror, DTelOrch *dtel, const string& rule, const string& table, const KeyOpFieldsValuesTuple&); virtual ~AclRule() {} protected: @@ -214,9 +285,7 @@ class AclRule static sai_uint32_t m_maxPriority; AclOrch *m_pAclOrch; string m_id; - string m_tableId; - acl_table_type_t m_tableType; - sai_object_id_t m_tableOid; + const AclTable* m_pTable {nullptr}; sai_object_id_t m_ruleOid; sai_object_id_t m_counterOid; uint32_t m_priority; @@ -232,13 +301,12 @@ class AclRule bool m_createCounter; }; -class AclRuleL3: public AclRule +class AclRulePacket: public AclRule { public: - AclRuleL3(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = true); + AclRulePacket(AclOrch *m_pAclOrch, string rule, string table, bool createCounter = true); bool validateAddAction(string attr_name, string attr_value); - bool validateAddMatch(string attr_name, string attr_value); bool validate(); void onUpdate(SubjectType, void *) override; @@ -246,33 +314,11 @@ class AclRuleL3: public AclRule sai_object_id_t getRedirectObjectId(const string& redirect_param); }; -class AclRuleL3V6: public AclRuleL3 -{ -public: - AclRuleL3V6(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type); - bool validateAddMatch(string attr_name, string attr_value); -}; - -class AclRulePfcwd: public AclRuleL3 -{ -public: - AclRulePfcwd(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = false); - bool validateAddMatch(string attr_name, string attr_value); -}; - -class AclRuleMux: public AclRuleL3 -{ -public: - AclRuleMux(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = false); - bool validateAddMatch(string attr_name, string attr_value); -}; - class AclRuleMirror: public AclRule { public: - AclRuleMirror(AclOrch *m_pAclOrch, MirrorOrch *m_pMirrorOrch, string rule, string table, acl_table_type_t type); + AclRuleMirror(AclOrch *m_pAclOrch, MirrorOrch *m_pMirrorOrch, string rule, string table); bool validateAddAction(string attr_name, string attr_value); - bool validateAddMatch(string attr_name, string attr_value); bool validate(); bool createRule(); bool removeRule(); @@ -291,7 +337,7 @@ class AclRuleMirror: public AclRule class AclRuleDTelFlowWatchListEntry: public AclRule { public: - AclRuleDTelFlowWatchListEntry(AclOrch *m_pAclOrch, DTelOrch *m_pDTelOrch, string rule, string table, acl_table_type_t type); + AclRuleDTelFlowWatchListEntry(AclOrch *m_pAclOrch, DTelOrch *m_pDTelOrch, string rule, string table); bool validateAddAction(string attr_name, string attr_value); bool validate(); bool createRule(); @@ -312,7 +358,7 @@ class AclRuleDTelFlowWatchListEntry: public AclRule class AclRuleDTelDropWatchListEntry: public AclRule { public: - AclRuleDTelDropWatchListEntry(AclOrch *m_pAclOrch, DTelOrch *m_pDTelOrch, string rule, string table, acl_table_type_t type); + AclRuleDTelDropWatchListEntry(AclOrch *m_pAclOrch, DTelOrch *m_pDTelOrch, string rule, string table); bool validateAddAction(string attr_name, string attr_value); bool validate(); void onUpdate(SubjectType, void *) override; @@ -320,14 +366,6 @@ class AclRuleDTelDropWatchListEntry: public AclRule DTelOrch *m_pDTelOrch; }; -class AclRuleMclag: public AclRuleL3 -{ -public: - AclRuleMclag(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = false); - bool validateAddMatch(string attr_name, string attr_value); - bool validate(); -}; - class AclTable { public: @@ -337,18 +375,23 @@ class AclTable AclTable() = default; ~AclTable() = default; - sai_object_id_t getOid() { return m_oid; } - string getId() { return id; } + sai_object_id_t getOid() const { return m_oid; } + string getId() const { return id; } void setDescription(const string &value) { description = value; } const string& getDescription() const { return description; } - bool validateAddType(const acl_table_type_t &value); + bool validateAddType(const AclTableType &tableType); bool validateAddStage(const acl_stage_type_t &value); bool validateAddPorts(const unordered_set &value); bool validate(); bool create(); + // validate AclRule match attribute against rule and table configuration + bool validateAclRuleMatch(sai_acl_entry_attr_t matchId, const AclRule& rule) const; + // validate AclRule action attribute against rule and table configuration + bool validateAclRuleAction(sai_acl_entry_attr_t actionId, const AclRule& rule) const; + // Bind the ACL table to a port which is already linked bool bind(sai_object_id_t portOid); // Unbind the ACL table to a port which is already linked @@ -376,7 +419,7 @@ class AclTable string id; string description; - acl_table_type_t type = ACL_TABLE_UNKNOWN; + AclTableType type; acl_stage_type_t stage = ACL_STAGE_INGRESS; // Map port oid to group member oid @@ -397,6 +440,7 @@ class AclOrch : public Orch, public Observer { public: AclOrch(vector& connectors, + DBConnector *m_stateDb, SwitchOrch *m_switchOrch, PortsOrch *portOrch, MirrorOrch *mirrorOrch, @@ -408,6 +452,7 @@ class AclOrch : public Orch, public Observer sai_object_id_t getTableById(string table_id); const AclTable* getTableByOid(sai_object_id_t oid) const; + const AclTableType* getAclTableType(const std::string& tableTypeName) const; static swss::Table& getCountersTable() { @@ -422,6 +467,8 @@ class AclOrch : public Orch, public Observer bool addAclTable(AclTable &aclTable); bool removeAclTable(string table_id); + bool addAclTableType(const AclTableType& tableType); + bool removeAclTableType(const string& tableTypeName); bool updateAclTable(AclTable ¤tTable, AclTable &newTable); bool updateAclTable(string table_id, AclTable &table); bool addAclRule(shared_ptr aclRule, string table_id); @@ -432,14 +479,15 @@ class AclOrch : public Orch, public Observer AclRule* getAclRule(string table_id, string rule_id); bool isCombinedMirrorV6Table(); - bool isAclMirrorTableSupported(acl_table_type_t type) const; + bool isAclMirrorV6Supported() const; + bool isAclMirrorV4Supported() const; + bool isAclMirrorTableSupported(string type) const; + bool isAclActionListMandatoryOnTableCreation(acl_stage_type_t stage) const; bool isAclActionSupported(acl_stage_type_t stage, sai_acl_action_type_t action) const; bool isAclActionEnumValueSupported(sai_acl_action_type_t action, sai_acl_action_parameter_t param) const; bool m_isCombinedMirrorV6Table = true; - map m_mirrorTableCapabilities; - - static sai_acl_action_type_t getAclActionFromAclEntry(sai_acl_entry_attr_t attr); + map m_mirrorTableCapabilities; // Get the OID for the ACL bind point for a given port static bool getAclBindPortId(Port& port, sai_object_id_t& port_id); @@ -455,7 +503,9 @@ class AclOrch : public Orch, public Observer void doTask(Consumer &consumer); void doAclTableTask(Consumer &consumer); void doAclRuleTask(Consumer &consumer); + void doAclTableTypeTask(Consumer &consumer); void init(vector& connectors, PortsOrch *portOrch, MirrorOrch *mirrorOrch, NeighOrch *neighOrch, RouteOrch *routeOrch); + void initDefaultTableTypes(); void queryMirrorTableCapability(); void queryAclActionCapability(); @@ -473,10 +523,10 @@ class AclOrch : public Orch, public Observer sai_status_t bindAclTable(AclTable &aclTable, bool bind = true); sai_status_t deleteUnbindAclTable(sai_object_id_t table_oid); - bool isAclTableTypeUpdated(acl_table_type_t table_type, AclTable &aclTable); - bool processAclTableType(string type, acl_table_type_t &table_type); + bool isAclTableTypeUpdated(string table_type, AclTable &aclTable); bool isAclTableStageUpdated(acl_stage_type_t acl_stage, AclTable &aclTable); bool processAclTableStage(string stage, acl_stage_type_t &acl_stage); + bool processAclTableType(string type, string &out_table_type); bool processAclTablePorts(string portList, AclTable &aclTable); bool validateAclTable(AclTable &aclTable); bool updateAclTablePorts(AclTable &newTable, AclTable &curTable); @@ -484,8 +534,8 @@ class AclOrch : public Orch, public Observer AclTable &curT, set &addSet, set &delSet); - sai_status_t createDTelWatchListTables(); - sai_status_t deleteDTelWatchListTables(); + void createDTelWatchListTables(); + void deleteDTelWatchListTables(); void registerFlexCounter(const AclRule& rule); void deregisterFlexCounter(const AclRule& rule); @@ -494,10 +544,13 @@ class AclOrch : public Orch, public Observer map m_AclTables; // TODO: Move all ACL tables into one map: name -> instance map m_ctrlAclTables; + map m_AclTableTypes; static DBConnector m_countersDb; static Table m_countersTable; + Table m_aclStageCapabilityTable; + map m_mirrorTableId; map m_mirrorV6TableId; diff --git a/orchagent/acltable.h b/orchagent/acltable.h index 44d6ea4dbf..081170984f 100644 --- a/orchagent/acltable.h +++ b/orchagent/acltable.h @@ -15,6 +15,10 @@ extern "C" { #define ACL_TABLE_PORTS "PORTS" #define ACL_TABLE_SERVICES "SERVICES" +#define ACL_TABLE_TYPE_MATCHES "MATCHES" +#define ACL_TABLE_TYPE_BPOINT_TYPES "BIND_POINTS" +#define ACL_TABLE_TYPE_ACTIONS "ACTIONS" + #define STAGE_INGRESS "INGRESS" #define STAGE_EGRESS "EGRESS" @@ -39,23 +43,3 @@ typedef enum } acl_stage_type_t; typedef std::unordered_map acl_stage_type_lookup_t; - -typedef enum -{ - ACL_TABLE_UNKNOWN, - ACL_TABLE_L3, - ACL_TABLE_L3V6, - ACL_TABLE_MIRROR, - ACL_TABLE_MIRRORV6, - ACL_TABLE_MIRROR_DSCP, - ACL_TABLE_PFCWD, - ACL_TABLE_CTRLPLANE, - ACL_TABLE_DTEL_FLOW_WATCHLIST, - ACL_TABLE_DTEL_DROP_WATCHLIST, - ACL_TABLE_MCLAG, - ACL_TABLE_MUX, - ACL_TABLE_DROP, - ACL_TABLE_PBH -} acl_table_type_t; - -typedef std::unordered_map acl_table_type_lookup_t; diff --git a/orchagent/muxorch.cpp b/orchagent/muxorch.cpp index f7af9f1f5c..5b7b0570a5 100644 --- a/orchagent/muxorch.cpp +++ b/orchagent/muxorch.cpp @@ -709,7 +709,6 @@ MuxAclHandler::MuxAclHandler(sai_object_id_t port, string alias) SWSS_LOG_ENTER(); // There is one handler instance per MUX port - acl_table_type_t table_type = ACL_TABLE_DROP; string table_name = MUX_ACL_TABLE_NAME; string rule_name = MUX_ACL_RULE_NAME; @@ -723,8 +722,8 @@ MuxAclHandler::MuxAclHandler(sai_object_id_t port, string alias) // First time handling of Mux Table, create ACL table, and bind createMuxAclTable(port, table_name); - shared_ptr newRule = - make_shared(gAclOrch, rule_name, table_name, table_type); + shared_ptr newRule = + make_shared(gAclOrch, rule_name, table_name); createMuxAclRule(newRule, table_name); } else @@ -734,8 +733,8 @@ MuxAclHandler::MuxAclHandler(sai_object_id_t port, string alias) AclRule* rule = gAclOrch->getAclRule(table_name, rule_name); if (rule == nullptr) { - shared_ptr newRule = - make_shared(gAclOrch, rule_name, table_name, table_type); + shared_ptr newRule = + make_shared(gAclOrch, rule_name, table_name); createMuxAclRule(newRule, table_name); } else @@ -776,7 +775,7 @@ void MuxAclHandler::createMuxAclTable(sai_object_id_t port, string strTable) auto inserted = acl_table_.emplace(piecewise_construct, std::forward_as_tuple(strTable), - std::forward_as_tuple()); + std::forward_as_tuple(gAclOrch, strTable)); assert(inserted.second); @@ -791,14 +790,15 @@ void MuxAclHandler::createMuxAclTable(sai_object_id_t port, string strTable) return; } - acl_table.type = ACL_TABLE_DROP; - acl_table.id = strTable; + auto dropType = gAclOrch->getAclTableType(TABLE_TYPE_DROP); + assert(dropType); + acl_table.validateAddType(*dropType); acl_table.stage = ACL_STAGE_INGRESS; gAclOrch->addAclTable(acl_table); bindAllPorts(acl_table); } -void MuxAclHandler::createMuxAclRule(shared_ptr rule, string strTable) +void MuxAclHandler::createMuxAclRule(shared_ptr rule, string strTable) { SWSS_LOG_ENTER(); diff --git a/orchagent/muxorch.h b/orchagent/muxorch.h index fa8b058830..6e4f70408c 100644 --- a/orchagent/muxorch.h +++ b/orchagent/muxorch.h @@ -43,7 +43,7 @@ class MuxAclHandler private: void createMuxAclTable(sai_object_id_t port, string strTable); - void createMuxAclRule(shared_ptr rule, string strTable); + void createMuxAclRule(shared_ptr rule, string strTable); void bindAllPorts(AclTable &acl_table); // class shared dict: ACL table name -> ACL table diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 9b533676ce..52beb0fd10 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -229,15 +229,19 @@ bool OrchDaemon::init() gMirrorOrch = new MirrorOrch(stateDbMirrorSession, confDbMirrorSession, gPortsOrch, gRouteOrch, gNeighOrch, gFdbOrch, policer_orch); TableConnector confDbAclTable(m_configDb, CFG_ACL_TABLE_TABLE_NAME); + TableConnector confDbAclTableType(m_configDb, CFG_ACL_TABLE_TYPE_TABLE_NAME); TableConnector confDbAclRuleTable(m_configDb, CFG_ACL_RULE_TABLE_NAME); TableConnector appDbAclTable(m_applDb, APP_ACL_TABLE_TABLE_NAME); + TableConnector appDbAclTableType(m_applDb, APP_ACL_TABLE_TYPE_TABLE_NAME); TableConnector appDbAclRuleTable(m_applDb, APP_ACL_RULE_TABLE_NAME); vector acl_table_connectors = { + confDbAclTableType, confDbAclTable, confDbAclRuleTable, appDbAclTable, - appDbAclRuleTable + appDbAclRuleTable, + appDbAclTableType, }; vector dtel_tables = { @@ -350,7 +354,9 @@ bool OrchDaemon::init() dtel_orch = new DTelOrch(m_configDb, dtel_tables, gPortsOrch); m_orchList.push_back(dtel_orch); } - gAclOrch = new AclOrch(acl_table_connectors, gSwitchOrch, gPortsOrch, gMirrorOrch, gNeighOrch, gRouteOrch, dtel_orch); + + gAclOrch = new AclOrch(acl_table_connectors, m_stateDb, + gSwitchOrch, gPortsOrch, gMirrorOrch, gNeighOrch, gRouteOrch, dtel_orch); vector mlag_tables = { { CFG_MCLAG_TABLE_NAME }, diff --git a/orchagent/pbh/pbhrule.cpp b/orchagent/pbh/pbhrule.cpp index d4e2218cf0..7d35f4bb8f 100644 --- a/orchagent/pbh/pbhrule.cpp +++ b/orchagent/pbh/pbhrule.cpp @@ -3,7 +3,7 @@ #include "pbhrule.h" AclRulePbh::AclRulePbh(AclOrch *pAclOrch, string rule, string table, bool createCounter) : - AclRule(pAclOrch, rule, table, ACL_TABLE_PBH, createCounter) + AclRule(pAclOrch, rule, table, createCounter) { } diff --git a/orchagent/pbhorch.cpp b/orchagent/pbhorch.cpp index 3d0b43ce01..83a1e80bd0 100644 --- a/orchagent/pbhorch.cpp +++ b/orchagent/pbhorch.cpp @@ -180,7 +180,18 @@ bool PbhOrch::createPbhTable(const PbhTable &table) AclTable pbhTable(this->aclOrch, table.name); - if (!pbhTable.validateAddType(acl_table_type_t::ACL_TABLE_PBH)) + static const auto pbhTableType = AclTableTypeBuilder() + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(std::make_shared(SAI_ACL_TABLE_ATTR_FIELD_GRE_KEY)) + .withMatch(std::make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(std::make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withMatch(std::make_shared(SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER)) + .withMatch(std::make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(std::make_shared(SAI_ACL_TABLE_ATTR_FIELD_INNER_ETHER_TYPE)) + .build(); + + if (!pbhTable.validateAddType(pbhTableType)) { SWSS_LOG_ERROR("Failed to configure PBH table(%s) type", table.key.c_str()); return false; diff --git a/orchagent/pfcactionhandler.cpp b/orchagent/pfcactionhandler.cpp index 0a24b1ba01..e44521f849 100644 --- a/orchagent/pfcactionhandler.cpp +++ b/orchagent/pfcactionhandler.cpp @@ -301,20 +301,20 @@ PfcWdAclHandler::PfcWdAclHandler(sai_object_id_t port, sai_object_id_t queue, { SWSS_LOG_ENTER(); - acl_table_type_t table_type; + string table_type; string queuestr = to_string(queueId); m_strRule = "Rule_PfcWdAclHandler_" + queuestr; // Ingress table/rule creation - table_type = ACL_TABLE_DROP; + table_type = TABLE_TYPE_DROP; m_strIngressTable = INGRESS_TABLE_DROP; auto found = m_aclTables.find(m_strIngressTable); if (found == m_aclTables.end()) { // First time of handling PFC for this queue, create ACL table, and bind createPfcAclTable(port, m_strIngressTable, true); - shared_ptr newRule = make_shared(gAclOrch, m_strRule, m_strIngressTable, table_type); + shared_ptr newRule = make_shared(gAclOrch, m_strRule, m_strIngressTable); createPfcAclRule(newRule, queueId, m_strIngressTable, port); } else @@ -322,7 +322,7 @@ PfcWdAclHandler::PfcWdAclHandler(sai_object_id_t port, sai_object_id_t queue, AclRule* rule = gAclOrch->getAclRule(m_strIngressTable, m_strRule); if (rule == nullptr) { - shared_ptr newRule = make_shared(gAclOrch, m_strRule, m_strIngressTable, table_type); + shared_ptr newRule = make_shared(gAclOrch, m_strRule, m_strIngressTable); createPfcAclRule(newRule, queueId, m_strIngressTable, port); } else @@ -332,14 +332,14 @@ PfcWdAclHandler::PfcWdAclHandler(sai_object_id_t port, sai_object_id_t queue, } // Egress table/rule creation - table_type = ACL_TABLE_PFCWD; + table_type = TABLE_TYPE_PFCWD; m_strEgressTable = "EgressTable_PfcWdAclHandler_" + queuestr; found = m_aclTables.find(m_strEgressTable); if (found == m_aclTables.end()) { // First time of handling PFC for this queue, create ACL table, and bind createPfcAclTable(port, m_strEgressTable, false); - shared_ptr newRule = make_shared(gAclOrch, m_strRule, m_strEgressTable, table_type); + shared_ptr newRule = make_shared(gAclOrch, m_strRule, m_strEgressTable); createPfcAclRule(newRule, queueId, m_strEgressTable, port); } else @@ -392,7 +392,7 @@ void PfcWdAclHandler::createPfcAclTable(sai_object_id_t port, string strTable, b auto inserted = m_aclTables.emplace(piecewise_construct, std::forward_as_tuple(strTable), - std::forward_as_tuple()); + std::forward_as_tuple(gAclOrch, strTable)); assert(inserted.second); @@ -408,23 +408,26 @@ void PfcWdAclHandler::createPfcAclTable(sai_object_id_t port, string strTable, b } aclTable.link(port); - aclTable.id = strTable; if (ingress) { - aclTable.type = ACL_TABLE_DROP; + auto dropType = gAclOrch->getAclTableType(TABLE_TYPE_DROP); + assert(dropType); + aclTable.validateAddType(*dropType); aclTable.stage = ACL_STAGE_INGRESS; } else { - aclTable.type = ACL_TABLE_PFCWD; + auto pfcwdType = gAclOrch->getAclTableType(TABLE_TYPE_PFCWD); + assert(pfcwdType); + aclTable.validateAddType(*pfcwdType); aclTable.stage = ACL_STAGE_EGRESS; } gAclOrch->addAclTable(aclTable); } -void PfcWdAclHandler::createPfcAclRule(shared_ptr rule, uint8_t queueId, string strTable, sai_object_id_t portOid) +void PfcWdAclHandler::createPfcAclRule(shared_ptr rule, uint8_t queueId, string strTable, sai_object_id_t portOid) { SWSS_LOG_ENTER(); diff --git a/orchagent/pfcactionhandler.h b/orchagent/pfcactionhandler.h index 381f9bdca8..23cabaee10 100644 --- a/orchagent/pfcactionhandler.h +++ b/orchagent/pfcactionhandler.h @@ -111,7 +111,7 @@ class PfcWdAclHandler: public PfcWdLossyHandler string m_strEgressTable; string m_strRule; void createPfcAclTable(sai_object_id_t port, string strTable, bool ingress); - void createPfcAclRule(shared_ptr rule, uint8_t queueId, string strTable, sai_object_id_t port); + void createPfcAclRule(shared_ptr rule, uint8_t queueId, string strTable, sai_object_id_t port); void updatePfcAclRule(shared_ptr rule, uint8_t queueId, string strTable, vector port); }; diff --git a/orchagent/saihelper.h b/orchagent/saihelper.h index 450acf8b69..a0b2aa2fac 100644 --- a/orchagent/saihelper.h +++ b/orchagent/saihelper.h @@ -4,6 +4,9 @@ #include +#define IS_ATTR_ID_IN_RANGE(attrId, objectType, attrPrefix) \ + ((attrId) >= SAI_ ## objectType ## _ATTR_ ## attrPrefix ## _START && (attrId) <= SAI_ ## objectType ## _ATTR_ ## attrPrefix ## _END) + void initSaiApi(); void initSaiRedis(const std::string &record_location, const std::string &record_filename); sai_status_t initSaiPhyApi(swss::gearbox_phy_t *phy); diff --git a/tests/dvslib/dvs_acl.py b/tests/dvslib/dvs_acl.py index dbf9791b53..9111de7a8e 100644 --- a/tests/dvslib/dvs_acl.py +++ b/tests/dvslib/dvs_acl.py @@ -6,6 +6,7 @@ class DVSAcl: """Manage ACL tables and rules on the virtual switch.""" CDB_ACL_TABLE_NAME = "ACL_TABLE" + CDB_ACL_TABLE_TYPE_NAME = "ACL_TABLE_TYPE" CDB_MIRROR_ACTION_LOOKUP = { "ingress": "MIRROR_INGRESS_ACTION", @@ -48,6 +49,26 @@ def __init__(self, asic_db, config_db, state_db, counters_db): self.state_db = state_db self.counters_db = counters_db + def create_acl_table_type( + self, + name: str, + matches: List[str], + bpoint_types: List[str] + ) -> None: + """Create a new ACL table type in Config DB. + + Args: + name: The name for the new ACL table type. + matches: A list of matches to use in ACL table. + bpoint_types: A list of bind point types to use in ACL table. + """ + table_type_attrs = { + "matches@": ",".join(matches), + "bind_points@": ",".join(bpoint_types) + } + + self.config_db.create_entry(self.CDB_ACL_TABLE_TYPE_NAME, name, table_type_attrs) + def create_acl_table( self, table_name: str, @@ -111,6 +132,14 @@ def remove_acl_table(self, table_name: str) -> None: """ self.config_db.delete_entry(self.CDB_ACL_TABLE_NAME, table_name) + def remove_acl_table_type(self, name: str) -> None: + """Remove an ACL table type from Config DB. + + Args: + name: The name of the ACL table type to delete. + """ + self.config_db.delete_entry(self.CDB_ACL_TABLE_TYPE_NAME, name) + def get_acl_table_ids(self, expected: int) -> List[str]: """Get all of the ACL table IDs in ASIC DB. diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index 4ffee617ad..c0d7399570 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -99,8 +99,25 @@ namespace aclorch_test TEST_F(AclTest, Create_L3_Acl_Table) { - AclTable acltable; - acltable.type = ACL_TABLE_L3; + AclTable acltable; /* this test shouldn't trigger a call to gAclOrch because it's nullptr */ + AclTableTypeBuilder builder; + auto l3TableType = builder + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_PORT) + .withBindPointType(SAI_ACL_BIND_POINT_TYPE_LAG) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_SRC_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_DST_IP)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT)) + .withMatch(make_shared(SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS)) + .withMatch(make_shared(set({SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}))) + .build(); + acltable.type = l3TableType; auto res = createAclTable(acltable); ASSERT_TRUE(res->ret_val); @@ -139,7 +156,7 @@ namespace aclorch_test vector acl_table_connectors = { confDbAclTable, confDbAclRuleTable }; - m_aclOrch = new AclOrch(acl_table_connectors, switchOrch, portsOrch, mirrorOrch, + m_aclOrch = new AclOrch(acl_table_connectors, state_db, switchOrch, portsOrch, mirrorOrch, neighOrch, routeOrch); } @@ -153,6 +170,15 @@ namespace aclorch_test return m_aclOrch; } + void doAclTableTypeTask(const deque &entries) + { + auto consumer = unique_ptr(new Consumer( + new swss::ConsumerStateTable(config_db, CFG_ACL_TABLE_TYPE_TABLE_NAME, 1, 1), m_aclOrch, CFG_ACL_TABLE_TYPE_TABLE_NAME)); + + consumer->addToSync(entries); + static_cast(m_aclOrch)->doTask(*consumer); + } + void doAclTableTask(const deque &entries) { auto consumer = unique_ptr(new Consumer( @@ -176,6 +202,27 @@ namespace aclorch_test return m_aclOrch->getTableById(table_id); } + const AclRule* getAclRule(string tableName, string ruleName) + { + return m_aclOrch->getAclRule(tableName, ruleName); + } + + const AclTable* getTableByOid(sai_object_id_t oid) + { + return m_aclOrch->getTableByOid(oid); + } + + const AclTable* getAclTable(string tableName) + { + auto oid = m_aclOrch->getTableById(tableName); + return getTableByOid(oid); + } + + const AclTableType* getAclTableType(string tableTypeName) + { + return m_aclOrch->getAclTableType(tableTypeName); + } + const map &getAclTables() const { return Portal::AclOrchInternal::getAclTables(m_aclOrch); @@ -448,24 +495,22 @@ namespace aclorch_test fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS", "true" }); fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE", "2:SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE,SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE" }); - switch (acl_table.type) + if (acl_table.type.getName() == TABLE_TYPE_L3) { - case ACL_TABLE_L3: - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE", "true" }); - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_SRC_IP", "true" }); - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_DST_IP", "true" }); - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL", "true" }); - break; - - case ACL_TABLE_L3V6: - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6", "true" }); - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6", "true" }); - fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER", "true" }); - break; - - default: - // We shouldn't get here. Will continue to add more test cases ...; - ; + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_SRC_IP", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_DST_IP", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL", "true" }); + } + else if (acl_table.type.getName() == TABLE_TYPE_L3V6) + { + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_SRC_IPV6", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_DST_IPV6", "true" }); + fields.push_back({ "SAI_ACL_TABLE_ATTR_FIELD_IPV6_NEXT_HEADER", "true" }); + } + else + { + // We shouldn't get here. Will continue to add more test cases ...; } if (ACL_STAGE_INGRESS == acl_table.stage) @@ -492,19 +537,17 @@ namespace aclorch_test fields.push_back({ "SAI_ACL_ENTRY_ATTR_ADMIN_STATE", "true" }); fields.push_back({ "SAI_ACL_ENTRY_ATTR_ACTION_COUNTER", counter_id }); - switch (acl_table.type) + if (acl_table.type.getName() == TABLE_TYPE_L3) { - case ACL_TABLE_L3: - fields.push_back({ "SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP", "1.2.3.4&mask:255.255.255.255" }); - break; - - case ACL_TABLE_L3V6: - fields.push_back({ "SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6", "::1.2.3.4&mask:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" }); - break; - - default: - // We shouldn't get here. Will continue to add more test cases ... - ; + fields.push_back({ "SAI_ACL_ENTRY_ATTR_FIELD_SRC_IP", "1.2.3.4&mask:255.255.255.255" }); + } + if (acl_table.type.getName() == TABLE_TYPE_L3V6) + { + fields.push_back({ "SAI_ACL_ENTRY_ATTR_FIELD_SRC_IPV6", "::1.2.3.4&mask:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" }); + } + else + { + // We shouldn't get here. Will continue to add more test cases ... } return shared_ptr(new SaiAttributeList(objecttype, fields, false)); @@ -569,13 +612,17 @@ namespace aclorch_test return true; } - bool validateAclTable(sai_object_id_t acl_table_oid, const AclTable &acl_table) + bool validateAclTable(sai_object_id_t acl_table_oid, const AclTable &acl_table, shared_ptr expAttrList = nullptr) { const sai_object_type_t objecttype = SAI_OBJECT_TYPE_ACL_TABLE; - auto exp_attrlist_2 = getAclTableAttributeList(objecttype, acl_table); + if (!expAttrList) + { + expAttrList = getAclTableAttributeList(objecttype, acl_table); + + } { - auto &exp_attrlist = *exp_attrlist_2; + auto &exp_attrlist = *expAttrList; vector act_attr; @@ -641,23 +688,16 @@ namespace aclorch_test auto const &resourceMap = Portal::CrmOrchInternal::getResourceMap(crmOrch); // Verify the ACL tables - uint32_t crmAclTableBindingCount = 0; + size_t crmAclTableBindingCount = 0; for (auto const &kv: resourceMap.at(CrmResourceType::CRM_ACL_TABLE).countersMap) { crmAclTableBindingCount += kv.second.usedCounter; } - uint32_t aclorchAclTableBindingCount = 0; + size_t aclorchAclTableBindingCount = 0; for (auto const &kv: Portal::AclOrchInternal::getAclTables(aclOrch)) { - if (kv.second.type == ACL_TABLE_PFCWD) - { - aclorchAclTableBindingCount += 1; // port binding only - } - else - { - aclorchAclTableBindingCount += 2; // port + LAG binding - } + aclorchAclTableBindingCount += kv.second.type.getBindPointTypes().size(); } if (crmAclTableBindingCount != aclorchAclTableBindingCount) @@ -770,21 +810,7 @@ namespace aclorch_test { if (fv.first == ACL_TABLE_TYPE) { - if (fv.second == TABLE_TYPE_L3) - { - if (acl_table.type != ACL_TABLE_L3) - { - return false; - } - } - else if (fv.second == TABLE_TYPE_L3V6) - { - if (acl_table.type != ACL_TABLE_L3V6) - { - return false; - } - } - else + if (acl_table.type.getName() != fv.second) { return false; } @@ -1336,6 +1362,279 @@ namespace aclorch_test ASSERT_EQ(tableIt, orch->getAclTables().end()); } + TEST_F(AclOrchTest, AclTableType_Configuration) + { + const string aclTableTypeName = "TEST_TYPE"; + const string aclTableName = "TEST_TABLE"; + const string aclRuleName = "TEST_RULE"; + + auto orch = createAclOrch(); + + auto tableKofvt = deque( + { + { + aclTableName, + SET_COMMAND, + { + { ACL_TABLE_DESCRIPTION, "Test table" }, + { ACL_TABLE_TYPE, aclTableTypeName}, + { ACL_TABLE_STAGE, STAGE_INGRESS }, + { ACL_TABLE_PORTS, "1,2" } + } + } + } + ); + + orch->doAclTableTask(tableKofvt); + + // Table not created without table type + ASSERT_FALSE(orch->getAclTable(aclTableName)); + + orch->doAclTableTypeTask( + deque( + { + { + aclTableTypeName, + SET_COMMAND, + { + { + ACL_TABLE_TYPE_MATCHES, + string(MATCH_SRC_IP) + comma + MATCH_ETHER_TYPE + comma + MATCH_L4_SRC_PORT_RANGE + }, + { + ACL_TABLE_TYPE_BPOINT_TYPES, + string(BIND_POINT_TYPE_PORT) + comma + BIND_POINT_TYPE_PORTCHANNEL + }, + } + } + } + ) + ); + + orch->doAclTableTask(tableKofvt); + + // Table is created now + ASSERT_TRUE(orch->getAclTable(aclTableName)); + + auto fvs = vector{ + { "SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST", "2:SAI_ACL_BIND_POINT_TYPE_PORT,SAI_ACL_BIND_POINT_TYPE_LAG" }, + { "SAI_ACL_TABLE_ATTR_FIELD_SRC_IP", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE", "1:SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE" }, + }; + + ASSERT_TRUE(validateAclTable( + orch->getAclTable(aclTableName)->getOid(), + *orch->getAclTable(aclTableName), + make_shared(SAI_OBJECT_TYPE_ACL_TABLE, fvs, false)) + ); + + orch->doAclRuleTask( + deque( + { + { + aclTableName + "|" + aclRuleName, + SET_COMMAND, + { + { MATCH_SRC_IP, "1.1.1.1/32" }, + { MATCH_L4_DST_PORT_RANGE, "80..100" }, + { ACTION_PACKET_ACTION, PACKET_ACTION_DROP }, + } + } + } + ) + ); + + // L4_DST_PORT_RANGE is not in the table type + ASSERT_FALSE(orch->getAclRule(aclTableName, aclRuleName)); + + orch->doAclRuleTask( + deque( + { + { + aclTableName + "|" + aclRuleName, + SET_COMMAND, + { + { MATCH_SRC_IP, "1.1.1.1/32" }, + { MATCH_DST_IP, "2.2.2.2/32" }, + { ACTION_PACKET_ACTION, PACKET_ACTION_DROP }, + } + } + } + ) + ); + + // DST_IP is not in the table type + ASSERT_FALSE(orch->getAclRule(aclTableName, aclRuleName)); + + orch->doAclRuleTask( + deque( + { + { + aclTableName + "|" + aclRuleName, + SET_COMMAND, + { + { MATCH_SRC_IP, "1.1.1.1/32" }, + { ACTION_PACKET_ACTION, PACKET_ACTION_DROP }, + } + } + } + ) + ); + + // Now it is valid for this table. + ASSERT_TRUE(orch->getAclRule(aclTableName, aclRuleName)); + + orch->doAclRuleTask( + deque( + { + { + aclTableName + "|" + aclRuleName, + DEL_COMMAND, + {} + } + } + ) + ); + + ASSERT_FALSE(orch->getAclRule(aclTableName, aclRuleName)); + + orch->doAclTableTypeTask( + deque( + { + { + aclTableTypeName, + DEL_COMMAND, + {} + } + } + ) + ); + + // Table still exists + ASSERT_TRUE(orch->getAclTable(aclTableName)); + ASSERT_FALSE(orch->getAclTableType(aclTableTypeName)); + + orch->doAclTableTask( + deque( + { + { + aclTableName, + DEL_COMMAND, + {} + } + } + ) + ); + + // Table is removed + ASSERT_FALSE(orch->getAclTable(aclTableName)); + } + + TEST_F(AclOrchTest, AclTableType_ActionValidation) + { + const string aclTableTypeName = "TEST_TYPE"; + const string aclTableName = "TEST_TABLE"; + const string aclRuleName = "TEST_RULE"; + + auto orch = createAclOrch(); + + orch->doAclTableTypeTask( + deque( + { + { + aclTableTypeName, + SET_COMMAND, + { + { + ACL_TABLE_TYPE_MATCHES, + string(MATCH_ETHER_TYPE) + comma + MATCH_L4_SRC_PORT_RANGE + comma + MATCH_L4_DST_PORT_RANGE + }, + { + ACL_TABLE_TYPE_BPOINT_TYPES, + BIND_POINT_TYPE_PORTCHANNEL + }, + { + ACL_TABLE_TYPE_ACTIONS, + ACTION_MIRROR_INGRESS_ACTION + } + } + } + } + ) + ); + + orch->doAclTableTask( + deque( + { + { + aclTableName, + SET_COMMAND, + { + { ACL_TABLE_DESCRIPTION, "Test table" }, + { ACL_TABLE_TYPE, aclTableTypeName}, + { ACL_TABLE_STAGE, STAGE_INGRESS }, + { ACL_TABLE_PORTS, "1,2" } + } + } + } + ) + ); + + ASSERT_TRUE(orch->getAclTable(aclTableName)); + + auto fvs = vector{ + { "SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST", "1:SAI_ACL_BIND_POINT_TYPE_LAG" }, + { "SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE", "true" }, + { "SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE", "2:SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE,SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE" }, + { "SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST", "1:SAI_ACL_ACTION_TYPE_MIRROR_INGRESS" }, + }; + + ASSERT_TRUE(validateAclTable( + orch->getAclTable(aclTableName)->getOid(), + *orch->getAclTable(aclTableName), + make_shared(SAI_OBJECT_TYPE_ACL_TABLE, fvs, false)) + ); + + orch->doAclRuleTask( + deque( + { + { + aclTableName + "|" + aclRuleName, + SET_COMMAND, + { + { MATCH_ETHER_TYPE, "2048" }, + { ACTION_PACKET_ACTION, PACKET_ACTION_DROP }, + } + } + } + ) + ); + + // Packet action is not supported on this table + ASSERT_FALSE(orch->getAclRule(aclTableName, aclRuleName)); + + const auto testSessionName = "test_session"; + gMirrorOrch->createEntry(testSessionName, {}); + orch->doAclRuleTask( + deque( + { + { + aclTableName + "|" + aclRuleName, + SET_COMMAND, + { + { MATCH_ETHER_TYPE, "2048" }, + { ACTION_MIRROR_INGRESS_ACTION, testSessionName }, + } + } + } + ) + ); + + // Mirror action is supported on this table + ASSERT_TRUE(orch->getAclRule(aclTableName, aclRuleName)); + } + TEST_F(AclOrchTest, AclRuleUpdate) { string acl_table_id = "acl_table_1"; @@ -1362,11 +1661,11 @@ namespace aclorch_test auto it_table = acl_tables.find(acl_table_oid); ASSERT_NE(it_table, acl_tables.end()); - class AclRuleTest : public AclRuleL3 + class AclRuleTest : public AclRulePacket { public: AclRuleTest(AclOrch* orch, string rule, string table): - AclRuleL3(orch, rule, table, ACL_TABLE_L3, true) + AclRulePacket(orch, rule, table, true) {} void setCounterEnabled(bool enabled) diff --git a/tests/test_acl.py b/tests/test_acl.py index d4b317bf55..fb8aecb0ea 100644 --- a/tests/test_acl.py +++ b/tests/test_acl.py @@ -10,6 +10,15 @@ L3V6_BIND_PORTS = ["Ethernet0", "Ethernet4", "Ethernet8"] L3V6_RULE_NAME = "L3V6_TEST_RULE" +MCLAG_TABLE_TYPE = "MCLAG" +MCLAG_TABLE_NAME = "MCLAG_TEST" +MCLAG_BIND_PORTS = ["Ethernet0", "Ethernet4", "Ethernet8", "Ethernet12"] +MCLAG_RULE_NAME = "MCLAG_TEST_RULE" + +MIRROR_TABLE_TYPE = "MIRROR" +MIRROR_TABLE_NAME = "MIRROR_TEST" +MIRROR_BIND_PORTS = ["Ethernet0", "Ethernet4", "Ethernet8", "Ethernet12"] +MIRROR_RULE_NAME = "MIRROR_TEST_RULE" class TestAcl: @pytest.yield_fixture @@ -32,6 +41,24 @@ def l3v6_acl_table(self, dvs_acl): dvs_acl.remove_acl_table(L3V6_TABLE_NAME) dvs_acl.verify_acl_table_count(0) + @pytest.yield_fixture + def mclag_acl_table(self, dvs_acl): + try: + dvs_acl.create_acl_table(MCLAG_TABLE_NAME, MCLAG_TABLE_TYPE, MCLAG_BIND_PORTS) + yield dvs_acl.get_acl_table_ids(1)[0] + finally: + dvs_acl.remove_acl_table(MCLAG_TABLE_NAME) + dvs_acl.verify_acl_table_count(0) + + @pytest.yield_fixture + def mirror_acl_table(self, dvs_acl): + try: + dvs_acl.create_acl_table(MIRROR_TABLE_NAME, MIRROR_TABLE_TYPE, MIRROR_BIND_PORTS) + yield dvs_acl.get_acl_table_ids(1)[0] + finally: + dvs_acl.remove_acl_table(MIRROR_TABLE_NAME) + dvs_acl.verify_acl_table_count(0) + @pytest.yield_fixture def setup_teardown_neighbor(self, dvs): try: @@ -133,42 +160,69 @@ def test_V6AclRuleNextHeaderAppendedForTCPFlags(self, dvs_acl, l3v6_acl_table): dvs_acl.remove_acl_rule(L3V6_TABLE_NAME, L3V6_RULE_NAME) dvs_acl.verify_no_acl_rules() - def test_AclRuleInOutPorts(self, dvs_acl, l3_acl_table): + def test_AclRuleInPorts(self, dvs_acl, mirror_acl_table): + """ + Verify IN_PORTS matches on ACL rule. + Using MIRROR table type for IN_PORTS matches. + """ config_qualifiers = { - "IN_PORTS": "Ethernet0,Ethernet4", - "OUT_PORTS": "Ethernet8,Ethernet12" + "IN_PORTS": "Ethernet8,Ethernet12", + } + + expected_sai_qualifiers = { + "SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS": dvs_acl.get_port_list_comparator(["Ethernet8", "Ethernet12"]) + } + + dvs_acl.create_acl_rule(MIRROR_TABLE_NAME, MIRROR_RULE_NAME, config_qualifiers) + dvs_acl.verify_acl_rule(expected_sai_qualifiers) + + dvs_acl.remove_acl_rule(MIRROR_TABLE_NAME, MIRROR_RULE_NAME) + dvs_acl.verify_no_acl_rules() + + def test_AclRuleOutPorts(self, dvs_acl, mclag_acl_table): + """ + Verify OUT_PORTS matches on ACL rule. + Using MCLAG table type for OUT_PORTS matches. + """ + config_qualifiers = { + "OUT_PORTS": "Ethernet8,Ethernet12", } expected_sai_qualifiers = { - "SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS": dvs_acl.get_port_list_comparator(["Ethernet0", "Ethernet4"]), "SAI_ACL_ENTRY_ATTR_FIELD_OUT_PORTS": dvs_acl.get_port_list_comparator(["Ethernet8", "Ethernet12"]) } - dvs_acl.create_acl_rule(L3_TABLE_NAME, L3_RULE_NAME, config_qualifiers) + dvs_acl.create_acl_rule(MCLAG_TABLE_NAME, MCLAG_RULE_NAME, config_qualifiers) dvs_acl.verify_acl_rule(expected_sai_qualifiers) - dvs_acl.remove_acl_rule(L3_TABLE_NAME, L3_RULE_NAME) + dvs_acl.remove_acl_rule(MCLAG_TABLE_NAME, MCLAG_RULE_NAME) dvs_acl.verify_no_acl_rules() - def test_AclRuleInPortsNonExistingInterface(self, dvs_acl, l3_acl_table): + def test_AclRuleInPortsNonExistingInterface(self, dvs_acl, mirror_acl_table): + """ + Using MIRROR table type as it has IN_PORTS matches. + """ config_qualifiers = { "IN_PORTS": "FOO_BAR_BAZ" } - dvs_acl.create_acl_rule(L3_TABLE_NAME, L3_RULE_NAME, config_qualifiers) + dvs_acl.create_acl_rule(MIRROR_TABLE_NAME, MIRROR_RULE_NAME, config_qualifiers) dvs_acl.verify_no_acl_rules() - dvs_acl.remove_acl_rule(L3_TABLE_NAME, L3_RULE_NAME) + dvs_acl.remove_acl_rule(MIRROR_TABLE_NAME, MIRROR_RULE_NAME) - def test_AclRuleOutPortsNonExistingInterface(self, dvs_acl, l3_acl_table): + def test_AclRuleOutPortsNonExistingInterface(self, dvs_acl, mclag_acl_table): + """ + Using MCLAG table type as it has OUT_PORTS matches. + """ config_qualifiers = { "OUT_PORTS": "FOO_BAR_BAZ" } - dvs_acl.create_acl_rule(L3_TABLE_NAME, L3_RULE_NAME, config_qualifiers) + dvs_acl.create_acl_rule(MCLAG_TABLE_NAME, MCLAG_RULE_NAME, config_qualifiers) dvs_acl.verify_no_acl_rules() - dvs_acl.remove_acl_rule(L3_TABLE_NAME, L3_RULE_NAME) + dvs_acl.remove_acl_rule(MCLAG_TABLE_NAME, MCLAG_RULE_NAME) def test_AclRuleVlanId(self, dvs_acl, l3_acl_table): config_qualifiers = {"VLAN_ID": "100"} @@ -550,15 +604,12 @@ def test_ValidateAclTableBindingCrmUtilization(self, dvs, dvs_acl): class TestAclRuleValidation: """Test class for cases that check if orchagent corectly validates ACL rules input.""" - SWITCH_CAPABILITY_TABLE = "SWITCH_CAPABILITY" + ACL_STAGE_CAPABILITY_TABLE_NAME = "ACL_STAGE_CAPABILITY_TABLE" + ACL_ACTION_LIST_FIELD_NAME = "action_list" def get_acl_actions_supported(self, dvs_acl, stage): - switch_id = dvs_acl.state_db.wait_for_n_keys(self.SWITCH_CAPABILITY_TABLE, 1)[0] - switch = dvs_acl.state_db.wait_for_entry(self.SWITCH_CAPABILITY_TABLE, switch_id) - - field = "ACL_ACTIONS|{}".format(stage.upper()) - - supported_actions = switch.get(field, None) + switch = dvs_acl.state_db.wait_for_entry(self.ACL_STAGE_CAPABILITY_TABLE_NAME, stage.upper()) + supported_actions = switch.get(self.ACL_ACTION_LIST_FIELD_NAME, None) if supported_actions: supported_actions = supported_actions.split(",") diff --git a/tests/test_acl_egress_table.py b/tests/test_acl_egress_table.py index f2b917ebc6..01800d6b20 100644 --- a/tests/test_acl_egress_table.py +++ b/tests/test_acl_egress_table.py @@ -1,6 +1,19 @@ import pytest -TABLE_TYPE = "L3" +TABLE_TYPE = "CUSTOM_L3" +CUSTOM_TABLE_TYPE_MATCHES = [ + "L4_SRC_PORT_RANGE", + "L4_DST_PORT_RANGE", + "ETHER_TYPE", + "TUNNEL_VNI", + "TC", + "INNER_IP_PROTOCOL", + "INNER_ETHER_TYPE", + "INNER_L4_SRC_PORT", + "INNER_L4_DST_PORT", + "VLAN_ID" +] +CUSTOM_TABLE_TYPE_BPOINT_TYPES = ["PORT","PORTCHANNEL"] TABLE_NAME = "EGRESS_TEST" BIND_PORTS = ["Ethernet0", "Ethernet4"] RULE_NAME = "EGRESS_TEST_RULE" @@ -10,14 +23,17 @@ class TestEgressAclTable: @pytest.yield_fixture def egress_acl_table(self, dvs_acl): try: + dvs_acl.create_acl_table_type(TABLE_TYPE, CUSTOM_TABLE_TYPE_MATCHES, CUSTOM_TABLE_TYPE_BPOINT_TYPES) dvs_acl.create_acl_table(TABLE_NAME, TABLE_TYPE, BIND_PORTS, stage="egress") yield dvs_acl.get_acl_table_ids(1)[0] finally: dvs_acl.remove_acl_table(TABLE_NAME) + dvs_acl.remove_acl_table_type(TABLE_TYPE) dvs_acl.verify_acl_table_count(0) def test_EgressAclTableCreationDeletion(self, dvs_acl): try: + dvs_acl.create_acl_table_type(TABLE_TYPE, CUSTOM_TABLE_TYPE_MATCHES, CUSTOM_TABLE_TYPE_BPOINT_TYPES) dvs_acl.create_acl_table(TABLE_NAME, TABLE_TYPE, BIND_PORTS, stage="egress") acl_table_id = dvs_acl.get_acl_table_ids(1)[0] @@ -27,6 +43,7 @@ def test_EgressAclTableCreationDeletion(self, dvs_acl): dvs_acl.verify_acl_table_port_binding(acl_table_id, BIND_PORTS, 1, stage="egress") finally: dvs_acl.remove_acl_table(TABLE_NAME) + dvs_acl.remove_acl_table_type(TABLE_TYPE) dvs_acl.verify_acl_table_count(0) def test_EgressAclRuleL4SrcPortRange(self, dvs_acl, egress_acl_table): diff --git a/tests/test_mirror_ipv6_combined.py b/tests/test_mirror_ipv6_combined.py index cafa5eaf47..e75f6af72d 100644 --- a/tests/test_mirror_ipv6_combined.py +++ b/tests/test_mirror_ipv6_combined.py @@ -473,85 +473,6 @@ def test_AclBindMirrorV6Reorder2(self, dvs, testlog): self.set_interface_status("Ethernet32", "down") - # Test case - create ACL rules associated with wrong table - # 0. predefine the VS platform: mellanox platform - # 1. create a mirror session - # 2. create the ipv4 ACL table - # 3. create the ipv6 ACL rule associated with ipv4 table - # 4. create the ipv6 ACL table - # 5. create the ipv4 ACL rule associated with ipv6 table - # 6. verify two rules are inserted successfully - def test_AclBindMirrorV6WrongConfig(self, dvs, testlog): - """ - This test verifies IPv6 rules cannot be inserted into MIRROR table - """ - self.setup_db(dvs) - - session = "MIRROR_SESSION" - acl_table = "MIRROR_TABLE" - acl_table_v6 = "MIRROR_TABLE_V6" - acl_rule_1 = "MIRROR_RULE_1" - acl_rule_2 = "MIRROR_RULE_2" - - # bring up port; assign ip; create neighbor; create route - self.set_interface_status("Ethernet32", "up") - self.add_ip_address("Ethernet32", "20.0.0.0/31") - self.add_neighbor("Ethernet32", "20.0.0.1", "02:04:06:08:10:12") - self.add_route(dvs, "4.4.4.4", "20.0.0.1") - - # create mirror session - self.create_mirror_session(session, "3.3.3.3", "4.4.4.4", "0x6558", "8", "100", "0") - assert self.get_mirror_session_state(session)["status"] == "active" - - # assert mirror session in asic database - tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_MIRROR_SESSION") - assert len(tbl.getKeys()) == 1 - mirror_session_oid = tbl.getKeys()[0] - - # create acl table ipv4 - self.create_acl_table(acl_table, ["Ethernet0", "Ethernet4"], "MIRROR") - - # create WRONG acl rule with IPv6 addresses - self.create_mirror_acl_ipv6_rule(acl_table, acl_rule_2, session) - - # assert acl rule is not created - tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") - rule_entries = [k for k in tbl.getKeys() if k not in dvs.asicdb.default_acl_entries] - assert len(rule_entries) == 0 - - # create acl table ipv6 - self.create_acl_table(acl_table_v6, ["Ethernet0", "Ethernet4"], "MIRRORV6") - - # create WRONG acl rule with IPv4 addresses - self.create_mirror_acl_ipv4_rule(acl_table_v6, acl_rule_1, session) - - # assert acl rules are created - tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") - rule_entries = [k for k in tbl.getKeys() if k not in dvs.asicdb.default_acl_entries] - assert len(rule_entries) == 0 - - # remove acl rule - self.remove_mirror_acl_rule(acl_table, acl_rule_1) - self.remove_mirror_acl_rule(acl_table_v6, acl_rule_2) - - # remove acl table - self.remove_acl_table(acl_table) - self.remove_acl_table(acl_table_v6) - - # remove mirror session - self.remove_mirror_session(session) - - # assert no mirror session in asic database - tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_MIRROR_SESSION") - assert len(tbl.getKeys()) == 0 - - # remove route; remove neighbor; remove ip; bring down port - self.remove_route(dvs, "4.4.4.4") - self.remove_neighbor("Ethernet32", "20.0.0.1") - self.remove_ip_address("Ethernet32", "20.0.0.0/31") - self.set_interface_status("Ethernet32", "down") - - # Add Dummy always-pass test at end as workaroud # for issue when Flaky fail on final test it invokes module tear-down before retrying def test_nonflaky_dummy(): From 9639db78de096a601df127b24b9ec0de6b475043 Mon Sep 17 00:00:00 2001 From: Wenda Ni Date: Tue, 23 Nov 2021 13:04:09 -0800 Subject: [PATCH 04/14] [vstest/subintf] Add vs test to validate sub interface ingress to a vnet (#1642) What I did Extended the following vs tests to validate the use case of sub interface ingress linked to a vnet: sub port interface creation sub port interface add ipv4 addresses sub port interface admin status change sub port interface remove ipv4 addresses sub port interface removal Why I did it Add vs test to validate that #1521 fixes Azure/sonic-buildimage#4238 --- tests/test_sub_port_intf.py | 216 ++++++++++++++++++++++++++++-------- 1 file changed, 167 insertions(+), 49 deletions(-) diff --git a/tests/test_sub_port_intf.py b/tests/test_sub_port_intf.py index ac65a30696..2d442fd027 100644 --- a/tests/test_sub_port_intf.py +++ b/tests/test_sub_port_intf.py @@ -11,6 +11,8 @@ CFG_LAG_TABLE_NAME = "PORTCHANNEL" CFG_LAG_MEMBER_TABLE_NAME = "PORTCHANNEL_MEMBER" CFG_VRF_TABLE_NAME = "VRF" +CFG_VXLAN_TUNNEL_TABLE_NAME = "VXLAN_TUNNEL" +CFG_VNET_TABLE_NAME = "VNET" STATE_PORT_TABLE_NAME = "PORT_TABLE" STATE_LAG_TABLE_NAME = "LAG_TABLE" @@ -20,6 +22,7 @@ APP_ROUTE_TABLE_NAME = "ROUTE_TABLE" APP_PORT_TABLE_NAME = "PORT_TABLE" APP_LAG_TABLE_NAME = "LAG_TABLE" +APP_VNET_TABLE_NAME = "VNET_TABLE" ASIC_RIF_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE" ASIC_ROUTE_ENTRY_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY" @@ -31,12 +34,20 @@ ASIC_VIRTUAL_ROUTER_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER" ASIC_PORT_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_PORT" ASIC_LAG_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_LAG" +ASIC_TUNNEL_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL" ADMIN_STATUS = "admin_status" VRF_NAME = "vrf_name" +VNET_NAME = "vnet_name" +SRC_IP = "src_ip" +VXLAN_TUNNEL = "vxlan_tunnel" +VNI = "vni" +PEER_LIST = "peer_list" ETHERNET_PREFIX = "Ethernet" LAG_PREFIX = "PortChannel" +VRF_PREFIX = "Vrf" +VNET_PREFIX = "Vnet" VLAN_SUB_INTERFACE_SEPARATOR = "." APPL_DB_SEPARATOR = ":" @@ -57,6 +68,11 @@ class TestSubPortIntf(object): VRF_UNDER_TEST = "Vrf0" + TUNNEL_UNDER_TEST = "Tunnel1" + VTEP_IP_UNDER_TEST = "1.1.1.1" + VNET_UNDER_TEST = "Vnet1000" + VNI_UNDER_TEST = "1000" + def connect_dbs(self, dvs): self.app_db = dvs.get_app_db() self.asic_db = dvs.get_asic_db() @@ -116,13 +132,32 @@ def set_parent_port_admin_status(self, dvs, port_name, status): else: self.set_parent_port_oper_status(dvs, port_name, "up") + def create_vxlan_tunnel(self, tunnel_name, vtep_ip): + fvs = { + SRC_IP: vtep_ip, + } + self.config_db.create_entry(CFG_VXLAN_TUNNEL_TABLE_NAME, tunnel_name, fvs) + + def create_vnet(self, vnet_name, tunnel_name, vni, peer_list=""): + fvs = { + VXLAN_TUNNEL: tunnel_name, + VNI: vni, + PEER_LIST: peer_list, + } + self.config_db.create_entry(CFG_VNET_TABLE_NAME, vnet_name, fvs) + def create_vrf(self, vrf_name): - self.config_db.create_entry(CFG_VRF_TABLE_NAME, vrf_name, {"NULL": "NULL"}) + if vrf_name.startswith(VRF_PREFIX): + self.config_db.create_entry(CFG_VRF_TABLE_NAME, vrf_name, {"NULL": "NULL"}) + else: + assert vrf_name.startswith(VNET_PREFIX) + self.create_vxlan_tunnel(self.TUNNEL_UNDER_TEST, self.VTEP_IP_UNDER_TEST) + self.create_vnet(vrf_name, self.TUNNEL_UNDER_TEST, self.VNI_UNDER_TEST) def create_sub_port_intf_profile(self, sub_port_intf_name, vrf_name=None): fvs = {ADMIN_STATUS: "up"} if vrf_name: - fvs[VRF_NAME] = vrf_name + fvs[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.config_db.create_entry(CFG_VLAN_SUB_INTF_TABLE_NAME, sub_port_intf_name, fvs) @@ -159,7 +194,7 @@ def create_sub_port_intf_profile_appl_db(self, sub_port_intf_name, admin_status, ("mtu", "0"), ] if vrf_name: - pairs.append((VRF_NAME, vrf_name)) + pairs.append((VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME, vrf_name)) fvs = swsscommon.FieldValuePairs(pairs) tbl = swsscommon.ProducerStateTable(self.app_db.db_connection, APP_INTF_TABLE_NAME) @@ -192,8 +227,18 @@ def set_sub_port_intf_admin_status(self, sub_port_intf_name, status): self.config_db.create_entry(CFG_VLAN_SUB_INTF_TABLE_NAME, sub_port_intf_name, fvs) + def remove_vxlan_tunnel(self, tunnel_name): + self.config_db.delete_entry(CFG_VXLAN_TUNNEL_TABLE_NAME, tunnel_name) + + def remove_vnet(self, vnet_name): + self.config_db.delete_entry(CFG_VNET_TABLE_NAME, vnet_name) + def remove_vrf(self, vrf_name): - self.config_db.delete_entry(CFG_VRF_TABLE_NAME, vrf_name) + if vrf_name.startswith(VRF_PREFIX): + self.config_db.delete_entry(CFG_VRF_TABLE_NAME, vrf_name) + else: + assert vrf_name.startswith(VNET_PREFIX) + self.remove_vnet(vrf_name) def check_vrf_removal(self, vrf_oid): self.asic_db.wait_for_deleted_keys(ASIC_VIRTUAL_ROUTER_TABLE, [vrf_oid]) @@ -298,17 +343,23 @@ def check_sub_port_intf_key_existence(self, db, table_name, key): def check_sub_port_intf_fvs(self, db, table_name, key, fv_dict): db.wait_for_field_match(table_name, key, fv_dict) - def check_sub_port_intf_route_entries(self, vrf_oid=None): + def check_sub_port_intf_route_entries(self, vrf_oid=None, check_ipv6=True): expected_dests = [self.IPV4_TOME_UNDER_TEST, self.IPV4_SUBNET_UNDER_TEST, self.IPV6_TOME_UNDER_TEST, self.IPV6_SUBNET_UNDER_TEST] + if not check_ipv6: + expected_dests.pop() + expected_dests.pop() if vrf_oid is None: vrf_oid = self.default_vrf_oid expected_vrf_oids = [vrf_oid, vrf_oid, vrf_oid, vrf_oid] + if not check_ipv6: + expected_vrf_oids.pop() + expected_vrf_oids.pop() def _access_function(): raw_route_entries = self.asic_db.get_keys(ASIC_ROUTE_ENTRY_TABLE) @@ -403,7 +454,7 @@ def _test_sub_port_intf_creation(self, dvs, sub_port_intf_name, vrf_name=None): ADMIN_STATUS: "up", } if vrf_name: - fv_dict[VRF_NAME] = vrf_name + fv_dict[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name, fv_dict) # Verify that a sub port router interface entry is created in ASIC_DB @@ -435,6 +486,9 @@ def _test_sub_port_intf_creation(self, dvs, sub_port_intf_name, vrf_name=None): if vrf_name: self.remove_vrf(vrf_name) self.check_vrf_removal(vrf_oid) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) if parent_port.startswith(LAG_PREFIX): # Remove lag members from lag parent port @@ -454,6 +508,9 @@ def test_sub_port_intf_creation(self, dvs): self._test_sub_port_intf_creation(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) self._test_sub_port_intf_creation(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) + self._test_sub_port_intf_creation(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + self._test_sub_port_intf_creation(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + def _test_sub_port_intf_add_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] @@ -468,7 +525,8 @@ def _test_sub_port_intf_add_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=Non self.create_sub_port_intf_profile(sub_port_intf_name, vrf_name) self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) @@ -478,8 +536,9 @@ def _test_sub_port_intf_add_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=Non } self.check_sub_port_intf_fvs(self.state_db, STATE_INTERFACE_TABLE_NAME, sub_port_intf_name + "|" + self.IPV4_ADDR_UNDER_TEST, fv_dict) - self.check_sub_port_intf_fvs(self.state_db, STATE_INTERFACE_TABLE_NAME, - sub_port_intf_name + "|" + self.IPV6_ADDR_UNDER_TEST, fv_dict) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.check_sub_port_intf_fvs(self.state_db, STATE_INTERFACE_TABLE_NAME, + sub_port_intf_name + "|" + self.IPV6_ADDR_UNDER_TEST, fv_dict) # Verify that ip address configuration is synced to APPL_DB INTF_TABLE by Intfmgrd fv_dict = { @@ -488,22 +547,26 @@ def _test_sub_port_intf_add_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=Non } self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name + ":" + self.IPV4_ADDR_UNDER_TEST, fv_dict) - fv_dict["family"] = "IPv6" - self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, - sub_port_intf_name + ":" + self.IPV6_ADDR_UNDER_TEST, fv_dict) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + fv_dict["family"] = "IPv6" + self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, + sub_port_intf_name + ":" + self.IPV6_ADDR_UNDER_TEST, fv_dict) # Verify that an IPv4 ip2me route entry is created in ASIC_DB # Verify that an IPv4 subnet route entry is created in ASIC_DB - # Verify that an IPv6 ip2me route entry is created in ASIC_DB - # Verify that an IPv6 subnet route entry is created in ASIC_DB - self.check_sub_port_intf_route_entries(vrf_oid) + # Verify that an IPv6 ip2me route entry is created in ASIC_DB for non-vnet case + # Verify that an IPv6 subnet route entry is created in ASIC_DB for non-vnet case + self.check_sub_port_intf_route_entries(vrf_oid, check_ipv6=True if vrf_name is None or not vrf_name.startswith(VNET_PREFIX) else False) # Remove IP addresses + ip_addrs = [ + self.IPV4_ADDR_UNDER_TEST, + ] self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) - self.check_sub_port_intf_ip_addr_removal(sub_port_intf_name, - [self.IPV4_ADDR_UNDER_TEST, - self.IPV6_ADDR_UNDER_TEST]) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + ip_addrs.append(self.IPV6_ADDR_UNDER_TEST) + self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + self.check_sub_port_intf_ip_addr_removal(sub_port_intf_name, ip_addrs) # Remove a sub port interface self.remove_sub_port_intf_profile(sub_port_intf_name) @@ -513,6 +576,9 @@ def _test_sub_port_intf_add_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=Non if vrf_name: self.remove_vrf(vrf_name) self.check_vrf_removal(vrf_oid) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag if parent_port.startswith(LAG_PREFIX): @@ -528,6 +594,9 @@ def test_sub_port_intf_add_ip_addrs(self, dvs): self._test_sub_port_intf_add_ip_addrs(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) self._test_sub_port_intf_add_ip_addrs(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) + self._test_sub_port_intf_add_ip_addrs(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + self._test_sub_port_intf_add_ip_addrs(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + def _test_sub_port_intf_appl_db_proc_seq(self, dvs, sub_port_intf_name, admin_up, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] @@ -549,7 +618,8 @@ def _test_sub_port_intf_appl_db_proc_seq(self, dvs, sub_port_intf_name, admin_up # Create ip address configuration in APPL_DB before creating configuration for sub port interface itself self.add_sub_port_intf_ip_addr_appl_db(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.add_sub_port_intf_ip_addr_appl_db(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.add_sub_port_intf_ip_addr_appl_db(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) time.sleep(2) # Create sub port interface configuration in APPL_DB @@ -570,7 +640,9 @@ def _test_sub_port_intf_appl_db_proc_seq(self, dvs, sub_port_intf_name, admin_up # Remove ip addresses from APPL_DB self.remove_sub_port_intf_ip_addr_appl_db(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.remove_sub_port_intf_ip_addr_appl_db(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.remove_sub_port_intf_ip_addr_appl_db(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + # Remove sub port interface from APPL_DB self.remove_sub_port_intf_profile_appl_db(sub_port_intf_name) self.check_sub_port_intf_profile_removal(rif_oid) @@ -579,6 +651,9 @@ def _test_sub_port_intf_appl_db_proc_seq(self, dvs, sub_port_intf_name, admin_up if vrf_name: self.remove_vrf(vrf_name) self.check_vrf_removal(vrf_oid) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag if parent_port.startswith(LAG_PREFIX): @@ -600,6 +675,12 @@ def test_sub_port_intf_appl_db_proc_seq(self, dvs): self._test_sub_port_intf_appl_db_proc_seq(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, admin_up=True, vrf_name=self.VRF_UNDER_TEST) self._test_sub_port_intf_appl_db_proc_seq(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, admin_up=False, vrf_name=self.VRF_UNDER_TEST) + self._test_sub_port_intf_appl_db_proc_seq(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, admin_up=True, vrf_name=self.VNET_UNDER_TEST) + self._test_sub_port_intf_appl_db_proc_seq(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, admin_up=False, vrf_name=self.VNET_UNDER_TEST) + + self._test_sub_port_intf_appl_db_proc_seq(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, admin_up=True, vrf_name=self.VNET_UNDER_TEST) + self._test_sub_port_intf_appl_db_proc_seq(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, admin_up=False, vrf_name=self.VNET_UNDER_TEST) + def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] @@ -614,13 +695,14 @@ def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_n self.create_sub_port_intf_profile(sub_port_intf_name, vrf_name) self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) fv_dict = { ADMIN_STATUS: "up", } if vrf_name: - fv_dict[VRF_NAME] = vrf_name + fv_dict[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name, fv_dict) fv_dict = { @@ -640,7 +722,7 @@ def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_n ADMIN_STATUS: "down", } if vrf_name: - fv_dict[VRF_NAME] = vrf_name + fv_dict[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name, fv_dict) # Verify that sub port router interface entry in ASIC_DB has the updated admin status @@ -661,7 +743,7 @@ def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_n ADMIN_STATUS: "up", } if vrf_name: - fv_dict[VRF_NAME] = vrf_name + fv_dict[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name, fv_dict) # Verify that sub port router interface entry in ASIC_DB has the updated admin status @@ -675,11 +757,14 @@ def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_n self.check_sub_port_intf_fvs(self.asic_db, ASIC_RIF_TABLE, rif_oid, fv_dict) # Remove IP addresses + ip_addrs = [ + self.IPV4_ADDR_UNDER_TEST, + ] self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) - self.check_sub_port_intf_ip_addr_removal(sub_port_intf_name, - [self.IPV4_ADDR_UNDER_TEST, - self.IPV6_ADDR_UNDER_TEST]) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + ip_addrs.append(self.IPV6_ADDR_UNDER_TEST) + self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + self.check_sub_port_intf_ip_addr_removal(sub_port_intf_name, ip_addrs) # Remove a sub port interface self.remove_sub_port_intf_profile(sub_port_intf_name) @@ -689,6 +774,9 @@ def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_n if vrf_name: self.remove_vrf(vrf_name) self.check_vrf_removal(vrf_oid) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag if parent_port.startswith(LAG_PREFIX): @@ -704,6 +792,9 @@ def test_sub_port_intf_admin_status_change(self, dvs): self._test_sub_port_intf_admin_status_change(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) self._test_sub_port_intf_admin_status_change(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) + self._test_sub_port_intf_admin_status_change(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + self._test_sub_port_intf_admin_status_change(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + def _test_sub_port_intf_remove_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] @@ -717,7 +808,8 @@ def _test_sub_port_intf_remove_ip_addrs(self, dvs, sub_port_intf_name, vrf_name= self.create_sub_port_intf_profile(sub_port_intf_name, vrf_name) self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) @@ -737,21 +829,22 @@ def _test_sub_port_intf_remove_ip_addrs(self, dvs, sub_port_intf_name, vrf_name= removed_route_entries = set([self.IPV4_TOME_UNDER_TEST, self.IPV4_SUBNET_UNDER_TEST]) self.check_sub_port_intf_route_entries_removal(removed_route_entries) - # Remove IPv6 address - self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + # Remove IPv6 address for non-vnet cases + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) - # Verify that IPv6 address state ok is removed from STATE_DB INTERFACE_TABLE by Intfmgrd - self.check_sub_port_intf_key_removal(self.state_db, STATE_INTERFACE_TABLE_NAME, - sub_port_intf_name + "|" + self.IPV6_ADDR_UNDER_TEST) + # Verify that IPv6 address state ok is removed from STATE_DB INTERFACE_TABLE by Intfmgrd + self.check_sub_port_intf_key_removal(self.state_db, STATE_INTERFACE_TABLE_NAME, + sub_port_intf_name + "|" + self.IPV6_ADDR_UNDER_TEST) - # Verify that IPv6 address configuration is removed from APPL_DB INTF_TABLE by Intfmgrd - self.check_sub_port_intf_key_removal(self.app_db, APP_INTF_TABLE_NAME, - sub_port_intf_name + ":" + self.IPV6_ADDR_UNDER_TEST) + # Verify that IPv6 address configuration is removed from APPL_DB INTF_TABLE by Intfmgrd + self.check_sub_port_intf_key_removal(self.app_db, APP_INTF_TABLE_NAME, + sub_port_intf_name + ":" + self.IPV6_ADDR_UNDER_TEST) - # Verify that IPv6 subnet route entry is removed from ASIC_DB - # Verify that IPv6 ip2me route entry is removed from ASIC_DB - removed_route_entries.update([self.IPV6_TOME_UNDER_TEST, self.IPV6_SUBNET_UNDER_TEST]) - self.check_sub_port_intf_route_entries_removal(removed_route_entries) + # Verify that IPv6 subnet route entry is removed from ASIC_DB + # Verify that IPv6 ip2me route entry is removed from ASIC_DB + removed_route_entries.update([self.IPV6_TOME_UNDER_TEST, self.IPV6_SUBNET_UNDER_TEST]) + self.check_sub_port_intf_route_entries_removal(removed_route_entries) # Verify that sub port router interface entry still exists in ASIC_DB self.check_sub_port_intf_key_existence(self.asic_db, ASIC_RIF_TABLE, rif_oid) @@ -764,6 +857,9 @@ def _test_sub_port_intf_remove_ip_addrs(self, dvs, sub_port_intf_name, vrf_name= if vrf_name: self.remove_vrf(vrf_name) self.asic_db.wait_for_n_keys(ASIC_VIRTUAL_ROUTER_TABLE, 1) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag if parent_port.startswith(LAG_PREFIX): @@ -779,6 +875,9 @@ def test_sub_port_intf_remove_ip_addrs(self, dvs): self._test_sub_port_intf_remove_ip_addrs(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) self._test_sub_port_intf_remove_ip_addrs(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) + self._test_sub_port_intf_remove_ip_addrs(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + self._test_sub_port_intf_remove_ip_addrs(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test=False, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] @@ -811,7 +910,8 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= self.create_sub_port_intf_profile(sub_port_intf_name, vrf_name) self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + self.add_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) fv_dict = { "state": "ok", @@ -831,7 +931,7 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= ADMIN_STATUS: "up", } if vrf_name: - fv_dict[VRF_NAME] = vrf_name + fv_dict[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name, fv_dict) if removal_seq_test == True: @@ -867,7 +967,7 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= ADMIN_STATUS: "up", } if vrf_name: - fv_dict[VRF_NAME] = vrf_name + fv_dict[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name self.check_sub_port_intf_fvs(self.app_db, APP_INTF_TABLE_NAME, sub_port_intf_name, fv_dict) # Verify that a sub port router interface entry persists in ASIC_DB @@ -886,11 +986,14 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) # Remove IP addresses + ip_addrs = [ + self.IPV4_ADDR_UNDER_TEST, + ] self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV4_ADDR_UNDER_TEST) - self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) - self.check_sub_port_intf_ip_addr_removal(sub_port_intf_name, - [self.IPV4_ADDR_UNDER_TEST, - self.IPV6_ADDR_UNDER_TEST]) + if vrf_name is None or not vrf_name.startswith(VNET_PREFIX): + ip_addrs.append(self.IPV6_ADDR_UNDER_TEST) + self.remove_sub_port_intf_ip_addr(sub_port_intf_name, self.IPV6_ADDR_UNDER_TEST) + self.check_sub_port_intf_ip_addr_removal(sub_port_intf_name, ip_addrs) if removal_seq_test == False: # Remove a sub port interface @@ -931,6 +1034,9 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= if vrf_name: self.remove_vrf(vrf_name) self.asic_db.wait_for_n_keys(ASIC_VIRTUAL_ROUTER_TABLE, 1) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) if parent_port.startswith(ETHERNET_PREFIX): if removal_seq_test == True: @@ -955,12 +1061,18 @@ def test_sub_port_intf_removal(self, dvs): self._test_sub_port_intf_removal(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, vrf_name=self.VRF_UNDER_TEST) self._test_sub_port_intf_removal(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, vrf_name=self.VRF_UNDER_TEST) + self._test_sub_port_intf_removal(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, vrf_name=self.VNET_UNDER_TEST) + self._test_sub_port_intf_removal(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, vrf_name=self.VNET_UNDER_TEST) + self._test_sub_port_intf_removal(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, removal_seq_test=True) self._test_sub_port_intf_removal(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, removal_seq_test=True) self._test_sub_port_intf_removal(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, removal_seq_test=True, vrf_name=self.VRF_UNDER_TEST) self._test_sub_port_intf_removal(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, removal_seq_test=True, vrf_name=self.VRF_UNDER_TEST) + self._test_sub_port_intf_removal(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, removal_seq_test=True, vrf_name=self.VNET_UNDER_TEST) + self._test_sub_port_intf_removal(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, removal_seq_test=True, vrf_name=self.VNET_UNDER_TEST) + def _test_sub_port_intf_mtu(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] @@ -1005,6 +1117,9 @@ def _test_sub_port_intf_mtu(self, dvs, sub_port_intf_name, vrf_name=None): if vrf_name: self.remove_vrf(vrf_name) self.check_vrf_removal(vrf_oid) + if vrf_name.startswith(VNET_PREFIX): + self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) + self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag if parent_port.startswith(LAG_PREFIX): @@ -1020,6 +1135,9 @@ def test_sub_port_intf_mtu(self, dvs): self._test_sub_port_intf_mtu(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) self._test_sub_port_intf_mtu(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VRF_UNDER_TEST) + self._test_sub_port_intf_mtu(dvs, self.SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + self._test_sub_port_intf_mtu(dvs, self.LAG_SUB_PORT_INTERFACE_UNDER_TEST, self.VNET_UNDER_TEST) + def create_nhg_router_intfs(self, dvs, parent_port_prefix, parent_port_idx_base, vlan_id, nhop_num, vrf_name=None): ifnames = [] parent_port_idx = parent_port_idx_base From 16d4bcdb893443d45f984eb7af8295b0e8af9bc3 Mon Sep 17 00:00:00 2001 From: Preetham <51771885+preetham-singh@users.noreply.github.com> Date: Wed, 24 Nov 2021 02:53:40 +0530 Subject: [PATCH 05/14] Routed subinterface enhancements (#1907) * Add support for long name and short name routed subinterfaces. * Handling Routed subinterface ADMIN status dependency with parent interface * Handling Routed subinterface MTU dependency with parent interface --- cfgmgr/Makefile.am | 2 +- cfgmgr/intfmgr.cpp | 326 +++++++++++++++++++++++++++++------ cfgmgr/intfmgr.h | 24 ++- orchagent/Makefile.am | 1 + orchagent/intfsorch.cpp | 19 +- orchagent/portsorch.cpp | 13 +- orchagent/portsorch.h | 2 +- portsyncd/linksync.cpp | 5 + teamsyncd/teamsync.cpp | 22 ++- teamsyncd/teamsync.h | 2 + tests/mock_tests/Makefile.am | 1 + tests/test_sub_port_intf.py | 130 ++++++++++++-- 12 files changed, 459 insertions(+), 88 deletions(-) diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index dcd652498c..21b1a5f91f 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -38,7 +38,7 @@ portmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) portmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) portmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -intfmgrd_SOURCES = intfmgrd.cpp intfmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +intfmgrd_SOURCES = intfmgrd.cpp intfmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/lib/subintf.cpp shellcmd.h intfmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) intfmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) intfmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) diff --git a/cfgmgr/intfmgr.cpp b/cfgmgr/intfmgr.cpp index d0eaca21d9..1567762ffa 100644 --- a/cfgmgr/intfmgr.cpp +++ b/cfgmgr/intfmgr.cpp @@ -9,13 +9,16 @@ #include "shellcmd.h" #include "macaddress.h" #include "warm_restart.h" +#include "subscriberstatetable.h" #include +#include "subintf.h" using namespace std; using namespace swss; #define VLAN_PREFIX "Vlan" #define LAG_PREFIX "PortChannel" +#define SUBINTF_LAG_PREFIX "Po" #define LOOPBACK_PREFIX "Loopback" #define VNET_PREFIX "Vnet" #define MTU_INHERITANCE "0" @@ -23,6 +26,7 @@ using namespace swss; #define VRF_MGMT "mgmt" #define LOOPBACK_DEFAULT_MTU_STR "65536" +#define DEFAULT_MTU_STR 9100 IntfMgr::IntfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const vector &tableNames) : Orch(cfgDb, tableNames), @@ -35,8 +39,19 @@ IntfMgr::IntfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, c m_stateVlanTable(stateDb, STATE_VLAN_TABLE_NAME), m_stateVrfTable(stateDb, STATE_VRF_TABLE_NAME), m_stateIntfTable(stateDb, STATE_INTERFACE_TABLE_NAME), - m_appIntfTableProducer(appDb, APP_INTF_TABLE_NAME) + m_appIntfTableProducer(appDb, APP_INTF_TABLE_NAME), + m_appLagTable(appDb, APP_LAG_TABLE_NAME) { + auto subscriberStateTable = new swss::SubscriberStateTable(stateDb, + STATE_PORT_TABLE_NAME, TableConsumable::DEFAULT_POP_BATCH_SIZE, 100); + auto stateConsumer = new Consumer(subscriberStateTable, this, STATE_PORT_TABLE_NAME); + Orch::addExecutor(stateConsumer); + + auto subscriberStateLagTable = new swss::SubscriberStateTable(stateDb, + STATE_LAG_TABLE_NAME, TableConsumable::DEFAULT_POP_BATCH_SIZE, 200); + auto stateLagConsumer = new Consumer(subscriberStateLagTable, this, STATE_LAG_TABLE_NAME); + Orch::addExecutor(stateLagConsumer); + if (!WarmStart::isWarmStart()) { flushLoopbackIntfs(); @@ -288,22 +303,160 @@ void IntfMgr::addHostSubIntf(const string&intf, const string &subIntf, const str EXEC_WITH_ERROR_THROW(cmd.str(), res); } -void IntfMgr::setHostSubIntfMtu(const string &subIntf, const string &mtu) + +std::string IntfMgr::getIntfAdminStatus(const string &alias) +{ + Table *portTable; + string admin = "down"; + if (!alias.compare(0, strlen("Eth"), "Eth")) + { + portTable = &m_statePortTable; + } + else if (!alias.compare(0, strlen("Po"), "Po")) + { + portTable = &m_appLagTable; + } + else + { + return admin; + } + + vector temp; + portTable->get(alias, temp); + + for (auto idx : temp) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + if (field == "admin_status") + { + admin = value; + } + } + return admin; +} + +std::string IntfMgr::getIntfMtu(const string &alias) +{ + Table *portTable; + string mtu = "0"; + if (!alias.compare(0, strlen("Eth"), "Eth")) + { + portTable = &m_statePortTable; + } + else if (!alias.compare(0, strlen("Po"), "Po")) + { + portTable = &m_appLagTable; + } + else + { + return mtu; + } + vector temp; + portTable->get(alias, temp); + for (auto idx : temp) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + if (field == "mtu") + { + mtu = value; + } + } + if (mtu.empty()) + { + mtu = std::to_string(DEFAULT_MTU_STR); + } + return mtu; +} + +void IntfMgr::updateSubIntfMtu(const string &alias, const string &mtu) +{ + string intf; + for (auto entry : m_subIntfList) + { + intf = entry.first; + subIntf subIf(intf); + if (subIf.parentIntf() == alias) + { + std::vector fvVector; + + string subif_config_mtu = m_subIntfList[intf].mtu; + if (subif_config_mtu == MTU_INHERITANCE || subif_config_mtu.empty()) + subif_config_mtu = std::to_string(DEFAULT_MTU_STR); + + string subintf_mtu = setHostSubIntfMtu(intf, subif_config_mtu, mtu); + + FieldValueTuple fvTuple("mtu", subintf_mtu); + fvVector.push_back(fvTuple); + m_appIntfTableProducer.set(intf, fvVector); + } + } +} + +std::string IntfMgr::setHostSubIntfMtu(const string &alias, const string &mtu, const string &parent_mtu) { stringstream cmd; string res; - cmd << IP_CMD " link set " << shellquote(subIntf) << " mtu " << shellquote(mtu); + string subifMtu = mtu; + subIntf subIf(alias); + + int pmtu = (uint32_t)stoul(parent_mtu); + int cmtu = (uint32_t)stoul(mtu); + + if (pmtu < cmtu) + { + subifMtu = parent_mtu; + } + SWSS_LOG_INFO("subintf %s active mtu: %s", alias.c_str(), subifMtu.c_str()); + cmd << IP_CMD " link set " << shellquote(alias) << " mtu " << shellquote(subifMtu); EXEC_WITH_ERROR_THROW(cmd.str(), res); + + return subifMtu; } -void IntfMgr::setHostSubIntfAdminStatus(const string &subIntf, const string &adminStatus) +void IntfMgr::updateSubIntfAdminStatus(const string &alias, const string &admin) +{ + string intf; + for (auto entry : m_subIntfList) + { + intf = entry.first; + subIntf subIf(intf); + if (subIf.parentIntf() == alias) + { + /* Avoid duplicate interface admin UP event. */ + string curr_admin = m_subIntfList[intf].currAdminStatus; + if (curr_admin == "up" && curr_admin == admin) + { + continue; + } + std::vector fvVector; + string subintf_admin = setHostSubIntfAdminStatus(intf, m_subIntfList[intf].adminStatus, admin); + m_subIntfList[intf].currAdminStatus = subintf_admin; + FieldValueTuple fvTuple("admin_status", subintf_admin); + fvVector.push_back(fvTuple); + m_appIntfTableProducer.set(intf, fvVector); + } + } +} + +std::string IntfMgr::setHostSubIntfAdminStatus(const string &alias, const string &admin_status, const string &parent_admin_status) { stringstream cmd; string res; - cmd << IP_CMD " link set " << shellquote(subIntf) << " " << shellquote(adminStatus); - EXEC_WITH_ERROR_THROW(cmd.str(), res); + if (parent_admin_status == "up" || admin_status == "down") + { + SWSS_LOG_INFO("subintf %s admin_status: %s", alias.c_str(), admin_status.c_str()); + cmd << IP_CMD " link set " << shellquote(alias) << " " << shellquote(admin_status); + EXEC_WITH_ERROR_THROW(cmd.str(), res); + return admin_status; + } + else + { + return "down"; + } } void IntfMgr::removeHostSubIntf(const string &subIntf) @@ -319,7 +472,7 @@ void IntfMgr::setSubIntfStateOk(const string &alias) { vector fvTuples = {{"state", "ok"}}; - if (!alias.compare(0, strlen(LAG_PREFIX), LAG_PREFIX)) + if (!alias.compare(0, strlen(SUBINTF_LAG_PREFIX), SUBINTF_LAG_PREFIX)) { m_stateLagTable.set(alias, fvTuples); } @@ -332,7 +485,7 @@ void IntfMgr::setSubIntfStateOk(const string &alias) void IntfMgr::removeSubIntfState(const string &alias) { - if (!alias.compare(0, strlen(LAG_PREFIX), LAG_PREFIX)) + if (!alias.compare(0, strlen(SUBINTF_LAG_PREFIX), SUBINTF_LAG_PREFIX)) { m_stateLagTable.del(alias); } @@ -451,6 +604,14 @@ bool IntfMgr::isIntfStateOk(const string &alias) { return true; } + else if (!alias.compare(0, strlen(SUBINTF_LAG_PREFIX), SUBINTF_LAG_PREFIX)) + { + if (m_stateLagTable.get(alias, temp)) + { + SWSS_LOG_DEBUG("Lag %s is ready", alias.c_str()); + return true; + } + } return false; } @@ -467,11 +628,24 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, size_t found = alias.find(VLAN_SUB_INTERFACE_SEPARATOR); if (found != string::npos) { - // This is a sub interface + subIntf subIf(alias); // alias holds the complete sub interface name // while parentAlias holds the parent port name - vlanId = alias.substr(found + 1); - parentAlias = alias.substr(0, found); + /*Check if subinterface is valid and sub interface name length is < 15(IFNAMSIZ)*/ + if (!subIf.isValid()) + { + SWSS_LOG_ERROR("Invalid subnitf: %s", alias.c_str()); + return true; + } + parentAlias = subIf.parentIntf(); + int subIntfId = subIf.subIntfIdx(); + /*If long name format, subinterface Id is vlanid */ + if (!subIf.isShortName()) + { + vlanId = std::to_string(subIntfId); + FieldValueTuple vlanTuple("vlan", vlanId); + data.push_back(vlanTuple); + } } bool is_lo = !alias.compare(0, strlen(LOOPBACK_PREFIX), LOOPBACK_PREFIX); string mac = ""; @@ -521,6 +695,10 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, { ipv6_link_local_mode = value; } + else if (field == "vlan") + { + vlanId = value; + } } if (op == SET_COMMAND) @@ -584,8 +762,14 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, if (!parentAlias.empty()) { + subIntf subIf(alias); if (m_subIntfList.find(alias) == m_subIntfList.end()) { + if (vlanId == "0" || vlanId.empty()) + { + SWSS_LOG_INFO("Vlan ID not configured for sub interface %s", alias.c_str()); + return false; + } try { addHostSubIntf(parentAlias, alias, vlanId); @@ -596,25 +780,33 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, return false; } - m_subIntfList.insert(alias); + m_subIntfList[alias].vlanId = vlanId; } if (!mtu.empty()) { + string subintf_mtu; try { - setHostSubIntfMtu(alias, mtu); + string parentMtu = getIntfMtu(subIf.parentIntf()); + subintf_mtu = setHostSubIntfMtu(alias, mtu, parentMtu); + FieldValueTuple fvTuple("mtu", mtu); + std::remove(data.begin(), data.end(), fvTuple); + FieldValueTuple newMtuFvTuple("mtu", subintf_mtu); + data.push_back(newMtuFvTuple); } catch (const std::runtime_error &e) { SWSS_LOG_NOTICE("Sub interface ip link set mtu failure. Runtime error: %s", e.what()); return false; } + m_subIntfList[alias].mtu = mtu; } else { FieldValueTuple fvTuple("mtu", MTU_INHERITANCE); data.push_back(fvTuple); + m_subIntfList[alias].mtu = MTU_INHERITANCE; } if (adminStatus.empty()) @@ -625,13 +817,20 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, } try { - setHostSubIntfAdminStatus(alias, adminStatus); + string parentAdmin = getIntfAdminStatus(subIf.parentIntf()); + string subintf_admin = setHostSubIntfAdminStatus(alias, adminStatus, parentAdmin); + m_subIntfList[alias].currAdminStatus = subintf_admin; + FieldValueTuple fvTuple("admin_status", adminStatus); + std::remove(data.begin(), data.end(), fvTuple); + FieldValueTuple newAdminFvTuple("admin_status", subintf_admin); + data.push_back(newAdminFvTuple); } catch (const std::runtime_error &e) { SWSS_LOG_NOTICE("Sub interface ip link set admin status %s failure. Runtime error: %s", adminStatus.c_str(), e.what()); return false; } + m_subIntfList[alias].adminStatus = adminStatus; // set STATE_DB port state setSubIntfStateOk(alias); @@ -788,51 +987,57 @@ void IntfMgr::doTask(Consumer &consumer) while (it != consumer.m_toSync.end()) { KeyOpFieldsValuesTuple t = it->second; - - vector keys = tokenize(kfvKey(t), config_db_key_delimiter); - const vector& data = kfvFieldsValues(t); - string op = kfvOp(t); - - if (keys.size() == 1) + if ((table_name == STATE_PORT_TABLE_NAME) || (table_name == STATE_LAG_TABLE_NAME)) { - if((table_name == CFG_VOQ_INBAND_INTERFACE_TABLE_NAME) && - (op == SET_COMMAND)) - { - //No further processing needed. Just relay to orchagent - m_appIntfTableProducer.set(keys[0], data); - m_stateIntfTable.hset(keys[0], "vrf", ""); + doPortTableTask(kfvKey(t), kfvFieldsValues(t), kfvOp(t)); + } + else + { + vector keys = tokenize(kfvKey(t), config_db_key_delimiter); + const vector& data = kfvFieldsValues(t); + string op = kfvOp(t); - it = consumer.m_toSync.erase(it); - continue; - } - if (!doIntfGeneralTask(keys, data, op)) - { - it++; - continue; - } - else + if (keys.size() == 1) { - //Entry programmed, remove it from pending list if present - m_pendingReplayIntfList.erase(keys[0]); + if((table_name == CFG_VOQ_INBAND_INTERFACE_TABLE_NAME) && + (op == SET_COMMAND)) + { + //No further processing needed. Just relay to orchagent + m_appIntfTableProducer.set(keys[0], data); + m_stateIntfTable.hset(keys[0], "vrf", ""); + + it = consumer.m_toSync.erase(it); + continue; + } + if (!doIntfGeneralTask(keys, data, op)) + { + it++; + continue; + } + else + { + //Entry programmed, remove it from pending list if present + m_pendingReplayIntfList.erase(keys[0]); + } } - } - else if (keys.size() == 2) - { - if (!doIntfAddrTask(keys, data, op)) + else if (keys.size() == 2) { - it++; - continue; + if (!doIntfAddrTask(keys, data, op)) + { + it++; + continue; + } + else + { + //Entry programmed, remove it from pending list if present + m_pendingReplayIntfList.erase(keys[0] + config_db_key_delimiter + keys[1] ); + } } else { - //Entry programmed, remove it from pending list if present - m_pendingReplayIntfList.erase(keys[0] + config_db_key_delimiter + keys[1] ); + SWSS_LOG_ERROR("Invalid key %s", kfvKey(t).c_str()); } } - else - { - SWSS_LOG_ERROR("Invalid key %s", kfvKey(t).c_str()); - } it = consumer.m_toSync.erase(it); } @@ -842,3 +1047,26 @@ void IntfMgr::doTask(Consumer &consumer) setWarmReplayDoneState(); } } + +void IntfMgr::doPortTableTask(const string& key, vector data, string op) +{ + if (op == SET_COMMAND) + { + for (auto idx : data) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + + if (field == "admin_status") + { + SWSS_LOG_INFO("Port %s Admin %s", key.c_str(), value.c_str()); + updateSubIntfAdminStatus(key, value); + } + else if (field == "mtu") + { + SWSS_LOG_INFO("Port %s MTU %s", key.c_str(), value.c_str()); + updateSubIntfMtu(key, value); + } + } + } +} diff --git a/cfgmgr/intfmgr.h b/cfgmgr/intfmgr.h index 655fb4deeb..e1a5049fff 100644 --- a/cfgmgr/intfmgr.h +++ b/cfgmgr/intfmgr.h @@ -9,6 +9,16 @@ #include #include +struct SubIntfInfo +{ + std::string vlanId; + std::string mtu; + std::string adminStatus; + std::string currAdminStatus; +}; + +typedef std::map SubIntfMap; + namespace swss { class IntfMgr : public Orch @@ -20,9 +30,9 @@ class IntfMgr : public Orch private: ProducerStateTable m_appIntfTableProducer; Table m_cfgIntfTable, m_cfgVlanIntfTable, m_cfgLagIntfTable, m_cfgLoopbackIntfTable; - Table m_statePortTable, m_stateLagTable, m_stateVlanTable, m_stateVrfTable, m_stateIntfTable; + Table m_statePortTable, m_stateLagTable, m_stateVlanTable, m_stateVrfTable, m_stateIntfTable, m_appLagTable; - std::set m_subIntfList; + SubIntfMap m_subIntfList; std::set m_loopbackIntfList; std::set m_pendingReplayIntfList; @@ -34,6 +44,7 @@ class IntfMgr : public Orch bool doIntfGeneralTask(const std::vector& keys, std::vector data, const std::string& op); bool doIntfAddrTask(const std::vector& keys, const std::vector& data, const std::string& op); void doTask(Consumer &consumer); + void doPortTableTask(const std::string& key, std::vector data, std::string op); bool isIntfStateOk(const std::string &alias); bool isIntfCreated(const std::string &alias); @@ -46,9 +57,11 @@ class IntfMgr : public Orch void delLoopbackIntf(const std::string &alias); void flushLoopbackIntfs(void); + std::string getIntfAdminStatus(const std::string &alias); + std::string getIntfMtu(const std::string &alias); void addHostSubIntf(const std::string&intf, const std::string &subIntf, const std::string &vlan); - void setHostSubIntfMtu(const std::string &subIntf, const std::string &mtu); - void setHostSubIntfAdminStatus(const std::string &subIntf, const std::string &admin_status); + std::string setHostSubIntfMtu(const std::string &alias, const std::string &mtu, const std::string &parent_mtu); + std::string setHostSubIntfAdminStatus(const std::string &alias, const std::string &admin_status, const std::string &parent_admin_status); void removeHostSubIntf(const std::string &subIntf); void setSubIntfStateOk(const std::string &alias); void removeSubIntfState(const std::string &alias); @@ -56,6 +69,9 @@ class IntfMgr : public Orch bool setIntfProxyArp(const std::string &alias, const std::string &proxy_arp); bool setIntfGratArp(const std::string &alias, const std::string &grat_arp); + void updateSubIntfAdminStatus(const std::string &alias, const std::string &admin); + void updateSubIntfMtu(const std::string &alias, const std::string &mtu); + bool m_replayDone {false}; }; diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index e0106f00ce..93d5aa01e0 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -38,6 +38,7 @@ endif orchagent_SOURCES = \ main.cpp \ $(top_srcdir)/lib/gearboxutils.cpp \ + $(top_srcdir)/lib/subintf.cpp \ orchdaemon.cpp \ orch.cpp \ notifications.cpp \ diff --git a/orchagent/intfsorch.cpp b/orchagent/intfsorch.cpp index b270a0c5e5..1feebb4d75 100644 --- a/orchagent/intfsorch.cpp +++ b/orchagent/intfsorch.cpp @@ -657,11 +657,13 @@ void IntfsOrch::doTask(Consumer &consumer) MacAddress mac; uint32_t mtu = 0; - bool adminUp = false; + bool adminUp; + bool adminStateChanged = false; uint32_t nat_zone_id = 0; string proxy_arp = ""; string inband_type = ""; bool mpls = false; + string vlan = ""; for (auto idx : data) { @@ -736,6 +738,7 @@ void IntfsOrch::doTask(Consumer &consumer) SWSS_LOG_WARN("Sub interface %s unknown admin status %s", alias.c_str(), value.c_str()); } } + adminStateChanged = true; } else if (field == "nat_zone") { @@ -749,6 +752,10 @@ void IntfsOrch::doTask(Consumer &consumer) { inband_type = value; } + else if (field == "vlan") + { + vlan = value; + } } if (alias == "eth0" || alias == "docker0") @@ -818,7 +825,11 @@ void IntfsOrch::doTask(Consumer &consumer) { if (!ip_prefix_in_key && isSubIntf) { - if (!gPortsOrch->addSubPort(port, alias, adminUp, mtu)) + if (adminStateChanged == false) + { + adminUp = port.m_admin_state_up; + } + if (!gPortsOrch->addSubPort(port, alias, vlan, adminUp, mtu)) { it++; continue; @@ -858,6 +869,10 @@ void IntfsOrch::doTask(Consumer &consumer) } else { + if (adminStateChanged == false) + { + adminUp = port.m_admin_state_up; + } if (!setIntf(alias, vrf_id, ip_prefix_in_key ? &ip_prefix : nullptr, adminUp, mtu)) { it++; diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index db592024a6..ada1f4bb92 100755 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -5,6 +5,7 @@ #include "gearboxutils.h" #include "vxlanorch.h" #include "directory.h" +#include "subintf.h" #include #include @@ -807,7 +808,7 @@ bool PortsOrch::getPortByBridgePortId(sai_object_id_t bridge_port_id, Port &port return false; } -bool PortsOrch::addSubPort(Port &port, const string &alias, const bool &adminUp, const uint32_t &mtu) +bool PortsOrch::addSubPort(Port &port, const string &alias, const string &vlan, const bool &adminUp, const uint32_t &mtu) { SWSS_LOG_ENTER(); @@ -817,21 +818,21 @@ bool PortsOrch::addSubPort(Port &port, const string &alias, const bool &adminUp, SWSS_LOG_ERROR("%s is not a sub interface", alias.c_str()); return false; } - string parentAlias = alias.substr(0, found); - string vlanId = alias.substr(found + 1); + subIntf subIf(alias); + string parentAlias = subIf.parentIntf(); sai_vlan_id_t vlan_id; try { - vlan_id = static_cast(stoul(vlanId)); + vlan_id = static_cast(stoul(vlan)); } catch (const std::invalid_argument &e) { - SWSS_LOG_ERROR("Invalid argument %s to %s()", vlanId.c_str(), e.what()); + SWSS_LOG_ERROR("Invalid argument %s to %s()", vlan.c_str(), e.what()); return false; } catch (const std::out_of_range &e) { - SWSS_LOG_ERROR("Out of range argument %s to %s()", vlanId.c_str(), e.what()); + SWSS_LOG_ERROR("Out of range argument %s to %s()", vlan.c_str(), e.what()); return false; } if (vlan_id > MAX_VALID_VLAN_ID) diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index c4e192aa14..16b4922bc9 100755 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -132,7 +132,7 @@ class PortsOrch : public Orch, public Subject void refreshPortStatus(); bool removeAclTableGroup(const Port &p); - bool addSubPort(Port &port, const string &alias, const bool &adminUp = true, const uint32_t &mtu = 0); + bool addSubPort(Port &port, const string &alias, const string &vlan, const bool &adminUp = true, const uint32_t &mtu = 0); bool removeSubPort(const string &alias); bool updateL3VniStatus(uint16_t vlan_id, bool status); void getLagMember(Port &lag, vector &portv); diff --git a/portsyncd/linksync.cpp b/portsyncd/linksync.cpp index 08df3ff9ec..4a2b351ee0 100644 --- a/portsyncd/linksync.cpp +++ b/portsyncd/linksync.cpp @@ -182,6 +182,7 @@ void LinkSync::onMsg(int nlmsg_type, struct nl_object *obj) unsigned int ifindex = rtnl_link_get_ifindex(link); int master = rtnl_link_get_master(link); char *type = rtnl_link_get_type(link); + unsigned int mtu = rtnl_link_get_mtu(link); if (type) { @@ -251,10 +252,14 @@ void LinkSync::onMsg(int nlmsg_type, struct nl_object *obj) { g_portSet.erase(key); FieldValueTuple tuple("state", "ok"); + FieldValueTuple admin_status("admin_status", (admin ? "up" : "down")); + FieldValueTuple port_mtu("mtu", to_string(mtu)); vector vector; vector.push_back(tuple); FieldValueTuple op("netdev_oper_status", oper ? "up" : "down"); vector.push_back(op); + vector.push_back(admin_status); + vector.push_back(port_mtu); m_statePortTable.set(key, vector); SWSS_LOG_NOTICE("Publish %s(ok:%s) to state db", key.c_str(), oper ? "up" : "down"); } diff --git a/teamsyncd/teamsync.cpp b/teamsyncd/teamsync.cpp index 846d474a6c..6d8c025911 100644 --- a/teamsyncd/teamsync.cpp +++ b/teamsyncd/teamsync.cpp @@ -157,11 +157,18 @@ void TeamSync::addLag(const string &lagName, int ifindex, bool admin_state, SWSS_LOG_INFO("Add %s admin_status:%s oper_status:%s, mtu: %d", lagName.c_str(), admin_state ? "up" : "down", oper_state ? "up" : "down", mtu); + bool lag_update = true; /* Return when the team instance has already been tracked */ if (m_teamSelectables.find(lagName) != m_teamSelectables.end()) - return; + { + auto tsync = m_teamSelectables[lagName]; + if (tsync->admin_state == admin_state && tsync->mtu == mtu) + return; + tsync->admin_state = admin_state; + tsync->mtu = mtu; + lag_update = false; + } - fvVector.clear(); FieldValueTuple s("state", "ok"); fvVector.push_back(s); if (m_warmstart) @@ -173,10 +180,13 @@ void TeamSync::addLag(const string &lagName, int ifindex, bool admin_state, m_stateLagTable.set(lagName, fvVector); } - /* Create the team instance */ - auto sync = make_shared(lagName, ifindex, &m_lagMemberTable); - m_teamSelectables[lagName] = sync; - m_selectablesToAdd.insert(lagName); + if (lag_update) + { + /* Create the team instance */ + auto sync = make_shared(lagName, ifindex, &m_lagMemberTable); + m_teamSelectables[lagName] = sync; + m_selectablesToAdd.insert(lagName); + } } void TeamSync::removeLag(const string &lagName) diff --git a/teamsyncd/teamsync.h b/teamsyncd/teamsync.h index 406953e312..deb5d84129 100644 --- a/teamsyncd/teamsync.h +++ b/teamsyncd/teamsync.h @@ -43,6 +43,8 @@ class TeamSync : public NetMsg /* member_name -> enabled|disabled */ std::map m_lagMembers; + bool admin_state; + unsigned int mtu; protected: int onChange(); static int teamdHandler(struct team_handle *th, void *arg, diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 7f31379658..7419463847 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -33,6 +33,7 @@ tests_SOURCES = aclorch_ut.cpp \ mock_redisreply.cpp \ bulker_ut.cpp \ $(top_srcdir)/lib/gearboxutils.cpp \ + $(top_srcdir)/lib/subintf.cpp \ $(top_srcdir)/orchagent/orchdaemon.cpp \ $(top_srcdir)/orchagent/orch.cpp \ $(top_srcdir)/orchagent/notifications.cpp \ diff --git a/tests/test_sub_port_intf.py b/tests/test_sub_port_intf.py index 2d442fd027..b0f2863aa6 100644 --- a/tests/test_sub_port_intf.py +++ b/tests/test_sub_port_intf.py @@ -45,6 +45,7 @@ PEER_LIST = "peer_list" ETHERNET_PREFIX = "Ethernet" +SUBINTF_LAG_PREFIX = "Po" LAG_PREFIX = "PortChannel" VRF_PREFIX = "Vrf" VNET_PREFIX = "Vnet" @@ -52,6 +53,8 @@ VLAN_SUB_INTERFACE_SEPARATOR = "." APPL_DB_SEPARATOR = ":" +ETHERNET_PORT_DEFAULT_MTU = "1500" + class TestSubPortIntf(object): SUB_PORT_INTERFACE_UNDER_TEST = "Ethernet64.10" @@ -97,7 +100,62 @@ def connect_dbs(self, dvs): if phy_port in key: self.buf_q_fvs[key] = self.config_db.get_entry("BUFFER_QUEUE", key) + def get_subintf_longname(self, port_name): + if port_name is None: + return None + sub_intf_sep_idx = port_name.find(VLAN_SUB_INTERFACE_SEPARATOR) + if sub_intf_sep_idx == -1: + return str(port_name) + parent_intf = port_name[:sub_intf_sep_idx] + sub_intf_idx = port_name[(sub_intf_sep_idx+1):] + if port_name.startswith("Eth"): + if port_name.startswith("Ethernet"): + intf_index=port_name[len("Ethernet"):sub_intf_sep_idx] + else: + intf_index=port_name[len("Eth"):sub_intf_sep_idx] + return "Ethernet"+intf_index+VLAN_SUB_INTERFACE_SEPARATOR+sub_intf_idx + elif port_name.startswith("Po"): + if port_name.startswith("PortChannel"): + intf_index=port_name[len("PortChannel"):sub_intf_sep_idx] + else: + intf_index=port_name[len("Po"):sub_intf_sep_idx] + return "PortChannel"+intf_index+VLAN_SUB_INTERFACE_SEPARATOR+sub_intf_idx + else: + return str(port_name) + + def get_port_longname(self, port_name): + if port_name is None: + return None + + if VLAN_SUB_INTERFACE_SEPARATOR in port_name: + return self.get_subintf_longname(port_name) + else: + if port_name.startswith("Eth"): + if port_name.startswith("Ethernet"): + return port_name + intf_index=port_name[len("Eth"):len(port_name)] + return "Ethernet"+intf_index + elif port_name.startswith("Po"): + if port_name.startswith("PortChannel"): + return port_name + intf_index=port_name[len("Po"):len(port_name)] + return "PortChannel"+intf_index + else: + return port_name + + def get_parent_port(self, port_name): + port = self.get_port_longname(port_name) + idx = port.find(VLAN_SUB_INTERFACE_SEPARATOR) + parent_port = "" + if port.startswith(ETHERNET_PREFIX): + parent_port = port[:idx] + else: + assert port.startswith(SUBINTF_LAG_PREFIX) + parent_port = port[:idx] + return parent_port + def get_parent_port_index(self, port_name): + port_name = self.get_port_longname(port_name) if port_name.startswith(ETHERNET_PREFIX): idx = int(port_name[len(ETHERNET_PREFIX):]) else: @@ -110,7 +168,7 @@ def set_parent_port_oper_status(self, dvs, port_name, status): srv_idx = self.get_parent_port_index(port_name) // 4 dvs.servers[srv_idx].runcmd("ip link set dev eth0 " + status) else: - assert port_name.startswith(LAG_PREFIX) + assert port_name.startswith(SUBINTF_LAG_PREFIX) dvs.runcmd("bash -c 'echo " + ("1" if status == "up" else "0") + " > /sys/class/net/" + port_name + "/carrier'") time.sleep(1) @@ -121,7 +179,7 @@ def set_parent_port_admin_status(self, dvs, port_name, status): if port_name.startswith(ETHERNET_PREFIX): tbl_name = CFG_PORT_TABLE_NAME else: - assert port_name.startswith(LAG_PREFIX) + assert port_name.startswith(SUBINTF_LAG_PREFIX) tbl_name = CFG_LAG_TABLE_NAME self.config_db.create_entry(tbl_name, port_name, fvs) time.sleep(1) @@ -154,8 +212,28 @@ def create_vrf(self, vrf_name): self.create_vxlan_tunnel(self.TUNNEL_UNDER_TEST, self.VTEP_IP_UNDER_TEST) self.create_vnet(vrf_name, self.TUNNEL_UNDER_TEST, self.VNI_UNDER_TEST) + def is_short_name(self, port_name): + idx = port_name.find(VLAN_SUB_INTERFACE_SEPARATOR) + parent_port = port_name[:idx] + is_short = False + if parent_port.startswith("Eth"): + if parent_port.startswith(ETHERNET_PREFIX): + is_short = False + else: + is_short = True + elif parent_port.startswith("Po"): + if parent_port.startswith("PortChannel"): + is_short = False + else: + is_short = True + return is_short + def create_sub_port_intf_profile(self, sub_port_intf_name, vrf_name=None): fvs = {ADMIN_STATUS: "up"} + idx = sub_port_intf_name.find(VLAN_SUB_INTERFACE_SEPARATOR) + sub_port_idx = sub_port_intf_name[(idx+1):] + if self.is_short_name(sub_port_intf_name) == True: + fvs["vlan"] = sub_port_idx if vrf_name: fvs[VRF_NAME if vrf_name.startswith(VRF_PREFIX) else VNET_NAME] = vrf_name @@ -189,8 +267,12 @@ def add_lag_members(self, lag, members): self.config_db.create_entry(CFG_LAG_MEMBER_TABLE_NAME, key, fvs) def create_sub_port_intf_profile_appl_db(self, sub_port_intf_name, admin_status, vrf_name=None): + substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) + parent_port = self.get_parent_port(sub_port_intf_name) + vlan_id = substrs[1] pairs = [ (ADMIN_STATUS, admin_status), + ("vlan", vlan_id), ("mtu", "0"), ] if vrf_name: @@ -255,7 +337,7 @@ def remove_parent_port_appl_db(self, port_name): if port_name in key: self.config_db.delete_entry("BUFFER_QUEUE", key) else: - assert port_name.startswith(LAG_PREFIX) + assert port_name.startswith(SUBINTF_LAG_PREFIX) tbl_name = APP_LAG_TABLE_NAME tbl = swsscommon.ProducerStateTable(self.app_db.db_connection, tbl_name) tbl._del(port_name) @@ -405,14 +487,14 @@ def _access_function(): def _test_sub_port_intf_creation(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) - parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vlan_id = substrs[1] if parent_port.startswith(ETHERNET_PREFIX): state_tbl_name = STATE_PORT_TABLE_NAME phy_ports = [parent_port] parent_port_oid = dvs.asicdb.portnamemap[parent_port] else: - assert parent_port.startswith(LAG_PREFIX) + assert parent_port.startswith(SUBINTF_LAG_PREFIX) state_tbl_name = STATE_LAG_TABLE_NAME phy_ports = self.LAG_MEMBERS_UNDER_TEST old_lag_oids = self.get_oids(ASIC_LAG_TABLE) @@ -421,7 +503,7 @@ def _test_sub_port_intf_creation(self, dvs, sub_port_intf_name, vrf_name=None): old_rif_oids = self.get_oids(ASIC_RIF_TABLE) self.set_parent_port_admin_status(dvs, parent_port, "up") - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): parent_port_oid = self.get_newly_created_oid(ASIC_LAG_TABLE, old_lag_oids) # Add lag members to test physical port host interface vlan tag attribute self.add_lag_members(parent_port, self.LAG_MEMBERS_UNDER_TEST) @@ -490,7 +572,7 @@ def _test_sub_port_intf_creation(self, dvs, sub_port_intf_name, vrf_name=None): self.remove_vxlan_tunnel(self.TUNNEL_UNDER_TEST) self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): # Remove lag members from lag parent port self.remove_lag_members(parent_port, self.LAG_MEMBERS_UNDER_TEST) self.asic_db.wait_for_n_keys(ASIC_LAG_MEMBER_TABLE, 0) @@ -513,7 +595,7 @@ def test_sub_port_intf_creation(self, dvs): def _test_sub_port_intf_add_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) - parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vrf_oid = self.default_vrf_oid old_rif_oids = self.get_oids(ASIC_RIF_TABLE) @@ -581,7 +663,7 @@ def _test_sub_port_intf_add_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=Non self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): self.remove_lag(parent_port) self.asic_db.wait_for_n_keys(ASIC_LAG_TABLE, 0) @@ -599,7 +681,7 @@ def test_sub_port_intf_add_ip_addrs(self, dvs): def _test_sub_port_intf_appl_db_proc_seq(self, dvs, sub_port_intf_name, admin_up, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) - parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vlan_id = substrs[1] vrf_oid = self.default_vrf_oid @@ -610,7 +692,7 @@ def _test_sub_port_intf_appl_db_proc_seq(self, dvs, sub_port_intf_name, admin_up if parent_port.startswith(ETHERNET_PREFIX): parent_port_oid = dvs.asicdb.portnamemap[parent_port] else: - assert parent_port.startswith(LAG_PREFIX) + assert parent_port.startswith(SUBINTF_LAG_PREFIX) parent_port_oid = self.get_newly_created_oid(ASIC_LAG_TABLE, old_lag_oids) if vrf_name: self.create_vrf(vrf_name) @@ -656,7 +738,7 @@ def _test_sub_port_intf_appl_db_proc_seq(self, dvs, sub_port_intf_name, admin_up self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): self.remove_lag(parent_port) self.check_lag_removal(parent_port_oid) @@ -684,6 +766,7 @@ def test_sub_port_intf_appl_db_proc_seq(self, dvs): def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vrf_oid = self.default_vrf_oid old_rif_oids = self.get_oids(ASIC_RIF_TABLE) @@ -779,7 +862,7 @@ def _test_sub_port_intf_admin_status_change(self, dvs, sub_port_intf_name, vrf_n self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): self.remove_lag(parent_port) self.asic_db.wait_for_n_keys(ASIC_LAG_TABLE, 0) @@ -798,6 +881,7 @@ def test_sub_port_intf_admin_status_change(self, dvs): def _test_sub_port_intf_remove_ip_addrs(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) old_rif_oids = self.get_oids(ASIC_RIF_TABLE) @@ -862,7 +946,7 @@ def _test_sub_port_intf_remove_ip_addrs(self, dvs, sub_port_intf_name, vrf_name= self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): self.remove_lag(parent_port) self.asic_db.wait_for_n_keys(ASIC_LAG_TABLE, 0) @@ -881,6 +965,7 @@ def test_sub_port_intf_remove_ip_addrs(self, dvs): def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test=False, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vlan_id = substrs[1] if parent_port.startswith(ETHERNET_PREFIX): state_tbl_name = STATE_PORT_TABLE_NAME @@ -888,7 +973,7 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= parent_port_oid = dvs.asicdb.portnamemap[parent_port] asic_tbl_name = ASIC_PORT_TABLE else: - assert parent_port.startswith(LAG_PREFIX) + assert parent_port.startswith(SUBINTF_LAG_PREFIX) state_tbl_name = STATE_LAG_TABLE_NAME phy_ports = self.LAG_MEMBERS_UNDER_TEST old_lag_oids = self.get_oids(ASIC_LAG_TABLE) @@ -898,7 +983,7 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= old_rif_oids = self.get_oids(ASIC_RIF_TABLE) self.set_parent_port_admin_status(dvs, parent_port, "up") - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): parent_port_oid = self.get_newly_created_oid(ASIC_LAG_TABLE, old_lag_oids) if removal_seq_test == False: # Add lag members to test physical port host interface vlan tag attribute @@ -981,6 +1066,10 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= "SAI_ROUTER_INTERFACE_ATTR_PORT_ID": parent_port_oid, } rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) + #If subintf mtu deleted, it inherits from parent + if vrf_name == self.VRF_UNDER_TEST: + if parent_port.startswith(ETHERNET_PREFIX): + fv_dict["SAI_ROUTER_INTERFACE_ATTR_MTU"] = ETHERNET_PORT_DEFAULT_MTU self.check_sub_port_intf_fvs(self.asic_db, ASIC_RIF_TABLE, rif_oid, fv_dict) else: rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) @@ -1076,6 +1165,7 @@ def test_sub_port_intf_removal(self, dvs): def _test_sub_port_intf_mtu(self, dvs, sub_port_intf_name, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vrf_oid = self.default_vrf_oid old_rif_oids = self.get_oids(ASIC_RIF_TABLE) @@ -1122,7 +1212,7 @@ def _test_sub_port_intf_mtu(self, dvs, sub_port_intf_name, vrf_name=None): self.app_db.wait_for_n_keys(ASIC_TUNNEL_TABLE, 0) # Remove lag - if parent_port.startswith(LAG_PREFIX): + if parent_port.startswith(SUBINTF_LAG_PREFIX): self.remove_lag(parent_port) self.asic_db.wait_for_n_keys(ASIC_LAG_TABLE, 0) @@ -1244,13 +1334,14 @@ def check_nhg_members_on_parent_port_oper_status_change(self, dvs, parent_port_p def _test_sub_port_intf_nhg_accel(self, dvs, sub_port_intf_name, nhop_num=3, create_intf_on_parent_port=False, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vlan_id = substrs[1] assert len(vlan_id) == 2 if parent_port.startswith(ETHERNET_PREFIX): parent_port_prefix = ETHERNET_PREFIX else: - assert parent_port.startswith(LAG_PREFIX) + assert parent_port.startswith(SUBINTF_LAG_PREFIX) parent_port_prefix = LAG_PREFIX parent_port_idx_base = self.get_parent_port_index(parent_port) @@ -1372,13 +1463,14 @@ def _test_sub_port_intf_oper_down_with_pending_neigh_route_tasks(self, dvs, sub_ create_intf_on_parent_port=False, vrf_name=None): substrs = sub_port_intf_name.split(VLAN_SUB_INTERFACE_SEPARATOR) parent_port = substrs[0] + parent_port = self.get_parent_port(sub_port_intf_name) vlan_id = substrs[1] assert len(vlan_id) == 2 if parent_port.startswith(ETHERNET_PREFIX): parent_port_prefix = ETHERNET_PREFIX else: - assert parent_port.startswith(LAG_PREFIX) + assert parent_port.startswith(SUBINTF_LAG_PREFIX) parent_port_prefix = LAG_PREFIX parent_port_idx_base = self.get_parent_port_index(parent_port) From fb0a5fd8d2d7a7758297ae8177d10da6076f9f05 Mon Sep 17 00:00:00 2001 From: Stephen Sun <5379172+stephenxs@users.noreply.github.com> Date: Wed, 24 Nov 2021 21:06:23 +0800 Subject: [PATCH 06/14] Don't handle buffer pool watermark during warm reboot reconciling (#1987) - What I did Don't handle buffer pool watermark during warm reboot reconciling - Why I did it This is to fix the community issue Azure/sonic-sairedis#862 and Azure/sonic-buildimage#8722 - How I verified it Perform a warm reboot. Check whether buffer pool watermark handling is skipped during reconciling and handled after it. other watermark handling is handled during reconciling as it was before. Details if related The warm reboot flow is like this: System starts. Orchagent fetches the items from database stored before warm reboot and pushes them into m_toSync of all orchagents. This is done by bake, which can be overridden by sub orchagent. All sub orchagents handle the items in m_toSync. At this point, any notification from redis-db is blocked. Warm reboot converges. Orchagent starts to handle notifications from redis-db. The fix is like this: in FlexCounterOrch::bake. the buffer pool watermark handling is skipped. Signed-off-by: Stephen Sun --- orchagent/flexcounterorch.cpp | 44 ++++++++++++++++++++++++++++++++++- orchagent/flexcounterorch.h | 3 +++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/orchagent/flexcounterorch.cpp b/orchagent/flexcounterorch.cpp index 7ccc52e06c..dea2fcd0a3 100644 --- a/orchagent/flexcounterorch.cpp +++ b/orchagent/flexcounterorch.cpp @@ -1,5 +1,4 @@ #include -#include "flexcounterorch.h" #include "portsorch.h" #include "fabricportsorch.h" #include "select.h" @@ -49,6 +48,7 @@ unordered_map flexCounterGroupMap = FlexCounterOrch::FlexCounterOrch(DBConnector *db, vector &tableNames): Orch(db, tableNames), + m_flexCounterConfigTable(db, CFG_FLEX_COUNTER_TABLE_NAME), m_flexCounterDb(new DBConnector("FLEX_COUNTER_DB", 0)), m_flexCounterGroupTable(new ProducerTable(m_flexCounterDb.get(), FLEX_COUNTER_GROUP_TABLE)) { @@ -188,3 +188,45 @@ bool FlexCounterOrch::getPortBufferDropCountersState() const { return m_port_buffer_drop_counter_enabled; } + +bool FlexCounterOrch::bake() +{ + /* + * bake is called during warmreboot reconciling procedure. + * By default, it should fetch items from the tables the sub agents listen to, + * and then push them into m_toSync of each sub agent. + * The motivation is to make sub agents handle the saved entries first and then handle the upcoming entries. + */ + + std::deque entries; + vector keys; + m_flexCounterConfigTable.getKeys(keys); + for (const auto &key: keys) + { + if (!flexCounterGroupMap.count(key)) + { + SWSS_LOG_NOTICE("FlexCounterOrch: Invalid flex counter group intput %s is skipped during reconciling", key.c_str()); + continue; + } + + if (key == BUFFER_POOL_WATERMARK_KEY) + { + SWSS_LOG_NOTICE("FlexCounterOrch: Do not handle any FLEX_COUNTER table for %s update during reconciling", + BUFFER_POOL_WATERMARK_KEY); + continue; + } + + KeyOpFieldsValuesTuple kco; + + kfvKey(kco) = key; + kfvOp(kco) = SET_COMMAND; + + if (!m_flexCounterConfigTable.get(key, kfvFieldsValues(kco))) + { + continue; + } + entries.push_back(kco); + } + Consumer* consumer = dynamic_cast(getExecutor(CFG_FLEX_COUNTER_TABLE_NAME)); + return consumer->addToSync(entries); +} diff --git a/orchagent/flexcounterorch.h b/orchagent/flexcounterorch.h index 0fb9f70e4b..9ae7e90aad 100644 --- a/orchagent/flexcounterorch.h +++ b/orchagent/flexcounterorch.h @@ -4,6 +4,7 @@ #include "orch.h" #include "port.h" #include "producertable.h" +#include "table.h" extern "C" { #include "sai.h" @@ -17,12 +18,14 @@ class FlexCounterOrch: public Orch virtual ~FlexCounterOrch(void); bool getPortCountersState() const; bool getPortBufferDropCountersState() const; + bool bake() override; private: std::shared_ptr m_flexCounterDb = nullptr; std::shared_ptr m_flexCounterGroupTable = nullptr; bool m_port_counter_enabled = false; bool m_port_buffer_drop_counter_enabled = false; + Table m_flexCounterConfigTable; }; #endif From 3d862a729cf55a981081fbfa74a1f702d8ab9e3b Mon Sep 17 00:00:00 2001 From: Preetham <51771885+preetham-singh@users.noreply.github.com> Date: Thu, 25 Nov 2021 11:13:39 +0530 Subject: [PATCH 07/14] Fixing subport vs test script for subport under VNET (#2048) * Fixed subport vs test script for subport under VNET --- tests/test_sub_port_intf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_sub_port_intf.py b/tests/test_sub_port_intf.py index b0f2863aa6..748e680e2a 100644 --- a/tests/test_sub_port_intf.py +++ b/tests/test_sub_port_intf.py @@ -1067,7 +1067,7 @@ def _test_sub_port_intf_removal(self, dvs, sub_port_intf_name, removal_seq_test= } rif_oid = self.get_newly_created_oid(ASIC_RIF_TABLE, old_rif_oids) #If subintf mtu deleted, it inherits from parent - if vrf_name == self.VRF_UNDER_TEST: + if vrf_name == self.VRF_UNDER_TEST or vrf_name == self.VNET_UNDER_TEST: if parent_port.startswith(ETHERNET_PREFIX): fv_dict["SAI_ROUTER_INTERFACE_ATTR_MTU"] = ETHERNET_PORT_DEFAULT_MTU self.check_sub_port_intf_fvs(self.asic_db, ASIC_RIF_TABLE, rif_oid, fv_dict) From 92589789aa79bf1e70937a35cb06eff8a358ab6b Mon Sep 17 00:00:00 2001 From: Brian O'Connor Date: Fri, 26 Nov 2021 12:38:58 -0500 Subject: [PATCH 08/14] [orchagent, cfgmgr] Add response publisher and state recording (#1992) * Add response publisher , Add APPL STATE DB recording. Co-authored-by: PINS Working Group --- cfgmgr/Makefile.am | 26 +- cfgmgr/buffermgrd.cpp | 4 + cfgmgr/coppmgrd.cpp | 4 + cfgmgr/intfmgrd.cpp | 4 + cfgmgr/macsecmgrd.cpp | 4 + cfgmgr/natmgrd.cpp | 5 +- cfgmgr/nbrmgrd.cpp | 4 + cfgmgr/portmgrd.cpp | 4 + cfgmgr/sflowmgrd.cpp | 4 + cfgmgr/teammgrd.cpp | 4 + cfgmgr/tunnelmgrd.cpp | 4 + cfgmgr/vlanmgrd.cpp | 4 + cfgmgr/vrfmgrd.cpp | 4 + cfgmgr/vxlanmgrd.cpp | 4 + orchagent/Makefile.am | 3 +- orchagent/main.cpp | 67 +++- orchagent/orch.cpp | 27 +- orchagent/orch.h | 3 + orchagent/response_publisher.cpp | 191 ++++++++++ orchagent/response_publisher.h | 51 +++ orchagent/response_publisher_interface.h | 36 ++ orchagent/return_code.h | 373 +++++++++++++++++++ tests/mock_tests/Makefile.am | 1 + tests/mock_tests/database_config.json | 5 + tests/mock_tests/fake_response_publisher.cpp | 22 ++ 25 files changed, 815 insertions(+), 43 deletions(-) create mode 100644 orchagent/response_publisher.cpp create mode 100644 orchagent/response_publisher.h create mode 100644 orchagent/response_publisher_interface.h create mode 100644 orchagent/return_code.h create mode 100644 tests/mock_tests/fake_response_publisher.cpp diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index 21b1a5f91f..e11ec4635a 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -23,67 +23,67 @@ else DBGFLAGS = -g endif -vlanmgrd_SOURCES = vlanmgrd.cpp vlanmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +vlanmgrd_SOURCES = vlanmgrd.cpp vlanmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h vlanmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vlanmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vlanmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -teammgrd_SOURCES = teammgrd.cpp teammgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +teammgrd_SOURCES = teammgrd.cpp teammgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h teammgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) teammgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) teammgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -portmgrd_SOURCES = portmgrd.cpp portmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +portmgrd_SOURCES = portmgrd.cpp portmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h portmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) portmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) portmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -intfmgrd_SOURCES = intfmgrd.cpp intfmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/lib/subintf.cpp shellcmd.h +intfmgrd_SOURCES = intfmgrd.cpp intfmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/lib/subintf.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h intfmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) intfmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) intfmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -buffermgrd_SOURCES = buffermgrd.cpp buffermgr.cpp buffermgrdyn.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +buffermgrd_SOURCES = buffermgrd.cpp buffermgr.cpp buffermgrdyn.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h buffermgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) buffermgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) buffermgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -vrfmgrd_SOURCES = vrfmgrd.cpp vrfmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +vrfmgrd_SOURCES = vrfmgrd.cpp vrfmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h vrfmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vrfmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vrfmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -nbrmgrd_SOURCES = nbrmgrd.cpp nbrmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +nbrmgrd_SOURCES = nbrmgrd.cpp nbrmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h nbrmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(LIBNL_CFLAGS) nbrmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(LIBNL_CPPFLAGS) nbrmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) $(LIBNL_LIBS) -vxlanmgrd_SOURCES = vxlanmgrd.cpp vxlanmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +vxlanmgrd_SOURCES = vxlanmgrd.cpp vxlanmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h vxlanmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vxlanmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vxlanmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -sflowmgrd_SOURCES = sflowmgrd.cpp sflowmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +sflowmgrd_SOURCES = sflowmgrd.cpp sflowmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h sflowmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) sflowmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) sflowmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -natmgrd_SOURCES = natmgrd.cpp natmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +natmgrd_SOURCES = natmgrd.cpp natmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h natmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) natmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) natmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -coppmgrd_SOURCES = coppmgrd.cpp coppmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +coppmgrd_SOURCES = coppmgrd.cpp coppmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h coppmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) coppmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) coppmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -tunnelmgrd_SOURCES = tunnelmgrd.cpp tunnelmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +tunnelmgrd_SOURCES = tunnelmgrd.cpp tunnelmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h tunnelmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) tunnelmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) tunnelmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) -macsecmgrd_SOURCES = macsecmgrd.cpp macsecmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +macsecmgrd_SOURCES = macsecmgrd.cpp macsecmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp $(top_srcdir)/orchagent/response_publisher.cpp shellcmd.h macsecmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) macsecmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) macsecmgrd_LDADD = $(COMMON_LIBS) $(SAIMETA_LIBS) diff --git a/cfgmgr/buffermgrd.cpp b/cfgmgr/buffermgrd.cpp index 74e88f7337..07355c9dd9 100644 --- a/cfgmgr/buffermgrd.cpp +++ b/cfgmgr/buffermgrd.cpp @@ -33,6 +33,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/coppmgrd.cpp b/cfgmgr/coppmgrd.cpp index 1995405cd6..60b0a2442a 100644 --- a/cfgmgr/coppmgrd.cpp +++ b/cfgmgr/coppmgrd.cpp @@ -29,6 +29,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/intfmgrd.cpp b/cfgmgr/intfmgrd.cpp index d6ed18526e..9ed3653333 100644 --- a/cfgmgr/intfmgrd.cpp +++ b/cfgmgr/intfmgrd.cpp @@ -29,6 +29,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/macsecmgrd.cpp b/cfgmgr/macsecmgrd.cpp index f77e3d8c07..913c0ac4ee 100644 --- a/cfgmgr/macsecmgrd.cpp +++ b/cfgmgr/macsecmgrd.cpp @@ -38,6 +38,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/natmgrd.cpp b/cfgmgr/natmgrd.cpp index 7e2aeba4a2..c2baf7eb87 100644 --- a/cfgmgr/natmgrd.cpp +++ b/cfgmgr/natmgrd.cpp @@ -52,6 +52,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; mutex gDbMutex; NatMgr *natmgr = NULL; @@ -200,4 +204,3 @@ int main(int argc, char **argv) } return -1; } - diff --git a/cfgmgr/nbrmgrd.cpp b/cfgmgr/nbrmgrd.cpp index d9b6829036..338d8d9d0d 100644 --- a/cfgmgr/nbrmgrd.cpp +++ b/cfgmgr/nbrmgrd.cpp @@ -33,6 +33,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/portmgrd.cpp b/cfgmgr/portmgrd.cpp index b0f0c887dd..180bbc1d63 100644 --- a/cfgmgr/portmgrd.cpp +++ b/cfgmgr/portmgrd.cpp @@ -28,6 +28,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/sflowmgrd.cpp b/cfgmgr/sflowmgrd.cpp index 0436ad5f00..7de5f15a2d 100644 --- a/cfgmgr/sflowmgrd.cpp +++ b/cfgmgr/sflowmgrd.cpp @@ -28,6 +28,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/teammgrd.cpp b/cfgmgr/teammgrd.cpp index e38456eebe..66bfa4b6d2 100644 --- a/cfgmgr/teammgrd.cpp +++ b/cfgmgr/teammgrd.cpp @@ -17,6 +17,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; bool received_sigterm = false; diff --git a/cfgmgr/tunnelmgrd.cpp b/cfgmgr/tunnelmgrd.cpp index d419b2b886..0165eb94b5 100644 --- a/cfgmgr/tunnelmgrd.cpp +++ b/cfgmgr/tunnelmgrd.cpp @@ -31,6 +31,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/vlanmgrd.cpp b/cfgmgr/vlanmgrd.cpp index 88e4745758..b69dc78122 100644 --- a/cfgmgr/vlanmgrd.cpp +++ b/cfgmgr/vlanmgrd.cpp @@ -36,6 +36,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/vrfmgrd.cpp b/cfgmgr/vrfmgrd.cpp index 556b937901..735e59191d 100644 --- a/cfgmgr/vrfmgrd.cpp +++ b/cfgmgr/vrfmgrd.cpp @@ -29,6 +29,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; diff --git a/cfgmgr/vxlanmgrd.cpp b/cfgmgr/vxlanmgrd.cpp index 809c580f82..d47893a614 100644 --- a/cfgmgr/vxlanmgrd.cpp +++ b/cfgmgr/vxlanmgrd.cpp @@ -34,6 +34,10 @@ bool gSwssRecord = false; bool gLogRotate = false; ofstream gRecordOfs; string gRecordFile; +bool gResponsePublisherRecord = false; +bool gResponsePublisherLogRotate = false; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; /* Global database mutex */ mutex gDbMutex; MacAddress gMacAddress; diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 93d5aa01e0..47de037266 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -89,7 +89,8 @@ orchagent_SOURCES = \ macsecorch.cpp \ lagid.cpp \ bfdorch.cpp \ - srv6orch.cpp + srv6orch.cpp \ + response_publisher.cpp orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp orchagent_SOURCES += debug_counter/debug_counter.cpp debug_counter/drop_counter.cpp diff --git a/orchagent/main.cpp b/orchagent/main.cpp index aacae5fe97..de96234a2d 100644 --- a/orchagent/main.cpp +++ b/orchagent/main.cpp @@ -13,6 +13,8 @@ extern "C" { #include #include #include +#include +#include #include #include @@ -54,8 +56,10 @@ int gBatchSize = DEFAULT_BATCH_SIZE; bool gSairedisRecord = true; bool gSwssRecord = true; +bool gResponsePublisherRecord = false; bool gLogRotate = false; bool gSaiRedisLogRotate = false; +bool gResponsePublisherLogRotate = false; bool gSyncMode = false; sai_redis_communication_mode_t gRedisCommunicationMode = SAI_REDIS_COMMUNICATION_MODE_REDIS_ASYNC; string gAsicInstance; @@ -64,6 +68,12 @@ extern bool gIsNatSupported; ofstream gRecordOfs; string gRecordFile; +ofstream gResponsePublisherRecordOfs; +string gResponsePublisherRecordFile; + +#define SAIREDIS_RECORD_ENABLE 0x1 +#define SWSS_RECORD_ENABLE (0x1 << 1) +#define RESPONSE_PUBLISHER_RECORD_ENABLE (0x1 << 2) string gMySwitchType = ""; int32_t gVoqMySwitchId = -1; @@ -77,10 +87,12 @@ void usage() cout << "usage: orchagent [-h] [-r record_type] [-d record_location] [-f swss_rec_filename] [-j sairedis_rec_filename] [-b batch_size] [-m MAC] [-i INST_ID] [-s] [-z mode] [-k bulk_size]" << endl; cout << " -h: display this message" << endl; cout << " -r record_type: record orchagent logs with type (default 3)" << endl; + cout << " Bit 0: sairedis.rec, Bit 1: swss.rec, Bit 2: responsepublisher.rec. For example:" << endl; cout << " 0: do not record logs" << endl; cout << " 1: record SAI call sequence as sairedis.rec" << endl; cout << " 2: record SwSS task sequence as swss.rec" << endl; cout << " 3: enable both above two records" << endl; + cout << " 7: enable sairedis.rec, swss.rec and responsepublisher.rec" << endl; cout << " -d record_location: set record logs folder location (default .)" << endl; cout << " -b batch_size: set consumer table pop operation batch size (default 128)" << endl; cout << " -m MAC: set switch MAC address" << endl; @@ -99,6 +111,7 @@ void sighup_handler(int signo) */ gLogRotate = true; gSaiRedisLogRotate = true; + gResponsePublisherLogRotate = true; } void syncd_apply_view() @@ -115,7 +128,7 @@ void syncd_apply_view() { SWSS_LOG_ERROR("Failed to notify syncd APPLY_VIEW %d", status); exit(EXIT_FAILURE); - } + } } /* @@ -321,6 +334,8 @@ int main(int argc, char **argv) string record_location = "."; string swss_rec_filename = "swss.rec"; string sairedis_rec_filename = "sairedis.rec"; + string responsepublisher_rec_filename = "responsepublisher.rec"; + int record_type = 3; // Only swss and sairedis recordings enabled by default. while ((opt = getopt(argc, argv, "b:m:r:f:j:d:i:hsz:k:")) != -1) { @@ -346,24 +361,10 @@ int main(int argc, char **argv) gMacAddress = MacAddress(optarg); break; case 'r': - if (!strcmp(optarg, "0")) - { - gSairedisRecord = false; - gSwssRecord = false; - } - else if (!strcmp(optarg, "1")) - { - gSwssRecord = false; - } - else if (!strcmp(optarg, "2")) - { - gSairedisRecord = false; - } - else if (!strcmp(optarg, "3")) - { - continue; /* default behavior */ - } - else + // Disable all recordings if atoi() fails i.e. returns 0 due to + // invalid command line argument. + record_type = atoi(optarg); + if (record_type < 0 || record_type > 7) { usage(); exit(EXIT_FAILURE); @@ -434,6 +435,14 @@ int main(int argc, char **argv) attr.value.ptr = (void *)on_fdb_event; attrs.push_back(attr); + // Initialize recording parameters. + gSairedisRecord = + (record_type & SAIREDIS_RECORD_ENABLE) == SAIREDIS_RECORD_ENABLE; + gSwssRecord = (record_type & SWSS_RECORD_ENABLE) == SWSS_RECORD_ENABLE; + gResponsePublisherRecord = + (record_type & RESPONSE_PUBLISHER_RECORD_ENABLE) == + RESPONSE_PUBLISHER_RECORD_ENABLE; + /* Disable/enable SwSS recording */ if (gSwssRecord) { @@ -447,6 +456,24 @@ int main(int argc, char **argv) gRecordOfs << getTimestamp() << "|recording started" << endl; } + // Disable/Enable response publisher recording. + if (gResponsePublisherRecord) + { + gResponsePublisherRecordFile = record_location + "/" + responsepublisher_rec_filename; + gResponsePublisherRecordOfs.open(gResponsePublisherRecordFile, std::ofstream::out | std::ofstream::app); + if (!gResponsePublisherRecordOfs.is_open()) + { + SWSS_LOG_ERROR("Failed to open Response Publisher recording file %s", + gResponsePublisherRecordFile.c_str()); + gResponsePublisherRecord = false; + } + else + { + gResponsePublisherRecordOfs << getTimestamp() << "|recording started" + << endl; + } + } + attr.id = SAI_SWITCH_ATTR_PORT_STATE_CHANGE_NOTIFY; attr.value.ptr = (void *)on_port_state_change; attrs.push_back(attr); @@ -644,7 +671,7 @@ int main(int argc, char **argv) } else { - orchDaemon = make_shared(&appl_db, &config_db, &state_db, chassis_app_db.get()); + orchDaemon = make_shared(&appl_db, &config_db, &state_db, chassis_app_db.get()); } if (!orchDaemon->init()) diff --git a/orchagent/orch.cpp b/orchagent/orch.cpp index 5a27e9161a..0992e329a4 100644 --- a/orchagent/orch.cpp +++ b/orchagent/orch.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include "timestamp.h" #include "orch.h" @@ -197,9 +199,16 @@ size_t Consumer::refillToSync() auto subTable = dynamic_cast(consumerTable); if (subTable != NULL) { - std::deque entries; - subTable->pops(entries); - return addToSync(entries); + size_t update_size = 0; + size_t total_size = 0; + do + { + std::deque entries; + subTable->pops(entries); + update_size = addToSync(entries); + total_size += update_size; + } while (update_size != 0); + return total_size; } else { @@ -215,10 +224,13 @@ void Consumer::execute() { SWSS_LOG_ENTER(); - std::deque entries; - getConsumerTable()->pops(entries); - - addToSync(entries); + size_t update_size = 0; + do + { + std::deque entries; + getConsumerTable()->pops(entries); + update_size = addToSync(entries); + } while (update_size != 0); drain(); } @@ -1047,4 +1059,3 @@ void Orch2::doTask(Consumer &consumer) } } } - diff --git a/orchagent/orch.h b/orchagent/orch.h index ca75fac179..46a5d446ce 100644 --- a/orchagent/orch.h +++ b/orchagent/orch.h @@ -20,6 +20,7 @@ extern "C" { #include "notificationconsumer.h" #include "selectabletimer.h" #include "macaddress.h" +#include "response_publisher.h" const char delimiter = ':'; const char list_item_delimiter = ','; @@ -246,6 +247,8 @@ class Orch virtual task_process_status handleSaiRemoveStatus(sai_api_t api, sai_status_t status, void *context = nullptr); virtual task_process_status handleSaiGetStatus(sai_api_t api, sai_status_t status, void *context = nullptr); bool parseHandleSaiStatusFailure(task_process_status status); + + ResponsePublisher m_publisher; private: void removeMeFromObjsReferencedByMe(type_map &type_maps, const std::string &table, const std::string &obj_name, const std::string &field, const std::string &old_referenced_obj_name); void addConsumer(swss::DBConnector *db, std::string tableName, int pri = default_orch_pri); diff --git a/orchagent/response_publisher.cpp b/orchagent/response_publisher.cpp new file mode 100644 index 0000000000..5d0490167c --- /dev/null +++ b/orchagent/response_publisher.cpp @@ -0,0 +1,191 @@ +#include "response_publisher.h" + +#include +#include +#include +#include + +#include "timestamp.h" + +extern bool gResponsePublisherRecord; +extern bool gResponsePublisherLogRotate; +extern std::ofstream gResponsePublisherRecordOfs; +extern std::string gResponsePublisherRecordFile; + +namespace +{ + +// Returns the component string that we need to prepend for sending the error +// message. +// Returns an empty string if the status is OK. +// Returns "[SAI] " if the ReturnCode is generated from a SAI status code. +// Else, returns "[OrchAgent] ". +std::string PrependedComponent(const ReturnCode &status) +{ + constexpr char *kOrchagentComponent = "[OrchAgent] "; + constexpr char *kSaiComponent = "[SAI] "; + if (status.ok()) + { + return ""; + } + if (status.isSai()) + { + return kSaiComponent; + } + return kOrchagentComponent; +} + +void PerformLogRotate() +{ + if (!gResponsePublisherLogRotate) + { + return; + } + gResponsePublisherLogRotate = false; + + gResponsePublisherRecordOfs.close(); + gResponsePublisherRecordOfs.open(gResponsePublisherRecordFile); + if (!gResponsePublisherRecordOfs.is_open()) + { + SWSS_LOG_ERROR("Failed to reopen Response Publisher record file %s: %s", gResponsePublisherRecordFile.c_str(), + strerror(errno)); + } +} + +void RecordDBWrite(const std::string &table, const std::string &key, const std::vector &attrs, + const std::string &op) +{ + if (!gResponsePublisherRecord) + { + return; + } + + std::string s = table + ":" + key + "|" + op; + for (const auto &attr : attrs) + { + s += "|" + fvField(attr) + ":" + fvValue(attr); + } + + PerformLogRotate(); + gResponsePublisherRecordOfs << swss::getTimestamp() << "|" << s << std::endl; +} + +void RecordResponse(const std::string &response_channel, const std::string &key, + const std::vector &attrs, const std::string &status) +{ + if (!gResponsePublisherRecord) + { + return; + } + + std::string s = response_channel + ":" + key + "|" + status; + for (const auto &attr : attrs) + { + s += "|" + fvField(attr) + ":" + fvValue(attr); + } + + PerformLogRotate(); + gResponsePublisherRecordOfs << swss::getTimestamp() << "|" << s << std::endl; +} + +} // namespace + +ResponsePublisher::ResponsePublisher() : m_db("APPL_STATE_DB", 0) +{ +} + +void ResponsePublisher::publish(const std::string &table, const std::string &key, + const std::vector &intent_attrs, const ReturnCode &status, + const std::vector &state_attrs, bool replace) +{ + // Write to the DB only if: + // 1) A write operation is being performed and state attributes are specified. + // 2) A successful delete operation. + if ((intent_attrs.size() && state_attrs.size()) || (status.ok() && !intent_attrs.size())) + { + writeToDB(table, key, state_attrs, intent_attrs.size() ? SET_COMMAND : DEL_COMMAND, replace); + } + + std::string response_channel = "APPL_DB_" + table + "_RESPONSE_CHANNEL"; + if (m_notifiers.find(table) == m_notifiers.end()) + { + m_notifiers[table] = std::make_unique(&m_db, response_channel); + } + + auto intent_attrs_copy = intent_attrs; + // Add error message as the first field-value-pair. + swss::FieldValueTuple err_str("err_str", PrependedComponent(status) + status.message()); + intent_attrs_copy.insert(intent_attrs_copy.begin(), err_str); + // Sends the response to the notification channel. + m_notifiers[table]->send(status.codeStr(), key, intent_attrs_copy); + RecordResponse(response_channel, key, intent_attrs_copy, status.codeStr()); +} + +void ResponsePublisher::publish(const std::string &table, const std::string &key, + const std::vector &intent_attrs, const ReturnCode &status, + bool replace) +{ + // If status is OK then intent attributes need to be written in + // APPL_STATE_DB. In this case, pass the intent attributes as state + // attributes. In case of a failure status, nothing needs to be written in + // APPL_STATE_DB. + std::vector state_attrs; + if (status.ok()) + { + state_attrs = intent_attrs; + } + publish(table, key, intent_attrs, status, state_attrs, replace); +} + +void ResponsePublisher::writeToDB(const std::string &table, const std::string &key, + const std::vector &values, const std::string &op, bool replace) +{ + if (m_tables.find(table) == m_tables.end()) + { + m_tables[table] = std::make_unique(&m_db, table); + } + + auto attrs = values; + if (op == SET_COMMAND) + { + if (replace) + { + m_tables[table]->del(key); + } + if (!values.size()) + { + attrs.push_back(swss::FieldValueTuple("NULL", "NULL")); + } + + // Write to DB only if the key does not exist or non-NULL attributes are + // being written to the entry. + std::vector fv; + if (!m_tables[table]->get(key, fv)) + { + m_tables[table]->set(key, attrs); + RecordDBWrite(table, key, attrs, op); + return; + } + for (auto it = attrs.cbegin(); it != attrs.cend();) + { + if (it->first == "NULL") + { + it = attrs.erase(it); + } + else + { + it++; + } + } + if (attrs.size()) + { + m_tables[table]->set(key, attrs); + RecordDBWrite(table, key, attrs, op); + } + } + else if (op == DEL_COMMAND) + { + m_tables[table]->del(key); + RecordDBWrite(table, key, {}, op); + } +} diff --git a/orchagent/response_publisher.h b/orchagent/response_publisher.h new file mode 100644 index 0000000000..cd688112e8 --- /dev/null +++ b/orchagent/response_publisher.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +#include "dbconnector.h" +#include "notificationproducer.h" +#include "response_publisher_interface.h" +#include "table.h" + +// This class performs two tasks when publish is called: +// 1. Sends a notification into the redis channel. +// 2. Writes the operation into the DB. +class ResponsePublisher : public ResponsePublisherInterface +{ + public: + explicit ResponsePublisher(); + virtual ~ResponsePublisher() = default; + + // Intent attributes are the attributes sent in the notification into the + // redis channel. + // State attributes are the list of attributes that need to be written in + // the DB namespace. These might be different from intent attributes. For + // example: + // 1) If only a subset of the intent attributes were successfully applied, the + // state attributes shall be different from intent attributes. + // 2) If additional state changes occur due to the intent attributes, more + // attributes need to be added in the state DB namespace. + // 3) Invalid attributes are excluded from the state attributes. + // State attributes will be written into the DB even if the status code + // consists of an error. + void publish(const std::string &table, const std::string &key, + const std::vector &intent_attrs, const ReturnCode &status, + const std::vector &state_attrs, bool replace = false) override; + + void publish(const std::string &table, const std::string &key, + const std::vector &intent_attrs, const ReturnCode &status, + bool replace = false) override; + + void writeToDB(const std::string &table, const std::string &key, const std::vector &values, + const std::string &op, bool replace = false) override; + + private: + swss::DBConnector m_db; + // Maps table names to tables. + std::unordered_map> m_tables; + // Maps table names to notifiers. + std::unordered_map> m_notifiers; +}; diff --git a/orchagent/response_publisher_interface.h b/orchagent/response_publisher_interface.h new file mode 100644 index 0000000000..92d364a500 --- /dev/null +++ b/orchagent/response_publisher_interface.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "return_code.h" +#include "table.h" + +class ResponsePublisherInterface { + public: + virtual ~ResponsePublisherInterface() = default; + + // Publishes the response status. + // If intent attributes are empty, it is a delete operation. + // What "publish" needs to do is completely up to implementation. + // This API does not include redis DB namespace. So if implementation chooses + // to write to a redis DB, it will need to use a fixed namespace. + // The replace flag indicates the state attributes will replace the old ones. + virtual void publish(const std::string& table, const std::string& key, + const std::vector& intent_attrs, + const ReturnCode& status, + const std::vector& state_attrs, + bool replace = false) = 0; + + // Publishes response status. If response status is OK then also writes the + // intent attributes into the DB. + // The replace flag indicates a replace operation. + virtual void publish(const std::string& table, const std::string& key, + const std::vector& intent_attrs, + const ReturnCode& status, bool replace = false) = 0; + + // Write to DB only. This API does not send notification. + // The replace flag indicates the new attributes will replace the old ones. + virtual void writeToDB(const std::string& table, const std::string& key, + const std::vector& values, + const std::string& op, bool replace = false) = 0; +}; diff --git a/orchagent/return_code.h b/orchagent/return_code.h new file mode 100644 index 0000000000..87a1a761e1 --- /dev/null +++ b/orchagent/return_code.h @@ -0,0 +1,373 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "sai_serialize.h" +#include "status_code_util.h" + +extern "C" +{ +#include "sai.h" +} + +using swss::StatusCode; + +// RETURN_IF_ERROR evaluates an expression that returns a ReturnCode. If the +// result is not ok, returns the result. Otherwise, continues. +// +// Example: +// ReturnCode Foo() {...} +// ReturnCode Bar() { +// RETURN_IF_ERROR(Foo()); +// return ReturnCode(); +// } +#define RETURN_IF_ERROR(expr) \ + do \ + { \ + ReturnCode RETURN_IF_ERROR_RC_ = expr; \ + if (!RETURN_IF_ERROR_RC_.ok()) \ + return RETURN_IF_ERROR_RC_; \ + } while (0) + +// LOG_ERROR_AND_RETURN evaluates an expression that should returns an error +// ReturnCode. Logs the error message in the ReturnCode by calling +// SWSS_LOG_ERROR and returns. +#define LOG_ERROR_AND_RETURN(expr) \ + do \ + { \ + ReturnCode LOG_ERROR_AND_RETURN_RC_ = expr; \ + SWSS_LOG_ERROR("%s", LOG_ERROR_AND_RETURN_RC_.message().c_str()); \ + return LOG_ERROR_AND_RETURN_RC_; \ + } while (0) + +// Same as RETURN_IF_ERROR, plus a call of SWSS_LOG_ERROR for the return code +// error message. +#define LOG_AND_RETURN_IF_ERROR(expr) \ + do \ + { \ + ReturnCode LOG_AND_RETURN_IF_ERROR_RC_ = expr; \ + if (!LOG_AND_RETURN_IF_ERROR_RC_.ok()) \ + { \ + SWSS_LOG_ERROR("%s", LOG_AND_RETURN_IF_ERROR_RC_.message().c_str()); \ + return LOG_AND_RETURN_IF_ERROR_RC_; \ + } \ + } while (0) + +#define RETURNCODE_MACROS_IMPL_CONCAT_INNER_(x, y) x##y + +#define RETURNCODE_MACROS_IMPL_CONCAT_(x, y) RETURNCODE_MACROS_IMPL_CONCAT_INNER_(x, y) + +// ASSIGN_OR_RETURN evaluates an expression that returns a ReturnCodeOr. If the +// result is ok, the value is saved to dest. Otherwise, the ReturnCode is +// returned. +// +// Example: +// ReturnCodeOr Foo() {...} +// ReturnCode Bar() { +// ASSIGN_OR_RETURN(int value, Foo()); +// std::cout << "value: " << value; +// return ReturnCode(); +// } +#define ASSIGN_OR_RETURN(dest, expr) \ + auto RETURNCODE_MACROS_IMPL_CONCAT_(ASSIGN_OR_RETURN_RESULT_, __LINE__) = expr; \ + if (!RETURNCODE_MACROS_IMPL_CONCAT_(ASSIGN_OR_RETURN_RESULT_, __LINE__).ok()) \ + { \ + return RETURNCODE_MACROS_IMPL_CONCAT_(ASSIGN_OR_RETURN_RESULT_, __LINE__).status(); \ + } \ + dest = std::move(RETURNCODE_MACROS_IMPL_CONCAT_(ASSIGN_OR_RETURN_RESULT_, __LINE__).value()) + +// CHECK_ERROR_AND_LOG evaluates an expression that returns a sai_status_t. If +// the result is not SAI_STATUS_SUCCESS, it will log an error message. +// +// Example: +// CHECK_ERROR_AND_LOG( +// sai_router_intfs_api->set_router_interface_attribute(...), +// "error message" << " stream"); +#define CHECK_ERROR_AND_LOG(expr, msg_stream) \ + do \ + { \ + sai_status_t CHECK_ERROR_AND_LOG_SAI_ = expr; \ + if (CHECK_ERROR_AND_LOG_SAI_ != SAI_STATUS_SUCCESS) \ + { \ + std::stringstream CHECK_ERROR_AND_LOG_SS_; \ + CHECK_ERROR_AND_LOG_SS_ << msg_stream; \ + SWSS_LOG_ERROR("%s SAI_STATUS: %s", CHECK_ERROR_AND_LOG_SS_.str().c_str(), \ + sai_serialize_status(CHECK_ERROR_AND_LOG_SAI_).c_str()); \ + } \ + } while (0) + +// CHECK_ERROR_AND_LOG_AND_RETURN evaluates an expression that returns a +// sai_status_t. If the result is not SAI_STATUS_SUCCESS, it will log an error +// message and return a ReturnCode. +// +// Example: +// CHECK_ERROR_AND_LOG_AND_RETURN( +// sai_router_intfs_api->set_router_interface_attribute(...), +// "error message" << " stream"); +#define CHECK_ERROR_AND_LOG_AND_RETURN(expr, msg_stream) \ + do \ + { \ + sai_status_t CHECK_ERROR_AND_LOG_AND_RETURN_SAI_ = expr; \ + if (CHECK_ERROR_AND_LOG_AND_RETURN_SAI_ != SAI_STATUS_SUCCESS) \ + { \ + ReturnCode CHECK_ERROR_AND_LOG_AND_RETURN_RC_ = ReturnCode(CHECK_ERROR_AND_LOG_AND_RETURN_SAI_) \ + << msg_stream; \ + SWSS_LOG_ERROR("%s SAI_STATUS: %s", CHECK_ERROR_AND_LOG_AND_RETURN_RC_.message().c_str(), \ + sai_serialize_status(CHECK_ERROR_AND_LOG_AND_RETURN_SAI_).c_str()); \ + return CHECK_ERROR_AND_LOG_AND_RETURN_RC_; \ + } \ + } while (0) + +// This macro raises critical state to indicate that something is seriously +// wrong in the system. Currently, this macro just logs an error message. +// TODO: Implement this macro. +#define SWSS_RAISE_CRITICAL_STATE(err_str) \ + do \ + { \ + std::string err_msge = err_str; \ + SWSS_LOG_ERROR("Orchagent is in critical state: %s", err_msge.c_str()); \ + } while (0) + +// RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL returns an error status of +// SWSS_RC_INTERNAL. It also logs the error message and reports critical state. +#define RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL(msg_stream) \ + do \ + { \ + ReturnCode RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL_RC_ = ReturnCode(StatusCode::SWSS_RC_INTERNAL) \ + << msg_stream; \ + SWSS_LOG_ERROR("%s", RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL_RC_.message().c_str()); \ + SWSS_RAISE_CRITICAL_STATE(RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL_RC_.message()); \ + return RETURN_INTERNAL_ERROR_AND_RAISE_CRITICAL_RC_; \ + } while (0) + +class ReturnCode +{ + public: + ReturnCode() + : status_(StatusCode::SWSS_RC_SUCCESS), stream_(std::ios_base::out | std::ios_base::ate), is_sai_(false) + { + } + + ReturnCode(const StatusCode &status, const std::string &message = "") + : status_(status), stream_(std::ios_base::out | std::ios_base::ate), is_sai_(false) + { + stream_ << message; + } + + ReturnCode(const sai_status_t &status, const std::string &message = "") + : stream_(std::ios_base::out | std::ios_base::ate), is_sai_(true) + { + if (m_saiStatusCodeLookup.find(status) == m_saiStatusCodeLookup.end()) + { + status_ = StatusCode::SWSS_RC_UNKNOWN; + } + else + { + status_ = m_saiStatusCodeLookup[status]; + } + stream_ << message; + } + + ReturnCode(const ReturnCode &return_code) : stream_(std::ios_base::out | std::ios_base::ate) + { + status_ = return_code.status_; + stream_ << return_code.stream_.str(); + is_sai_ = return_code.is_sai_; + } + + ReturnCode &operator=(const ReturnCode &return_code) + { + status_ = return_code.status_; + stream_.str(return_code.stream_.str()); + is_sai_ = return_code.is_sai_; + return *this; + } + + ~ReturnCode() = default; + + bool ok() const + { + return status_ == StatusCode::SWSS_RC_SUCCESS; + } + + StatusCode code() const + { + return status_; + } + + std::string codeStr() const + { + return swss::statusCodeToStr(status_); + } + + std::string message() const + { + if (stream_.str().empty()) + { + return codeStr(); + } + return stream_.str(); + } + + ReturnCode &prepend(const std::string &msg) + { + const std::string &tmp = stream_.str(); + stream_.str(msg + tmp); + return *this; + } + + std::string toString() const + { + return codeStr() + ":" + message(); + } + + // Whether the ReturnCode is generated from a SAI status code or not. + bool isSai() const + { + return is_sai_; + } + + template ReturnCode &operator<<(T val) + { + stream_ << val; + return *this; + } + + bool operator==(const ReturnCode &x) const + { + return status_ == x.status_ && message() == x.message(); + } + + bool operator!=(const ReturnCode &x) const + { + return status_ != x.status_ || message() != x.message(); + } + + bool operator==(const StatusCode &x) const + { + return status_ == x; + } + + bool operator!=(const StatusCode &x) const + { + return status_ != x; + } + + private: + // SAI codes that are not included in this lookup map will map to + // SWSS_RC_UNKNOWN. This includes the general SAI failure: SAI_STATUS_FAILURE. + std::unordered_map m_saiStatusCodeLookup = { + {SAI_STATUS_SUCCESS, StatusCode::SWSS_RC_SUCCESS}, + {SAI_STATUS_NOT_SUPPORTED, StatusCode::SWSS_RC_UNIMPLEMENTED}, + {SAI_STATUS_NO_MEMORY, StatusCode::SWSS_RC_NO_MEMORY}, + {SAI_STATUS_INSUFFICIENT_RESOURCES, StatusCode::SWSS_RC_FULL}, + {SAI_STATUS_INVALID_PARAMETER, StatusCode::SWSS_RC_INVALID_PARAM}, + {SAI_STATUS_ITEM_ALREADY_EXISTS, StatusCode::SWSS_RC_EXISTS}, + {SAI_STATUS_ITEM_NOT_FOUND, StatusCode::SWSS_RC_NOT_FOUND}, + {SAI_STATUS_TABLE_FULL, StatusCode::SWSS_RC_FULL}, + {SAI_STATUS_NOT_IMPLEMENTED, StatusCode::SWSS_RC_UNIMPLEMENTED}, + {SAI_STATUS_OBJECT_IN_USE, StatusCode::SWSS_RC_IN_USE}, + }; + + StatusCode status_; + std::stringstream stream_; + // Whether the ReturnCode is generated from a SAI status code or not. + bool is_sai_; +}; + +inline bool operator==(const StatusCode &lhs, const ReturnCode &rhs) +{ + return lhs == rhs.code(); +} + +inline bool operator!=(const StatusCode &lhs, const ReturnCode &rhs) +{ + return lhs != rhs.code(); +} + +template class ReturnCodeOr +{ + public: + using value_type = T; + + // Value Constructors. + ReturnCodeOr(const T &value) : return_code_(ReturnCode()), value_(std::unique_ptr(new T(value))) + { + } + ReturnCodeOr(T &&value) : return_code_(ReturnCode()), value_(std::unique_ptr(new T(std::move(value)))) + { + } + + // ReturnCode constructors. + ReturnCodeOr(const ReturnCode &return_code) : return_code_(return_code) + { + assert(!return_code.ok()); + } + + // ReturnCode accessors. + bool ok() const + { + return return_code_.ok(); + } + const ReturnCode &status() const + { + return return_code_; + } + + // Value accessors. + const T &value() const & + { + assert(return_code_.ok()); + return *value_; + } + T &value() & + { + assert(return_code_.ok()); + return *value_; + } + const T &&value() const && + { + assert(return_code_.ok()); + return std::move(*value_); + } + T &&value() && + { + assert(return_code_.ok()); + return std::move(*value_); + } + + const T &operator*() const & + { + return value(); + } + T &operator*() & + { + return value(); + } + const T &&operator*() const && + { + return value(); + } + T &&operator*() && + { + return value(); + } + + const T *operator->() const + { + return value_.get(); + } + T *operator->() + { + return value_.get(); + } + + private: + ReturnCode return_code_; + std::unique_ptr value_; +}; diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 7419463847..1e4fd1903e 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -32,6 +32,7 @@ tests_SOURCES = aclorch_ut.cpp \ mock_hiredis.cpp \ mock_redisreply.cpp \ bulker_ut.cpp \ + fake_response_publisher.cpp \ $(top_srcdir)/lib/gearboxutils.cpp \ $(top_srcdir)/lib/subintf.cpp \ $(top_srcdir)/orchagent/orchdaemon.cpp \ diff --git a/tests/mock_tests/database_config.json b/tests/mock_tests/database_config.json index 1b6343d20e..8301848683 100644 --- a/tests/mock_tests/database_config.json +++ b/tests/mock_tests/database_config.json @@ -61,6 +61,11 @@ "id" : 12, "separator": "|", "instance" : "redis_chassis" + }, + "APPL_STATE_DB" : { + "id" : 14, + "separator": ":", + "instance" : "redis" } }, "VERSION" : "1.0" diff --git a/tests/mock_tests/fake_response_publisher.cpp b/tests/mock_tests/fake_response_publisher.cpp new file mode 100644 index 0000000000..94480913d5 --- /dev/null +++ b/tests/mock_tests/fake_response_publisher.cpp @@ -0,0 +1,22 @@ +#include +#include + +#include "response_publisher.h" + +ResponsePublisher::ResponsePublisher() : m_db("APPL_STATE_DB", 0) {} + +void ResponsePublisher::publish( + const std::string& table, const std::string& key, + const std::vector& intent_attrs, + const ReturnCode& status, + const std::vector& state_attrs, bool replace) {} + +void ResponsePublisher::publish( + const std::string& table, const std::string& key, + const std::vector& intent_attrs, + const ReturnCode& status, bool replace) {} + +void ResponsePublisher::writeToDB( + const std::string& table, const std::string& key, + const std::vector& values, const std::string& op, + bool replace) {} From fd887bf881b22de58b653226e0cd423e68c83866 Mon Sep 17 00:00:00 2001 From: Stephen Sun <5379172+stephenxs@users.noreply.github.com> Date: Tue, 30 Nov 2021 00:00:29 +0800 Subject: [PATCH 09/14] [Reclaim buffer] Reclaim unused buffer for dynamic buffer model (#1910) Signed-off-by: Stephen Sun stephens@nvidia.com What I did Reclaim reserved buffer of unused ports for both dynamic and traditional models. This is done by Removing lossless priority groups on unused ports. Applying zero buffer profiles on the buffer objects of unused ports. In the dynamic buffer model, the zero profiles are loaded from a JSON file and applied to APPL_DB if there are admin down ports. The default buffer configuration will be configured on all ports. Buffer manager will apply zero profiles on admin down ports. In the static buffer model, the zero profiles are loaded by the buffer template. Why I did it How I verified it Regression test and vs test. Details if related Static buffer model Remove the lossless buffer priority group if the port is admin-down and the buffer profile aligns with the speed and cable length of the port. Dynamic buffer model Handle zero buffer pools and profiles buffermgrd: add a CLI option to load the JSON file for zero profiles. (done in PR [Reclaiming buffer] Common code update #1996) Load them from JSON file into the internal buffer manager's data structure (done in PR [Reclaiming buffer] Common code update #1996) Apply them to APPL_DB once there is at least one admin-down port Record zero profiles' names in the pool object it references. By doing so, the zero profile lists can be constructed according to the normal profile list. There should be one profile for each pool on the ingress/egress side. And then apply the zero profiles to the buffer objects of the port. Unload them from APPL_DB once all ports are admin-up since the zero pools and profiles are no longer referenced. Remove buffer pool counter id when the zero pool is removed. Now that it's possible that a pool will be removed from the system, the watermark counter of the pool is removed ahead of the pool itself being removed. Handle port admin status change Currently, there is a logic of removing buffer priority groups of admin down ports. This logic will be reused and extended for all buffer objects, including BUFFER_QUEUE, BUFFER_PORT_INGRESS_PROFILE_LIST, and BUFFER_PORT_EGRESS_PROFILE_LIST. When the port is admin down, The normal profiles are removed from the buffer objects of the port The zero profiles, if provided, are applied to the port When the port is admin up, The zero profiles, if applied, are removed from the port The normal profiles are applied to the port. Ports orchagent exposes the number of queues and priority groups to STATE_DB. Buffer manager can take advantage of these values to apply zero profiles on all the priority groups and queues of the admin-down ports. In case it is not necessary to apply zero profiles on all priority groups or queues on a certain platform, ids_to_reclaim can be customized in the JSON file. Handle all buffer tables, including BUFFER_PG, BUFFER_QUEUE, BUFFER_PORT_INGRESS_PROFILE_LIST and BUFFER_PORT_EGRESS_PROFILE_LIST Originally, only the BUFFER_PG table was cached in the dynamic buffer manager. Now, all tables are cached in order to apply zero profiles when a port is admin down and apply normal profiles when it's up. The index of such tables can include a single port or a list of ports, like BUFFER_PG|Ethernet0|3-4 or BUFFER_PG|Ethernet0,Ethernet4,Ethernet8|3-4. Originally, there is a logic to handle such indexes for the BUFFER_PG table. Now it is reused and extended to handle all the tables. [Mellanox] Plugin to calculate buffer pool size: Originally, buffer for the queue, buffer profile list, etc. were not reclaimed for admin-down ports so they are reserved for all ports. Now, they are reserved for admin-up ports only. Accelerate the progress of applying buffer tables to APPL_DB This is an optimization on top of reclaiming buffer. Don't apply buffer profiles, buffer objects to APPL_DB before buffer pools are applied when the system is starting. This is to apply the items in an order from referenced items to referencing items and try to avoid buffer orchagent retrying due to referenced table items. However, it is still possible that the referencing items are handled before referenced items. In that case, there should not be any error message. [Mellanox] Plugin to calculate buffer pool size: Return the buffer pool sizes value currently in APPL_DB if the pool sizes are not able to be calculated due to lacking some information. This typically happens at the system start. This is to accelerate the progress of pushing tables to APPL_DB. --- cfgmgr/buffer_pool_mellanox.lua | 130 ++- cfgmgr/buffermgrd.cpp | 4 + cfgmgr/buffermgrdyn.cpp | 1544 +++++++++++++++++++++++++++---- cfgmgr/buffermgrdyn.h | 133 ++- tests/test_buffer_dynamic.py | 113 ++- 5 files changed, 1686 insertions(+), 238 deletions(-) diff --git a/cfgmgr/buffer_pool_mellanox.lua b/cfgmgr/buffer_pool_mellanox.lua index e49032fdf5..8c51c28706 100644 --- a/cfgmgr/buffer_pool_mellanox.lua +++ b/cfgmgr/buffer_pool_mellanox.lua @@ -28,11 +28,23 @@ local port_set_8lanes = {} local lossless_port_count = 0 local function iterate_all_items(all_items, check_lossless) + -- Iterates all items in all_items, check the buffer profile each item referencing, and update reference count accordingly + -- Arguments: + -- all_items is a list, holding all keys in BUFFER_PORT_INGRESS_PROFILE_LIST or BUFFER_PORT_EGRESS_PROFILE_LIST table + -- format of keys: |, like Ethernet0|3-4 + -- Return: + -- 0 successful + -- 1 failure, typically caused by the items just updated are still pended in orchagent's queue table.sort(all_items) local lossless_ports = {} local port local fvpairs for i = 1, #all_items, 1 do + -- XXX_TABLE_KEY_SET or XXX_TABLE_DEL_SET existing means the orchagent hasn't handled all updates + -- In this case, the pool sizes are not calculated for now and will retry later + if string.sub(all_items[i], -4, -1) == "_SET" then + return 1 + end -- Count the number of priorities or queues in each BUFFER_PG or BUFFER_QUEUE item -- For example, there are: -- 3 queues in 'BUFFER_QUEUE_TABLE:Ethernet0:0-2' @@ -73,6 +85,83 @@ local function iterate_all_items(all_items, check_lossless) return 0 end +local function iterate_profile_list(all_items) + -- Iterates all items in all_items, check the buffer profiles each item referencing, and update reference count accordingly + -- Arguments: + -- all_items is a list, holding all keys in BUFFER_PORT_INGRESS_PROFILE_LIST or BUFFER_PORT_EGRESS_PROFILE_LIST table + -- format of keys: + -- Return: + -- 0 successful + -- 1 failure, typically caused by the items just updated are still pended in orchagent's queue + local port + for i = 1, #all_items, 1 do + -- XXX_TABLE_KEY_SET or XXX_TABLE_DEL_SET existing means the orchagent hasn't handled all updates + -- In this case, the pool sizes are not calculated for now and will retry later + if string.sub(all_items[i], -4, -1) == "_SET" then + return 1 + end + port = string.match(all_items[i], "Ethernet%d+") + local profile_list = redis.call('HGET', all_items[i], 'profile_list') + if not profile_list then + return 0 + end + for profile_name in string.gmatch(profile_list, "([^,]+)") do + -- The format of profile_list is profile_name,profile_name + -- We need to handle each of the profile in the list + -- The ingress_lossy_profile is shared by both BUFFER_PG||0 and BUFFER_PORT_INGRESS_PROFILE_LIST + -- It occupies buffers in BUFFER_PG but not in BUFFER_PORT_INGRESS_PROFILE_LIST + -- To distinguish both cases, a new name "ingress_lossy_profile_list" is introduced to indicate + -- the profile is used by the profile list where its size should be zero. + profile_name = 'BUFFER_PROFILE_TABLE:' .. profile_name + if profile_name == 'BUFFER_PROFILE_TABLE:ingress_lossy_profile' then + profile_name = profile_name .. '_list' + if profiles[profile_name] == nil then + profiles[profile_name] = 0 + end + end + local profile_ref_count = profiles[profile_name] + if profile_ref_count == nil then + return 1 + end + profiles[profile_name] = profile_ref_count + 1 + end + end + + return 0 +end + +local function fetch_buffer_pool_size_from_appldb() + local buffer_pools = {} + redis.call('SELECT', config_db) + local buffer_pool_keys = redis.call('KEYS', 'BUFFER_POOL|*') + local pool_name + for i = 1, #buffer_pool_keys, 1 do + local size = redis.call('HGET', buffer_pool_keys[i], 'size') + if not size then + pool_name = string.match(buffer_pool_keys[i], "BUFFER_POOL|([^%s]+)$") + table.insert(buffer_pools, pool_name) + end + end + + redis.call('SELECT', appl_db) + buffer_pool_keys = redis.call('KEYS', 'BUFFER_POOL_TABLE:*') + local size + local xoff + local output + for i = 1, #buffer_pools, 1 do + size = redis.call('HGET', 'BUFFER_POOL_TABLE:' .. buffer_pools[i], 'size') + if not size then + size = "0" + end + xoff = redis.call('HGET', 'BUFFER_POOL_TABLE:' .. buffer_pools[i], 'xoff') + if not xoff then + table.insert(result, buffer_pools[i] .. ':' .. size) + else + table.insert(result, buffer_pools[i] .. ':' .. size .. ':' .. xoff) + end + end +end + -- Connect to CONFIG_DB redis.call('SELECT', config_db) @@ -82,7 +171,10 @@ total_port = #ports_table -- Initialize the port_set_8lanes set local lanes -local number_of_lanes +local number_of_lanes = 0 +local admin_status +local admin_up_port = 0 +local admin_up_8lanes_port = 0 local port for i = 1, total_port, 1 do -- Load lanes from PORT table @@ -99,13 +191,26 @@ for i = 1, total_port, 1 do port_set_8lanes[port] = false end end + admin_status = redis.call('HGET', ports_table[i], 'admin_status') + if admin_status == 'up' then + admin_up_port = admin_up_port + 1 + if (number_of_lanes == 8) then + admin_up_8lanes_port = admin_up_8lanes_port + 1 + end + end + number_of_lanes = 0 end local egress_lossless_pool_size = redis.call('HGET', 'BUFFER_POOL|egress_lossless_pool', 'size') -- Whether shared headroom pool is enabled? local default_lossless_param_keys = redis.call('KEYS', 'DEFAULT_LOSSLESS_BUFFER_PARAMETER*') -local over_subscribe_ratio = tonumber(redis.call('HGET', default_lossless_param_keys[1], 'over_subscribe_ratio')) +local over_subscribe_ratio +if #default_lossless_param_keys > 0 then + over_subscribe_ratio = tonumber(redis.call('HGET', default_lossless_param_keys[1], 'over_subscribe_ratio')) +else + over_subscribe_ratio = 0 +end -- Fetch the shared headroom pool size local shp_size = tonumber(redis.call('HGET', 'BUFFER_POOL|ingress_lossless_pool', 'xoff')) @@ -161,7 +266,18 @@ local fail_count = 0 fail_count = fail_count + iterate_all_items(all_pgs, true) fail_count = fail_count + iterate_all_items(all_tcs, false) if fail_count > 0 then - return {} + fetch_buffer_pool_size_from_appldb() + return result +end + +local all_ingress_profile_lists = redis.call('KEYS', 'BUFFER_PORT_INGRESS_PROFILE_LIST*') +local all_egress_profile_lists = redis.call('KEYS', 'BUFFER_PORT_EGRESS_PROFILE_LIST*') + +fail_count = fail_count + iterate_profile_list(all_ingress_profile_lists) +fail_count = fail_count + iterate_profile_list(all_egress_profile_lists) +if fail_count > 0 then + fetch_buffer_pool_size_from_appldb() + return result end local statistics = {} @@ -177,9 +293,6 @@ for name in pairs(profiles) do if name == "BUFFER_PROFILE_TABLE:ingress_lossy_profile" then size = size + lossypg_reserved end - if name == "BUFFER_PROFILE_TABLE:egress_lossy_profile" then - profiles[name] = total_port - end if size ~= 0 then if shp_enabled and shp_size == 0 then local xon = tonumber(redis.call('HGET', name, 'xon')) @@ -211,11 +324,11 @@ if shp_enabled then end -- Accumulate sizes for management PGs -local accumulative_management_pg = (total_port - port_count_8lanes) * lossypg_reserved + port_count_8lanes * lossypg_reserved_8lanes +local accumulative_management_pg = (admin_up_port - admin_up_8lanes_port) * lossypg_reserved + admin_up_8lanes_port * lossypg_reserved_8lanes accumulative_occupied_buffer = accumulative_occupied_buffer + accumulative_management_pg -- Accumulate sizes for egress mirror and management pool -local accumulative_egress_mirror_overhead = total_port * egress_mirror_headroom +local accumulative_egress_mirror_overhead = admin_up_port * egress_mirror_headroom accumulative_occupied_buffer = accumulative_occupied_buffer + accumulative_egress_mirror_overhead + mgmt_pool_size -- Switch to CONFIG_DB @@ -295,5 +408,6 @@ table.insert(result, "debug:egress_mirror:" .. accumulative_egress_mirror_overhe table.insert(result, "debug:shp_enabled:" .. tostring(shp_enabled)) table.insert(result, "debug:shp_size:" .. shp_size) table.insert(result, "debug:total port:" .. total_port .. " ports with 8 lanes:" .. port_count_8lanes) +table.insert(result, "debug:admin up port:" .. admin_up_port .. " admin up ports with 8 lanes:" .. admin_up_8lanes_port) return result diff --git a/cfgmgr/buffermgrd.cpp b/cfgmgr/buffermgrd.cpp index 07355c9dd9..05932a9e3c 100644 --- a/cfgmgr/buffermgrd.cpp +++ b/cfgmgr/buffermgrd.cpp @@ -12,6 +12,7 @@ #include #include "json.h" #include "json.hpp" +#include "warm_restart.h" using namespace std; using namespace swss; @@ -185,6 +186,9 @@ int main(int argc, char **argv) if (dynamicMode) { + WarmStart::initialize("buffermgrd", "swss"); + WarmStart::checkWarmStart("buffermgrd", "swss"); + vector buffer_table_connectors = { TableConnector(&cfgDb, CFG_PORT_TABLE_NAME), TableConnector(&cfgDb, CFG_PORT_CABLE_LEN_TABLE_NAME), diff --git a/cfgmgr/buffermgrdyn.cpp b/cfgmgr/buffermgrdyn.cpp index a1c64b7e5e..0888e9e6c6 100644 --- a/cfgmgr/buffermgrdyn.cpp +++ b/cfgmgr/buffermgrdyn.cpp @@ -30,26 +30,26 @@ using namespace swss; BufferMgrDynamic::BufferMgrDynamic(DBConnector *cfgDb, DBConnector *stateDb, DBConnector *applDb, const vector &tables, shared_ptr> gearboxInfo, shared_ptr> zeroProfilesInfo) : Orch(tables), m_platform(), + m_bufferDirections({BUFFER_INGRESS, BUFFER_EGRESS}), + m_bufferObjectNames({"priority group", "queue"}), + m_bufferDirectionNames({"ingress", "egress"}), m_applDb(applDb), - m_cfgPortTable(cfgDb, CFG_PORT_TABLE_NAME), - m_cfgCableLenTable(cfgDb, CFG_PORT_CABLE_LEN_TABLE_NAME), - m_cfgBufferProfileTable(cfgDb, CFG_BUFFER_PROFILE_TABLE_NAME), - m_cfgBufferPgTable(cfgDb, CFG_BUFFER_PG_TABLE_NAME), - m_cfgLosslessPgPoolTable(cfgDb, CFG_BUFFER_POOL_TABLE_NAME), + m_zeroProfilesLoaded(false), + m_supportRemoving(true), m_cfgDefaultLosslessBufferParam(cfgDb, CFG_DEFAULT_LOSSLESS_BUFFER_PARAMETER), m_applBufferPoolTable(applDb, APP_BUFFER_POOL_TABLE_NAME), m_applBufferProfileTable(applDb, APP_BUFFER_PROFILE_TABLE_NAME), - m_applBufferPgTable(applDb, APP_BUFFER_PG_TABLE_NAME), - m_applBufferQueueTable(applDb, APP_BUFFER_QUEUE_TABLE_NAME), - m_applBufferIngressProfileListTable(applDb, APP_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME), - m_applBufferEgressProfileListTable(applDb, APP_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME), + m_applBufferObjectTables({ProducerStateTable(applDb, APP_BUFFER_PG_TABLE_NAME), ProducerStateTable(applDb, APP_BUFFER_QUEUE_TABLE_NAME)}), + m_applBufferProfileListTables({ProducerStateTable(applDb, APP_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME), ProducerStateTable(applDb, APP_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME)}), m_statePortTable(stateDb, STATE_PORT_TABLE_NAME), m_stateBufferMaximumTable(stateDb, STATE_BUFFER_MAXIMUM_VALUE_TABLE), m_stateBufferPoolTable(stateDb, STATE_BUFFER_POOL_TABLE_NAME), m_stateBufferProfileTable(stateDb, STATE_BUFFER_PROFILE_TABLE_NAME), m_applPortTable(applDb, APP_PORT_TABLE_NAME), m_portInitDone(false), - m_firstTimeCalculateBufferPool(true), + m_bufferPoolReady(false), + m_bufferObjectsPending(true), + m_bufferCompletelyInitialized(false), m_mmuSizeNumber(0) { SWSS_LOG_ENTER(); @@ -57,6 +57,8 @@ BufferMgrDynamic::BufferMgrDynamic(DBConnector *cfgDb, DBConnector *stateDb, DBC // Initialize the handler map initTableHandlerMap(); parseGearboxInfo(gearboxInfo); + if (nullptr != zeroProfilesInfo) + m_zeroPoolAndProfileInfo = *zeroProfilesInfo; string platform = getenv("ASIC_VENDOR") ? getenv("ASIC_VENDOR") : ""; if (platform == "") @@ -118,6 +120,23 @@ BufferMgrDynamic::BufferMgrDynamic(DBConnector *cfgDb, DBConnector *stateDb, DBC { m_cfgDefaultLosslessBufferParam.hget(keys[0], "default_dynamic_th", m_defaultThreshold); } + + // m_waitApplyAdditionalZeroProfiles represents for how long applying additional zero profiles will be deferred + // after normal profiles and profiles for configured items have been applied + // For warm reboot, it is not deferred as the additional zero profiles have been in the APPL_DB + // In this case, they should be replayed as soon as possible + // For fast/cold reboot and other initialization flow, it is defered for 30 seconds. + // This is to accelerate the fast reboot converging time. + if (WarmStart::isWarmStart()) + { + m_waitApplyAdditionalZeroProfiles = 0; + WarmStart::setWarmStartState("buffermgrd", WarmStart::INITIALIZED); + } + else + { + m_waitApplyAdditionalZeroProfiles = 3; + WarmStart::setWarmStartState("buffermgrd", WarmStart::WSDISABLED); + } } void BufferMgrDynamic::parseGearboxInfo(shared_ptr> gearboxInfo) @@ -177,6 +196,214 @@ void BufferMgrDynamic::parseGearboxInfo(shared_ptr|: represents a zero buffer object, like a zero buffer pool or zero buffer profile + * All necessary fields of the object should be provided in the json file according to the type of the object. + * For the buffer profiles, if the buffer pools referenced are the normal pools, like {ingress|egress}_{lossless|lossy}_pool, + * the zero profile name will be stored in the referenced pool's "zero_profile_name" field for the purpose of + * - constructing the zero profile list or providing the zero profiles for PGs or queues + * - fetching the zero profile for a certain pool when applying the zero profile for configured items + * + * - control_fields: represents the ids required for reclaiming unused buffers, including: + * - pgs_to_apply_zero_profile, represents the PGs on which the zero profiles will be applied for reclaiming unused buffers + * If it is not provided, zero profiles will be applied on all PGs. + * - ingress_zero_profile, represents the zero buffer profille which will be applied on PGs for reclaiming unused buffer. + * Typically, it is provided along with pgs_to_apply_zero_profile. + * In case pgs_to_apply_zero_profile is defined but ingress_zero_profile is not provided, the first zero profile on the ingress side in json file will be used. + * - queues_to_apply_zero_profile, represents the queues on which the zero profiles will be applied + * If it is not provided, zero profiles will be applied on all queues. + * - egress_zero_profile, represents the zero buffer profille which will be applied on queues for reclaiming unused buffer. + * Typically, it is provided along with queues_to_apply_zero_profile. + * In case queues_to_apply_zero_profile is defined but egress_zero_profile is not provided, the first zero profile on the egress side in json file will be used. + * - support_removing_buffer_items, represents whether the buffer items are supported to be removed. + * The number of queues and PGs are pushed into BUFFER_MAX_PARAM table in STATE_DB at the beginning of ports orchagent + * and will be learnt by buffer manager when it's starting. + */ +void BufferMgrDynamic::loadZeroPoolAndProfiles() +{ + for (auto &kfv : m_zeroPoolAndProfileInfo) + { + auto &table_key = kfvKey(kfv); + + if (table_key == "control_fields") + { + auto &fvs = kfvFieldsValues(kfv); + for (auto &fv : fvs) + { + if (fvField(fv) == "pgs_to_apply_zero_profile") + { + m_bufferObjectIdsToZero[BUFFER_PG] = fvValue(fv); + } + else if (fvField(fv) == "ingress_zero_profile") + { + m_bufferZeroProfileName[BUFFER_PG] = fvValue(fv); + } + else if (fvField(fv) == "queues_to_apply_zero_profile") + { + m_bufferObjectIdsToZero[BUFFER_QUEUE] = fvValue(fv); + } + else if (fvField(fv) == "egress_zero_profile") + { + m_bufferZeroProfileName[BUFFER_QUEUE] = fvValue(fv); + } + else if (fvField(fv) == "support_removing_buffer_items") + { + m_supportRemoving = (fvValue(fv) == "yes"); + } + } + + continue; + } + + auto const &table = parseObjectNameFromKey(table_key, 0); + auto const &key = parseObjectNameFromKey(table_key, 1); + + if (table.empty() || key.empty()) + { + SWSS_LOG_ERROR("Invalid format of key %s for zero profile info, won't initialize it", + kfvKey(kfv).c_str()); + return; + } + + if (table == APP_BUFFER_POOL_TABLE_NAME) + { + m_applBufferPoolTable.set(key, kfvFieldsValues(kfv)); + m_stateBufferPoolTable.set(key, kfvFieldsValues(kfv)); + SWSS_LOG_NOTICE("Loaded zero buffer pool %s", key.c_str()); + m_zeroPoolNameSet.insert(key); + } + else if (table == APP_BUFFER_PROFILE_TABLE_NAME) + { + auto &fvs = kfvFieldsValues(kfv); + bool poolNotFound = false; + for (auto &fv : fvs) + { + if (fvField(fv) == "pool") + { + const auto &poolName = fvValue(fv); + auto poolSearchRef = m_bufferPoolLookup.find(poolName); + if (poolSearchRef != m_bufferPoolLookup.end()) + { + auto &poolObj = poolSearchRef->second; + if (poolObj.zero_profile_name.empty()) + { + poolObj.zero_profile_name = key; + if (m_bufferZeroProfileName[poolObj.direction].empty()) + m_bufferZeroProfileName[poolObj.direction] = poolObj.zero_profile_name; + } + else + { + SWSS_LOG_ERROR("Multiple zero profiles (%s, %s) detected for pool %s, takes the former and ignores the latter", + poolObj.zero_profile_name.c_str(), + key.c_str(), + fvValue(fv).c_str()); + } + } + else if (m_zeroPoolNameSet.find(poolName) == m_zeroPoolNameSet.end()) + { + SWSS_LOG_WARN("Profile %s is not loaded as the referenced pool %s is not defined", + key.c_str(), + fvValue(fv).c_str()); + poolNotFound = true; + break; + } + + m_zeroProfiles.emplace_back(key, poolName); + } + } + if (poolNotFound) + { + continue; + } + m_applBufferProfileTable.set(key, fvs); + m_stateBufferProfileTable.set(key, fvs); + SWSS_LOG_NOTICE("Loaded zero buffer profile %s", key.c_str()); + } + else + { + SWSS_LOG_ERROR("Unknown keys %s with zero table name %s isn't loaded to APPL_DB", key.c_str(), table.c_str()); + continue; + } + } + + // Consistency checking + // 1. For any buffer pool, if there is no zero profile provided, removing buffer items must be supported + // because the reserved buffer will be reclaimed by removing buffer items + // 2. If pgs_to_apply_zero_profile or queues_to_apply_zero_profile is provided, removing buffer items must be supported + // because the PGs or queues that are not in the ID list will be removed + bool noReclaiming = false; + if (!m_supportRemoving) + { + for (auto &poolRef: m_bufferPoolLookup) + { + if (poolRef.second.zero_profile_name.empty()) + { + // For any buffer pool, zero profile must be provided + SWSS_LOG_ERROR("Zero profile is not provided for pool %s while removing buffer items is not supported, reserved buffer can not be reclaimed correctly", poolRef.first.c_str()); + noReclaiming = true; + } + } + + if (!m_bufferObjectIdsToZero[BUFFER_PG].empty() || !m_bufferObjectIdsToZero[BUFFER_QUEUE].empty()) + { + SWSS_LOG_ERROR("Unified IDs of queues or priority groups specified while removing buffer items is not supported, reserved buffer can not be reclaimed correctly"); + noReclaiming = true; + } + } + + if (noReclaiming) + { + unloadZeroPoolAndProfiles(); + m_zeroPoolAndProfileInfo.clear(); + } + else + { + m_zeroProfilesLoaded = true; + } +} + +void BufferMgrDynamic::unloadZeroPoolAndProfiles() +{ + for (auto &zeroProfile : m_zeroProfiles) + { + auto &zeroProfileName = zeroProfile.first; + auto &poolReferenced = zeroProfile.second; + + auto poolSearchRef = m_bufferPoolLookup.find(poolReferenced); + if (poolSearchRef != m_bufferPoolLookup.end()) + { + auto &poolObj = poolSearchRef->second; + poolObj.zero_profile_name.clear(); + } + m_applBufferProfileTable.del(zeroProfileName); + m_stateBufferProfileTable.del(zeroProfileName); + SWSS_LOG_NOTICE("Unloaded zero buffer profile %s", zeroProfileName.c_str()); + } + + m_zeroProfiles.clear(); + + for (auto &zeroPool : m_zeroPoolNameSet) + { + m_applBufferPoolTable.del(zeroPool); + m_stateBufferPoolTable.del(zeroPool); + SWSS_LOG_NOTICE("Unloaded zero buffer pool %s", zeroPool.c_str()); + } + + m_zeroPoolNameSet.clear(); + + m_zeroProfilesLoaded = false; +} + void BufferMgrDynamic::initTableHandlerMap() { m_bufferTableHandlerMap.insert(buffer_handler_pair(STATE_BUFFER_MAXIMUM_VALUE_TABLE, &BufferMgrDynamic::handleBufferMaxParam)); @@ -190,6 +417,11 @@ void BufferMgrDynamic::initTableHandlerMap() m_bufferTableHandlerMap.insert(buffer_handler_pair(CFG_PORT_TABLE_NAME, &BufferMgrDynamic::handlePortTable)); m_bufferTableHandlerMap.insert(buffer_handler_pair(CFG_PORT_CABLE_LEN_TABLE_NAME, &BufferMgrDynamic::handleCableLenTable)); m_bufferTableHandlerMap.insert(buffer_handler_pair(STATE_PORT_TABLE_NAME, &BufferMgrDynamic::handlePortStateTable)); + + m_bufferSingleItemHandlerMap.insert(buffer_single_item_handler_pair(CFG_BUFFER_QUEUE_TABLE_NAME, &BufferMgrDynamic::handleSingleBufferQueueEntry)); + m_bufferSingleItemHandlerMap.insert(buffer_single_item_handler_pair(CFG_BUFFER_PG_TABLE_NAME, &BufferMgrDynamic::handleSingleBufferPgEntry)); + m_bufferSingleItemHandlerMap.insert(buffer_single_item_handler_pair(CFG_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME, &BufferMgrDynamic::handleSingleBufferPortIngressProfileListEntry)); + m_bufferSingleItemHandlerMap.insert(buffer_single_item_handler_pair(CFG_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME, &BufferMgrDynamic::handleSingleBufferPortEgressProfileListEntry)); } // APIs to handle variant kinds of keys @@ -397,6 +629,34 @@ void BufferMgrDynamic::recalculateSharedBufferPool() vector keys = {}; vector argv = {}; + if (!m_bufferPoolReady) + { + // In case all buffer pools have a configured size, + // The m_bufferPoolReady will be set to true + // It can happen on vs. + bool hasDynamicSizePool = false, hasBufferPool = false; + for (auto &poolRef : m_bufferPoolLookup) + { + hasBufferPool = true; + if (poolRef.second.dynamic_size) + { + hasDynamicSizePool = true; + } + } + + if (!hasBufferPool) + { + SWSS_LOG_INFO("No shared buffer pool configured, skip calculating shared buffer pool size"); + return; + } + + if (hasBufferPool && !hasDynamicSizePool) + { + m_bufferPoolReady = true; + SWSS_LOG_NOTICE("No pool requires calculating size dynamically. All buffer pools are ready"); + } + } + auto ret = runRedisScript(*m_applDb, m_bufferpoolSha, keys, argv); // The format of the result: @@ -513,6 +773,9 @@ void BufferMgrDynamic::recalculateSharedBufferPool() { SWSS_LOG_WARN("Lua scripts for buffer calculation were not executed successfully"); } + + if (!m_bufferPoolReady) + m_bufferPoolReady = true; } void BufferMgrDynamic::checkSharedBufferPoolSize(bool force_update_during_initialization = false) @@ -535,7 +798,7 @@ void BufferMgrDynamic::checkSharedBufferPoolSize(bool force_update_during_initia } else { - if (m_firstTimeCalculateBufferPool) + if (!m_bufferPoolReady) { // It's something like a placeholder especially for warm reboot flow // without all buffer pools created, buffer profiles are unable to be created, @@ -545,7 +808,6 @@ void BufferMgrDynamic::checkSharedBufferPoolSize(bool force_update_during_initia // until portInitDone // Eventually, the correct values will pushed to APPL_DB and then ASIC_DB recalculateSharedBufferPool(); - m_firstTimeCalculateBufferPool = false; SWSS_LOG_NOTICE("Buffer pool update deferred because port is still under initialization, start polling timer"); } @@ -562,10 +824,7 @@ void BufferMgrDynamic::updateBufferPoolToDb(const string &name, const buffer_poo { vector fvVector; - if (pool.ingress) - fvVector.emplace_back("type", "ingress"); - else - fvVector.emplace_back("type", "egress"); + fvVector.emplace_back("type", m_bufferDirectionNames[pool.direction]); if (!pool.xoff.empty()) fvVector.emplace_back("xoff", pool.xoff); @@ -581,19 +840,29 @@ void BufferMgrDynamic::updateBufferPoolToDb(const string &name, const buffer_poo void BufferMgrDynamic::updateBufferProfileToDb(const string &name, const buffer_profile_t &profile) { + if (!m_bufferPoolReady) + { + SWSS_LOG_NOTICE("Buffer pools are not ready when configuring buffer profile %s, pending", name.c_str()); + m_bufferObjectsPending = true; + return; + } + vector fvVector; string mode = getPgPoolMode(); // profile threshold field name mode += "_th"; - fvVector.emplace_back("xon", profile.xon); - if (!profile.xon_offset.empty()) { - fvVector.emplace_back("xon_offset", profile.xon_offset); + if (profile.lossless) + { + fvVector.emplace_back("xon", profile.xon); + if (!profile.xon_offset.empty()) { + fvVector.emplace_back("xon_offset", profile.xon_offset); + } + fvVector.emplace_back("xoff", profile.xoff); } - fvVector.emplace_back("xoff", profile.xoff); fvVector.emplace_back("size", profile.size); - fvVector.emplace_back("pool", INGRESS_LOSSLESS_PG_POOL_NAME); + fvVector.emplace_back("pool", profile.pool_name); fvVector.emplace_back(mode, profile.threshold); m_applBufferProfileTable.set(name, fvVector); @@ -601,22 +870,49 @@ void BufferMgrDynamic::updateBufferProfileToDb(const string &name, const buffer_ } // Database operation -// Set/remove BUFFER_PG table entry -void BufferMgrDynamic::updateBufferPgToDb(const string &key, const string &profile, bool add) +// Set/remove BUFFER_PG or BUFFER_QUEUE table entry +void BufferMgrDynamic::updateBufferObjectToDb(const string &key, const string &profile, bool add, buffer_direction_t dir=BUFFER_PG) { + auto &table = m_applBufferObjectTables[dir]; + const auto &objType = m_bufferObjectNames[dir]; + if (add) { - vector fvVector; + if (!m_bufferPoolReady) + { + SWSS_LOG_NOTICE("Buffer pools are not ready when configuring buffer %s %s, pending", objType.c_str(), key.c_str()); + m_bufferObjectsPending = true; + return; + } - fvVector.clear(); + vector fvVector; + fvVector.emplace_back(buffer_profile_field_name, profile); - fvVector.push_back(make_pair("profile", profile)); - m_applBufferPgTable.set(key, fvVector); + table.set(key, fvVector); } else { - m_applBufferPgTable.del(key); + table.del(key); + } +} + +void BufferMgrDynamic::updateBufferObjectListToDb(const string &key, const string &profileList, buffer_direction_t dir) +{ + auto &table = m_applBufferProfileListTables[dir]; + const auto &direction = m_bufferDirectionNames[dir]; + + if (!m_bufferPoolReady) + { + SWSS_LOG_NOTICE("Buffer pools are not ready when configuring buffer %s profile list %s, pending", direction.c_str(), key.c_str()); + m_bufferObjectsPending = true; + return; } + + vector fvVector; + + fvVector.emplace_back(buffer_profile_list_field_name, profileList); + + table.set(key, fvVector); } // We have to check the headroom ahead of applying them @@ -645,6 +941,7 @@ task_process_status BufferMgrDynamic::allocateProfile(const string &speed, const profile.port_mtu = mtu; profile.gearbox_model = gearbox_model; profile.lane_count = lane_count; + profile.pool_name = INGRESS_LOSSLESS_PG_POOL_NAME; // Call vendor-specific lua plugin to calculate the xon, xoff, xon_offset, size // Pay attention, the threshold can contain valid value @@ -785,30 +1082,268 @@ bool BufferMgrDynamic::isHeadroomResourceValid(const string &port, const buffer_ return result; } -task_process_status BufferMgrDynamic::removeAllPgsFromPort(const string &port) +/* + * The following functions are defnied for reclaiming reserved buffers + * - constructZeroProfileListFromNormalProfileList + * - reclaimReservedBufferForPort + * - removeSupportedButNotConfiguredItemsOnPort + * - applyNormalBufferObjectsOnPort, handling queues and profile lists. + * The priority groups are handle by refreshPgsForPort + * + * The overall logic of reclaiming reserved buffers is handled in handlePortTable: + * Shutdown flow (to reclaim unused buffer): + * 1. reclaimReservedBufferForPort + * Start up flow (to reserved buffer for the port) + * 1. removeSupportedButNotConfiguredItemsOnPort + * 2. applyNormalBufferObjectsOnPort + * 3. refreshPgsForPort + */ + +/* + * constructZeroProfileListFromNormalProfileList + * Tool function for constructing zero profile list from normal profile list. + * There should be one buffer profile for each buffer pool on each side (ingress/egress) in the profile_list + * in BUFFER_PORT_INGRESS_PROFILE_LIST and BUFFER_PORT_EGRESS_PROFILE_LIST table. + * For example, on ingress side, there are ingress_lossless_profile and ingress_lossy_profile in the profile list + * for buffer pools ingress_lossless_pool and ingress_lossy_pool respectively. + * There should be one zero profile for each pool in the profile_list when reclaiming buffer on a port as well. + * The arguments is the normal profile list and the port. + * The logic is: + * For each buffer profile in the list + * 1. Fetch the buffer pool referenced by the profile + * 2. Fetch the zero buffer profile of the buffer pool. The pool is skipped in case there is no zero buffer profile defined for it. + * 3. Construct the zero buffer profile by joining all zero buffer profiles, separating by "," + */ +string BufferMgrDynamic::constructZeroProfileListFromNormalProfileList(const string &normalProfileList, const string &port) { - buffer_pg_lookup_t &portPgs = m_portPgLookup[port]; + string zeroProfileNameList; + + auto profileRefs = tokenize(normalProfileList, ','); + for (auto &profileName : profileRefs) + { + const auto &zeroProfile = fetchZeroProfileFromNormalProfile(profileName); + if (!zeroProfile.empty()) + { + zeroProfileNameList += zeroProfile + ","; + } + else + { + SWSS_LOG_WARN("Unable to apply zero profile for profile %s on port %s because no zero profile configured for the pool", + profileName.c_str(), + port.c_str()); + } + } + + if (!zeroProfileNameList.empty()) + zeroProfileNameList.pop_back(); + + return zeroProfileNameList; +} + +/* + * removeSupportedButNotConfiguredItemsOnPort + * Remove the supported-but-not-configured buffer items from the port. + * They were applied when the port was shut down. + * Called when a port is started up. + * + * Arguments: + * portInfo, represents the port information of the port + * portName, represents the name of the port + * Return: + * None. + */ +void BufferMgrDynamic::removeSupportedButNotConfiguredItemsOnPort(port_info_t &portInfo, const string &portName) +{ + auto const &portPrefix = portName + delimiter; + + for (auto dir : m_bufferDirections) + { + if (!portInfo.supported_but_not_configured_buffer_objects[dir].empty()) + { + for (auto &it: portInfo.supported_but_not_configured_buffer_objects[dir]) + { + m_applBufferObjectTables[dir].del(portPrefix + it); + } + portInfo.supported_but_not_configured_buffer_objects[dir].clear(); + } + } +} + +/* + * applyNormalBufferObjectsOnPort + * Apply normal buffer profiles on buffer queues, and buffer profile lists. + * The buffer priority group will be handled by refreshPgsForPort + * Called when a port is started up. + * + * Arguments: + * port, represents the name of the port + * Return: + * None. + */ +void BufferMgrDynamic::applyNormalBufferObjectsOnPort(const string &port) +{ + vector fvVector; + auto &portQueues = m_portQueueLookup[port]; + + for (auto &queue : portQueues) + { + SWSS_LOG_NOTICE("Profile %s has been applied on queue %s", queue.second.running_profile_name.c_str(), queue.first.c_str()); + updateBufferObjectToDb(queue.first, queue.second.running_profile_name, true, BUFFER_QUEUE); + } + + for (auto dir : m_bufferDirections) + { + auto &profileList = m_portProfileListLookups[dir][port]; + if (!profileList.empty()) + { + fvVector.emplace_back(buffer_profile_list_field_name, profileList); + m_applBufferProfileListTables[dir].set(port, fvVector); + fvVector.clear(); + } + } +} + +string &BufferMgrDynamic::fetchZeroProfileFromNormalProfile(const string &profile) +{ + auto const &profileRef = m_bufferProfileLookup.find(profile); + auto const &poolName = (profileRef == m_bufferProfileLookup.end() ? INGRESS_LOSSLESS_PG_POOL_NAME : profileRef->second.pool_name); + auto &poolInfo = m_bufferPoolLookup[poolName]; + + return poolInfo.zero_profile_name; +} + +bool BufferMgrDynamic::isReadyToReclaimBufferOnPort(const string &port) +{ + auto &portInfo = m_portInfoLookup[port]; + + for (auto dir : m_bufferDirections) + { + if (m_bufferObjectIdsToZero[dir].empty() && 0 == portInfo.maximum_buffer_objects[dir]) + { + SWSS_LOG_NOTICE("Maximum supported priority groups and queues have not been populated in STATE_DB for port %s, reclaiming reserved buffer deferred", port.c_str()); + return false; + } + } + + return true; +} + +/* + * reclaimReservedBufferForPort + * Called when a port is admin down + * Reserved buffer is reclaimed by applying zero profiles on the buffer item or just removing them. + * + * Parameters: + * - port: the name of the port + * Return: + * - task_process_status + * + * Purpose: + * 1. Reclaim all the priority groups and queues of the port from APPL_DB + * 2. Remove all the buffer profiles that are dynamically calculated and no longer referenced + * 3. Reclaim the ingress/egress profile list of the port form APPL_DB + * + * The flow: + * 1. Load the zero pools and profiles into APPL_DB if they have been provided but not loaded. + * 2. Handle priority group, and queues: + * Two modes for them to be reclaimed: + * - Apply zero buffer profiles on all configured and supported-but-not-configured items. + * Eg. + * - 16 queues supported, 0-2, 5-6 are configured as lossy and 3-4 are configured as lossless + * - Zero profiles will be applied on + * - 0-2, 5-6, 7-15: egress_lossy_zero_profile + * - 3-4: egress_lossless_zero_profile + * - Apply zero buffer profiles on items specified by vendor and remove all other items. + * Eg. + * - 8 PGs supported, 0 is configured as lossy and 3-4 are configured as lossless + * - PGs to be applied zero profile on is 0, 3-4 will be removed + * Queues and priority groups share the common logic. + * 3. Handle ingress and egress buffer profile list, which shared the common logic: + * - Construct the zero buffer profile list from the normal buffer profile list. + * - Apply the zero buffer profile list on the port. + */ +template task_process_status BufferMgrDynamic::reclaimReservedBufferForPort(const string &port, T &portObjectLookup, buffer_direction_t dir) +{ + auto &portBufferObjects = portObjectLookup[port]; + auto &portInfo = m_portInfoLookup[port]; set profilesToBeReleased; + const string &portKeyPrefix = port + delimiter; + const string &objectTypeName = m_bufferObjectNames[dir]; + + if (!m_zeroPoolAndProfileInfo.empty() && (!m_zeroProfilesLoaded)) + { + loadZeroPoolAndProfiles(); + } - SWSS_LOG_INFO("Removing all PGs from port %s", port.c_str()); + bool applyZeroProfileOnSpecifiedObjects = !m_bufferObjectIdsToZero[dir].empty(); - for (auto it = portPgs.begin(); it != portPgs.end(); ++it) + SWSS_LOG_NOTICE("Reclaiming buffer reserved for all %s(s) from port %s", objectTypeName.c_str(), port.c_str()); + + unsigned long objectsMap = (1 << portInfo.maximum_buffer_objects[dir]) - 1; + for (auto &it : portBufferObjects) { - auto &key = it->first; - auto &portPg = it->second; + auto &key = it.first; + auto &portObject = it.second; + const string &runningProfile = portObject.running_profile_name; + const string &zeroProfile = fetchZeroProfileFromNormalProfile(runningProfile); - SWSS_LOG_INFO("Removing PG %s from port %s", key.c_str(), port.c_str()); + SWSS_LOG_INFO("Reclaiming buffer reserved for %s %s", objectTypeName.c_str(), key.c_str()); - if (portPg.running_profile_name.empty()) - continue; + if (!applyZeroProfileOnSpecifiedObjects) + { + // Apply zero profiles on each configured buffer objects and supported-but-not-configured buffer objects + // Fetch the zero profile of the pool referenced by the configured profile + if (!zeroProfile.empty()) + { + SWSS_LOG_INFO("Applying zero profile %s on %s %s", zeroProfile.c_str(), objectTypeName.c_str(), key.c_str()); + updateBufferObjectToDb(key, zeroProfile, true, dir); + } + else + { + // No zero profile defined for the pool. Reclaim buffer by removing the buffer object + SWSS_LOG_INFO("Zero profile is not defined for profile %s, reclaim reserved buffer by removing %s %s", runningProfile.c_str(), objectTypeName.c_str(), key.c_str()); + updateBufferObjectToDb(key, runningProfile, false, dir); + } + + objectsMap ^= generateBitMapFromIdsStr(parseObjectNameFromKey(key, 1)); + } + else + { + // Apply the zero profile on specified buffer objects only. + // Remove each buffer object first + // In this case, removing must be supported (checked when the json was loaded) + SWSS_LOG_INFO("Zero profile will be applied on %s. Remove %s %s first", m_bufferObjectIdsToZero[dir].c_str(), objectTypeName.c_str(), key.c_str()); + updateBufferObjectToDb(key, runningProfile, false, dir); + } - m_bufferProfileLookup[portPg.running_profile_name].port_pgs.erase(key); - updateBufferPgToDb(key, portPg.running_profile_name, false); - profilesToBeReleased.insert(portPg.running_profile_name); - portPg.running_profile_name.clear(); + if (dir == BUFFER_PG && !runningProfile.empty()) + { + m_bufferProfileLookup[runningProfile].port_pgs.erase(key); + profilesToBeReleased.insert(runningProfile); + portObject.running_profile_name.clear(); + } } - checkSharedBufferPoolSize(); + if (applyZeroProfileOnSpecifiedObjects) + { + updateBufferObjectToDb(portKeyPrefix + m_bufferObjectIdsToZero[dir], m_bufferZeroProfileName[dir], true, dir); + } + else if (!m_bufferZeroProfileName[dir].empty()) + { + // Apply zero profiles on supported-but-not-configured buffer objects + portInfo.supported_but_not_configured_buffer_objects[dir] = generateIdListFromMap(objectsMap, portInfo.maximum_buffer_objects[dir]); + if (m_waitApplyAdditionalZeroProfiles == 0) + { + for(auto &it: portInfo.supported_but_not_configured_buffer_objects[dir]) + { + updateBufferObjectToDb(portKeyPrefix + it, m_bufferZeroProfileName[dir], true, dir); + } + } + else + { + m_pendingSupportedButNotConfiguredPorts[dir].insert(port); + } + } // Remove the old profile which is probably not referenced anymore. if (!profilesToBeReleased.empty()) @@ -819,6 +1354,17 @@ task_process_status BufferMgrDynamic::removeAllPgsFromPort(const string &port) } } + vector fvVector; + + SWSS_LOG_NOTICE("Reclaiming buffer reserved for ingress profile list from port %s", port.c_str()); + const auto &profileList = m_portProfileListLookups[dir][port]; + if (!profileList.empty()) + { + const string &zeroIngressProfileNameList = constructZeroProfileListFromNormalProfileList(profileList, port); + fvVector.emplace_back(buffer_profile_list_field_name, zeroIngressProfileNameList); + m_applBufferProfileListTables[dir].set(port, fvVector); + } + return task_process_status::task_success; } @@ -857,6 +1403,14 @@ task_process_status BufferMgrDynamic::refreshPgsForPort(const string &port, cons return task_process_status::task_success; } + if (!m_bufferPoolReady) + { + SWSS_LOG_INFO("Nothing to be done since the buffer pool is not ready"); + return task_process_status::task_success; + } + + SWSS_LOG_NOTICE("Refresh priority groups for port %s", port.c_str()); + // Iterate all the lossless PGs configured on this port for (auto it = portPgs.begin(); it != portPgs.end(); ++it) { @@ -930,7 +1484,7 @@ task_process_status BufferMgrDynamic::refreshPgsForPort(const string &port, cons } // appl_db Database operation: set item BUFFER_PG|| - updateBufferPgToDb(key, newProfile, true); + updateBufferObjectToDb(key, newProfile, true); isHeadroomUpdated = true; } @@ -1097,12 +1651,6 @@ task_process_status BufferMgrDynamic::doUpdatePgTask(const string &pg_key, const case PORT_ADMIN_DOWN: SWSS_LOG_NOTICE("Skip setting BUFFER_PG for %s because the port is administratively down", port.c_str()); - if (!m_portInitDone) - { - // In case the port is admin down during initialization, the PG will be removed from the port, - // which effectively notifies bufferOrch to add the item to the m_ready_list - m_applBufferPgTable.del(pg_key); - } break; default: @@ -1125,7 +1673,7 @@ task_process_status BufferMgrDynamic::doRemovePgTask(const string &pg_key, const // Remove the PG from APPL_DB string null_str(""); - updateBufferPgToDb(pg_key, null_str, false); + updateBufferObjectToDb(pg_key, null_str, false); SWSS_LOG_NOTICE("Remove BUFFER_PG %s (profile %s, %s)", pg_key.c_str(), bufferPg.running_profile_name.c_str(), bufferPg.configured_profile_name.c_str()); @@ -1224,32 +1772,94 @@ task_process_status BufferMgrDynamic::doUpdateBufferProfileForSize(buffer_profil return task_process_status::task_success; } +/* + * handleBufferMaxParam, handles the BUFFER_MAX_PARAMETER table which contains some thresholds related to buffer. + * The available fields depend on the key of the item: + * 1. "global", available keys: + * - mmu_size, represents the maximum buffer size of the chip + * 2. "", available keys: + * - max_priority_groups, represents the maximum number of priority groups of the port. + * It is pushed into STATE_DB by ports orchagent when it starts and will be used to generate the default priority groups to reclaim. + * When reserved buffer is reclaimed for a port, and no priority group IDs specified in the "control_fields" in json file, + * the default priority groups will be used to apply zero buffer profiles on all priority groups. + * - max_queues, represents the maximum number of queues of the port. + * It is used in the same way as max_priority_groups. + * - max_headroom_size, represents the maximum headroom size of the port. + * It is used for checking whether the accumulative headroom of the port exceeds the port's threshold + * before applying a new priority group on a port or changing an existing buffer profile. + * It is referenced by lua plugin "check headroom size" only and will not be handled in this function. + */ task_process_status BufferMgrDynamic::handleBufferMaxParam(KeyOpFieldsValuesTuple &tuple) { - string op = kfvOp(tuple); + string &op = kfvOp(tuple); + string &key = kfvKey(tuple); if (op == SET_COMMAND) { - for (auto i : kfvFieldsValues(tuple)) + if (key != "global") { - if (fvField(i) == "mmu_size") + const auto &portSearchRef = m_portInfoLookup.find(key); + if (portSearchRef == m_portInfoLookup.end()) + { + SWSS_LOG_INFO("BUFFER_MAX_PARAM: Port %s is not configured, need retry", key.c_str()); + return task_process_status::task_need_retry; + } + + auto &portInfo = portSearchRef->second; + for (auto i : kfvFieldsValues(tuple)) { - m_mmuSize = fvValue(i); - if (!m_mmuSize.empty()) + auto &value = fvValue(i); + if (fvField(i) == "max_priority_groups") { - m_mmuSizeNumber = atol(m_mmuSize.c_str()); + auto pgCount = atol(value.c_str()); + if (pgCount <= 0) + { + SWSS_LOG_ERROR("BUFFER_MAX_PARAM: Invaild priority group count %s of port %s", value.c_str(), key.c_str()); + return task_process_status::task_failed; + } + + SWSS_LOG_INFO("BUFFER_MAX_PARAM: Got port %s's max priority group %s", key.c_str(), value.c_str()); + + portInfo.maximum_buffer_objects[BUFFER_PG] = (sai_uint32_t)pgCount; } - if (0 == m_mmuSizeNumber) + else if (fvField(i) == "max_queues") { - SWSS_LOG_ERROR("BUFFER_MAX_PARAM: Got invalid mmu size %s", m_mmuSize.c_str()); - return task_process_status::task_failed; + auto queueCount = atol(value.c_str()); + if (queueCount <= 0) + { + SWSS_LOG_ERROR("BUFFER_MAX_PARAM: Invaild queue count %s of port %s", value.c_str(), key.c_str()); + return task_process_status::task_failed; + } + + SWSS_LOG_INFO("BUFFER_MAX_PARAM: Got port %s's max queue %s", key.c_str(), value.c_str()); + + portInfo.maximum_buffer_objects[BUFFER_QUEUE] = (sai_uint32_t)queueCount; } - SWSS_LOG_DEBUG("Handling Default Lossless Buffer Param table field mmu_size %s", m_mmuSize.c_str()); } } - } - - return task_process_status::task_success; + else + { + for (auto i : kfvFieldsValues(tuple)) + { + if (fvField(i) == "mmu_size") + { + m_mmuSize = fvValue(i); + if (!m_mmuSize.empty()) + { + m_mmuSizeNumber = atol(m_mmuSize.c_str()); + } + if (0 == m_mmuSizeNumber) + { + SWSS_LOG_ERROR("BUFFER_MAX_PARAM: Got invalid mmu size %s", m_mmuSize.c_str()); + return task_process_status::task_failed; + } + SWSS_LOG_DEBUG("Handling Default Lossless Buffer Param table field mmu_size %s", m_mmuSize.c_str()); + } + } + } + } + + return task_process_status::task_success; } task_process_status BufferMgrDynamic::handleDefaultLossLessBufferParam(KeyOpFieldsValuesTuple &tuple) @@ -1437,6 +2047,7 @@ task_process_status BufferMgrDynamic::handlePortTable(KeyOpFieldsValuesTuple &tu string op = kfvOp(tuple); bool effective_speed_updated = false, mtu_updated = false, admin_status_updated = false, admin_up = false; bool need_check_speed = false; + bool first_time_create = (m_portInfoLookup.find(port) == m_portInfoLookup.end()); SWSS_LOG_DEBUG("Processing command:%s PORT table key %s", op.c_str(), port.c_str()); @@ -1521,7 +2132,7 @@ task_process_status BufferMgrDynamic::handlePortTable(KeyOpFieldsValuesTuple &tu string &mtu = portInfo.mtu; string &effective_speed = portInfo.effective_speed; - bool need_refresh_all_pgs = false, need_remove_all_pgs = false; + bool need_refresh_all_buffer_objects = false, need_handle_admin_down = false, was_admin_down = false; if (effective_speed_updated || mtu_updated) { @@ -1539,11 +2150,11 @@ task_process_status BufferMgrDynamic::handlePortTable(KeyOpFieldsValuesTuple &tu // It's the same case as that in handleCableLenTable mtu = DEFAULT_MTU_STR; } - need_refresh_all_pgs = true; + need_refresh_all_buffer_objects = true; break; case PORT_READY: - need_refresh_all_pgs = true; + need_refresh_all_buffer_objects = true; break; case PORT_ADMIN_DOWN: @@ -1575,28 +2186,70 @@ task_process_status BufferMgrDynamic::handlePortTable(KeyOpFieldsValuesTuple &tu else portInfo.state = PORT_INITIALIZING; - need_refresh_all_pgs = true; + need_refresh_all_buffer_objects = true; + was_admin_down = true; } else { portInfo.state = PORT_ADMIN_DOWN; - need_remove_all_pgs = true; + need_handle_admin_down = true; } SWSS_LOG_INFO("Recalculate shared buffer pool size due to port %s's admin_status updated to %s", port.c_str(), (admin_up ? "up" : "down")); } + else if (first_time_create) + { + // The port is initialized as PORT_ADMIN_DOWN state. + // The admin status not updated after port created means the port is still in admin down state + // We need to apply zero buffers accordingly + need_handle_admin_down = true; + } - // In case both need_remove_all_pgs and need_refresh_all_pgs are true, the need_remove_all_pgs will take effect. + // In case both need_handle_admin_down and need_refresh_all_buffer_objects are true, the need_handle_admin_down will take effect. // This can happen when both effective speed (or mtu) is changed and the admin_status is down. // In this case, we just need record the new effective speed (or mtu) but don't need to refresh all PGs on the port since the port is administratively down - if (need_remove_all_pgs) + if (need_handle_admin_down) { - task_status = removeAllPgsFromPort(port); + m_adminDownPorts.insert(port); + + if (!m_bufferPoolReady) + { + // Applying zero profiles requires all the buffer pools referenced by zero profiles to be ready. + // In case buffer pools haven't been ready yet, the procedure has to be deferred. + m_pendingApplyZeroProfilePorts.insert(port); + SWSS_LOG_NOTICE("Admin-down port %s is not handled for now because buffer pools are not configured yet", port.c_str()); + } + else + { + if (isReadyToReclaimBufferOnPort(port)) + { + reclaimReservedBufferForPort(port, m_portPgLookup, BUFFER_PG); + reclaimReservedBufferForPort(port, m_portQueueLookup, BUFFER_QUEUE); + checkSharedBufferPoolSize(); + } + else + { + m_pendingApplyZeroProfilePorts.insert(port); + SWSS_LOG_NOTICE("Admin-down port %s is not handled for now because maximum numbers of queues and PGs have not been populated into STATE_DB", port.c_str()); + // Return task_success as the port has been inserted into m_pendingApplyZeroProfilePorts + } + } + + task_status = task_process_status::task_success; } - else if (need_refresh_all_pgs) + else if (need_refresh_all_buffer_objects) { + if (was_admin_down) + { + removeSupportedButNotConfiguredItemsOnPort(portInfo, port); + applyNormalBufferObjectsOnPort(port); + m_adminDownPorts.erase(port); + m_pendingApplyZeroProfilePorts.erase(port); + if (m_adminDownPorts.empty()) + unloadZeroPoolAndProfiles(); + } task_status = refreshPgsForPort(port, portInfo.effective_speed, portInfo.cable_length, portInfo.mtu); } } @@ -1641,7 +2294,10 @@ task_process_status BufferMgrDynamic::handleBufferPoolTable(KeyOpFieldsValuesTup } else if (field == buffer_pool_type_field_name) { - bufferPool.ingress = (value == buffer_value_ingress); + if (value == buffer_value_ingress) + bufferPool.direction = BUFFER_INGRESS; + else + bufferPool.direction = BUFFER_EGRESS; } fvVector.emplace_back(field, value); SWSS_LOG_INFO("Inserting BUFFER_POOL table field %s value %s", field.c_str(), value.c_str()); @@ -1708,7 +2364,6 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues SWSS_LOG_ENTER(); string profileName = kfvKey(tuple); string op = kfvOp(tuple); - vector fvVector; SWSS_LOG_DEBUG("Processing command:%s BUFFER_PROFILE table key %s", op.c_str(), profileName.c_str()); if (op == SET_COMMAND) @@ -1735,7 +2390,7 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues { if (!value.empty()) { - auto poolName = value; + auto &poolName = value; if (poolName.empty()) { SWSS_LOG_ERROR("BUFFER_PROFILE: Invalid format of reference to pool: %s", value.c_str()); @@ -1745,11 +2400,11 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues auto poolRef = m_bufferPoolLookup.find(poolName); if (poolRef == m_bufferPoolLookup.end()) { - SWSS_LOG_WARN("Pool %s hasn't been configured yet, skip", poolName.c_str()); + SWSS_LOG_WARN("Pool %s hasn't been configured yet, need retry", poolName.c_str()); return task_process_status::task_need_retry; } profileApp.pool_name = poolName; - profileApp.ingress = poolRef->second.ingress; + profileApp.direction = poolRef->second.direction; } else { @@ -1790,15 +2445,21 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues // For dynamic calculated headroom, user can provide this field only // We need to supply lossless and ingress profileApp.lossless = true; - profileApp.ingress = true; + profileApp.direction = BUFFER_INGRESS; } } - fvVector.emplace_back(field, value); SWSS_LOG_INFO("Inserting BUFFER_PROFILE table field %s value %s", field.c_str(), value.c_str()); } - // don't insert dynamically calculated profiles into APPL_DB - if (profileApp.lossless && profileApp.ingress) + + // Don't insert dynamically calculated profiles into APPL_DB + if (profileApp.lossless) { + if (profileApp.direction != BUFFER_INGRESS) + { + SWSS_LOG_ERROR("BUFFER_PROFILE %s is ingress but referencing an egress pool %s", profileName.c_str(), profileApp.pool_name.c_str()); + return task_process_status::task_success; + } + if (profileApp.dynamic_calculated) { profileApp.state = PROFILE_NORMAL; @@ -1818,11 +2479,8 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues } else { - m_applBufferProfileTable.set(profileName, fvVector); SWSS_LOG_NOTICE("BUFFER_PROFILE %s has been inserted into APPL_DB directly", profileName.c_str()); - - m_stateBufferProfileTable.set(profileName, fvVector); - m_bufferProfileIgnored.insert(profileName); + updateBufferProfileToDb(profileName, profileApp); } } else if (op == DEL_COMMAND) @@ -1859,7 +2517,6 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues } m_bufferProfileLookup.erase(profileName); - m_bufferProfileIgnored.erase(profileName); } else { @@ -1874,15 +2531,206 @@ task_process_status BufferMgrDynamic::handleBufferProfileTable(KeyOpFieldsValues return task_process_status::task_success; } -task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, const string &port, const string &op, const KeyOpFieldsValuesTuple &tuple) +void BufferMgrDynamic::handleSetSingleBufferObjectOnAdminDownPort(buffer_direction_t direction, const string &port, const string &key, const string &profile) { - vector fvVector; + auto &idsToZero = m_bufferObjectIdsToZero[direction]; + auto &table = m_applBufferObjectTables[direction]; + auto const &objectName = m_bufferObjectNames[direction]; + auto &portInfo = m_portInfoLookup[port]; + + if (m_portInitDone) + { + if (idsToZero.empty()) + { + // If initialization finished, no extra handle required. + // Check whether the key overlaps with supported but not configured map + auto const &idsToAdd = parseObjectNameFromKey(key, 1); + auto idsToAddBitmap = generateBitMapFromIdsStr(idsToAdd); + auto &supportedButNotConfiguredItems = portInfo.supported_but_not_configured_buffer_objects[direction]; + unsigned long overlappedUnconfiguredIdsMap = 0; + string overlappedUnconfiguredIdsStr; + const string &portPrefix = port + delimiter; + for (auto const &ids : supportedButNotConfiguredItems) + { + /* + * An exmaple to explain the logic + * Item no 01234567 + * Supported but not + * configured items: -**-**** + * Items to be added: -----**- + * Slice to be removed + * in the next iternation: ----**** + * Legend: *: valid -: empty + * Currently, supported but not configured items: 1-2, 4-7 + * Items to be added: 5-6 + * It will result in + * - items 4-7 to be removed from APPL_DB + * - items 4, 5-6, 7 to be added to APPL_DB + */ + auto idsBitmap = generateBitMapFromIdsStr(ids); + if ((idsToAddBitmap & idsBitmap) == idsToAddBitmap) + { + // Ids to add overlaps with this slice of unconfigured IDs + // Remove the slice and regenerate them + // It should be slice 4-7 in the above example + auto const &keyToRemove = portPrefix + ids; + SWSS_LOG_INFO("Buffer %s %s overlapped with existing zero item %s, remove the latter first", + objectName.c_str(), key.c_str(), keyToRemove.c_str()); + table.del(keyToRemove); + overlappedUnconfiguredIdsMap = (idsBitmap ^ idsToAddBitmap); + overlappedUnconfiguredIdsStr = ids; + break; + } + /* + * Result after iteration: + * overlappedUnconfiguredIdsMap: ----*--* (Items to be added has been removed from this map) + * overlappedUnconfiguredIdsStr: ----**** + */ + } + // Regenerate unconfigured IDs + supportedButNotConfiguredItems.erase(overlappedUnconfiguredIdsStr); + auto const &splitIds = generateIdListFromMap(overlappedUnconfiguredIdsMap, portInfo.maximum_buffer_objects[direction]); + /* + * Slices returned: ----*--* + * Readd zero profiles for each slice in the set + */ + for (auto const &splitId : splitIds) + { + auto const &keyToAdd = portPrefix + splitId; + updateBufferObjectToDb(keyToAdd, m_bufferZeroProfileName[direction], true, direction); + supportedButNotConfiguredItems.insert(splitId); + SWSS_LOG_INFO("Add %s zero item %s", objectName.c_str(), keyToAdd.c_str()); + } + + auto const &zeroProfile = fetchZeroProfileFromNormalProfile(profile); + if (!zeroProfile.empty()) + { + updateBufferObjectToDb(key, zeroProfile, true, direction); + SWSS_LOG_INFO("Add configured %s item %s as zero profile", objectName.c_str(), key.c_str()); + } + else + { + updateBufferObjectToDb(key, zeroProfile, false, direction); + SWSS_LOG_INFO("No zero profile defined for %s, reclaiming buffer by removing buffer %s %s", + profile.c_str(), objectName.c_str(), key.c_str()); + } + } + return; + } + + if (!idsToZero.empty()) + { + if (port + delimiter + idsToZero != key) + { + // For admin-down ports, if zero profiles have been applied on specified items, + // notify APPL_DB to remove only if the item ID doesn't equal specified items + // and the system is during initialization + // This is to guarantee the port can be "ready" in orchagent + // In case the port is admin down during initialization, the PG will be removed from the port, + // which effectively notifies bufferOrch to add the item to the m_ready_list + table.del(key); + } + } + else + { + // Notify APPL_DB to add zero profile to items + // An side effect is the items will be ready in orchagent (added to m_ready_list) + string &zeroProfile = fetchZeroProfileFromNormalProfile(profile); + if (!zeroProfile.empty()) + { + SWSS_LOG_INFO("Reclaim reserved buffer for %s %s by applying zero profile %s", objectName.c_str(), key.c_str(), zeroProfile.c_str()); + updateBufferObjectToDb(key, zeroProfile, true, direction); + } + else + { + SWSS_LOG_INFO("Reclaim reserved buffer for %s %s by removing it due to empty zero profile", objectName.c_str(), key.c_str()); + updateBufferObjectToDb(key, zeroProfile, false, direction); + } + } +} + +void BufferMgrDynamic::handleDelSingleBufferObjectOnAdminDownPort(buffer_direction_t direction, const string &port, const string &key, port_info_t &portInfo) +{ + auto &idsToZero = m_bufferObjectIdsToZero[direction]; + auto &supportedNotConfiguredItems = portInfo.supported_but_not_configured_buffer_objects[direction]; + auto const &objectName = m_bufferObjectNames[direction]; + + if (idsToZero.empty()) + { + // For admin down ports, if zero profiles have been applied to all configured items + // do NOT remove it otherwise SDK default value will be set for the items + // Move the key to supported_but_not_configured_items so that the slice of items + // can be removed when the port is started up + auto const &idsToDelStr = parseObjectNameFromKey(key, 1); + auto const &keyPrefix = port + delimiter; + auto idsToDelMap = generateBitMapFromIdsStr(idsToDelStr); + // If this item can be combined with any existing items, let's do. + // adjancentItemsSet will hold all adjancent slices. + // Otherwise, just add it to the list. + set adjancentItemsSet; + for (auto const &idStr : supportedNotConfiguredItems) + { + unsigned long idsMap = generateBitMapFromIdsStr(idStr); + // Make sure there is no overlap + if ((idsMap & idsToDelMap) == 0) + { + if (isItemIdsMapContinuous(idsMap|idsToDelMap, portInfo.maximum_buffer_objects[direction])) + { + SWSS_LOG_INFO("Removing buffer %s item %s, found adjancent item %s, remove the later first", + objectName.c_str(), key.c_str(), idStr.c_str()); + adjancentItemsSet.insert(idStr); + updateBufferObjectToDb(keyPrefix + idStr, "", false, direction); + idsToDelMap |= idsMap; + } + } + } + + if (!adjancentItemsSet.empty()) + { + SWSS_LOG_INFO("Removing buffer %s item %s from APPL_DB because it will be combined", objectName.c_str(), key.c_str()); + updateBufferObjectToDb(key, "", false, direction); + + for (auto const &idStr : adjancentItemsSet) + { + supportedNotConfiguredItems.erase(idStr); + } + + auto const combinedIdsStr = generateIdListFromMap(idsToDelMap, portInfo.maximum_buffer_objects[direction]); + // combinedIdsStr should contain only one id string. + for (auto const &idStr : combinedIdsStr) + { + SWSS_LOG_INFO("Removing buffer %s item %s, got combined item %s", + objectName.c_str(), key.c_str(), idStr.c_str()); + supportedNotConfiguredItems.insert(idStr); + // According to the logic in reclaimReservedBufferForPort, + // only if the m_bufferZeroProfileName is not empty will supported_but_not_configured list be generated. + // Now that adjancentItemsSet, which is a sub set of supported_but_not_configured, is NOT empty, + // m_bufferZeroProfileName must NOT be empty. + updateBufferObjectToDb(keyPrefix + idStr, m_bufferZeroProfileName[direction], true, direction); + } + } + else if (!m_bufferZeroProfileName[direction].empty()) + { + SWSS_LOG_INFO("Removing buffer %s item %s", objectName.c_str(), key.c_str()); + supportedNotConfiguredItems.insert(idsToDelStr); + } + } + + // For admin down ports, if zero profiles configured on specified items only + // it has been removed from APPL_DB when the port is admin down + // Just removing it from m_portQueueLookup or m_portPgLookup should suffice. + // Do NOT touch APPL_DB +} + +task_process_status BufferMgrDynamic::handleSingleBufferPgEntry(const string &key, const string &port, const KeyOpFieldsValuesTuple &tuple) +{ + string op = kfvOp(tuple); buffer_pg_t &bufferPg = m_portPgLookup[port][key]; + port_info_t &portInfo = m_portInfoLookup[port]; SWSS_LOG_DEBUG("Processing command:%s table BUFFER_PG key %s", op.c_str(), key.c_str()); if (op == SET_COMMAND) { - bool ignored = false; bool pureDynamic = true; // For set command: // 1. Create the corresponding table entries in APPL_DB @@ -1895,7 +2743,7 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, if (!bufferPg.configured_profile_name.empty()) { m_bufferProfileLookup[bufferPg.configured_profile_name].port_pgs.erase(key); - bufferPg.configured_profile_name = ""; + bufferPg.configured_profile_name.clear(); } for (auto i = kfvFieldsValues(tuple).begin(); i != kfvFieldsValues(tuple).end(); i++) @@ -1908,7 +2756,7 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, { // Headroom override pureDynamic = false; - string profileName = value; + const string &profileName = value; if (profileName.empty()) { SWSS_LOG_ERROR("BUFFER_PG: Invalid format of reference to profile: %s", value.c_str()); @@ -1918,22 +2766,11 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, auto searchRef = m_bufferProfileLookup.find(profileName); if (searchRef == m_bufferProfileLookup.end()) { - if (m_bufferProfileIgnored.find(profileName) != m_bufferProfileIgnored.end()) - { - // Referencing an ignored profile, the PG should be ignored as well - ignored = true; - bufferPg.dynamic_calculated = false; - bufferPg.lossless = false; - bufferPg.configured_profile_name = profileName; - } - else - { - // In this case, we shouldn't set the dynamic calculated flag to true - // It will be updated when its profile configured. - bufferPg.dynamic_calculated = false; - SWSS_LOG_WARN("Profile %s hasn't been configured yet, skip", profileName.c_str()); - return task_process_status::task_need_retry; - } + // In this case, we shouldn't set the dynamic calculated flag to true + // It will be updated when its profile configured. + bufferPg.dynamic_calculated = false; + SWSS_LOG_WARN("Profile %s hasn't been configured yet, skip", profileName.c_str()); + return task_process_status::task_need_retry; } else { @@ -1952,7 +2789,6 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, return task_process_status::task_invalid_entry; } - fvVector.emplace_back(field, value); SWSS_LOG_INFO("Inserting BUFFER_PG table field %s value %s", field.c_str(), value.c_str()); } @@ -1963,25 +2799,21 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, bufferPg.lossless = true; } - if (!ignored && bufferPg.lossless) + if (PORT_ADMIN_DOWN == portInfo.state) + { + // In case the port is admin down during initialization, the PG will be removed from the port, + // which effectively notifies bufferOrch to add the item to the m_ready_list + handleSetSingleBufferObjectOnAdminDownPort(BUFFER_PG, port, key, bufferPg.configured_profile_name); + } + else if (bufferPg.lossless) { doUpdatePgTask(key, port); } else { - port_info_t &portInfo = m_portInfoLookup[port]; - if (PORT_ADMIN_DOWN != portInfo.state) - { - SWSS_LOG_NOTICE("Inserting BUFFER_PG table entry %s into APPL_DB directly", key.c_str()); - m_applBufferPgTable.set(key, fvVector); - bufferPg.running_profile_name = bufferPg.configured_profile_name; - } - else if (!m_portInitDone) - { - // In case the port is admin down during initialization, the PG will be removed from the port, - // which effectively notifies bufferOrch to add the item to the m_ready_list - m_applBufferPgTable.del(key); - } + SWSS_LOG_NOTICE("Inserting BUFFER_PG table entry %s into APPL_DB directly", key.c_str()); + bufferPg.running_profile_name = bufferPg.configured_profile_name; + updateBufferObjectToDb(key, bufferPg.running_profile_name, true); } if (!bufferPg.configured_profile_name.empty()) @@ -1997,6 +2829,12 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, string &runningProfileName = bufferPg.running_profile_name; string &configProfileName = bufferPg.configured_profile_name; + if (!m_supportRemoving) + { + SWSS_LOG_ERROR("Removing priority group %s is not supported on the platform", key.c_str()); + return task_process_status::task_failed; + } + if (!runningProfileName.empty()) { m_bufferProfileLookup[runningProfileName].port_pgs.erase(key); @@ -2006,14 +2844,18 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, m_bufferProfileLookup[configProfileName].port_pgs.erase(key); } - if (bufferPg.lossless) + if (portInfo.state == PORT_ADMIN_DOWN) + { + handleDelSingleBufferObjectOnAdminDownPort(BUFFER_PG, port, key, portInfo); + } + else if (bufferPg.lossless) { doRemovePgTask(key, port); } else { SWSS_LOG_NOTICE("Removing BUFFER_PG table entry %s from APPL_DB directly", key.c_str()); - m_applBufferPgTable.del(key); + m_applBufferObjectTables[BUFFER_PG].del(key); } m_portPgLookup[port].erase(key); @@ -2033,109 +2875,270 @@ task_process_status BufferMgrDynamic::handleOneBufferPgEntry(const string &key, return task_process_status::task_success; } -task_process_status BufferMgrDynamic::handleBufferPgTable(KeyOpFieldsValuesTuple &tuple) +task_process_status BufferMgrDynamic::checkBufferProfileDirection(const string &profiles, buffer_direction_t dir) { - SWSS_LOG_ENTER(); - string key = kfvKey(tuple); - string op = kfvOp(tuple); - - transformSeperator(key); - string ports = parseObjectNameFromKey(key); - string pgs = parseObjectNameFromKey(key, 1); - if (ports.empty() || pgs.empty()) + // Fetch profile and check whether it's an egress profile + vector profileRefList = tokenize(profiles, ','); + for (auto &profileName : profileRefList) { - SWSS_LOG_ERROR("Invalid key format %s for BUFFER_PG table", key.c_str()); - return task_process_status::task_invalid_entry; + auto profileSearchRef = m_bufferProfileLookup.find(profileName); + if (profileSearchRef == m_bufferProfileLookup.end()) + { + SWSS_LOG_NOTICE("Profile %s doesn't exist, need retry", profileName.c_str()); + return task_process_status::task_need_retry; + } + + auto &profileObj = profileSearchRef->second; + if (dir != profileObj.direction) + { + SWSS_LOG_ERROR("Profile %s's direction is %s but %s is expected, applying profile failed", + profileName.c_str(), + m_bufferDirectionNames[profileObj.direction].c_str(), + m_bufferDirectionNames[dir].c_str()); + return task_process_status::task_failed; + } } - auto portsList = tokenize(ports, ','); + return task_process_status::task_success; +} - task_process_status rc = task_process_status::task_success; +task_process_status BufferMgrDynamic::handleSingleBufferQueueEntry(const string &key, const string &port, const KeyOpFieldsValuesTuple &tuple) +{ + SWSS_LOG_ENTER(); - if (portsList.size() == 1) + const auto &op = kfvOp(tuple); + const auto &queues = key; + auto &portInfo = m_portInfoLookup[port]; + + if (op == SET_COMMAND) { - rc = handleOneBufferPgEntry(key, ports, op, tuple); + auto &portQueue = m_portQueueLookup[port][queues]; + + SWSS_LOG_INFO("Inserting entry BUFFER_QUEUE_TABLE:%s to APPL_DB", key.c_str()); + + for (auto i : kfvFieldsValues(tuple)) + { + // Transform the separator in values from "|" to ":" + if (fvField(i) == buffer_profile_field_name) + { + auto rc = checkBufferProfileDirection(fvValue(i), BUFFER_EGRESS); + if (rc != task_process_status::task_success) + return rc; + portQueue.running_profile_name = fvValue(i); + SWSS_LOG_NOTICE("Queue %s has been configured on the system, referencing profile %s", key.c_str(), fvValue(i).c_str()); + } + else + { + SWSS_LOG_ERROR("Unknown field %s in BUFFER_QUEUE|%s", fvField(i).c_str(), key.c_str()); + continue; + } + + SWSS_LOG_INFO("Inserting field %s value %s", fvField(i).c_str(), fvValue(i).c_str()); + } + + // TODO: check overlap. Currently, assume there is no overlap + + if (PORT_ADMIN_DOWN == portInfo.state) + { + handleSetSingleBufferObjectOnAdminDownPort(BUFFER_QUEUE, port, key, portQueue.running_profile_name); + } + else + { + updateBufferObjectToDb(key, portQueue.running_profile_name, true, BUFFER_QUEUE); + } } - else + else if (op == DEL_COMMAND) { - for (auto port : portsList) + if (!m_supportRemoving) { - string singleKey = port + ':' + pgs; - rc = handleOneBufferPgEntry(singleKey, port, op, tuple); - if (rc == task_process_status::task_need_retry) - return rc; + SWSS_LOG_ERROR("Removing queue %s is not supported on the platform", key.c_str()); + return task_process_status::task_failed; + } + SWSS_LOG_INFO("Removing entry %s from APPL_DB", key.c_str()); + m_portQueueLookup[port].erase(queues); + if (PORT_ADMIN_DOWN == portInfo.state) + { + handleDelSingleBufferObjectOnAdminDownPort(BUFFER_QUEUE, port, key, portInfo); + } + else + { + m_applBufferObjectTables[BUFFER_QUEUE].del(key); } } - return rc; + return task_process_status::task_success; } -task_process_status BufferMgrDynamic::handleBufferQueueTable(KeyOpFieldsValuesTuple &tuple) +task_process_status BufferMgrDynamic::handleSingleBufferPortProfileListEntry(const string &key, buffer_direction_t dir, const KeyOpFieldsValuesTuple &tuple) { - return doBufferTableTask(tuple, m_applBufferQueueTable); + SWSS_LOG_ENTER(); + + const string &port = key; + const string &op = kfvOp(tuple); + const string &tableName = dir == BUFFER_INGRESS ? APP_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME : APP_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME; + ProducerStateTable &appTable = m_applBufferProfileListTables[dir]; + port_profile_list_lookup_t &profileListLookup = m_portProfileListLookups[dir]; + + if (op == SET_COMMAND) + { + SWSS_LOG_INFO("Inserting entry %s:%s to APPL_DB", tableName.c_str(), key.c_str()); + + for (auto i : kfvFieldsValues(tuple)) + { + if (fvField(i) == buffer_profile_list_field_name) + { + auto rc = checkBufferProfileDirection(fvValue(i), dir); + if (rc != task_process_status::task_success) + return rc; + profileListLookup[port] = fvValue(i); + SWSS_LOG_NOTICE("%s %s has been configured on the system, referencing profile list %s", tableName.c_str(), key.c_str(), fvValue(i).c_str()); + } + else + { + SWSS_LOG_ERROR("Unknown field %s in %s", fvField(i).c_str(), key.c_str()); + continue; + } + } + + auto &portInfo = m_portInfoLookup[port]; + if (PORT_ADMIN_DOWN != portInfo.state) + { + // Only apply profile list on admin up port + // For admin-down ports, zero profile list has been applied on the port when it entered admin-down state + updateBufferObjectListToDb(key, profileListLookup[port], dir); + } + } + else if (op == DEL_COMMAND) + { + // Not supported on Mellanox platform for now. + SWSS_LOG_INFO("Removing entry %s:%s from APPL_DB", tableName.c_str(), key.c_str()); + profileListLookup.erase(port); + appTable.del(key); + } + + return task_process_status::task_success; } -task_process_status BufferMgrDynamic::handleBufferPortIngressProfileListTable(KeyOpFieldsValuesTuple &tuple) +task_process_status BufferMgrDynamic::handleSingleBufferPortIngressProfileListEntry(const string &key, const string &port, const KeyOpFieldsValuesTuple &tuple) { - return doBufferTableTask(tuple, m_applBufferIngressProfileListTable); + return handleSingleBufferPortProfileListEntry(key, BUFFER_INGRESS, tuple); } -task_process_status BufferMgrDynamic::handleBufferPortEgressProfileListTable(KeyOpFieldsValuesTuple &tuple) +task_process_status BufferMgrDynamic::handleSingleBufferPortEgressProfileListEntry(const string &key, const string &port, const KeyOpFieldsValuesTuple &tuple) { - return doBufferTableTask(tuple, m_applBufferEgressProfileListTable); + return handleSingleBufferPortProfileListEntry(key, BUFFER_EGRESS, tuple); } /* - * This function copies the data from tables in CONFIG_DB to APPL_DB. - * With dynamically buffer calculation supported, the following tables - * will be moved to APPL_DB from CONFIG_DB because the CONFIG_DB contains - * configured entries only while APPL_DB contains dynamically generated entries - * - BUFFER_POOL - * - BUFFER_PROFILE - * - BUFFER_PG - * The following tables have to be moved to APPL_DB because they reference - * some entries that have been moved to APPL_DB - * - BUFFER_QUEUE - * - BUFFER_PORT_INGRESS_PROFILE_LIST - * - BUFFER_PORT_EGRESS_PROFILE_LIST - * One thing we need to handle is to transform the separator from | to : - * The following items contain separator: - * - keys of each item - * - pool in BUFFER_POOL - * - profile in BUFFER_PG + * handleBufferObjectTables + * + * For the buffer object tables, like BUFFER_PG, BUFFER_QUEUE, BUFFER_PORT_INGRESS_PROFILE_LIST, and BUFFER_PORT_EGRESS_PROFILE_LIST, + * their keys can be either a single port or a port list separated by a comma, which can be handled by a common logic. + * So the logic to parse the keys is common and the logic to handle each table is different. + * This function is introduced to handle the common logic and a handler map (buffer_single_item_handler_map) is introduced + * to dispatch to different handler according to the table name. + * + * Arguments: + * tuple, the KeyOpFieldsValuesTuple, containing key, op, list of tuples consisting of field and value pair + * table, the name of the table + * keyWithIds, whether the keys contains sublevel IDs of objects. + * - For queue and priority groups, it is true since the keys format is like |IDs + * Eg: input key "Ethernet0,Ethernet4,Ethernet8,Ethernet12|3-4" will be decomposed to the following list + * [Ethernet0|3-4, Ethernet4|3-4, Ethernet8|3-4, Ethernet12|3-4] + * - For profile list tables, it is false since the keys format is + * Eg: input key "Ethernet0,Ethernet4,Ethernet8,Ethernet12" will be decomposed to the following list + * [Ethernet0, Ethernet4, Ethernet8, Ethernet12] + * + * It is wrapped by the following functions which are the table handler of the above tables: + * - handleBufferPgTable + * - handleBufferQueueTable + * - handleBufferPortIngressProfileListTable + * - handleBufferPortEgressProfileListTable + * and will call the following table handlers which handles table update for a single port: + * - handleSingleBufferPgEntry + * - handleSingleBufferQueueEntry + * - handleSingleBufferPortIngressProfileListEntry + * - handleSingleBufferPortEgressProfileListEntry + * + * The flow: + * 1. Parse the key. + * 2. Fetch the handler according to the table name + * 3. For each port in the key list, + * - Construct a new key as + * - A single port + IDs if keyWidthIds is true + * - Or a single port only if keyWidthIds is false + * - Call the corresponding handler. */ -task_process_status BufferMgrDynamic::doBufferTableTask(KeyOpFieldsValuesTuple &tuple, ProducerStateTable &applTable) +task_process_status BufferMgrDynamic::handleBufferObjectTables(KeyOpFieldsValuesTuple &tuple, const string &table, bool keyWithIds) { SWSS_LOG_ENTER(); - string key = kfvKey(tuple); - const string &name = applTable.getTableName(); - // Transform the separator in key from "|" to ":" transformSeperator(key); - string op = kfvOp(tuple); - if (op == SET_COMMAND) + string ports = parseObjectNameFromKey(key); + if (ports.empty()) { - vector fvVector; - - SWSS_LOG_INFO("Inserting entry %s|%s from CONFIG_DB to APPL_DB", name.c_str(), key.c_str()); + SWSS_LOG_ERROR("Invalid key format %s for %s table", key.c_str(), table.c_str()); + return task_process_status::task_invalid_entry; + } - for (auto i : kfvFieldsValues(tuple)) + string ids; + if (keyWithIds) + { + ids = parseObjectNameFromKey(key, 1); + if (ids.empty()) { - // Transform the separator in values from "|" to ":" - fvVector.emplace_back(fvField(i), fvValue(i)); - SWSS_LOG_INFO("Inserting field %s value %s", fvField(i).c_str(), fvValue(i).c_str()); + SWSS_LOG_ERROR("Invalid key format %s for %s table", key.c_str(), table.c_str()); + return task_process_status::task_invalid_entry; } - applTable.set(key, fvVector); } - else if (op == DEL_COMMAND) + + auto portsList = tokenize(ports, ','); + + task_process_status rc = task_process_status::task_success; + auto &handler = m_bufferSingleItemHandlerMap[table]; + + if (portsList.size() == 1) { - SWSS_LOG_INFO("Removing entry %s from APPL_DB", key.c_str()); - applTable.del(key); + rc = (this->*handler)(key, ports, tuple); + } + else + { + for (auto port : portsList) + { + string singleKey; + if (keyWithIds) + singleKey = port + ':' + ids; + else + singleKey = port; + rc = (this->*handler)(singleKey, port, tuple); + if (rc == task_process_status::task_need_retry) + return rc; + } } - return task_process_status::task_success; + return rc; +} + +task_process_status BufferMgrDynamic::handleBufferPgTable(KeyOpFieldsValuesTuple &tuple) +{ + return handleBufferObjectTables(tuple, CFG_BUFFER_PG_TABLE_NAME, true); +} + +task_process_status BufferMgrDynamic::handleBufferQueueTable(KeyOpFieldsValuesTuple &tuple) +{ + return handleBufferObjectTables(tuple, CFG_BUFFER_QUEUE_TABLE_NAME, true); +} + +task_process_status BufferMgrDynamic::handleBufferPortIngressProfileListTable(KeyOpFieldsValuesTuple &tuple) +{ + return handleBufferObjectTables(tuple, CFG_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME, false); +} + +task_process_status BufferMgrDynamic::handleBufferPortEgressProfileListTable(KeyOpFieldsValuesTuple &tuple) +{ + return handleBufferObjectTables(tuple, CFG_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME, false); } void BufferMgrDynamic::doTask(Consumer &consumer) @@ -2175,7 +3178,174 @@ void BufferMgrDynamic::doTask(Consumer &consumer) } } +/* + * We do not apply any buffer profiles and objects until the buffer pools are ready. + * This is to prevent buffer orchagent from keeping retrying if buffer profiles and objects are inserted to APPL_DB while buffer pools are not. + * + * Originally, all buffer profiles and objects were applied to APPL_DB as soon as they were learnt from CONFIG_DB. + * The buffer profiles, and objects will be available as soon as they are received from CONFIG_DB when the system is starting + * but it takes more seconds for the buffer pools to be applied to APPL_DB + * because the sizes need to be calculated first and can be calculated only after some other data available. + * This causes buffer orchagent receives buffer profiles and objects first and then buffer pools. + * The buffer orchagent has to keep retrying in such case, which wastes time and incurs error messages. + * + * Now, we will guarantee the buffer profiles and objects to be applied only after buffer pools are ready. + * This is achieved by the following steps: + * - m_bufferPoolReady indicates whether the buffer pools are ready. + * - When the buffer profiles and objects are received from CONFIG_DB, + * they will be handled and stored in internal data struct + * (but not applied to APPL_DB until the buffer pools are ready, AKA. m_bufferPoolReady == true) + * - Once buffer pools are ready, all stored buffer profiles and objects will be applied to APPL_DB by this function. + * + * Applying additional zero profiles, which is supported but not configured profiles, if any, will be deferred in non-warmreboot. + * This is to accelerate the start flow especially for fast reboot. + * + * The progress of pushing buffer pools into APPL_DB has also been accelerated by the lua plugin for calculating buffer sizes. + * Originally, if the data are not available for calculating buffer sizes, it just return an empty vectors and buffer manager will retry + * Now, the lua plugin will return the sizes calculated by the following logic: + * - If there are buffer sizes in APPL_DB, return the sizes in APPL_DB. + * - Otherwise, return all 0 as sizes. + * This makes sure there will be available sizes of buffer pools as sonn as possible when the system is starting + * The sizes will be updated to non zero when the data for calculating it are ready. + * Even this is Mellanox specific, other vendors can also take advantage of this logic. + */ +void BufferMgrDynamic::handlePendingBufferObjects() +{ + if (m_bufferPoolReady) + { + if (!m_pendingApplyZeroProfilePorts.empty()) + { + // No action as no zero profiles defined. + if (!m_zeroPoolAndProfileInfo.empty() && !m_zeroProfilesLoaded) + { + loadZeroPoolAndProfiles(); + } + } + + if (m_bufferObjectsPending) + { + // Apply all pending buffer profiles + for (auto &profile : m_bufferProfileLookup) + { + updateBufferProfileToDb(profile.first, profile.second); + } + + for (auto &port : m_portInfoLookup) + { + if (port.second.state != PORT_ADMIN_DOWN) + { + // The admin-down ports should not be touched here. Two scenarios + // 1. If admin-status is handled ahead of m_bufferPoolReady being true, + // They should be in m_pendingApplyZeroProfilePorts and will be handled later in this function + // 2. Otherwise, they should have been handled and zero profiles have been applied. + refreshPgsForPort(port.first, port.second.effective_speed, port.second.cable_length, port.second.mtu); + + // Apply pending buffer queues + auto &queues = m_portQueueLookup[port.first]; + for (auto &queue : queues) + { + updateBufferObjectToDb(queue.first, queue.second.running_profile_name, true, BUFFER_QUEUE); + } + + // Apply all pending buffer profile lists + for (auto dir : m_bufferDirections) + { + auto &profileList = m_portProfileListLookups[dir][port.first]; + if (!profileList.empty()) + updateBufferObjectListToDb(port.first, profileList, dir); + } + } + } + + m_bufferObjectsPending = false; + } + + if (!m_pendingApplyZeroProfilePorts.empty()) + { + // m_pendingApplyZeroProfilePorts contains all the admin down ports + // We do NOT iterate all admin down ports but the set containing ports need to be applied zero profiles + set portsNeedRetry; + for ( auto &port : m_pendingApplyZeroProfilePorts) + { + if (isReadyToReclaimBufferOnPort(port)) + { + reclaimReservedBufferForPort(port, m_portPgLookup, BUFFER_PG); + reclaimReservedBufferForPort(port, m_portQueueLookup, BUFFER_QUEUE); + SWSS_LOG_NOTICE("Admin-down port %s is handled after buffer pools have been configured", port.c_str()); + } + else + { + SWSS_LOG_NOTICE("Admin-down port %s is still not ready to reclaim after buffer pools have been configured, need retry", port.c_str()); + portsNeedRetry.insert(port); + } + } + m_pendingApplyZeroProfilePorts = move(portsNeedRetry); + } + + // configuredItemsDone means all buffer objects (profiles, priority groups, queues, profile lists) in the CONFIG_DB have been applied to APPL_DB + // - on admin up ports, normal profiles are applied + // - on admin down ports, zero profiles are applied + bool configuredItemsDone = !m_bufferObjectsPending && m_pendingApplyZeroProfilePorts.empty(); + + if (WarmStart::isWarmStart()) + { + // For warm restart, all buffer items have been applied now + if (configuredItemsDone) + { + WarmStart::setWarmStartState("buffermgrd", WarmStart::REPLAYED); + // There is no operation to be performed for buffermgrd reconcillation + // Hence mark it reconciled right away + WarmStart::setWarmStartState("buffermgrd", WarmStart::RECONCILED); + m_bufferCompletelyInitialized = true; + SWSS_LOG_NOTICE("All bufer configuration has been applied. Buffer initialization done"); + } + } + else + { + // For fast reboot, cold reboot and other initialization flow, the additional zero profiles will be applied + // 30 seconds later once normal profiles and zero profiles of configured items have been applied + // This is to accelerate fast reboot flow. + // m_waitApplyAdditionalZeroProfiles is initialized as 3 during buffer manager initialization + // Timer's period is 10. So total deferred time is 3*10 = 30 seconds + if (m_waitApplyAdditionalZeroProfiles > 0) + { + if (m_portInitDone && configuredItemsDone) + { + SWSS_LOG_NOTICE("Additional zero profiles will be appied after %d seconds", m_waitApplyAdditionalZeroProfiles * 10); + m_waitApplyAdditionalZeroProfiles --; + } + } + else + { + // For admin down ports, apply supported but not configured items + for (auto dir : m_bufferDirections) + { + for ( auto &port : m_pendingSupportedButNotConfiguredPorts[dir]) + { + auto &portInfo = m_portInfoLookup[port]; + for(auto &it: portInfo.supported_but_not_configured_buffer_objects[dir]) + { + auto const &key = port + delimiter + it; + SWSS_LOG_INFO("Applying additional zero profiles on port %s", key.c_str()); + updateBufferObjectToDb(key, m_bufferZeroProfileName[dir], true, dir); + } + } + m_pendingSupportedButNotConfiguredPorts[dir].clear(); + } + + if (configuredItemsDone) + { + m_bufferCompletelyInitialized = true; + SWSS_LOG_NOTICE("All bufer configuration has been applied. Buffer initialization done"); + } + } + } + } +} + void BufferMgrDynamic::doTask(SelectableTimer &timer) { checkSharedBufferPoolSize(true); + if (!m_bufferCompletelyInitialized) + handlePendingBufferObjects(); } diff --git a/cfgmgr/buffermgrdyn.h b/cfgmgr/buffermgrdyn.h index fa1c69dbc2..d316aee73c 100644 --- a/cfgmgr/buffermgrdyn.h +++ b/cfgmgr/buffermgrdyn.h @@ -16,12 +16,21 @@ namespace swss { #define BUFFERMGR_TIMER_PERIOD 10 +typedef enum { + BUFFER_INGRESS = 0, + BUFFER_PG = BUFFER_INGRESS, + BUFFER_EGRESS = 1, + BUFFER_QUEUE = BUFFER_EGRESS, + BUFFER_DIR_MAX +} buffer_direction_t; + typedef struct { - bool ingress; + buffer_direction_t direction; bool dynamic_size; std::string total_size; std::string mode; std::string xoff; + std::string zero_profile_name; } buffer_pool_t; // State of the profile. @@ -45,8 +54,8 @@ typedef struct { profile_state_t state; bool dynamic_calculated; bool static_configured; - bool ingress; bool lossless; + buffer_direction_t direction; // fields representing parameters by which the headroom is calculated std::string speed; @@ -69,6 +78,10 @@ typedef struct { } buffer_profile_t; typedef struct { + std::string running_profile_name; +} buffer_object_t; + +typedef struct : public buffer_object_t{ bool lossless; bool dynamic_calculated; bool static_configured; @@ -76,7 +89,6 @@ typedef struct { // - the configured profile for static one, // - the dynamically generated profile otherwise. std::string configured_profile_name; - std::string running_profile_name; } buffer_pg_t; typedef enum { @@ -101,6 +113,8 @@ typedef struct { std::string supported_speeds; long lane_count; + sai_uint32_t maximum_buffer_objects[BUFFER_DIR_MAX]; + std::set supported_but_not_configured_buffer_objects[BUFFER_DIR_MAX]; } port_info_t; //TODO: @@ -116,12 +130,16 @@ typedef std::map port_info_lookup_t; typedef std::map buffer_profile_lookup_t; //map from name to pool typedef std::map buffer_pool_lookup_t; -//port -> headroom override -typedef std::map headroom_override_t; //map from pg to info typedef std::map buffer_pg_lookup_t; //map from port to all its pgs typedef std::map port_pg_lookup_t; +//map from object name to its profile +typedef std::map buffer_object_lookup_t; +//map from port to all its objects +typedef std::map port_object_lookup_t; +//map from port to profile_list +typedef std::map port_profile_list_lookup_t; //map from gearbox model to gearbox delay typedef std::map gearbox_delay_t; @@ -132,30 +150,52 @@ class BufferMgrDynamic : public Orch using Orch::doTask; private: + std::string m_platform; + std::vector m_bufferDirections; + const std::string m_bufferObjectNames[BUFFER_DIR_MAX]; + const std::string m_bufferDirectionNames[BUFFER_DIR_MAX]; + typedef task_process_status (BufferMgrDynamic::*buffer_table_handler)(KeyOpFieldsValuesTuple &t); typedef std::map buffer_table_handler_map; typedef std::pair buffer_handler_pair; - std::string m_platform; - buffer_table_handler_map m_bufferTableHandlerMap; + typedef task_process_status (BufferMgrDynamic::*buffer_single_item_handler)(const std::string &key, const std::string &port, const KeyOpFieldsValuesTuple &tuple); + typedef std::map buffer_single_item_handler_map; + typedef std::pair buffer_single_item_handler_pair; + + buffer_single_item_handler_map m_bufferSingleItemHandlerMap; + bool m_portInitDone; - bool m_firstTimeCalculateBufferPool; + bool m_bufferPoolReady; + bool m_bufferObjectsPending; + bool m_bufferCompletelyInitialized; std::string m_configuredSharedHeadroomPoolSize; std::shared_ptr m_applDb = nullptr; SelectableTimer *m_buffermgrPeriodtimer = nullptr; - // PORT and CABLE_LENGTH table and caches - Table m_cfgPortTable; - Table m_cfgCableLenTable; + // Fields for zero pool and profiles + std::vector m_zeroPoolAndProfileInfo; + std::set m_zeroPoolNameSet; + std::vector> m_zeroProfiles; + bool m_zeroProfilesLoaded; + bool m_supportRemoving; + std::string m_bufferZeroProfileName[BUFFER_DIR_MAX]; + std::string m_bufferObjectIdsToZero[BUFFER_DIR_MAX]; + + // PORT table and caches Table m_statePortTable; // m_portInfoLookup // key: port name // updated only when a port's speed and cable length updated port_info_lookup_t m_portInfoLookup; + std::set m_adminDownPorts; + std::set m_pendingApplyZeroProfilePorts; + std::set m_pendingSupportedButNotConfiguredPorts[BUFFER_DIR_MAX]; + int m_waitApplyAdditionalZeroProfiles; // BUFFER_POOL table and cache ProducerStateTable m_applBufferPoolTable; @@ -164,20 +204,15 @@ class BufferMgrDynamic : public Orch // BUFFER_PROFILE table and caches ProducerStateTable m_applBufferProfileTable; - Table m_cfgBufferProfileTable; Table m_stateBufferProfileTable; // m_bufferProfileLookup - the cache for the following set: // 1. CFG_BUFFER_PROFILE // 2. Dynamically calculated headroom info stored in APPL_BUFFER_PROFILE // key: profile name buffer_profile_lookup_t m_bufferProfileLookup; - // A set where the ignored profiles are stored. - // A PG that reference an ignored profile should also be ignored. - std::set m_bufferProfileIgnored; // BUFFER_PG table and caches - ProducerStateTable m_applBufferPgTable; - Table m_cfgBufferPgTable; + ProducerStateTable m_applBufferObjectTables[BUFFER_DIR_MAX]; // m_portPgLookup - the cache for CFG_BUFFER_PG and APPL_BUFFER_PG // 1st level key: port name, 2nd level key: PGs // Updated in: @@ -185,16 +220,23 @@ class BufferMgrDynamic : public Orch // 2. refreshPriorityGroupsForPort, speed/cable length updated port_pg_lookup_t m_portPgLookup; + // BUFFER_QUEUE table and caches + // m_portQueueLookup - the cache for BUFFER_QUEUE table + // 1st level key: port name, 2nd level key: queues + port_object_lookup_t m_portQueueLookup; + + // BUFFER_INGRESS_PROFILE_LIST/BUFFER_EGRESS_PROFILE_LIST table and caches + ProducerStateTable m_applBufferProfileListTables[BUFFER_DIR_MAX]; + port_profile_list_lookup_t m_portProfileListLookups[BUFFER_DIR_MAX]; + + // table and caches + port_profile_list_lookup_t m_portEgressProfileListLookup; + // Other tables - Table m_cfgLosslessPgPoolTable; Table m_cfgDefaultLosslessBufferParam; Table m_stateBufferMaximumTable; - ProducerStateTable m_applBufferQueueTable; - ProducerStateTable m_applBufferIngressProfileListTable; - ProducerStateTable m_applBufferEgressProfileListTable; - Table m_applPortTable; bool m_supportGearbox; @@ -218,6 +260,8 @@ class BufferMgrDynamic : public Orch // Initializers void initTableHandlerMap(); void parseGearboxInfo(std::shared_ptr> gearboxInfo); + void loadZeroPoolAndProfiles(); + void unloadZeroPoolAndProfiles(); // Tool functions to parse keys and references std::string getPgPoolMode(); @@ -229,11 +273,13 @@ class BufferMgrDynamic : public Orch return !value.empty() && value != "0"; } std::string getMaxSpeedFromList(std::string speedList); + std::string &fetchZeroProfileFromNormalProfile(const std::string &profile); // APPL_DB table operations void updateBufferPoolToDb(const std::string &name, const buffer_pool_t &pool); void updateBufferProfileToDb(const std::string &name, const buffer_profile_t &profile); - void updateBufferPgToDb(const std::string &key, const std::string &profile, bool add); + void updateBufferObjectToDb(const std::string &key, const std::string &profile, bool add, buffer_direction_t dir); + void updateBufferObjectListToDb(const std::string &key, const std::string &profileList, buffer_direction_t dir); // Meta flows bool needRefreshPortDueToEffectiveSpeed(port_info_t &portInfo, std::string &portName); @@ -244,30 +290,41 @@ class BufferMgrDynamic : public Orch void releaseProfile(const std::string &profile_name); bool isHeadroomResourceValid(const std::string &port, const buffer_profile_t &profile, const std::string &new_pg); void refreshSharedHeadroomPool(bool enable_state_updated_by_ratio, bool enable_state_updated_by_size); + task_process_status checkBufferProfileDirection(const std::string &profiles, buffer_direction_t dir); + std::string constructZeroProfileListFromNormalProfileList(const std::string &normalProfileList, const std::string &port); + void removeSupportedButNotConfiguredItemsOnPort(port_info_t &portInfo, const std::string &port); + void applyNormalBufferObjectsOnPort(const std::string &port); + void handlePendingBufferObjects(); + void handleSetSingleBufferObjectOnAdminDownPort(buffer_direction_t direction, const std::string &port, const std::string &key, const std::string &profile); + void handleDelSingleBufferObjectOnAdminDownPort(buffer_direction_t direction, const std::string &port, const std::string &key, port_info_t &portInfo); + bool isReadyToReclaimBufferOnPort(const std::string &port); // Main flows - task_process_status removeAllPgsFromPort(const std::string &port); + template task_process_status reclaimReservedBufferForPort(const std::string &port, T &obj, buffer_direction_t dir); task_process_status refreshPgsForPort(const std::string &port, const std::string &speed, const std::string &cable_length, const std::string &mtu, const std::string &exactly_matched_key); task_process_status doUpdatePgTask(const std::string &pg_key, const std::string &port); task_process_status doRemovePgTask(const std::string &pg_key, const std::string &port); - task_process_status doAdminStatusTask(const std::string port, const std::string adminStatus); task_process_status doUpdateBufferProfileForDynamicTh(buffer_profile_t &profile); task_process_status doUpdateBufferProfileForSize(buffer_profile_t &profile, bool update_pool_size); // Table update handlers - task_process_status handleBufferMaxParam(KeyOpFieldsValuesTuple &t); - task_process_status handleDefaultLossLessBufferParam(KeyOpFieldsValuesTuple &t); - task_process_status handleCableLenTable(KeyOpFieldsValuesTuple &t); - task_process_status handlePortStateTable(KeyOpFieldsValuesTuple &t); - task_process_status handlePortTable(KeyOpFieldsValuesTuple &t); - task_process_status handleBufferPoolTable(KeyOpFieldsValuesTuple &t); - task_process_status handleBufferProfileTable(KeyOpFieldsValuesTuple &t); - task_process_status handleOneBufferPgEntry(const std::string &key, const std::string &port, const std::string &op, const KeyOpFieldsValuesTuple &tuple); - task_process_status handleBufferPgTable(KeyOpFieldsValuesTuple &t); - task_process_status handleBufferQueueTable(KeyOpFieldsValuesTuple &t); - task_process_status handleBufferPortIngressProfileListTable(KeyOpFieldsValuesTuple &t); - task_process_status handleBufferPortEgressProfileListTable(KeyOpFieldsValuesTuple &t); - task_process_status doBufferTableTask(KeyOpFieldsValuesTuple &t, ProducerStateTable &applTable); + task_process_status handleBufferMaxParam(KeyOpFieldsValuesTuple &tuple); + task_process_status handleDefaultLossLessBufferParam(KeyOpFieldsValuesTuple &tuple); + task_process_status handleCableLenTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handlePortStateTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handlePortTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handleBufferPoolTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handleBufferProfileTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handleSingleBufferPgEntry(const std::string &key, const std::string &port, const KeyOpFieldsValuesTuple &tuple); + task_process_status handleSingleBufferQueueEntry(const std::string &key, const std::string &port, const KeyOpFieldsValuesTuple &tuple); + task_process_status handleSingleBufferPortProfileListEntry(const std::string &key, buffer_direction_t dir, const KeyOpFieldsValuesTuple &tuple); + task_process_status handleSingleBufferPortIngressProfileListEntry(const std::string &key, const std::string &port, const KeyOpFieldsValuesTuple &tuple); + task_process_status handleSingleBufferPortEgressProfileListEntry(const std::string &key, const std::string &port, const KeyOpFieldsValuesTuple &tuple); + task_process_status handleBufferObjectTables(KeyOpFieldsValuesTuple &tuple, const std::string &table, bool keyWithIds); + task_process_status handleBufferPgTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handleBufferQueueTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handleBufferPortIngressProfileListTable(KeyOpFieldsValuesTuple &tuple); + task_process_status handleBufferPortEgressProfileListTable(KeyOpFieldsValuesTuple &tuple); void doTask(Consumer &consumer); void doTask(SelectableTimer &timer); }; diff --git a/tests/test_buffer_dynamic.py b/tests/test_buffer_dynamic.py index 329279ab44..e44f2824f5 100644 --- a/tests/test_buffer_dynamic.py +++ b/tests/test_buffer_dynamic.py @@ -39,13 +39,19 @@ def setup_db(self, dvs): # Check whether cable length has been configured fvs = self.config_db.wait_for_entry("CABLE_LENGTH", "AZURE") self.originalCableLen = fvs["Ethernet0"] + self.cableLenBeforeTest = self.originalCableLen if self.originalCableLen == self.cableLenTest1: self.cableLenTest1 = "20m" elif self.originalCableLen == self.cableLenTest2: self.cableLenTest2 = "20m" + elif self.originalCableLen == "0m": + fvs["Ethernet0"] = "5m" + self.originalCableLen = "5m" + self.config_db.update_entry("CABLE_LENGTH", "AZURE", fvs) fvs = {"mmu_size": "12766208"} self.state_db.create_entry("BUFFER_MAX_PARAM_TABLE", "global", fvs) + self.bufferMaxParameter = self.state_db.wait_for_entry("BUFFER_MAX_PARAM_TABLE", "Ethernet0") # The default lossless priority group will be removed ahead of staring test # By doing so all the dynamically generated profiles will be removed and @@ -78,10 +84,18 @@ def setup_db(self, dvs): assert not lossless_profile, \ "There is still lossless profile ({}) {} seconds after all lossless PGs have been removed".format(lossless_profile, seconds_delayed) + time.sleep(10) + self.setup_asic_db(dvs) self.initialized = True + def cleanup_db(self, dvs): + # Clean up: restore the origin cable length + fvs = self.config_db.wait_for_entry("CABLE_LENGTH", "AZURE") + fvs["Ethernet0"] = self.cableLenBeforeTest + self.config_db.update_entry("CABLE_LENGTH", "AZURE", fvs) + def setup_asic_db(self, dvs): buffer_pool_set = set(self.asic_db.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_BUFFER_POOL")) self.initProfileSet = set(self.asic_db.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_BUFFER_PROFILE")) @@ -141,11 +155,17 @@ def change_cable_length(self, cable_length): cable_lengths['Ethernet0'] = cable_length self.config_db.update_entry('CABLE_LENGTH', 'AZURE', cable_lengths) + def check_queues_after_port_startup(self, dvs): + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "{}:0-2".format("Ethernet0"), {"profile": "egress_lossy_profile"}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "{}:3-4".format("Ethernet0"), {"profile": "egress_lossless_profile"}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "{}:5-6".format("Ethernet0"), {"profile": "egress_lossy_profile"}) + def test_changeSpeed(self, dvs, testlog): self.setup_db(dvs) # Startup interface dvs.runcmd('config interface startup Ethernet0') + self.check_queues_after_port_startup(dvs) # Configure lossless PG 3-4 on interface self.config_db.update_entry('BUFFER_PG', 'Ethernet0|3-4', {'profile': 'NULL'}) @@ -192,6 +212,8 @@ def test_changeSpeed(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_changeCableLen(self, dvs, testlog): self.setup_db(dvs) @@ -242,6 +264,8 @@ def test_changeCableLen(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_MultipleLosslessPg(self, dvs, testlog): self.setup_db(dvs) @@ -289,6 +313,8 @@ def test_MultipleLosslessPg(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_headroomOverride(self, dvs, testlog): self.setup_db(dvs) @@ -372,6 +398,8 @@ def test_headroomOverride(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_mtuUpdate(self, dvs, testlog): self.setup_db(dvs) @@ -406,6 +434,8 @@ def test_mtuUpdate(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_nonDefaultAlpha(self, dvs, testlog): self.setup_db(dvs) @@ -448,6 +478,8 @@ def test_nonDefaultAlpha(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_sharedHeadroomPool(self, dvs, testlog): self.setup_db(dvs) @@ -546,11 +578,20 @@ def test_sharedHeadroomPool(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_shutdownPort(self, dvs, testlog): self.setup_db(dvs) lossy_pg_reference_config_db = 'ingress_lossy_profile' lossy_pg_reference_appl_db = 'ingress_lossy_profile' + lossy_queue_reference_config_db = 'egress_lossy_profile' + lossy_queue_reference_appl_db = 'egress_lossy_profile' + lossless_queue_reference_appl_db = 'egress_lossless_profile' + + lossy_pg_zero_reference = 'ingress_lossy_pg_zero_profile' + lossy_queue_zero_reference = 'egress_lossy_zero_profile' + lossless_queue_zero_reference = 'egress_lossless_zero_profile' # Startup interface dvs.runcmd('config interface startup Ethernet0') @@ -560,9 +601,14 @@ def test_shutdownPort(self, dvs, testlog): expectedProfile = self.make_lossless_profile_name(self.originalSpeed, self.originalCableLen) self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:3-4", {"profile": expectedProfile}) - # Shutdown port and check whether all the PGs have been removed + # Shutdown port and check whether zero profiles have been applied on queues and the PG 0 + maximumQueues = int(self.bufferMaxParameter['max_queues']) - 1 dvs.runcmd("config interface shutdown Ethernet0") - self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:0") + self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:0", {"profile": lossy_pg_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:0-2", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:3-4", {"profile": lossless_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:5-6", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:7-{}".format(maximumQueues), {"profile": lossy_queue_zero_reference}) self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:3-4") self.app_db.wait_for_deleted_entry("BUFFER_PROFILE", expectedProfile) @@ -570,25 +616,76 @@ def test_shutdownPort(self, dvs, testlog): self.config_db.update_entry('BUFFER_PG', 'Ethernet0|1', {'profile': lossy_pg_reference_config_db}) self.config_db.update_entry('BUFFER_PG', 'Ethernet0|6', {'profile': 'NULL'}) + # Add extra queue when a port is administratively down + self.config_db.update_entry('BUFFER_QUEUE', 'Ethernet0|8', {"profile": lossy_queue_reference_config_db}) + + # For queues, the slice in supported but not configured list should be '7-19'. + # After queue '8' is added, '7-19' should be removed and '7', '8', and '9-19' will be added + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:7-{}".format(maximumQueues)) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:7", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:8", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:9-{}".format(maximumQueues), {"profile": lossy_queue_zero_reference}) # Make sure they have not been added to APPL_DB time.sleep(30) self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:1") self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:6") - # Startup port and check whether all the PGs haved been added + # Startup port and check whether all the PGs have been added dvs.runcmd("config interface startup Ethernet0") self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:0", {"profile": lossy_pg_reference_appl_db}) self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:1", {"profile": lossy_pg_reference_appl_db}) - self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:3-4", {"profile": expectedProfile }) + self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:3-4", {"profile": expectedProfile}) self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:6", {"profile": expectedProfile}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:0-2", {"profile": lossy_queue_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:5-6", {"profile": lossy_queue_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:8", {"profile": lossy_queue_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:3-4", {"profile": lossless_queue_reference_appl_db}) + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:7") + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:9-{}".format(maximumQueues)) + + # Shutdown the port again to verify flow to remove buffer objects from an admin down port + dvs.runcmd("config interface shutdown Ethernet0") + # First, check whether the objects have been correctly handled + self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:0", {"profile": lossy_pg_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:0-2", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:3-4", {"profile": lossless_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:5-6", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:7", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:8", {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:9-{}".format(maximumQueues), {"profile": lossy_queue_zero_reference}) + self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:3-4") + self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:1") + self.app_db.wait_for_deleted_entry("BUFFER_PG_TABLE", "Ethernet0:6") + self.app_db.wait_for_deleted_entry("BUFFER_PROFILE", expectedProfile) + + # Remove buffer objects from an admon down port + self.config_db.delete_entry('BUFFER_QUEUE', 'Ethernet0|8') + self.config_db.delete_entry('BUFFER_PG', 'Ethernet0|1') + self.config_db.delete_entry('BUFFER_PG', 'Ethernet0|6') + + # Checking + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:7") + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:8") + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:9-{}".format(maximumQueues)) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:7-{}".format(maximumQueues), {"profile": lossy_queue_zero_reference}) + + # Startup again + dvs.runcmd("config interface startup Ethernet0") + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:0-2", {"profile": lossy_queue_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:3-4", {"profile": lossless_queue_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_QUEUE_TABLE", "Ethernet0:5-6", {"profile": lossy_queue_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:0", {"profile": lossy_pg_reference_appl_db}) + self.app_db.wait_for_field_match("BUFFER_PG_TABLE", "Ethernet0:3-4", {"profile": expectedProfile}) + self.app_db.wait_for_deleted_entry("BUFFER_QUEUE_TABLE", "Ethernet0:7-{}".format(maximumQueues)) # Remove lossless PG 3-4 on interface self.config_db.delete_entry('BUFFER_PG', 'Ethernet0|3-4') - self.config_db.delete_entry('BUFFER_PG', 'Ethernet0|6') # Shutdown interface dvs.runcmd("config interface shutdown Ethernet0") + self.cleanup_db(dvs) + def test_autoNegPort(self, dvs, testlog): self.setup_db(dvs) @@ -637,6 +734,8 @@ def test_autoNegPort(self, dvs, testlog): # Shutdown interface dvs.runcmd('config interface shutdown Ethernet0') + self.cleanup_db(dvs) + def test_removeBufferPool(self, dvs, testlog): self.setup_db(dvs) # Initialize additional databases that are used by this test only @@ -668,9 +767,13 @@ def test_removeBufferPool(self, dvs, testlog): if counter_poll_disabled: self.config_db.delete_entry("FLEX_COUNTER_TABLE", "BUFFER_POOL_WATERMARK") + self.cleanup_db(dvs) + def test_bufferPortMaxParameter(self, dvs, testlog): self.setup_db(dvs) # Check whether port's maximum parameter has been exposed to STATE_DB fvs = self.state_db.wait_for_entry("BUFFER_MAX_PARAM_TABLE", "Ethernet0") assert int(fvs["max_queues"]) and int(fvs["max_priority_groups"]) + + self.cleanup_db(dvs) From f6f6f86782967cc4079a9cf48649323c37cb4835 Mon Sep 17 00:00:00 2001 From: novikauanton Date: Mon, 29 Nov 2021 20:22:20 +0200 Subject: [PATCH 10/14] [mclaglink] fix acl out ports (#2026) --- mclagsyncd/mclaglink.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mclagsyncd/mclaglink.cpp b/mclagsyncd/mclaglink.cpp index 7e7a9aff27..68b700fdb9 100644 --- a/mclagsyncd/mclaglink.cpp +++ b/mclagsyncd/mclaglink.cpp @@ -334,7 +334,6 @@ void MclagLink::setPortIsolate(char *msg) acl_rule_attrs.push_back(ip_type_attr); string temp; - isolate_dst_port.insert(0, (const char*)cur, op_hdr->op_len); istringstream dst_ss(isolate_dst_port); isolate_dst_port.clear(); From bcb7d61ae5daf8d4e1b4fffb34ffc4a66c0fdc9d Mon Sep 17 00:00:00 2001 From: donNewtonAlpha Date: Tue, 30 Nov 2021 12:04:04 -0500 Subject: [PATCH 11/14] P4Orch: inital add of source (#1997) Add P4orch code and subdir Co-authored-by: PINS Working Group --- orchagent/p4orch/acl_rule_manager.cpp | 2009 ++++++++ orchagent/p4orch/acl_rule_manager.h | 153 + orchagent/p4orch/acl_table_manager.cpp | 901 ++++ orchagent/p4orch/acl_table_manager.h | 106 + orchagent/p4orch/acl_util.cpp | 874 ++++ orchagent/p4orch/acl_util.h | 710 +++ orchagent/p4orch/mirror_session_manager.cpp | 726 +++ orchagent/p4orch/mirror_session_manager.h | 122 + orchagent/p4orch/neighbor_manager.cpp | 376 ++ orchagent/p4orch/neighbor_manager.h | 73 + orchagent/p4orch/next_hop_manager.cpp | 333 ++ orchagent/p4orch/next_hop_manager.h | 94 + orchagent/p4orch/object_manager_interface.h | 15 + orchagent/p4orch/p4oidmapper.cpp | 180 + orchagent/p4orch/p4oidmapper.h | 83 + orchagent/p4orch/p4orch.cpp | 237 + orchagent/p4orch/p4orch.h | 64 + orchagent/p4orch/p4orch_util.cpp | 103 + orchagent/p4orch/p4orch_util.h | 226 + orchagent/p4orch/route_manager.cpp | 579 +++ orchagent/p4orch/route_manager.h | 102 + orchagent/p4orch/router_interface_manager.cpp | 398 ++ orchagent/p4orch/router_interface_manager.h | 73 + orchagent/p4orch/tests/Makefile.am | 88 + orchagent/p4orch/tests/acl_manager_test.cpp | 4253 +++++++++++++++++ .../p4orch/tests/fake_consumerstatetable.cpp | 11 + orchagent/p4orch/tests/fake_crmorch.cpp | 67 + orchagent/p4orch/tests/fake_dbconnector.cpp | 46 + .../tests/fake_notificationconsumer.cpp | 13 + orchagent/p4orch/tests/fake_portorch.cpp | 691 +++ orchagent/p4orch/tests/fake_producertable.cpp | 27 + .../tests/fake_subscriberstatetable.cpp | 11 + orchagent/p4orch/tests/fake_table.cpp | 97 + .../tests/mirror_session_manager_test.cpp | 1011 ++++ .../p4orch/tests/mock_response_publisher.h | 19 + orchagent/p4orch/tests/mock_sai_acl.cpp | 68 + orchagent/p4orch/tests/mock_sai_acl.h | 87 + orchagent/p4orch/tests/mock_sai_hostif.cpp | 67 + orchagent/p4orch/tests/mock_sai_hostif.h | 76 + orchagent/p4orch/tests/mock_sai_mirror.h | 53 + orchagent/p4orch/tests/mock_sai_neighbor.h | 50 + orchagent/p4orch/tests/mock_sai_next_hop.h | 51 + .../p4orch/tests/mock_sai_next_hop_group.h | 64 + orchagent/p4orch/tests/mock_sai_policer.h | 53 + orchagent/p4orch/tests/mock_sai_route.h | 108 + .../p4orch/tests/mock_sai_router_interface.h | 51 + orchagent/p4orch/tests/mock_sai_serialize.cpp | 13 + orchagent/p4orch/tests/mock_sai_serialize.h | 25 + orchagent/p4orch/tests/mock_sai_switch.cpp | 14 + orchagent/p4orch/tests/mock_sai_switch.h | 24 + orchagent/p4orch/tests/mock_sai_udf.cpp | 36 + orchagent/p4orch/tests/mock_sai_udf.h | 54 + .../p4orch/tests/mock_sai_virtual_router.h | 50 + .../p4orch/tests/neighbor_manager_test.cpp | 822 ++++ .../p4orch/tests/next_hop_manager_test.cpp | 709 +++ orchagent/p4orch/tests/p4oidmapper_test.cpp | 122 + orchagent/p4orch/tests/p4orch_util_test.cpp | 80 + orchagent/p4orch/tests/return_code_test.cpp | 176 + orchagent/p4orch/tests/route_manager_test.cpp | 1558 ++++++ .../tests/router_interface_manager_test.cpp | 843 ++++ orchagent/p4orch/tests/test_main.cpp | 201 + orchagent/p4orch/tests/wcmp_manager_test.cpp | 1852 +++++++ orchagent/p4orch/wcmp_manager.cpp | 760 +++ orchagent/p4orch/wcmp_manager.h | 178 + 64 files changed, 23116 insertions(+) create mode 100644 orchagent/p4orch/acl_rule_manager.cpp create mode 100644 orchagent/p4orch/acl_rule_manager.h create mode 100644 orchagent/p4orch/acl_table_manager.cpp create mode 100644 orchagent/p4orch/acl_table_manager.h create mode 100644 orchagent/p4orch/acl_util.cpp create mode 100644 orchagent/p4orch/acl_util.h create mode 100644 orchagent/p4orch/mirror_session_manager.cpp create mode 100644 orchagent/p4orch/mirror_session_manager.h create mode 100644 orchagent/p4orch/neighbor_manager.cpp create mode 100644 orchagent/p4orch/neighbor_manager.h create mode 100644 orchagent/p4orch/next_hop_manager.cpp create mode 100644 orchagent/p4orch/next_hop_manager.h create mode 100644 orchagent/p4orch/object_manager_interface.h create mode 100644 orchagent/p4orch/p4oidmapper.cpp create mode 100644 orchagent/p4orch/p4oidmapper.h create mode 100644 orchagent/p4orch/p4orch.cpp create mode 100644 orchagent/p4orch/p4orch.h create mode 100644 orchagent/p4orch/p4orch_util.cpp create mode 100644 orchagent/p4orch/p4orch_util.h create mode 100644 orchagent/p4orch/route_manager.cpp create mode 100644 orchagent/p4orch/route_manager.h create mode 100644 orchagent/p4orch/router_interface_manager.cpp create mode 100644 orchagent/p4orch/router_interface_manager.h create mode 100644 orchagent/p4orch/tests/Makefile.am create mode 100644 orchagent/p4orch/tests/acl_manager_test.cpp create mode 100644 orchagent/p4orch/tests/fake_consumerstatetable.cpp create mode 100644 orchagent/p4orch/tests/fake_crmorch.cpp create mode 100644 orchagent/p4orch/tests/fake_dbconnector.cpp create mode 100644 orchagent/p4orch/tests/fake_notificationconsumer.cpp create mode 100644 orchagent/p4orch/tests/fake_portorch.cpp create mode 100644 orchagent/p4orch/tests/fake_producertable.cpp create mode 100644 orchagent/p4orch/tests/fake_subscriberstatetable.cpp create mode 100644 orchagent/p4orch/tests/fake_table.cpp create mode 100644 orchagent/p4orch/tests/mirror_session_manager_test.cpp create mode 100644 orchagent/p4orch/tests/mock_response_publisher.h create mode 100644 orchagent/p4orch/tests/mock_sai_acl.cpp create mode 100644 orchagent/p4orch/tests/mock_sai_acl.h create mode 100644 orchagent/p4orch/tests/mock_sai_hostif.cpp create mode 100644 orchagent/p4orch/tests/mock_sai_hostif.h create mode 100644 orchagent/p4orch/tests/mock_sai_mirror.h create mode 100644 orchagent/p4orch/tests/mock_sai_neighbor.h create mode 100644 orchagent/p4orch/tests/mock_sai_next_hop.h create mode 100644 orchagent/p4orch/tests/mock_sai_next_hop_group.h create mode 100644 orchagent/p4orch/tests/mock_sai_policer.h create mode 100644 orchagent/p4orch/tests/mock_sai_route.h create mode 100644 orchagent/p4orch/tests/mock_sai_router_interface.h create mode 100644 orchagent/p4orch/tests/mock_sai_serialize.cpp create mode 100644 orchagent/p4orch/tests/mock_sai_serialize.h create mode 100644 orchagent/p4orch/tests/mock_sai_switch.cpp create mode 100644 orchagent/p4orch/tests/mock_sai_switch.h create mode 100644 orchagent/p4orch/tests/mock_sai_udf.cpp create mode 100644 orchagent/p4orch/tests/mock_sai_udf.h create mode 100644 orchagent/p4orch/tests/mock_sai_virtual_router.h create mode 100644 orchagent/p4orch/tests/neighbor_manager_test.cpp create mode 100644 orchagent/p4orch/tests/next_hop_manager_test.cpp create mode 100644 orchagent/p4orch/tests/p4oidmapper_test.cpp create mode 100644 orchagent/p4orch/tests/p4orch_util_test.cpp create mode 100644 orchagent/p4orch/tests/return_code_test.cpp create mode 100644 orchagent/p4orch/tests/route_manager_test.cpp create mode 100644 orchagent/p4orch/tests/router_interface_manager_test.cpp create mode 100644 orchagent/p4orch/tests/test_main.cpp create mode 100644 orchagent/p4orch/tests/wcmp_manager_test.cpp create mode 100644 orchagent/p4orch/wcmp_manager.cpp create mode 100644 orchagent/p4orch/wcmp_manager.h diff --git a/orchagent/p4orch/acl_rule_manager.cpp b/orchagent/p4orch/acl_rule_manager.cpp new file mode 100644 index 0000000000..3984fd956f --- /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 0000000000..cc00735d84 --- /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 0000000000..456c2f04d2 --- /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 0000000000..6243c08cb4 --- /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 0000000000..6caf67cade --- /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 0000000000..c06849506b --- /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 0000000000..067bc5aa1a --- /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 0000000000..c41dc07eb3 --- /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 0000000000..059aa76698 --- /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 0000000000..2ede9de763 --- /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 0000000000..3e2d9ff548 --- /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 0000000000..7b4a318f87 --- /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 0000000000..ec9775f8e4 --- /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 0000000000..f4ff6e3433 --- /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 0000000000..6f7b86ab8f --- /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 0000000000..ada1fa2c77 --- /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 0000000000..42159f3981 --- /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 0000000000..e5d4479436 --- /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 0000000000..a3684a5fb8 --- /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 0000000000..7732a143e5 --- /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 0000000000..6e494709b3 --- /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 0000000000..ea9abf083a --- /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 0000000000..a300b2a7a4 --- /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 0000000000..489acd8f99 --- /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 0000000000..64ba37e5a3 --- /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 0000000000..045950abfd --- /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 0000000000..03f15c28ac --- /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 0000000000..1709d9d977 --- /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 0000000000..b903b336ab --- /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 0000000000..aaf766e1aa --- /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 0000000000..703be025eb --- /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 0000000000..729fbcefae --- /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 0000000000..e1f785f048 --- /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 0000000000..c45a0d9bcd --- /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 0000000000..d163333bd0 --- /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 0000000000..531a71b3a5 --- /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 0000000000..252eb1768a --- /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 0000000000..7dcc0f70c2 --- /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 0000000000..0e758aeebd --- /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 0000000000..39991d583e --- /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 0000000000..cd8f2aa0a9 --- /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 0000000000..83e6e7d506 --- /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 0000000000..5398ec5a70 --- /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 0000000000..351cca14c0 --- /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 0000000000..b40cf6605e --- /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 0000000000..9c0caa3004 --- /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 0000000000..ada42acf02 --- /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 0000000000..4e4ae50573 --- /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 0000000000..180de2d6f9 --- /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 0000000000..2883178ea3 --- /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 0000000000..4133988c8b --- /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 0000000000..251f8c2b1a --- /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 0000000000..943b9b4828 --- /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 0000000000..e3986ef701 --- /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 0000000000..a78310cc8d --- /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 0000000000..131ee7aedb --- /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 0000000000..8c171b1e6e --- /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 0000000000..7a866827d7 --- /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 0000000000..de1238761b --- /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 0000000000..661fe33efa --- /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 0000000000..23cf37d8e1 --- /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 0000000000..73cf34be25 --- /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 0000000000..6078f92221 --- /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 0000000000..4c6629a398 --- /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 From e9b05a31789ce7672614807026c7096a4202da32 Mon Sep 17 00:00:00 2001 From: Shi Su <67605788+shi-su@users.noreply.github.com> Date: Tue, 30 Nov 2021 16:00:16 -0800 Subject: [PATCH 12/14] [vnetorch] ECMP for vnet tunnel routes with endpoint health monitor (#1955) What I did Add functions to create/remove next hop groups for vnet tunnel routes. Count the reference count of next hop groups to create and remove as needed. Share the counter of next hop groups with routeorch. Adapt route endpoint according to the BFD state of endpoints. Why I did it To add support for overlay ECMP. How I verified it Verify ECMP groups are properly created and removed with the functions. --- orchagent/orchdaemon.cpp | 5 +- orchagent/vnetorch.cpp | 482 ++++++++++++++++++++++++++++++++++++++- orchagent/vnetorch.h | 42 +++- tests/test_vnet.py | 438 +++++++++++++++++++++++++++++++++++ 4 files changed, 949 insertions(+), 18 deletions(-) diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 52beb0fd10..14e4d8aa77 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -122,6 +122,8 @@ bool OrchDaemon::init() TableConnector stateDbFdb(m_stateDb, STATE_FDB_TABLE_NAME); TableConnector stateMclagDbFdb(m_stateDb, STATE_MCLAG_REMOTE_FDB_TABLE_NAME); gFdbOrch = new FdbOrch(m_applDb, app_fdb_tables, stateDbFdb, stateMclagDbFdb, gPortsOrch); + TableConnector stateDbBfdSessionTable(m_stateDb, STATE_BFD_SESSION_TABLE_NAME); + gBfdOrch = new BfdOrch(m_applDb, APP_BFD_SESSION_TABLE_NAME, stateDbBfdSessionTable); vector vnet_tables = { APP_VNET_RT_TABLE_NAME, @@ -309,9 +311,6 @@ bool OrchDaemon::init() gMacsecOrch = new MACsecOrch(m_applDb, m_stateDb, macsec_app_tables, gPortsOrch); - TableConnector stateDbBfdSessionTable(m_stateDb, STATE_BFD_SESSION_TABLE_NAME); - gBfdOrch = new BfdOrch(m_applDb, APP_BFD_SESSION_TABLE_NAME, stateDbBfdSessionTable); - gNhgMapOrch = new NhgMapOrch(m_applDb, APP_FC_TO_NHG_INDEX_MAP_TABLE_NAME); /* diff --git a/orchagent/vnetorch.cpp b/orchagent/vnetorch.cpp index 053784e298..dc5838d8a5 100644 --- a/orchagent/vnetorch.cpp +++ b/orchagent/vnetorch.cpp @@ -39,6 +39,7 @@ extern NeighOrch *gNeighOrch; extern CrmOrch *gCrmOrch; extern RouteOrch *gRouteOrch; extern MacAddress gVxlanMacAddress; +extern BfdOrch *gBfdOrch; /* * VRF Modeling and VNetVrf class definitions @@ -558,9 +559,14 @@ static bool del_route(sai_object_id_t vr_id, sai_ip_prefix_t& ip_pfx) route_entry.destination = ip_pfx; sai_status_t status = sai_route_api->remove_route_entry(&route_entry); - if (status != SAI_STATUS_SUCCESS) + if (status == SAI_STATUS_ITEM_NOT_FOUND || status == SAI_STATUS_INVALID_PARAMETER) + { + SWSS_LOG_INFO("Unable to remove route since route is already removed"); + return true; + } + else if (status != SAI_STATUS_SUCCESS) { - SWSS_LOG_ERROR("SAI Failed to remove route"); + SWSS_LOG_ERROR("SAI Failed to remove route, rv: %d", status); return false; } @@ -630,12 +636,17 @@ static bool update_route(sai_object_id_t vr_id, sai_ip_prefix_t& ip_pfx, sai_obj } VNetRouteOrch::VNetRouteOrch(DBConnector *db, vector &tableNames, VNetOrch *vnetOrch) - : Orch2(db, tableNames, request_), vnet_orch_(vnetOrch) + : Orch2(db, tableNames, request_), vnet_orch_(vnetOrch), bfd_session_producer_(db, APP_BFD_SESSION_TABLE_NAME) { SWSS_LOG_ENTER(); handler_map_.insert(handler_pair(APP_VNET_RT_TABLE_NAME, &VNetRouteOrch::handleRoutes)); handler_map_.insert(handler_pair(APP_VNET_RT_TUNNEL_TABLE_NAME, &VNetRouteOrch::handleTunnel)); + + state_db_ = shared_ptr(new DBConnector("STATE_DB", 0)); + state_vnet_rt_tunnel_table_ = unique_ptr(new Table(state_db_.get(), STATE_VNET_RT_TUNNEL_TABLE_NAME)); + + gBfdOrch->attach(this); } bool VNetRouteOrch::hasNextHopGroup(const string& vnet, const NextHopGroupKey& nexthops) @@ -667,6 +678,10 @@ bool VNetRouteOrch::addNextHopGroup(const string& vnet, const NextHopGroupKey &n for (auto it : next_hop_set) { + if (nexthop_info_[vnet].find(it.ip_address) != nexthop_info_[vnet].end() && nexthop_info_[vnet][it.ip_address].bfd_state != SAI_BFD_SESSION_STATE_UP) + { + continue; + } sai_object_id_t next_hop_id = vrf_obj->getTunnelNextHop(it); next_hop_ids.push_back(next_hop_id); nhopgroup_members_set[next_hop_id] = it; @@ -797,7 +812,8 @@ bool VNetRouteOrch::removeNextHopGroup(const string& vnet, const NextHopGroupKey template<> bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipPrefix, - NextHopGroupKey& nexthops, string& op) + NextHopGroupKey& nexthops, string& op, + const map& monitors) { SWSS_LOG_ENTER(); @@ -835,9 +851,9 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP if (op == SET_COMMAND) { sai_object_id_t nh_id; - /* The route is pointing to one single endpoint */ if (!hasNextHopGroup(vnet, nexthops)) { + setEndpointMonitor(vnet, monitors, nexthops); if (nexthops.getSize() == 1) { NextHopKey nexthop(nexthops.to_string(), true); @@ -851,6 +867,7 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP { if (!addNextHopGroup(vnet, nexthops, vrf_obj)) { + delEndpointMonitor(vnet, nexthops); SWSS_LOG_ERROR("Failed to create next hop group %s", nexthops.to_string().c_str()); return false; } @@ -863,13 +880,37 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP { bool route_status = true; - if (it_route == syncd_tunnel_routes_[vnet].end()) + // Remove route if the nexthop group has no active endpoint + if (syncd_nexthop_groups_[vnet][nexthops].active_members.empty()) { - route_status = add_route(vr_id, pfx, nh_id); + if (it_route != syncd_tunnel_routes_[vnet].end()) + { + NextHopGroupKey nhg = it_route->second; + // Remove route when updating from a nhg with active member to another nhg without + if (!syncd_nexthop_groups_[vnet][nhg].active_members.empty()) + { + del_route(vr_id, pfx); + } + } } else { - route_status = update_route(vr_id, pfx, nh_id); + if (it_route == syncd_tunnel_routes_[vnet].end()) + { + route_status = add_route(vr_id, pfx, nh_id); + } + else + { + NextHopGroupKey nhg = it_route->second; + if (syncd_nexthop_groups_[vnet][nhg].active_members.empty()) + { + route_status = add_route(vr_id, pfx, nh_id); + } + else + { + route_status = update_route(vr_id, pfx, nh_id); + } + } } if (!route_status) @@ -900,13 +941,22 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP NextHopKey nexthop(nhg.to_string(), true); vrf_obj->removeTunnelNextHop(nexthop); } + delEndpointMonitor(vnet, nhg); + } + else + { + syncd_nexthop_groups_[vnet][nhg].tunnel_routes.erase(ipPrefix); } vrf_obj->removeRoute(ipPrefix); } + syncd_nexthop_groups_[vnet][nexthops].tunnel_routes.insert(ipPrefix); + syncd_tunnel_routes_[vnet][ipPrefix] = nexthops; syncd_nexthop_groups_[vnet][nexthops].ref_count++; vrf_obj->addRoute(ipPrefix, nexthops); + + postRouteState(vnet, ipPrefix, nexthops); } else if (op == DEL_COMMAND) { @@ -921,10 +971,14 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP for (auto vr_id : vr_set) { - if (!del_route(vr_id, pfx)) + // If an nhg has no active member, the route should already be removed + if (!syncd_nexthop_groups_[vnet][nhg].active_members.empty()) { - SWSS_LOG_ERROR("Route del failed for %s, vr_id '0x%" PRIx64, ipPrefix.to_string().c_str(), vr_id); - return false; + if (!del_route(vr_id, pfx)) + { + SWSS_LOG_ERROR("Route del failed for %s, vr_id '0x%" PRIx64, ipPrefix.to_string().c_str(), vr_id); + return false; + } } } @@ -940,6 +994,11 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP NextHopKey nexthop(nhg.to_string(), true); vrf_obj->removeTunnelNextHop(nexthop); } + delEndpointMonitor(vnet, nhg); + } + else + { + syncd_nexthop_groups_[vnet][nhg].tunnel_routes.erase(ipPrefix); } syncd_tunnel_routes_[vnet].erase(ipPrefix); @@ -949,6 +1008,84 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP } vrf_obj->removeRoute(ipPrefix); + + removeRouteState(vnet, ipPrefix); + } + + return true; +} + +bool VNetRouteOrch::updateTunnelRoute(const string& vnet, IpPrefix& ipPrefix, + NextHopGroupKey& nexthops, string& op) +{ + SWSS_LOG_ENTER(); + + if (!vnet_orch_->isVnetExists(vnet)) + { + SWSS_LOG_WARN("VNET %s doesn't exist for prefix %s, op %s", + vnet.c_str(), ipPrefix.to_string().c_str(), op.c_str()); + return (op == DEL_COMMAND)?true:false; + } + + set vr_set; + auto& peer_list = vnet_orch_->getPeerList(vnet); + + auto l_fn = [&] (const string& vnet) { + auto *vnet_obj = vnet_orch_->getTypePtr(vnet); + sai_object_id_t vr_id = vnet_obj->getVRidIngress(); + vr_set.insert(vr_id); + }; + + l_fn(vnet); + for (auto peer : peer_list) + { + if (!vnet_orch_->isVnetExists(peer)) + { + SWSS_LOG_INFO("Peer VNET %s not yet created", peer.c_str()); + return false; + } + l_fn(peer); + } + + sai_ip_prefix_t pfx; + copy(pfx, ipPrefix); + + if (op == SET_COMMAND) + { + sai_object_id_t nh_id = syncd_nexthop_groups_[vnet][nexthops].next_hop_group_id; + + for (auto vr_id : vr_set) + { + bool route_status = true; + + route_status = add_route(vr_id, pfx, nh_id); + + if (!route_status) + { + SWSS_LOG_ERROR("Route add failed for %s, vr_id '0x%" PRIx64, ipPrefix.to_string().c_str(), vr_id); + return false; + } + } + } + else if (op == DEL_COMMAND) + { + auto it_route = syncd_tunnel_routes_[vnet].find(ipPrefix); + if (it_route == syncd_tunnel_routes_[vnet].end()) + { + SWSS_LOG_INFO("Failed to find tunnel route entry, prefix %s\n", + ipPrefix.to_string().c_str()); + return true; + } + NextHopGroupKey nhg = it_route->second; + + for (auto vr_id : vr_set) + { + if (!del_route(vr_id, pfx)) + { + SWSS_LOG_ERROR("Route del failed for %s, vr_id '0x%" PRIx64, ipPrefix.to_string().c_str(), vr_id); + return false; + } + } } return true; @@ -1315,6 +1452,311 @@ void VNetRouteOrch::delRoute(const IpPrefix& ipPrefix) syncd_routes_.erase(route_itr); } +void VNetRouteOrch::createBfdSession(const string& vnet, const NextHopKey& endpoint, const IpAddress& monitor_addr) +{ + SWSS_LOG_ENTER(); + + IpAddress endpoint_addr = endpoint.ip_address; + if (nexthop_info_[vnet].find(endpoint_addr) != nexthop_info_[vnet].end()) + { + SWSS_LOG_ERROR("BFD session for endpoint %s already exist", endpoint_addr.to_string().c_str()); + return; + } + + if (bfd_sessions_.find(monitor_addr) == bfd_sessions_.end()) + { + vector data; + string key = "default:default:" + monitor_addr.to_string(); + + auto tun_name = vnet_orch_->getTunnelName(vnet); + VxlanTunnelOrch* vxlan_orch = gDirectory.get(); + auto tunnel_obj = vxlan_orch->getVxlanTunnel(tun_name); + IpAddress src_ip = tunnel_obj->getSrcIP(); + + FieldValueTuple fvTuple("local_addr", src_ip.to_string()); + data.push_back(fvTuple); + + bfd_session_producer_.set(key, data); + + bfd_sessions_[monitor_addr].bfd_state = SAI_BFD_SESSION_STATE_DOWN; + } + + BfdSessionInfo& bfd_info = bfd_sessions_[monitor_addr]; + bfd_info.vnet = vnet; + bfd_info.endpoint = endpoint; + VNetNextHopInfo nexthop_info; + nexthop_info.monitor_addr = monitor_addr; + nexthop_info.bfd_state = bfd_info.bfd_state; + nexthop_info.ref_count = 0; + nexthop_info_[vnet][endpoint_addr] = nexthop_info; +} + +void VNetRouteOrch::removeBfdSession(const string& vnet, const NextHopKey& endpoint, const IpAddress& monitor_addr) +{ + SWSS_LOG_ENTER(); + + IpAddress endpoint_addr = endpoint.ip_address; + if (nexthop_info_[vnet].find(endpoint_addr) == nexthop_info_[vnet].end()) + { + SWSS_LOG_ERROR("BFD session for endpoint %s does not exist", endpoint_addr.to_string().c_str()); + } + nexthop_info_[vnet].erase(endpoint_addr); + + string key = "default:default:" + monitor_addr.to_string(); + + bfd_session_producer_.del(key); + + bfd_sessions_.erase(monitor_addr); +} + +void VNetRouteOrch::setEndpointMonitor(const string& vnet, const map& monitors, NextHopGroupKey& nexthops) +{ + SWSS_LOG_ENTER(); + + for (auto monitor : monitors) + { + NextHopKey nh = monitor.first; + IpAddress monitor_ip = monitor.second; + if (nexthop_info_[vnet].find(nh.ip_address) == nexthop_info_[vnet].end()) + { + createBfdSession(vnet, nh, monitor_ip); + } + + nexthop_info_[vnet][nh.ip_address].ref_count++; + } +} + +void VNetRouteOrch::delEndpointMonitor(const string& vnet, NextHopGroupKey& nexthops) +{ + SWSS_LOG_ENTER(); + + std::set nhks = nexthops.getNextHops(); + for (auto nhk: nhks) + { + IpAddress ip = nhk.ip_address; + if (nexthop_info_[vnet].find(ip) != nexthop_info_[vnet].end()) { + if (--nexthop_info_[vnet][ip].ref_count == 0) + { + removeBfdSession(vnet, nhk, nexthop_info_[vnet][ip].monitor_addr); + } + } + } +} + +void VNetRouteOrch::postRouteState(const string& vnet, IpPrefix& ipPrefix, NextHopGroupKey& nexthops) +{ + const string state_db_key = vnet + state_db_key_delimiter + ipPrefix.to_string(); + vector fvVector; + + NextHopGroupInfo& nhg_info = syncd_nexthop_groups_[vnet][nexthops]; + string route_state = nhg_info.active_members.empty() ? "inactive" : "active"; + string ep_str = ""; + int idx_ep = 0; + for (auto nh_pair : nhg_info.active_members) + { + NextHopKey nh = nh_pair.first; + ep_str += idx_ep == 0 ? nh.ip_address.to_string() : "," + nh.ip_address.to_string(); + idx_ep++; + } + + fvVector.emplace_back("active_endpoints", ep_str); + fvVector.emplace_back("state", route_state); + + state_vnet_rt_tunnel_table_->set(state_db_key, fvVector); +} + +void VNetRouteOrch::removeRouteState(const string& vnet, IpPrefix& ipPrefix) +{ + const string state_db_key = vnet + state_db_key_delimiter + ipPrefix.to_string(); + state_vnet_rt_tunnel_table_->del(state_db_key); +} + +void VNetRouteOrch::update(SubjectType type, void *cntx) +{ + SWSS_LOG_ENTER(); + + assert(cntx); + + switch(type) { + case SUBJECT_TYPE_BFD_SESSION_STATE_CHANGE: + { + BfdUpdate *update = static_cast(cntx); + updateVnetTunnel(*update); + break; + } + default: + // Received update in which we are not interested + // Ignore it + return; + } +} + +void VNetRouteOrch::updateVnetTunnel(const BfdUpdate& update) +{ + SWSS_LOG_ENTER(); + + auto key = update.peer; + sai_bfd_session_state_t state = update.state; + + size_t found_vrf = key.find(state_db_key_delimiter); + if (found_vrf == string::npos) + { + SWSS_LOG_ERROR("Failed to parse key %s, no vrf is given", key.c_str()); + return; + } + + size_t found_ifname = key.find(state_db_key_delimiter, found_vrf + 1); + if (found_ifname == string::npos) + { + SWSS_LOG_ERROR("Failed to parse key %s, no ifname is given", key.c_str()); + return; + } + + string vrf_name = key.substr(0, found_vrf); + string alias = key.substr(found_vrf + 1, found_ifname - found_vrf - 1); + IpAddress peer_address(key.substr(found_ifname + 1)); + + if (alias != "default" || vrf_name != "default") + { + return; + } + + auto it_peer = bfd_sessions_.find(peer_address); + + if (it_peer == bfd_sessions_.end()) { + SWSS_LOG_INFO("No endpoint for BFD peer %s", peer_address.to_string().c_str()); + return; + } + + BfdSessionInfo& bfd_info = it_peer->second; + bfd_info.bfd_state = state; + + string vnet = bfd_info.vnet; + NextHopKey endpoint = bfd_info.endpoint; + auto *vrf_obj = vnet_orch_->getTypePtr(vnet); + + if (syncd_nexthop_groups_.find(vnet) == syncd_nexthop_groups_.end()) + { + SWSS_LOG_ERROR("Vnet %s not found", vnet.c_str()); + return; + } + + nexthop_info_[vnet][endpoint.ip_address].bfd_state = state; + + for (auto& nhg_info_pair : syncd_nexthop_groups_[vnet]) + { + NextHopGroupKey nexthops = nhg_info_pair.first; + NextHopGroupInfo& nhg_info = nhg_info_pair.second; + + if (!(nexthops.contains(endpoint))) + { + continue; + } + + if (state == SAI_BFD_SESSION_STATE_UP) + { + sai_object_id_t next_hop_group_member_id = SAI_NULL_OBJECT_ID; + if (nexthops.getSize() > 1) + { + // Create a next hop group member + vector nhgm_attrs; + + sai_attribute_t nhgm_attr; + nhgm_attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID; + nhgm_attr.value.oid = nhg_info.next_hop_group_id; + nhgm_attrs.push_back(nhgm_attr); + + nhgm_attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID; + nhgm_attr.value.oid = vrf_obj->getTunnelNextHop(endpoint); + nhgm_attrs.push_back(nhgm_attr); + + sai_status_t status = sai_next_hop_group_api->create_next_hop_group_member(&next_hop_group_member_id, + gSwitchId, + (uint32_t)nhgm_attrs.size(), + nhgm_attrs.data()); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to add next hop member to group %" PRIx64 ": %d\n", + nhg_info.next_hop_group_id, status); + task_process_status handle_status = handleSaiCreateStatus(SAI_API_NEXT_HOP_GROUP, status); + if (handle_status != task_success) + { + continue; + } + } + + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); + } + + // Re-create routes when it was temporarily removed + if (nhg_info.active_members.empty()) + { + nhg_info.active_members[endpoint] = next_hop_group_member_id; + if (vnet_orch_->isVnetExecVrf()) + { + for (auto ip_pfx : syncd_nexthop_groups_[vnet][nexthops].tunnel_routes) + { + string op = SET_COMMAND; + updateTunnelRoute(vnet, ip_pfx, nexthops, op); + } + } + } + else + { + nhg_info.active_members[endpoint] = next_hop_group_member_id; + } + } + else + { + if (nexthops.getSize() > 1 && nhg_info.active_members.find(endpoint) != nhg_info.active_members.end()) + { + sai_object_id_t nexthop_id = nhg_info.active_members[endpoint]; + sai_status_t status = sai_next_hop_group_api->remove_next_hop_group_member(nexthop_id); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove next hop member %" PRIx64 " from group %" PRIx64 ": %d\n", + nexthop_id, nhg_info.next_hop_group_id, status); + task_process_status handle_status = handleSaiRemoveStatus(SAI_API_NEXT_HOP_GROUP, status); + if (handle_status != task_success) + { + continue; + } + } + + vrf_obj->removeTunnelNextHop(endpoint); + + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); + } + + if (nhg_info.active_members.find(endpoint) != nhg_info.active_members.end()) + { + nhg_info.active_members.erase(endpoint); + + // Remove routes when nexthop group has no active endpoint + if (nhg_info.active_members.empty()) + { + if (vnet_orch_->isVnetExecVrf()) + { + for (auto ip_pfx : syncd_nexthop_groups_[vnet][nexthops].tunnel_routes) + { + string op = DEL_COMMAND; + updateTunnelRoute(vnet, ip_pfx, nexthops, op); + } + } + } + } + } + + // Post configured in State DB + for (auto ip_pfx : syncd_nexthop_groups_[vnet][nexthops].tunnel_routes) + { + postRouteState(vnet, ip_pfx, nexthops); + } + } +} + bool VNetRouteOrch::handleTunnel(const Request& request) { SWSS_LOG_ENTER(); @@ -1322,6 +1764,7 @@ bool VNetRouteOrch::handleTunnel(const Request& request) vector ip_list; vector mac_list; vector vni_list; + vector monitor_list; for (const auto& name: request.getAttrFieldNames()) { @@ -1339,6 +1782,10 @@ bool VNetRouteOrch::handleTunnel(const Request& request) string mac_str = request.getAttrString(name); mac_list = tokenize(mac_str, ','); } + else if (name == "endpoint_monitor") + { + monitor_list = request.getAttrIPList(name); + } else { SWSS_LOG_INFO("Unknown attribute: %s", name.c_str()); @@ -1358,6 +1805,12 @@ bool VNetRouteOrch::handleTunnel(const Request& request) return false; } + if (!monitor_list.empty() && monitor_list.size() != ip_list.size()) + { + SWSS_LOG_ERROR("Peer monitor size of %zu does not match endpoint size of %zu", monitor_list.size(), ip_list.size()); + return false; + } + const std::string& vnet_name = request.getKeyString(0); auto ip_pfx = request.getKeyIpPrefix(1); auto op = request.getOperation(); @@ -1366,6 +1819,7 @@ bool VNetRouteOrch::handleTunnel(const Request& request) op.c_str(), ip_pfx.to_string().c_str()); NextHopGroupKey nhg("", true); + map monitors; for (size_t idx_ip = 0; idx_ip < ip_list.size(); idx_ip++) { IpAddress ip = ip_list[idx_ip]; @@ -1387,11 +1841,15 @@ bool VNetRouteOrch::handleTunnel(const Request& request) NextHopKey nh(ip, mac, vni, true); nhg.add(nh); + if (!monitor_list.empty()) + { + monitors[nh] = monitor_list[idx_ip]; + } } if (vnet_orch_->isVnetExecVrf()) { - return doRouteTask(vnet_name, ip_pfx, nhg, op); + return doRouteTask(vnet_name, ip_pfx, nhg, op, monitors); } return true; diff --git a/orchagent/vnetorch.h b/orchagent/vnetorch.h index 569a23f2e0..7e493c5f30 100644 --- a/orchagent/vnetorch.h +++ b/orchagent/vnetorch.h @@ -13,6 +13,7 @@ #include "producerstatetable.h" #include "observer.h" #include "nexthopgroupkey.h" +#include "bfdorch.h" #define VNET_BITMAP_SIZE 32 #define VNET_TUNNEL_SIZE 40960 @@ -72,6 +73,7 @@ struct NextHopGroupInfo sai_object_id_t next_hop_group_id; // next hop group id (null for single nexthop) int ref_count; // reference count std::map active_members; // active nexthops and nexthop group member id (null for single nexthop) + std::set tunnel_routes; }; class VNetObject @@ -252,7 +254,7 @@ const request_description_t vnet_route_description = { { "nexthop", REQ_T_STRING }, { "vni", REQ_T_STRING }, { "mac_address", REQ_T_STRING }, - { "endpoint_monitor", REQ_T_STRING }, + { "endpoint_monitor", REQ_T_IP_LIST }, }, { } }; @@ -283,10 +285,26 @@ struct VNetNextHopObserverEntry /* NextHopObserverTable: Destination IP address, next hop observer entry */ typedef std::map VNetNextHopObserverTable; +struct VNetNextHopInfo +{ + IpAddress monitor_addr; + sai_bfd_session_state_t bfd_state; + int ref_count; +}; + +struct BfdSessionInfo +{ + sai_bfd_session_state_t bfd_state; + std::string vnet; + NextHopKey endpoint; +}; + typedef std::map VNetNextHopGroupInfoTable; typedef std::map VNetTunnelRouteTable; +typedef std::map BfdSessionTable; +typedef std::map VNetEndpointInfoTable; -class VNetRouteOrch : public Orch2, public Subject +class VNetRouteOrch : public Orch2, public Subject, public Observer { public: VNetRouteOrch(DBConnector *db, vector &tableNames, VNetOrch *); @@ -297,6 +315,8 @@ class VNetRouteOrch : public Orch2, public Subject void attach(Observer* observer, const IpAddress& dstAddr); void detach(Observer* observer, const IpAddress& dstAddr); + void update(SubjectType, void *); + private: virtual bool addOperation(const Request& request); virtual bool delOperation(const Request& request); @@ -312,8 +332,19 @@ class VNetRouteOrch : public Orch2, public Subject bool addNextHopGroup(const string&, const NextHopGroupKey&, VNetVrfObject *vrf_obj); bool removeNextHopGroup(const string&, const NextHopGroupKey&, VNetVrfObject *vrf_obj); + void createBfdSession(const string& vnet, const NextHopKey& endpoint, const IpAddress& ipAddr); + void removeBfdSession(const string& vnet, const NextHopKey& endpoint, const IpAddress& ipAddr); + void setEndpointMonitor(const string& vnet, const map& monitors, NextHopGroupKey& nexthops); + void delEndpointMonitor(const string& vnet, NextHopGroupKey& nexthops); + void postRouteState(const string& vnet, IpPrefix& ipPrefix, NextHopGroupKey& nexthops); + void removeRouteState(const string& vnet, IpPrefix& ipPrefix); + + void updateVnetTunnel(const BfdUpdate&); + bool updateTunnelRoute(const string& vnet, IpPrefix& ipPrefix, NextHopGroupKey& nexthops, string& op); + template - bool doRouteTask(const string& vnet, IpPrefix& ipPrefix, NextHopGroupKey& nexthops, string& op); + bool doRouteTask(const string& vnet, IpPrefix& ipPrefix, NextHopGroupKey& nexthops, string& op, + const std::map& monitors=std::map()); template bool doRouteTask(const string& vnet, IpPrefix& ipPrefix, nextHop& nh, string& op); @@ -326,6 +357,11 @@ class VNetRouteOrch : public Orch2, public Subject VNetNextHopObserverTable next_hop_observers_; std::map syncd_nexthop_groups_; std::map syncd_tunnel_routes_; + BfdSessionTable bfd_sessions_; + std::map nexthop_info_; + ProducerStateTable bfd_session_producer_; + shared_ptr state_db_; + unique_ptr
state_vnet_rt_tunnel_table_; }; class VNetCfgRouteOrch : public Orch diff --git a/tests/test_vnet.py b/tests/test_vnet.py index 595c80a28b..a41f9ee39f 100644 --- a/tests/test_vnet.py +++ b/tests/test_vnet.py @@ -420,6 +420,69 @@ def check_linux_intf_arp_proxy(dvs, ifname): assert out != "1", "ARP proxy is not enabled for VNET interface in Linux kernel" +def update_bfd_session_state(dvs, addr, state): + bfd_id = get_bfd_session_id(dvs, addr) + assert bfd_id is not None + + bfd_sai_state = {"Admin_Down": "SAI_BFD_SESSION_STATE_ADMIN_DOWN", + "Down": "SAI_BFD_SESSION_STATE_DOWN", + "Init": "SAI_BFD_SESSION_STATE_INIT", + "Up": "SAI_BFD_SESSION_STATE_UP"} + + asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) + ntf = swsscommon.NotificationProducer(asic_db, "NOTIFICATIONS") + fvp = swsscommon.FieldValuePairs() + ntf_data = "[{\"bfd_session_id\":\""+bfd_id+"\",\"session_state\":\""+bfd_sai_state[state]+"\"}]" + ntf.send("bfd_session_state_change", ntf_data, fvp) + + +def get_bfd_session_id(dvs, addr): + asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) + tbl = swsscommon.Table(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_BFD_SESSION") + entries = set(tbl.getKeys()) + for entry in entries: + status, fvs = tbl.get(entry) + fvs = dict(fvs) + assert status, "Got an error when get a key" + if fvs["SAI_BFD_SESSION_ATTR_DST_IP_ADDRESS"] == addr: + return entry + + return None + + +def check_del_bfd_session(dvs, addrs): + for addr in addrs: + assert get_bfd_session_id(dvs, addr) is None + + +def check_bfd_session(dvs, addrs): + for addr in addrs: + assert get_bfd_session_id(dvs, addr) is not None + + +def check_state_db_routes(dvs, vnet, prefix, endpoints): + state_db = swsscommon.DBConnector(swsscommon.STATE_DB, dvs.redis_sock, 0) + tbl = swsscommon.Table(state_db, "VNET_ROUTE_TUNNEL_TABLE") + + status, fvs = tbl.get(vnet + '|' + prefix) + assert status, "Got an error when get a key" + + fvs = dict(fvs) + assert fvs['active_endpoints'] == ','.join(endpoints) + + if endpoints: + assert fvs['state'] == 'active' + else: + assert fvs['state'] == 'inactive' + + +def check_remove_state_db_routes(dvs, vnet, prefix): + state_db = swsscommon.DBConnector(swsscommon.STATE_DB, dvs.redis_sock, 0) + tbl = swsscommon.Table(state_db, "VNET_ROUTE_TUNNEL_TABLE") + keys = tbl.getKeys() + + assert vnet + '|' + prefix not in keys + loopback_id = 0 def_vr_id = 0 switch_mac = None @@ -438,6 +501,7 @@ class VnetVxlanVrfTunnel(object): ASIC_VLAN_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_VLAN" ASIC_NEXT_HOP_GROUP = "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP" ASIC_NEXT_HOP_GROUP_MEMBER = "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER" + ASIC_BFD_SESSION = "ASIC_STATE:SAI_OBJECT_TYPE_BFD_SESSION" tunnel_map_ids = set() tunnel_map_entry_ids = set() @@ -460,6 +524,7 @@ def fetch_exist_entries(self, dvs): self.routes = get_exist_entries(dvs, self.ASIC_ROUTE_ENTRY) self.nhops = get_exist_entries(dvs, self.ASIC_NEXT_HOP) self.nhgs = get_exist_entries(dvs, self.ASIC_NEXT_HOP_GROUP) + self.bfd_sessions = get_exist_entries(dvs, self.ASIC_BFD_SESSION) global loopback_id, def_vr_id, switch_mac if not loopback_id: @@ -863,6 +928,7 @@ def test_vnet_orch_1(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "100.100.1.1/32", 'Vnet_2000', '10.10.10.1') vnet_obj.check_vnet_routes(dvs, 'Vnet_2000', '10.10.10.1', tunnel_name) + check_state_db_routes(dvs, 'Vnet_2000', "100.100.1.1/32", ['10.10.10.1']) create_vnet_local_routes(dvs, "100.100.3.0/24", 'Vnet_2000', 'Vlan100') vnet_obj.check_vnet_local_routes(dvs, 'Vnet_2000') @@ -883,6 +949,7 @@ def test_vnet_orch_1(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "100.100.2.1/32", 'Vnet_2001', '10.10.10.2', "00:12:34:56:78:9A") vnet_obj.check_vnet_routes(dvs, 'Vnet_2001', '10.10.10.2', tunnel_name, "00:12:34:56:78:9A") + check_state_db_routes(dvs, 'Vnet_2001', "100.100.2.1/32", ['10.10.10.2']) create_vnet_local_routes(dvs, "100.102.1.0/24", 'Vnet_2001', 'Ethernet4') vnet_obj.check_vnet_local_routes(dvs, 'Vnet_2001') @@ -900,9 +967,11 @@ def test_vnet_orch_1(self, dvs, testlog): delete_vnet_routes(dvs, "100.100.2.1/32", 'Vnet_2001') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_2001') + check_remove_state_db_routes(dvs, 'Vnet_2001', "100.100.2.1/32") delete_vnet_routes(dvs, "100.100.1.1/32", 'Vnet_2000') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_2000') + check_remove_state_db_routes(dvs, 'Vnet_2000', "100.100.1.1/32") delete_phy_interface(dvs, "Ethernet4", "100.102.1.1/24") vnet_obj.check_del_router_interface(dvs, "Ethernet4") @@ -943,18 +1012,22 @@ def test_vnet_orch_2(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "1.1.1.10/32", 'Vnet_1', '100.1.1.10') vnet_obj.check_vnet_routes(dvs, 'Vnet_1', '100.1.1.10', tunnel_name) + check_state_db_routes(dvs, 'Vnet_1', "1.1.1.10/32", ['100.1.1.10']) vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "1.1.1.11/32", 'Vnet_1', '100.1.1.10') vnet_obj.check_vnet_routes(dvs, 'Vnet_1', '100.1.1.10', tunnel_name) + check_state_db_routes(dvs, 'Vnet_1', "1.1.1.11/32", ['100.1.1.10']) vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "1.1.1.12/32", 'Vnet_1', '200.200.1.200') vnet_obj.check_vnet_routes(dvs, 'Vnet_1', '200.200.1.200', tunnel_name) + check_state_db_routes(dvs, 'Vnet_1', "1.1.1.12/32", ['200.200.1.200']) vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "1.1.1.14/32", 'Vnet_1', '200.200.1.201') vnet_obj.check_vnet_routes(dvs, 'Vnet_1', '200.200.1.201', tunnel_name) + check_state_db_routes(dvs, 'Vnet_1', "1.1.1.14/32", ['200.200.1.201']) create_vnet_local_routes(dvs, "1.1.10.0/24", 'Vnet_1', 'Vlan1001') vnet_obj.check_vnet_local_routes(dvs, 'Vnet_1') @@ -970,10 +1043,12 @@ def test_vnet_orch_2(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "2.2.2.10/32", 'Vnet_2', '100.1.1.20') vnet_obj.check_vnet_routes(dvs, 'Vnet_2', '100.1.1.20', tunnel_name) + check_state_db_routes(dvs, 'Vnet_2', "2.2.2.10/32", ['100.1.1.20']) vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "2.2.2.11/32", 'Vnet_2', '100.1.1.20') vnet_obj.check_vnet_routes(dvs, 'Vnet_2', '100.1.1.20', tunnel_name) + check_state_db_routes(dvs, 'Vnet_2', "2.2.2.11/32", ['100.1.1.20']) create_vnet_local_routes(dvs, "2.2.10.0/24", 'Vnet_2', 'Vlan1002') vnet_obj.check_vnet_local_routes(dvs, 'Vnet_2') @@ -988,21 +1063,27 @@ def test_vnet_orch_2(self, dvs, testlog): delete_vnet_routes(dvs, "2.2.2.11/32", 'Vnet_2') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_2') + check_remove_state_db_routes(dvs, 'Vnet_2', "2.2.2.11/32") delete_vnet_routes(dvs, "2.2.2.10/32", 'Vnet_2') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_2') + check_remove_state_db_routes(dvs, 'Vnet_2', "2.2.2.10/32") delete_vnet_routes(dvs, "1.1.1.14/32", 'Vnet_1') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_1') + check_remove_state_db_routes(dvs, 'Vnet_1', "1.1.1.14/32") delete_vnet_routes(dvs, "1.1.1.12/32", 'Vnet_1') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_1') + check_remove_state_db_routes(dvs, 'Vnet_1', "1.1.1.12/32") delete_vnet_routes(dvs, "1.1.1.11/32", 'Vnet_1') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_1') + check_remove_state_db_routes(dvs, 'Vnet_1', "1.1.1.11/32") delete_vnet_routes(dvs, "1.1.1.10/32", 'Vnet_1') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_1') + check_remove_state_db_routes(dvs, 'Vnet_1', "1.1.1.10/32") delete_vlan_interface(dvs, "Vlan1002", "2.2.10.1/24") vnet_obj.check_del_router_interface(dvs, "Vlan1002") @@ -1049,10 +1130,12 @@ def test_vnet_orch_3(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "5.5.5.10/32", 'Vnet_10', '50.1.1.10') vnet_obj.check_vnet_routes(dvs, 'Vnet_10', '50.1.1.10', tunnel_name) + check_state_db_routes(dvs, 'Vnet_10', "5.5.5.10/32", ['50.1.1.10']) vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "8.8.8.10/32", 'Vnet_20', '80.1.1.20') vnet_obj.check_vnet_routes(dvs, 'Vnet_10', '80.1.1.20', tunnel_name) + check_state_db_routes(dvs, 'Vnet_20', "8.8.8.10/32", ['80.1.1.20']) create_vnet_local_routes(dvs, "5.5.10.0/24", 'Vnet_10', 'Vlan2001') vnet_obj.check_vnet_local_routes(dvs, 'Vnet_10') @@ -1070,9 +1153,11 @@ def test_vnet_orch_3(self, dvs, testlog): delete_vnet_routes(dvs, "5.5.5.10/32", 'Vnet_10') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_10') + check_remove_state_db_routes(dvs, 'Vnet_10', "5.5.5.10/32") delete_vnet_routes(dvs, "8.8.8.10/32", 'Vnet_20') vnet_obj.check_del_vnet_routes(dvs, 'Vnet_20') + check_remove_state_db_routes(dvs, 'Vnet_20', "8.8.8.10/32") delete_vlan_interface(dvs, "Vlan2001", "5.5.10.1/24") vnet_obj.check_del_router_interface(dvs, "Vlan2001") @@ -1112,9 +1197,11 @@ def test_vnet_orch_4(self, dvs, testlog): create_vnet_routes(dvs, "100.100.1.1/32", 'Vnet3001', '2000:1000:2000:3000:4000:5000:6000:7000') vnet_obj.check_vnet_routes(dvs, 'Vnet3001', '2000:1000:2000:3000:4000:5000:6000:7000', tunnel_name) + check_state_db_routes(dvs, 'Vnet3001', "100.100.1.1/32", ['2000:1000:2000:3000:4000:5000:6000:7000']) create_vnet_routes(dvs, "100.100.1.2/32", 'Vnet3001', '2000:1000:2000:3000:4000:5000:6000:7000') vnet_obj.check_vnet_routes(dvs, 'Vnet3001', '2000:1000:2000:3000:4000:5000:6000:7000', tunnel_name) + check_state_db_routes(dvs, 'Vnet3001', "100.100.1.2/32", ['2000:1000:2000:3000:4000:5000:6000:7000']) create_vnet_local_routes(dvs, "100.100.3.0/24", 'Vnet3001', 'Vlan300') vnet_obj.check_vnet_local_routes(dvs, 'Vnet3001') @@ -1134,6 +1221,7 @@ def test_vnet_orch_4(self, dvs, testlog): create_vnet_routes(dvs, "100.100.2.1/32", 'Vnet3002', 'fd:2::34', "00:12:34:56:78:9A") vnet_obj.check_vnet_routes(dvs, 'Vnet3002', 'fd:2::34', tunnel_name, "00:12:34:56:78:9A") + check_state_db_routes(dvs, 'Vnet3002', "100.100.2.1/32", ['fd:2::34']) create_vnet_local_routes(dvs, "100.102.1.0/24", 'Vnet3002', 'Ethernet60') vnet_obj.check_vnet_local_routes(dvs, 'Vnet3002') @@ -1151,17 +1239,21 @@ def test_vnet_orch_4(self, dvs, testlog): create_vnet_routes(dvs, "5.5.5.10/32", 'Vnet3003', 'fd:2::35') vnet_obj.check_vnet_routes(dvs, 'Vnet3004', 'fd:2::35', tunnel_name) + check_state_db_routes(dvs, 'Vnet3003', "5.5.5.10/32", ['fd:2::35']) create_vnet_routes(dvs, "8.8.8.10/32", 'Vnet3004', 'fd:2::36') vnet_obj.check_vnet_routes(dvs, 'Vnet3003', 'fd:2::36', tunnel_name) + check_state_db_routes(dvs, 'Vnet3004', "8.8.8.10/32", ['fd:2::36']) # Clean-up and verify remove flows delete_vnet_routes(dvs, "5.5.5.10/32", 'Vnet3003') vnet_obj.check_del_vnet_routes(dvs, 'Vnet3003') + check_remove_state_db_routes(dvs, 'Vnet3003', "5.5.5.10/32") delete_vnet_routes(dvs, "8.8.8.10/32", 'Vnet3004') vnet_obj.check_del_vnet_routes(dvs, 'Vnet3004') + check_remove_state_db_routes(dvs, 'Vnet3004', "8.8.8.10/32") delete_vnet_entry(dvs, 'Vnet3003') vnet_obj.check_del_vnet_entry(dvs, 'Vnet3003') @@ -1171,6 +1263,7 @@ def test_vnet_orch_4(self, dvs, testlog): delete_vnet_routes(dvs, "100.100.2.1/24", 'Vnet3002') vnet_obj.check_del_vnet_routes(dvs, 'Vnet3002') + check_remove_state_db_routes(dvs, 'Vnet3002', "100.100.2.1/24") delete_vnet_local_routes(dvs, "100.102.1.0/24", 'Vnet3002') vnet_obj.check_del_vnet_local_routes(dvs, 'Vnet3002') @@ -1189,9 +1282,11 @@ def test_vnet_orch_4(self, dvs, testlog): delete_vnet_routes(dvs, "100.100.1.1/32", 'Vnet3001') vnet_obj.check_del_vnet_routes(dvs, 'Vnet3001') + check_remove_state_db_routes(dvs, 'Vnet3001', "100.100.1.1/32") delete_vnet_routes(dvs, "100.100.1.2/32", 'Vnet3001') vnet_obj.check_del_vnet_routes(dvs, 'Vnet3001') + check_remove_state_db_routes(dvs, 'Vnet3001', "100.100.1.2/32") delete_vlan_interface(dvs, "Vlan300", "100.100.3.1/24") vnet_obj.check_del_router_interface(dvs, "Vlan300") @@ -1259,10 +1354,12 @@ def test_vnet_orch_7(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "100.100.1.1/32", 'Vnet7', '7.0.0.1,7.0.0.2,7.0.0.3') route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet7', ['7.0.0.1', '7.0.0.2', '7.0.0.3'], tunnel_name) + check_state_db_routes(dvs, 'Vnet7', "100.100.1.1/32", ['7.0.0.1', '7.0.0.2', '7.0.0.3']) # Set the tunnel route to another nexthop group set_vnet_routes(dvs, "100.100.1.1/32", 'Vnet7', '7.0.0.1,7.0.0.2,7.0.0.3,7.0.0.4') route1, nhg1_2 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet7', ['7.0.0.1', '7.0.0.2', '7.0.0.3', '7.0.0.4'], tunnel_name, route_ids=route1) + check_state_db_routes(dvs, 'Vnet7', "100.100.1.1/32", ['7.0.0.1', '7.0.0.2', '7.0.0.3', '7.0.0.4']) # Check the previous nexthop group is removed vnet_obj.fetch_exist_entries(dvs) @@ -1271,12 +1368,14 @@ def test_vnet_orch_7(self, dvs, testlog): # Create another tunnel route to the same set of endpoints create_vnet_routes(dvs, "100.100.2.1/32", 'Vnet7', '7.0.0.1,7.0.0.2,7.0.0.3,7.0.0.4') route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet7', ['7.0.0.1', '7.0.0.2', '7.0.0.3', '7.0.0.4'], tunnel_name) + check_state_db_routes(dvs, 'Vnet7', "100.100.2.1/32", ['7.0.0.1', '7.0.0.2', '7.0.0.3', '7.0.0.4']) assert nhg2_1 == nhg1_2 # Remove one of the tunnel routes delete_vnet_routes(dvs, "100.100.1.1/32", 'Vnet7') vnet_obj.check_del_vnet_routes(dvs, 'Vnet7', ["100.100.1.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet7', "100.100.1.1/32") # Check the nexthop group still exists vnet_obj.fetch_exist_entries(dvs) @@ -1285,6 +1384,7 @@ def test_vnet_orch_7(self, dvs, testlog): # Remove the other tunnel route delete_vnet_routes(dvs, "100.100.2.1/32", 'Vnet7') vnet_obj.check_del_vnet_routes(dvs, 'Vnet7', ["100.100.2.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet7', "100.100.2.1/32") # Check the nexthop group is removed vnet_obj.fetch_exist_entries(dvs) @@ -1315,10 +1415,12 @@ def test_vnet_orch_8(self, dvs, testlog): vnet_obj.fetch_exist_entries(dvs) create_vnet_routes(dvs, "fd:8:10::32/128", 'Vnet8', 'fd:8:1::1,fd:8:1::2,fd:8:1::3') route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet8', ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3'], tunnel_name) + check_state_db_routes(dvs, 'Vnet8', "fd:8:10::32/128", ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3']) # Set the tunnel route to another nexthop group set_vnet_routes(dvs, "fd:8:10::32/128", 'Vnet8', 'fd:8:1::1,fd:8:1::2,fd:8:1::3,fd:8:1::4') route1, nhg1_2 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet8', ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3', 'fd:8:1::4'], tunnel_name, route_ids=route1) + check_state_db_routes(dvs, 'Vnet8', "fd:8:10::32/128", ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3', 'fd:8:1::4']) # Check the previous nexthop group is removed vnet_obj.fetch_exist_entries(dvs) @@ -1327,18 +1429,21 @@ def test_vnet_orch_8(self, dvs, testlog): # Create another tunnel route to the same set of endpoints create_vnet_routes(dvs, "fd:8:20::32/128", 'Vnet8', 'fd:8:1::1,fd:8:1::2,fd:8:1::3,fd:8:1::4') route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet8', ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3', 'fd:8:1::4'], tunnel_name) + check_state_db_routes(dvs, 'Vnet8', "fd:8:20::32/128", ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3', 'fd:8:1::4']) assert nhg2_1 == nhg1_2 # Create another tunnel route with ipv4 prefix to the same set of endpoints create_vnet_routes(dvs, "8.0.0.0/24", 'Vnet8', 'fd:8:1::1,fd:8:1::2,fd:8:1::3,fd:8:1::4') route3, nhg3_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet8', ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3', 'fd:8:1::4'], tunnel_name) + check_state_db_routes(dvs, 'Vnet8', "8.0.0.0/24", ['fd:8:1::1', 'fd:8:1::2', 'fd:8:1::3', 'fd:8:1::4']) assert nhg3_1 == nhg1_2 # Remove one of the tunnel routes delete_vnet_routes(dvs, "fd:8:10::32/128", 'Vnet8') vnet_obj.check_del_vnet_routes(dvs, 'Vnet8', ["fd:8:10::32/128"]) + check_remove_state_db_routes(dvs, 'Vnet8', "fd:8:10::32/128") # Check the nexthop group still exists vnet_obj.fetch_exist_entries(dvs) @@ -1347,10 +1452,12 @@ def test_vnet_orch_8(self, dvs, testlog): # Remove tunnel route 2 delete_vnet_routes(dvs, "fd:8:20::32/128", 'Vnet8') vnet_obj.check_del_vnet_routes(dvs, 'Vnet8', ["fd:8:20::32/128"]) + check_remove_state_db_routes(dvs, 'Vnet8', "fd:8:20::32/128") # Remove tunnel route 3 delete_vnet_routes(dvs, "8.0.0.0/24", 'Vnet8') vnet_obj.check_del_vnet_routes(dvs, 'Vnet8', ["8.0.0.0/24"]) + check_remove_state_db_routes(dvs, 'Vnet8', "8.0.0.0/24") # Check the nexthop group is removed vnet_obj.fetch_exist_entries(dvs) @@ -1358,6 +1465,337 @@ def test_vnet_orch_8(self, dvs, testlog): delete_vnet_entry(dvs, 'Vnet8') vnet_obj.check_del_vnet_entry(dvs, 'Vnet8') + + + ''' + Test 9 - Test for vnet tunnel routes with ECMP nexthop group with endpoint health monitor + ''' + def test_vnet_orch_9(self, dvs, testlog): + vnet_obj = self.get_vnet_obj() + + tunnel_name = 'tunnel_9' + + vnet_obj.fetch_exist_entries(dvs) + + create_vxlan_tunnel(dvs, tunnel_name, '9.9.9.9') + create_vnet_entry(dvs, 'Vnet9', tunnel_name, '10009', "") + + vnet_obj.check_vnet_entry(dvs, 'Vnet9') + vnet_obj.check_vxlan_tunnel_entry(dvs, tunnel_name, 'Vnet9', '10009') + + vnet_obj.check_vxlan_tunnel(dvs, tunnel_name, '9.9.9.9') + + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "100.100.1.1/32", 'Vnet9', '9.0.0.1,9.0.0.2,9.0.0.3', ep_monitor='9.1.0.1,9.1.0.2,9.1.0.3') + + # default bfd status is down, route should not be programmed in this status + vnet_obj.check_del_vnet_routes(dvs, 'Vnet9', ["100.100.1.1/32"]) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", []) + + # Route should be properly configured when all bfd session states go up + update_bfd_session_state(dvs, '9.1.0.1', 'Up') + update_bfd_session_state(dvs, '9.1.0.2', 'Up') + update_bfd_session_state(dvs, '9.1.0.3', 'Up') + time.sleep(2) + route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1', '9.0.0.2', '9.0.0.3'], tunnel_name) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", ['9.0.0.1', '9.0.0.2', '9.0.0.3']) + + # Remove endpoint from group if it goes down + update_bfd_session_state(dvs, '9.1.0.2', 'Down') + time.sleep(2) + route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1', '9.0.0.3'], tunnel_name, route_ids=route1, nhg=nhg1_1) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", ['9.0.0.1', '9.0.0.3']) + + # Create another tunnel route with endpoint group overlapped with route1 + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "100.100.2.1/32", 'Vnet9', '9.0.0.1,9.0.0.2,9.0.0.5', ep_monitor='9.1.0.1,9.1.0.2,9.1.0.5') + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1'], tunnel_name) + check_state_db_routes(dvs, 'Vnet9', "100.100.2.1/32", ['9.0.0.1']) + + # Update BFD session state and verify route change + update_bfd_session_state(dvs, '9.1.0.5', 'Up') + time.sleep(2) + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1', '9.0.0.5'], tunnel_name, route_ids=route2, nhg=nhg2_1) + check_state_db_routes(dvs, 'Vnet9', "100.100.2.1/32", ['9.0.0.1', '9.0.0.5']) + + # Update BFD state and check route nexthop + update_bfd_session_state(dvs, '9.1.0.3', 'Down') + time.sleep(2) + + route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1'], tunnel_name, route_ids=route1, nhg=nhg1_1) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", ['9.0.0.1']) + + # Set the route1 to a new group + set_vnet_routes(dvs, "100.100.1.1/32", 'Vnet9', '9.0.0.1,9.0.0.2,9.0.0.3,9.0.0.4', ep_monitor='9.1.0.1,9.1.0.2,9.1.0.3,9.1.0.4') + update_bfd_session_state(dvs, '9.1.0.4', 'Up') + time.sleep(2) + route1, nhg1_2 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1', '9.0.0.4'], tunnel_name, route_ids=route1) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", ['9.0.0.1', '9.0.0.4']) + + # Check the previous nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg1_1 not in vnet_obj.nhgs + + # Set BFD session state for a down endpoint to up + update_bfd_session_state(dvs, '9.1.0.2', 'Up') + time.sleep(2) + route1, nhg1_2 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.1', '9.0.0.2', '9.0.0.4'], tunnel_name, route_ids=route1, nhg=nhg1_2) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", ['9.0.0.1', '9.0.0.2', '9.0.0.4']) + + # Set all endpoint to down state + update_bfd_session_state(dvs, '9.1.0.1', 'Down') + update_bfd_session_state(dvs, '9.1.0.2', 'Down') + update_bfd_session_state(dvs, '9.1.0.3', 'Down') + update_bfd_session_state(dvs, '9.1.0.4', 'Down') + time.sleep(2) + + # Confirm the tunnel route is updated in ASIC + vnet_obj.check_del_vnet_routes(dvs, 'Vnet9', ["100.100.1.1/32"]) + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet9', ['9.0.0.5'], tunnel_name, route_ids=route2, nhg=nhg2_1) + check_state_db_routes(dvs, 'Vnet9', "100.100.2.1/32", ['9.0.0.5']) + check_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32", []) + + # Remove tunnel route2 + delete_vnet_routes(dvs, "100.100.2.1/32", 'Vnet9') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet9', ["100.100.2.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet9', "100.100.2.1/32") + + # Check the corresponding nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg2_1 not in vnet_obj.nhgs + + # Check the BFD session specific to the endpoint group is removed while others exist + check_del_bfd_session(dvs, ['9.1.0.5']) + check_bfd_session(dvs, ['9.1.0.1', '9.1.0.2', '9.1.0.3', '9.1.0.4']) + + # Remove tunnel route 1 + delete_vnet_routes(dvs, "100.100.1.1/32", 'Vnet9') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet9', ["100.100.1.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet9', "100.100.1.1/32") + + # Check the previous nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg1_2 not in vnet_obj.nhgs + + # Confirm the BFD sessions are removed + check_del_bfd_session(dvs, ['9.1.0.1', '9.1.0.2', '9.1.0.3', '9.1.0.4', '9.1.0.5']) + + delete_vnet_entry(dvs, 'Vnet9') + vnet_obj.check_del_vnet_entry(dvs, 'Vnet9') + + + ''' + Test 10 - Test for ipv6 vnet tunnel routes with ECMP nexthop group with endpoint health monitor + ''' + def test_vnet_orch_10(self, dvs, testlog): + vnet_obj = self.get_vnet_obj() + + tunnel_name = 'tunnel_10' + + vnet_obj.fetch_exist_entries(dvs) + + create_vxlan_tunnel(dvs, tunnel_name, 'fd:10::32') + create_vnet_entry(dvs, 'Vnet10', tunnel_name, '10010', "") + + vnet_obj.check_vnet_entry(dvs, 'Vnet10') + vnet_obj.check_vxlan_tunnel_entry(dvs, tunnel_name, 'Vnet10', '10010') + + vnet_obj.check_vxlan_tunnel(dvs, tunnel_name, 'fd:10::32') + + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "fd:10:10::1/128", 'Vnet10', 'fd:10:1::1,fd:10:1::2,fd:10:1::3', ep_monitor='fd:10:2::1,fd:10:2::2,fd:10:2::3') + + # default bfd status is down, route should not be programmed in this status + vnet_obj.check_del_vnet_routes(dvs, 'Vnet10', ["fd:10:10::1/128"]) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", []) + + # Route should be properly configured when all bfd session states go up + update_bfd_session_state(dvs, 'fd:10:2::1', 'Up') + update_bfd_session_state(dvs, 'fd:10:2::2', 'Up') + update_bfd_session_state(dvs, 'fd:10:2::3', 'Up') + time.sleep(2) + route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1', 'fd:10:1::2', 'fd:10:1::3'], tunnel_name) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", ['fd:10:1::1', 'fd:10:1::2', 'fd:10:1::3']) + + # Remove endpoint from group if it goes down + update_bfd_session_state(dvs, 'fd:10:2::2', 'Down') + time.sleep(2) + route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1', 'fd:10:1::3'], tunnel_name, route_ids=route1, nhg=nhg1_1) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", ['fd:10:1::1', 'fd:10:1::3']) + + # Create another tunnel route with endpoint group overlapped with route1 + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "fd:10:20::1/128", 'Vnet10', 'fd:10:1::1,fd:10:1::2,fd:10:1::5', ep_monitor='fd:10:2::1,fd:10:2::2,fd:10:2::5') + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1'], tunnel_name) + check_state_db_routes(dvs, 'Vnet10', "fd:10:20::1/128", ['fd:10:1::1']) + + # Update BFD session state and verify route change + update_bfd_session_state(dvs, 'fd:10:2::5', 'Up') + time.sleep(2) + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1', 'fd:10:1::5'], tunnel_name, route_ids=route2, nhg=nhg2_1) + check_state_db_routes(dvs, 'Vnet10', "fd:10:20::1/128", ['fd:10:1::1', 'fd:10:1::5']) + + # Update BFD state and check route nexthop + update_bfd_session_state(dvs, 'fd:10:2::3', 'Down') + update_bfd_session_state(dvs, 'fd:10:2::2', 'Up') + time.sleep(2) + + route1, nhg1_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1', 'fd:10:1::2'], tunnel_name, route_ids=route1, nhg=nhg1_1) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", ['fd:10:1::1', 'fd:10:1::2']) + + # Set the route to a new group + set_vnet_routes(dvs, "fd:10:10::1/128", 'Vnet10', 'fd:10:1::1,fd:10:1::2,fd:10:1::3,fd:10:1::4', ep_monitor='fd:10:2::1,fd:10:2::2,fd:10:2::3,fd:10:2::4') + update_bfd_session_state(dvs, 'fd:10:2::4', 'Up') + time.sleep(2) + route1, nhg1_2 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1', 'fd:10:1::2', 'fd:10:1::4'], tunnel_name, route_ids=route1) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", ['fd:10:1::1', 'fd:10:1::2', 'fd:10:1::4']) + # Check the previous nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg1_1 not in vnet_obj.nhgs + + # Set BFD session state for a down endpoint to up + update_bfd_session_state(dvs, 'fd:10:2::3', 'Up') + time.sleep(2) + route1, nhg1_2 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::1', 'fd:10:1::2', 'fd:10:1::3', 'fd:10:1::4'], tunnel_name, route_ids=route1, nhg=nhg1_2) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", ['fd:10:1::1', 'fd:10:1::2', 'fd:10:1::3', 'fd:10:1::4']) + + # Set all endpoint to down state + update_bfd_session_state(dvs, 'fd:10:2::1', 'Down') + update_bfd_session_state(dvs, 'fd:10:2::2', 'Down') + update_bfd_session_state(dvs, 'fd:10:2::3', 'Down') + update_bfd_session_state(dvs, 'fd:10:2::4', 'Down') + time.sleep(2) + + # Confirm the tunnel route is updated in ASIC + vnet_obj.check_del_vnet_routes(dvs, 'Vnet10', ["fd:10:10::1/128"]) + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet10', ['fd:10:1::5'], tunnel_name, route_ids=route2, nhg=nhg2_1) + check_state_db_routes(dvs, 'Vnet10', "fd:10:20::1/128", ['fd:10:1::5']) + check_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128", []) + + # Remove tunnel route2 + delete_vnet_routes(dvs, "fd:10:20::1/128", 'Vnet10') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet10', ["fd:10:20::1/128"]) + check_remove_state_db_routes(dvs, 'Vnet10', "fd:10:20::1/128") + + # Check the corresponding nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg2_1 not in vnet_obj.nhgs + + # Check the BFD session specific to the endpoint group is removed while others exist + check_del_bfd_session(dvs, ['fd:10:2::5']) + check_bfd_session(dvs, ['fd:10:2::1', 'fd:10:2::2', 'fd:10:2::3', 'fd:10:2::4']) + + # Check the BFD session specific to the endpoint group is removed while others exist + check_del_bfd_session(dvs, ['fd:10:2::5']) + check_bfd_session(dvs, ['fd:10:2::1', 'fd:10:2::2', 'fd:10:2::3', 'fd:10:2::4']) + + # Remove tunnel route 1 + delete_vnet_routes(dvs, "fd:10:10::1/128", 'Vnet10') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet10', ["fd:10:10::1/128"]) + check_remove_state_db_routes(dvs, 'Vnet10', "fd:10:10::1/128") + + # Confirm the BFD sessions are removed + check_del_bfd_session(dvs, ['fd:10:2::1', 'fd:10:2::2', 'fd:10:2::3', 'fd:10:2::4', 'fd:10:2::5']) + + # Check the previous nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg1_2 not in vnet_obj.nhgs + + delete_vnet_entry(dvs, 'Vnet10') + vnet_obj.check_del_vnet_entry(dvs, 'Vnet10') + + + ''' + Test 11 - Test for vnet tunnel routes with both single endpoint and ECMP group with endpoint health monitor + ''' + def test_vnet_orch_11(self, dvs, testlog): + vnet_obj = self.get_vnet_obj() + + tunnel_name = 'tunnel_11' + + vnet_obj.fetch_exist_entries(dvs) + + create_vxlan_tunnel(dvs, tunnel_name, '11.11.11.11') + create_vnet_entry(dvs, 'Vnet11', tunnel_name, '100011', "") + + vnet_obj.check_vnet_entry(dvs, 'Vnet11') + vnet_obj.check_vxlan_tunnel_entry(dvs, tunnel_name, 'Vnet11', '100011') + + vnet_obj.check_vxlan_tunnel(dvs, tunnel_name, '11.11.11.11') + + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "100.100.1.1/32", 'Vnet11', '11.0.0.1', ep_monitor='11.1.0.1') + + # default bfd status is down, route should not be programmed in this status + vnet_obj.check_del_vnet_routes(dvs, 'Vnet11', ["100.100.1.1/32"]) + check_state_db_routes(dvs, 'Vnet11', "100.100.1.1/32", []) + + # Route should be properly configured when bfd session state goes up + update_bfd_session_state(dvs, '11.1.0.1', 'Up') + time.sleep(2) + vnet_obj.check_vnet_routes(dvs, 'Vnet11', '11.0.0.1', tunnel_name) + check_state_db_routes(dvs, 'Vnet11', "100.100.1.1/32", ['11.0.0.1']) + + # Create another tunnel route with endpoint group overlapped with route1 + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "100.100.2.1/32", 'Vnet11', '11.0.0.1,11.0.0.2', ep_monitor='11.1.0.1,11.1.0.2') + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet11', ['11.0.0.1'], tunnel_name) + check_state_db_routes(dvs, 'Vnet11', "100.100.2.1/32", ['11.0.0.1']) + + # Create a third tunnel route with another endpoint + vnet_obj.fetch_exist_entries(dvs) + create_vnet_routes(dvs, "100.100.3.1/32", 'Vnet11', '11.0.0.2', ep_monitor='11.1.0.2') + + # Update BFD session state and verify route change + update_bfd_session_state(dvs, '11.1.0.2', 'Up') + time.sleep(2) + vnet_obj.check_vnet_routes(dvs, 'Vnet11', '11.0.0.2', tunnel_name) + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet11', ['11.0.0.1', '11.0.0.2'], tunnel_name, route_ids=route2, nhg=nhg2_1) + check_state_db_routes(dvs, 'Vnet11', "100.100.3.1/32", ['11.0.0.2']) + check_state_db_routes(dvs, 'Vnet11', "100.100.2.1/32", ['11.0.0.1', '11.0.0.2']) + + update_bfd_session_state(dvs, '11.1.0.1', 'Down') + time.sleep(2) + route2, nhg2_1 = vnet_obj.check_vnet_ecmp_routes(dvs, 'Vnet11', ['11.0.0.2'], tunnel_name, route_ids=route2, nhg=nhg2_1) + vnet_obj.check_del_vnet_routes(dvs, 'Vnet11', ["100.100.1.1/32"]) + check_state_db_routes(dvs, 'Vnet11', "100.100.2.1/32", ['11.0.0.2']) + check_state_db_routes(dvs, 'Vnet11', "100.100.1.1/32", []) + + # Set the route1 to a new endpoint + vnet_obj.fetch_exist_entries(dvs) + set_vnet_routes(dvs, "100.100.1.1/32", 'Vnet11', '11.0.0.2', ep_monitor='11.1.0.2') + vnet_obj.check_vnet_routes(dvs, 'Vnet11', '11.0.0.2', tunnel_name) + check_state_db_routes(dvs, 'Vnet11', "100.100.3.1/32", ['11.0.0.2']) + + # Remove tunnel route2 + delete_vnet_routes(dvs, "100.100.2.1/32", 'Vnet11') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet11', ["100.100.2.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet11', "100.100.2.1/32") + + # Check the corresponding nexthop group is removed + vnet_obj.fetch_exist_entries(dvs) + assert nhg2_1 not in vnet_obj.nhgs + + # Check the BFD session specific to the endpoint group is removed while others exist + check_del_bfd_session(dvs, ['11.1.0.1']) + check_bfd_session(dvs, ['11.1.0.2']) + + # Remove tunnel route 1 + delete_vnet_routes(dvs, "100.100.1.1/32", 'Vnet11') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet11', ["100.100.1.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet11', "100.100.1.1/32") + + # Remove tunnel route 3 + delete_vnet_routes(dvs, "100.100.3.1/32", 'Vnet11') + vnet_obj.check_del_vnet_routes(dvs, 'Vnet11', ["100.100.3.1/32"]) + check_remove_state_db_routes(dvs, 'Vnet11', "100.100.3.1/32") + + # Confirm the BFD sessions are removed + check_del_bfd_session(dvs, ['11.1.0.1', '11.1.0.2']) + + delete_vnet_entry(dvs, 'Vnet11') + vnet_obj.check_del_vnet_entry(dvs, 'Vnet11') + # Add Dummy always-pass test at end as workaroud From ed783e1f583111133cbb2e6797bb48ef0c345bea Mon Sep 17 00:00:00 2001 From: Junchao-Mellanox <57339448+Junchao-Mellanox@users.noreply.github.com> Date: Wed, 1 Dec 2021 09:54:52 +0800 Subject: [PATCH 13/14] [orchagent] Add trap flow counter support (#1951) * Add trap flow counter support. See HLD: Azure/SONiC#858 * Flow counters are usually used for debugging, troubleshooting and performance enhancement processes. Host interface trap counter can get number of received traps per Trap ID. --- orchagent/Makefile.am | 5 +- orchagent/copporch.cpp | 258 ++++++++++-- orchagent/copporch.h | 39 +- .../flex_counter/flex_counter_manager.cpp | 7 +- orchagent/flex_counter/flex_counter_manager.h | 5 +- .../flex_counter/flow_counter_handler.cpp | 49 +++ orchagent/flex_counter/flow_counter_handler.h | 16 + orchagent/flexcounterorch.cpp | 17 + orchagent/flexcounterorch.h | 3 + orchagent/orchdaemon.cpp | 5 +- orchagent/saihelper.cpp | 15 +- orchagent/trap_rates.lua | 67 +++ tests/mock_tests/Makefile.am | 2 +- tests/test_flex_counters.py | 382 ++++++++++++++---- 14 files changed, 736 insertions(+), 134 deletions(-) create mode 100644 orchagent/flex_counter/flow_counter_handler.cpp create mode 100644 orchagent/flex_counter/flow_counter_handler.h create mode 100644 orchagent/trap_rates.lua diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 47de037266..7225917e4d 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -25,7 +25,8 @@ dist_swss_DATA = \ watermark_pg.lua \ watermark_bufferpool.lua \ lagids.lua \ - tunnel_rates.lua + tunnel_rates.lua \ + trap_rates.lua bin_PROGRAMS = orchagent routeresync orchagent_restart_check @@ -92,7 +93,7 @@ orchagent_SOURCES = \ srv6orch.cpp \ response_publisher.cpp -orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp +orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp flex_counter/flow_counter_handler.cpp orchagent_SOURCES += debug_counter/debug_counter.cpp debug_counter/drop_counter.cpp orchagent_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) diff --git a/orchagent/copporch.cpp b/orchagent/copporch.cpp index 6b1321b5bb..d193e215c3 100644 --- a/orchagent/copporch.cpp +++ b/orchagent/copporch.cpp @@ -1,8 +1,14 @@ #include "sai.h" #include "copporch.h" #include "portsorch.h" +#include "flexcounterorch.h" #include "tokenize.h" #include "logger.h" +#include "sai_serialize.h" +#include "schema.h" +#include "directory.h" +#include "flow_counter_handler.h" +#include "timer.h" #include #include @@ -18,8 +24,11 @@ extern sai_switch_api_t* sai_switch_api; extern sai_object_id_t gSwitchId; extern PortsOrch* gPortsOrch; +extern Directory gDirectory; extern bool gIsNatSupported; +#define FLEX_COUNTER_UPD_INTERVAL 1 + static map policer_meter_map = { {"packets", SAI_METER_TYPE_PACKETS}, {"bytes", SAI_METER_TYPE_BYTES} @@ -82,6 +91,21 @@ static map trap_id_map = { {"bfdv6_micro", SAI_HOSTIF_TRAP_TYPE_BFDV6_MICRO} }; + +std::string get_trap_name_by_type(sai_hostif_trap_type_t trap_type) +{ + static map trap_name_to_id_map; + if (trap_name_to_id_map.empty()) + { + for (const auto &kv : trap_id_map) + { + trap_name_to_id_map.emplace(kv.second, kv.first); + } + } + + return trap_name_to_id_map.at(trap_type); +} + static map packet_action_map = { {"drop", SAI_PACKET_ACTION_DROP}, {"forward", SAI_PACKET_ACTION_FORWARD}, @@ -97,11 +121,23 @@ const string default_trap_group = "default"; const vector default_trap_ids = { SAI_HOSTIF_TRAP_TYPE_TTL_ERROR }; +const uint HOSTIF_TRAP_COUNTER_POLLING_INTERVAL_MS = 10000; CoppOrch::CoppOrch(DBConnector* db, string tableName) : - Orch(db, tableName) + Orch(db, tableName), + m_counter_db(std::shared_ptr(new DBConnector("COUNTERS_DB", 0))), + m_flex_db(std::shared_ptr(new DBConnector("FLEX_COUNTER_DB", 0))), + m_asic_db(std::shared_ptr(new DBConnector("ASIC_DB", 0))), + m_counter_table(std::unique_ptr
(new Table(m_counter_db.get(), COUNTERS_TRAP_NAME_MAP))), + m_vidToRidTable(std::unique_ptr
(new Table(m_asic_db.get(), "VIDTORID"))), + m_flex_counter_group_table(std::unique_ptr(new ProducerTable(m_flex_db.get(), FLEX_COUNTER_GROUP_TABLE))), + m_trap_counter_manager(HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, HOSTIF_TRAP_COUNTER_POLLING_INTERVAL_MS, false) { SWSS_LOG_ENTER(); + auto intervT = timespec { .tv_sec = FLEX_COUNTER_UPD_INTERVAL , .tv_nsec = 0 }; + m_FlexCounterUpdTimer = new SelectableTimer(intervT); + auto executorT = new ExecutableTimer(m_FlexCounterUpdTimer, this, "FLEX_COUNTER_UPD_TIMER"); + Orch::addExecutor(executorT); initDefaultHostIntfTable(); initDefaultTrapGroup(); @@ -321,6 +357,8 @@ bool CoppOrch::applyAttributesToTrapIds(sai_object_id_t trap_group_id, } m_syncdTrapIds[trap_id].trap_group_obj = trap_group_id; m_syncdTrapIds[trap_id].trap_obj = hostif_trap_id; + m_syncdTrapIds[trap_id].trap_type = trap_id; + bindTrapCounter(hostif_trap_id, trap_id); } return true; } @@ -706,6 +744,35 @@ void CoppOrch::doTask(Consumer &consumer) } } +void CoppOrch::doTask(SelectableTimer &timer) +{ + SWSS_LOG_ENTER(); + + string value; + for (auto it = m_pendingAddToFlexCntr.begin(); it != m_pendingAddToFlexCntr.end(); ) + { + const auto id = sai_serialize_object_id(it->first); + if (m_vidToRidTable->hget("", id, value)) + { + SWSS_LOG_INFO("Registering %s, id %s", it->second.c_str(), id.c_str()); + + std::unordered_set counter_stats; + FlowCounterHandler::getGenericCounterStatIdList(counter_stats); + m_trap_counter_manager.setCounterIdList(it->first, CounterType::HOSTIF_TRAP, counter_stats); + it = m_pendingAddToFlexCntr.erase(it); + } + else + { + ++it; + } + } + + if (m_pendingAddToFlexCntr.empty()) + { + m_FlexCounterUpdTimer->stop(); + } +} + void CoppOrch::getTrapAddandRemoveList(string trap_group_name, vector &trap_ids, vector &add_trap_ids, @@ -777,17 +844,9 @@ bool CoppOrch::trapGroupProcessTrapIdChange (string trap_group_name, { if (m_syncdTrapIds.find(i)!= m_syncdTrapIds.end()) { - sai_status_t sai_status = sai_hostif_api->remove_hostif_trap( - m_syncdTrapIds[i].trap_obj); - if (sai_status != SAI_STATUS_SUCCESS) + if (!removeTrap(m_syncdTrapIds[i].trap_obj)) { - SWSS_LOG_ERROR("Failed to remove trap object %" PRId64 "", - m_syncdTrapIds[i].trap_obj); - task_process_status handle_status = handleSaiRemoveStatus(SAI_API_HOSTIF, sai_status); - if (handle_status != task_success) - { - return parseHandleSaiStatusFailure(handle_status); - } + return false; } } } @@ -830,17 +889,9 @@ bool CoppOrch::trapGroupProcessTrapIdChange (string trap_group_name, */ if (m_syncdTrapIds[i].trap_group_obj == m_trap_group_map[trap_group_name]) { - sai_status_t sai_status = sai_hostif_api->remove_hostif_trap( - m_syncdTrapIds[i].trap_obj); - if (sai_status != SAI_STATUS_SUCCESS) + if (!removeTrap(m_syncdTrapIds[i].trap_obj)) { - SWSS_LOG_ERROR("Failed to remove trap object %" PRId64 "", - m_syncdTrapIds[i].trap_obj); - task_process_status handle_status = handleSaiRemoveStatus(SAI_API_HOSTIF, sai_status); - if (handle_status != task_success) - { - return parseHandleSaiStatusFailure(handle_status); - } + return false; } m_syncdTrapIds.erase(i); } @@ -882,15 +933,9 @@ bool CoppOrch::processTrapGroupDel (string trap_group_name) if (it.second.trap_group_obj == m_trap_group_map[trap_group_name]) { trap_ids_to_reset.push_back(it.first); - sai_status_t sai_status = sai_hostif_api->remove_hostif_trap(it.second.trap_obj); - if (sai_status != SAI_STATUS_SUCCESS) + if (!removeTrap(it.second.trap_obj)) { - SWSS_LOG_ERROR("Failed to remove trap object %" PRId64 "", it.second.trap_obj); - task_process_status handle_status = handleSaiRemoveStatus(SAI_API_HOSTIF, sai_status); - if (handle_status != task_success) - { - return parseHandleSaiStatusFailure(handle_status); - } + return false; } } } @@ -1096,3 +1141,158 @@ bool CoppOrch::trapGroupUpdatePolicer (string trap_group_name, } return true; } + +void CoppOrch::initTrapRatePlugin() +{ + if (m_trap_rate_plugin_loaded) + { + return; + } + + std::string trapRatePluginName = "trap_rates.lua"; + try + { + std::string trapLuaScript = swss::loadLuaScript(trapRatePluginName); + std::string trapSha = swss::loadRedisScript(m_counter_db.get(), trapLuaScript); + + vector fieldValues; + fieldValues.emplace_back(FLOW_COUNTER_PLUGIN_FIELD, trapSha); + fieldValues.emplace_back(STATS_MODE_FIELD, STATS_MODE_READ); + m_flex_counter_group_table->set(HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP, fieldValues); + } + catch (const runtime_error &e) + { + SWSS_LOG_ERROR("Trap flex counter groups were not set successfully: %s", e.what()); + } + m_trap_rate_plugin_loaded = true; +} + +bool CoppOrch::removeTrap(sai_object_id_t hostif_trap_id) +{ + unbindTrapCounter(hostif_trap_id); + + sai_status_t sai_status = sai_hostif_api->remove_hostif_trap(hostif_trap_id); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove trap object %" PRId64 "", + hostif_trap_id); + task_process_status handle_status = handleSaiRemoveStatus(SAI_API_HOSTIF, sai_status); + if (handle_status != task_success) + { + return parseHandleSaiStatusFailure(handle_status); + } + } + + return true; +} + +bool CoppOrch::bindTrapCounter(sai_object_id_t hostif_trap_id, sai_hostif_trap_type_t trap_type) +{ + auto flex_counters_orch = gDirectory.get(); + + if (!flex_counters_orch || !flex_counters_orch->getHostIfTrapCounterState()) + { + return false; + } + + if (m_trap_obj_name_map.count(hostif_trap_id) > 0) + { + return true; + } + + initTrapRatePlugin(); + + // Create generic counter + sai_object_id_t counter_id; + if (!FlowCounterHandler::createGenericCounter(counter_id)) + { + return false; + } + + // Bind generic counter to trap + sai_attribute_t trap_attr; + trap_attr.id = SAI_HOSTIF_TRAP_ATTR_COUNTER_ID; + trap_attr.value.oid = counter_id; + sai_status_t sai_status = sai_hostif_api->set_hostif_trap_attribute(hostif_trap_id, &trap_attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Failed to bind trap %" PRId64 " to counter %" PRId64 "", hostif_trap_id, counter_id); + return false; + } + + // Update COUNTERS_TRAP_NAME_MAP + auto trap_name = get_trap_name_by_type(trap_type); + vector nameMapFvs; + nameMapFvs.emplace_back(trap_name, sai_serialize_object_id(counter_id)); + m_counter_table->set("", nameMapFvs); + + auto was_empty = m_pendingAddToFlexCntr.empty(); + m_pendingAddToFlexCntr[counter_id] = trap_name; + + if (was_empty) + { + m_FlexCounterUpdTimer->start(); + } + + m_trap_obj_name_map.emplace(hostif_trap_id, trap_name); + return true; +} + +void CoppOrch::unbindTrapCounter(sai_object_id_t hostif_trap_id) +{ + auto iter = m_trap_obj_name_map.find(hostif_trap_id); + if (iter == m_trap_obj_name_map.end()) + { + return; + } + + std::string counter_oid_str; + m_counter_table->hget("", iter->second, counter_oid_str); + + // Clear FLEX_COUNTER table + sai_object_id_t counter_id; + sai_deserialize_object_id(counter_oid_str, counter_id); + auto update_iter = m_pendingAddToFlexCntr.find(counter_id); + if (update_iter == m_pendingAddToFlexCntr.end()) + { + m_trap_counter_manager.clearCounterIdList(counter_id); + } + else + { + m_pendingAddToFlexCntr.erase(update_iter); + } + + // Remove trap from COUNTERS_TRAP_NAME_MAP + m_counter_table->hdel("", iter->second); + + // Unbind generic counter to trap + sai_attribute_t trap_attr; + trap_attr.id = SAI_HOSTIF_TRAP_ATTR_COUNTER_ID; + trap_attr.value.oid = SAI_NULL_OBJECT_ID; + sai_status_t sai_status = sai_hostif_api->set_hostif_trap_attribute(hostif_trap_id, &trap_attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to unbind trap %" PRId64 " to counter %" PRId64 "", hostif_trap_id, counter_id); + } + + // Remove generic counter + FlowCounterHandler::removeGenericCounter(counter_id); + + m_trap_obj_name_map.erase(iter); +} + +void CoppOrch::generateHostIfTrapCounterIdList() +{ + for (const auto &kv : m_syncdTrapIds) + { + bindTrapCounter(kv.second.trap_obj, kv.second.trap_type); + } +} + +void CoppOrch::clearHostIfTrapCounterIdList() +{ + for (const auto &kv : m_syncdTrapIds) + { + unbindTrapCounter(kv.second.trap_obj); + } +} diff --git a/orchagent/copporch.h b/orchagent/copporch.h index 4794cfd2a6..096979ebb8 100644 --- a/orchagent/copporch.h +++ b/orchagent/copporch.h @@ -3,7 +3,17 @@ #include #include +#include +#include "dbconnector.h" #include "orch.h" +#include "flex_counter_manager.h" +#include "producertable.h" +#include "table.h" +#include "selectabletimer.h" + +using namespace swss; + +#define HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP "HOSTIF_TRAP_FLOW_COUNTER" // trap fields const std::string copp_trap_id_list = "trap_ids"; @@ -33,6 +43,7 @@ struct copp_trap_objects { sai_object_id_t trap_obj; sai_object_id_t trap_group_obj; + sai_hostif_trap_type_t trap_type; }; /* TrapGroupPolicerTable: trap group ID, policer ID */ @@ -45,11 +56,15 @@ typedef std::map TrapGroupHostIfMap; typedef std::map TrapIdHostIfTableMap; /* Trap group to trap ID attributes */ typedef std::map TrapGroupTrapIdAttribs; +/* Trap OID to trap name*/ +typedef std::map TrapObjectTrapNameMap; class CoppOrch : public Orch { public: CoppOrch(swss::DBConnector* db, std::string tableName); + void generateHostIfTrapCounterIdList(); + void clearHostIfTrapCounterIdList(); protected: object_map m_trap_group_map; @@ -59,10 +74,26 @@ class CoppOrch : public Orch TrapGroupHostIfMap m_trap_group_hostif_map; TrapIdHostIfTableMap m_trapid_hostif_table_map; TrapGroupTrapIdAttribs m_trap_group_trap_id_attrs; + TrapObjectTrapNameMap m_trap_obj_name_map; + std::map m_pendingAddToFlexCntr; + + std::shared_ptr m_counter_db; + std::shared_ptr m_flex_db; + std::shared_ptr m_asic_db; + std::unique_ptr
m_counter_table; + std::unique_ptr
m_vidToRidTable; + std::unique_ptr m_flex_counter_group_table; + + FlexCounterManager m_trap_counter_manager; + + bool m_trap_rate_plugin_loaded = false; + + SelectableTimer* m_FlexCounterUpdTimer = nullptr; void initDefaultHostIntfTable(); void initDefaultTrapGroup(); void initDefaultTrapIds(); + void initTrapRatePlugin(); task_process_status processCoppRule(Consumer& consumer); bool isValidList(std::vector &trap_id_list, std::vector &all_items) const; @@ -82,7 +113,7 @@ class CoppOrch : public Orch std::vector &add_trap_ids, std::vector &rem_trap_ids); - void getTrapIdsFromTrapGroup (sai_object_id_t trap_group_obj, + void getTrapIdsFromTrapGroup (sai_object_id_t trap_group_obj, std::vector &trap_ids); bool trapGroupProcessTrapIdChange (std::string trap_group_name, @@ -99,7 +130,13 @@ class CoppOrch : public Orch bool trapGroupUpdatePolicer (std::string trap_group_name, std::vector &policer_attribs); + bool removeTrap(sai_object_id_t hostif_trap_id); + + bool bindTrapCounter(sai_object_id_t hostif_trap_id, sai_hostif_trap_type_t trap_type); + void unbindTrapCounter(sai_object_id_t hostif_trap_id); + virtual void doTask(Consumer& consumer); + void doTask(swss::SelectableTimer&) override; }; #endif /* SWSS_COPPORCH_H */ diff --git a/orchagent/flex_counter/flex_counter_manager.cpp b/orchagent/flex_counter/flex_counter_manager.cpp index 86048c2601..71731e84d3 100644 --- a/orchagent/flex_counter/flex_counter_manager.cpp +++ b/orchagent/flex_counter/flex_counter_manager.cpp @@ -43,6 +43,7 @@ const unordered_map FlexCounterManager::counter_id_field_lo { CounterType::MACSEC_FLOW, MACSEC_FLOW_COUNTER_ID_LIST }, { CounterType::ACL_COUNTER, ACL_COUNTER_ATTR_ID_LIST }, { CounterType::TUNNEL, TUNNEL_COUNTER_ID_LIST }, + { CounterType::HOSTIF_TRAP, FLOW_COUNTER_ID_LIST }, }; FlexManagerDirectory g_FlexManagerDirectory; @@ -57,19 +58,19 @@ FlexCounterManager *FlexManagerDirectory::createFlexCounterManager(const string& { if (stats_mode != m_managers[group_name]->getStatsMode()) { - SWSS_LOG_ERROR("Stats mode mismatch with already created flex counter manager %s", + SWSS_LOG_ERROR("Stats mode mismatch with already created flex counter manager %s", group_name.c_str()); return NULL; } if (polling_interval != m_managers[group_name]->getPollingInterval()) { - SWSS_LOG_ERROR("Polling interval mismatch with already created flex counter manager %s", + SWSS_LOG_ERROR("Polling interval mismatch with already created flex counter manager %s", group_name.c_str()); return NULL; } if (enabled != m_managers[group_name]->getEnabled()) { - SWSS_LOG_ERROR("Enabled field mismatch with already created flex counter manager %s", + SWSS_LOG_ERROR("Enabled field mismatch with already created flex counter manager %s", group_name.c_str()); return NULL; } diff --git a/orchagent/flex_counter/flex_counter_manager.h b/orchagent/flex_counter/flex_counter_manager.h index 250586ab98..6e80feb8fb 100644 --- a/orchagent/flex_counter/flex_counter_manager.h +++ b/orchagent/flex_counter/flex_counter_manager.h @@ -30,6 +30,7 @@ enum class CounterType MACSEC_FLOW, ACL_COUNTER, TUNNEL, + HOSTIF_TRAP, }; // FlexCounterManager allows users to manage a group of flex counters. @@ -47,7 +48,7 @@ class FlexCounterManager const bool enabled, swss::FieldValueTuple fv_plugin = std::make_pair("","")); - FlexCounterManager() + FlexCounterManager() {} FlexCounterManager(const FlexCounterManager&) = delete; @@ -114,7 +115,7 @@ class FlexManagerDirectory { public: FlexCounterManager* createFlexCounterManager(const std::string& group_name, const StatsMode stats_mode, - const uint polling_interval, const bool enabled, + const uint polling_interval, const bool enabled, swss::FieldValueTuple fv_plugin = std::make_pair("","")); private: std::unordered_map m_managers; diff --git a/orchagent/flex_counter/flow_counter_handler.cpp b/orchagent/flex_counter/flow_counter_handler.cpp new file mode 100644 index 0000000000..89f621fe7b --- /dev/null +++ b/orchagent/flex_counter/flow_counter_handler.cpp @@ -0,0 +1,49 @@ +#include +#include +#include "flow_counter_handler.h" +#include "logger.h" +#include "sai_serialize.h" + +extern sai_object_id_t gSwitchId; +extern sai_counter_api_t* sai_counter_api; + +const std::vector generic_counter_stat_ids = +{ + SAI_COUNTER_STAT_PACKETS, + SAI_COUNTER_STAT_BYTES, +}; + +bool FlowCounterHandler::createGenericCounter(sai_object_id_t &counter_id) +{ + sai_attribute_t counter_attr; + counter_attr.id = SAI_COUNTER_ATTR_TYPE; + counter_attr.value.s32 = SAI_COUNTER_TYPE_REGULAR; + sai_status_t sai_status = sai_counter_api->create_counter(&counter_id, gSwitchId, 1, &counter_attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Failed to create generic counter"); + return false; + } + + return true; +} + +bool FlowCounterHandler::removeGenericCounter(sai_object_id_t counter_id) +{ + sai_status_t sai_status = sai_counter_api->remove_counter(counter_id); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove generic counter: %" PRId64 "", counter_id); + return false; + } + + return true; +} + +void FlowCounterHandler::getGenericCounterStatIdList(std::unordered_set& counter_stats) +{ + for (const auto& it: generic_counter_stat_ids) + { + counter_stats.emplace(sai_serialize_counter_stat(it)); + } +} diff --git a/orchagent/flex_counter/flow_counter_handler.h b/orchagent/flex_counter/flow_counter_handler.h new file mode 100644 index 0000000000..1b6a8bbe2a --- /dev/null +++ b/orchagent/flex_counter/flow_counter_handler.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +extern "C" { +#include "sai.h" +} + +class FlowCounterHandler +{ +public: + static bool createGenericCounter(sai_object_id_t &counter_id); + static bool removeGenericCounter(sai_object_id_t counter_id); + static void getGenericCounterStatIdList(std::unordered_set& counter_stats); +}; diff --git a/orchagent/flexcounterorch.cpp b/orchagent/flexcounterorch.cpp index dea2fcd0a3..dc14998774 100644 --- a/orchagent/flexcounterorch.cpp +++ b/orchagent/flexcounterorch.cpp @@ -9,6 +9,7 @@ #include "flexcounterorch.h" #include "debugcounterorch.h" #include "directory.h" +#include "copporch.h" extern sai_port_api_t *sai_port_api; @@ -17,6 +18,7 @@ extern FabricPortsOrch *gFabricPortsOrch; extern IntfsOrch *gIntfsOrch; extern BufferOrch *gBufferOrch; extern Directory gDirectory; +extern CoppOrch *gCoppOrch; #define BUFFER_POOL_WATERMARK_KEY "BUFFER_POOL_WATERMARK" #define PORT_KEY "PORT" @@ -26,6 +28,7 @@ extern Directory gDirectory; #define RIF_KEY "RIF" #define ACL_KEY "ACL" #define TUNNEL_KEY "TUNNEL" +#define FLOW_CNT_TRAP_KEY "FLOW_CNT_TRAP" unordered_map flexCounterGroupMap = { @@ -43,6 +46,7 @@ unordered_map flexCounterGroupMap = {"DEBUG_COUNTER", DEBUG_COUNTER_FLEX_COUNTER_GROUP}, {"ACL", ACL_COUNTER_FLEX_COUNTER_GROUP}, {"TUNNEL", TUNNEL_STAT_COUNTER_FLEX_COUNTER_GROUP}, + {FLOW_CNT_TRAP_KEY, HOSTIF_TRAP_COUNTER_FLEX_COUNTER_GROUP}, }; @@ -158,6 +162,19 @@ void FlexCounterOrch::doTask(Consumer &consumer) { vxlan_tunnel_orch->generateTunnelCounterMap(); } + if (gCoppOrch && (key == FLOW_CNT_TRAP_KEY)) + { + if (value == "enable") + { + m_hostif_trap_counter_enabled = true; + gCoppOrch->generateHostIfTrapCounterIdList(); + } + else if (value == "disable") + { + gCoppOrch->clearHostIfTrapCounterIdList(); + m_hostif_trap_counter_enabled = false; + } + } vector fieldValues; fieldValues.emplace_back(FLEX_COUNTER_STATUS_FIELD, value); m_flexCounterGroupTable->set(flexCounterGroupMap[key], fieldValues); diff --git a/orchagent/flexcounterorch.h b/orchagent/flexcounterorch.h index 9ae7e90aad..ceb8187506 100644 --- a/orchagent/flexcounterorch.h +++ b/orchagent/flexcounterorch.h @@ -18,13 +18,16 @@ class FlexCounterOrch: public Orch virtual ~FlexCounterOrch(void); bool getPortCountersState() const; bool getPortBufferDropCountersState() const; + bool getHostIfTrapCounterState() const {return m_hostif_trap_counter_enabled;} bool bake() override; + private: std::shared_ptr m_flexCounterDb = nullptr; std::shared_ptr m_flexCounterGroupTable = nullptr; bool m_port_counter_enabled = false; bool m_port_buffer_drop_counter_enabled = false; + bool m_hostif_trap_counter_enabled = false; Table m_flexCounterConfigTable; }; diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 14e4d8aa77..0fee695c4e 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -47,6 +47,7 @@ NatOrch *gNatOrch; MlagOrch *gMlagOrch; IsoGrpOrch *gIsoGrpOrch; MACsecOrch *gMacsecOrch; +CoppOrch *gCoppOrch; BfdOrch *gBfdOrch; Srv6Orch *gSrv6Orch; @@ -182,7 +183,7 @@ bool OrchDaemon::init() gNhgOrch = new NhgOrch(m_applDb, APP_NEXTHOP_GROUP_TABLE_NAME); gCbfNhgOrch = new CbfNhgOrch(m_applDb, APP_CLASS_BASED_NEXT_HOP_GROUP_TABLE_NAME); - CoppOrch *copp_orch = new CoppOrch(m_applDb, APP_COPP_TABLE_NAME); + gCoppOrch = new CoppOrch(m_applDb, APP_COPP_TABLE_NAME); TunnelDecapOrch *tunnel_decap_orch = new TunnelDecapOrch(m_applDb, APP_TUNNEL_DECAP_TABLE_NAME); VxlanTunnelOrch *vxlan_tunnel_orch = new VxlanTunnelOrch(m_stateDb, m_applDb, APP_VXLAN_TUNNEL_TABLE_NAME); @@ -321,7 +322,7 @@ bool OrchDaemon::init() * when iterating ConsumerMap. This is ensured implicitly by the order of keys in ordered map. * For cases when Orch has to process tables in specific order, like PortsOrch during warm start, it has to override Orch::doTask() */ - m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, mux_orch, mux_cb_orch, gIntfsOrch, gNeighOrch, gNhgMapOrch, gNhgOrch, gCbfNhgOrch, gRouteOrch, copp_orch, qos_orch, wm_orch, policer_orch, tunnel_decap_orch, sflow_orch, debug_counter_orch, gMacsecOrch, gBfdOrch, gSrv6Orch}; + m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, mux_orch, mux_cb_orch, gIntfsOrch, gNeighOrch, gNhgMapOrch, gNhgOrch, gCbfNhgOrch, gRouteOrch, gCoppOrch, qos_orch, wm_orch, policer_orch, tunnel_decap_orch, sflow_orch, debug_counter_orch, gMacsecOrch, gBfdOrch, gSrv6Orch}; bool initialize_dtel = false; if (platform == BFN_PLATFORM_SUBSTRING || platform == VS_PLATFORM_SUBSTRING) diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 90578dc8a9..8db9676f39 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -68,6 +68,7 @@ sai_system_port_api_t* sai_system_port_api; sai_macsec_api_t* sai_macsec_api; sai_srv6_api_t** sai_srv6_api;; sai_l2mc_group_api_t* sai_l2mc_group_api; +sai_counter_api_t* sai_counter_api; sai_bfd_api_t* sai_bfd_api; extern sai_object_id_t gSwitchId; @@ -85,15 +86,15 @@ static map hardware_access_map = map gProfileMap; -sai_status_t mdio_read(uint64_t platform_context, - uint32_t mdio_addr, uint32_t reg_addr, +sai_status_t mdio_read(uint64_t platform_context, + uint32_t mdio_addr, uint32_t reg_addr, uint32_t number_of_registers, uint32_t *data) { return SAI_STATUS_NOT_IMPLEMENTED; } -sai_status_t mdio_write(uint64_t platform_context, - uint32_t mdio_addr, uint32_t reg_addr, +sai_status_t mdio_write(uint64_t platform_context, + uint32_t mdio_addr, uint32_t reg_addr, uint32_t number_of_registers, uint32_t *data) { return SAI_STATUS_NOT_IMPLEMENTED; @@ -194,6 +195,7 @@ void initSaiApi() sai_api_query(SAI_API_MACSEC, (void **)&sai_macsec_api); sai_api_query(SAI_API_SRV6, (void **)&sai_srv6_api); sai_api_query(SAI_API_L2MC_GROUP, (void **)&sai_l2mc_group_api); + sai_api_query(SAI_API_COUNTER, (void **)&sai_counter_api); sai_api_query(SAI_API_BFD, (void **)&sai_bfd_api); sai_log_set(SAI_API_SWITCH, SAI_LOG_LEVEL_NOTICE); @@ -229,6 +231,7 @@ void initSaiApi() sai_log_set(SAI_API_MACSEC, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_SRV6, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_L2MC_GROUP, SAI_LOG_LEVEL_NOTICE); + sai_log_set(SAI_API_COUNTER, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BFD, SAI_LOG_LEVEL_NOTICE); } @@ -452,8 +455,8 @@ sai_status_t initSaiPhyApi(swss::gearbox_phy_t *phy) { SWSS_LOG_ERROR("BOX: Failed to get firmware major version:%d rtn:%d", phy->phy_id, status); return status; - } - else + } + else { phy->firmware_major_version = string(attr.value.chardata); } diff --git a/orchagent/trap_rates.lua b/orchagent/trap_rates.lua new file mode 100644 index 0000000000..69b9c5cd3f --- /dev/null +++ b/orchagent/trap_rates.lua @@ -0,0 +1,67 @@ +-- KEYS - generic counter IDs +-- ARGV[1] - counters db index +-- ARGV[2] - counters table name +-- ARGV[3] - poll time interval +-- return log + +local logtable = {} + +local function logit(msg) + logtable[#logtable+1] = tostring(msg) +end + +local counters_db = ARGV[1] +local counters_table_name = ARGV[2] +local rates_table_name = "RATES" + +-- Get configuration +redis.call('SELECT', counters_db) +local smooth_interval = redis.call('HGET', rates_table_name .. ':' .. 'TRAP', 'TRAP_SMOOTH_INTERVAL') +local alpha = redis.call('HGET', rates_table_name .. ':' .. 'TRAP', 'TRAP_ALPHA') +if not alpha then + logit("Alpha is not defined") + return logtable +end +local one_minus_alpha = 1.0 - alpha +local delta = tonumber(ARGV[3]) + +logit(alpha) +logit(one_minus_alpha) +logit(delta) + +local n = table.getn(KEYS) +for i = 1, n do + local state_table = rates_table_name .. ':' .. KEYS[i] .. ':' .. 'TRAP' + local initialized = redis.call('HGET', state_table, 'INIT_DONE') + logit(initialized) + + -- Get new COUNTERS values + local in_pkts = redis.call('HGET', counters_table_name .. ':' .. KEYS[i], 'SAI_COUNTER_STAT_PACKETS') + + if initialized == 'DONE' or initialized == 'COUNTERS_LAST' then + -- Get old COUNTERS values + local in_pkts_last = redis.call('HGET', rates_table_name .. ':' .. KEYS[i], 'SAI_COUNTER_STAT_PACKETS_last') + + -- Calculate new rates values + local rx_pps_new = (in_pkts - in_pkts_last) / delta * 1000 + + if initialized == "DONE" then + -- Get old rates values + local rx_pps_old = redis.call('HGET', rates_table_name .. ':' .. KEYS[i], 'RX_PPS') + + -- Smooth the rates values and store them in DB + redis.call('HSET', rates_table_name .. ':' .. KEYS[i], 'RX_PPS', alpha*rx_pps_new + one_minus_alpha*rx_pps_old) + else + -- Store unsmoothed initial rates values in DB + redis.call('HSET', rates_table_name .. ':' .. KEYS[i], 'RX_PPS', rx_pps_new) + redis.call('HSET', state_table, 'INIT_DONE', 'DONE') + end + else + redis.call('HSET', state_table, 'INIT_DONE', 'COUNTERS_LAST') + end + + -- Set old COUNTERS values + redis.call('HSET', rates_table_name .. ':' .. KEYS[i], 'SAI_COUNTER_STAT_PACKETS_last', in_pkts) +end + +return logtable diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 1e4fd1903e..51df17f729 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -87,7 +87,7 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/bfdorch.cpp \ $(top_srcdir)/orchagent/srv6orch.cpp -tests_SOURCES += $(FLEX_CTR_DIR)/flex_counter_manager.cpp $(FLEX_CTR_DIR)/flex_counter_stat_manager.cpp +tests_SOURCES += $(FLEX_CTR_DIR)/flex_counter_manager.cpp $(FLEX_CTR_DIR)/flex_counter_stat_manager.cpp $(FLEX_CTR_DIR)/flow_counter_handler.cpp tests_SOURCES += $(DEBUG_CTR_DIR)/debug_counter.cpp $(DEBUG_CTR_DIR)/drop_counter.cpp tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) diff --git a/tests/test_flex_counters.py b/tests/test_flex_counters.py index 60a3a129d1..ea950af7c1 100644 --- a/tests/test_flex_counters.py +++ b/tests/test_flex_counters.py @@ -1,48 +1,67 @@ import time import pytest -# Counter keys on ConfigDB -PORT_KEY = "PORT" -QUEUE_KEY = "QUEUE" -RIF_KEY = "RIF" -BUFFER_POOL_WATERMARK_KEY = "BUFFER_POOL_WATERMARK" -PORT_BUFFER_DROP_KEY = "PORT_BUFFER_DROP" -PG_WATERMARK_KEY = "PG_WATERMARK" -ACL_KEY = "ACL" -TUNNEL_KEY = "TUNNEL" - -# Counter stats on FlexCountersDB -PORT_STAT = "PORT_STAT_COUNTER" -QUEUE_STAT = "QUEUE_STAT_COUNTER" -RIF_STAT = "RIF_STAT_COUNTER" -BUFFER_POOL_WATERMARK_STAT = "BUFFER_POOL_WATERMARK_STAT_COUNTER" -PORT_BUFFER_DROP_STAT = "PORT_BUFFER_DROP_STAT" -PG_WATERMARK_STAT = "PG_WATERMARK_STAT_COUNTER" -ACL_STAT = "ACL_STAT_COUNTER" -TUNNEL_STAT = "TUNNEL_STAT_COUNTER" - -# Counter maps on CountersDB -PORT_MAP = "COUNTERS_PORT_NAME_MAP" -QUEUE_MAP = "COUNTERS_QUEUE_NAME_MAP" -RIF_MAP = "COUNTERS_RIF_NAME_MAP" -BUFFER_POOL_WATERMARK_MAP = "COUNTERS_BUFFER_POOL_NAME_MAP" -PORT_BUFFER_DROP_MAP = "COUNTERS_PORT_NAME_MAP" -PG_WATERMARK_MAP = "COUNTERS_PG_NAME_MAP" -ACL_MAP = "ACL_COUNTER_RULE_MAP" -TUNNEL_MAP = "COUNTERS_TUNNEL_NAME_MAP" +from swsscommon import swsscommon TUNNEL_TYPE_MAP = "COUNTERS_TUNNEL_TYPE_MAP" NUMBER_OF_RETRIES = 10 CPU_PORT_OID = "0x0" -counter_type_dict = {"port_counter":[PORT_KEY, PORT_STAT, PORT_MAP], - "queue_counter":[QUEUE_KEY, QUEUE_STAT, QUEUE_MAP], - "rif_counter":[RIF_KEY, RIF_STAT, RIF_MAP], - "buffer_pool_watermark_counter":[BUFFER_POOL_WATERMARK_KEY, BUFFER_POOL_WATERMARK_STAT, BUFFER_POOL_WATERMARK_MAP], - "port_buffer_drop_counter":[PORT_BUFFER_DROP_KEY, PORT_BUFFER_DROP_STAT, PORT_BUFFER_DROP_MAP], - "pg_watermark_counter":[PG_WATERMARK_KEY, PG_WATERMARK_STAT, PG_WATERMARK_MAP], - "acl_counter":[ACL_KEY, ACL_STAT, ACL_MAP], - "vxlan_tunnel_counter":[TUNNEL_KEY, TUNNEL_STAT, TUNNEL_MAP]} +counter_group_meta = { + 'port_counter': { + 'key': 'PORT', + 'group_name': 'PORT_STAT_COUNTER', + 'name_map': 'COUNTERS_PORT_NAME_MAP', + 'post_test': 'post_port_counter_test', + }, + 'queue_counter': { + 'key': 'QUEUE', + 'group_name': 'QUEUE_STAT_COUNTER', + 'name_map': 'COUNTERS_QUEUE_NAME_MAP', + }, + 'rif_counter': { + 'key': 'RIF', + 'group_name': 'RIF_STAT_COUNTER', + 'name_map': 'COUNTERS_RIF_NAME_MAP', + 'pre_test': 'pre_rif_counter_test', + 'post_test': 'post_rif_counter_test', + }, + 'buffer_pool_watermark_counter': { + 'key': 'BUFFER_POOL_WATERMARK', + 'group_name': 'BUFFER_POOL_WATERMARK_STAT_COUNTER', + 'name_map': 'COUNTERS_BUFFER_POOL_NAME_MAP', + }, + 'port_buffer_drop_counter': { + 'key': 'PORT_BUFFER_DROP', + 'group_name': 'PORT_BUFFER_DROP_STAT', + 'name_map': 'COUNTERS_PORT_NAME_MAP', + }, + 'pg_watermark_counter': { + 'key': 'PG_WATERMARK', + 'group_name': 'PG_WATERMARK_STAT_COUNTER', + 'name_map': 'COUNTERS_PG_NAME_MAP', + }, + 'trap_flow_counter': { + 'key': 'FLOW_CNT_TRAP', + 'group_name': 'HOSTIF_TRAP_FLOW_COUNTER', + 'name_map': 'COUNTERS_TRAP_NAME_MAP', + 'post_test': 'post_trap_flow_counter_test', + }, + 'tunnel_counter': { + 'key': 'TUNNEL', + 'group_name': 'TUNNEL_STAT_COUNTER', + 'name_map': 'COUNTERS_TUNNEL_NAME_MAP', + 'pre_test': 'pre_vxlan_tunnel_counter_test', + 'post_test': 'post_vxlan_tunnel_counter_test', + }, + 'acl_counter': { + 'key': 'ACL', + 'group_name': 'ACL_STAT_COUNTER', + 'name_map': 'ACL_COUNTER_RULE_MAP', + 'pre_test': 'pre_acl_tunnel_counter_test', + 'post_test': 'post_acl_tunnel_counter_test', + } +} class TestFlexCounters(object): @@ -51,6 +70,7 @@ def setup_dbs(self, dvs): self.config_db = dvs.get_config_db() self.flex_db = dvs.get_flex_db() self.counters_db = dvs.get_counters_db() + self.app_db = dvs.get_app_db() def wait_for_table(self, table): for retry in range(NUMBER_OF_RETRIES): @@ -62,6 +82,16 @@ def wait_for_table(self, table): assert False, str(table) + " not created in Counters DB" + def wait_for_table_empty(self, table): + for retry in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(table) + if len(counters_keys) == 0: + return + else: + time.sleep(1) + + assert False, str(table) + " is still in Counters DB" + def wait_for_id_list(self, stat, name, oid): for retry in range(NUMBER_OF_RETRIES): id_list = self.flex_db.db_connection.hgetall("FLEX_COUNTER_TABLE:" + stat + ":" + oid).items() @@ -72,6 +102,27 @@ def wait_for_id_list(self, stat, name, oid): assert False, "No ID list for counter " + str(name) + def wait_for_id_list_remove(self, stat, name, oid): + for retry in range(NUMBER_OF_RETRIES): + id_list = self.flex_db.db_connection.hgetall("FLEX_COUNTER_TABLE:" + stat + ":" + oid).items() + if len(id_list) == 0: + return + else: + time.sleep(1) + + assert False, "ID list for counter " + str(name) + " is still there" + + def wait_for_interval_set(self, group, interval): + interval_value = None + for retry in range(NUMBER_OF_RETRIES): + interval_value = self.flex_db.db_connection.hget("FLEX_COUNTER_GROUP_TABLE:" + group, 'POLL_INTERVAL') + if interval_value == interval: + return + else: + time.sleep(1) + + assert False, "Polling interval is not applied to FLEX_COUNTER_GROUP_TABLE for group {}, expect={}, actual={}".format(group, interval, interval_value) + def verify_no_flex_counters_tables(self, counter_stat): counters_stat_keys = self.flex_db.get_keys("FLEX_COUNTER_TABLE:" + counter_stat) assert len(counters_stat_keys) == 0, "FLEX_COUNTER_TABLE:" + str(counter_stat) + " tables exist before enabling the flex counter group" @@ -92,26 +143,34 @@ def verify_flex_counters_populated(self, map, stat): oid = counter_entry[1] self.wait_for_id_list(stat, name, oid) - def verify_tunnel_type_vxlan(self, name_map, type_map): - counters_keys = self.counters_db.db_connection.hgetall(name_map) + def verify_tunnel_type_vxlan(self, meta_data, type_map): + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) for counter_entry in counters_keys.items(): oid = counter_entry[1] fvs = self.counters_db.get_entry(type_map, "") assert fvs != {} assert fvs.get(oid) == "SAI_TUNNEL_TYPE_VXLAN" - def verify_only_phy_ports_created(self): - port_counters_keys = self.counters_db.db_connection.hgetall(PORT_MAP) - port_counters_stat_keys = self.flex_db.get_keys("FLEX_COUNTER_TABLE:" + PORT_STAT) + def verify_only_phy_ports_created(self, meta_data): + port_counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + port_counters_stat_keys = self.flex_db.get_keys("FLEX_COUNTER_TABLE:" + meta_data['group_name']) for port_stat in port_counters_stat_keys: assert port_stat in dict(port_counters_keys.items()).values(), "Non PHY port created on PORT_STAT_COUNTER group: {}".format(port_stat) - def enable_flex_counter_group(self, group, map): - group_stats_entry = {"FLEX_COUNTER_STATUS": "enable"} + def set_flex_counter_group_status(self, group, map, status='enable'): + group_stats_entry = {"FLEX_COUNTER_STATUS": status} self.config_db.create_entry("FLEX_COUNTER_TABLE", group, group_stats_entry) - self.wait_for_table(map) + if status == 'enable': + self.wait_for_table(map) + else: + self.wait_for_table_empty(map) - @pytest.mark.parametrize("counter_type", counter_type_dict.keys()) + def set_flex_counter_group_interval(self, key, group, interval): + group_stats_entry = {"POLL_INTERVAL": interval} + self.config_db.create_entry("FLEX_COUNTER_TABLE", key, group_stats_entry) + self.wait_for_interval_set(group, interval) + + @pytest.mark.parametrize("counter_type", counter_group_meta.keys()) def test_flex_counters(self, dvs, counter_type): """ The test will check there are no flex counters tables on FlexCounter DB when the counters are disabled. @@ -119,50 +178,197 @@ def test_flex_counters(self, dvs, counter_type): For some counter types the MAPS on COUNTERS DB will be created as well after enabling the counter group, this will be also verified on this test. """ self.setup_dbs(dvs) - counter_key = counter_type_dict[counter_type][0] - counter_stat = counter_type_dict[counter_type][1] - counter_map = counter_type_dict[counter_type][2] + meta_data = counter_group_meta[counter_type] + counter_key = meta_data['key'] + counter_stat = meta_data['group_name'] + counter_map = meta_data['name_map'] + pre_test = meta_data.get('pre_test') + post_test = meta_data.get('post_test') self.verify_no_flex_counters_tables(counter_stat) - if counter_type == "rif_counter": - self.config_db.db_connection.hset('INTERFACE|Ethernet0', "NULL", "NULL") - self.config_db.db_connection.hset('INTERFACE|Ethernet0|192.168.0.1/24', "NULL", "NULL") - elif counter_type == "acl_counter": - self.config_db.create_entry('ACL_TABLE', 'DATAACL', - { - 'STAGE': 'INGRESS', - 'PORTS': 'Ethernet0', - 'TYPE': 'L3' - } - ) - self.config_db.create_entry('ACL_RULE', 'DATAACL|RULE0', - { - 'ETHER_TYPE': '2048', - 'PACKET_ACTION': 'FORWARD', - 'PRIORITY': '9999' - } - ) - - if counter_type == "vxlan_tunnel_counter": - self.config_db.db_connection.hset("VLAN|Vlan10", "vlanid", "10") - self.config_db.db_connection.hset("VXLAN_TUNNEL|vtep1", "src_ip", "1.1.1.1") - self.config_db.db_connection.hset("VXLAN_TUNNEL_MAP|vtep1|map_100_Vlan10", "vlan", "Vlan10") - self.config_db.db_connection.hset("VXLAN_TUNNEL_MAP|vtep1|map_100_Vlan10", "vni", "100") - - self.enable_flex_counter_group(counter_key, counter_map) + if pre_test: + cb = getattr(self, pre_test) + cb(meta_data) + + self.set_flex_counter_group_status(counter_key, counter_map) self.verify_flex_counters_populated(counter_map, counter_stat) + self.set_flex_counter_group_interval(counter_key, counter_stat, '2500') + + if post_test: + cb = getattr(self, post_test) + cb(meta_data) + + def pre_rif_counter_test(self, meta_data): + self.config_db.db_connection.hset('INTERFACE|Ethernet0', "NULL", "NULL") + self.config_db.db_connection.hset('INTERFACE|Ethernet0|192.168.0.1/24', "NULL", "NULL") + + def pre_vxlan_tunnel_counter_test(self, meta_data): + self.config_db.db_connection.hset("VLAN|Vlan10", "vlanid", "10") + self.config_db.db_connection.hset("VXLAN_TUNNEL|vtep1", "src_ip", "1.1.1.1") + self.config_db.db_connection.hset("VXLAN_TUNNEL_MAP|vtep1|map_100_Vlan10", "vlan", "Vlan10") + self.config_db.db_connection.hset("VXLAN_TUNNEL_MAP|vtep1|map_100_Vlan10", "vni", "100") + + def pre_acl_tunnel_counter_test(self, meta_data): + self.config_db.create_entry('ACL_TABLE', 'DATAACL', + { + 'STAGE': 'INGRESS', + 'PORTS': 'Ethernet0', + 'TYPE': 'L3' + } + ) + self.config_db.create_entry('ACL_RULE', 'DATAACL|RULE0', + { + 'ETHER_TYPE': '2048', + 'PACKET_ACTION': 'FORWARD', + 'PRIORITY': '9999' + } + ) + + def post_rif_counter_test(self, meta_data): + self.config_db.db_connection.hdel('INTERFACE|Ethernet0|192.168.0.1/24', "NULL") + + def post_port_counter_test(self, meta_data): + self.verify_only_phy_ports_created(meta_data) + + def post_trap_flow_counter_test(self, meta_data): + """Post verification for test_flex_counters for trap_flow_counter. Steps: + 1. Disable test_flex_counters + 2. Verify name map and counter ID list are cleared + 3. Clear trap ids to avoid affecting further test cases + + Args: + meta_data (object): flex counter meta data + """ + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + self.set_flex_counter_group_status(meta_data['key'], meta_data['group_name'], 'disable') + + for counter_entry in counters_keys.items(): + self.wait_for_id_list_remove(meta_data['group_name'], counter_entry[0], counter_entry[1]) + self.wait_for_table_empty(meta_data['name_map']) + + def post_vxlan_tunnel_counter_test(self, meta_data): + self.verify_tunnel_type_vxlan(meta_data, TUNNEL_TYPE_MAP) + self.config_db.delete_entry("VLAN","Vlan10") + self.config_db.delete_entry("VLAN_TUNNEL","vtep1") + self.config_db.delete_entry("VLAN_TUNNEL_MAP","vtep1|map_100_Vlan10") + self.verify_no_flex_counters_tables_after_delete(meta_data['group_name']) + + def post_acl_tunnel_counter_test(self, meta_data): + self.config_db.delete_entry('ACL_RULE', 'DATAACL|RULE0') + self.config_db.delete_entry('ACL_TABLE', 'DATAACL') + + def test_add_remove_trap(self, dvs): + """Test steps: + 1. Enable trap_flow_counter + 2. Remove a COPP trap + 3. Verify counter is automatically unbind + 4. Add the COPP trap back + 5. Verify counter is added back + + Args: + dvs (object): virtual switch object + """ + self.setup_dbs(dvs) + meta_data = counter_group_meta['trap_flow_counter'] + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map']) + + removed_trap = None + changed_group = None + trap_ids = None + copp_groups = self.app_db.db_connection.keys('COPP_TABLE:*') + for copp_group in copp_groups: + trap_ids = self.app_db.db_connection.hget(copp_group, 'trap_ids') + if trap_ids and ',' in trap_ids: + trap_ids = [x.strip() for x in trap_ids.split(',')] + removed_trap = trap_ids.pop() + changed_group = copp_group.split(':')[1] + break + + if not removed_trap: + pytest.skip('There is not copp group with more than 1 traps, skip rest of the test') + + oid = None + for _ in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + if removed_trap in counters_keys: + oid = counters_keys[removed_trap] + break + else: + time.sleep(1) + + assert oid, 'trap counter is not created for {}'.format(removed_trap) + self.wait_for_id_list(meta_data['group_name'], removed_trap, oid) + + app_copp_table = swsscommon.ProducerStateTable(self.app_db.db_connection, 'COPP_TABLE') + app_copp_table.set(changed_group, [('trap_ids', ','.join(trap_ids))]) + self.wait_for_id_list_remove(meta_data['group_name'], removed_trap, oid) + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + assert removed_trap not in counters_keys + + trap_ids.append(removed_trap) + app_copp_table.set(changed_group, [('trap_ids', ','.join(trap_ids))]) + + oid = None + for _ in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + if removed_trap in counters_keys: + oid = counters_keys[removed_trap] + break + else: + time.sleep(1) + + assert oid, 'Add trap {}, but trap counter is not created'.format(removed_trap) + self.wait_for_id_list(meta_data['group_name'], removed_trap, oid) + self.set_flex_counter_group_status(meta_data['key'], meta_data['group_name'], 'disable') + + def test_remove_trap_group(self, dvs): + """Remove trap group and verify that all related trap counters are removed + + Args: + dvs (object): virtual switch object + """ + self.setup_dbs(dvs) + meta_data = counter_group_meta['trap_flow_counter'] + self.set_flex_counter_group_status(meta_data['key'], meta_data['name_map']) + + removed_group = None + trap_ids = None + copp_groups = self.app_db.db_connection.keys('COPP_TABLE:*') + for copp_group in copp_groups: + trap_ids = self.app_db.db_connection.hget(copp_group, 'trap_ids') + if trap_ids and trap_ids.strip(): + removed_group = copp_group.split(':')[1] + break + + if not removed_group: + pytest.skip('There is not copp group with at least 1 traps, skip rest of the test') + + trap_ids = [x.strip() for x in trap_ids.split(',')] + for _ in range(NUMBER_OF_RETRIES): + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + found = True + for trap_id in trap_ids: + if trap_id not in counters_keys: + found = False + break + if found: + break + else: + time.sleep(1) + + assert found, 'Not all trap id found in name map' + for trap_id in trap_ids: + self.wait_for_id_list(meta_data['group_name'], trap_id, counters_keys[trap_id]) + + app_copp_table = swsscommon.ProducerStateTable(self.app_db.db_connection, 'COPP_TABLE') + app_copp_table._del(removed_group) + + for trap_id in trap_ids: + self.wait_for_id_list_remove(meta_data['group_name'], trap_id, counters_keys[trap_id]) + + counters_keys = self.counters_db.db_connection.hgetall(meta_data['name_map']) + for trap_id in trap_ids: + assert trap_id not in counters_keys - if counter_type == "port_counter": - self.verify_only_phy_ports_created() - elif counter_type == "rif_counter": - self.config_db.db_connection.hdel('INTERFACE|Ethernet0|192.168.0.1/24', "NULL") - elif counter_type == "acl_counter": - self.config_db.delete_entry('ACL_RULE', 'DATAACL|RULE0') - self.config_db.delete_entry('ACL_TABLE', 'DATAACL') - elif counter_type == "vxlan_tunnel_counter": - self.verify_tunnel_type_vxlan(counter_map, TUNNEL_TYPE_MAP) - self.config_db.delete_entry("VLAN","Vlan10") - self.config_db.delete_entry("VLAN_TUNNEL","vtep1") - self.config_db.delete_entry("VLAN_TUNNEL_MAP","vtep1|map_100_Vlan10") - self.verify_no_flex_counters_tables_after_delete(counter_stat) + self.set_flex_counter_group_status(meta_data['key'], meta_data['group_name'], 'disable') From 15a3b6cae4cfa103984620d8a7e4eb4c401167ba Mon Sep 17 00:00:00 2001 From: Akhilesh Samineni <47657796+AkhileshSamineni@users.noreply.github.com> Date: Wed, 1 Dec 2021 11:02:00 +0530 Subject: [PATCH 14/14] Delete the IPv6 link-local Neighbor when ipv6 link-local mode is disabled (#1897) * IPv6 link-local Neighbor deletion when ipv6 link-local mode is disabled. Signed-off-by: Akhilesh Samineni --- cfgmgr/intfmgr.cpp | 48 +++++++++++++++ cfgmgr/intfmgr.h | 3 + neighsyncd/neighsync.cpp | 2 +- tests/test_ipv6_link_local.py | 112 ++++++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 tests/test_ipv6_link_local.py diff --git a/cfgmgr/intfmgr.cpp b/cfgmgr/intfmgr.cpp index 1567762ffa..8489d09bb3 100644 --- a/cfgmgr/intfmgr.cpp +++ b/cfgmgr/intfmgr.cpp @@ -40,6 +40,7 @@ IntfMgr::IntfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, c m_stateVrfTable(stateDb, STATE_VRF_TABLE_NAME), m_stateIntfTable(stateDb, STATE_INTERFACE_TABLE_NAME), m_appIntfTableProducer(appDb, APP_INTF_TABLE_NAME), + m_neighTable(appDb, APP_NEIGH_TABLE_NAME), m_appLagTable(appDb, APP_LAG_TABLE_NAME) { auto subscriberStateTable = new swss::SubscriberStateTable(stateDb, @@ -616,6 +617,35 @@ bool IntfMgr::isIntfStateOk(const string &alias) return false; } +void IntfMgr::delIpv6LinkLocalNeigh(const string &alias) +{ + vector neighEntries; + + SWSS_LOG_INFO("Deleting ipv6 link local neighbors for %s", alias.c_str()); + + m_neighTable.getKeys(neighEntries); + for (auto neighKey : neighEntries) + { + if (!neighKey.compare(0, alias.size(), alias.c_str())) + { + vector keys = tokenize(neighKey, ':', 1); + if (keys.size() == 2) + { + IpAddress ipAddress(keys[1]); + if (ipAddress.getAddrScope() == IpAddress::AddrScope::LINK_SCOPE) + { + stringstream cmd; + string res; + + cmd << IP_CMD << " neigh del dev " << keys[0] << " " << keys[1] ; + swss::exec(cmd.str(), res); + SWSS_LOG_INFO("Deleted ipv6 link local neighbor - %s", keys[1].c_str()); + } + } + } + } +} + bool IntfMgr::doIntfGeneralTask(const vector& keys, vector data, const string& op) @@ -755,6 +785,17 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, /* Set ipv6 mode */ if (!ipv6_link_local_mode.empty()) { + if ((ipv6_link_local_mode == "enable") && (m_ipv6LinkLocalModeList.find(alias) == m_ipv6LinkLocalModeList.end())) + { + m_ipv6LinkLocalModeList.insert(alias); + SWSS_LOG_INFO("Inserted ipv6 link local mode list for %s", alias.c_str()); + } + else if ((ipv6_link_local_mode == "disable") && (m_ipv6LinkLocalModeList.find(alias) != m_ipv6LinkLocalModeList.end())) + { + m_ipv6LinkLocalModeList.erase(alias); + delIpv6LinkLocalNeigh(alias); + SWSS_LOG_INFO("Erased ipv6 link local mode list for %s", alias.c_str()); + } FieldValueTuple fvTuple("ipv6_use_link_local_only", ipv6_link_local_mode); data.push_back(fvTuple); } @@ -910,6 +951,13 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, removeSubIntfState(alias); } + if (m_ipv6LinkLocalModeList.find(alias) != m_ipv6LinkLocalModeList.end()) + { + m_ipv6LinkLocalModeList.erase(alias); + delIpv6LinkLocalNeigh(alias); + SWSS_LOG_INFO("Erased ipv6 link local mode list for %s", alias.c_str()); + } + m_appIntfTableProducer.del(alias); m_stateIntfTable.del(alias); } diff --git a/cfgmgr/intfmgr.h b/cfgmgr/intfmgr.h index e1a5049fff..84c0020eb0 100644 --- a/cfgmgr/intfmgr.h +++ b/cfgmgr/intfmgr.h @@ -31,10 +31,12 @@ class IntfMgr : public Orch ProducerStateTable m_appIntfTableProducer; Table m_cfgIntfTable, m_cfgVlanIntfTable, m_cfgLagIntfTable, m_cfgLoopbackIntfTable; Table m_statePortTable, m_stateLagTable, m_stateVlanTable, m_stateVrfTable, m_stateIntfTable, m_appLagTable; + Table m_neighTable; SubIntfMap m_subIntfList; std::set m_loopbackIntfList; std::set m_pendingReplayIntfList; + std::set m_ipv6LinkLocalModeList; void setIntfIp(const std::string &alias, const std::string &opCmd, const IpPrefix &ipPrefix); void setIntfVrf(const std::string &alias, const std::string &vrfName); @@ -65,6 +67,7 @@ class IntfMgr : public Orch void removeHostSubIntf(const std::string &subIntf); void setSubIntfStateOk(const std::string &alias); void removeSubIntfState(const std::string &alias); + void delIpv6LinkLocalNeigh(const std::string &alias); bool setIntfProxyArp(const std::string &alias, const std::string &proxy_arp); bool setIntfGratArp(const std::string &alias, const std::string &grat_arp); diff --git a/neighsyncd/neighsync.cpp b/neighsyncd/neighsync.cpp index 6b1abc235f..cb04371d41 100644 --- a/neighsyncd/neighsync.cpp +++ b/neighsyncd/neighsync.cpp @@ -82,7 +82,7 @@ void NeighSync::onMsg(int nlmsg_type, struct nl_object *obj) /* Ignore IPv6 link-local addresses as neighbors, if ipv6 link local mode is disabled */ if (family == IPV6_NAME && IN6_IS_ADDR_LINKLOCAL(nl_addr_get_binary_addr(rtnl_neigh_get_dst(neigh)))) { - if (isLinkLocalEnabled(intfName) == false) + if ((isLinkLocalEnabled(intfName) == false) && (nlmsg_type != RTM_DELNEIGH)) { return; } diff --git a/tests/test_ipv6_link_local.py b/tests/test_ipv6_link_local.py new file mode 100644 index 0000000000..048b8f2e17 --- /dev/null +++ b/tests/test_ipv6_link_local.py @@ -0,0 +1,112 @@ +import time +import json +import pytest + +from swsscommon import swsscommon + +class TestIPv6LinkLocal(object): + def setup_db(self, dvs): + self.pdb = dvs.get_app_db() + self.adb = dvs.get_asic_db() + self.cdb = dvs.get_config_db() + + def set_admin_status(self, interface, status): + self.cdb.update_entry("PORT", interface, {"admin_status": status}) + + def add_ip_address(self, interface, ip): + self.cdb.create_entry("INTERFACE", interface + "|" + ip, {"NULL": "NULL"}) + time.sleep(2) + + def remove_ip_address(self, interface, ip): + self.cdb.delete_entry("INTERFACE", interface + "|" + ip) + time.sleep(2) + + def create_ipv6_link_local_intf(self, interface): + self.cdb.create_entry("INTERFACE", interface, {"ipv6_use_link_local_only": "enable"}) + time.sleep(2) + + def remove_ipv6_link_local_intf(self, interface): + self.cdb.delete_entry("INTERFACE", interface) + time.sleep(2) + + def test_NeighborAddRemoveIpv6LinkLocal(self, dvs, testlog): + self.setup_db(dvs) + + # create ipv6 link local intf + self.create_ipv6_link_local_intf("Ethernet0") + self.create_ipv6_link_local_intf("Ethernet4") + + # bring up interface + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") + + # set ip address + self.add_ip_address("Ethernet0", "2000::1/64") + self.add_ip_address("Ethernet4", "2001::1/64") + dvs.runcmd("sysctl -w net.ipv6.conf.all.forwarding=1") + dvs.runcmd("sysctl -w net.ipv6.conf.default.forwarding=1") + + # set ip address and default route + dvs.servers[0].runcmd("ip -6 address add 2000::2/64 dev eth0") + dvs.servers[0].runcmd("ip -6 route add default via 2000::1") + + dvs.servers[1].runcmd("ip -6 address add 2001::2/64 dev eth0") + dvs.servers[1].runcmd("ip -6 route add default via 2001::1") + time.sleep(2) + + # get neighbor entry + dvs.servers[0].runcmd("ping -6 -c 1 2001::2") + time.sleep(2) + + # Neigh entries should contain Ipv6-link-local neighbors, should be 4 + neigh_entries = self.pdb.get_keys("NEIGH_TABLE") + assert (len(neigh_entries) == 4) + + found_entry = False + for key in neigh_entries: + if (key.find("Ethernet4:2001::2") or key.find("Ethernet0:2000::2")): + found_entry = True + + assert found_entry + + # remove ip address + self.remove_ip_address("Ethernet0", "2000::1/64") + self.remove_ip_address("Ethernet4", "2001::1/64") + + # remove ipv6 link local intf + self.remove_ipv6_link_local_intf("Ethernet0") + self.remove_ipv6_link_local_intf("Ethernet4") + + # Neigh entries should not contain Ipv6-link-local neighbors, should be 2 + neigh_after_entries = self.pdb.get_keys("NEIGH_TABLE") + print(neigh_after_entries) + assert (len(neigh_after_entries) == 2) + + found_existing_entry = False + for key in neigh_entries: + if (key.find("Ethernet4:2001::2") or key.find("Ethernet0:2000::2")): + found_existing_entry = True + + assert found_existing_entry + + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + + # remove ip address and default route + dvs.servers[0].runcmd("ip -6 route del default dev eth0") + dvs.servers[0].runcmd("ip -6 address del 2000::2/64 dev eth0") + + dvs.servers[1].runcmd("ip -6 route del default dev eth0") + dvs.servers[1].runcmd("ip -6 address del 2001::2/64 dev eth0") + + dvs.runcmd("sysctl -w net.ipv6.conf.all.forwarding=0") + dvs.runcmd("sysctl -w net.ipv6.conf.default.forwarding=0") + + neigh_entries = self.pdb.get_keys("NEIGH_TABLE") + assert (len(neigh_entries) == 0) + +# Add Dummy always-pass test at end as workaroud +# for issue when Flaky fail on final test it invokes module tear-down before retrying +def test_nonflaky_dummy(): + pass +