From eb79ca4a524899f57995abd5ae5ec5f773ec85d0 Mon Sep 17 00:00:00 2001 From: Nazarii Hnydyn Date: Wed, 1 Sep 2021 04:59:39 +0300 Subject: [PATCH] [pbh]: Add PBH OA (#1782) * [pbh]: Add PBH OA. * [pbh]: Helper and PBH Container Signed-off-by: Nazarii Hnydyn --- orchagent/Makefile.am | 11 +- orchagent/aclorch.cpp | 318 ++++++- orchagent/aclorch.h | 70 +- orchagent/acltable.h | 3 +- orchagent/orchdaemon.cpp | 20 + orchagent/orchdaemon.h | 1 + orchagent/pbh/pbhcnt.cpp | 97 ++ orchagent/pbh/pbhcnt.h | 177 ++++ orchagent/pbh/pbhmgr.cpp | 1142 +++++++++++++++++++++++ orchagent/pbh/pbhmgr.h | 116 +++ orchagent/pbh/pbhrule.cpp | 113 +++ orchagent/pbh/pbhrule.h | 15 + orchagent/pbhorch.cpp | 1497 +++++++++++++++++++++++++++++++ orchagent/pbhorch.h | 78 ++ orchagent/saihelper.cpp | 3 + tests/conftest.py | 12 +- tests/dvslib/dvs_acl.py | 36 + tests/dvslib/dvs_pbh.py | 127 +++ tests/mock_tests/Makefile.am | 4 + tests/mock_tests/aclorch_ut.cpp | 127 +++ tests/test_pbh.py | 797 ++++++++++++++++ 21 files changed, 4712 insertions(+), 52 deletions(-) create mode 100644 orchagent/pbh/pbhcnt.cpp create mode 100644 orchagent/pbh/pbhcnt.h create mode 100644 orchagent/pbh/pbhmgr.cpp create mode 100644 orchagent/pbh/pbhmgr.h create mode 100644 orchagent/pbh/pbhrule.cpp create mode 100644 orchagent/pbh/pbhrule.h create mode 100644 orchagent/pbhorch.cpp create mode 100644 orchagent/pbhorch.h create mode 100644 tests/dvslib/dvs_pbh.py create mode 100644 tests/test_pbh.py diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index f4317134f630..d4f362720327 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -1,4 +1,9 @@ -INCLUDES = -I $(top_srcdir)/lib -I $(top_srcdir) -I $(top_srcdir)/warmrestart -I flex_counter -I debug_counter +INCLUDES = -I $(top_srcdir)/lib \ + -I $(top_srcdir) \ + -I $(top_srcdir)/warmrestart \ + -I flex_counter \ + -I debug_counter \ + -I pbh CFLAGS_SAI = -I /usr/include/sai @@ -46,6 +51,10 @@ orchagent_SOURCES = \ mirrororch.cpp \ fdborch.cpp \ aclorch.cpp \ + pbh/pbhcnt.cpp \ + pbh/pbhmgr.cpp \ + pbh/pbhrule.cpp \ + pbhorch.cpp \ saihelper.cpp \ switchorch.cpp \ pfcwdorch.cpp \ diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index 54a57c47ede5..77fa2e130c51 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -157,18 +157,18 @@ static acl_ip_type_lookup_t aclIpTypeLookup = { IP_TYPE_ARP_REPLY, SAI_ACL_IP_TYPE_ARP_REPLY } }; -AclRule::AclRule(AclOrch *aclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : - m_pAclOrch(aclOrch), - 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) +AclRule::AclRule(AclOrch *pAclOrch, string rule, string table, acl_table_type_t type, 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 = aclOrch->getTableById(m_tableId); + m_tableOid = pAclOrch->getTableById(m_tableId); } bool AclRule::validateAddPriority(string attr_name, string attr_value) @@ -486,8 +486,6 @@ bool AclRule::create() return false; } - SWSS_LOG_INFO("Created counter for the rule %s in table %s", m_id.c_str(), m_tableId.c_str()); - // store table oid this rule belongs to attr.id = SAI_ACL_ENTRY_ATTR_TABLE_ID; attr.value.oid = table_oid; @@ -630,10 +628,7 @@ bool AclRule::remove() decreaseNextHopRefCount(); res = removeRanges(); - if (m_createCounter) - { - res &= removeCounter(); - } + res &= removeCounter(); return res; } @@ -659,7 +654,7 @@ AclRuleCounters AclRule::getCounters() { SWSS_LOG_ENTER(); - if (!m_createCounter) + if (m_counterOid == SAI_NULL_OBJECT_ID) { return AclRuleCounters(); } @@ -767,6 +762,79 @@ shared_ptr AclRule::makeShared(acl_table_type_t type, AclOrch *acl, Mir throw runtime_error("Wrong combination of table type and action in rule " + rule); } +bool AclRule::enableCounter() +{ + SWSS_LOG_ENTER(); + + if (m_counterOid != SAI_NULL_OBJECT_ID) + { + return true; + } + + 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()); + return false; + } + + if (!createCounter()) + { + return false; + } + + sai_attribute_t attr; + + attr.id = SAI_ACL_ENTRY_ATTR_ACTION_COUNTER; + attr.value.aclaction.parameter.oid = m_counterOid; + attr.value.aclaction.enable = true; + + 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()); + removeCounter(); + return false; + } + + return true; +} + +bool AclRule::disableCounter() +{ + SWSS_LOG_ENTER(); + + if (m_counterOid == SAI_NULL_OBJECT_ID) + { + return true; + } + + 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()); + return false; + } + + sai_attribute_t attr; + + attr.id = SAI_ACL_ENTRY_ATTR_ACTION_COUNTER; + attr.value.aclaction.parameter.oid = SAI_NULL_OBJECT_ID; + attr.value.aclaction.enable = false; + + 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()); + return false; + } + + if (!removeCounter()) + { + return false; + } + + return true; +} + bool AclRule::createCounter() { SWSS_LOG_ENTER(); @@ -774,6 +842,11 @@ bool AclRule::createCounter() sai_attribute_t attr; vector counter_attrs; + if (m_counterOid != SAI_NULL_OBJECT_ID) + { + return true; + } + attr.id = SAI_ACL_COUNTER_ATTR_TABLE_ID; attr.value.oid = m_tableOid; counter_attrs.push_back(attr); @@ -794,6 +867,8 @@ bool AclRule::createCounter() gCrmOrch->incCrmAclTableUsedCounter(CrmResourceType::CRM_ACL_COUNTER, m_tableOid); + SWSS_LOG_INFO("Created counter for the rule %s in table %s", m_id.c_str(), m_tableId.c_str()); + return true; } @@ -833,6 +908,8 @@ bool AclRule::removeCounter() 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()); + return true; } @@ -1337,6 +1414,83 @@ bool AclRuleMclag::validate() return true; } +AclTable::AclTable(AclOrch *pAclOrch, string id) noexcept : m_pAclOrch(pAclOrch), id(id) +{ + +} + +AclTable::AclTable(AclOrch *pAclOrch) noexcept : m_pAclOrch(pAclOrch) +{ + +} + +bool AclTable::validateAddType(const acl_table_type_t &value) +{ + 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; + + return true; +} + +bool AclTable::validateAddStage(const acl_stage_type_t &value) +{ + SWSS_LOG_ENTER(); + + if (value == ACL_STAGE_UNKNOWN) + { + SWSS_LOG_ERROR("Failed to validate stage: unknown stage"); + return false; + } + + stage = value; + + return true; +} + +bool AclTable::validateAddPorts(const unordered_set &value) +{ + SWSS_LOG_ENTER(); + + for (const auto &itAlias: value) + { + Port port; + if (!gPortsOrch->getPort(itAlias, port)) + { + SWSS_LOG_INFO( + "Add unready port %s to pending list for ACL table %s", + itAlias.c_str(), id.c_str() + ); + pendingPortSet.emplace(itAlias); + continue; + } + + sai_object_id_t bindPortOid; + if (!AclOrch::getAclBindPortId(port, bindPortOid)) + { + SWSS_LOG_ERROR( + "Failed to get port %s bind port ID for ACL table %s", + itAlias.c_str(), id.c_str() + ); + return false; + } + + link(bindPortOid); + portSet.emplace(itAlias); + } + + return true; +} + bool AclTable::validate() { if (type == ACL_TABLE_CTRLPLANE) @@ -1371,6 +1525,47 @@ bool AclTable::create() attr.value.s32list.list = bpoint_list.data(); table_attrs.push_back(attr); + if (type == ACL_TABLE_PBH) + { + 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) + { + 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); + } + + return status == SAI_STATUS_SUCCESS; + } + if ((type == ACL_TABLE_PFCWD) || (type == ACL_TABLE_DROP)) { attr.id = SAI_ACL_TABLE_ATTR_FIELD_TC; @@ -2773,6 +2968,26 @@ bool AclOrch::updateAclTable(AclTable ¤tTable, AclTable &newTable) return true; } +bool AclOrch::updateAclTable(string table_id, AclTable &table) +{ + SWSS_LOG_ENTER(); + + auto tableOid = getTableById(table_id); + if (tableOid == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("Failed to update ACL table %s: object doesn't exist", table_id.c_str()); + return false; + } + + if (!updateAclTable(m_AclTables.at(tableOid), table)) + { + SWSS_LOG_ERROR("Failed to update ACL table %s", table_id.c_str()); + return false; + } + + return true; +} + bool AclOrch::addAclTable(AclTable &newTable) { SWSS_LOG_ENTER(); @@ -3053,11 +3268,78 @@ bool AclOrch::updateAclRule(string table_id, string rule_id, string attr_name, v return true; } +bool AclOrch::updateAclRule(string table_id, string rule_id, bool enableCounter) +{ + SWSS_LOG_ENTER(); + + auto tableOid = getTableById(table_id); + if (tableOid == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR( + "Failed to update ACL rule %s: ACL table %s doesn't exist", + rule_id.c_str(), + table_id.c_str() + ); + return false; + } + + const auto &cit = m_AclTables.at(tableOid).rules.find(rule_id); + if (cit == m_AclTables.at(tableOid).rules.cend()) + { + SWSS_LOG_ERROR( + "Failed to update ACL rule %s in ACL table %s: object doesn't exist", + rule_id.c_str(), + table_id.c_str() + ); + return false; + } + + auto &rule = cit->second; + + if (enableCounter) + { + if (!rule->enableCounter()) + { + SWSS_LOG_ERROR( + "Failed to enable ACL counter for ACL rule %s in ACL table %s", + rule_id.c_str(), + table_id.c_str() + ); + return false; + } + + return true; + } + + if (!rule->disableCounter()) + { + SWSS_LOG_ERROR( + "Failed to disable ACL counter for ACL rule %s in ACL table %s", + rule_id.c_str(), + table_id.c_str() + ); + return false; + } + + return true; +} + bool AclOrch::isCombinedMirrorV6Table() { return m_isCombinedMirrorV6Table; } +bool AclOrch::isAclMirrorTableSupported(acl_table_type_t type) const +{ + const auto &cit = m_mirrorTableCapabilities.find(type); + if (cit == m_mirrorTableCapabilities.cend()) + { + return false; + } + + return cit->second; +} + bool AclOrch::isAclActionSupported(acl_stage_type_t stage, sai_acl_action_type_t action) const { const auto& it = m_aclCapabilities.find(stage); diff --git a/orchagent/aclorch.h b/orchagent/aclorch.h index b5f47736665b..f2a5e0826b87 100644 --- a/orchagent/aclorch.h +++ b/orchagent/aclorch.h @@ -154,7 +154,7 @@ struct AclRuleCounters class AclRule { public: - AclRule(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = true); + 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); @@ -170,6 +170,9 @@ class AclRule virtual bool remove(); virtual void update(SubjectType, void *) = 0; virtual void updateInPorts(); + + virtual bool enableCounter(); + virtual bool disableCounter(); virtual AclRuleCounters getCounters(); string getId() @@ -316,40 +319,24 @@ class AclRuleMclag: public AclRuleL3 bool validate(); }; -class AclTable { - sai_object_id_t m_oid; - AclOrch *m_pAclOrch; +class AclTable +{ public: - string id; - string description; - acl_table_type_t type; - acl_stage_type_t stage; + AclTable(AclOrch *pAclOrch, string id) noexcept; + AclTable(AclOrch *pAclOrch) noexcept; - // Map port oid to group member oid - std::map ports; - // Map rule name to rule data - map> rules; - // Set to store the ACL table port alias - set portSet; - // Set to store the not configured ACL table port alias - set pendingPortSet; - - AclTable() - : m_pAclOrch(NULL) - , type(ACL_TABLE_UNKNOWN) - , m_oid(SAI_NULL_OBJECT_ID) - , stage(ACL_STAGE_INGRESS) - {} - - AclTable(AclOrch *aclOrch) - : m_pAclOrch(aclOrch) - , type(ACL_TABLE_UNKNOWN) - , m_oid(SAI_NULL_OBJECT_ID) - , stage(ACL_STAGE_INGRESS) - {} + AclTable() = default; + ~AclTable() = default; sai_object_id_t getOid() { return m_oid; } string getId() { return id; } + + void setDescription(const string &value) { description = value; } + const string& getDescription() const { return description; } + + bool validateAddType(const acl_table_type_t &value); + bool validateAddStage(const acl_stage_type_t &value); + bool validateAddPorts(const unordered_set &value); bool validate(); bool create(); @@ -373,6 +360,26 @@ class AclTable { bool clear(); // Update table subject to changes void update(SubjectType, void *); + +public: + string id; + string description; + + acl_table_type_t type = ACL_TABLE_UNKNOWN; + acl_stage_type_t stage = ACL_STAGE_INGRESS; + + // Map port oid to group member oid + std::map ports; + // Map rule name to rule data + map> rules; + // Set to store the ACL table port alias + set portSet; + // Set to store the not configured ACL table port alias + set pendingPortSet; + +private: + sai_object_id_t m_oid = SAI_NULL_OBJECT_ID; + AclOrch *m_pAclOrch = nullptr; }; class AclOrch : public Orch, public Observer @@ -405,12 +412,15 @@ class AclOrch : public Orch, public Observer bool addAclTable(AclTable &aclTable); bool removeAclTable(string table_id); bool updateAclTable(AclTable ¤tTable, AclTable &newTable); + 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(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); bool isCombinedMirrorV6Table(); + bool isAclMirrorTableSupported(acl_table_type_t type) 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; diff --git a/orchagent/acltable.h b/orchagent/acltable.h index 7dd345e1e823..44d6ea4dbf81 100644 --- a/orchagent/acltable.h +++ b/orchagent/acltable.h @@ -54,7 +54,8 @@ typedef enum ACL_TABLE_DTEL_DROP_WATCHLIST, ACL_TABLE_MCLAG, ACL_TABLE_MUX, - ACL_TABLE_DROP + ACL_TABLE_DROP, + ACL_TABLE_PBH } acl_table_type_t; typedef std::unordered_map acl_table_type_lookup_t; diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index a546ffdac14b..814729ee0cfc 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -33,6 +33,7 @@ NeighOrch *gNeighOrch; RouteOrch *gRouteOrch; FgNhgOrch *gFgNhgOrch; AclOrch *gAclOrch; +PbhOrch *gPbhOrch; MirrorOrch *gMirrorOrch; CrmOrch *gCrmOrch; BufferOrch *gBufferOrch; @@ -343,9 +344,28 @@ bool OrchDaemon::init() gIsoGrpOrch = new IsoGrpOrch(iso_grp_tbl_ctrs); + // + // Policy Based Hashing (PBH) orchestrator + // + + TableConnector cfgDbPbhTable(m_configDb, CFG_PBH_TABLE_TABLE_NAME); + TableConnector cfgDbPbhRuleTable(m_configDb, CFG_PBH_RULE_TABLE_NAME); + TableConnector cfgDbPbhHashTable(m_configDb, CFG_PBH_HASH_TABLE_NAME); + TableConnector cfgDbPbhHashFieldTable(m_configDb, CFG_PBH_HASH_FIELD_TABLE_NAME); + + vector pbhTableConnectorList = { + cfgDbPbhTable, + cfgDbPbhRuleTable, + cfgDbPbhHashTable, + cfgDbPbhHashFieldTable + }; + + gPbhOrch = new PbhOrch(pbhTableConnectorList, gAclOrch, gPortsOrch); + m_orchList.push_back(gFdbOrch); m_orchList.push_back(gMirrorOrch); m_orchList.push_back(gAclOrch); + m_orchList.push_back(gPbhOrch); m_orchList.push_back(chassis_frontend_orch); m_orchList.push_back(vrf_orch); m_orchList.push_back(vxlan_tunnel_orch); diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index bdcc053ed3bb..460d9052a558 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -18,6 +18,7 @@ #include "mirrororch.h" #include "fdborch.h" #include "aclorch.h" +#include "pbhorch.h" #include "pfcwdorch.h" #include "switchorch.h" #include "crmorch.h" diff --git a/orchagent/pbh/pbhcnt.cpp b/orchagent/pbh/pbhcnt.cpp new file mode 100644 index 000000000000..be8ca0ebf1e0 --- /dev/null +++ b/orchagent/pbh/pbhcnt.cpp @@ -0,0 +1,97 @@ +// includes ----------------------------------------------------------------------------------------------------------- + +#include + +#include "pbhcnt.h" + +using namespace swss; + +// PBH container ------------------------------------------------------------------------------------------------------ + +PbhContainer::PbhContainer(const std::string &key, const std::string &op) noexcept +{ + this->key = key; + this->op = op; +} + +std::uint64_t PbhContainer::getRefCount() const noexcept +{ + return this->refCount; +} + +void PbhContainer::incrementRefCount() noexcept +{ + static const std::uint64_t max = std::numeric_limits::max(); + + if (this->refCount < max) + { + this->refCount++; + } +} + +void PbhContainer::decrementRefCount() noexcept +{ + static const std::uint64_t min = std::numeric_limits::min(); + + if (this->refCount > min) + { + this->refCount--; + } +} + +void PbhContainer::clearRefCount() noexcept +{ + this->refCount = 0; +} + +// PBH table ---------------------------------------------------------------------------------------------------------- + +PbhTable::PbhTable(const std::string &key, const std::string &op) noexcept : + PbhContainer(key, op) +{ + +} + +// PBH rule ----------------------------------------------------------------------------------------------------------- + +PbhRule::PbhRule(const std::string &key, const std::string &op) noexcept : + PbhContainer(key, op) +{ + +} + +// PBH hash ----------------------------------------------------------------------------------------------------------- + +PbhHash::PbhHash(const std::string &key, const std::string &op) noexcept : + PbhContainer(key, op) +{ + +} + +sai_object_id_t PbhHash::getOid() const noexcept +{ + return this->oid; +} + +void PbhHash::setOid(sai_object_id_t oid) noexcept +{ + this->oid = oid; +} + +// PBH hash field ----------------------------------------------------------------------------------------------------- + +PbhHashField::PbhHashField(const std::string &key, const std::string &op) noexcept : + PbhContainer(key, op) +{ + +} + +sai_object_id_t PbhHashField::getOid() const noexcept +{ + return this->oid; +} + +void PbhHashField::setOid(sai_object_id_t oid) noexcept +{ + this->oid = oid; +} diff --git a/orchagent/pbh/pbhcnt.h b/orchagent/pbh/pbhcnt.h new file mode 100644 index 000000000000..787d91b63c7d --- /dev/null +++ b/orchagent/pbh/pbhcnt.h @@ -0,0 +1,177 @@ +#pragma once + +extern "C" { +#include "saiacl.h" +#include "saihash.h" +} + +#include + +#include +#include +#include + +#include "ipaddress.h" + +class PbhContainer +{ +public: + PbhContainer() = default; + virtual ~PbhContainer() = default; + + PbhContainer(const std::string &key, const std::string &op) noexcept; + + std::uint64_t getRefCount() const noexcept; + void incrementRefCount() noexcept; + void decrementRefCount() noexcept; + void clearRefCount() noexcept; + + std::string key; + std::string op; + std::unordered_map fieldValueMap; + +protected: + std::uint64_t refCount = 0; +}; + +class PbhTable final : public PbhContainer +{ +public: + PbhTable() = default; + ~PbhTable() = default; + + PbhTable(const std::string &key, const std::string &op) noexcept; + + struct { + std::unordered_set value; + bool is_set = false; + } interface_list; + + struct { + std::string value; + bool is_set = false; + } description; + + std::string name; +}; + +class PbhRule final : public PbhContainer +{ +public: + PbhRule() = default; + ~PbhRule() = default; + + PbhRule(const std::string &key, const std::string &op) noexcept; + + struct { + sai_uint32_t value; + bool is_set = false; + } priority; + + struct { + sai_uint32_t value; + sai_uint32_t mask; + bool is_set = false; + } gre_key; + + struct { + sai_uint16_t value; + sai_uint16_t mask; + bool is_set = false; + } ether_type; + + struct { + sai_uint8_t value; + sai_uint8_t mask; + bool is_set = false; + } ip_protocol; + + struct { + sai_uint8_t value; + sai_uint8_t mask; + bool is_set = false; + } ipv6_next_header; + + struct { + sai_uint16_t value; + sai_uint16_t mask; + bool is_set = false; + } l4_dst_port; + + struct { + sai_uint16_t value; + sai_uint16_t mask; + bool is_set = false; + } inner_ether_type; + + struct { + std::string value; + bool is_set = false; + } hash; + + struct { + sai_acl_entry_attr_t value; + bool is_set = false; + } packet_action; + + struct { + struct { + std::string name; + } meta; + bool value; + bool is_set = false; + } flow_counter; + + std::string name; + std::string table; +}; + +class PbhHash final : public PbhContainer +{ +public: + PbhHash() = default; + ~PbhHash() = default; + + PbhHash(const std::string &key, const std::string &op) noexcept; + + sai_object_id_t getOid() const noexcept; + void setOid(sai_object_id_t oid) noexcept; + + struct { + std::unordered_set value; + bool is_set = false; + } hash_field_list; + +private: + sai_object_id_t oid = SAI_NULL_OBJECT_ID; +}; + +class PbhHashField final : public PbhContainer +{ +public: + PbhHashField() = default; + ~PbhHashField() = default; + + PbhHashField(const std::string &key, const std::string &op) noexcept; + + sai_object_id_t getOid() const noexcept; + void setOid(sai_object_id_t oid) noexcept; + + struct { + sai_native_hash_field_t value; + bool is_set = false; + } hash_field; + + struct { + swss::IpAddress value; + bool is_set = false; + } ip_mask; + + struct { + sai_uint32_t value; + bool is_set = false; + } sequence_id; + +private: + sai_object_id_t oid = SAI_NULL_OBJECT_ID; +}; diff --git a/orchagent/pbh/pbhmgr.cpp b/orchagent/pbh/pbhmgr.cpp new file mode 100644 index 000000000000..ed10ff756cd6 --- /dev/null +++ b/orchagent/pbh/pbhmgr.cpp @@ -0,0 +1,1142 @@ +// includes ----------------------------------------------------------------------------------------------------------- + +#include + +#include +#include +#include +#include + +#include "ipaddress.h" +#include "converter.h" +#include "tokenize.h" +#include "logger.h" + +#include "pbhmgr.h" + +using namespace swss; + +// defines ------------------------------------------------------------------------------------------------------------ + +#define PBH_TABLE_INTERFACE_LIST "interface_list" +#define PBH_TABLE_DESCRIPTION "description" + +#define PBH_RULE_PACKET_ACTION_SET_ECMP_HASH "SET_ECMP_HASH" +#define PBH_RULE_PACKET_ACTION_SET_LAG_HASH "SET_LAG_HASH" + +#define PBH_RULE_FLOW_COUNTER_ENABLED "ENABLED" +#define PBH_RULE_FLOW_COUNTER_DISABLED "DISABLED" + +#define PBH_RULE_PRIORITY "priority" +#define PBH_RULE_GRE_KEY "gre_key" +#define PBH_RULE_ETHER_TYPE "ether_type" +#define PBH_RULE_IP_PROTOCOL "ip_protocol" +#define PBH_RULE_IPV6_NEXT_HEADER "ipv6_next_header" +#define PBH_RULE_L4_DST_PORT "l4_dst_port" +#define PBH_RULE_INNER_ETHER_TYPE "inner_ether_type" +#define PBH_RULE_HASH "hash" +#define PBH_RULE_PACKET_ACTION "packet_action" +#define PBH_RULE_FLOW_COUNTER "flow_counter" + +#define PBH_HASH_HASH_FIELD_LIST "hash_field_list" + +#define PBH_HASH_FIELD_HASH_FIELD_INNER_IP_PROTOCOL "INNER_IP_PROTOCOL" +#define PBH_HASH_FIELD_HASH_FIELD_INNER_L4_DST_PORT "INNER_L4_DST_PORT" +#define PBH_HASH_FIELD_HASH_FIELD_INNER_L4_SRC_PORT "INNER_L4_SRC_PORT" +#define PBH_HASH_FIELD_HASH_FIELD_INNER_DST_IPV4 "INNER_DST_IPV4" +#define PBH_HASH_FIELD_HASH_FIELD_INNER_SRC_IPV4 "INNER_SRC_IPV4" +#define PBH_HASH_FIELD_HASH_FIELD_INNER_DST_IPV6 "INNER_DST_IPV6" +#define PBH_HASH_FIELD_HASH_FIELD_INNER_SRC_IPV6 "INNER_SRC_IPV6" + +#define PBH_HASH_FIELD_HASH_FIELD "hash_field" +#define PBH_HASH_FIELD_IP_MASK "ip_mask" +#define PBH_HASH_FIELD_SEQUENCE_ID "sequence_id" + +// constants ---------------------------------------------------------------------------------------------------------- + +static const std::unordered_map pbhRulePacketActionMap = +{ + { PBH_RULE_PACKET_ACTION_SET_ECMP_HASH, SAI_ACL_ENTRY_ATTR_ACTION_SET_ECMP_HASH_ID }, + { PBH_RULE_PACKET_ACTION_SET_LAG_HASH, SAI_ACL_ENTRY_ATTR_ACTION_SET_LAG_HASH_ID } +}; + +static const std::unordered_map pbhRuleFlowCounterMap = +{ + { PBH_RULE_FLOW_COUNTER_ENABLED, true }, + { PBH_RULE_FLOW_COUNTER_DISABLED, false } +}; + +static const std::unordered_map pbhHashFieldHashFieldMap = +{ + { PBH_HASH_FIELD_HASH_FIELD_INNER_IP_PROTOCOL, SAI_NATIVE_HASH_FIELD_INNER_IP_PROTOCOL }, + { PBH_HASH_FIELD_HASH_FIELD_INNER_L4_DST_PORT, SAI_NATIVE_HASH_FIELD_INNER_L4_DST_PORT }, + { PBH_HASH_FIELD_HASH_FIELD_INNER_L4_SRC_PORT, SAI_NATIVE_HASH_FIELD_INNER_L4_SRC_PORT }, + { PBH_HASH_FIELD_HASH_FIELD_INNER_DST_IPV4, SAI_NATIVE_HASH_FIELD_INNER_DST_IPV4 }, + { PBH_HASH_FIELD_HASH_FIELD_INNER_SRC_IPV4, SAI_NATIVE_HASH_FIELD_INNER_SRC_IPV4 }, + { PBH_HASH_FIELD_HASH_FIELD_INNER_DST_IPV6, SAI_NATIVE_HASH_FIELD_INNER_DST_IPV6 }, + { PBH_HASH_FIELD_HASH_FIELD_INNER_SRC_IPV6, SAI_NATIVE_HASH_FIELD_INNER_SRC_IPV6 } +}; + +// functions ---------------------------------------------------------------------------------------------------------- + +template +static inline T toUInt(const std::string &hexStr) +{ + if (hexStr.substr(0, 2) != "0x") + { + throw std::invalid_argument("Invalid argument: '" + hexStr + "'"); + } + + return to_uint(hexStr); +} + +static inline std::uint8_t toUInt8(const std::string &hexStr) +{ + return toUInt(hexStr); +} + +static inline std::uint16_t toUInt16(const std::string &hexStr) +{ + return toUInt(hexStr); +} + +static inline std::uint32_t toUInt32(const std::string &hexStr) +{ + return toUInt(hexStr); +} + +// PBH helper --------------------------------------------------------------------------------------------------------- + +bool PbhHelper::hasDependencies(const PbhContainer &obj) const +{ + return obj.getRefCount() > 0; +} + +template<> +bool PbhHelper::validateDependencies(const PbhRule &obj) const +{ + const auto &tCit = this->tableMap.find(obj.table); + if (tCit == this->tableMap.cend()) + { + return false; + } + + const auto &hCit = this->hashMap.find(obj.hash.value); + if (hCit == this->hashMap.cend()) + { + return false; + } + + return true; +} + +template<> +bool PbhHelper::validateDependencies(const PbhHash &obj) const +{ + for (const auto &cit : obj.hash_field_list.value) + { + const auto &hfCit = this->hashFieldMap.find(cit); + if (hfCit == this->hashFieldMap.cend()) + { + return false; + } + } + + return true; +} + +template<> +bool PbhHelper::incRefCount(const PbhRule &obj) +{ + const auto &tCit = this->tableMap.find(obj.table); + if (tCit == this->tableMap.cend()) + { + return false; + } + + const auto &hCit = this->hashMap.find(obj.hash.value); + if (hCit == this->hashMap.cend()) + { + return false; + } + + auto &table = tCit->second; + table.incrementRefCount(); + + auto &hash = hCit->second; + hash.incrementRefCount(); + + return true; +} + +template<> +bool PbhHelper::incRefCount(const PbhHash &obj) +{ + std::vector::iterator> itList; + + for (const auto &cit : obj.hash_field_list.value) + { + const auto &hfCit = this->hashFieldMap.find(cit); + if (hfCit == this->hashFieldMap.cend()) + { + return false; + } + + itList.push_back(hfCit); + } + + for (auto &it : itList) + { + auto &hashField = it->second; + hashField.incrementRefCount(); + } + + return true; +} + +template<> +bool PbhHelper::decRefCount(const PbhRule &obj) +{ + const auto &tCit = this->tableMap.find(obj.table); + if (tCit == this->tableMap.cend()) + { + return false; + } + + const auto &hCit = this->hashMap.find(obj.hash.value); + if (hCit == this->hashMap.cend()) + { + return false; + } + + auto &table = tCit->second; + table.decrementRefCount(); + + auto &hash = hCit->second; + hash.decrementRefCount(); + + return true; +} + +template<> +bool PbhHelper::decRefCount(const PbhHash &obj) +{ + std::vector::iterator> itList; + + for (const auto &cit : obj.hash_field_list.value) + { + const auto &hfCit = this->hashFieldMap.find(cit); + if (hfCit == this->hashFieldMap.cend()) + { + return false; + } + + itList.push_back(hfCit); + } + + for (auto &it : itList) + { + auto &hashField = it->second; + hashField.decrementRefCount(); + } + + return true; +} + +template<> +auto PbhHelper::getPbhObjMap() const -> const std::unordered_map& +{ + return this->tableMap; +} + +template<> +auto PbhHelper::getPbhObjMap() const -> const std::unordered_map& +{ + return this->ruleMap; +} + +template<> +auto PbhHelper::getPbhObjMap() const -> const std::unordered_map& +{ + return this->hashMap; +} + +template<> +auto PbhHelper::getPbhObjMap() const -> const std::unordered_map& +{ + return this->hashFieldMap; +} + +template +bool PbhHelper::getPbhObj(T &obj, const std::string &key) const +{ + const auto &objMap = this->getPbhObjMap(); + + const auto &cit = objMap.find(key); + if (cit == objMap.cend()) + { + return false; + } + + obj = cit->second; + + return true; +} + +template bool PbhHelper::getPbhObj(PbhTable &obj, const std::string &key) const; +template bool PbhHelper::getPbhObj(PbhRule &obj, const std::string &key) const; +template bool PbhHelper::getPbhObj(PbhHash &obj, const std::string &key) const; +template bool PbhHelper::getPbhObj(PbhHashField &obj, const std::string &key) const; + +bool PbhHelper::getPbhTable(PbhTable &table, const std::string &key) const +{ + return this->getPbhObj(table, key); +} + +bool PbhHelper::getPbhRule(PbhRule &rule, const std::string &key) const +{ + return this->getPbhObj(rule, key); +} + +bool PbhHelper::getPbhHash(PbhHash &hash, const std::string &key) const +{ + return this->getPbhObj(hash, key); +} + +bool PbhHelper::getPbhHashField(PbhHashField &hashField, const std::string &key) const +{ + return this->getPbhObj(hashField, key); +} + +template<> +auto PbhHelper::getPbhObjMap() -> std::unordered_map& +{ + return this->tableMap; +} + +template<> +auto PbhHelper::getPbhObjMap() -> std::unordered_map& +{ + return this->ruleMap; +} + +template<> +auto PbhHelper::getPbhObjMap() -> std::unordered_map& +{ + return this->hashMap; +} + +template<> +auto PbhHelper::getPbhObjMap() -> std::unordered_map& +{ + return this->hashFieldMap; +} + +template +bool PbhHelper::addPbhObj(const T &obj) +{ + auto &objMap = this->getPbhObjMap(); + + const auto &cit = objMap.find(obj.key); + if (cit != objMap.cend()) + { + return false; + } + + objMap[obj.key] = obj; + + return true; +} + +template bool PbhHelper::addPbhObj(const PbhTable &obj); +template bool PbhHelper::addPbhObj(const PbhRule &obj); +template bool PbhHelper::addPbhObj(const PbhHash &obj); +template bool PbhHelper::addPbhObj(const PbhHashField &obj); + +bool PbhHelper::addPbhTable(const PbhTable &table) +{ + return this->addPbhObj(table); +} + +bool PbhHelper::addPbhRule(const PbhRule &rule) +{ + return this->addPbhObj(rule); +} + +bool PbhHelper::addPbhHash(const PbhHash &hash) +{ + return this->addPbhObj(hash); +} + +bool PbhHelper::addPbhHashField(const PbhHashField &hashField) +{ + return this->addPbhObj(hashField); +} + +template +bool PbhHelper::updatePbhObj(const T &obj) +{ + auto &objMap = this->getPbhObjMap(); + + const auto &cit = objMap.find(obj.key); + if (cit == objMap.cend()) + { + return false; + } + + objMap[obj.key] = obj; + + return true; +} + +template bool PbhHelper::updatePbhObj(const PbhTable &obj); +template bool PbhHelper::updatePbhObj(const PbhRule &obj); +template bool PbhHelper::updatePbhObj(const PbhHash &obj); +template bool PbhHelper::updatePbhObj(const PbhHashField &obj); + +bool PbhHelper::updatePbhTable(const PbhTable &table) +{ + return this->updatePbhObj(table); +} + +bool PbhHelper::updatePbhRule(const PbhRule &rule) +{ + return this->updatePbhObj(rule); +} + +bool PbhHelper::updatePbhHash(const PbhHash &hash) +{ + return this->updatePbhObj(hash); +} + +bool PbhHelper::updatePbhHashField(const PbhHashField &hashField) +{ + return this->updatePbhObj(hashField); +} + +template +bool PbhHelper::removePbhObj(const std::string &key) +{ + auto &objMap = this->getPbhObjMap(); + + const auto &cit = objMap.find(key); + if (cit == objMap.cend()) + { + return false; + } + + objMap.erase(cit); + + return true; +} + +template bool PbhHelper::removePbhObj(const std::string &key); +template bool PbhHelper::removePbhObj(const std::string &key); +template bool PbhHelper::removePbhObj(const std::string &key); +template bool PbhHelper::removePbhObj(const std::string &key); + +bool PbhHelper::removePbhTable(const std::string &key) +{ + return this->removePbhObj(key); +} + +bool PbhHelper::removePbhRule(const std::string &key) +{ + return this->removePbhObj(key); +} + +bool PbhHelper::removePbhHash(const std::string &key) +{ + return this->removePbhObj(key); +} + +bool PbhHelper::removePbhHashField(const std::string &key) +{ + return this->removePbhObj(key); +} + +bool PbhHelper::parsePbhTableInterfaceList(PbhTable &table, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + const auto &ifList = tokenize(value, ','); + + if (ifList.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty list is prohibited", field.c_str()); + return false; + } + + table.interface_list.value = std::unordered_set(ifList.cbegin(), ifList.cend()); + table.interface_list.is_set = true; + + if (table.interface_list.value.size() != ifList.size()) + { + SWSS_LOG_WARN("Duplicate interfaces in field(%s): unexpected value(%s)", field.c_str(), value.c_str()); + } + + return true; +} + +bool PbhHelper::parsePbhTableDescription(PbhTable &table, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty string is prohibited", field.c_str()); + return false; + } + + table.description.value = value; + table.description.is_set = true; + + return true; +} + +bool PbhHelper::parsePbhTable(PbhTable &table) const +{ + SWSS_LOG_ENTER(); + + for (const auto &cit : table.fieldValueMap) + { + const auto &field = cit.first; + const auto &value = cit.second; + + if (field == PBH_TABLE_INTERFACE_LIST) + { + if (!this->parsePbhTableInterfaceList(table, field, value)) + { + return false; + } + } + else if (field == PBH_TABLE_DESCRIPTION) + { + if (!this->parsePbhTableDescription(table, field, value)) + { + return false; + } + } + else + { + SWSS_LOG_WARN("Unknown field(%s): skipping ...", field.c_str()); + } + } + + return this->validatePbhTable(table); +} + +bool PbhHelper::parsePbhRulePriority(PbhRule &rule, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty value is prohibited", field.c_str()); + return false; + } + + try + { + rule.priority.value = to_uint(value); + rule.priority.is_set = true; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to parse field(%s): %s", field.c_str(), e.what()); + return false; + } + + return true; +} + +bool PbhHelper::parsePbhRuleGreKey(PbhRule &rule, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + const auto &vmList = tokenize(value, '/'); + + if (vmList.size() != 2) + { + SWSS_LOG_ERROR("Failed to parse field(%s): invalid value(%s)", field.c_str(), value.c_str()); + return false; + } + + try + { + rule.gre_key.value = toUInt32(vmList.at(0)); + rule.gre_key.mask = toUInt32(vmList.at(1)); + rule.gre_key.is_set = true; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to parse field(%s): %s", field.c_str(), e.what()); + return false; + } + + return true; +} + +bool PbhHelper::parsePbhRuleEtherType(PbhRule &rule, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty value is prohibited", field.c_str()); + return false; + } + + try + { + rule.ether_type.value = toUInt16(value); + rule.ether_type.mask = 0xFFFF; + rule.ether_type.is_set = true; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to parse field(%s): %s", field.c_str(), e.what()); + return false; + } + + return true; +} + +bool PbhHelper::parsePbhRuleIpProtocol(PbhRule &rule, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty value is prohibited", field.c_str()); + return false; + } + + try + { + rule.ip_protocol.value = toUInt8(value); + rule.ip_protocol.mask = 0xFF; + rule.ip_protocol.is_set = true; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to parse field(%s): %s", field.c_str(), e.what()); + return false; + } + + return true; +} + +bool PbhHelper::parsePbhRuleIpv6NextHeader(PbhRule &rule, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty value is prohibited", field.c_str()); + return false; + } + + try + { + rule.ipv6_next_header.value = toUInt8(value); + rule.ipv6_next_header.mask = 0xFF; + rule.ipv6_next_header.is_set = true; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to parse field(%s): %s", field.c_str(), e.what()); + return false; + } + + return true; +} + +bool PbhHelper::parsePbhRuleL4DstPort(PbhRule &rule, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty value is prohibited", field.c_str()); + return false; + } + + try + { + rule.l4_dst_port.value = toUInt16(value); + rule.l4_dst_port.mask = 0xFFFF; + rule.l4_dst_port.is_set = true; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to parse field(%s): %s", field.c_str(), e.what()); + return false; + } + + return true; +} + +bool PbhHelper::parsePbhRuleInnerEtherType(PbhRule &rule, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty value is prohibited", field.c_str()); + return false; + } + + try + { + rule.inner_ether_type.value = toUInt16(value); + rule.inner_ether_type.mask = 0xFFFF; + rule.inner_ether_type.is_set = true; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to parse field(%s): %s", field.c_str(), e.what()); + return false; + } + + return true; +} + +bool PbhHelper::parsePbhRuleHash(PbhRule &rule, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty value is prohibited", field.c_str()); + return false; + } + + rule.hash.value = value; + rule.hash.is_set = true; + + return true; +} + +bool PbhHelper::parsePbhRulePacketAction(PbhRule &rule, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + const auto &cit = pbhRulePacketActionMap.find(value); + if (cit == pbhRulePacketActionMap.cend()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): invalid value(%s)", field.c_str(), value.c_str()); + return false; + } + + rule.packet_action.value = cit->second; + rule.packet_action.is_set = true; + + return true; +} + +bool PbhHelper::parsePbhRuleFlowCounter(PbhRule &rule, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + const auto &cit = pbhRuleFlowCounterMap.find(value); + if (cit == pbhRuleFlowCounterMap.cend()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): invalid value(%s)", field.c_str(), value.c_str()); + return false; + } + + rule.flow_counter.meta.name = field; + rule.flow_counter.value = cit->second; + rule.flow_counter.is_set = true; + + return true; +} + +bool PbhHelper::parsePbhRule(PbhRule &rule) const +{ + SWSS_LOG_ENTER(); + + for (const auto &cit : rule.fieldValueMap) + { + const auto &field = cit.first; + const auto &value = cit.second; + + if (field == PBH_RULE_PRIORITY) + { + if (!this->parsePbhRulePriority(rule, field, value)) + { + return false; + } + } + else if (field == PBH_RULE_GRE_KEY) + { + if (!this->parsePbhRuleGreKey(rule, field, value)) + { + return false; + } + } + else if (field == PBH_RULE_ETHER_TYPE) + { + if (!this->parsePbhRuleEtherType(rule, field, value)) + { + return false; + } + } + else if (field == PBH_RULE_IP_PROTOCOL) + { + if (!this->parsePbhRuleIpProtocol(rule, field, value)) + { + return false; + } + } + else if (field == PBH_RULE_IPV6_NEXT_HEADER) + { + if (!this->parsePbhRuleIpv6NextHeader(rule, field, value)) + { + return false; + } + } + else if (field == PBH_RULE_L4_DST_PORT) + { + if (!this->parsePbhRuleL4DstPort(rule, field, value)) + { + return false; + } + } + else if (field == PBH_RULE_INNER_ETHER_TYPE) + { + if (!this->parsePbhRuleInnerEtherType(rule, field, value)) + { + return false; + } + } + else if (field == PBH_RULE_HASH) + { + if (!this->parsePbhRuleHash(rule, field, value)) + { + return false; + } + } + else if (field == PBH_RULE_PACKET_ACTION) + { + if (!this->parsePbhRulePacketAction(rule, field, value)) + { + return false; + } + } + else if (field == PBH_RULE_FLOW_COUNTER) + { + if (!this->parsePbhRuleFlowCounter(rule, field, value)) + { + return false; + } + } + else + { + SWSS_LOG_WARN("Unknown field(%s): skipping ...", field.c_str()); + } + } + + return this->validatePbhRule(rule); +} + +bool PbhHelper::parsePbhHashHashFieldList(PbhHash &hash, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + const auto &hfList = tokenize(value, ','); + + if (hfList.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty list is prohibited", field.c_str()); + return false; + } + + hash.hash_field_list.value = std::unordered_set(hfList.cbegin(), hfList.cend()); + hash.hash_field_list.is_set = true; + + if (hash.hash_field_list.value.size() != hfList.size()) + { + SWSS_LOG_WARN("Duplicate hash fields in field(%s): unexpected value(%s)", field.c_str(), value.c_str()); + } + + return true; +} + +bool PbhHelper::parsePbhHash(PbhHash &hash) const +{ + SWSS_LOG_ENTER(); + + for (const auto &cit : hash.fieldValueMap) + { + const auto &field = cit.first; + const auto &value = cit.second; + + if (field == PBH_HASH_HASH_FIELD_LIST) + { + if (!this->parsePbhHashHashFieldList(hash, field, value)) + { + return false; + } + } + else + { + SWSS_LOG_WARN("Unknown field(%s): skipping ...", field.c_str()); + } + } + + return this->validatePbhHash(hash); +} + +bool PbhHelper::parsePbhHashFieldHashField(PbhHashField &hashField, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + const auto &cit = pbhHashFieldHashFieldMap.find(value); + if (cit == pbhHashFieldHashFieldMap.cend()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): invalid value(%s)", field.c_str(), value.c_str()); + return false; + } + + hashField.hash_field.value = cit->second; + hashField.hash_field.is_set = true; + + return true; +} + +bool PbhHelper::parsePbhHashFieldIpMask(PbhHashField &hashField, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty value is prohibited", field.c_str()); + return false; + } + + try + { + hashField.ip_mask.value = IpAddress(value); + hashField.ip_mask.is_set = true; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to parse field(%s): %s", field.c_str(), e.what()); + return false; + } + + return true; +} + +bool PbhHelper::parsePbhHashFieldSequenceId(PbhHashField &hashField, const std::string &field, const std::string &value) const +{ + SWSS_LOG_ENTER(); + + if (value.empty()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): empty value is prohibited", field.c_str()); + return false; + } + + try + { + hashField.sequence_id.value = to_uint(value); + hashField.sequence_id.is_set = true; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Failed to parse field(%s): %s", field.c_str(), e.what()); + return false; + } + + return true; +} + +bool PbhHelper::parsePbhHashField(PbhHashField &hashField) const +{ + SWSS_LOG_ENTER(); + + for (const auto &cit : hashField.fieldValueMap) + { + const auto &field = cit.first; + const auto &value = cit.second; + + if (field == PBH_HASH_FIELD_HASH_FIELD) + { + if (!this->parsePbhHashFieldHashField(hashField, field, value)) + { + return false; + } + } + else if (field == PBH_HASH_FIELD_IP_MASK) + { + if (!this->parsePbhHashFieldIpMask(hashField, field, value)) + { + return false; + } + } + else if (field == PBH_HASH_FIELD_SEQUENCE_ID) + { + if (!this->parsePbhHashFieldSequenceId(hashField, field, value)) + { + return false; + } + } + else + { + SWSS_LOG_WARN("Unknown field(%s): skipping ...", field.c_str()); + } + } + + return this->validatePbhHashField(hashField); +} + +bool PbhHelper::validatePbhTable(PbhTable &table) const +{ + SWSS_LOG_ENTER(); + + if (!table.interface_list.is_set) + { + SWSS_LOG_ERROR("Validation error: missing mandatory field(%s)", PBH_TABLE_INTERFACE_LIST); + return false; + } + + if (!table.description.is_set) + { + SWSS_LOG_ERROR("Validation error: missing mandatory field(%s)", PBH_TABLE_DESCRIPTION); + return false; + } + + return true; +} + +bool PbhHelper::validatePbhRule(PbhRule &rule) const +{ + SWSS_LOG_ENTER(); + + if (!rule.priority.is_set) + { + SWSS_LOG_ERROR("Validation error: missing mandatory field(%s)", PBH_RULE_PRIORITY); + return false; + } + + if (!rule.hash.is_set) + { + SWSS_LOG_ERROR("Validation error: missing mandatory field(%s)", PBH_RULE_HASH); + return false; + } + + if (!rule.packet_action.is_set) + { + SWSS_LOG_NOTICE( + "Missing non mandatory field(%s): setting default value(%s)", + PBH_RULE_PACKET_ACTION, + PBH_RULE_PACKET_ACTION_SET_ECMP_HASH + ); + + rule.packet_action.value = SAI_ACL_ENTRY_ATTR_ACTION_SET_ECMP_HASH_ID; + rule.packet_action.is_set = true; + + rule.fieldValueMap[PBH_RULE_PACKET_ACTION] = PBH_RULE_PACKET_ACTION_SET_ECMP_HASH; + } + + if (!rule.flow_counter.is_set) + { + SWSS_LOG_NOTICE( + "Missing non mandatory field(%s): setting default value(%s)", + PBH_RULE_FLOW_COUNTER, + PBH_RULE_FLOW_COUNTER_DISABLED + ); + rule.flow_counter.meta.name = PBH_RULE_FLOW_COUNTER; + rule.flow_counter.value = false; + rule.flow_counter.is_set = true; + + rule.fieldValueMap[PBH_RULE_FLOW_COUNTER] = PBH_RULE_FLOW_COUNTER_DISABLED; + } + + return true; +} + +bool PbhHelper::validatePbhHash(PbhHash &hash) const +{ + SWSS_LOG_ENTER(); + + if (!hash.hash_field_list.is_set) + { + SWSS_LOG_ERROR("Validation error: missing mandatory field(%s)", PBH_HASH_HASH_FIELD_LIST); + return false; + } + + return true; +} + +bool PbhHelper::validatePbhHashField(PbhHashField &hashField) const +{ + SWSS_LOG_ENTER(); + + if (!hashField.hash_field.is_set) + { + SWSS_LOG_ERROR("Validation error: missing mandatory field(%s)", PBH_HASH_FIELD_HASH_FIELD); + return false; + } + + if (hashField.ip_mask.is_set) + { + if (hashField.ip_mask.value.isV4()) + { + if (!this->isIpv4MaskRequired(hashField.hash_field.value)) + { + SWSS_LOG_ERROR("Validation error: field(%s) is prohibited", PBH_HASH_FIELD_IP_MASK); + return false; + } + } + else + { + if (!this->isIpv6MaskRequired(hashField.hash_field.value)) + { + SWSS_LOG_ERROR("Validation error: field(%s) is prohibited", PBH_HASH_FIELD_IP_MASK); + return false; + } + } + } + + if (!hashField.sequence_id.is_set) + { + SWSS_LOG_ERROR("Validation error: missing mandatory field(%s)", PBH_HASH_FIELD_SEQUENCE_ID); + return false; + } + + return true; +} + +bool PbhHelper::isIpv4MaskRequired(const sai_native_hash_field_t &value) const +{ + switch (value) + { + case SAI_NATIVE_HASH_FIELD_INNER_DST_IPV4: + case SAI_NATIVE_HASH_FIELD_INNER_SRC_IPV4: + return true; + + default: + break; + } + + return false; +} + +bool PbhHelper::isIpv6MaskRequired(const sai_native_hash_field_t &value) const +{ + switch (value) + { + case SAI_NATIVE_HASH_FIELD_INNER_DST_IPV6: + case SAI_NATIVE_HASH_FIELD_INNER_SRC_IPV6: + return true; + + default: + break; + } + + return false; +} diff --git a/orchagent/pbh/pbhmgr.h b/orchagent/pbh/pbhmgr.h new file mode 100644 index 000000000000..3f3fc7873cbb --- /dev/null +++ b/orchagent/pbh/pbhmgr.h @@ -0,0 +1,116 @@ +#pragma once + +#include "pbhcnt.h" + +class PbhHelper final +{ +public: + PbhHelper() = default; + ~PbhHelper() = default; + + bool hasDependencies(const PbhContainer &obj) const; + template + bool validateDependencies(const T &obj) const; + + template + bool incRefCount(const T &obj); + template + bool decRefCount(const T &obj); + + bool getPbhTable(PbhTable &table, const std::string &key) const; + bool getPbhRule(PbhRule &rule, const std::string &key) const; + bool getPbhHash(PbhHash &hash, const std::string &key) const; + bool getPbhHashField(PbhHashField &hashField, const std::string &key) const; + + bool addPbhTable(const PbhTable &table); + bool updatePbhTable(const PbhTable &table); + bool removePbhTable(const std::string &key); + + bool addPbhRule(const PbhRule &rule); + bool updatePbhRule(const PbhRule &rule); + bool removePbhRule(const std::string &key); + + bool addPbhHash(const PbhHash &hash); + bool updatePbhHash(const PbhHash &hash); + bool removePbhHash(const std::string &key); + + bool addPbhHashField(const PbhHashField &hashField); + bool updatePbhHashField(const PbhHashField &hashField); + bool removePbhHashField(const std::string &key); + + bool parsePbhTable(PbhTable &table) const; + bool parsePbhRule(PbhRule &rule) const; + bool parsePbhHash(PbhHash &hash) const; + bool parsePbhHashField(PbhHashField &hashField) const; + +private: + template + auto getPbhObjMap() const -> const std::unordered_map&; + template + auto getPbhObjMap() -> std::unordered_map&; + + template + bool getPbhObj(T &obj, const std::string &key) const; + + template + bool addPbhObj(const T &obj); + template + bool updatePbhObj(const T &obj); + template + bool removePbhObj(const std::string &key); + + bool parsePbhTableInterfaceList(PbhTable &table, const std::string &field, const std::string &value) const; + bool parsePbhTableDescription(PbhTable &table, const std::string &field, const std::string &value) const; + + bool parsePbhRulePriority(PbhRule &rule, const std::string &field, const std::string &value) const; + bool parsePbhRuleGreKey(PbhRule &rule, const std::string &field, const std::string &value) const; + bool parsePbhRuleEtherType(PbhRule &rule, const std::string &field, const std::string &value) const; + bool parsePbhRuleIpProtocol(PbhRule &rule, const std::string &field, const std::string &value) const; + bool parsePbhRuleIpv6NextHeader(PbhRule &rule, const std::string &field, const std::string &value) const; + bool parsePbhRuleL4DstPort(PbhRule &rule, const std::string &field, const std::string &value) const; + bool parsePbhRuleInnerEtherType(PbhRule &rule, const std::string &field, const std::string &value) const; + bool parsePbhRuleHash(PbhRule &rule, const std::string &field, const std::string &value) const; + bool parsePbhRulePacketAction(PbhRule &rule, const std::string &field, const std::string &value) const; + bool parsePbhRuleFlowCounter(PbhRule &rule, const std::string &field, const std::string &value) const; + + bool parsePbhHashHashFieldList(PbhHash &hash, const std::string &field, const std::string &value) const; + + bool parsePbhHashFieldHashField(PbhHashField &hashField, const std::string &field, const std::string &value) const; + bool parsePbhHashFieldIpMask(PbhHashField &hashField, const std::string &field, const std::string &value) const; + bool parsePbhHashFieldSequenceId(PbhHashField &hashField, const std::string &field, const std::string &value) const; + + bool validatePbhTable(PbhTable &table) const; + bool validatePbhRule(PbhRule &rule) const; + bool validatePbhHash(PbhHash &hash) const; + bool validatePbhHashField(PbhHashField &hashField) const; + + bool isIpv4MaskRequired(const sai_native_hash_field_t &value) const; + bool isIpv6MaskRequired(const sai_native_hash_field_t &value) const; + +public: + struct { + std::unordered_map pendingSetupMap; + std::unordered_map pendingRemoveMap; + } tableTask; + + struct { + std::unordered_map pendingSetupMap; + std::unordered_map pendingRemoveMap; + } ruleTask; + + struct { + std::unordered_map pendingSetupMap; + std::unordered_map pendingRemoveMap; + } hashTask; + + struct { + std::unordered_map pendingSetupMap; + std::unordered_map pendingRemoveMap; + } hashFieldTask; + +private: + std::unordered_map tableMap; + std::unordered_map ruleMap; + std::unordered_map hashMap; + std::unordered_map hashFieldMap; +}; diff --git a/orchagent/pbh/pbhrule.cpp b/orchagent/pbh/pbhrule.cpp new file mode 100644 index 000000000000..52812e35b647 --- /dev/null +++ b/orchagent/pbh/pbhrule.cpp @@ -0,0 +1,113 @@ +#include "sai_serialize.h" + +#include "pbhrule.h" + +AclRulePbh::AclRulePbh(AclOrch *pAclOrch, string rule, string table, bool createCounter) : + AclRule(pAclOrch, rule, table, ACL_TABLE_PBH, createCounter) +{ +} + +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; +} + +bool AclRulePbh::validateAddMatch(const sai_attribute_t &attr) +{ + SWSS_LOG_ENTER(); + + bool validate = false; + + auto attrId = static_cast(attr.id); + auto attrName = sai_serialize_enum(attrId, &sai_metadata_enum_sai_acl_entry_attr_t); + + switch (attrId) + { + case SAI_ACL_ENTRY_ATTR_FIELD_GRE_KEY: + case SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE: + case SAI_ACL_ENTRY_ATTR_FIELD_IP_PROTOCOL: + case SAI_ACL_ENTRY_ATTR_FIELD_IPV6_NEXT_HEADER: + case SAI_ACL_ENTRY_ATTR_FIELD_L4_DST_PORT: + case SAI_ACL_ENTRY_ATTR_FIELD_INNER_ETHER_TYPE: + validate = true; + break; + + default: + break; + } + + if (!validate) + { + SWSS_LOG_ERROR("Failed to validate match field: invalid attribute %s", attrName.c_str()); + return false; + } + + m_matches[attrId] = attr.value; + + return true; +} + +bool AclRulePbh::validateAddAction(const sai_attribute_t &attr) +{ + SWSS_LOG_ENTER(); + + bool validate = false; + + auto attrId = static_cast(attr.id); + auto attrName = sai_serialize_enum(attrId, &sai_metadata_enum_sai_acl_entry_attr_t); + + switch (attrId) + { + case SAI_ACL_ENTRY_ATTR_ACTION_SET_ECMP_HASH_ID: + case SAI_ACL_ENTRY_ATTR_ACTION_SET_LAG_HASH_ID: + validate = true; + break; + + default: + break; + } + + if (!validate) + { + SWSS_LOG_ERROR("Failed to validate action field: invalid attribute %s", attrName.c_str()); + 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; +} + +bool AclRulePbh::validate() +{ + SWSS_LOG_ENTER(); + + if (m_matches.size() == 0 || m_actions.size() != 1) + { + SWSS_LOG_ERROR("Failed to validate rule: invalid parameters"); + return false; + } + + return true; +} + +void AclRulePbh::update(SubjectType, void *) +{ + // Do nothing +} diff --git a/orchagent/pbh/pbhrule.h b/orchagent/pbh/pbhrule.h new file mode 100644 index 000000000000..3e753a0f0390 --- /dev/null +++ b/orchagent/pbh/pbhrule.h @@ -0,0 +1,15 @@ +#pragma once + +#include "aclorch.h" + +class AclRulePbh: public AclRule +{ +public: + AclRulePbh(AclOrch *pAclOrch, string rule, string table, bool createCounter = false); + + bool validateAddPriority(const sai_uint32_t &value); + bool validateAddMatch(const sai_attribute_t &attr); + bool validateAddAction(const sai_attribute_t &attr); + bool validate() override; + void update(SubjectType, void *) override; +}; diff --git a/orchagent/pbhorch.cpp b/orchagent/pbhorch.cpp new file mode 100644 index 000000000000..3d0b43ce010e --- /dev/null +++ b/orchagent/pbhorch.cpp @@ -0,0 +1,1497 @@ +// includes ----------------------------------------------------------------------------------------------------------- + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "schema.h" +#include "tokenize.h" +#include "logger.h" + +#include "pbhorch.h" + +template +using umap_t = std::unordered_map; + +using namespace swss; + +// variables ---------------------------------------------------------------------------------------------------------- + +extern sai_hash_api_t *sai_hash_api; +extern sai_object_id_t gSwitchId; + +// functions ---------------------------------------------------------------------------------------------------------- + +template +static inline std::set uMapToKeySet(const umap_t &uMap) +{ + std::set s; + + std::transform( + uMap.cbegin(), + uMap.cend(), + std::inserter(s, s.begin()), + [](const std::pair &p) { + return p.first; + } + ); + + return s; +} + +template +static inline std::vector uMapDiffByKey(const umap_t &uMap1, const umap_t &uMap2) +{ + std::vector v; + + const auto &s1 = uMapToKeySet(uMap1); + const auto &s2 = uMapToKeySet(uMap2); + + std::set_symmetric_difference( + s1.cbegin(), + s1.cend(), + s2.cbegin(), + s2.cend(), + std::back_inserter(v) + ); + + return v; +} + +// PBH OA ------------------------------------------------------------------------------------------------------------- + +PbhOrch::PbhOrch( + std::vector &connectorList, + AclOrch *aclOrch, + PortsOrch *portsOrch +) : Orch(connectorList) +{ + this->aclOrch = aclOrch; + this->portsOrch = portsOrch; +} + +PbhOrch::~PbhOrch() +{ + +} + +template<> +auto PbhOrch::getPbhSetupTaskMap() const -> const std::unordered_map& +{ + return this->pbhHlpr.tableTask.pendingSetupMap; +} + +template<> +auto PbhOrch::getPbhSetupTaskMap() const -> const std::unordered_map& +{ + return this->pbhHlpr.ruleTask.pendingSetupMap; +} + +template<> +auto PbhOrch::getPbhSetupTaskMap() const -> const std::unordered_map& +{ + return this->pbhHlpr.hashTask.pendingSetupMap; +} + +template<> +auto PbhOrch::getPbhSetupTaskMap() const -> const std::unordered_map& +{ + return this->pbhHlpr.hashFieldTask.pendingSetupMap; +} + +template +bool PbhOrch::pbhSetupTaskExists(const T &obj) const +{ + const auto &taskMap = this->getPbhSetupTaskMap(); + return taskMap.find(obj.key) != taskMap.cend(); +} + +template bool PbhOrch::pbhSetupTaskExists(const PbhTable &obj) const; +template bool PbhOrch::pbhSetupTaskExists(const PbhRule &obj) const; +template bool PbhOrch::pbhSetupTaskExists(const PbhHash &obj) const; +template bool PbhOrch::pbhSetupTaskExists(const PbhHashField &obj) const; + +template<> +auto PbhOrch::getPbhRemoveTaskMap() const -> const std::unordered_map& +{ + return this->pbhHlpr.tableTask.pendingRemoveMap; +} + +template<> +auto PbhOrch::getPbhRemoveTaskMap() const -> const std::unordered_map& +{ + return this->pbhHlpr.ruleTask.pendingRemoveMap; +} + +template<> +auto PbhOrch::getPbhRemoveTaskMap() const -> const std::unordered_map& +{ + return this->pbhHlpr.hashTask.pendingRemoveMap; +} + +template<> +auto PbhOrch::getPbhRemoveTaskMap() const -> const std::unordered_map& +{ + return this->pbhHlpr.hashFieldTask.pendingRemoveMap; +} + +template +bool PbhOrch::pbhRemoveTaskExists(const T &obj) const +{ + const auto &taskMap = this->getPbhRemoveTaskMap(); + return taskMap.find(obj.key) != taskMap.cend(); +} + +template bool PbhOrch::pbhRemoveTaskExists(const PbhTable &obj) const; +template bool PbhOrch::pbhRemoveTaskExists(const PbhRule &obj) const; +template bool PbhOrch::pbhRemoveTaskExists(const PbhHash &obj) const; +template bool PbhOrch::pbhRemoveTaskExists(const PbhHashField &obj) const; + +template +bool PbhOrch::pbhTaskExists(const T &obj) const +{ + return this->pbhRemoveTaskExists(obj) || this->pbhSetupTaskExists(obj); +} + +template bool PbhOrch::pbhTaskExists(const PbhTable &obj) const; +template bool PbhOrch::pbhTaskExists(const PbhRule &obj) const; +template bool PbhOrch::pbhTaskExists(const PbhHash &obj) const; +template bool PbhOrch::pbhTaskExists(const PbhHashField &obj) const; + +// PBH table ---------------------------------------------------------------------------------------------------------- + +bool PbhOrch::createPbhTable(const PbhTable &table) +{ + SWSS_LOG_ENTER(); + + PbhTable tObj; + + if (this->pbhHlpr.getPbhTable(tObj, table.key)) + { + SWSS_LOG_ERROR("Failed to create PBH table(%s) in SAI: object already exists", table.key.c_str()); + return false; + } + + AclTable pbhTable(this->aclOrch, table.name); + + if (!pbhTable.validateAddType(acl_table_type_t::ACL_TABLE_PBH)) + { + SWSS_LOG_ERROR("Failed to configure PBH table(%s) type", table.key.c_str()); + return false; + } + + if (!pbhTable.validateAddStage(acl_stage_type_t::ACL_STAGE_INGRESS)) + { + SWSS_LOG_ERROR("Failed to configure PBH table(%s) stage", table.key.c_str()); + return false; + } + + if (table.interface_list.is_set) + { + if (!pbhTable.validateAddPorts(table.interface_list.value)) + { + SWSS_LOG_ERROR("Failed to configure PBH table(%s) ports", table.key.c_str()); + return false; + } + } + + if (table.description.is_set) + { + pbhTable.setDescription(table.description.value); + } + + if (!pbhTable.validate()) + { + SWSS_LOG_ERROR("Failed to validate PBH table(%s)", table.key.c_str()); + return false; + } + + if (!this->aclOrch->addAclTable(pbhTable)) + { + SWSS_LOG_ERROR("Failed to create PBH table(%s) in SAI", table.key.c_str()); + return false; + } + + if (!this->pbhHlpr.addPbhTable(table)) + { + SWSS_LOG_ERROR("Failed to add PBH table(%s) to internal cache", table.key.c_str()); + return false; + } + + SWSS_LOG_NOTICE("Created PBH table(%s) in SAI", table.key.c_str()); + + return true; +} + +bool PbhOrch::updatePbhTable(const PbhTable &table) +{ + SWSS_LOG_ENTER(); + + PbhTable tObj; + + if (!this->pbhHlpr.getPbhTable(tObj, table.key)) + { + SWSS_LOG_ERROR("Failed to update PBH table(%s) in SAI: object doesn't exist", table.key.c_str()); + return false; + } + + AclTable pbhTable(this->aclOrch, table.name); + + if (table.interface_list.is_set) + { + if (!pbhTable.validateAddPorts(table.interface_list.value)) + { + SWSS_LOG_ERROR("Failed to configure PBH table(%s) ports", table.key.c_str()); + return false; + } + } + + if (table.description.is_set) + { + pbhTable.setDescription(table.description.value); + } + + if (!this->aclOrch->updateAclTable(table.name, pbhTable)) + { + SWSS_LOG_ERROR("Failed to update PBH table(%s) in SAI", table.key.c_str()); + return false; + } + + if (!this->pbhHlpr.updatePbhTable(table)) + { + SWSS_LOG_ERROR("Failed to update PBH table(%s) in internal cache", table.key.c_str()); + return false; + } + + SWSS_LOG_NOTICE("Updated PBH table(%s) in SAI", table.key.c_str()); + + return true; +} + +bool PbhOrch::removePbhTable(const PbhTable &table) +{ + SWSS_LOG_ENTER(); + + PbhTable tObj; + + if (!this->pbhHlpr.getPbhTable(tObj, table.key)) + { + SWSS_LOG_ERROR("Failed to remove PBH table(%s) from SAI: object doesn't exist", table.key.c_str()); + return false; + } + + if (!this->aclOrch->removeAclTable(table.name)) + { + SWSS_LOG_ERROR("Failed to remove PBH table(%s) from SAI", table.key.c_str()); + return false; + } + + if (!this->pbhHlpr.removePbhTable(table.key)) + { + SWSS_LOG_ERROR("Failed to remove PBH table(%s) from internal cache", table.key.c_str()); + return false; + } + + SWSS_LOG_NOTICE("Removed PBH table(%s) from SAI", table.key.c_str()); + + return true; +} + +void PbhOrch::deployPbhTableSetupTasks() +{ + SWSS_LOG_ENTER(); + + auto &map = this->pbhHlpr.tableTask.pendingSetupMap; + auto it = map.begin(); + + while (it != map.end()) + { + auto &key = it->first; + auto &table = it->second; + + PbhTable tObj; + + if (!this->pbhHlpr.getPbhTable(tObj, key)) + { + if (!this->createPbhTable(table)) + { + SWSS_LOG_ERROR("Failed to create PBH table(%s): ASIC and CONFIG DB are diverged", key.c_str()); + } + } + else + { + if (!this->updatePbhTable(table)) + { + SWSS_LOG_ERROR("Failed to update PBH table(%s): ASIC and CONFIG DB are diverged", key.c_str()); + } + } + + it = map.erase(it); + } +} + +void PbhOrch::deployPbhTableRemoveTasks() +{ + SWSS_LOG_ENTER(); + + auto &map = this->pbhHlpr.tableTask.pendingRemoveMap; + auto it = map.begin(); + + while (it != map.end()) + { + auto &key = it->first; + auto &table = it->second; + + PbhTable tObj; + + if (!this->pbhHlpr.getPbhTable(tObj, key)) + { + SWSS_LOG_ERROR("Failed to remove PBH table(%s): object doesn't exist", key.c_str()); + it = map.erase(it); + continue; + } + + if (this->pbhHlpr.hasDependencies(tObj)) + { + SWSS_LOG_NOTICE("Unable to remove PBH table(%s): object has dependencies: adding a retry", key.c_str()); + it++; + continue; + } + + if (!this->removePbhTable(table)) + { + SWSS_LOG_ERROR("Failed to remove PBH table(%s): ASIC and CONFIG DB are diverged", key.c_str()); + it = map.erase(it); + continue; + } + + it = map.erase(it); + } +} + +// PBH rule ----------------------------------------------------------------------------------------------------------- + +bool PbhOrch::createPbhRule(const PbhRule &rule) +{ + SWSS_LOG_ENTER(); + + PbhRule rObj; + + if (this->pbhHlpr.getPbhRule(rObj, rule.key)) + { + SWSS_LOG_ERROR("Failed to create PBH rule(%s) in SAI: object already exists", rule.key.c_str()); + return false; + } + + std::shared_ptr pbhRule; + + if (rule.flow_counter.is_set) + { + pbhRule = std::make_shared(this->aclOrch, rule.name, rule.table, rule.flow_counter.value); + } + else + { + pbhRule = std::make_shared(this->aclOrch, rule.name, rule.table); + } + + if (rule.priority.is_set) + { + if (!pbhRule->validateAddPriority(rule.priority.value)) + { + SWSS_LOG_ERROR("Failed to configure PBH rule(%s) priority", rule.key.c_str()); + return false; + } + } + + if (rule.gre_key.is_set) + { + sai_attribute_t attr; + + attr.id = SAI_ACL_ENTRY_ATTR_FIELD_GRE_KEY; + attr.value.aclfield.enable = true; + attr.value.aclfield.data.u32 = rule.gre_key.value; + attr.value.aclfield.mask.u32 = rule.gre_key.mask; + + if (!pbhRule->validateAddMatch(attr)) + { + SWSS_LOG_ERROR("Failed to configure PBH rule(%s) match: GRE_KEY", rule.key.c_str()); + return false; + } + } + + if (rule.ether_type.is_set) + { + sai_attribute_t attr; + + attr.id = SAI_ACL_ENTRY_ATTR_FIELD_ETHER_TYPE; + attr.value.aclfield.enable = true; + attr.value.aclfield.data.u16 = rule.ether_type.value; + attr.value.aclfield.mask.u16 = rule.ether_type.mask; + + if (!pbhRule->validateAddMatch(attr)) + { + SWSS_LOG_ERROR("Failed to configure PBH rule(%s) match: ETHER_TYPE", rule.key.c_str()); + return false; + } + } + + if (rule.ip_protocol.is_set) + { + sai_attribute_t attr; + + attr.id = SAI_ACL_ENTRY_ATTR_FIELD_IP_PROTOCOL; + attr.value.aclfield.enable = true; + attr.value.aclfield.data.u8 = rule.ip_protocol.value; + attr.value.aclfield.mask.u8 = rule.ip_protocol.mask; + + if (!pbhRule->validateAddMatch(attr)) + { + SWSS_LOG_ERROR("Failed to configure PBH rule(%s) match: IP_PROTOCOL", rule.key.c_str()); + return false; + } + } + + if (rule.ipv6_next_header.is_set) + { + sai_attribute_t attr; + + attr.id = SAI_ACL_ENTRY_ATTR_FIELD_IPV6_NEXT_HEADER; + attr.value.aclfield.enable = true; + attr.value.aclfield.data.u8 = rule.ipv6_next_header.value; + attr.value.aclfield.mask.u8 = rule.ipv6_next_header.mask; + + if (!pbhRule->validateAddMatch(attr)) + { + SWSS_LOG_ERROR("Failed to configure PBH rule(%s) match: IPV6_NEXT_HEADER", rule.key.c_str()); + return false; + } + } + + if (rule.l4_dst_port.is_set) + { + sai_attribute_t attr; + + attr.id = SAI_ACL_ENTRY_ATTR_FIELD_L4_DST_PORT; + attr.value.aclfield.enable = true; + attr.value.aclfield.data.u16 = rule.l4_dst_port.value; + attr.value.aclfield.mask.u16 = rule.l4_dst_port.mask; + + if (!pbhRule->validateAddMatch(attr)) + { + SWSS_LOG_ERROR("Failed to configure PBH rule(%s) match: L4_DST_PORT", rule.key.c_str()); + return false; + } + } + + if (rule.inner_ether_type.is_set) + { + sai_attribute_t attr; + + attr.id = SAI_ACL_ENTRY_ATTR_FIELD_INNER_ETHER_TYPE; + attr.value.aclfield.enable = true; + attr.value.aclfield.data.u16 = rule.inner_ether_type.value; + attr.value.aclfield.mask.u16 = rule.inner_ether_type.mask; + + if (!pbhRule->validateAddMatch(attr)) + { + SWSS_LOG_ERROR("Failed to configure PBH rule(%s) match: INNER_ETHER_TYPE", rule.key.c_str()); + return false; + } + } + + if (rule.hash.is_set && rule.packet_action.is_set) + { + PbhHash hObj; + + if (this->pbhHlpr.getPbhHash(hObj, rule.hash.value)) + { + sai_attribute_t attr; + + attr.id = rule.packet_action.value; + attr.value.aclaction.enable = true; + attr.value.aclaction.parameter.oid = hObj.getOid(); + + if (!pbhRule->validateAddAction(attr)) + { + SWSS_LOG_ERROR("Failed to configure PBH rule(%s) action", rule.key.c_str()); + return false; + } + } + } + + if (!pbhRule->validate()) + { + SWSS_LOG_ERROR("Failed to validate PBH rule(%s)", rule.key.c_str()); + return false; + } + + if (!this->aclOrch->addAclRule(pbhRule, rule.table)) + { + SWSS_LOG_ERROR("Failed to create PBH rule(%s) in SAI", rule.key.c_str()); + return false; + } + + if (!this->pbhHlpr.addPbhRule(rule)) + { + SWSS_LOG_ERROR("Failed to add PBH rule(%s) to internal cache", rule.key.c_str()); + return false; + } + + if (!this->pbhHlpr.incRefCount(rule)) + { + SWSS_LOG_ERROR("Failed to add PBH rule(%s) dependencies", rule.key.c_str()); + return false; + } + + SWSS_LOG_NOTICE("Created PBH rule(%s) in SAI", rule.key.c_str()); + + return true; +} + +bool PbhOrch::updatePbhRule(const PbhRule &rule) +{ + SWSS_LOG_ENTER(); + + PbhRule rObj; + + if (!this->pbhHlpr.getPbhRule(rObj, rule.key)) + { + SWSS_LOG_ERROR("Failed to update PBH rule(%s) in SAI: object doesn't exist", rule.key.c_str()); + return false; + } + + if (!uMapDiffByKey(rObj.fieldValueMap, rule.fieldValueMap).empty()) + { + SWSS_LOG_ERROR("Failed to update PBH rule(%s) in SAI: fields add/remove is prohibited", rule.key.c_str()); + return false; + } + + bool flowCounterUpdate = false; + + for (const auto &oCit : rObj.fieldValueMap) + { + const auto &field = oCit.first; + + const auto &oValue = oCit.second; + const auto &nValue = rule.fieldValueMap.at(field); + + if (oValue == nValue) + { + continue; + } + + if (field != rule.flow_counter.meta.name) + { + SWSS_LOG_ERROR( + "Failed to update PBH rule(%s) in SAI: field(%s) update is prohibited", + rule.key.c_str(), + field.c_str() + ); + return false; + } + + flowCounterUpdate = true; + } + + if (!flowCounterUpdate) + { + SWSS_LOG_NOTICE("PBH rule(%s) in SAI is up-to-date", rule.key.c_str()); + return true; + } + + if (!this->aclOrch->updateAclRule(rule.table, rule.name, rule.flow_counter.value)) + { + SWSS_LOG_ERROR("Failed to update PBH rule(%s) in SAI", rule.key.c_str()); + return false; + } + + if (!this->pbhHlpr.updatePbhRule(rule)) + { + SWSS_LOG_ERROR("Failed to update PBH rule(%s) in internal cache", rule.key.c_str()); + return false; + } + + SWSS_LOG_NOTICE("Updated PBH rule(%s) in SAI", rule.key.c_str()); + + return true; +} + +bool PbhOrch::removePbhRule(const PbhRule &rule) +{ + SWSS_LOG_ENTER(); + + PbhRule rObj; + + if (!this->pbhHlpr.getPbhRule(rObj, rule.key)) + { + SWSS_LOG_ERROR("Failed to remove PBH rule(%s) from SAI: object doesn't exist", rule.key.c_str()); + return false; + } + + if (!this->aclOrch->removeAclRule(rObj.table, rObj.name)) + { + SWSS_LOG_ERROR("Failed to remove PBH rule(%s) from SAI", rObj.key.c_str()); + return false; + } + + if (!this->pbhHlpr.removePbhRule(rObj.key)) + { + SWSS_LOG_ERROR("Failed to remove PBH rule(%s) from internal cache", rObj.key.c_str()); + return false; + } + + if (!this->pbhHlpr.decRefCount(rObj)) + { + SWSS_LOG_ERROR("Failed to remove PBH rule(%s) dependencies", rObj.key.c_str()); + return false; + } + + SWSS_LOG_NOTICE("Removed PBH rule(%s) from SAI", rObj.key.c_str()); + + return true; +} + +void PbhOrch::deployPbhRuleSetupTasks() +{ + SWSS_LOG_ENTER(); + + auto &map = this->pbhHlpr.ruleTask.pendingSetupMap; + auto it = map.begin(); + + while (it != map.end()) + { + auto &key = it->first; + auto &rule = it->second; + + if (!this->pbhHlpr.validateDependencies(rule)) + { + SWSS_LOG_NOTICE("Unable to setup PBH rule(%s): object has missing dependencies: adding a retry", key.c_str()); + it++; + continue; + } + + PbhRule rObj; + + if (!this->pbhHlpr.getPbhRule(rObj, key)) + { + if (!this->createPbhRule(rule)) + { + SWSS_LOG_ERROR("Failed to create PBH rule(%s): ASIC and CONFIG DB are diverged", key.c_str()); + } + } + else + { + if (!this->updatePbhRule(rule)) + { + SWSS_LOG_ERROR("Failed to update PBH rule(%s): ASIC and CONFIG DB are diverged", key.c_str()); + } + } + + it = map.erase(it); + } +} + +void PbhOrch::deployPbhRuleRemoveTasks() +{ + SWSS_LOG_ENTER(); + + auto &map = this->pbhHlpr.ruleTask.pendingRemoveMap; + auto it = map.begin(); + + while (it != map.end()) + { + auto &key = it->first; + auto &rule = it->second; + + PbhRule rObj; + + if (!this->pbhHlpr.getPbhRule(rObj, key)) + { + SWSS_LOG_ERROR("Failed to remove PBH rule(%s): object doesn't exist", key.c_str()); + it = map.erase(it); + continue; + } + + if (!this->removePbhRule(rule)) + { + SWSS_LOG_ERROR("Failed to remove PBH rule(%s): ASIC and CONFIG DB are diverged", key.c_str()); + it = map.erase(it); + continue; + } + + it = map.erase(it); + } +} + +// PBH hash ----------------------------------------------------------------------------------------------------------- + +bool PbhOrch::createPbhHash(const PbhHash &hash) +{ + SWSS_LOG_ENTER(); + + PbhHash hObj; + + if (this->pbhHlpr.getPbhHash(hObj, hash.key)) + { + SWSS_LOG_ERROR("Failed to create PBH hash(%s) in SAI: object already exists", hash.key.c_str()); + return false; + } + + std::vector hashFieldOidList; + + if (hash.hash_field_list.is_set) + { + for (const auto &cit : hash.hash_field_list.value) + { + PbhHashField hfObj; + + if (!this->pbhHlpr.getPbhHashField(hfObj, cit)) + { + SWSS_LOG_ERROR( + "Failed to create PBH hash(%s) in SAI: missing hash field(%s)", + hash.key.c_str(), + cit.c_str() + ); + return false; + } + + hashFieldOidList.push_back(hfObj.getOid()); + } + } + + if (hashFieldOidList.empty()) + { + SWSS_LOG_ERROR("Failed to create PBH hash(%s) in SAI: missing hash fields", hash.key.c_str()); + return false; + } + + sai_attribute_t attr; + std::vector attrList; + + attr.id = SAI_HASH_ATTR_FINE_GRAINED_HASH_FIELD_LIST; + attr.value.objlist.count = static_cast(hashFieldOidList.size()); + attr.value.objlist.list = hashFieldOidList.data(); + attrList.push_back(attr); + + sai_status_t status; + sai_object_id_t hashOid; + + status = sai_hash_api->create_hash(&hashOid, gSwitchId, static_cast(attrList.size()), attrList.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create PBH hash(%s) in SAI", hash.key.c_str()); + return false; + } + + hObj = hash; + hObj.setOid(hashOid); + + if (!this->pbhHlpr.addPbhHash(hObj)) + { + SWSS_LOG_ERROR("Failed to add PBH hash(%s) to internal cache", hObj.key.c_str()); + return false; + } + + if (!this->pbhHlpr.incRefCount(hObj)) + { + SWSS_LOG_ERROR("Failed to add PBH hash(%s) dependencies", hObj.key.c_str()); + return false; + } + + SWSS_LOG_NOTICE("Created PBH hash(%s) in SAI", hObj.key.c_str()); + + return true; +} + +bool PbhOrch::updatePbhHash(const PbhHash &hash) +{ + SWSS_LOG_ENTER(); + + PbhHash hObj; + + if (!this->pbhHlpr.getPbhHash(hObj, hash.key)) + { + SWSS_LOG_ERROR("Failed to update PBH hash(%s) in SAI: object doesn't exist", hash.key.c_str()); + return false; + } + + if (!uMapDiffByKey(hObj.fieldValueMap, hash.fieldValueMap).empty()) + { + SWSS_LOG_ERROR("Failed to update PBH hash(%s) in SAI: fields add/remove is prohibited", hash.key.c_str()); + return false; + } + + for (const auto &oCit : hObj.fieldValueMap) + { + const auto &field = oCit.first; + + const auto &oValue = oCit.second; + const auto &nValue = hash.fieldValueMap.at(field); + + if (oValue != nValue) + { + SWSS_LOG_ERROR( + "Failed to update PBH hash(%s) in SAI: field(%s) update is prohibited", + hash.key.c_str(), + field.c_str() + ); + return false; + } + } + + SWSS_LOG_NOTICE("PBH hash(%s) in SAI is up-to-date", hash.key.c_str()); + + return true; +} + +bool PbhOrch::removePbhHash(const PbhHash &hash) +{ + SWSS_LOG_ENTER(); + + PbhHash hObj; + + if (!this->pbhHlpr.getPbhHash(hObj, hash.key)) + { + SWSS_LOG_ERROR("Failed to remove PBH hash(%s) from SAI: object doesn't exist", hash.key.c_str()); + return false; + } + + sai_status_t status; + + status = sai_hash_api->remove_hash(hObj.getOid()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove PBH hash(%s) from SAI", hObj.key.c_str()); + return false; + } + + if (!this->pbhHlpr.removePbhHash(hObj.key)) + { + SWSS_LOG_ERROR("Failed to remove PBH hash(%s) from internal cache", hObj.key.c_str()); + return false; + } + + if (!this->pbhHlpr.decRefCount(hObj)) + { + SWSS_LOG_ERROR("Failed to remove PBH hash(%s) dependencies", hObj.key.c_str()); + return false; + } + + SWSS_LOG_NOTICE("Removed PBH hash(%s) from SAI", hObj.key.c_str()); + + return true; +} + +void PbhOrch::deployPbhHashSetupTasks() +{ + SWSS_LOG_ENTER(); + + auto &map = this->pbhHlpr.hashTask.pendingSetupMap; + auto it = map.begin(); + + while (it != map.end()) + { + auto &key = it->first; + auto &hash = it->second; + + if (!this->pbhHlpr.validateDependencies(hash)) + { + SWSS_LOG_NOTICE("Unable to create PBH hash(%s): object has missing dependencies: adding a retry", key.c_str()); + it++; + continue; + } + + PbhHash hObj; + + if (!this->pbhHlpr.getPbhHash(hObj, key)) + { + if (!this->createPbhHash(hash)) + { + SWSS_LOG_ERROR("Failed to create PBH hash(%s): ASIC and CONFIG DB are diverged", key.c_str()); + } + } + else + { + if (!this->updatePbhHash(hash)) + { + SWSS_LOG_ERROR("Failed to update PBH hash(%s): ASIC and CONFIG DB are diverged", key.c_str()); + } + } + + it = map.erase(it); + } +} + +void PbhOrch::deployPbhHashRemoveTasks() +{ + SWSS_LOG_ENTER(); + + auto &map = this->pbhHlpr.hashTask.pendingRemoveMap; + auto it = map.begin(); + + while (it != map.end()) + { + auto &key = it->first; + auto &hash = it->second; + + PbhHash hObj; + + if (!this->pbhHlpr.getPbhHash(hObj, key)) + { + SWSS_LOG_ERROR("Failed to remove PBH hash(%s): object doesn't exist", key.c_str()); + it = map.erase(it); + continue; + } + + if (this->pbhHlpr.hasDependencies(hObj)) + { + SWSS_LOG_NOTICE("Unable to remove PBH hash(%s): object has dependencies: adding a retry", key.c_str()); + it++; + continue; + } + + if (!this->removePbhHash(hash)) + { + SWSS_LOG_ERROR("Failed to remove PBH hash(%s): ASIC and CONFIG DB are diverged", key.c_str()); + it = map.erase(it); + continue; + } + + it = map.erase(it); + } +} + +// PBH hash field ----------------------------------------------------------------------------------------------------- + +bool PbhOrch::createPbhHashField(const PbhHashField &hashField) +{ + SWSS_LOG_ENTER(); + + PbhHashField hfObj; + + if (this->pbhHlpr.getPbhHashField(hfObj, hashField.key)) + { + SWSS_LOG_ERROR("Failed to create PBH hash field(%s) in SAI: object already exists", hashField.key.c_str()); + return false; + } + + std::vector attrList; + + if (hashField.hash_field.is_set) + { + sai_attribute_t attr; + + attr.id = SAI_FINE_GRAINED_HASH_FIELD_ATTR_NATIVE_HASH_FIELD; + attr.value.s32 = hashField.hash_field.value; + + attrList.push_back(attr); + } + + if (hashField.ip_mask.is_set) + { + sai_attribute_t attr; + + if (hashField.ip_mask.value.isV4()) + { + attr.id = SAI_FINE_GRAINED_HASH_FIELD_ATTR_IPV4_MASK; + attr.value.ip4 = hashField.ip_mask.value.getV4Addr(); + } + else + { + attr.id = SAI_FINE_GRAINED_HASH_FIELD_ATTR_IPV6_MASK; + std::memcpy(attr.value.ip6, hashField.ip_mask.value.getV6Addr(), sizeof(attr.value.ip6)); + } + + attrList.push_back(attr); + } + + if (hashField.sequence_id.is_set) + { + sai_attribute_t attr; + + attr.id = SAI_FINE_GRAINED_HASH_FIELD_ATTR_SEQUENCE_ID; + attr.value.u32 = hashField.sequence_id.value; + + attrList.push_back(attr); + } + + if (attrList.empty()) + { + SWSS_LOG_ERROR("Failed to create PBH hash field(%s) in SAI: missing attributes", hashField.key.c_str()); + return false; + } + + sai_status_t status; + sai_object_id_t hfOid; + + status = sai_hash_api->create_fine_grained_hash_field(&hfOid, gSwitchId, static_cast(attrList.size()), attrList.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create PBH hash field(%s) in SAI", hashField.key.c_str()); + return false; + } + + hfObj = hashField; + hfObj.setOid(hfOid); + + if (!this->pbhHlpr.addPbhHashField(hfObj)) + { + SWSS_LOG_ERROR("Failed to add PBH hash field(%s) to internal cache", hfObj.key.c_str()); + return false; + } + + SWSS_LOG_NOTICE("Created PBH hash field(%s) in SAI", hfObj.key.c_str()); + + return true; +} + +bool PbhOrch::updatePbhHashField(const PbhHashField &hashField) +{ + PbhHashField hfObj; + + if (!this->pbhHlpr.getPbhHashField(hfObj, hashField.key)) + { + SWSS_LOG_ERROR("Failed to update PBH hash field(%s) in SAI: object doesn't exist", hashField.key.c_str()); + return false; + } + + if (!uMapDiffByKey(hfObj.fieldValueMap, hashField.fieldValueMap).empty()) + { + SWSS_LOG_ERROR("Failed to update PBH hash field(%s) in SAI: fields add/remove is prohibited", hashField.key.c_str()); + return false; + } + + for (const auto &oCit : hfObj.fieldValueMap) + { + const auto &field = oCit.first; + + const auto &oValue = oCit.second; + const auto &nValue = hashField.fieldValueMap.at(field); + + if (oValue != nValue) + { + SWSS_LOG_ERROR( + "Failed to update PBH hash field(%s) in SAI: field(%s) update is prohibited", + hashField.key.c_str(), + field.c_str() + ); + return false; + } + } + + SWSS_LOG_NOTICE("PBH hash field(%s) in SAI is up-to-date", hashField.key.c_str()); + + return true; +} + +bool PbhOrch::removePbhHashField(const PbhHashField &hashField) +{ + SWSS_LOG_ENTER(); + + PbhHashField hfObj; + + if (!this->pbhHlpr.getPbhHashField(hfObj, hashField.key)) + { + SWSS_LOG_ERROR("Failed to remove PBH hash field(%s) from SAI: object doesn't exist", hashField.key.c_str()); + return false; + } + + sai_status_t status; + + status = sai_hash_api->remove_fine_grained_hash_field(hfObj.getOid()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove PBH hash field(%s) from SAI", hfObj.key.c_str()); + return false; + } + + if (!this->pbhHlpr.removePbhHashField(hfObj.key)) + { + SWSS_LOG_ERROR("Failed to remove PBH hash field(%s) from internal cache", hfObj.key.c_str()); + return false; + } + + SWSS_LOG_NOTICE("Removed PBH hash field(%s) from SAI", hfObj.key.c_str()); + + return true; +} + +void PbhOrch::deployPbhHashFieldSetupTasks() +{ + SWSS_LOG_ENTER(); + + auto &map = this->pbhHlpr.hashFieldTask.pendingSetupMap; + auto it = map.begin(); + + while (it != map.end()) + { + auto &key = it->first; + auto &hashField = it->second; + + PbhHashField hfObj; + + if (!this->pbhHlpr.getPbhHashField(hfObj, key)) + { + if (!this->createPbhHashField(hashField)) + { + SWSS_LOG_ERROR("Failed to create PBH hash field(%s): ASIC and CONFIG DB are diverged", key.c_str()); + } + } + else + { + if (!this->updatePbhHashField(hashField)) + { + SWSS_LOG_ERROR("Failed to update PBH hash field(%s): ASIC and CONFIG DB are diverged", key.c_str()); + } + } + + it = map.erase(it); + } +} + +void PbhOrch::deployPbhHashFieldRemoveTasks() +{ + SWSS_LOG_ENTER(); + + auto &map = this->pbhHlpr.hashFieldTask.pendingRemoveMap; + auto it = map.begin(); + + while (it != map.end()) + { + auto &key = it->first; + auto &hashField = it->second; + + PbhHashField hfObj; + + if (!this->pbhHlpr.getPbhHashField(hfObj, key)) + { + SWSS_LOG_ERROR("Failed to remove PBH hash field(%s): object doesn't exist", key.c_str()); + it = map.erase(it); + continue; + } + + if (this->pbhHlpr.hasDependencies(hfObj)) + { + SWSS_LOG_NOTICE("Unable to remove PBH hash field(%s): object has dependencies: adding a retry", key.c_str()); + it++; + continue; + } + + if (!this->removePbhHashField(hashField)) + { + SWSS_LOG_ERROR("Failed to remove PBH hash field(%s): ASIC and CONFIG DB are diverged", key.c_str()); + it = map.erase(it); + continue; + } + + it = map.erase(it); + } +} + +// PBH task ----------------------------------------------------------------------------------------------------------- + +void PbhOrch::deployPbhTasks() +{ + this->deployPbhRuleRemoveTasks(); + this->deployPbhTableRemoveTasks(); + this->deployPbhHashRemoveTasks(); + this->deployPbhHashFieldRemoveTasks(); + + this->deployPbhHashFieldSetupTasks(); + this->deployPbhHashSetupTasks(); + this->deployPbhTableSetupTasks(); + this->deployPbhRuleSetupTasks(); +} + +void PbhOrch::doPbhTableTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto &map = consumer.m_toSync; + auto it = map.begin(); + + while (it != map.end()) + { + auto keyOpFieldsValues = it->second; + auto key = kfvKey(keyOpFieldsValues); + auto op = kfvOp(keyOpFieldsValues); + + SWSS_LOG_INFO("KEY: %s, OP: %s", key.c_str(), op.c_str()); + + if (key.empty()) + { + SWSS_LOG_ERROR("Failed to parse PBH table key: empty string"); + it = map.erase(it); + continue; + } + + PbhTable table(key, op); + table.name = key; + + if (this->pbhTaskExists(table)) + { + SWSS_LOG_WARN("Unable to process PBH table(%s): task already exists: adding a retry", key.c_str()); + it++; + continue; + } + + if (op == SET_COMMAND) + { + for (const auto &cit : kfvFieldsValues(keyOpFieldsValues)) + { + auto fieldName = fvField(cit); + auto fieldValue = fvValue(cit); + + SWSS_LOG_INFO("FIELD: %s, VALUE: %s", fieldName.c_str(), fieldValue.c_str()); + + table.fieldValueMap[fieldName] = fieldValue; + } + + if (this->pbhHlpr.parsePbhTable(table)) + { + this->pbhHlpr.tableTask.pendingSetupMap[table.key] = table; + } + } + else if (op == DEL_COMMAND) + { + this->pbhHlpr.tableTask.pendingRemoveMap[table.key] = table; + } + else + { + SWSS_LOG_ERROR("Unknown operation(%s)", op.c_str()); + } + + it = map.erase(it); + } +} + +void PbhOrch::doPbhRuleTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto &map = consumer.m_toSync; + auto it = map.begin(); + + while (it != map.end()) + { + auto keyOpFieldsValues = it->second; + auto key = kfvKey(keyOpFieldsValues); + auto op = kfvOp(keyOpFieldsValues); + + SWSS_LOG_INFO("KEY: %s, OP: %s", key.c_str(), op.c_str()); + + auto keyTokens = tokenize(key, consumer.getConsumerTable()->getTableNameSeparator()[0]); + + if (keyTokens.size() != 2) + { + SWSS_LOG_ERROR("Failed to parse PBH rule key(%s): invalid format", key.c_str()); + it = map.erase(it); + continue; + } + + auto tableName = keyTokens[0]; + auto ruleName = keyTokens[1]; + + PbhRule rule(key, op); + rule.name = ruleName; + rule.table = tableName; + + if (this->pbhTaskExists(rule)) + { + SWSS_LOG_WARN("Unable to process PBH rule(%s): task already exists: adding a retry", key.c_str()); + it++; + continue; + } + + if (op == SET_COMMAND) + { + for (const auto &cit : kfvFieldsValues(keyOpFieldsValues)) + { + auto fieldName = fvField(cit); + auto fieldValue = fvValue(cit); + + SWSS_LOG_INFO("FIELD: %s, VALUE: %s", fieldName.c_str(), fieldValue.c_str()); + + rule.fieldValueMap[fieldName] = fieldValue; + } + + if (this->pbhHlpr.parsePbhRule(rule)) + { + this->pbhHlpr.ruleTask.pendingSetupMap[rule.key] = rule; + } + } + else if (op == DEL_COMMAND) + { + this->pbhHlpr.ruleTask.pendingRemoveMap[rule.key] = rule; + } + else + { + SWSS_LOG_ERROR("Unknown operation(%s)", op.c_str()); + } + + it = map.erase(it); + } +} + +void PbhOrch::doPbhHashTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto &map = consumer.m_toSync; + auto it = map.begin(); + + while (it != map.end()) + { + auto keyOpFieldsValues = it->second; + auto key = kfvKey(keyOpFieldsValues); + auto op = kfvOp(keyOpFieldsValues); + + SWSS_LOG_INFO("KEY: %s, OP: %s", key.c_str(), op.c_str()); + + if (key.empty()) + { + SWSS_LOG_ERROR("Failed to parse PBH hash key: empty string"); + it = map.erase(it); + continue; + } + + PbhHash hash(key, op); + + if (this->pbhTaskExists(hash)) + { + SWSS_LOG_WARN("Unable to process PBH hash(%s): task already exists: adding a retry", key.c_str()); + it++; + continue; + } + + if (op == SET_COMMAND) + { + for (const auto &cit : kfvFieldsValues(keyOpFieldsValues)) + { + auto fieldName = fvField(cit); + auto fieldValue = fvValue(cit); + + SWSS_LOG_INFO("FIELD: %s, VALUE: %s", fieldName.c_str(), fieldValue.c_str()); + + hash.fieldValueMap[fieldName] = fieldValue; + } + + if (this->pbhHlpr.parsePbhHash(hash)) + { + this->pbhHlpr.hashTask.pendingSetupMap[hash.key] = hash; + } + } + else if (op == DEL_COMMAND) + { + this->pbhHlpr.hashTask.pendingRemoveMap[hash.key] = hash; + } + else + { + SWSS_LOG_ERROR("Unknown operation(%s)", op.c_str()); + } + + it = map.erase(it); + } +} + +void PbhOrch::doPbhHashFieldTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto &map = consumer.m_toSync; + auto it = map.begin(); + + while (it != map.end()) + { + auto keyOpFieldsValues = it->second; + auto key = kfvKey(keyOpFieldsValues); + auto op = kfvOp(keyOpFieldsValues); + + SWSS_LOG_INFO("KEY: %s, OP: %s", key.c_str(), op.c_str()); + + if (key.empty()) + { + SWSS_LOG_ERROR("Failed to parse PBH hash field key: empty string"); + it = map.erase(it); + continue; + } + + PbhHashField hashField(key, op); + + if (this->pbhTaskExists(hashField)) + { + SWSS_LOG_WARN("Unable to process PBH hash field(%s): task already exists: adding a retry", key.c_str()); + it++; + continue; + } + + if (op == SET_COMMAND) + { + for (const auto &cit : kfvFieldsValues(keyOpFieldsValues)) + { + auto fieldName = fvField(cit); + auto fieldValue = fvValue(cit); + + SWSS_LOG_INFO("FIELD: %s, VALUE: %s", fieldName.c_str(), fieldValue.c_str()); + + hashField.fieldValueMap[fieldName] = fieldValue; + } + + if (this->pbhHlpr.parsePbhHashField(hashField)) + { + this->pbhHlpr.hashFieldTask.pendingSetupMap[hashField.key] = hashField; + } + } + else if (op == DEL_COMMAND) + { + this->pbhHlpr.hashFieldTask.pendingRemoveMap[hashField.key] = hashField; + } + else + { + SWSS_LOG_ERROR("Unknown operation(%s)", op.c_str()); + } + + it = map.erase(it); + } +} + +void PbhOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (!this->portsOrch->allPortsReady()) + { + return; + } + + auto tableName = consumer.getTableName(); + + if (tableName == CFG_PBH_TABLE_TABLE_NAME) + { + this->doPbhTableTask(consumer); + } + else if (tableName == CFG_PBH_RULE_TABLE_NAME) + { + this->doPbhRuleTask(consumer); + } + else if (tableName == CFG_PBH_HASH_TABLE_NAME) + { + this->doPbhHashTask(consumer); + } + else if (tableName == CFG_PBH_HASH_FIELD_TABLE_NAME) + { + this->doPbhHashFieldTask(consumer); + } + else + { + SWSS_LOG_ERROR("Unknown table(%s)", tableName.c_str()); + } + + this->deployPbhTasks(); +} diff --git a/orchagent/pbhorch.h b/orchagent/pbhorch.h new file mode 100644 index 000000000000..1aa49e1d26bb --- /dev/null +++ b/orchagent/pbhorch.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +#include "orch.h" +#include "aclorch.h" +#include "portsorch.h" + +#include "pbh/pbhrule.h" +#include "pbh/pbhmgr.h" + +class PbhOrch final : public Orch +{ +public: + PbhOrch( + std::vector &connectorList, + AclOrch *aclOrch, + PortsOrch *portsOrch + ); + ~PbhOrch(); + + using Orch::doTask; // Allow access to the basic doTask + +private: + template + auto getPbhSetupTaskMap() const -> const std::unordered_map&; + template + auto getPbhRemoveTaskMap() const -> const std::unordered_map&; + + template + bool pbhSetupTaskExists(const T &obj) const; + template + bool pbhRemoveTaskExists(const T &obj) const; + + template + bool pbhTaskExists(const T &obj) const; + + bool createPbhTable(const PbhTable &table); + bool updatePbhTable(const PbhTable &table); + bool removePbhTable(const PbhTable &table); + + bool createPbhRule(const PbhRule &rule); + bool updatePbhRule(const PbhRule &rule); + bool removePbhRule(const PbhRule &rule); + + bool createPbhHash(const PbhHash &hash); + bool updatePbhHash(const PbhHash &hash); + bool removePbhHash(const PbhHash &hash); + + bool createPbhHashField(const PbhHashField &hashField); + bool updatePbhHashField(const PbhHashField &hashField); + bool removePbhHashField(const PbhHashField &hashField); + + void deployPbhTableSetupTasks(); + void deployPbhTableRemoveTasks(); + + void deployPbhRuleSetupTasks(); + void deployPbhRuleRemoveTasks(); + + void deployPbhHashSetupTasks(); + void deployPbhHashRemoveTasks(); + + void deployPbhHashFieldSetupTasks(); + void deployPbhHashFieldRemoveTasks(); + + void deployPbhTasks(); + + void doPbhTableTask(Consumer &consumer); + void doPbhRuleTask(Consumer &consumer); + void doPbhHashTask(Consumer &consumer); + void doPbhHashFieldTask(Consumer &consumer); + void doTask(Consumer &consumer); + + AclOrch *aclOrch; + PortsOrch *portsOrch; + + PbhHelper pbhHlpr; +}; diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 561fd5df8d06..490efb90b360 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -56,6 +56,7 @@ sai_wred_api_t* sai_wred_api; sai_qos_map_api_t* sai_qos_map_api; sai_buffer_api_t* sai_buffer_api; sai_acl_api_t* sai_acl_api; +sai_hash_api_t* sai_hash_api; sai_mirror_api_t* sai_mirror_api; sai_fdb_api_t* sai_fdb_api; sai_dtel_api_t* sai_dtel_api; @@ -180,6 +181,7 @@ void initSaiApi() sai_api_query(SAI_API_BUFFER, (void **)&sai_buffer_api); sai_api_query(SAI_API_SCHEDULER_GROUP, (void **)&sai_scheduler_group_api); sai_api_query(SAI_API_ACL, (void **)&sai_acl_api); + sai_api_query(SAI_API_HASH, (void **)&sai_hash_api); sai_api_query(SAI_API_DTEL, (void **)&sai_dtel_api); sai_api_query(SAI_API_SAMPLEPACKET, (void **)&sai_samplepacket_api); sai_api_query(SAI_API_DEBUG_COUNTER, (void **)&sai_debug_counter_api); @@ -212,6 +214,7 @@ void initSaiApi() sai_log_set(SAI_API_BUFFER, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_SCHEDULER_GROUP, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_ACL, SAI_LOG_LEVEL_NOTICE); + sai_log_set(SAI_API_HASH, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_DTEL, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_SAMPLEPACKET, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_DEBUG_COUNTER, SAI_LOG_LEVEL_NOTICE); diff --git a/tests/conftest.py b/tests/conftest.py index a303c5f17e1a..d77a2e17e1c5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,6 +19,7 @@ from dvslib.dvs_database import DVSDatabase from dvslib.dvs_common import PollingConfig, wait_for_result from dvslib.dvs_acl import DVSAcl +from dvslib.dvs_pbh import DVSPbh from dvslib.dvs_route import DVSRoute from dvslib import dvs_vlan from dvslib import dvs_lag @@ -1605,9 +1606,9 @@ def vct(request): @pytest.yield_fixture def testlog(request, dvs): - dvs.runcmd(f"logger === start test {request.node.name} ===") + dvs.runcmd(f"logger -t pytest === start test {request.node.nodeid} ===") yield testlog - dvs.runcmd(f"logger === finish test {request.node.name} ===") + dvs.runcmd(f"logger -t pytest === finish test {request.node.nodeid} ===") ################# DVSLIB module manager fixtures ############################# @pytest.fixture(scope="class") @@ -1617,6 +1618,13 @@ def dvs_acl(request, dvs) -> DVSAcl: dvs.get_state_db(), dvs.get_counters_db()) + +@pytest.fixture(scope="class") +def dvs_pbh(request, dvs) -> DVSPbh: + return DVSPbh(dvs.get_asic_db(), + dvs.get_config_db()) + + @pytest.fixture(scope="class") def dvs_route(request, dvs) -> DVSRoute: return DVSRoute(dvs.get_asic_db(), diff --git a/tests/dvslib/dvs_acl.py b/tests/dvslib/dvs_acl.py index f14aad8c3590..6e351139044f 100644 --- a/tests/dvslib/dvs_acl.py +++ b/tests/dvslib/dvs_acl.py @@ -31,6 +31,11 @@ class DVSAcl: "egress": "SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_EGRESS" } + ADB_PORTCHANNEL_ATTR_LOOKUP = { + "ingress": "SAI_LAG_ATTR_INGRESS_ACL", + "egress": "SAI_LAG_ATTR_EGRESS_ACL" + } + ADB_PORT_ATTR_LOOKUP = { "ingress": "SAI_PORT_ATTR_INGRESS_ACL", "egress": "SAI_PORT_ATTR_EGRESS_ACL" @@ -196,6 +201,37 @@ def verify_acl_table_group_members(self, acl_table_id: str, acl_table_group_ids: assert set(member_groups) == set(acl_table_group_ids) + def verify_acl_table_portchannel_binding( + self, + acl_table_id: str, + bind_portchannels: List[str], + num_tables: int, + stage: str = "ingress" + ) -> None: + """Verify that the ACL table has been bound to the given list of portchannels. + + Args: + acl_table_id: The ACL table that is being checked. + bind_portchannels: The portchannels that should be bound to the given ACL table. + num_tables: The total number of ACL tables in ASIC DB. + stage: The stage of the ACL table that was created. + """ + acl_table_group_ids = self.asic_db.wait_for_n_keys(self.ADB_ACL_GROUP_TABLE_NAME, len(bind_portchannels)) + + portchannel_groups = [] + for portchannel in bind_portchannels: + portchannel_oid = self.counters_db.get_entry("COUNTERS_LAG_NAME_MAP", "").get(portchannel) + fvs = self.asic_db.wait_for_entry("ASIC_STATE:SAI_OBJECT_TYPE_LAG", portchannel_oid) + + acl_table_group_id = fvs.pop(self.ADB_PORTCHANNEL_ATTR_LOOKUP[stage], None) + assert acl_table_group_id in acl_table_group_ids + portchannel_groups.append(acl_table_group_id) + + assert len(portchannel_groups) == len(bind_portchannels) + assert set(portchannel_groups) == set(acl_table_group_ids) + + self.verify_acl_table_group_members(acl_table_id, acl_table_group_ids, num_tables) + def verify_acl_table_port_binding( self, acl_table_id: str, diff --git a/tests/dvslib/dvs_pbh.py b/tests/dvslib/dvs_pbh.py new file mode 100644 index 000000000000..79a58681a9cd --- /dev/null +++ b/tests/dvslib/dvs_pbh.py @@ -0,0 +1,127 @@ +"""Utilities for interacting with PBH objects when writing VS tests.""" +from typing import Dict, List + + +class DVSPbh: + """Manage PBH table/rule/hash and hash field objects on the virtual switch.""" + + CDB_PBH_TABLE = "PBH_TABLE" + CDB_PBH_RULE = "PBH_RULE" + CDB_PBH_HASH = "PBH_HASH" + CDB_PBH_HASH_FIELD = "PBH_HASH_FIELD" + + def __init__(self, asic_db, config_db): + """Create a new DVS PBH Manager.""" + self.asic_db = asic_db + self.config_db = config_db + + def create_pbh_table( + self, + table_name: str, + interface_list: List[str], + description: str = None + ) -> None: + """Create PBH table in Config DB.""" + attr_dict = { + "interface_list": ",".join(interface_list), + "description": description + } + + if description is not None: + attr_dict["description"] = table_name + + self.config_db.create_entry(self.CDB_PBH_TABLE, table_name, attr_dict) + + def remove_pbh_table( + self, + table_name: str + ) -> None: + """Remove PBH table from Config DB.""" + self.config_db.delete_entry(self.CDB_PBH_TABLE, table_name) + + def create_pbh_rule( + self, + table_name: str, + rule_name: str, + priority: str, + qualifiers: Dict[str, str], + hash_name: str, + packet_action: str = "SET_ECMP_HASH", + flow_counter: str = "DISABLED" + ) -> None: + """Create PBH rule in Config DB.""" + attr_dict = { + "priority": priority, + "hash": hash_name, + "packet_action": packet_action, + "flow_counter": flow_counter, + **qualifiers + } + + self.config_db.create_entry(self.CDB_PBH_RULE, "{}|{}".format(table_name, rule_name), attr_dict) + + def remove_pbh_rule( + self, + table_name: str, + rule_name: str + ) -> None: + """Remove PBH rule from Config DB.""" + self.config_db.delete_entry(self.CDB_PBH_RULE, "{}|{}".format(table_name, rule_name)) + + def create_pbh_hash( + self, + hash_name: str, + hash_field_list: List[str] + ) -> None: + """Create PBH hash in Config DB.""" + attr_dict = { + "hash_field_list": ",".join(hash_field_list) + } + + self.config_db.create_entry(self.CDB_PBH_HASH, hash_name, attr_dict) + + def remove_pbh_hash( + self, + hash_name: str + ) -> None: + """Remove PBH hash from Config DB.""" + self.config_db.delete_entry(self.CDB_PBH_HASH, hash_name) + + def verify_pbh_hash_count( + self, + expected: int + ) -> None: + """Verify that there are N hash objects in ASIC DB.""" + self.asic_db.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_HASH", expected) + + def create_pbh_hash_field( + self, + hash_field_name: str, + hash_field: str, + sequence_id: str, + ip_mask: str = None + ) -> None: + """Create PBH hash field in Config DB.""" + attr_dict = { + "hash_field": hash_field, + "sequence_id": sequence_id + } + + if ip_mask is not None: + attr_dict["ip_mask"] = ip_mask + + self.config_db.create_entry(self.CDB_PBH_HASH_FIELD, hash_field_name, attr_dict) + + def remove_pbh_hash_field( + self, + hash_field_name: str + ) -> None: + """Remove PBH hash field from Config DB.""" + self.config_db.delete_entry(self.CDB_PBH_HASH_FIELD, hash_field_name) + + def verify_pbh_hash_field_count( + self, + expected: int + ) -> None: + """Verify that there are N hash field objects in ASIC DB.""" + self.asic_db.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_FINE_GRAINED_HASH_FIELD", expected) diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 69f25a6b0e1f..5fed8001a4e0 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -50,6 +50,10 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/mirrororch.cpp \ $(top_srcdir)/orchagent/fdborch.cpp \ $(top_srcdir)/orchagent/aclorch.cpp \ + $(top_srcdir)/orchagent/pbh/pbhcnt.cpp \ + $(top_srcdir)/orchagent/pbh/pbhmgr.cpp \ + $(top_srcdir)/orchagent/pbh/pbhrule.cpp \ + $(top_srcdir)/orchagent/pbhorch.cpp \ $(top_srcdir)/orchagent/saihelper.cpp \ $(top_srcdir)/orchagent/switchorch.cpp \ $(top_srcdir)/orchagent/pfcwdorch.cpp \ diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index 4b1ce101ae50..4cd76888837f 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -937,6 +937,35 @@ namespace aclorch_test } return true; } + + bool validateAclRuleCounter(const AclRule &rule, bool enabled) + { + auto ruleOid = Portal::AclRuleInternal::getRuleOid(&rule); + + sai_attribute_t attr; + attr.id = SAI_ACL_ENTRY_ATTR_ACTION_COUNTER; + + auto status = sai_acl_api->get_acl_entry_attribute(ruleOid, 1, &attr); + if (status != SAI_STATUS_SUCCESS) + { + return false; + } + + auto &aclEnable = attr.value.aclaction.enable; + auto &aclOid = attr.value.aclaction.parameter.oid; + + if (enabled) + { + if (aclEnable && aclOid != SAI_NULL_OBJECT_ID) + { + return true; + } + + return false; + } + + return !aclEnable && aclOid == SAI_NULL_OBJECT_ID; + } }; map AclOrchTest::gProfileMap; @@ -1180,4 +1209,102 @@ namespace aclorch_test } } + // When received ACL table/rule SET_COMMAND, orchagent can create corresponding ACL table/rule + // When received ACL table/rule DEL_COMMAND, orchagent can delete corresponding ACL table/rule + // + // Verify ACL rule counter enable/disable + // + TEST_F(AclOrchTest, AclRule_Counter_Configuration) + { + string tableId = "acl_table_1"; + string ruleId = "acl_rule_1"; + + auto orch = createAclOrch(); + + // add acl table ... + + auto kvfAclTable = deque({{ + tableId, + SET_COMMAND, + { + { ACL_TABLE_DESCRIPTION, "L3 table" }, + { ACL_TABLE_TYPE, TABLE_TYPE_L3 }, + { ACL_TABLE_STAGE, STAGE_INGRESS }, + { ACL_TABLE_PORTS, "1,2" } + } + }}); + + orch->doAclTableTask(kvfAclTable); + + // validate acl table add ... + + auto tableOid = orch->getTableById(tableId); + ASSERT_NE(tableOid, SAI_NULL_OBJECT_ID); + + auto tableIt = orch->getAclTables().find(tableOid); + ASSERT_NE(tableIt, orch->getAclTables().end()); + + // add acl rule ... + + auto kvfAclRule = deque({{ + tableId + "|" + ruleId, + SET_COMMAND, + { + { ACTION_PACKET_ACTION, PACKET_ACTION_FORWARD }, + { MATCH_SRC_IP, "1.2.3.4" }, + { MATCH_DST_IP, "4.3.2.1" } + } + }}); + + orch->doAclRuleTask(kvfAclRule); + + // validate acl rule add ... + + auto ruleIt = tableIt->second.rules.find(ruleId); + ASSERT_NE(ruleIt, tableIt->second.rules.end()); + + auto &tableObj = tableIt->second; + auto &ruleObj = ruleIt->second; + + // validate acl counter disabled ... + + ASSERT_TRUE(ruleObj->disableCounter()); + ASSERT_TRUE(validateAclRuleCounter(*ruleObj, false)); + + // validate acl counter enabled ... + + ASSERT_TRUE(ruleObj->enableCounter()); + ASSERT_TRUE(validateAclRuleCounter(*ruleObj, true)); + + // delete acl rule ... + + kvfAclRule = deque({{ + tableId + "|" + ruleId, + DEL_COMMAND, + {} + }}); + + orch->doAclRuleTask(kvfAclRule); + + // validate acl rule delete ... + + ruleIt = tableObj.rules.find(ruleId); + ASSERT_EQ(ruleIt, tableObj.rules.end()); + + // delete acl table ... + + kvfAclTable = deque({{ + tableId, + DEL_COMMAND, + {} + }}); + + orch->doAclTableTask(kvfAclTable); + + // validate acl table delete ... + + tableIt = orch->getAclTables().find(tableOid); + ASSERT_EQ(tableIt, orch->getAclTables().end()); + } + } // namespace nsAclOrchTest diff --git a/tests/test_pbh.py b/tests/test_pbh.py new file mode 100644 index 000000000000..328e8231bcfe --- /dev/null +++ b/tests/test_pbh.py @@ -0,0 +1,797 @@ +import pytest +import logging + + +PBH_HASH_FIELD_NAME = "inner_ip_proto" +PBH_HASH_FIELD_HASH_FIELD = "INNER_IP_PROTOCOL" +PBH_HASH_FIELD_SEQUENCE_ID = "1" + +PBH_HASH_NAME = "inner_v4_hash" +PBH_HASH_HASH_FIELD_LIST = ["inner_ip_proto"] + +PBH_RULE_NAME = "nvgre" +PBH_RULE_PRIORITY = "1" +PBH_RULE_ETHER_TYPE = "0x0800" +PBH_RULE_IP_PROTOCOL = "0x2f" +PBH_RULE_GRE_KEY = "0x2500/0xffffff00" +PBH_RULE_INNER_ETHER_TYPE = "0x86dd" +PBH_RULE_HASH = "inner_v4_hash" + +PBH_TABLE_NAME = "pbh_table" +PBH_TABLE_INTERFACE_LIST = ["Ethernet0", "Ethernet4"] +PBH_TABLE_DESCRIPTION = "NVGRE and VxLAN" + + +logging.basicConfig(level=logging.INFO) +pbhlogger = logging.getLogger(__name__) + + +@pytest.fixture(autouse=True, scope="class") +def dvs_api(request, dvs_pbh, dvs_acl): + # Fixtures are created when first requested by a test, and are destroyed based on their scope + if request.cls is None: + yield + return + pbhlogger.info("Initialize DVS API: PBH, ACL") + request.cls.dvs_pbh = dvs_pbh + request.cls.dvs_acl = dvs_acl + yield + pbhlogger.info("Deinitialize DVS API: PBH, ACL") + del request.cls.dvs_pbh + del request.cls.dvs_acl + + +@pytest.mark.usefixtures("dvs_lag_manager") +class TestPbhInterfaceBinding: + def test_PbhTablePortBinding(self, testlog): + try: + port_list = ["Ethernet0", "Ethernet4"] + + pbhlogger.info("Create PBH table: {}".format(PBH_TABLE_NAME)) + self.dvs_pbh.create_pbh_table( + table_name=PBH_TABLE_NAME, + interface_list=port_list, + description=PBH_TABLE_DESCRIPTION + ) + self.dvs_acl.verify_acl_table_count(1) + + pbhlogger.info("Validate PBH table port binding: {}".format(",".join(port_list))) + acl_table_id = self.dvs_acl.get_acl_table_ids(1)[0] + acl_table_group_ids = self.dvs_acl.get_acl_table_group_ids(len(port_list)) + + self.dvs_acl.verify_acl_table_group_members(acl_table_id, acl_table_group_ids, 1) + self.dvs_acl.verify_acl_table_port_binding(acl_table_id, port_list, 1) + finally: + pbhlogger.info("Remove PBH table: {}".format(PBH_TABLE_NAME)) + self.dvs_pbh.remove_pbh_table(PBH_TABLE_NAME) + self.dvs_acl.verify_acl_table_count(0) + + def test_PbhTablePortChannelBinding(self, testlog): + try: + # PortChannel0001 + pbhlogger.info("Create LAG: PortChannel0001") + self.dvs_lag.create_port_channel("0001") + self.dvs_lag.get_and_verify_port_channel(1) + + pbhlogger.info("Create LAG member: Ethernet120") + self.dvs_lag.create_port_channel_member("0001", "Ethernet120") + self.dvs_lag.get_and_verify_port_channel_members(1) + + # PortChannel0002 + pbhlogger.info("Create LAG: PortChannel0002") + self.dvs_lag.create_port_channel("0002") + self.dvs_lag.get_and_verify_port_channel(2) + + pbhlogger.info("Create LAG member: Ethernet124") + self.dvs_lag.create_port_channel_member("0002", "Ethernet124") + self.dvs_lag.get_and_verify_port_channel_members(2) + + # PBH table + portchannel_list = ["PortChannel0001", "PortChannel0002"] + + pbhlogger.info("Create PBH table: {}".format(PBH_TABLE_NAME)) + self.dvs_pbh.create_pbh_table( + table_name=PBH_TABLE_NAME, + interface_list=portchannel_list, + description=PBH_TABLE_DESCRIPTION + ) + self.dvs_acl.verify_acl_table_count(1) + + pbhlogger.info("Validate PBH table LAG binding: {}".format(",".join(portchannel_list))) + acl_table_id = self.dvs_acl.get_acl_table_ids(1)[0] + acl_table_group_ids = self.dvs_acl.get_acl_table_group_ids(len(portchannel_list)) + + self.dvs_acl.verify_acl_table_group_members(acl_table_id, acl_table_group_ids, 1) + self.dvs_acl.verify_acl_table_portchannel_binding(acl_table_id, portchannel_list, 1) + finally: + # PBH table + pbhlogger.info("Remove PBH table: {}".format(PBH_TABLE_NAME)) + self.dvs_pbh.remove_pbh_table(PBH_TABLE_NAME) + self.dvs_acl.verify_acl_table_count(0) + + # PortChannel0001 + pbhlogger.info("Remove LAG member: Ethernet120") + self.dvs_lag.remove_port_channel_member("0001", "Ethernet120") + self.dvs_lag.get_and_verify_port_channel_members(1) + + pbhlogger.info("Remove LAG: PortChannel0001") + self.dvs_lag.remove_port_channel("0001") + self.dvs_lag.get_and_verify_port_channel(1) + + # PortChannel0002 + pbhlogger.info("Remove LAG member: Ethernet124") + self.dvs_lag.remove_port_channel_member("0002", "Ethernet124") + self.dvs_lag.get_and_verify_port_channel_members(0) + + pbhlogger.info("Remove LAG: PortChannel0002") + self.dvs_lag.remove_port_channel("0002") + self.dvs_lag.get_and_verify_port_channel(0) + + +class TestPbhBasicFlows: + def test_PbhHashFieldCreationDeletion(self, testlog): + try: + pbhlogger.info("Create PBH hash field: {}".format(PBH_HASH_FIELD_NAME)) + self.dvs_pbh.create_pbh_hash_field( + hash_field_name=PBH_HASH_FIELD_NAME, + hash_field=PBH_HASH_FIELD_HASH_FIELD, + sequence_id=PBH_HASH_FIELD_SEQUENCE_ID + ) + self.dvs_pbh.verify_pbh_hash_field_count(1) + finally: + pbhlogger.info("Remove PBH hash field: {}".format(PBH_HASH_FIELD_NAME)) + self.dvs_pbh.remove_pbh_hash_field(PBH_HASH_FIELD_NAME) + self.dvs_pbh.verify_pbh_hash_field_count(0) + + def test_PbhHashCreationDeletion(self, testlog): + try: + # PBH hash field + pbhlogger.info("Create PBH hash field: {}".format(PBH_HASH_FIELD_NAME)) + self.dvs_pbh.create_pbh_hash_field( + hash_field_name=PBH_HASH_FIELD_NAME, + hash_field=PBH_HASH_FIELD_HASH_FIELD, + sequence_id=PBH_HASH_FIELD_SEQUENCE_ID + ) + self.dvs_pbh.verify_pbh_hash_field_count(1) + + # PBH hash + pbhlogger.info("Create PBH hash: {}".format(PBH_HASH_NAME)) + self.dvs_pbh.create_pbh_hash( + hash_name=PBH_HASH_NAME, + hash_field_list=PBH_HASH_HASH_FIELD_LIST + ) + self.dvs_pbh.verify_pbh_hash_count(1) + finally: + # PBH hash + pbhlogger.info("Remove PBH hash: {}".format(PBH_HASH_NAME)) + self.dvs_pbh.remove_pbh_hash(PBH_HASH_NAME) + self.dvs_pbh.verify_pbh_hash_count(0) + + # PBH hash field + pbhlogger.info("Remove PBH hash field: {}".format(PBH_HASH_FIELD_NAME)) + self.dvs_pbh.remove_pbh_hash_field(PBH_HASH_FIELD_NAME) + self.dvs_pbh.verify_pbh_hash_field_count(0) + + def test_PbhTableCreationDeletion(self, testlog): + try: + pbhlogger.info("Create PBH table: {}".format(PBH_TABLE_NAME)) + self.dvs_pbh.create_pbh_table( + table_name=PBH_TABLE_NAME, + interface_list=PBH_TABLE_INTERFACE_LIST, + description=PBH_TABLE_DESCRIPTION + ) + self.dvs_acl.verify_acl_table_count(1) + finally: + pbhlogger.info("Remove PBH table: {}".format(PBH_TABLE_NAME)) + self.dvs_pbh.remove_pbh_table(PBH_TABLE_NAME) + self.dvs_acl.verify_acl_table_count(0) + + def test_PbhRuleCreationDeletion(self, testlog): + try: + # PBH hash field + pbhlogger.info("Create PBH hash field: {}".format(PBH_HASH_FIELD_NAME)) + self.dvs_pbh.create_pbh_hash_field( + hash_field_name=PBH_HASH_FIELD_NAME, + hash_field=PBH_HASH_FIELD_HASH_FIELD, + sequence_id=PBH_HASH_FIELD_SEQUENCE_ID + ) + self.dvs_pbh.verify_pbh_hash_field_count(1) + + # PBH hash + pbhlogger.info("Create PBH hash: {}".format(PBH_HASH_NAME)) + self.dvs_pbh.create_pbh_hash( + hash_name=PBH_HASH_NAME, + hash_field_list=PBH_HASH_HASH_FIELD_LIST + ) + self.dvs_pbh.verify_pbh_hash_count(1) + + # PBH table + pbhlogger.info("Create PBH table: {}".format(PBH_TABLE_NAME)) + self.dvs_pbh.create_pbh_table( + table_name=PBH_TABLE_NAME, + interface_list=PBH_TABLE_INTERFACE_LIST, + description=PBH_TABLE_DESCRIPTION + ) + self.dvs_acl.verify_acl_table_count(1) + + # PBH rule + attr_dict = { + "ether_type": PBH_RULE_ETHER_TYPE, + "ip_protocol": PBH_RULE_IP_PROTOCOL, + "gre_key": PBH_RULE_GRE_KEY, + "inner_ether_type": PBH_RULE_INNER_ETHER_TYPE + } + + pbhlogger.info("Create PBH rule: {}".format(PBH_RULE_NAME)) + self.dvs_pbh.create_pbh_rule( + table_name=PBH_TABLE_NAME, + rule_name=PBH_RULE_NAME, + priority=PBH_RULE_PRIORITY, + qualifiers=attr_dict, + hash_name=PBH_RULE_HASH + ) + self.dvs_acl.verify_acl_rule_count(1) + finally: + # PBH rule + pbhlogger.info("Remove PBH rule: {}".format(PBH_RULE_NAME)) + self.dvs_pbh.remove_pbh_rule(PBH_TABLE_NAME, PBH_RULE_NAME) + self.dvs_acl.verify_acl_rule_count(0) + + # PBH table + pbhlogger.info("Remove PBH table: {}".format(PBH_TABLE_NAME)) + self.dvs_pbh.remove_pbh_table(PBH_TABLE_NAME) + self.dvs_acl.verify_acl_table_count(0) + + # PBH hash + pbhlogger.info("Remove PBH hash: {}".format(PBH_HASH_NAME)) + self.dvs_pbh.remove_pbh_hash(PBH_HASH_NAME) + self.dvs_pbh.verify_pbh_hash_count(0) + + # PBH hash field + pbhlogger.info("Remove PBH hash field: {}".format(PBH_HASH_FIELD_NAME)) + self.dvs_pbh.remove_pbh_hash_field(PBH_HASH_FIELD_NAME) + self.dvs_pbh.verify_pbh_hash_field_count(0) + + +@pytest.mark.usefixtures("dvs_lag_manager") +class TestPbhExtendedFlows: + class PbhRefCountHelper(object): + def __init__(self): + self.hashFieldCount = 0 + self.hashCount = 0 + self.ruleCount = 0 + self.tableCount = 0 + + def incPbhHashFieldCount(self): + self.hashFieldCount += 1 + + def decPbhHashFieldCount(self): + self.hashFieldCount -= 1 + + def getPbhHashFieldCount(self): + return self.hashFieldCount + + def incPbhHashCount(self): + self.hashCount += 1 + + def decPbhHashCount(self): + self.hashCount -= 1 + + def getPbhHashCount(self): + return self.hashCount + + def incPbhRuleCount(self): + self.ruleCount += 1 + + def decPbhRuleCount(self): + self.ruleCount -= 1 + + def getPbhRuleCount(self): + return self.ruleCount + + def incPbhTableCount(self): + self.tableCount += 1 + + def decPbhTableCount(self): + self.tableCount -= 1 + + def getPbhTableCount(self): + return self.tableCount + + class LagRefCountHelper(object): + def __init__(self): + self.lagCount = 0 + self.lagMemberCount = 0 + + def incLagCount(self): + self.lagCount += 1 + + def decLagCount(self): + self.lagCount -= 1 + + def getLagCount(self): + return self.lagCount + + def incLagMemberCount(self): + self.lagMemberCount += 1 + + def decLagMemberCount(self): + self.lagMemberCount -= 1 + + def getLagMemberCount(self): + return self.lagMemberCount + + def strip_prefix(self, s, p): + return s[len(p):] if s.startswith(p) else s + + def create_basic_lag(self, meta_dict, lag_ref_count): + lag_id = self.strip_prefix(meta_dict["name"], "PortChannel") + + pbhlogger.info("Create LAG: {}".format(meta_dict["name"])) + self.dvs_lag.create_port_channel(lag_id) + lag_ref_count.incLagCount() + self.dvs_lag.get_and_verify_port_channel(lag_ref_count.getLagCount()) + + pbhlogger.info("Create LAG member: {}".format(meta_dict["member"])) + self.dvs_lag.create_port_channel_member(lag_id, meta_dict["member"]) + lag_ref_count.incLagMemberCount() + self.dvs_lag.get_and_verify_port_channel_members(lag_ref_count.getLagMemberCount()) + + def remove_basic_lag(self, meta_dict, lag_ref_count): + lag_id = self.strip_prefix(meta_dict["name"], "PortChannel") + + pbhlogger.info("Remove LAG member: {}".format(meta_dict["member"])) + self.dvs_lag.remove_port_channel_member(lag_id, meta_dict["member"]) + lag_ref_count.decLagMemberCount() + self.dvs_lag.get_and_verify_port_channel_members(lag_ref_count.getLagMemberCount()) + + pbhlogger.info("Remove LAG: {}".format(meta_dict["name"])) + self.dvs_lag.remove_port_channel(lag_id) + lag_ref_count.decLagCount() + self.dvs_lag.get_and_verify_port_channel(lag_ref_count.getLagCount()) + + def create_hash_field(self, meta_dict, pbh_ref_count): + pbhlogger.info("Create PBH hash field: {}".format(meta_dict["name"])) + self.dvs_pbh.create_pbh_hash_field( + hash_field_name=meta_dict["name"], + hash_field=meta_dict["hash_field"], + ip_mask=meta_dict["ip_mask"] if "ip_mask" in meta_dict else None, + sequence_id=meta_dict["sequence_id"] + ) + pbh_ref_count.incPbhHashFieldCount() + self.dvs_pbh.verify_pbh_hash_field_count(pbh_ref_count.getPbhHashFieldCount()) + + def remove_hash_field(self, meta_dict, pbh_ref_count): + pbhlogger.info("Remove PBH hash field: {}".format(meta_dict["name"])) + self.dvs_pbh.remove_pbh_hash_field(meta_dict["name"]) + pbh_ref_count.decPbhHashFieldCount() + self.dvs_pbh.verify_pbh_hash_field_count(pbh_ref_count.getPbhHashFieldCount()) + + def create_hash(self, meta_dict, pbh_ref_count): + pbhlogger.info("Create PBH hash: {}".format(meta_dict["name"])) + self.dvs_pbh.create_pbh_hash( + hash_name=meta_dict["name"], + hash_field_list=meta_dict["hash_field_list"] + ) + pbh_ref_count.incPbhHashCount() + self.dvs_pbh.verify_pbh_hash_count(pbh_ref_count.getPbhHashCount()) + + def remove_hash(self, meta_dict, pbh_ref_count): + pbhlogger.info("Remove PBH hash: {}".format(meta_dict["name"])) + self.dvs_pbh.remove_pbh_hash(meta_dict["name"]) + pbh_ref_count.decPbhHashCount() + self.dvs_pbh.verify_pbh_hash_count(pbh_ref_count.getPbhHashCount()) + + def create_table(self, meta_dict, pbh_ref_count): + pbhlogger.info("Create PBH table: {}".format(meta_dict["name"])) + self.dvs_pbh.create_pbh_table( + table_name=meta_dict["name"], + interface_list=meta_dict["interface_list"], + description=meta_dict["description"] + ) + pbh_ref_count.incPbhTableCount() + self.dvs_acl.verify_acl_table_count(pbh_ref_count.getPbhTableCount()) + + def remove_table(self, meta_dict, pbh_ref_count): + pbhlogger.info("Remove PBH table: {}".format(meta_dict["name"])) + self.dvs_pbh.remove_pbh_table(meta_dict["name"]) + pbh_ref_count.decPbhTableCount() + self.dvs_acl.verify_acl_table_count(pbh_ref_count.getPbhTableCount()) + + def create_rule(self, meta_dict, attr_dict, pbh_ref_count): + pbhlogger.info("Create PBH rule: {}".format(meta_dict["name"])) + self.dvs_pbh.create_pbh_rule( + table_name=meta_dict["table"], + rule_name=meta_dict["name"], + priority=meta_dict["priority"], + qualifiers=attr_dict, + hash_name=meta_dict["hash"], + packet_action=meta_dict["packet_action"] if "packet_action" in meta_dict else None, + flow_counter=meta_dict["flow_counter"] if "flow_counter" in meta_dict else None + ) + pbh_ref_count.incPbhRuleCount() + self.dvs_acl.verify_acl_rule_count(pbh_ref_count.getPbhRuleCount()) + + def remove_rule(self, meta_dict, pbh_ref_count): + pbhlogger.info("Remove PBH rule: {}".format(meta_dict["name"])) + self.dvs_pbh.remove_pbh_rule(meta_dict["table"], meta_dict["name"]) + pbh_ref_count.decPbhRuleCount() + self.dvs_acl.verify_acl_rule_count(pbh_ref_count.getPbhRuleCount()) + + @pytest.fixture(autouse=True) + def pbh_ref_count(self): + pbhlogger.info("Create PBH reference count helper") + yield self.PbhRefCountHelper() + pbhlogger.info("Remove PBH reference count helper") + + @pytest.fixture(autouse=True) + def lag_ref_count(self): + pbhlogger.info("Create LAG reference count helper") + yield self.LagRefCountHelper() + pbhlogger.info("Remove LAG reference count helper") + + @pytest.fixture(autouse=True) + def pbh_port_channel_0001(self, lag_ref_count): + try: + meta_dict = { + "name": "PortChannel0001", + "member": "Ethernet120" + } + self.create_basic_lag(meta_dict, lag_ref_count) + yield meta_dict + finally: + self.remove_basic_lag(meta_dict, lag_ref_count) + + @pytest.fixture(autouse=True) + def pbh_port_channel_0002(self, lag_ref_count): + try: + meta_dict = { + "name": "PortChannel0002", + "member": "Ethernet124" + } + self.create_basic_lag(meta_dict, lag_ref_count) + yield meta_dict + finally: + self.remove_basic_lag(meta_dict, lag_ref_count) + + @pytest.fixture + def pbh_inner_ip_proto(self, pbh_ref_count): + try: + meta_dict = { + "name": "inner_ip_proto", + "hash_field": "INNER_IP_PROTOCOL", + "sequence_id": "1" + } + self.create_hash_field(meta_dict, pbh_ref_count) + yield meta_dict + finally: + self.remove_hash_field(meta_dict, pbh_ref_count) + + @pytest.fixture + def pbh_inner_l4_dst_port(self, pbh_ref_count): + try: + meta_dict = { + "name": "inner_l4_dst_port", + "hash_field": "INNER_L4_DST_PORT", + "sequence_id": "2" + } + self.create_hash_field(meta_dict, pbh_ref_count) + yield meta_dict + finally: + self.remove_hash_field(meta_dict, pbh_ref_count) + + @pytest.fixture + def pbh_inner_l4_src_port(self, pbh_ref_count): + try: + meta_dict = { + "name": "inner_l4_src_port", + "hash_field": "INNER_L4_SRC_PORT", + "sequence_id": "2" + } + self.create_hash_field(meta_dict, pbh_ref_count) + yield meta_dict + finally: + self.remove_hash_field(meta_dict, pbh_ref_count) + + @pytest.fixture + def pbh_inner_dst_ipv4(self, pbh_ref_count): + try: + meta_dict = { + "name": "inner_dst_ipv4", + "hash_field": "INNER_DST_IPV4", + "ip_mask": "255.0.0.0", + "sequence_id": "3" + } + self.create_hash_field(meta_dict, pbh_ref_count) + yield meta_dict + finally: + self.remove_hash_field(meta_dict, pbh_ref_count) + + @pytest.fixture + def pbh_inner_src_ipv4(self, pbh_ref_count): + try: + meta_dict = { + "name": "inner_src_ipv4", + "hash_field": "INNER_SRC_IPV4", + "ip_mask": "0.0.0.255", + "sequence_id": "3" + } + self.create_hash_field(meta_dict, pbh_ref_count) + yield meta_dict + finally: + self.remove_hash_field(meta_dict, pbh_ref_count) + + @pytest.fixture + def pbh_inner_dst_ipv6(self, pbh_ref_count): + try: + meta_dict = { + "name": "inner_dst_ipv6", + "hash_field": "INNER_DST_IPV6", + "ip_mask": "ffff::", + "sequence_id": "4" + } + self.create_hash_field(meta_dict, pbh_ref_count) + yield meta_dict + finally: + self.remove_hash_field(meta_dict, pbh_ref_count) + + @pytest.fixture + def pbh_inner_src_ipv6(self, pbh_ref_count): + try: + meta_dict = { + "name": "inner_src_ipv6", + "hash_field": "INNER_SRC_IPV6", + "ip_mask": "::ffff", + "sequence_id": "4" + } + self.create_hash_field(meta_dict, pbh_ref_count) + yield meta_dict + finally: + self.remove_hash_field(meta_dict, pbh_ref_count) + + @pytest.fixture + def pbh_inner_v4( + self, + pbh_ref_count, + pbh_inner_ip_proto, + pbh_inner_l4_dst_port, + pbh_inner_l4_src_port, + pbh_inner_dst_ipv4, + pbh_inner_src_ipv4 + ): + try: + meta_dict = { + "name": "inner_v4_hash", + "hash_field_list": [ + pbh_inner_ip_proto["name"], + pbh_inner_l4_dst_port["name"], + pbh_inner_l4_src_port["name"], + pbh_inner_dst_ipv4["name"], + pbh_inner_src_ipv4["name"] + ] + } + self.create_hash(meta_dict, pbh_ref_count) + yield meta_dict + finally: + self.remove_hash(meta_dict, pbh_ref_count) + + @pytest.fixture + def pbh_inner_v6( + self, + pbh_ref_count, + pbh_inner_ip_proto, + pbh_inner_l4_dst_port, + pbh_inner_l4_src_port, + pbh_inner_dst_ipv6, + pbh_inner_src_ipv6 + ): + try: + meta_dict = { + "name": "inner_v6_hash", + "hash_field_list": [ + pbh_inner_ip_proto["name"], + pbh_inner_l4_dst_port["name"], + pbh_inner_l4_src_port["name"], + pbh_inner_dst_ipv6["name"], + pbh_inner_src_ipv6["name"] + ] + } + self.create_hash(meta_dict, pbh_ref_count) + yield meta_dict + finally: + self.remove_hash(meta_dict, pbh_ref_count) + + @pytest.fixture + def pbh_table( + self, + pbh_ref_count, + pbh_port_channel_0001, + pbh_port_channel_0002 + ): + try: + meta_dict = { + "name": "pbh_table", + "interface_list": [ + "Ethernet0", + "Ethernet4", + pbh_port_channel_0001["name"], + pbh_port_channel_0002["name"] + ], + "description": "NVGRE and VxLAN" + } + self.create_table(meta_dict, pbh_ref_count) + yield meta_dict + finally: + self.remove_table(meta_dict, pbh_ref_count) + + @pytest.fixture + def pbh_nvgre( + self, + pbh_ref_count, + pbh_table, + pbh_inner_v6 + ): + try: + meta_dict = { + "table": pbh_table["name"], + "name": "nvgre", + "priority": "1", + "ether_type": "0x0800", + "ip_protocol": "0x2f", + "gre_key": "0x2500/0xffffff00", + "inner_ether_type": "0x86dd", + "hash": pbh_inner_v6["name"], + "packet_action": "SET_ECMP_HASH", + "flow_counter": "DISABLED" + } + attr_dict = { + "gre_key": meta_dict["gre_key"], + "inner_ether_type": meta_dict["inner_ether_type"] + } + self.create_rule(meta_dict, attr_dict, pbh_ref_count) + yield meta_dict + finally: + self.remove_rule(meta_dict, pbh_ref_count) + + @pytest.fixture + def pbh_vxlan( + self, + pbh_ref_count, + pbh_table, + pbh_inner_v4 + ): + try: + meta_dict = { + "table": pbh_table["name"], + "name": "vxlan", + "priority": "2", + "ether_type": "0x0800", + "ip_protocol": "0x11", + "l4_dst_port": "0x12b5", + "inner_ether_type": "0x0800", + "hash": pbh_inner_v4["name"], + "packet_action": "SET_LAG_HASH", + "flow_counter": "ENABLED" + } + attr_dict = { + "ip_protocol": meta_dict["ip_protocol"], + "l4_dst_port": meta_dict["l4_dst_port"], + "inner_ether_type": meta_dict["inner_ether_type"] + } + self.create_rule(meta_dict, attr_dict, pbh_ref_count) + yield meta_dict + finally: + self.remove_rule(meta_dict, pbh_ref_count) + + def test_PbhNvgreVxlanConfiguration(self, testlog, pbh_nvgre, pbh_vxlan): + pass + + +class TestPbhDependencyFlows: + def test_PbhHashCreationDeletionWithDependencies(self, testlog): + try: + # PBH hash + pbhlogger.info("Create PBH hash: {}".format(PBH_HASH_NAME)) + self.dvs_pbh.create_pbh_hash( + hash_name=PBH_HASH_NAME, + hash_field_list=PBH_HASH_HASH_FIELD_LIST + ) + self.dvs_pbh.verify_pbh_hash_count(0) + + # PBH hash field + pbhlogger.info("Create PBH hash field: {}".format(PBH_HASH_FIELD_NAME)) + self.dvs_pbh.create_pbh_hash_field( + hash_field_name=PBH_HASH_FIELD_NAME, + hash_field=PBH_HASH_FIELD_HASH_FIELD, + sequence_id=PBH_HASH_FIELD_SEQUENCE_ID + ) + self.dvs_pbh.verify_pbh_hash_field_count(1) + self.dvs_pbh.verify_pbh_hash_count(1) + finally: + # PBH hash field + pbhlogger.info("Remove PBH hash field: {}".format(PBH_HASH_FIELD_NAME)) + self.dvs_pbh.remove_pbh_hash_field(PBH_HASH_FIELD_NAME) + self.dvs_pbh.verify_pbh_hash_field_count(1) + + # PBH hash + pbhlogger.info("Remove PBH hash: {}".format(PBH_HASH_NAME)) + self.dvs_pbh.remove_pbh_hash(PBH_HASH_NAME) + self.dvs_pbh.verify_pbh_hash_count(0) + self.dvs_pbh.verify_pbh_hash_field_count(0) + + def test_PbhRuleCreationDeletionWithDependencies(self, testlog): + try: + # PBH hash + pbhlogger.info("Create PBH hash: {}".format(PBH_HASH_NAME)) + self.dvs_pbh.create_pbh_hash( + hash_name=PBH_HASH_NAME, + hash_field_list=PBH_HASH_HASH_FIELD_LIST + ) + self.dvs_pbh.verify_pbh_hash_count(0) + + # PBH hash field + pbhlogger.info("Create PBH hash field: {}".format(PBH_HASH_FIELD_NAME)) + self.dvs_pbh.create_pbh_hash_field( + hash_field_name=PBH_HASH_FIELD_NAME, + hash_field=PBH_HASH_FIELD_HASH_FIELD, + sequence_id=PBH_HASH_FIELD_SEQUENCE_ID + ) + self.dvs_pbh.verify_pbh_hash_field_count(1) + self.dvs_pbh.verify_pbh_hash_count(1) + + # PBH rule + attr_dict = { + "ether_type": PBH_RULE_ETHER_TYPE, + "ip_protocol": PBH_RULE_IP_PROTOCOL, + "gre_key": PBH_RULE_GRE_KEY, + "inner_ether_type": PBH_RULE_INNER_ETHER_TYPE + } + + pbhlogger.info("Create PBH rule: {}".format(PBH_RULE_NAME)) + self.dvs_pbh.create_pbh_rule( + table_name=PBH_TABLE_NAME, + rule_name=PBH_RULE_NAME, + priority=PBH_RULE_PRIORITY, + qualifiers=attr_dict, + hash_name=PBH_RULE_HASH + ) + self.dvs_acl.verify_acl_rule_count(0) + + # PBH table + pbhlogger.info("Create PBH table: {}".format(PBH_TABLE_NAME)) + self.dvs_pbh.create_pbh_table( + table_name=PBH_TABLE_NAME, + interface_list=PBH_TABLE_INTERFACE_LIST, + description=PBH_TABLE_DESCRIPTION + ) + self.dvs_acl.verify_acl_table_count(1) + self.dvs_acl.verify_acl_rule_count(1) + + finally: + # PBH table + pbhlogger.info("Remove PBH table: {}".format(PBH_TABLE_NAME)) + self.dvs_pbh.remove_pbh_table(PBH_TABLE_NAME) + self.dvs_acl.verify_acl_table_count(1) + + # PBH rule + pbhlogger.info("Remove PBH rule: {}".format(PBH_RULE_NAME)) + self.dvs_pbh.remove_pbh_rule(PBH_TABLE_NAME, PBH_RULE_NAME) + self.dvs_acl.verify_acl_rule_count(0) + self.dvs_acl.verify_acl_table_count(0) + + # PBH hash field + pbhlogger.info("Remove PBH hash field: {}".format(PBH_HASH_FIELD_NAME)) + self.dvs_pbh.remove_pbh_hash_field(PBH_HASH_FIELD_NAME) + self.dvs_pbh.verify_pbh_hash_field_count(1) + + # PBH hash + pbhlogger.info("Remove PBH hash: {}".format(PBH_HASH_NAME)) + self.dvs_pbh.remove_pbh_hash(PBH_HASH_NAME) + self.dvs_pbh.verify_pbh_hash_count(0) + self.dvs_pbh.verify_pbh_hash_field_count(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