diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index b20f780d832..606619a611b 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -1,6 +1,7 @@ INCLUDES = -I $(top_srcdir)/lib \ -I $(top_srcdir) \ -I $(top_srcdir)/warmrestart \ + -I switch \ -I flex_counter \ -I debug_counter \ -I pbh \ @@ -73,6 +74,8 @@ orchagent_SOURCES = \ pbhorch.cpp \ saihelper.cpp \ saiattr.cpp \ + switch/switch_capabilities.cpp \ + switch/switch_helper.cpp \ switchorch.cpp \ pfcwdorch.cpp \ pfcactionhandler.cpp \ diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 5d536348600..79c9e625fe9 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -113,11 +113,13 @@ bool OrchDaemon::init() gCrmOrch = new CrmOrch(m_configDb, CFG_CRM_TABLE_NAME); - TableConnector stateDbSwitchTable(m_stateDb, "SWITCH_CAPABILITY"); + TableConnector stateDbSwitchTable(m_stateDb, STATE_SWITCH_CAPABILITY_TABLE_NAME); TableConnector app_switch_table(m_applDb, APP_SWITCH_TABLE_NAME); TableConnector conf_asic_sensors(m_configDb, CFG_ASIC_SENSORS_TABLE_NAME); + TableConnector conf_switch_hash(m_configDb, CFG_SWITCH_HASH_TABLE_NAME); vector switch_tables = { + conf_switch_hash, conf_asic_sensors, app_switch_table }; diff --git a/orchagent/p4orch/tests/Makefile.am b/orchagent/p4orch/tests/Makefile.am index d759033c68a..34fd93f59b5 100644 --- a/orchagent/p4orch/tests/Makefile.am +++ b/orchagent/p4orch/tests/Makefile.am @@ -26,6 +26,8 @@ p4orch_tests_SOURCES = $(ORCHAGENT_DIR)/orch.cpp \ $(ORCHAGENT_DIR)/vrforch.cpp \ $(ORCHAGENT_DIR)/vxlanorch.cpp \ $(ORCHAGENT_DIR)/copporch.cpp \ + $(ORCHAGENT_DIR)/switch/switch_capabilities.cpp \ + $(ORCHAGENT_DIR)/switch/switch_helper.cpp \ $(ORCHAGENT_DIR)/switchorch.cpp \ $(ORCHAGENT_DIR)/request_parser.cpp \ $(ORCHAGENT_DIR)/flex_counter/flex_counter_manager.cpp \ diff --git a/orchagent/p4orch/tests/test_main.cpp b/orchagent/p4orch/tests/test_main.cpp index 08b72a9377f..e76104ce45e 100644 --- a/orchagent/p4orch/tests/test_main.cpp +++ b/orchagent/p4orch/tests/test_main.cpp @@ -72,6 +72,7 @@ sai_acl_api_t *sai_acl_api; sai_policer_api_t *sai_policer_api; sai_virtual_router_api_t *sai_virtual_router_api; sai_hostif_api_t *sai_hostif_api; +sai_hash_api_t *sai_hash_api; sai_switch_api_t *sai_switch_api; sai_mirror_api_t *sai_mirror_api; sai_udf_api_t *sai_udf_api; @@ -191,6 +192,7 @@ int main(int argc, char *argv[]) sai_policer_api_t policer_api; sai_virtual_router_api_t virtual_router_api; sai_hostif_api_t hostif_api; + sai_hash_api_t hash_api; sai_switch_api_t switch_api; sai_mirror_api_t mirror_api; sai_udf_api_t udf_api; @@ -207,6 +209,7 @@ int main(int argc, char *argv[]) sai_policer_api = &policer_api; sai_virtual_router_api = &virtual_router_api; sai_hostif_api = &hostif_api; + sai_hash_api = &hash_api; sai_switch_api = &switch_api; sai_mirror_api = &mirror_api; sai_udf_api = &udf_api; diff --git a/orchagent/switch/switch_capabilities.cpp b/orchagent/switch/switch_capabilities.cpp new file mode 100644 index 00000000000..d826a9e49f2 --- /dev/null +++ b/orchagent/switch/switch_capabilities.cpp @@ -0,0 +1,354 @@ +// includes ----------------------------------------------------------------------------------------------------------- + +extern "C" { +#include +#include +#include +#include +} + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "switch_schema.h" +#include "switch_capabilities.h" + +using namespace swss; + +// defines ------------------------------------------------------------------------------------------------------------ + +#define SWITCH_CAPABILITY_HASH_NATIVE_HASH_FIELD_LIST_FIELD "HASH|NATIVE_HASH_FIELD_LIST" +#define SWITCH_CAPABILITY_ECMP_HASH_CAPABLE_FIELD "ECMP_HASH_CAPABLE" +#define SWITCH_CAPABILITY_LAG_HASH_CAPABLE_FIELD "LAG_HASH_CAPABLE" + +#define SWITCH_CAPABILITY_KEY "switch" + +#define SWITCH_STATE_DB_NAME "STATE_DB" +#define SWITCH_STATE_DB_TIMEOUT 0 + +// constants ---------------------------------------------------------------------------------------------------------- + +static const std::unordered_map swHashHashFieldMap = +{ + { SAI_NATIVE_HASH_FIELD_IN_PORT, SWITCH_HASH_FIELD_IN_PORT }, + { SAI_NATIVE_HASH_FIELD_DST_MAC, SWITCH_HASH_FIELD_DST_MAC }, + { SAI_NATIVE_HASH_FIELD_SRC_MAC, SWITCH_HASH_FIELD_SRC_MAC }, + { SAI_NATIVE_HASH_FIELD_ETHERTYPE, SWITCH_HASH_FIELD_ETHERTYPE }, + { SAI_NATIVE_HASH_FIELD_VLAN_ID, SWITCH_HASH_FIELD_VLAN_ID }, + { SAI_NATIVE_HASH_FIELD_IP_PROTOCOL, SWITCH_HASH_FIELD_IP_PROTOCOL }, + { SAI_NATIVE_HASH_FIELD_DST_IP, SWITCH_HASH_FIELD_DST_IP }, + { SAI_NATIVE_HASH_FIELD_SRC_IP, SWITCH_HASH_FIELD_SRC_IP }, + { SAI_NATIVE_HASH_FIELD_L4_DST_PORT, SWITCH_HASH_FIELD_L4_DST_PORT }, + { SAI_NATIVE_HASH_FIELD_L4_SRC_PORT, SWITCH_HASH_FIELD_L4_SRC_PORT }, + { SAI_NATIVE_HASH_FIELD_INNER_DST_MAC, SWITCH_HASH_FIELD_INNER_DST_MAC }, + { SAI_NATIVE_HASH_FIELD_INNER_SRC_MAC, SWITCH_HASH_FIELD_INNER_SRC_MAC }, + { SAI_NATIVE_HASH_FIELD_INNER_ETHERTYPE, SWITCH_HASH_FIELD_INNER_ETHERTYPE }, + { SAI_NATIVE_HASH_FIELD_INNER_IP_PROTOCOL, SWITCH_HASH_FIELD_INNER_IP_PROTOCOL }, + { SAI_NATIVE_HASH_FIELD_INNER_DST_IP, SWITCH_HASH_FIELD_INNER_DST_IP }, + { SAI_NATIVE_HASH_FIELD_INNER_SRC_IP, SWITCH_HASH_FIELD_INNER_SRC_IP }, + { SAI_NATIVE_HASH_FIELD_INNER_L4_DST_PORT, SWITCH_HASH_FIELD_INNER_L4_DST_PORT }, + { SAI_NATIVE_HASH_FIELD_INNER_L4_SRC_PORT, SWITCH_HASH_FIELD_INNER_L4_SRC_PORT } +}; + +// variables ---------------------------------------------------------------------------------------------------------- + +extern sai_object_id_t gSwitchId; + +// functions ---------------------------------------------------------------------------------------------------------- + +static std::string toStr(sai_object_type_t objType, sai_attr_id_t attrId) noexcept +{ + const auto *meta = sai_metadata_get_attr_metadata(objType, attrId); + + return meta != nullptr ? meta->attridname : "UNKNOWN"; +} + +static std::string toStr(const std::set &value) noexcept +{ + std::vector strList; + + for (const auto &cit1 : value) + { + const auto &cit2 = swHashHashFieldMap.find(cit1); + if (cit2 != swHashHashFieldMap.cend()) + { + strList.push_back(cit2->second); + } + } + + return join(",", strList.cbegin(), strList.cend()); +} + +static std::string toStr(bool value) noexcept +{ + return value ? "true" : "false"; +} + +// Switch capabilities ------------------------------------------------------------------------------------------------ + +DBConnector SwitchCapabilities::stateDb(SWITCH_STATE_DB_NAME, SWITCH_STATE_DB_TIMEOUT); +Table SwitchCapabilities::capTable(&stateDb, STATE_SWITCH_CAPABILITY_TABLE_NAME); + +SwitchCapabilities::SwitchCapabilities() +{ + queryHashCapabilities(); + querySwitchCapabilities(); + + writeHashCapabilitiesToDb(); + writeSwitchCapabilitiesToDb(); +} + +bool SwitchCapabilities::isSwitchEcmpHashSupported() const +{ + const auto &nativeHashFieldList = hashCapabilities.nativeHashFieldList; + const auto &ecmpHash = switchCapabilities.ecmpHash; + + return nativeHashFieldList.isAttrSupported && ecmpHash.isAttrSupported; +} + +bool SwitchCapabilities::isSwitchLagHashSupported() const +{ + const auto &nativeHashFieldList = hashCapabilities.nativeHashFieldList; + const auto &lagHash = switchCapabilities.lagHash; + + return nativeHashFieldList.isAttrSupported && lagHash.isAttrSupported; +} + +bool SwitchCapabilities::validateSwitchHashFieldCap(const std::set &hfSet) const +{ + if (!hashCapabilities.nativeHashFieldList.isEnumSupported) + { + return true; + } + + if (hashCapabilities.nativeHashFieldList.hfSet.empty()) + { + SWSS_LOG_ERROR("Failed to validate hash field: no hash field capabilities"); + return false; + } + + for (const auto &cit : hfSet) + { + if (hashCapabilities.nativeHashFieldList.hfSet.count(cit) == 0) + { + SWSS_LOG_ERROR("Failed to validate hash field: value(%s) is not supported"); + return false; + } + } + + return true; +} + +FieldValueTuple SwitchCapabilities::makeHashFieldCapDbEntry() const +{ + const auto &nativeHashFieldList = hashCapabilities.nativeHashFieldList; + + auto field = SWITCH_CAPABILITY_HASH_NATIVE_HASH_FIELD_LIST_FIELD; + auto value = nativeHashFieldList.isEnumSupported ? toStr(nativeHashFieldList.hfSet) : "N/A"; + + return FieldValueTuple(field, value); +} + +FieldValueTuple SwitchCapabilities::makeEcmpHashCapDbEntry() const +{ + auto field = SWITCH_CAPABILITY_ECMP_HASH_CAPABLE_FIELD; + auto value = toStr(isSwitchEcmpHashSupported()); + + return FieldValueTuple(field, value); +} + +FieldValueTuple SwitchCapabilities::makeLagHashCapDbEntry() const +{ + auto field = SWITCH_CAPABILITY_LAG_HASH_CAPABLE_FIELD; + auto value = toStr(isSwitchLagHashSupported()); + + return FieldValueTuple(field, value); +} + +sai_status_t SwitchCapabilities::queryEnumCapabilitiesSai(std::vector &capList, sai_object_type_t objType, sai_attr_id_t attrId) const +{ + sai_s32_list_t enumList = { .count = 0, .list = nullptr }; + + auto status = sai_query_attribute_enum_values_capability(gSwitchId, objType, attrId, &enumList); + if ((status != SAI_STATUS_SUCCESS) && (status != SAI_STATUS_BUFFER_OVERFLOW)) + { + return status; + } + + capList.resize(enumList.count); + enumList.list = capList.data(); + + return sai_query_attribute_enum_values_capability(gSwitchId, objType, attrId, &enumList); +} + +sai_status_t SwitchCapabilities::queryAttrCapabilitiesSai(sai_attr_capability_t &attrCap, sai_object_type_t objType, sai_attr_id_t attrId) const +{ + return sai_query_attribute_capability(gSwitchId, objType, attrId, &attrCap); +} + +void SwitchCapabilities::queryHashNativeHashFieldListEnumCapabilities() +{ + SWSS_LOG_ENTER(); + + std::vector hfList; + auto status = queryEnumCapabilitiesSai( + hfList, SAI_OBJECT_TYPE_HASH, SAI_HASH_ATTR_NATIVE_HASH_FIELD_LIST + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) enum value capabilities", + toStr(SAI_OBJECT_TYPE_HASH, SAI_HASH_ATTR_NATIVE_HASH_FIELD_LIST).c_str() + ); + return; + } + + auto &hfSet = hashCapabilities.nativeHashFieldList.hfSet; + std::transform( + hfList.cbegin(), hfList.cend(), std::inserter(hfSet, hfSet.begin()), + [](sai_int32_t value) { return static_cast(value); } + ); + + hashCapabilities.nativeHashFieldList.isEnumSupported = true; +} + +void SwitchCapabilities::queryHashNativeHashFieldListAttrCapabilities() +{ + SWSS_LOG_ENTER(); + + sai_attr_capability_t attrCap; + + auto status = queryAttrCapabilitiesSai( + attrCap, SAI_OBJECT_TYPE_HASH, SAI_HASH_ATTR_NATIVE_HASH_FIELD_LIST + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) capabilities", + toStr(SAI_OBJECT_TYPE_HASH, SAI_HASH_ATTR_NATIVE_HASH_FIELD_LIST).c_str() + ); + return; + } + + if (!attrCap.set_implemented) + { + SWSS_LOG_WARN( + "Attribute(%s) SET is not implemented in SAI", + toStr(SAI_OBJECT_TYPE_HASH, SAI_HASH_ATTR_NATIVE_HASH_FIELD_LIST).c_str() + ); + return; + } + + hashCapabilities.nativeHashFieldList.isAttrSupported = true; +} + +void SwitchCapabilities::queryHashCapabilities() +{ + queryHashNativeHashFieldListEnumCapabilities(); + queryHashNativeHashFieldListAttrCapabilities(); +} + +void SwitchCapabilities::querySwitchEcmpHashCapabilities() +{ + SWSS_LOG_ENTER(); + + sai_attr_capability_t attrCap; + + auto status = queryAttrCapabilitiesSai( + attrCap, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_ECMP_HASH + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) capabilities", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_ECMP_HASH).c_str() + ); + return; + } + + if (!attrCap.get_implemented) + { + SWSS_LOG_WARN( + "Attribute(%s) GET is not implemented in SAI", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_ECMP_HASH).c_str() + ); + return; + } + + switchCapabilities.ecmpHash.isAttrSupported = true; +} + +void SwitchCapabilities::querySwitchLagHashCapabilities() +{ + SWSS_LOG_ENTER(); + + sai_attr_capability_t attrCap; + + auto status = queryAttrCapabilitiesSai( + attrCap, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_LAG_HASH + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) capabilities", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_LAG_HASH).c_str() + ); + return; + } + + if (!attrCap.get_implemented) + { + SWSS_LOG_WARN( + "Attribute(%s) GET is not implemented in SAI", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_LAG_HASH).c_str() + ); + return; + } + + switchCapabilities.lagHash.isAttrSupported = true; +} + +void SwitchCapabilities::querySwitchCapabilities() +{ + querySwitchEcmpHashCapabilities(); + querySwitchLagHashCapabilities(); +} + +void SwitchCapabilities::writeHashCapabilitiesToDb() +{ + SWSS_LOG_ENTER(); + + auto key = SwitchCapabilities::capTable.getKeyName(SWITCH_CAPABILITY_KEY); + + std::vector fvList = { + makeHashFieldCapDbEntry() + }; + + SwitchCapabilities::capTable.set(SWITCH_CAPABILITY_KEY, fvList); + + SWSS_LOG_NOTICE("Wrote hash enum capabilities to State DB: %s key", key.c_str()); +} + +void SwitchCapabilities::writeSwitchCapabilitiesToDb() +{ + SWSS_LOG_ENTER(); + + auto key = SwitchCapabilities::capTable.getKeyName(SWITCH_CAPABILITY_KEY); + + std::vector fvList = { + makeEcmpHashCapDbEntry(), + makeLagHashCapDbEntry() + }; + + SwitchCapabilities::capTable.set(SWITCH_CAPABILITY_KEY, fvList); + + SWSS_LOG_NOTICE("Wrote switch hash capabilities to State DB: %s key", key.c_str()); +} diff --git a/orchagent/switch/switch_capabilities.h b/orchagent/switch/switch_capabilities.h new file mode 100644 index 00000000000..47dcb0c7ecb --- /dev/null +++ b/orchagent/switch/switch_capabilities.h @@ -0,0 +1,68 @@ +#pragma once + +extern "C" { +#include +#include +#include +} + +#include +#include + +#include +#include + +class SwitchCapabilities final +{ +public: + SwitchCapabilities(); + ~SwitchCapabilities() = default; + + bool isSwitchEcmpHashSupported() const; + bool isSwitchLagHashSupported() const; + + bool validateSwitchHashFieldCap(const std::set &hfSet) const; + +private: + swss::FieldValueTuple makeHashFieldCapDbEntry() const; + swss::FieldValueTuple makeEcmpHashCapDbEntry() const; + swss::FieldValueTuple makeLagHashCapDbEntry() const; + + sai_status_t queryEnumCapabilitiesSai(std::vector &capList, sai_object_type_t objType, sai_attr_id_t attrId) const; + sai_status_t queryAttrCapabilitiesSai(sai_attr_capability_t &attrCap, sai_object_type_t objType, sai_attr_id_t attrId) const; + + void queryHashNativeHashFieldListEnumCapabilities(); + void queryHashNativeHashFieldListAttrCapabilities(); + + void querySwitchEcmpHashCapabilities(); + void querySwitchLagHashCapabilities(); + + void queryHashCapabilities(); + void querySwitchCapabilities(); + + void writeHashCapabilitiesToDb(); + void writeSwitchCapabilitiesToDb(); + + // Hash SAI capabilities + struct { + struct { + std::set hfSet; + bool isEnumSupported = false; + bool isAttrSupported = false; + } nativeHashFieldList; + } hashCapabilities; + + // Switch SAI capabilities + struct { + struct { + bool isAttrSupported = false; + } ecmpHash; + + struct { + bool isAttrSupported = false; + } lagHash; + } switchCapabilities; + + static swss::DBConnector stateDb; + static swss::Table capTable; +}; diff --git a/orchagent/switch/switch_container.h b/orchagent/switch/switch_container.h new file mode 100644 index 00000000000..c56ae166f0b --- /dev/null +++ b/orchagent/switch/switch_container.h @@ -0,0 +1,28 @@ +#pragma once + +extern "C" { +#include +} + +#include +#include +#include + +class SwitchHash final +{ +public: + SwitchHash() = default; + ~SwitchHash() = default; + + struct { + std::set value; + bool is_set = false; + } ecmp_hash; + + struct { + std::set value; + bool is_set = false; + } lag_hash; + + std::unordered_map fieldValueMap; +}; diff --git a/orchagent/switch/switch_helper.cpp b/orchagent/switch/switch_helper.cpp new file mode 100644 index 00000000000..d91f382b25a --- /dev/null +++ b/orchagent/switch/switch_helper.cpp @@ -0,0 +1,142 @@ +// includes ----------------------------------------------------------------------------------------------------------- + +#include +#include +#include + +#include +#include + +#include "switch_schema.h" +#include "switch_helper.h" + +using namespace swss; + +// constants ---------------------------------------------------------------------------------------------------------- + +static const std::unordered_map swHashHashFieldMap = +{ + { SWITCH_HASH_FIELD_IN_PORT, SAI_NATIVE_HASH_FIELD_IN_PORT }, + { SWITCH_HASH_FIELD_DST_MAC, SAI_NATIVE_HASH_FIELD_DST_MAC }, + { SWITCH_HASH_FIELD_SRC_MAC, SAI_NATIVE_HASH_FIELD_SRC_MAC }, + { SWITCH_HASH_FIELD_ETHERTYPE, SAI_NATIVE_HASH_FIELD_ETHERTYPE }, + { SWITCH_HASH_FIELD_VLAN_ID, SAI_NATIVE_HASH_FIELD_VLAN_ID }, + { SWITCH_HASH_FIELD_IP_PROTOCOL, SAI_NATIVE_HASH_FIELD_IP_PROTOCOL }, + { SWITCH_HASH_FIELD_DST_IP, SAI_NATIVE_HASH_FIELD_DST_IP }, + { SWITCH_HASH_FIELD_SRC_IP, SAI_NATIVE_HASH_FIELD_SRC_IP }, + { SWITCH_HASH_FIELD_L4_DST_PORT, SAI_NATIVE_HASH_FIELD_L4_DST_PORT }, + { SWITCH_HASH_FIELD_L4_SRC_PORT, SAI_NATIVE_HASH_FIELD_L4_SRC_PORT }, + { SWITCH_HASH_FIELD_INNER_DST_MAC, SAI_NATIVE_HASH_FIELD_INNER_DST_MAC }, + { SWITCH_HASH_FIELD_INNER_SRC_MAC, SAI_NATIVE_HASH_FIELD_INNER_SRC_MAC }, + { SWITCH_HASH_FIELD_INNER_ETHERTYPE, SAI_NATIVE_HASH_FIELD_INNER_ETHERTYPE }, + { SWITCH_HASH_FIELD_INNER_IP_PROTOCOL, SAI_NATIVE_HASH_FIELD_INNER_IP_PROTOCOL }, + { SWITCH_HASH_FIELD_INNER_DST_IP, SAI_NATIVE_HASH_FIELD_INNER_DST_IP }, + { SWITCH_HASH_FIELD_INNER_SRC_IP, SAI_NATIVE_HASH_FIELD_INNER_SRC_IP }, + { SWITCH_HASH_FIELD_INNER_L4_DST_PORT, SAI_NATIVE_HASH_FIELD_INNER_L4_DST_PORT }, + { SWITCH_HASH_FIELD_INNER_L4_SRC_PORT, SAI_NATIVE_HASH_FIELD_INNER_L4_SRC_PORT } +}; + +// switch helper ------------------------------------------------------------------------------------------------------ + +const SwitchHash& SwitchHelper::getSwHash() const +{ + return swHash; +} + +void SwitchHelper::setSwHash(const SwitchHash &hash) +{ + swHash = hash; +} + +template +bool SwitchHelper::parseSwHashFieldList(T &obj, 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; + } + + const auto &hfSet = std::unordered_set(hfList.cbegin(), hfList.cend()); + + if (hfSet.size() != hfList.size()) + { + SWSS_LOG_ERROR("Duplicate hash fields in field(%s): unexpected value(%s)", field.c_str(), value.c_str()); + return false; + } + + for (const auto &cit1 : hfSet) + { + const auto &cit2 = swHashHashFieldMap.find(cit1); + if (cit2 == swHashHashFieldMap.cend()) + { + SWSS_LOG_ERROR("Failed to parse field(%s): invalid value(%s)", field.c_str(), value.c_str()); + return false; + } + + obj.value.insert(cit2->second); + } + + obj.is_set = true; + + return true; +} + +bool SwitchHelper::parseSwHashEcmpHash(SwitchHash &hash, const std::string &field, const std::string &value) const +{ + return parseSwHashFieldList(hash.ecmp_hash, field, value); +} + +bool SwitchHelper::parseSwHashLagHash(SwitchHash &hash, const std::string &field, const std::string &value) const +{ + return parseSwHashFieldList(hash.lag_hash, field, value); +} + +bool SwitchHelper::parseSwHash(SwitchHash &hash) const +{ + SWSS_LOG_ENTER(); + + for (const auto &cit : hash.fieldValueMap) + { + const auto &field = cit.first; + const auto &value = cit.second; + + if (field == SWITCH_HASH_ECMP_HASH) + { + if (!parseSwHashEcmpHash(hash, field, value)) + { + return false; + } + } + else if (field == SWITCH_HASH_LAG_HASH) + { + if (!parseSwHashLagHash(hash, field, value)) + { + return false; + } + } + else + { + SWSS_LOG_WARN("Unknown field(%s): skipping ...", field.c_str()); + } + } + + return validateSwHash(hash); +} + +bool SwitchHelper::validateSwHash(SwitchHash &hash) const +{ + SWSS_LOG_ENTER(); + + if (!hash.ecmp_hash.is_set && !hash.lag_hash.is_set) + { + SWSS_LOG_ERROR("Validation error: missing valid fields"); + return false; + } + + return true; +} diff --git a/orchagent/switch/switch_helper.h b/orchagent/switch/switch_helper.h new file mode 100644 index 00000000000..611ce2b6fbd --- /dev/null +++ b/orchagent/switch/switch_helper.h @@ -0,0 +1,27 @@ +#pragma once + +#include "switch_container.h" + +class SwitchHelper final +{ +public: + SwitchHelper() = default; + ~SwitchHelper() = default; + + const SwitchHash& getSwHash() const; + void setSwHash(const SwitchHash &hash); + + bool parseSwHash(SwitchHash &hash) const; + +private: + template + bool parseSwHashFieldList(T &obj, const std::string &field, const std::string &value) const; + + bool parseSwHashEcmpHash(SwitchHash &hash, const std::string &field, const std::string &value) const; + bool parseSwHashLagHash(SwitchHash &hash, const std::string &field, const std::string &value) const; + + bool validateSwHash(SwitchHash &hash) const; + +private: + SwitchHash swHash; +}; diff --git a/orchagent/switch/switch_schema.h b/orchagent/switch/switch_schema.h new file mode 100644 index 00000000000..c836eff120c --- /dev/null +++ b/orchagent/switch/switch_schema.h @@ -0,0 +1,25 @@ +#pragma once + +// defines ------------------------------------------------------------------------------------------------------------ + +#define SWITCH_HASH_FIELD_IN_PORT "IN_PORT" +#define SWITCH_HASH_FIELD_DST_MAC "DST_MAC" +#define SWITCH_HASH_FIELD_SRC_MAC "SRC_MAC" +#define SWITCH_HASH_FIELD_ETHERTYPE "ETHERTYPE" +#define SWITCH_HASH_FIELD_VLAN_ID "VLAN_ID" +#define SWITCH_HASH_FIELD_IP_PROTOCOL "IP_PROTOCOL" +#define SWITCH_HASH_FIELD_DST_IP "DST_IP" +#define SWITCH_HASH_FIELD_SRC_IP "SRC_IP" +#define SWITCH_HASH_FIELD_L4_DST_PORT "L4_DST_PORT" +#define SWITCH_HASH_FIELD_L4_SRC_PORT "L4_SRC_PORT" +#define SWITCH_HASH_FIELD_INNER_DST_MAC "INNER_DST_MAC" +#define SWITCH_HASH_FIELD_INNER_SRC_MAC "INNER_SRC_MAC" +#define SWITCH_HASH_FIELD_INNER_ETHERTYPE "INNER_ETHERTYPE" +#define SWITCH_HASH_FIELD_INNER_IP_PROTOCOL "INNER_IP_PROTOCOL" +#define SWITCH_HASH_FIELD_INNER_DST_IP "INNER_DST_IP" +#define SWITCH_HASH_FIELD_INNER_SRC_IP "INNER_SRC_IP" +#define SWITCH_HASH_FIELD_INNER_L4_DST_PORT "INNER_L4_DST_PORT" +#define SWITCH_HASH_FIELD_INNER_L4_SRC_PORT "INNER_L4_SRC_PORT" + +#define SWITCH_HASH_ECMP_HASH "ecmp_hash" +#define SWITCH_HASH_LAG_HASH "lag_hash" diff --git a/orchagent/switchorch.cpp b/orchagent/switchorch.cpp index 52ef7a24b32..76c17812d49 100644 --- a/orchagent/switchorch.cpp +++ b/orchagent/switchorch.cpp @@ -17,6 +17,7 @@ using namespace swss; extern sai_object_id_t gSwitchId; extern sai_switch_api_t *sai_switch_api; extern sai_acl_api_t *sai_acl_api; +extern sai_hash_api_t *sai_hash_api; extern MacAddress gVxlanMacAddress; extern CrmOrch *gCrmOrch; @@ -86,6 +87,8 @@ SwitchOrch::SwitchOrch(DBConnector *db, vector& connectors, Tabl initSensorsTable(); querySwitchTpidCapability(); querySwitchPortEgressSampleCapability(); + querySwitchHashDefaults(); + auto executorT = new ExecutableTimer(m_sensorsPollerTimer, this, "ASIC_SENSORS_POLL_TIMER"); Orch::addExecutor(executorT); } @@ -473,24 +476,197 @@ void SwitchOrch::doAppSwitchTableTask(Consumer &consumer) } } +bool SwitchOrch::setSwitchHashFieldListSai(const SwitchHash &hash, bool isEcmpHash) const +{ + const auto &oid = isEcmpHash ? m_switchHashDefaults.ecmpHash.oid : m_switchHashDefaults.lagHash.oid; + const auto &hfSet = isEcmpHash ? hash.ecmp_hash.value : hash.lag_hash.value; + + std::vector hfList; + std::transform( + hfSet.cbegin(), hfSet.cend(), std::back_inserter(hfList), + [](sai_native_hash_field_t value) { return static_cast(value); } + ); + + sai_attribute_t attr; + + attr.id = SAI_HASH_ATTR_NATIVE_HASH_FIELD_LIST; + attr.value.s32list.list = hfList.data(); + attr.value.s32list.count = static_cast(hfList.size()); + + auto status = sai_hash_api->set_hash_attribute(oid, &attr); + return status == SAI_STATUS_SUCCESS; +} + +bool SwitchOrch::setSwitchHash(const SwitchHash &hash) +{ + SWSS_LOG_ENTER(); + + auto hObj = swHlpr.getSwHash(); + auto cfgUpd = false; + + if (hash.ecmp_hash.is_set) + { + if (hObj.ecmp_hash.value != hash.ecmp_hash.value) + { + if (swCap.isSwitchEcmpHashSupported()) + { + if (!swCap.validateSwitchHashFieldCap(hash.ecmp_hash.value)) + { + SWSS_LOG_ERROR("Failed to validate switch ECMP hash: capability is not supported"); + return false; + } + + if (!setSwitchHashFieldListSai(hash, true)) + { + SWSS_LOG_ERROR("Failed to set switch ECMP hash in SAI"); + return false; + } + + cfgUpd = true; + } + else + { + SWSS_LOG_WARN("Switch ECMP hash configuration is not supported: skipping ..."); + } + } + } + else + { + if (hObj.ecmp_hash.is_set) + { + SWSS_LOG_ERROR("Failed to remove switch ECMP hash configuration: operation is not supported"); + return false; + } + } + + if (hash.lag_hash.is_set) + { + if (hObj.lag_hash.value != hash.lag_hash.value) + { + if (swCap.isSwitchLagHashSupported()) + { + if (!swCap.validateSwitchHashFieldCap(hash.lag_hash.value)) + { + SWSS_LOG_ERROR("Failed to validate switch LAG hash: capability is not supported"); + return false; + } + + if (!setSwitchHashFieldListSai(hash, false)) + { + SWSS_LOG_ERROR("Failed to set switch LAG hash in SAI"); + return false; + } + + cfgUpd = true; + } + else + { + SWSS_LOG_WARN("Switch LAG hash configuration is not supported: skipping ..."); + } + } + } + else + { + if (hObj.lag_hash.is_set) + { + SWSS_LOG_ERROR("Failed to remove switch LAG hash configuration: operation is not supported"); + return false; + } + } + + // Don't update internal cache when config remains unchanged + if (!cfgUpd) + { + SWSS_LOG_NOTICE("Switch hash in SAI is up-to-date"); + return true; + } + + swHlpr.setSwHash(hash); + + SWSS_LOG_NOTICE("Set switch hash in SAI"); + + return true; +} + +void SwitchOrch::doCfgSwitchHashTableTask(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 switch hash key: empty string"); + it = map.erase(it); + continue; + } + + SwitchHash hash; + + 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 (swHlpr.parseSwHash(hash)) + { + if (!setSwitchHash(hash)) + { + SWSS_LOG_ERROR("Failed to set switch hash: ASIC and CONFIG DB are diverged"); + } + } + } + else if (op == DEL_COMMAND) + { + SWSS_LOG_ERROR("Failed to remove switch hash: operation is not supported: ASIC and CONFIG DB are diverged"); + } + else + { + SWSS_LOG_ERROR("Unknown operation(%s)", op.c_str()); + } + + it = map.erase(it); + } +} + void SwitchOrch::doTask(Consumer &consumer) { SWSS_LOG_ENTER(); - const string & table_name = consumer.getTableName(); - if (table_name == APP_SWITCH_TABLE_NAME) + const auto &tableName = consumer.getTableName(); + + if (tableName == APP_SWITCH_TABLE_NAME) { doAppSwitchTableTask(consumer); } - else if (table_name == CFG_ASIC_SENSORS_TABLE_NAME) + else if (tableName == CFG_ASIC_SENSORS_TABLE_NAME) { doCfgSensorsTableTask(consumer); } + else if (tableName == CFG_SWITCH_HASH_TABLE_NAME) + { + doCfgSwitchHashTableTask(consumer); + } else { - SWSS_LOG_ERROR("Unknown table : %s", table_name.c_str()); + SWSS_LOG_ERROR("Unknown table : %s", tableName.c_str()); } - } void SwitchOrch::doTask(NotificationConsumer& consumer) @@ -814,6 +990,38 @@ void SwitchOrch::querySwitchTpidCapability() } } +bool SwitchOrch::getSwitchHashOidSai(sai_object_id_t &oid, bool isEcmpHash) const +{ + sai_attribute_t attr; + attr.id = isEcmpHash ? SAI_SWITCH_ATTR_ECMP_HASH : SAI_SWITCH_ATTR_LAG_HASH; + attr.value.oid = SAI_NULL_OBJECT_ID; + + auto status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + if (status != SAI_STATUS_SUCCESS) + { + return false; + } + + oid = attr.value.oid; + + return true; +} + +void SwitchOrch::querySwitchHashDefaults() +{ + SWSS_LOG_ENTER(); + + if (!getSwitchHashOidSai(m_switchHashDefaults.ecmpHash.oid, true)) + { + SWSS_LOG_WARN("Failed to get switch ECMP hash OID"); + } + + if (!getSwitchHashOidSai(m_switchHashDefaults.lagHash.oid, false)) + { + SWSS_LOG_WARN("Failed to get switch LAG hash OID"); + } +} + bool SwitchOrch::querySwitchCapability(sai_object_type_t sai_object, sai_attr_id_t attr_id) { SWSS_LOG_ENTER(); diff --git a/orchagent/switchorch.h b/orchagent/switchorch.h index cabdb9359d2..c6ee9997f94 100644 --- a/orchagent/switchorch.h +++ b/orchagent/switchorch.h @@ -3,6 +3,8 @@ #include "acltable.h" #include "orch.h" #include "timer.h" +#include "switch/switch_capabilities.h" +#include "switch/switch_helper.h" #define DEFAULT_ASIC_SENSORS_POLLER_INTERVAL 60 #define ASIC_SENSORS_POLLER_STATUS "ASIC_SENSORS_POLLER_STATUS" @@ -47,11 +49,20 @@ class SwitchOrch : public Orch private: void doTask(Consumer &consumer); void doTask(swss::SelectableTimer &timer); + void doCfgSwitchHashTableTask(Consumer &consumer); void doCfgSensorsTableTask(Consumer &consumer); void doAppSwitchTableTask(Consumer &consumer); void initSensorsTable(); void querySwitchTpidCapability(); void querySwitchPortEgressSampleCapability(); + + // Switch hash + bool setSwitchHashFieldListSai(const SwitchHash &hash, bool isEcmpHash) const; + bool setSwitchHash(const SwitchHash &hash); + + bool getSwitchHashOidSai(sai_object_id_t &oid, bool isEcmpHash) const; + void querySwitchHashDefaults(); + sai_status_t setSwitchTunnelVxlanParams(swss::FieldValueTuple &val); void setSwitchNonSaiAttributes(swss::FieldValueTuple &val); @@ -87,7 +98,23 @@ class SwitchOrch : public Orch bool m_orderedEcmpEnable = false; bool m_PfcDlrInitEnable = false; + // Switch hash SAI defaults + struct { + struct { + sai_object_id_t oid = SAI_NULL_OBJECT_ID; + } ecmpHash; + struct { + sai_object_id_t oid = SAI_NULL_OBJECT_ID; + } lagHash; + } m_switchHashDefaults; + // Information contained in the request from // external program for orchagent pre-shutdown state check WarmRestartCheck m_warmRestartCheck = {false, false, false}; + + // Switch OA capabilities + SwitchCapabilities swCap; + + // Switch OA helper + SwitchHelper swHlpr; }; diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index e626490c30c..435474ac3c8 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -84,6 +84,8 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/pbhorch.cpp \ $(top_srcdir)/orchagent/saihelper.cpp \ $(top_srcdir)/orchagent/saiattr.cpp \ + $(top_srcdir)/orchagent/switch/switch_capabilities.cpp \ + $(top_srcdir)/orchagent/switch/switch_helper.cpp \ $(top_srcdir)/orchagent/switchorch.cpp \ $(top_srcdir)/orchagent/pfcwdorch.cpp \ $(top_srcdir)/orchagent/pfcactionhandler.cpp \ diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index 5c8866b240c..b03d2191380 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -19,6 +19,7 @@ extern VRFOrch *gVrfOrch; extern sai_acl_api_t *sai_acl_api; extern sai_switch_api_t *sai_switch_api; +extern sai_hash_api_t *sai_hash_api; extern sai_port_api_t *sai_port_api; extern sai_vlan_api_t *sai_vlan_api; extern sai_bridge_api_t *sai_bridge_api; @@ -312,6 +313,7 @@ namespace aclorch_test ASSERT_EQ(status, SAI_STATUS_SUCCESS); sai_api_query(SAI_API_SWITCH, (void **)&sai_switch_api); + sai_api_query(SAI_API_HASH, (void **)&sai_hash_api); sai_api_query(SAI_API_BRIDGE, (void **)&sai_bridge_api); sai_api_query(SAI_API_PORT, (void **)&sai_port_api); sai_api_query(SAI_API_VLAN, (void **)&sai_vlan_api); diff --git a/tests/mock_tests/mock_orchagent_main.h b/tests/mock_tests/mock_orchagent_main.h index 4fd00d41fcf..3d57dff88f9 100644 --- a/tests/mock_tests/mock_orchagent_main.h +++ b/tests/mock_tests/mock_orchagent_main.h @@ -66,6 +66,7 @@ extern Directory gDirectory; extern sai_acl_api_t *sai_acl_api; extern sai_switch_api_t *sai_switch_api; +extern sai_hash_api_t *sai_hash_api; extern sai_virtual_router_api_t *sai_virtual_router_api; extern sai_port_api_t *sai_port_api; extern sai_lag_api_t *sai_lag_api; diff --git a/tests/mock_tests/ut_saihelper.cpp b/tests/mock_tests/ut_saihelper.cpp index f7a1f2fb2db..8b6b35b6f7c 100644 --- a/tests/mock_tests/ut_saihelper.cpp +++ b/tests/mock_tests/ut_saihelper.cpp @@ -64,6 +64,7 @@ namespace ut_helper } sai_api_query(SAI_API_SWITCH, (void **)&sai_switch_api); + sai_api_query(SAI_API_HASH, (void **)&sai_hash_api); sai_api_query(SAI_API_BRIDGE, (void **)&sai_bridge_api); sai_api_query(SAI_API_VIRTUAL_ROUTER, (void **)&sai_virtual_router_api); sai_api_query(SAI_API_SAMPLEPACKET, (void **)&sai_samplepacket_api); diff --git a/tests/test_hash.py b/tests/test_hash.py new file mode 100644 index 00000000000..9c08aabb656 --- /dev/null +++ b/tests/test_hash.py @@ -0,0 +1,174 @@ +import pytest +import logging + + +logging.basicConfig(level=logging.INFO) +hashlogger = logging.getLogger(__name__) + + +HASH_FIELD_LIST = [ + "DST_MAC", + "SRC_MAC", + "ETHERTYPE", + "IP_PROTOCOL", + "DST_IP", + "SRC_IP", + "L4_DST_PORT", + "L4_SRC_PORT" +] +INNER_HASH_FIELD_LIST = [ + "INNER_DST_MAC", + "INNER_SRC_MAC", + "INNER_ETHERTYPE", + "INNER_IP_PROTOCOL", + "INNER_DST_IP", + "INNER_SRC_IP", + "INNER_L4_DST_PORT", + "INNER_L4_SRC_PORT" +] +DEFAULT_HASH_FIELD_LIST = [ + "DST_MAC", + "SRC_MAC", + "ETHERTYPE", + "IN_PORT" +] + +SAI_HASH_FIELD_LIST = [ + "SAI_NATIVE_HASH_FIELD_DST_MAC", + "SAI_NATIVE_HASH_FIELD_SRC_MAC", + "SAI_NATIVE_HASH_FIELD_ETHERTYPE", + "SAI_NATIVE_HASH_FIELD_IP_PROTOCOL", + "SAI_NATIVE_HASH_FIELD_DST_IP", + "SAI_NATIVE_HASH_FIELD_SRC_IP", + "SAI_NATIVE_HASH_FIELD_L4_DST_PORT", + "SAI_NATIVE_HASH_FIELD_L4_SRC_PORT" +] +SAI_INNER_HASH_FIELD_LIST = [ + "SAI_NATIVE_HASH_FIELD_INNER_DST_MAC", + "SAI_NATIVE_HASH_FIELD_INNER_SRC_MAC", + "SAI_NATIVE_HASH_FIELD_INNER_ETHERTYPE", + "SAI_NATIVE_HASH_FIELD_INNER_IP_PROTOCOL", + "SAI_NATIVE_HASH_FIELD_INNER_DST_IP", + "SAI_NATIVE_HASH_FIELD_INNER_SRC_IP", + "SAI_NATIVE_HASH_FIELD_INNER_L4_DST_PORT", + "SAI_NATIVE_HASH_FIELD_INNER_L4_SRC_PORT" +] +SAI_DEFAULT_HASH_FIELD_LIST = [ + "SAI_NATIVE_HASH_FIELD_DST_MAC", + "SAI_NATIVE_HASH_FIELD_SRC_MAC", + "SAI_NATIVE_HASH_FIELD_ETHERTYPE", + "SAI_NATIVE_HASH_FIELD_IN_PORT" +] + + +@pytest.mark.usefixtures("dvs_hash_manager") +class TestHashBasicFlows: + @pytest.fixture(scope="class") + def hashData(self, dvs_hash_manager): + hashlogger.info("Initialize HASH data") + + hashlogger.info("Verify HASH count") + self.dvs_hash.verify_hash_count(0) + + hashlogger.info("Get ECMP/LAG HASH id") + hashIdList = sorted(self.dvs_hash.get_hash_ids()) + + # Assumption: VS has only two HASH objects: ECMP, LAG + meta_dict = { + "ecmp": hashIdList[0], + "lag": hashIdList[1] + } + + yield meta_dict + + hashlogger.info("Deinitialize HASH data") + + @pytest.mark.parametrize( + "hash,field", [ + pytest.param( + "ecmp", + "ecmp_hash", + id="ecmp-hash" + ), + pytest.param( + "lag", + "lag_hash", + id="lag-hash" + ) + ] + ) + @pytest.mark.parametrize( + "hfList,saiHfList", [ + pytest.param( + ",".join(HASH_FIELD_LIST), + SAI_HASH_FIELD_LIST, + id="outer-frame" + ), + pytest.param( + ",".join(INNER_HASH_FIELD_LIST), + SAI_INNER_HASH_FIELD_LIST, + id="inner-frame" + ) + ] + ) + def test_HashSwitchGlobalConfiguration(self, hash, field, hfList, saiHfList, testlog, hashData): + attr_dict = { + field: hfList + } + + hashlogger.info("Update {} hash".format(hash.upper())) + self.dvs_hash.update_switch_hash( + qualifiers=attr_dict + ) + + hashId = hashData[hash] + sai_attr_dict = { + "SAI_HASH_ATTR_NATIVE_HASH_FIELD_LIST": saiHfList + } + + hashlogger.info("Validate {} hash".format(hash.upper())) + self.dvs_hash.verify_hash_generic( + sai_hash_id=hashId, + sai_qualifiers=sai_attr_dict + ) + + @pytest.mark.parametrize( + "hash,field", [ + pytest.param( + "ecmp", + "ecmp_hash", + id="ecmp-hash" + ), + pytest.param( + "lag", + "lag_hash", + id="lag-hash" + ) + ] + ) + def test_HashDefaultSwitchGlobalConfiguration(self, hash, field, testlog, hashData): + attr_dict = { + field: ",".join(DEFAULT_HASH_FIELD_LIST) + } + + hashlogger.info("Update {} hash".format(hash.upper())) + self.dvs_hash.update_switch_hash( + qualifiers=attr_dict + ) + + hashId = hashData[hash] + sai_attr_dict = { + "SAI_HASH_ATTR_NATIVE_HASH_FIELD_LIST": SAI_DEFAULT_HASH_FIELD_LIST + } + + hashlogger.info("Validate {} hash".format(hash.upper())) + self.dvs_hash.verify_hash_generic( + sai_hash_id=hashId, + sai_qualifiers=sai_attr_dict + ) + + +# 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