diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index c81138db..b51dc6ee 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -67,6 +67,7 @@ orchagent_SOURCES = \ copporch.cpp \ tunneldecaporch.cpp \ qosorch.cpp \ + buffer/bufferhelper.cpp \ bufferorch.cpp \ mirrororch.cpp \ fdborch.cpp \ diff --git a/orchagent/buffer/buffercontainer.h b/orchagent/buffer/buffercontainer.h new file mode 100644 index 00000000..351ebaa3 --- /dev/null +++ b/orchagent/buffer/buffercontainer.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +#include +#include +#include + +class BufferContainer +{ +public: + BufferContainer() = default; + virtual ~BufferContainer() = default; + + std::unordered_map fieldValueMap; +}; + +class BufferProfileConfig final : public BufferContainer +{ +public: + BufferProfileConfig() = default; + ~BufferProfileConfig() = default; + + inline bool isTrimmingProhibited() const + { + return ((pgRefCount > 0) || (iBufProfListRefCount > 0) || (eBufProfListRefCount)) ? true : false; + } + + std::uint64_t pgRefCount = 0; + std::uint64_t iBufProfListRefCount = 0; + std::uint64_t eBufProfListRefCount = 0; + + bool isTrimmingEligible = false; +}; + +class BufferPriorityGroupConfig final : public BufferContainer +{ +public: + BufferPriorityGroupConfig() = default; + ~BufferPriorityGroupConfig() = default; + + struct { + std::string value; + bool is_set = false; + } profile; +}; + +class IngressBufferProfileListConfig final : public BufferContainer +{ +public: + IngressBufferProfileListConfig() = default; + ~IngressBufferProfileListConfig() = default; + + struct { + std::unordered_set value; + bool is_set = false; + } profile_list; +}; + +class EgressBufferProfileListConfig final : public BufferContainer +{ +public: + EgressBufferProfileListConfig() = default; + ~EgressBufferProfileListConfig() = default; + + struct { + std::unordered_set value; + bool is_set = false; + } profile_list; +}; diff --git a/orchagent/buffer/bufferhelper.cpp b/orchagent/buffer/bufferhelper.cpp new file mode 100644 index 00000000..6a585057 --- /dev/null +++ b/orchagent/buffer/bufferhelper.cpp @@ -0,0 +1,303 @@ +// includes ----------------------------------------------------------------------------------------------------------- + +#include +#include + +#include + +#include "bufferschema.h" + +#include "bufferhelper.h" + +using namespace swss; + +// helper ------------------------------------------------------------------------------------------------------------- + +void BufferHelper::parseBufferConfig(BufferProfileConfig &cfg) const +{ + auto &map = cfg.fieldValueMap; + + const auto &cit = map.find(BUFFER_PROFILE_PACKET_DISCARD_ACTION); + if (cit != map.cend()) + { + cfg.isTrimmingEligible = cit->second == BUFFER_PROFILE_PACKET_DISCARD_ACTION_TRIM ? true : false; + } +} + +void BufferHelper::parseBufferConfig(BufferPriorityGroupConfig &cfg) const +{ + auto &map = cfg.fieldValueMap; + + const auto &cit = map.find(BUFFER_PG_PROFILE); + if (cit != map.cend()) + { + cfg.profile.value = cit->second; + cfg.profile.is_set = true; + } +} + +void BufferHelper::parseBufferConfig(IngressBufferProfileListConfig &cfg) const +{ + auto &map = cfg.fieldValueMap; + + const auto &cit = map.find(BUFFER_PORT_INGRESS_PROFILE_LIST_PROFILE_LIST); + if (cit != map.cend()) + { + auto profList = tokenize(cit->second, ','); + + cfg.profile_list.value.insert(profList.begin(), profList.end()); + cfg.profile_list.is_set = true; + } +} + +void BufferHelper::parseBufferConfig(EgressBufferProfileListConfig &cfg) const +{ + auto &map = cfg.fieldValueMap; + + const auto &cit = map.find(BUFFER_PORT_EGRESS_PROFILE_LIST_PROFILE_LIST); + if (cit != map.cend()) + { + auto profList = tokenize(cit->second, ','); + + cfg.profile_list.value.insert(profList.begin(), profList.end()); + cfg.profile_list.is_set = true; + } +} + +template<> +void BufferHelper::setObjRef(const BufferProfileConfig &cfg) +{ + // No actions are required +} + +template<> +void BufferHelper::setObjRef(const BufferPriorityGroupConfig &cfg) +{ + if (cfg.profile.is_set) + { + const auto &cit = profMap.find(cfg.profile.value); + if (cit != profMap.cend()) + { + cit->second.pgRefCount++; + } + } +} + +template<> +void BufferHelper::setObjRef(const IngressBufferProfileListConfig &cfg) +{ + if (cfg.profile_list.is_set) + { + for (const auto &cit1 : cfg.profile_list.value) + { + const auto &cit2 = profMap.find(cit1); + if (cit2 != profMap.cend()) + { + cit2->second.iBufProfListRefCount++; + } + } + } +} + +template<> +void BufferHelper::setObjRef(const EgressBufferProfileListConfig &cfg) +{ + if (cfg.profile_list.is_set) + { + for (const auto &cit1 : cfg.profile_list.value) + { + const auto &cit2 = profMap.find(cit1); + if (cit2 != profMap.cend()) + { + cit2->second.eBufProfListRefCount++; + } + } + } +} + +template<> +void BufferHelper::delObjRef(const BufferProfileConfig &cfg) +{ + // No actions are required +} + +template<> +void BufferHelper::delObjRef(const BufferPriorityGroupConfig &cfg) +{ + if (cfg.profile.is_set) + { + const auto &cit = profMap.find(cfg.profile.value); + if (cit != profMap.cend()) + { + cit->second.pgRefCount--; + } + } +} + +template<> +void BufferHelper::delObjRef(const IngressBufferProfileListConfig &cfg) +{ + if (cfg.profile_list.is_set) + { + for (const auto &cit1 : cfg.profile_list.value) + { + const auto &cit2 = profMap.find(cit1); + if (cit2 != profMap.cend()) + { + cit2->second.iBufProfListRefCount--; + } + } + } +} + +template<> +void BufferHelper::delObjRef(const EgressBufferProfileListConfig &cfg) +{ + if (cfg.profile_list.is_set) + { + for (const auto &cit1 : cfg.profile_list.value) + { + const auto &cit2 = profMap.find(cit1); + if (cit2 != profMap.cend()) + { + cit2->second.eBufProfListRefCount--; + } + } + } +} + +template<> +auto BufferHelper::getBufferObjMap() const -> const std::unordered_map& +{ + return profMap; +} + +template<> +auto BufferHelper::getBufferObjMap() const -> const std::unordered_map& +{ + return pgMap; +} + +template<> +auto BufferHelper::getBufferObjMap() const -> const std::unordered_map& +{ + return iBufProfListMap; +} + +template<> +auto BufferHelper::getBufferObjMap() const -> const std::unordered_map& +{ + return eBufProfListMap; +} + +template<> +auto BufferHelper::getBufferObjMap() -> std::unordered_map& +{ + return profMap; +} + +template<> +auto BufferHelper::getBufferObjMap() -> std::unordered_map& +{ + return pgMap; +} + +template<> +auto BufferHelper::getBufferObjMap() -> std::unordered_map& +{ + return iBufProfListMap; +} + +template<> +auto BufferHelper::getBufferObjMap() -> std::unordered_map& +{ + return eBufProfListMap; +} + +template +void BufferHelper::setBufferConfig(const std::string &key, const T &cfg) +{ + auto &map = getBufferObjMap(); + + const auto &cit = map.find(key); + if (cit != map.cend()) + { + delObjRef(cit->second); + } + setObjRef(cfg); + + map[key] = cfg; +} + +template void BufferHelper::setBufferConfig(const std::string &key, const BufferProfileConfig &cfg); +template void BufferHelper::setBufferConfig(const std::string &key, const BufferPriorityGroupConfig &cfg); +template void BufferHelper::setBufferConfig(const std::string &key, const IngressBufferProfileListConfig &cfg); +template void BufferHelper::setBufferConfig(const std::string &key, const EgressBufferProfileListConfig &cfg); + +template +bool BufferHelper::getBufferConfig(T &cfg, const std::string &key) const +{ + auto &map = getBufferObjMap(); + + const auto &cit = map.find(key); + if (cit != map.cend()) + { + cfg = cit->second; + return true; + } + + return false; +} + +template bool BufferHelper::getBufferConfig(BufferProfileConfig &cfg, const std::string &key) const; +template bool BufferHelper::getBufferConfig(BufferPriorityGroupConfig &cfg, const std::string &key) const; +template bool BufferHelper::getBufferConfig(IngressBufferProfileListConfig &cfg, const std::string &key) const; +template bool BufferHelper::getBufferConfig(EgressBufferProfileListConfig &cfg, const std::string &key) const; + +void BufferHelper::delBufferProfileConfig(const std::string &key) +{ + const auto &cit = profMap.find(key); + if (cit == profMap.cend()) + { + return; + } + + delObjRef(cit->second); + profMap.erase(cit); +} + +void BufferHelper::delBufferPriorityGroupConfig(const std::string &key) +{ + const auto &cit = pgMap.find(key); + if (cit == pgMap.cend()) + { + return; + } + + delObjRef(cit->second); + pgMap.erase(cit); +} + +void BufferHelper::delIngressBufferProfileListConfig(const std::string &key) +{ + const auto &cit = iBufProfListMap.find(key); + if (cit == iBufProfListMap.cend()) + { + return; + } + + delObjRef(cit->second); + iBufProfListMap.erase(cit); +} + +void BufferHelper::delEgressBufferProfileListConfig(const std::string &key) +{ + const auto &cit = eBufProfListMap.find(key); + if (cit == eBufProfListMap.cend()) + { + return; + } + + delObjRef(cit->second); + eBufProfListMap.erase(cit); +} diff --git a/orchagent/buffer/bufferhelper.h b/orchagent/buffer/bufferhelper.h new file mode 100644 index 00000000..264ac085 --- /dev/null +++ b/orchagent/buffer/bufferhelper.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include "buffercontainer.h" + +class BufferHelper final +{ +public: + BufferHelper() = default; + ~BufferHelper() = default; + + void parseBufferConfig(BufferProfileConfig &cfg) const; + void parseBufferConfig(BufferPriorityGroupConfig &cfg) const; + void parseBufferConfig(IngressBufferProfileListConfig &cfg) const; + void parseBufferConfig(EgressBufferProfileListConfig &cfg) const; + + template + void setBufferConfig(const std::string &key, const T &cfg); + template + bool getBufferConfig(T &cfg, const std::string &key) const; + + void delBufferProfileConfig(const std::string &key); + void delBufferPriorityGroupConfig(const std::string &key); + void delIngressBufferProfileListConfig(const std::string &key); + void delEgressBufferProfileListConfig(const std::string &key); + +private: + template + auto getBufferObjMap() const -> const std::unordered_map&; + template + auto getBufferObjMap() -> std::unordered_map&; + + template + void setObjRef(const T &cfg); + template + void delObjRef(const T &cfg); + + std::unordered_map profMap; + std::unordered_map pgMap; + std::unordered_map iBufProfListMap; + std::unordered_map eBufProfListMap; +}; diff --git a/orchagent/buffer/bufferschema.h b/orchagent/buffer/bufferschema.h index 0d4e6ffc..f53c8774 100644 --- a/orchagent/buffer/bufferschema.h +++ b/orchagent/buffer/bufferschema.h @@ -6,3 +6,8 @@ #define BUFFER_PROFILE_PACKET_DISCARD_ACTION_TRIM "trim" #define BUFFER_PROFILE_PACKET_DISCARD_ACTION "packet_discard_action" + +#define BUFFER_PG_PROFILE "profile" + +#define BUFFER_PORT_INGRESS_PROFILE_LIST_PROFILE_LIST "profile_list" +#define BUFFER_PORT_EGRESS_PROFILE_LIST_PROFILE_LIST "profile_list" diff --git a/orchagent/bufferorch.cpp b/orchagent/bufferorch.cpp index d4a71092..37de6e7d 100644 --- a/orchagent/bufferorch.cpp +++ b/orchagent/bufferorch.cpp @@ -396,7 +396,8 @@ task_process_status BufferOrch::processBufferPool(KeyOpFieldsValuesTuple &tuple) string op = kfvOp(tuple); string xoff; - SWSS_LOG_DEBUG("object name:%s", object_name.c_str()); + SWSS_LOG_DEBUG("KEY: %s, OP: %s", object_name.c_str(), op.c_str()); + if (m_buffer_type_maps[map_type_name]->find(object_name) != m_buffer_type_maps[map_type_name]->end()) { sai_object = (*(m_buffer_type_maps[map_type_name]))[object_name].m_saiObjectId; @@ -407,7 +408,6 @@ task_process_status BufferOrch::processBufferPool(KeyOpFieldsValuesTuple &tuple) return task_process_status::task_need_retry; } } - SWSS_LOG_DEBUG("processing command:%s", op.c_str()); if (op == SET_COMMAND) { @@ -417,7 +417,8 @@ task_process_status BufferOrch::processBufferPool(KeyOpFieldsValuesTuple &tuple) string field = fvField(*i); string value = fvValue(*i); - SWSS_LOG_DEBUG("field:%s, value:%s", field.c_str(), value.c_str()); + SWSS_LOG_DEBUG("FIELD: %s, VALUE: %s", field.c_str(), value.c_str()); + sai_attribute_t attr; if (field == buffer_size_field_name) { @@ -603,7 +604,8 @@ task_process_status BufferOrch::processBufferProfile(KeyOpFieldsValuesTuple &tup string op = kfvOp(tuple); string pool_name; - SWSS_LOG_DEBUG("object name:%s", object_name.c_str()); + SWSS_LOG_DEBUG("KEY: %s, OP: %s", object_name.c_str(), op.c_str()); + if (m_buffer_type_maps[map_type_name]->find(object_name) != m_buffer_type_maps[map_type_name]->end()) { sai_object = (*(m_buffer_type_maps[map_type_name]))[object_name].m_saiObjectId; @@ -614,16 +616,21 @@ task_process_status BufferOrch::processBufferProfile(KeyOpFieldsValuesTuple &tup return task_process_status::task_need_retry; } } - SWSS_LOG_DEBUG("processing command:%s", op.c_str()); + if (op == SET_COMMAND) { + BufferProfileConfig cfg; + m_bufHlpr.getBufferConfig(cfg, object_name); vector attribs; for (auto i = kfvFieldsValues(tuple).begin(); i != kfvFieldsValues(tuple).end(); i++) { string field = fvField(*i); string value = fvValue(*i); - SWSS_LOG_DEBUG("field:%s, value:%s", field.c_str(), value.c_str()); + cfg.fieldValueMap[field] = value; + + SWSS_LOG_DEBUG("FIELD: %s, VALUE: %s", field.c_str(), value.c_str()); + sai_attribute_t attr; if (field == buffer_pool_field_name) { @@ -740,6 +747,18 @@ task_process_status BufferOrch::processBufferProfile(KeyOpFieldsValuesTuple &tup continue; } } + + m_bufHlpr.parseBufferConfig(cfg); + + if (cfg.isTrimmingEligible && cfg.isTrimmingProhibited()) + { + SWSS_LOG_ERROR( + "Failed to configure buffer profile(%s): trimming is prohibited by dependency constraint check", + object_name.c_str() + ); + return task_process_status::task_failed; + } + if (SAI_NULL_OBJECT_ID != sai_object) { vector attribs_to_retry; @@ -791,6 +810,9 @@ task_process_status BufferOrch::processBufferProfile(KeyOpFieldsValuesTuple &tup SWSS_LOG_NOTICE("Created buffer profile %s with type %s", object_name.c_str(), map_type_name.c_str()); } + // Update config state + m_bufHlpr.setBufferConfig(object_name, cfg); + // Add reference to the buffer pool object setObjectReference(m_buffer_type_maps, map_type_name, object_name, buffer_pool_field_name, pool_name); } @@ -821,6 +843,7 @@ task_process_status BufferOrch::processBufferProfile(KeyOpFieldsValuesTuple &tup SWSS_LOG_NOTICE("Remove buffer profile %s with type %s", object_name.c_str(), map_type_name.c_str()); removeObject(m_buffer_type_maps, map_type_name, object_name); + m_bufHlpr.delBufferProfileConfig(object_name); } else { @@ -850,7 +873,8 @@ task_process_status BufferOrch::processQueue(KeyOpFieldsValuesTuple &tuple) string old_buffer_profile_name; string local_port_name; - SWSS_LOG_DEBUG("Processing:%s", key.c_str()); + SWSS_LOG_DEBUG("KEY: %s, OP: %s", key.c_str(), op.c_str()); + tokens = tokenize(key, delimiter); vector port_names; @@ -1246,7 +1270,8 @@ task_process_status BufferOrch::processPriorityGroup(KeyOpFieldsValuesTuple &tup bool counter_needs_to_add = false; string old_buffer_profile_name; - SWSS_LOG_DEBUG("processing:%s", key.c_str()); + SWSS_LOG_DEBUG("KEY: %s, OP: %s", key.c_str(), op.c_str()); + tokens = tokenize(key, delimiter); if (tokens.size() != 2) { @@ -1287,6 +1312,40 @@ task_process_status BufferOrch::processPriorityGroup(KeyOpFieldsValuesTuple &tup return task_process_status::task_success; } + BufferPriorityGroupConfig cfg; + m_bufHlpr.getBufferConfig(cfg, key); + + for (const auto &cit : kfvFieldsValues(tuple)) + { + auto field = fvField(cit); + auto value = fvValue(cit); + + SWSS_LOG_DEBUG("FIELD: %s, VALUE: %s", field.c_str(), value.c_str()); + + cfg.fieldValueMap[field] = value; + } + + m_bufHlpr.parseBufferConfig(cfg); + + if (cfg.profile.is_set) + { + BufferProfileConfig profCfg; + + if (m_bufHlpr.getBufferConfig(profCfg, cfg.profile.value)) + { + if (profCfg.isTrimmingEligible) + { + SWSS_LOG_ERROR( + "Failed to configure ingress priority group(%s): buffer profile(%s) is trimming eligible", + key.c_str(), cfg.profile.value.c_str() + ); + return task_process_status::task_failed; + } + } + } + + m_bufHlpr.setBufferConfig(key, cfg); + SWSS_LOG_NOTICE("Set buffer PG %s to %s", key.c_str(), buffer_profile_name.c_str()); setObjectReference(m_buffer_type_maps, APP_BUFFER_PG_TABLE_NAME, key, buffer_profile_field_name, buffer_profile_name); @@ -1305,6 +1364,7 @@ task_process_status BufferOrch::processPriorityGroup(KeyOpFieldsValuesTuple &tup sai_buffer_profile = SAI_NULL_OBJECT_ID; SWSS_LOG_NOTICE("Remove buffer PG %s", key.c_str()); removeObject(m_buffer_type_maps, APP_BUFFER_PG_TABLE_NAME, key); + m_bufHlpr.delBufferPriorityGroupConfig(key); } else { @@ -1558,7 +1618,7 @@ task_process_status BufferOrch::processIngressBufferProfileList(KeyOpFieldsValue string key = kfvKey(tuple); string op = kfvOp(tuple); - SWSS_LOG_DEBUG("processing:%s", key.c_str()); + SWSS_LOG_DEBUG("KEY: %s, OP: %s", key.c_str(), op.c_str()); vector port_names = tokenize(key, list_item_delimiter); vector profile_list; @@ -1590,6 +1650,43 @@ task_process_status BufferOrch::processIngressBufferProfileList(KeyOpFieldsValue return task_process_status::task_success; } + IngressBufferProfileListConfig cfg; + m_bufHlpr.getBufferConfig(cfg, key); + + for (const auto &cit : kfvFieldsValues(tuple)) + { + auto field = fvField(cit); + auto value = fvValue(cit); + + SWSS_LOG_DEBUG("FIELD: %s, VALUE: %s", field.c_str(), value.c_str()); + + cfg.fieldValueMap[field] = value; + } + + m_bufHlpr.parseBufferConfig(cfg); + + if (cfg.profile_list.is_set) + { + for (const auto &cit : cfg.profile_list.value) + { + BufferProfileConfig profCfg; + + if (m_bufHlpr.getBufferConfig(profCfg, cit)) + { + if (profCfg.isTrimmingEligible) + { + SWSS_LOG_ERROR( + "Failed to configure ingress buffer profile list(%s): buffer profile(%s) is trimming eligible", + key.c_str(), cit.c_str() + ); + return task_process_status::task_failed; + } + } + } + } + + m_bufHlpr.setBufferConfig(key, cfg); + setObjectReference(m_buffer_type_maps, APP_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME, key, buffer_profile_list_field_name, profile_name_list); attr.value.objlist.count = (uint32_t)profile_list.size(); @@ -1599,6 +1696,7 @@ task_process_status BufferOrch::processIngressBufferProfileList(KeyOpFieldsValue { SWSS_LOG_NOTICE("%s has been removed from BUFFER_PORT_INGRESS_PROFILE_LIST_TABLE", key.c_str()); removeObject(m_buffer_type_maps, APP_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME, key); + m_bufHlpr.delIngressBufferProfileListConfig(key); attr.value.objlist.count = 0; attr.value.objlist.list = profile_list.data(); } @@ -1709,7 +1807,9 @@ task_process_status BufferOrch::processEgressBufferProfileList(KeyOpFieldsValues Port port; string key = kfvKey(tuple); string op = kfvOp(tuple); - SWSS_LOG_DEBUG("processing:%s", key.c_str()); + + SWSS_LOG_DEBUG("KEY: %s, OP: %s", key.c_str(), op.c_str()); + vector port_names = tokenize(key, list_item_delimiter); vector profile_list; sai_attribute_t attr; @@ -1740,6 +1840,43 @@ task_process_status BufferOrch::processEgressBufferProfileList(KeyOpFieldsValues return task_process_status::task_success; } + EgressBufferProfileListConfig cfg; + m_bufHlpr.getBufferConfig(cfg, key); + + for (const auto &cit : kfvFieldsValues(tuple)) + { + auto field = fvField(cit); + auto value = fvValue(cit); + + SWSS_LOG_DEBUG("FIELD: %s, VALUE: %s", field.c_str(), value.c_str()); + + cfg.fieldValueMap[field] = value; + } + + m_bufHlpr.parseBufferConfig(cfg); + + if (cfg.profile_list.is_set) + { + for (const auto &cit : cfg.profile_list.value) + { + BufferProfileConfig profCfg; + + if (m_bufHlpr.getBufferConfig(profCfg, cit)) + { + if (profCfg.isTrimmingEligible) + { + SWSS_LOG_ERROR( + "Failed to configure egress buffer profile list(%s): buffer profile(%s) is trimming eligible", + key.c_str(), cit.c_str() + ); + return task_process_status::task_failed; + } + } + } + } + + m_bufHlpr.setBufferConfig(key, cfg); + setObjectReference(m_buffer_type_maps, APP_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME, key, buffer_profile_list_field_name, profile_name_list); attr.value.objlist.count = (uint32_t)profile_list.size(); @@ -1749,6 +1886,7 @@ task_process_status BufferOrch::processEgressBufferProfileList(KeyOpFieldsValues { SWSS_LOG_NOTICE("%s has been removed from BUFFER_PORT_EGRESS_PROFILE_LIST_TABLE", key.c_str()); removeObject(m_buffer_type_maps, APP_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME, key); + m_bufHlpr.delEgressBufferProfileListConfig(key); attr.value.objlist.count = 0; attr.value.objlist.list = profile_list.data(); } diff --git a/orchagent/bufferorch.h b/orchagent/bufferorch.h index aba955a7..f57f2f03 100644 --- a/orchagent/bufferorch.h +++ b/orchagent/bufferorch.h @@ -9,6 +9,8 @@ #include "redisapi.h" #include "saiattr.h" +#include "buffer/bufferhelper.h" + #define BUFFER_POOL_WATERMARK_STAT_COUNTER_FLEX_COUNTER_GROUP "BUFFER_POOL_WATERMARK_STAT_COUNTER" #define BUFFER_POOL_WATERMARK_FLEX_STAT_COUNTER_POLL_MSECS "60000" @@ -160,6 +162,9 @@ class BufferOrch : public Orch std::map> m_portEgressBufferProfileListBulk; std::map> m_priorityGroupBulk; std::map> m_queueBulk; + + // Buffer OA helper + BufferHelper m_bufHlpr; }; #endif /* SWSS_BUFFORCH_H */ diff --git a/orchagent/switch/trimming/capabilities.cpp b/orchagent/switch/trimming/capabilities.cpp index c4c8efc7..9992fa3a 100644 --- a/orchagent/switch/trimming/capabilities.cpp +++ b/orchagent/switch/trimming/capabilities.cpp @@ -29,7 +29,10 @@ using namespace swss; // defines ------------------------------------------------------------------------------------------------------------ +#define CAPABILITY_SWITCH_DSCP_RESOLUTION_MODE_FIELD "SWITCH|PACKET_TRIMMING_DSCP_RESOLUTION_MODE" #define CAPABILITY_SWITCH_QUEUE_RESOLUTION_MODE_FIELD "SWITCH|PACKET_TRIMMING_QUEUE_RESOLUTION_MODE" +#define CAPABILITY_SWITCH_NUMBER_OF_TRAFFIC_CLASSES_FIELD "SWITCH|NUMBER_OF_TRAFFIC_CLASSES" +#define CAPABILITY_SWITCH_NUMBER_OF_UNICAST_QUEUES_FIELD "SWITCH|NUMBER_OF_UNICAST_QUEUES" #define CAPABILITY_SWITCH_TRIMMING_CAPABLE_FIELD "SWITCH_TRIMMING_CAPABLE" @@ -40,7 +43,13 @@ using namespace swss; // constants ---------------------------------------------------------------------------------------------------------- -static const std::unordered_map modeMap = +static const std::unordered_map dscpModeMap = +{ + { SAI_PACKET_TRIM_DSCP_RESOLUTION_MODE_DSCP_VALUE, SWITCH_TRIMMING_DSCP_MODE_DSCP_VALUE }, + { SAI_PACKET_TRIM_DSCP_RESOLUTION_MODE_FROM_TC, SWITCH_TRIMMING_DSCP_MODE_FROM_TC } +}; + +static const std::unordered_map queueModeMap = { { SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_STATIC, SWITCH_TRIMMING_QUEUE_MODE_STATIC }, { SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_DYNAMIC, SWITCH_TRIMMING_QUEUE_MODE_DYNAMIC } @@ -49,6 +58,7 @@ static const std::unordered_mapattridname : "UNKNOWN"; } +static std::string toStr(sai_packet_trim_dscp_resolution_mode_t value) +{ + const auto *name = sai_metadata_get_packet_trim_dscp_resolution_mode_name(value); + + return name != nullptr ? name : "UNKNOWN"; +} + +static std::string toStr(const std::set &value) +{ + std::vector strList; + + for (const auto &cit1 : value) + { + const auto &cit2 = dscpModeMap.find(cit1); + if (cit2 != dscpModeMap.cend()) + { + strList.push_back(cit2->second); + } + } + + return join(",", strList.cbegin(), strList.cend()); +} + static std::string toStr(sai_packet_trim_queue_resolution_mode_t value) { const auto *name = sai_metadata_get_packet_trim_queue_resolution_mode_name(value); @@ -72,8 +105,8 @@ static std::string toStr(const std::set for (const auto &cit1 : value) { - const auto &cit2 = modeMap.find(cit1); - if (cit2 != modeMap.cend()) + const auto &cit2 = queueModeMap.find(cit1); + if (cit2 != queueModeMap.cend()) { strList.push_back(cit2->second); } @@ -82,6 +115,16 @@ static std::string toStr(const std::set return join(",", strList.cbegin(), strList.cend()); } +static std::string toStr(sai_uint8_t value) +{ + return std::to_string(value); +} + +static std::string toStr(sai_uint32_t value) +{ + return std::to_string(value); +} + static std::string toStr(bool value) { return value ? "true" : "false"; @@ -97,37 +140,100 @@ SwitchTrimmingCapabilities::SwitchTrimmingCapabilities() bool SwitchTrimmingCapabilities::isSwitchTrimmingSupported() const { - auto size = capabilities.size.isAttrSupported; - auto dscp = capabilities.dscp.isAttrSupported; - auto mode = capabilities.mode.isAttrSupported; - auto queue = true; + auto size = trimCap.size.isAttrSupported; + auto dscpMode = trimCap.dscp.mode.isAttrSupported; + auto dscp = true; + auto tc = true; + auto queueMode = trimCap.queue.mode.isAttrSupported; + auto queueIndex = true; + + // Do not care of dscp configuration capabilities, + // if DSCP_VALUE dscp resolution mode is not supported + if (trimCap.dscp.mode.isDscpValueModeSupported) + { + dscp = trimCap.dscp.isAttrSupported; + } + + // Do not care of tc configuration capabilities, + // if FROM_TC dscp resolution mode is not supported + if (trimCap.dscp.mode.isFromTcModeSupported) + { + tc = trimCap.tc.isAttrSupported; + } // Do not care of queue index configuration capabilities, - // if static queue resolution mode is not supported - if (capabilities.mode.isStaticModeSupported) + // if STATIC queue resolution mode is not supported + if (trimCap.queue.mode.isStaticModeSupported) { - queue = capabilities.queue.isAttrSupported; + queueIndex = trimCap.queue.index.isAttrSupported; + } + + return size && dscpMode && dscp && tc && queueMode && queueIndex; +} + +bool SwitchTrimmingCapabilities::validateTrimDscpModeCap(sai_packet_trim_dscp_resolution_mode_t value) const +{ + SWSS_LOG_ENTER(); + + if (!trimCap.dscp.mode.isEnumSupported) + { + return true; + } + + if (trimCap.dscp.mode.mSet.empty()) + { + SWSS_LOG_ERROR("Failed to validate dscp resolution mode: no capabilities"); + return false; + } + + if (trimCap.dscp.mode.mSet.count(value) == 0) + { + SWSS_LOG_ERROR("Failed to validate dscp resolution mode: value(%s) is not supported", toStr(value).c_str()); + return false; + } + + return true; +} + +bool SwitchTrimmingCapabilities::validateTrimTcCap(sai_uint8_t value) const +{ + SWSS_LOG_ENTER(); + + if (!genCap.tcNum.isAttrSupported) + { + return true; + } + + auto maxTC = genCap.tcNum.value - 1; + + if (!(value <= maxTC)) + { + SWSS_LOG_ERROR( + "Failed to validate traffic class: value(%u) is out of range: 0 <= class <= %u", + value, maxTC + ); + return false; } - return size && dscp && mode && queue; + return true; } -bool SwitchTrimmingCapabilities::validateQueueModeCap(sai_packet_trim_queue_resolution_mode_t value) const +bool SwitchTrimmingCapabilities::validateTrimQueueModeCap(sai_packet_trim_queue_resolution_mode_t value) const { SWSS_LOG_ENTER(); - if (!capabilities.mode.isEnumSupported) + if (!trimCap.queue.mode.isEnumSupported) { return true; } - if (capabilities.mode.mSet.empty()) + if (trimCap.queue.mode.mSet.empty()) { SWSS_LOG_ERROR("Failed to validate queue resolution mode: no capabilities"); return false; } - if (capabilities.mode.mSet.count(value) == 0) + if (trimCap.queue.mode.mSet.count(value) == 0) { SWSS_LOG_ERROR("Failed to validate queue resolution mode: value(%s) is not supported", toStr(value).c_str()); return false; @@ -136,6 +242,29 @@ bool SwitchTrimmingCapabilities::validateQueueModeCap(sai_packet_trim_queue_reso return true; } +bool SwitchTrimmingCapabilities::validateQueueIndexCap(sai_uint32_t value) const +{ + SWSS_LOG_ENTER(); + + if (!genCap.uqNum.isAttrSupported) + { + return true; + } + + auto maxUQIdx = genCap.uqNum.value - 1; + + if (!(value <= maxUQIdx)) + { + SWSS_LOG_ERROR( + "Failed to validate queue index: value(%u) is out of range: 0 <= index <= %u", + value, maxUQIdx + ); + return false; + } + + return true; +} + sai_status_t SwitchTrimmingCapabilities::queryEnumCapabilitiesSai(std::vector &capList, sai_object_type_t objType, sai_attr_id_t attrId) const { sai_s32_list_t enumList = { .count = 0, .list = nullptr }; @@ -184,7 +313,81 @@ void SwitchTrimmingCapabilities::queryTrimSizeAttrCapabilities() return; } - capabilities.size.isAttrSupported = true; + trimCap.size.isAttrSupported = true; +} + +void SwitchTrimmingCapabilities::queryTrimDscpModeEnumCapabilities() +{ + SWSS_LOG_ENTER(); + + std::vector mList; + auto status = queryEnumCapabilitiesSai( + mList, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) enum value capabilities", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE).c_str() + ); + return; + } + + auto &mSet = trimCap.dscp.mode.mSet; + std::transform( + mList.cbegin(), mList.cend(), std::inserter(mSet, mSet.begin()), + [](sai_int32_t value) { return static_cast(value); } + ); + + if (!mSet.empty()) + { + if (mSet.count(SAI_PACKET_TRIM_DSCP_RESOLUTION_MODE_DSCP_VALUE) == 0) + { + trimCap.dscp.mode.isDscpValueModeSupported = false; + } + + if (mSet.count(SAI_PACKET_TRIM_DSCP_RESOLUTION_MODE_FROM_TC) == 0) + { + trimCap.dscp.mode.isFromTcModeSupported = false; + } + } + else + { + trimCap.dscp.mode.isDscpValueModeSupported = false; + trimCap.dscp.mode.isFromTcModeSupported = false; + } + + trimCap.dscp.mode.isEnumSupported = true; +} + +void SwitchTrimmingCapabilities::queryTrimDscpModeAttrCapabilities() +{ + SWSS_LOG_ENTER(); + + sai_attr_capability_t attrCap; + + auto status = queryAttrCapabilitiesSai( + attrCap, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) capabilities", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE).c_str() + ); + return; + } + + if (!attrCap.set_implemented) + { + SWSS_LOG_WARN( + "Attribute(%s) SET is not implemented in SAI", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE).c_str() + ); + return; + } + + trimCap.dscp.mode.isAttrSupported = true; } void SwitchTrimmingCapabilities::queryTrimDscpAttrCapabilities() @@ -214,10 +417,40 @@ void SwitchTrimmingCapabilities::queryTrimDscpAttrCapabilities() return; } - capabilities.dscp.isAttrSupported = true; + trimCap.dscp.isAttrSupported = true; +} + +void SwitchTrimmingCapabilities::queryTrimTcAttrCapabilities() +{ + SWSS_LOG_ENTER(); + + sai_attr_capability_t attrCap; + + auto status = queryAttrCapabilitiesSai( + attrCap, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) capabilities", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE).c_str() + ); + return; + } + + if (!attrCap.set_implemented) + { + SWSS_LOG_WARN( + "Attribute(%s) SET is not implemented in SAI", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE).c_str() + ); + return; + } + + trimCap.tc.isAttrSupported = true; } -void SwitchTrimmingCapabilities::queryTrimModeEnumCapabilities() +void SwitchTrimmingCapabilities::queryTrimQueueModeEnumCapabilities() { SWSS_LOG_ENTER(); @@ -234,7 +467,7 @@ void SwitchTrimmingCapabilities::queryTrimModeEnumCapabilities() return; } - auto &mSet = capabilities.mode.mSet; + auto &mSet = trimCap.queue.mode.mSet; std::transform( mList.cbegin(), mList.cend(), std::inserter(mSet, mSet.begin()), [](sai_int32_t value) { return static_cast(value); } @@ -242,13 +475,13 @@ void SwitchTrimmingCapabilities::queryTrimModeEnumCapabilities() if (mSet.empty() || (mSet.count(SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_STATIC) == 0)) { - capabilities.mode.isStaticModeSupported = false; + trimCap.queue.mode.isStaticModeSupported = false; } - capabilities.mode.isEnumSupported = true; + trimCap.queue.mode.isEnumSupported = true; } -void SwitchTrimmingCapabilities::queryTrimModeAttrCapabilities() +void SwitchTrimmingCapabilities::queryTrimQueueModeAttrCapabilities() { SWSS_LOG_ENTER(); @@ -275,10 +508,10 @@ void SwitchTrimmingCapabilities::queryTrimModeAttrCapabilities() return; } - capabilities.mode.isAttrSupported = true; + trimCap.queue.mode.isAttrSupported = true; } -void SwitchTrimmingCapabilities::queryTrimQueueAttrCapabilities() +void SwitchTrimmingCapabilities::queryTrimQueueIndexAttrCapabilities() { SWSS_LOG_ENTER(); @@ -305,18 +538,132 @@ void SwitchTrimmingCapabilities::queryTrimQueueAttrCapabilities() return; } - capabilities.queue.isAttrSupported = true; + trimCap.queue.index.isAttrSupported = true; +} + +void SwitchTrimmingCapabilities::queryTrimTrafficClassNumberAttrCapabilities() +{ + SWSS_LOG_ENTER(); + + sai_attr_capability_t attrCap; + + auto status = queryAttrCapabilitiesSai( + attrCap, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_QOS_MAX_NUMBER_OF_TRAFFIC_CLASSES + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) capabilities", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_QOS_MAX_NUMBER_OF_TRAFFIC_CLASSES).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_QOS_MAX_NUMBER_OF_TRAFFIC_CLASSES).c_str() + ); + return; + } + + sai_attribute_t attr; + attr.id = SAI_SWITCH_ATTR_QOS_MAX_NUMBER_OF_TRAFFIC_CLASSES; + + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) value", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_QOS_MAX_NUMBER_OF_TRAFFIC_CLASSES).c_str() + ); + return; + } + + if (attr.value.u8 == 0) + { + SWSS_LOG_WARN( + "Unexpected attribute(%s) value: traffic classes are not supported", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_QOS_MAX_NUMBER_OF_TRAFFIC_CLASSES).c_str() + ); + return; + } + + genCap.tcNum.isAttrSupported = true; + genCap.tcNum.value = attr.value.u8; +} + +void SwitchTrimmingCapabilities::queryTrimUnicastQueueNumberAttrCapabilities() +{ + SWSS_LOG_ENTER(); + + sai_attr_capability_t attrCap; + + auto status = queryAttrCapabilitiesSai( + attrCap, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_NUMBER_OF_UNICAST_QUEUES + ); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) capabilities", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_NUMBER_OF_UNICAST_QUEUES).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_NUMBER_OF_UNICAST_QUEUES).c_str() + ); + return; + } + + sai_attribute_t attr; + attr.id = SAI_SWITCH_ATTR_NUMBER_OF_UNICAST_QUEUES; + + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR( + "Failed to get attribute(%s) value", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_NUMBER_OF_UNICAST_QUEUES).c_str() + ); + return; + } + + if (attr.value.u32 == 0) + { + SWSS_LOG_WARN( + "Unexpected attribute(%s) value: unicast queues are not supported", + toStr(SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_NUMBER_OF_UNICAST_QUEUES).c_str() + ); + return; + } + + genCap.uqNum.isAttrSupported = true; + genCap.uqNum.value = attr.value.u32; } void SwitchTrimmingCapabilities::queryCapabilities() { queryTrimSizeAttrCapabilities(); + + queryTrimDscpModeEnumCapabilities(); + queryTrimDscpModeAttrCapabilities(); + queryTrimDscpAttrCapabilities(); + queryTrimTcAttrCapabilities(); - queryTrimModeEnumCapabilities(); - queryTrimModeAttrCapabilities(); + queryTrimQueueModeEnumCapabilities(); + queryTrimQueueModeAttrCapabilities(); - queryTrimQueueAttrCapabilities(); + queryTrimQueueIndexAttrCapabilities(); + + queryTrimTrafficClassNumberAttrCapabilities(); + queryTrimUnicastQueueNumberAttrCapabilities(); } FieldValueTuple SwitchTrimmingCapabilities::makeSwitchTrimmingCapDbEntry() const @@ -327,10 +674,34 @@ FieldValueTuple SwitchTrimmingCapabilities::makeSwitchTrimmingCapDbEntry() const return FieldValueTuple(field, value); } +FieldValueTuple SwitchTrimmingCapabilities::makeDscpModeCapDbEntry() const +{ + auto field = CAPABILITY_SWITCH_DSCP_RESOLUTION_MODE_FIELD; + auto value = trimCap.dscp.mode.isEnumSupported ? toStr(trimCap.dscp.mode.mSet) : "N/A"; + + return FieldValueTuple(field, value); +} + FieldValueTuple SwitchTrimmingCapabilities::makeQueueModeCapDbEntry() const { auto field = CAPABILITY_SWITCH_QUEUE_RESOLUTION_MODE_FIELD; - auto value = capabilities.mode.isEnumSupported ? toStr(capabilities.mode.mSet) : "N/A"; + auto value = trimCap.queue.mode.isEnumSupported ? toStr(trimCap.queue.mode.mSet) : "N/A"; + + return FieldValueTuple(field, value); +} + +FieldValueTuple SwitchTrimmingCapabilities::makeTrafficClassNumberCapDbEntry() const +{ + auto field = CAPABILITY_SWITCH_NUMBER_OF_TRAFFIC_CLASSES_FIELD; + auto value = genCap.tcNum.isAttrSupported ? toStr(genCap.tcNum.value) : "N/A"; + + return FieldValueTuple(field, value); +} + +FieldValueTuple SwitchTrimmingCapabilities::makeUnicastQueueNumberCapDbEntry() const +{ + auto field = CAPABILITY_SWITCH_NUMBER_OF_UNICAST_QUEUES_FIELD; + auto value = genCap.uqNum.isAttrSupported ? toStr(genCap.uqNum.value) : "N/A"; return FieldValueTuple(field, value); } @@ -344,7 +715,10 @@ void SwitchTrimmingCapabilities::writeCapabilitiesToDb() std::vector fvList = { makeSwitchTrimmingCapDbEntry(), - makeQueueModeCapDbEntry() + makeDscpModeCapDbEntry(), + makeQueueModeCapDbEntry(), + makeTrafficClassNumberCapDbEntry(), + makeUnicastQueueNumberCapDbEntry() }; capTable.set(CAPABILITY_KEY, fvList); diff --git a/orchagent/switch/trimming/capabilities.h b/orchagent/switch/trimming/capabilities.h index 52ced127..ee62889f 100644 --- a/orchagent/switch/trimming/capabilities.h +++ b/orchagent/switch/trimming/capabilities.h @@ -17,20 +17,31 @@ class SwitchTrimmingCapabilities final bool isSwitchTrimmingSupported() const; - bool validateQueueModeCap(sai_packet_trim_queue_resolution_mode_t value) const; + bool validateTrimDscpModeCap(sai_packet_trim_dscp_resolution_mode_t value) const; + bool validateTrimTcCap(sai_uint8_t value) const; + bool validateTrimQueueModeCap(sai_packet_trim_queue_resolution_mode_t value) const; + bool validateQueueIndexCap(sai_uint32_t value) const; private: swss::FieldValueTuple makeSwitchTrimmingCapDbEntry() const; + swss::FieldValueTuple makeDscpModeCapDbEntry() const; swss::FieldValueTuple makeQueueModeCapDbEntry() const; + swss::FieldValueTuple makeTrafficClassNumberCapDbEntry() const; + swss::FieldValueTuple makeUnicastQueueNumberCapDbEntry() 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 queryTrimSizeAttrCapabilities(); + void queryTrimDscpModeEnumCapabilities(); + void queryTrimDscpModeAttrCapabilities(); void queryTrimDscpAttrCapabilities(); - void queryTrimModeEnumCapabilities(); - void queryTrimModeAttrCapabilities(); - void queryTrimQueueAttrCapabilities(); + void queryTrimTcAttrCapabilities(); + void queryTrimQueueModeEnumCapabilities(); + void queryTrimQueueModeAttrCapabilities(); + void queryTrimQueueIndexAttrCapabilities(); + void queryTrimTrafficClassNumberAttrCapabilities(); + void queryTrimUnicastQueueNumberAttrCapabilities(); void queryCapabilities(); void writeCapabilitiesToDb(); @@ -41,18 +52,47 @@ class SwitchTrimmingCapabilities final } size; // SAI_SWITCH_ATTR_PACKET_TRIM_SIZE struct { + struct { + std::set mSet; + bool isDscpValueModeSupported = true; + bool isFromTcModeSupported = true; + bool isEnumSupported = false; + bool isAttrSupported = false; + } mode; // SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE + bool isAttrSupported = false; } dscp; // SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE struct { - std::set mSet; - bool isStaticModeSupported = true; - bool isEnumSupported = false; bool isAttrSupported = false; - } mode; // SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE + } tc; // SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE + + struct { + struct { + std::set mSet; + bool isStaticModeSupported = true; + bool isEnumSupported = false; + bool isAttrSupported = false; + } mode; // SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE + + struct { + bool isAttrSupported = false; + } index; // SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX + } queue; + } trimCap; + + struct + { + struct { + sai_uint8_t value; + bool is_set = false; + bool isAttrSupported = false; + } tcNum; // SAI_SWITCH_ATTR_QOS_MAX_NUMBER_OF_TRAFFIC_CLASSES struct { + sai_uint32_t value; + bool is_set = false; bool isAttrSupported = false; - } queue; // SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX - } capabilities; + } uqNum; // SAI_SWITCH_ATTR_NUMBER_OF_UNICAST_QUEUES + } genCap; }; diff --git a/orchagent/switch/trimming/container.h b/orchagent/switch/trimming/container.h index 7a9ad1d6..045c2a04 100644 --- a/orchagent/switch/trimming/container.h +++ b/orchagent/switch/trimming/container.h @@ -19,15 +19,31 @@ class SwitchTrimming final } size; // Trim packets to this size to reduce bandwidth struct { + struct { + sai_packet_trim_dscp_resolution_mode_t value; + bool is_set = false; + } mode; + sai_uint8_t value; bool is_set = false; } dscp; // New packet trimming DSCP value + struct { + struct { + sai_uint8_t value; + bool is_set = false; + } cache; + + sai_uint8_t value; + bool is_set = false; + } tc; // New packet trimming TC value + struct { struct { sai_packet_trim_queue_resolution_mode_t value; bool is_set = false; } mode; + struct { sai_uint8_t value; bool is_set = false; diff --git a/orchagent/switch/trimming/helper.cpp b/orchagent/switch/trimming/helper.cpp index 0c2f5b81..7484035c 100644 --- a/orchagent/switch/trimming/helper.cpp +++ b/orchagent/switch/trimming/helper.cpp @@ -39,6 +39,11 @@ static inline std::uint32_t toUInt32(const std::string &str) // helper ------------------------------------------------------------------------------------------------------------- +bool SwitchTrimmingHelper::isSymDscpMode(const SwitchTrimming &cfg) const +{ + return cfg.dscp.mode.is_set && (cfg.dscp.mode.value == SAI_PACKET_TRIM_DSCP_RESOLUTION_MODE_DSCP_VALUE); +} + bool SwitchTrimmingHelper::isStaticQueueMode(const SwitchTrimming &cfg) const { return cfg.queue.mode.is_set && (cfg.queue.mode.value == SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_STATIC); @@ -54,7 +59,7 @@ void SwitchTrimmingHelper::setConfig(const SwitchTrimming &value) cfg = value; } -bool SwitchTrimmingHelper::parseSize(SwitchTrimming &cfg, const std::string &field, const std::string &value) const +bool SwitchTrimmingHelper::parseTrimSize(SwitchTrimming &cfg, const std::string &field, const std::string &value) const { SWSS_LOG_ENTER(); @@ -78,7 +83,7 @@ bool SwitchTrimmingHelper::parseSize(SwitchTrimming &cfg, const std::string &fie return true; } -bool SwitchTrimmingHelper::parseDscp(SwitchTrimming &cfg, const std::string &field, const std::string &value) const +bool SwitchTrimmingHelper::parseTrimDscp(SwitchTrimming &cfg, const std::string &field, const std::string &value) const { SWSS_LOG_ENTER(); @@ -88,6 +93,13 @@ bool SwitchTrimmingHelper::parseDscp(SwitchTrimming &cfg, const std::string &fie return false; } + if (boost::algorithm::to_lower_copy(value) == SWITCH_TRIMMING_DSCP_VALUE_FROM_TC) + { + cfg.dscp.mode.value = SAI_PACKET_TRIM_DSCP_RESOLUTION_MODE_FROM_TC; + cfg.dscp.mode.is_set = true; + return true; + } + try { cfg.dscp.value = toUInt8(value); @@ -102,16 +114,43 @@ bool SwitchTrimmingHelper::parseDscp(SwitchTrimming &cfg, const std::string &fie if (!((minDscp <= cfg.dscp.value) && (cfg.dscp.value <= maxDscp))) { SWSS_LOG_ERROR( - "Failed to parse field(%s): value(%s) is out of range: %u <= speed <= %u", + "Failed to parse field(%s): value(%s) is out of range: %u <= dscp <= %u", field.c_str(), value.c_str(), minDscp, maxDscp ); return false; } + cfg.dscp.mode.value = SAI_PACKET_TRIM_DSCP_RESOLUTION_MODE_DSCP_VALUE; + cfg.dscp.mode.is_set = true; + + return true; +} + +bool SwitchTrimmingHelper::parseTrimTc(SwitchTrimming &cfg, 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 + { + cfg.tc.value = toUInt8(value); + cfg.tc.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 SwitchTrimmingHelper::parseQueue(SwitchTrimming &cfg, const std::string &field, const std::string &value) const +bool SwitchTrimmingHelper::parseTrimQueue(SwitchTrimming &cfg, const std::string &field, const std::string &value) const { SWSS_LOG_ENTER(); @@ -145,7 +184,7 @@ bool SwitchTrimmingHelper::parseQueue(SwitchTrimming &cfg, const std::string &fi return true; } -bool SwitchTrimmingHelper::parseConfig(SwitchTrimming &cfg) const +bool SwitchTrimmingHelper::parseTrimConfig(SwitchTrimming &cfg) const { SWSS_LOG_ENTER(); @@ -156,21 +195,28 @@ bool SwitchTrimmingHelper::parseConfig(SwitchTrimming &cfg) const if (field == SWITCH_TRIMMING_SIZE) { - if (!parseSize(cfg, field, value)) + if (!parseTrimSize(cfg, field, value)) { return false; } } else if (field == SWITCH_TRIMMING_DSCP_VALUE) { - if (!parseDscp(cfg, field, value)) + if (!parseTrimDscp(cfg, field, value)) + { + return false; + } + } + else if (field == SWITCH_TRIMMING_TC_VALUE) + { + if (!parseTrimTc(cfg, field, value)) { return false; } } else if (field == SWITCH_TRIMMING_QUEUE_INDEX) { - if (!parseQueue(cfg, field, value)) + if (!parseTrimQueue(cfg, field, value)) { return false; } @@ -181,14 +227,14 @@ bool SwitchTrimmingHelper::parseConfig(SwitchTrimming &cfg) const } } - return validateConfig(cfg); + return validateTrimConfig(cfg); } -bool SwitchTrimmingHelper::validateConfig(SwitchTrimming &cfg) const +bool SwitchTrimmingHelper::validateTrimConfig(SwitchTrimming &cfg) const { SWSS_LOG_ENTER(); - auto cond = cfg.size.is_set || cfg.dscp.is_set || cfg.queue.mode.is_set; + auto cond = cfg.size.is_set || cfg.dscp.mode.is_set || cfg.tc.is_set || cfg.queue.mode.is_set; if (!cond) { diff --git a/orchagent/switch/trimming/helper.h b/orchagent/switch/trimming/helper.h index f805b58d..7cc8eb88 100644 --- a/orchagent/switch/trimming/helper.h +++ b/orchagent/switch/trimming/helper.h @@ -10,19 +10,21 @@ class SwitchTrimmingHelper final SwitchTrimmingHelper() = default; ~SwitchTrimmingHelper() = default; + bool isSymDscpMode(const SwitchTrimming &cfg) const; bool isStaticQueueMode(const SwitchTrimming &cfg) const; const SwitchTrimming& getConfig() const; void setConfig(const SwitchTrimming &cfg); - bool parseConfig(SwitchTrimming &cfg) const; + bool parseTrimConfig(SwitchTrimming &cfg) const; private: - bool parseSize(SwitchTrimming &cfg, const std::string &field, const std::string &value) const; - bool parseDscp(SwitchTrimming &cfg, const std::string &field, const std::string &value) const; - bool parseQueue(SwitchTrimming &cfg, const std::string &field, const std::string &value) const; + bool parseTrimSize(SwitchTrimming &cfg, const std::string &field, const std::string &value) const; + bool parseTrimDscp(SwitchTrimming &cfg, const std::string &field, const std::string &value) const; + bool parseTrimTc(SwitchTrimming &cfg, const std::string &field, const std::string &value) const; + bool parseTrimQueue(SwitchTrimming &cfg, const std::string &field, const std::string &value) const; - bool validateConfig(SwitchTrimming &cfg) const; + bool validateTrimConfig(SwitchTrimming &cfg) const; private: SwitchTrimming cfg; diff --git a/orchagent/switch/trimming/schema.h b/orchagent/switch/trimming/schema.h index 3069b247..7bc58a88 100644 --- a/orchagent/switch/trimming/schema.h +++ b/orchagent/switch/trimming/schema.h @@ -2,6 +2,11 @@ // defines ------------------------------------------------------------------------------------------------------------ +#define SWITCH_TRIMMING_DSCP_VALUE_FROM_TC "from-tc" + +#define SWITCH_TRIMMING_DSCP_MODE_DSCP_VALUE "DSCP_VALUE" +#define SWITCH_TRIMMING_DSCP_MODE_FROM_TC "FROM_TC" + #define SWITCH_TRIMMING_QUEUE_INDEX_DYNAMIC "dynamic" #define SWITCH_TRIMMING_QUEUE_MODE_STATIC "STATIC" @@ -9,4 +14,5 @@ #define SWITCH_TRIMMING_SIZE "size" #define SWITCH_TRIMMING_DSCP_VALUE "dscp_value" +#define SWITCH_TRIMMING_TC_VALUE "tc_value" #define SWITCH_TRIMMING_QUEUE_INDEX "queue_index" diff --git a/orchagent/switchorch.cpp b/orchagent/switchorch.cpp index 54441777..a4d16f2e 100644 --- a/orchagent/switchorch.cpp +++ b/orchagent/switchorch.cpp @@ -911,6 +911,17 @@ bool SwitchOrch::setSwitchTrimmingSizeSai(const SwitchTrimming &trim) const return status == SAI_STATUS_SUCCESS; } +bool SwitchOrch::setSwitchTrimmingDscpModeSai(const SwitchTrimming &trim) const +{ + sai_attribute_t attr; + + attr.id = SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE; + attr.value.s32 = trim.dscp.mode.value; + + auto status = sai_switch_api->set_switch_attribute(gSwitchId, &attr); + return status == SAI_STATUS_SUCCESS; +} + bool SwitchOrch::setSwitchTrimmingDscpSai(const SwitchTrimming &trim) const { sai_attribute_t attr; @@ -922,6 +933,17 @@ bool SwitchOrch::setSwitchTrimmingDscpSai(const SwitchTrimming &trim) const return status == SAI_STATUS_SUCCESS; } +bool SwitchOrch::setSwitchTrimmingTcSai(const SwitchTrimming &trim) const +{ + sai_attribute_t attr; + + attr.id = SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE; + attr.value.u8 = trim.tc.value; + + auto status = sai_switch_api->set_switch_attribute(gSwitchId, &attr); + return status == SAI_STATUS_SUCCESS; +} + bool SwitchOrch::setSwitchTrimmingQueueModeSai(const SwitchTrimming &trim) const { sai_attribute_t attr; @@ -949,8 +971,15 @@ bool SwitchOrch::setSwitchTrimming(const SwitchTrimming &trim) SWSS_LOG_ENTER(); auto tObj = trimHlpr.getConfig(); + + auto dscpBak = false; + auto tcBak = false; + auto queueBak = false; + + auto tcUpdate = false; + auto tcSync = false; + auto cfgUpd = false; - auto qIdxBak = false; if (!trimCap.isSwitchTrimmingSupported()) { @@ -980,24 +1009,104 @@ bool SwitchOrch::setSwitchTrimming(const SwitchTrimming &trim) } } + if (trim.dscp.mode.is_set) + { + if (!tObj.dscp.mode.is_set || (tObj.dscp.mode.value != trim.dscp.mode.value)) + { + if (!trimCap.validateTrimDscpModeCap(trim.dscp.mode.value)) + { + SWSS_LOG_ERROR("Failed to validate switch trimming DSCP mode: capability is not supported"); + return false; + } + + if (!setSwitchTrimmingDscpModeSai(trim)) + { + SWSS_LOG_ERROR("Failed to set switch trimming DSCP mode in SAI"); + return false; + } + + if (trimHlpr.isSymDscpMode(tObj)) + { + dscpBak = true; + } + + if (!trimHlpr.isSymDscpMode(trim)) + { + if (!tObj.tc.cache.is_set) + { + tcUpdate = true; + } + else + { + tObj.tc.value = tObj.tc.cache.value; + } + } + + cfgUpd = true; + } + } + else + { + if (tObj.dscp.mode.is_set) + { + SWSS_LOG_ERROR("Failed to remove switch trimming DSCP configuration: operation is not supported"); + return false; + } + } + if (trim.dscp.is_set) { if (!tObj.dscp.is_set || (tObj.dscp.value != trim.dscp.value)) { if (!setSwitchTrimmingDscpSai(trim)) { - SWSS_LOG_ERROR("Failed to set switch trimming DSCP in SAI"); + SWSS_LOG_ERROR("Failed to set switch trimming DSCP value in SAI"); return false; } cfgUpd = true; } } + + if (trim.tc.is_set) + { + if (!tObj.tc.is_set || (tObj.tc.value != trim.tc.value) || tcUpdate) + { + if (!trimHlpr.isSymDscpMode(trim)) + { + if (!trimCap.validateTrimTcCap(trim.tc.value)) + { + SWSS_LOG_ERROR("Failed to validate switch trimming TC value: capability is not supported"); + return false; + } + + if (!setSwitchTrimmingTcSai(trim)) + { + SWSS_LOG_ERROR("Failed to set switch trimming TC value in SAI"); + return false; + } + + tcSync = true; + } + else + { + SWSS_LOG_WARN("Skip setting switch trimming TC value for symmetric DSCP mode"); + } + + cfgUpd = true; + } + + // Cache synchronization and backup are mutually exclusive + if (!tcSync) + { + tcBak = true; + } + } else { - if (tObj.dscp.is_set) + if (tObj.tc.is_set) { - SWSS_LOG_ERROR("Failed to remove switch trimming DSCP configuration: operation is not supported"); + SWSS_LOG_ERROR("Failed to remove switch trimming TC configuration: operation is not supported"); return false; } } @@ -1006,7 +1115,7 @@ bool SwitchOrch::setSwitchTrimming(const SwitchTrimming &trim) { if (!tObj.queue.mode.is_set || (tObj.queue.mode.value != trim.queue.mode.value)) { - if (!trimCap.validateQueueModeCap(trim.queue.mode.value)) + if (!trimCap.validateTrimQueueModeCap(trim.queue.mode.value)) { SWSS_LOG_ERROR("Failed to validate switch trimming queue mode: capability is not supported"); return false; @@ -1020,7 +1129,7 @@ bool SwitchOrch::setSwitchTrimming(const SwitchTrimming &trim) if (trimHlpr.isStaticQueueMode(tObj)) { - qIdxBak = true; + queueBak = true; } cfgUpd = true; @@ -1039,6 +1148,12 @@ bool SwitchOrch::setSwitchTrimming(const SwitchTrimming &trim) { if (!tObj.queue.index.is_set || (tObj.queue.index.value != trim.queue.index.value)) { + if (!trimCap.validateQueueIndexCap(trim.queue.index.value)) + { + SWSS_LOG_ERROR("Failed to validate switch trimming queue index: capability is not supported"); + return false; + } + if (!setSwitchTrimmingQueueIndexSai(trim)) { SWSS_LOG_ERROR("Failed to set switch trimming queue index in SAI"); @@ -1056,10 +1171,32 @@ bool SwitchOrch::setSwitchTrimming(const SwitchTrimming &trim) return true; } - if (qIdxBak) // Override queue index configuration during transition from static -> dynamic + if (dscpBak || tcBak || queueBak || tcSync) // Custom configuration update { auto cfg = trim; - cfg.queue.index = tObj.queue.index; + + if (dscpBak) // Override dscp configuration during transition from symmetric -> asymmetric + { + cfg.dscp = tObj.dscp; + cfg.dscp.mode = trim.dscp.mode; + } + + if (tcBak) // Override tc configuration to pass synchronization cache + { + cfg.tc.cache = tObj.tc.cache; + } + + if (queueBak) // override queue configuration during transition from static -> dynamic + { + cfg.queue.index = tObj.queue.index; + } + + if (tcSync) // Update tc synchronization cache + { + cfg.tc.cache.value = trim.tc.value; + cfg.tc.cache.is_set = true; + } + trimHlpr.setConfig(cfg); } else // Regular configuration update @@ -1108,7 +1245,7 @@ void SwitchOrch::doCfgSwitchTrimmingTableTask(Consumer &consumer) trim.fieldValueMap[fieldName] = fieldValue; } - if (trimHlpr.parseConfig(trim)) + if (trimHlpr.parseTrimConfig(trim)) { if (!setSwitchTrimming(trim)) { diff --git a/orchagent/switchorch.h b/orchagent/switchorch.h index e00a5a86..c98c430c 100644 --- a/orchagent/switchorch.h +++ b/orchagent/switchorch.h @@ -92,7 +92,9 @@ class SwitchOrch : public Orch // Switch trimming bool setSwitchTrimmingSizeSai(const SwitchTrimming &trim) const; + bool setSwitchTrimmingDscpModeSai(const SwitchTrimming &trim) const; bool setSwitchTrimmingDscpSai(const SwitchTrimming &trim) const; + bool setSwitchTrimmingTcSai(const SwitchTrimming &trim) const; bool setSwitchTrimmingQueueModeSai(const SwitchTrimming &trim) const; bool setSwitchTrimmingQueueIndexSai(const SwitchTrimming &trim) const; bool setSwitchTrimming(const SwitchTrimming &trim); diff --git a/tests/buffer_model.py b/tests/buffer_model.py index ae2d1ecb..b63d37a8 100644 --- a/tests/buffer_model.py +++ b/tests/buffer_model.py @@ -1,7 +1,12 @@ import re import time + lossless_profile_name_pattern = 'pg_lossless_([1-9][0-9]*000)_([1-9][0-9]*m)_profile' +zero_profile_name_pattern = '.+_zero_profile' +zero_pool_name_pattern = '.+_zero_pool' + + def enable_dynamic_buffer(config_db, cmd_runner): # check whether it's already running dynamic mode device_meta = config_db.get_entry('DEVICE_METADATA', 'localhost') @@ -55,7 +60,10 @@ def enable_dynamic_buffer(config_db, cmd_runner): time.sleep(20) -def disable_dynamic_buffer(config_db, cmd_runner): +def disable_dynamic_buffer(dvs): + config_db = dvs.get_config_db() + app_db = dvs.get_app_db() + device_meta = config_db.get_entry('DEVICE_METADATA', 'localhost') assert 'buffer_model' in device_meta, "'buffer_model' doesn't exist in DEVICE_METADATA|localhost" if device_meta['buffer_model'] == 'traditional': @@ -86,6 +94,17 @@ def disable_dynamic_buffer(config_db, cmd_runner): finally: # restart daemon - cmd_runner("supervisorctl restart buffermgrd") + dvs.runcmd("supervisorctl restart buffermgrd") + + # Remove all the non-default zero profiles + profiles = app_db.get_keys('BUFFER_PROFILE_TABLE') + for key in profiles: + if re.search(zero_profile_name_pattern, key): + app_db.delete_entry('BUFFER_PROFILE_TABLE', key) + # Remove all the non-default zero pools + pools = app_db.get_keys('BUFFER_POOL_TABLE') + for key in pools: + if re.search(zero_pool_name_pattern, key): + app_db.delete_entry('BUFFER_POOL_TABLE', key) time.sleep(20) diff --git a/tests/conftest.py b/tests/conftest.py index 2b6bd27c..8db786cd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1389,6 +1389,11 @@ def set_nat_zone(self, interface, nat_zone): tbl.set(interface, fvs) time.sleep(1) + # db + def delete_entry_tbl(self, db, table, key): + tbl = swsscommon.Table(db, table) + tbl._del(key) + # deps: acl, crm, fdb def setReadOnlyAttr(self, obj, attr, val): db = swsscommon.DBConnector(swsscommon.ASIC_DB, self.redis_sock, 0) @@ -2045,8 +2050,10 @@ def dvs_twamp_manager(request, dvs): @pytest.fixture(scope="class") def dvs_buffer_manager(request, dvs): request.cls.dvs_buffer = dvs_buffer.DVSBuffer(dvs.get_asic_db(), + dvs.get_app_db(), dvs.get_config_db(), - dvs.get_state_db()) + dvs.get_state_db(), + dvs.get_counters_db()) @pytest.fixture(scope="class") def dvs_queue_manager(request, dvs): diff --git a/tests/dvslib/dvs_buffer.py b/tests/dvslib/dvs_buffer.py index 61f4feaf..36ab5afa 100644 --- a/tests/dvslib/dvs_buffer.py +++ b/tests/dvslib/dvs_buffer.py @@ -1,23 +1,140 @@ """Utilities for interacting with BUFFER objects when writing VS tests.""" -from typing import Dict +from typing import Dict, List class DVSBuffer: """Manage buffer objects on the virtual switch.""" ASIC_BUFFER_PROFILE = "ASIC_STATE:SAI_OBJECT_TYPE_BUFFER_PROFILE" + ASIC_PRIORITY_GROUP = "ASIC_STATE:SAI_OBJECT_TYPE_INGRESS_PRIORITY_GROUP" + + APPL_BUFFER_PROFILE = "BUFFER_PROFILE_TABLE" CONFIG_BUFFER_PROFILE = "BUFFER_PROFILE" + CONFIG_BUFFER_PG = "BUFFER_PG" + + CONFIG_DEVICE_METADATA = "DEVICE_METADATA" + KEY_DEVICE_METADATA_LOCALHOST = "localhost" STATE_BUFFER_MAX_PARAM = "BUFFER_MAX_PARAM_TABLE" KEY_BUFFER_MAX_PARAM_GLOBAL = "global" - def __init__(self, asic_db, config_db, state_db): + COUNTERS_PG_NAME_MAP = "COUNTERS_PG_NAME_MAP" + + def __init__(self, asic_db, app_db, config_db, state_db, counters_db): """Create a new DVS buffer manager.""" self.asic_db = asic_db + self.app_db = app_db self.config_db = config_db self.state_db = state_db + self.counters_db = counters_db + + def get_buffer_pg_keys( + self, + port_name: str, + pg_index: str + ) -> List[str]: + """Get priority group buffer keys from CONFIG DB.""" + keyList = [] + + keys = self.config_db.get_keys(self.CONFIG_BUFFER_PG) + + for key in keys: + if port_name in key: + assert "|" in key, \ + "Malformed priority group buffer entry: key={}".format(key) + _, pg = key.split("|") + + if "-" in pg: + idx1, idx2 = pg.split("-") + if int(idx1) <= int(pg_index) and int(pg_index) <= int(idx2): + keyList.append(key) + else: + if int(pg_index) == int(pg): + keyList.append(key) + + return keyList + + def get_buffer_pg_value( + self, + pg_buffer_key: str, + pg_buffer_field: str = "profile" + ) -> str: + """Get priority group buffer value from CONFIG DB.""" + attr_list = [ pg_buffer_field ] + fvs = self.config_db.wait_for_fields(self.CONFIG_BUFFER_PG, pg_buffer_key, attr_list) + + return fvs[pg_buffer_field] + + def update_buffer_pg( + self, + pg_buffer_key: str, + pg_buffer_profile: str + ) -> None: + """Update priority group in CONFIG DB.""" + attr_dict = { + "profile": pg_buffer_profile + } + self.config_db.update_entry(self.CONFIG_BUFFER_PG, pg_buffer_key, attr_dict) + + def remove_buffer_pg( + self, + pg_buffer_key: str + ) -> None: + """Remove priority group from CONFIG DB.""" + self.config_db.delete_entry(self.CONFIG_BUFFER_PG, pg_buffer_key) + + def is_dynamic_buffer_model( + self + ) -> bool: + """Checks whether traditional/dynamic buffer model is configured in CONFIG DB.""" + fvs = self.config_db.wait_for_entry(self.CONFIG_DEVICE_METADATA, self.KEY_DEVICE_METADATA_LOCALHOST) + return fvs.get("buffer_model", "") == "dynamic" + + def wait_for_buffer_profiles( + self + ) -> None: + """Verify all buffer profiles are in ASIC DB.""" + zeroBufferProfileList = [ + "ingress_lossy_pg_zero_profile", + "ingress_lossy_zero_profile", + "ingress_lossless_zero_profile", + "egress_lossy_zero_profile", + "egress_lossless_zero_profile" + ] + bufferProfileList = list(self.config_db.get_keys(self.CONFIG_BUFFER_PROFILE)) + + if self.is_dynamic_buffer_model(): + bufferProfileList.extend(zeroBufferProfileList) + + self.app_db.wait_for_matching_keys(self.APPL_BUFFER_PROFILE, bufferProfileList) + self.asic_db.wait_for_n_keys(self.ASIC_BUFFER_PROFILE, len(bufferProfileList)) + + def get_buffer_profile_ids( + self, + expected: int = None + ) -> List[str]: + """Get all buffer profile ids from ASIC DB.""" + if expected is None: + return self.asic_db.get_keys(self.ASIC_BUFFER_PROFILE) + + return self.asic_db.wait_for_n_keys(self.ASIC_BUFFER_PROFILE, expected) + + def create_buffer_profile( + self, + buffer_profile_name: str, + qualifiers: Dict[str, str] + ) -> None: + """Create buffer profile in CONFIG DB.""" + self.config_db.create_entry(self.CONFIG_BUFFER_PROFILE, buffer_profile_name, qualifiers) + + def remove_buffer_profile( + self, + buffer_profile_name: str + ) -> None: + """Remove buffer profile from CONFIG DB.""" + self.config_db.delete_entry(self.CONFIG_BUFFER_PROFILE, buffer_profile_name) def update_buffer_profile( self, @@ -27,6 +144,14 @@ def update_buffer_profile( """Update buffer profile in CONFIG DB.""" self.config_db.update_entry(self.CONFIG_BUFFER_PROFILE, buffer_profile_name, qualifiers) + def verify_buffer_profile( + self, + sai_buffer_profile_id: str, + sai_qualifiers: Dict[str, str] + ) -> None: + """Verify that buffer profile object has correct ASIC DB representation.""" + self.asic_db.wait_for_field_match(self.ASIC_BUFFER_PROFILE, sai_buffer_profile_id, sai_qualifiers) + def update_buffer_mmu( self, mmu_size: str @@ -43,10 +168,56 @@ def remove_buffer_mmu( """Remove buffer MMU size from STATE DB.""" self.state_db.delete_entry(self.STATE_BUFFER_MAX_PARAM, self.KEY_BUFFER_MAX_PARAM_GLOBAL) - def verify_buffer_profile( + def is_priority_group_exists( self, - sai_buffer_profile_id: str, + port_name: str, + pg_index: str + ) -> bool: + """Verify priority group existence in CONFIG DB.""" + key = "{}|{}".format(port_name, pg_index) + fvs = self.config_db.get_entry(self.CONFIG_BUFFER_PG, key) + + return bool(fvs) + + def get_priority_group_id( + self, + port_name: str, + pg_index: str + ) -> str: + """Get priority group id from COUNTERS DB.""" + field = "{}:{}".format(port_name, pg_index) + + attr_list = [ field ] + fvs = self.counters_db.wait_for_fields(self.COUNTERS_PG_NAME_MAP, "", attr_list) + + return fvs[field] + + def update_priority_group( + self, + port_name: str, + pg_index: str, + buffer_profile_name: str + ) -> None: + """Update priority group in CONFIG DB.""" + attr_dict = { + "profile": buffer_profile_name + } + key = "{}|{}".format(port_name, pg_index) + self.config_db.update_entry(self.CONFIG_BUFFER_PG, key, attr_dict) + + def remove_priority_group( + self, + port_name: str, + pg_index: str + ) -> None: + """Remove priority group from CONFIG DB.""" + key = "{}|{}".format(port_name, pg_index) + self.config_db.delete_entry(self.CONFIG_BUFFER_PG, key) + + def verify_priority_group( + self, + sai_priority_group_id: str, sai_qualifiers: Dict[str, str] ) -> None: - """Verify that buffer profile object has correct ASIC DB representation.""" - self.asic_db.wait_for_field_match(self.ASIC_BUFFER_PROFILE, sai_buffer_profile_id, sai_qualifiers) + """Verify that priority group object has correct ASIC DB representation.""" + self.asic_db.wait_for_field_match(self.ASIC_PRIORITY_GROUP, sai_priority_group_id, sai_qualifiers) diff --git a/tests/dvslib/dvs_database.py b/tests/dvslib/dvs_database.py index 67246982..54813768 100644 --- a/tests/dvslib/dvs_database.py +++ b/tests/dvslib/dvs_database.py @@ -4,7 +4,7 @@ - Reference DBs by name rather than ID/socket - Add support for ProducerStateTable """ -from typing import Dict, List +from typing import Dict, List, Callable from swsscommon import swsscommon from swsscommon.swsscommon import SonicDBConfig from dvslib.dvs_common import wait_for_result, PollingConfig @@ -96,8 +96,12 @@ def delete_entry(self, table_name: str, key: str) -> None: table_name: The name of the table where the entry is being removed. key: The key that maps to the entry being removed. """ - table = swsscommon.Table(self.db_connection, table_name) - table._del(key) # pylint: disable=protected-access + if self.db_connection.getDbId() == swsscommon.APPL_DB: + table = swsscommon.ProducerStateTable(self.db_connection, table_name) + table._del(key) + else: + table = swsscommon.Table(self.db_connection, table_name) + table._del(key) # pylint: disable=protected-access def delete_field(self, table_name: str, key: str, field: str) -> None: """Remove a field from an entry stored at `key` in the specified table. @@ -213,6 +217,7 @@ def wait_for_field_match( key: str, expected_fields: Dict[str, str], polling_config: PollingConfig = PollingConfig(), + comparator: Callable[[], bool] = None, failure_message: str = None, ) -> Dict[str, str]: """Wait for the entry stored at `key` to have the specified field/values and retrieve it. @@ -234,6 +239,11 @@ def wait_for_field_match( def access_function(): fv_pairs = self.get_entry(table_name, key) + + if comparator is not None: + result = all(comparator(k, fv_pairs.get(k, None), v) for k, v in expected_fields.items()) + return (result, fv_pairs) + return ( all(fv_pairs.get(k) == v for k, v in expected_fields.items()), fv_pairs, diff --git a/tests/dvslib/dvs_port.py b/tests/dvslib/dvs_port.py index 8adaae65..f2b476a7 100644 --- a/tests/dvslib/dvs_port.py +++ b/tests/dvslib/dvs_port.py @@ -12,10 +12,13 @@ class DVSPort(object): CHANNEL_UNITTEST = "SAI_VS_UNITTEST_CHANNEL" ASIC_VIDTORID = "VIDTORID" + ASIC_PORT = "ASIC_STATE:SAI_OBJECT_TYPE_PORT" - CFGDB_PORT = "PORT" - APPDB_PORT = "PORT_TABLE" - ASICDB_PORT = "ASIC_STATE:SAI_OBJECT_TYPE_PORT" + APPL_PORT = "PORT_TABLE" + + CONFIG_PORT = "PORT" + CONFIG_BUFFER_INGRESS_PROFILE_LIST = "BUFFER_PORT_INGRESS_PROFILE_LIST" + CONFIG_BUFFER_EGRESS_PROFILE_LIST = "BUFFER_PORT_EGRESS_PROFILE_LIST" COUNTERS_COUNTERS = "COUNTERS" COUNTERS_PORT_NAME_MAP = "COUNTERS_PORT_NAME_MAP" @@ -33,21 +36,21 @@ def create_port_generic( speed: str, qualifiers: Dict[str, str] = {} ) -> None: - """Create PORT in Config DB.""" + """Create PORT in CONFIG DB.""" attr_dict = { "lanes": lanes, "speed": speed, **qualifiers } - self.config_db.create_entry(self.CFGDB_PORT, port_name, attr_dict) + self.config_db.create_entry(self.CONFIG_PORT, port_name, attr_dict) def remove_port_generic( self, port_name: str )-> None: - """Remove PORT from Config DB.""" - self.config_db.delete_entry(self.CFGDB_PORT, port_name) + """Remove PORT from CONFIG DB.""" + self.config_db.delete_entry(self.CONFIG_PORT, port_name) def remove_port(self, port_name): self.config_db.delete_field("CABLE_LENGTH", "AZURE", port_name) @@ -69,8 +72,35 @@ def update_port( port_name: str, attr_dict: Dict[str, str] ) -> None: - """Update PORT in Config DB.""" - self.config_db.update_entry(self.CFGDB_PORT, port_name, attr_dict) + """Update PORT in CONFIG DB.""" + self.config_db.update_entry(self.CONFIG_PORT, port_name, attr_dict) + + def verify_port( + self, + sai_port_id: str, + sai_qualifiers: Dict[str, str] + ) -> None: + """Verify that port object has correct ASIC DB representation. + + Args: + sai_port_id: The specific port id to check in ASIC DB. + sai_qualifiers: The expected set of SAI qualifiers to be found in ASIC DB. + """ + def comparator(k, v1, v2): + def profile_list_handler(v1, v2): + if v1 is None: + return False + bpList = v1[v1.index(":")+1:].split(",") + return set(bpList) == set(v2) + + if k == "SAI_PORT_ATTR_QOS_INGRESS_BUFFER_PROFILE_LIST": + return profile_list_handler(v1, v2) + elif k == "SAI_PORT_ATTR_QOS_EGRESS_BUFFER_PROFILE_LIST": + return profile_list_handler(v1, v2) + + return v1 == v2 + + self.asic_db.wait_for_field_match(self.ASIC_PORT, sai_port_id, sai_qualifiers, comparator=comparator) def get_port_id( self, @@ -93,10 +123,10 @@ def get_port_ids( if dbid == swsscommon.ASIC_DB: conn = self.asic_db - table = self.ASICDB_PORT + table = self.ASIC_PORT elif dbid == swsscommon.APPL_DB: conn = self.app_db - table = self.APPDB_PORT + table = self.APPL_PORT else: raise RuntimeError("Interface not implemented") @@ -105,6 +135,14 @@ def get_port_ids( return conn.wait_for_n_keys(table, expected) + def verify_port_count( + self, + expected: int, + dbid: int = swsscommon.ASIC_DB + ) -> None: + """Verify that there are N PORT objects in ASIC/APP DB.""" + self.get_port_ids(expected, dbid) + def set_port_counter( self, sai_port_id: str, @@ -137,10 +175,46 @@ def verify_port_counter( """Verify that port counter object has correct COUNTERS DB representation.""" self.counters_db.wait_for_field_match(self.COUNTERS_COUNTERS, sai_port_id, sai_qualifiers) - def verify_port_count( + def update_buffer_profile_list( self, - expected: int, - dbid: int = swsscommon.ASIC_DB + port_name: str, + profile_list: str, + ingress: bool = True ) -> None: - """Verify that there are N PORT objects in ASIC/APP DB.""" - self.get_port_ids(expected, dbid) + """Update ingress/egress buffer profile list in CONFIG DB.""" + attr_dict = { + "profile_list": profile_list + } + table_name = self.CONFIG_BUFFER_INGRESS_PROFILE_LIST if ingress else self.CONFIG_BUFFER_EGRESS_PROFILE_LIST + self.config_db.update_entry(table_name, port_name, attr_dict) + + def remove_buffer_profile_list( + self, + port_name: str, + ingress: bool = True + ) -> None: + """Remove ingress/egress buffer profile list from CONFIG DB.""" + table_name = self.CONFIG_BUFFER_INGRESS_PROFILE_LIST if ingress else self.CONFIG_BUFFER_EGRESS_PROFILE_LIST + self.config_db.delete_entry(table_name, port_name) + + def is_buffer_profile_list_exists( + self, + port_name: str, + ingress: bool = True + ) -> str: + """Verify ingress/egress buffer profile list existence in CONFIG DB.""" + table_name = self.CONFIG_BUFFER_INGRESS_PROFILE_LIST if ingress else self.CONFIG_BUFFER_EGRESS_PROFILE_LIST + fvs = self.config_db.get_entry(table_name, port_name) + + return bool(fvs) + + def get_buffer_profile_list( + self, + port_name: str, + ingress: bool = True + ) -> str: + """Get ingress/egress buffer profile list from CONFIG DB.""" + table_name = self.CONFIG_BUFFER_INGRESS_PROFILE_LIST if ingress else self.CONFIG_BUFFER_EGRESS_PROFILE_LIST + fvs = self.config_db.wait_for_entry(table_name, port_name) + + return fvs["profile_list"].split(",") diff --git a/tests/dvslib/dvs_switch.py b/tests/dvslib/dvs_switch.py index 61ac3042..f858e6ad 100644 --- a/tests/dvslib/dvs_switch.py +++ b/tests/dvslib/dvs_switch.py @@ -57,60 +57,15 @@ def verify_switch_count( """ self.get_switch_ids(expected) - def verify_switch_generic( - self, - sai_switch_id: str, - sai_qualifiers: Dict[str, str] - ) -> None: - """Verify that switch object has correct ASIC DB representation. - - Args: - sai_switch_id: The specific switch id to check in ASIC DB. - sai_qualifiers: The expected set of SAI qualifiers to be found in ASIC DB. - """ - entry = self.asic_db.wait_for_entry(self.ADB_SWITCH, sai_switch_id) - - for k, v in entry.items(): - if k == "NULL": - continue - elif k in sai_qualifiers: - if k == "SAI_SWITCH_ATTR_ECMP_DEFAULT_HASH_ALGORITHM": - assert sai_qualifiers[k] == v - elif k == "SAI_SWITCH_ATTR_LAG_DEFAULT_HASH_ALGORITHM": - assert sai_qualifiers[k] == v - elif k == "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": - assert sai_qualifiers[k] == v - elif k == "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE": - assert sai_qualifiers[k] == v - elif k == "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": - assert sai_qualifiers[k] == v - elif k == "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": - assert sai_qualifiers[k] == v - else: - assert False, "Unknown SAI qualifier: key={}, value={}".format(k, v) - def verify_switch( self, sai_switch_id: str, - sai_qualifiers: Dict[str, str], - strict: bool = False + sai_qualifiers: Dict[str, str] ) -> None: """Verify that switch object has correct ASIC DB representation. Args: sai_switch_id: The specific switch id to check in ASIC DB. sai_qualifiers: The expected set of SAI qualifiers to be found in ASIC DB. - strict: Specifies whether verification should be strict """ - if strict: - self.verify_switch_generic(sai_switch_id, sai_qualifiers) - return - - entry = self.asic_db.wait_for_entry(self.ADB_SWITCH, sai_switch_id) - - attr_dict = { - **entry, - **sai_qualifiers - } - - self.verify_switch_generic(sai_switch_id, attr_dict) + self.asic_db.wait_for_field_match(self.ADB_SWITCH, sai_switch_id, sai_qualifiers) diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 42a211f4..ee4aa8cf 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -88,6 +88,7 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/copporch.cpp \ $(top_srcdir)/orchagent/tunneldecaporch.cpp \ $(top_srcdir)/orchagent/qosorch.cpp \ + $(top_srcdir)/orchagent/buffer/bufferhelper.cpp \ $(top_srcdir)/orchagent/bufferorch.cpp \ $(top_srcdir)/orchagent/mirrororch.cpp \ $(top_srcdir)/orchagent/fdborch.cpp \ diff --git a/tests/test_buffer_dynamic.py b/tests/test_buffer_dynamic.py index 2737d28d..364b752e 100644 --- a/tests/test_buffer_dynamic.py +++ b/tests/test_buffer_dynamic.py @@ -9,7 +9,7 @@ def dynamic_buffer(dvs): buffer_model.enable_dynamic_buffer(dvs.get_config_db(), dvs.runcmd) yield - buffer_model.disable_dynamic_buffer(dvs.get_config_db(), dvs.runcmd) + buffer_model.disable_dynamic_buffer(dvs) @pytest.mark.usefixtures("dynamic_buffer") class TestBufferMgrDyn(object): @@ -140,7 +140,7 @@ def check_new_profile_in_asic_db(self, dvs, profile): 'SAI_BUFFER_PROFILE_ATTR_POOL_ID': self.ingress_lossless_pool_oid, 'SAI_BUFFER_PROFILE_ATTR_THRESHOLD_MODE': sai_threshold_mode, sai_threshold_name: sai_threshold_value}, - self.DEFAULT_POLLING_CONFIG) + polling_config=self.DEFAULT_POLLING_CONFIG) def make_lossless_profile_name(self, speed, cable_length, mtu = None, dynamic_th = None): extra = "" @@ -889,7 +889,7 @@ def test_bufferPoolInitWithSHP(self, dvs, testlog): pass # 4. Remove the ingress_lossless_pool from the APPL_DB - self.app_db.delete_entry('BUFFER_POOL_TABLE', 'ingress_lossless_pool') + dvs.delete_entry_tbl(self.app_db.db_connection, 'BUFFER_POOL_TABLE', 'ingress_lossless_pool') # 5. Mock it by adding a "TABLE_SET" entry to trigger the fallback logic self.app_db.update_entry("BUFFER_PG_TABLE_SET", "", {"NULL": "NULL"}) @@ -901,7 +901,7 @@ def test_bufferPoolInitWithSHP(self, dvs, testlog): finally: self.config_db.update_entry('BUFFER_POOL', 'ingress_lossless_pool', original_ingress_lossless_pool) self.config_db.delete_entry('DEFAULT_LOSSLESS_BUFFER_PARAMETER', 'AZURE') - self.app_db.delete_entry("BUFFER_PG_TABLE_SET", "") + dvs.delete_entry_tbl(self.app_db.db_connection, 'BUFFER_PG_TABLE_SET', '') dvs.runcmd("kill -s SIGCONT {}".format(oa_pid)) diff --git a/tests/test_port_add_remove.py b/tests/test_port_add_remove.py index 54cd6599..6371454c 100644 --- a/tests/test_port_add_remove.py +++ b/tests/test_port_add_remove.py @@ -19,7 +19,7 @@ def dynamic_buffer(dvs): buffer_model.enable_dynamic_buffer(dvs.get_config_db(), dvs.runcmd) yield - buffer_model.disable_dynamic_buffer(dvs.get_config_db(), dvs.runcmd) + buffer_model.disable_dynamic_buffer(dvs) @pytest.mark.usefixtures('dvs_port_manager') diff --git a/tests/test_trimming.py b/tests/test_trimming.py index 3eb9b2c6..6e6fad54 100644 --- a/tests/test_trimming.py +++ b/tests/test_trimming.py @@ -1,4 +1,5 @@ import pytest +import time import logging from typing import NamedTuple @@ -10,6 +11,10 @@ trimlogger = logging.getLogger(__name__) +SAI_DSCP_MODE_DICT = { + "dscp-value": "SAI_PACKET_TRIM_DSCP_RESOLUTION_MODE_DSCP_VALUE", + "from-tc": "SAI_PACKET_TRIM_DSCP_RESOLUTION_MODE_FROM_TC" +} SAI_QUEUE_MODE_DICT = { "static": "SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_STATIC", "dynamic": "SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_DYNAMIC" @@ -18,20 +23,27 @@ "drop": "SAI_BUFFER_PROFILE_PACKET_ADMISSION_FAIL_ACTION_DROP", "trim": "SAI_BUFFER_PROFILE_PACKET_ADMISSION_FAIL_ACTION_DROP_AND_TRIM" } +SAI_BUFFER_PROFILE_LIST_DICT = { + "ingress": "SAI_PORT_ATTR_QOS_INGRESS_BUFFER_PROFILE_LIST", + "egress": "SAI_PORT_ATTR_QOS_EGRESS_BUFFER_PROFILE_LIST" +} class TrimmingTuple(NamedTuple): """Config DB trimming attribute container""" size: str dscp: str + tc: str queue: str class TrimmingTupleSai(NamedTuple): """ASIC DB trimming attribute container""" size: str + dscpMode: str dscp: str - mode: str + tc: str + queueMode: str queue: str @@ -40,7 +52,7 @@ def dynamicModel(dvs): trimlogger.info("Enable dynamic buffer model") buffer_model.enable_dynamic_buffer(dvs.get_config_db(), dvs.runcmd) yield - buffer_model.disable_dynamic_buffer(dvs.get_config_db(), dvs.runcmd) + buffer_model.disable_dynamic_buffer(dvs) trimlogger.info("Disable dynamic buffer model") @@ -53,6 +65,15 @@ def portCounters(dvs): trimlogger.info("Deinitialize port counters") +@pytest.fixture(scope="class") +def pgCounters(dvs): + trimlogger.info("Initialize priority group counters") + dvs.runcmd("counterpoll watermark enable") + yield + dvs.runcmd("counterpoll watermark disable") + trimlogger.info("Deinitialize priority group counters") + + @pytest.fixture(scope="class") def queueCounters(dvs): trimlogger.info("Initialize queue counters") @@ -64,7 +85,7 @@ def queueCounters(dvs): @pytest.mark.usefixtures("dvs_switch_manager") @pytest.mark.usefixtures("testlog") -class TestTrimmingBasicFlows: +class TestTrimmingFlows: @pytest.fixture(scope="class") def switchData(self): trimlogger.info("Initialize switch data") @@ -84,17 +105,57 @@ def switchData(self): trimlogger.info("Deinitialize switch data") + +class TestTrimmingBasicFlows(TestTrimmingFlows): @pytest.mark.parametrize( "attrDict,saiAttrDict", [ pytest.param( - TrimmingTuple(size="100", dscp="10", queue="1"), - TrimmingTupleSai(size="100", dscp="10", mode=SAI_QUEUE_MODE_DICT["static"], queue="1"), - id="static-queue-index" + TrimmingTuple(size="100", dscp="10", tc=None, queue="1"), + TrimmingTupleSai( + size="100", + dscpMode=SAI_DSCP_MODE_DICT["dscp-value"], + dscp="10", + tc=None, + queueMode=SAI_QUEUE_MODE_DICT["static"], + queue="1" + ), + id="symmetric-dscp-static-queue-index" + ), + pytest.param( + TrimmingTuple(size="200", dscp="20", tc=None, queue="dynamic"), + TrimmingTupleSai( + size="200", + dscpMode=SAI_DSCP_MODE_DICT["dscp-value"], + dscp="20", + tc=None, + queueMode=SAI_QUEUE_MODE_DICT["dynamic"], + queue="1" + ), + id="symmetric-dscp-dynamic-queue-index" + ), + pytest.param( + TrimmingTuple(size="100", dscp="from-tc", tc="1", queue="1"), + TrimmingTupleSai( + size="100", + dscpMode=SAI_DSCP_MODE_DICT["from-tc"], + dscp="20", + tc="1", + queueMode=SAI_QUEUE_MODE_DICT["static"], + queue="1" + ), + id="asymmetric-dscp-static-queue-index" ), pytest.param( - TrimmingTuple(size="200", dscp="20", queue="dynamic"), - TrimmingTupleSai(size="200", dscp="20", mode=SAI_QUEUE_MODE_DICT["dynamic"], queue="1"), - id="dynamic-queue-index" + TrimmingTuple(size="200", dscp="from-tc", tc="2", queue="dynamic"), + TrimmingTupleSai( + size="200", + dscpMode=SAI_DSCP_MODE_DICT["from-tc"], + dscp="20", + tc="2", + queueMode=SAI_QUEUE_MODE_DICT["dynamic"], + queue="1" + ), + id="asymmetric-dscp-dynamic-queue-index" ) ] ) @@ -105,6 +166,9 @@ def test_TrimSwitchGlobalConfiguration(self, switchData, attrDict, saiAttrDict): "queue_index": attrDict.queue } + if attrDict.tc is not None: + attr_dict["tc_value"] = attrDict.tc + trimlogger.info("Update trimming global") self.dvs_switch.update_switch_trimming( qualifiers=attr_dict @@ -113,23 +177,407 @@ def test_TrimSwitchGlobalConfiguration(self, switchData, attrDict, saiAttrDict): switchId = switchData["id"] sai_attr_dict = { "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": saiAttrDict.size, + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE": saiAttrDict.dscpMode, "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE": saiAttrDict.dscp, - "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": saiAttrDict.mode, + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": saiAttrDict.queueMode, "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": saiAttrDict.queue } + if saiAttrDict.tc is not None: + sai_attr_dict["SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE"] = saiAttrDict.tc + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + +class TestTrimmingAdvancedFlows(TestTrimmingFlows): + def test_TrimAsymToSymMigration(self, switchData): + switchId = switchData["id"] + + # Configure Asymmetric DSCP mode + attr_dict = { + "size": "200", + "dscp_value": "from-tc", + "tc_value": "2", + "queue_index": "2" + } + + trimlogger.info("Update trimming global") + self.dvs_switch.update_switch_trimming( + qualifiers=attr_dict + ) + + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": "200", + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE": SAI_DSCP_MODE_DICT["from-tc"], + "SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE": "2", + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": SAI_QUEUE_MODE_DICT["static"], + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": "2" + } + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + # Configure Symmetric DSCP mode + attr_dict = { + "size": "100", + "dscp_value": "10", + "queue_index": "1" + } + + trimlogger.info("Update trimming global") + self.dvs_switch.update_switch_trimming( + qualifiers=attr_dict + ) + + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": "100", + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE": SAI_DSCP_MODE_DICT["dscp-value"], + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE": "10", + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": SAI_QUEUE_MODE_DICT["static"], + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": "1" + } + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + # Update TC value + attr_dict = { + "tc_value": "5" + } + + trimlogger.info("Update trimming global") + self.dvs_switch.update_switch_trimming( + qualifiers=attr_dict + ) + + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE": "2" + } + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + # Configure Asymmetric DSCP mode + attr_dict = { + "size": "200", + "dscp_value": "from-tc", + "tc_value": "2", + "queue_index": "2" + } + + trimlogger.info("Update trimming global") + self.dvs_switch.update_switch_trimming( + qualifiers=attr_dict + ) + + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": "200", + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE": SAI_DSCP_MODE_DICT["from-tc"], + "SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE": "2", + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": SAI_QUEUE_MODE_DICT["static"], + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": "2" + } + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + +@pytest.mark.usefixtures("genericConfig") +@pytest.mark.usefixtures("restoreConfig") +class TestTrimmingNegativeFlows(TestTrimmingFlows): + @pytest.fixture(scope="class") + def genericConfig(self, switchData): + trimlogger.info("Add generic configuration") + + switchId = switchData["id"] + + # Asymmetric DSCP mode + + attr_dict = { + "size": "100", + "dscp_value": "from-tc", + "tc_value": "1", + "queue_index": "1" + } + + trimlogger.info("Update trimming global") + self.dvs_switch.update_switch_trimming( + qualifiers=attr_dict + ) + + # Symmetric DSCP mode + + attr_dict = { + "size": "100", + "dscp_value": "10", + "queue_index": "1" + } + + trimlogger.info("Update trimming global") + self.dvs_switch.update_switch_trimming( + qualifiers=attr_dict + ) + + # Validation + + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": "100", + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE": SAI_DSCP_MODE_DICT["dscp-value"], + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE": "10", + "SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE": "1", + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": SAI_QUEUE_MODE_DICT["static"], + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": "1" + } + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + yield + trimlogger.info("Validate trimming global") self.dvs_switch.verify_switch( sai_switch_id=switchId, sai_qualifiers=sai_attr_dict ) + trimlogger.info("Verify generic configuration") + + @pytest.fixture(scope="function") + def restoreConfig(self, switchData, request): + switchId = switchData["id"] + + attrDict = request.getfixturevalue("attrDict") + saiAttrDict = request.getfixturevalue("saiAttrDict") + + yield + + attr_dict = {} + + if attrDict.size is not None: + attr_dict = { + "size": "100" + } + + if attrDict.dscp is not None: + attr_dict = { + "dscp_value": "10" + } + + if attrDict.tc is not None: + attr_dict = { + "tc_value": "1" + } + + if attrDict.queue is not None: + attr_dict = { + "queue_index": "1" + } + + trimlogger.info("Update trimming global") + self.dvs_switch.update_switch_trimming( + qualifiers=attr_dict + ) + + sai_attr_dict = {} + + if saiAttrDict.size is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": "100" + } + + if saiAttrDict.dscp is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE": SAI_DSCP_MODE_DICT["dscp-value"], + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE": "10" + } + + if saiAttrDict.tc is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE": SAI_DSCP_MODE_DICT["dscp-value"], + "SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE": "1" + } + + + if saiAttrDict.queue is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": SAI_QUEUE_MODE_DICT["static"], + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": "1" + } + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + trimlogger.info("Verify configuration rollback: {}".format(str(attrDict))) + + @pytest.mark.parametrize( + "attrDict,saiAttrDict", [ + pytest.param( + TrimmingTuple(size="", dscp=None, tc=None, queue=None), + TrimmingTupleSai(size="100", dscpMode=None, dscp=None, tc=None, queueMode=None, queue=None), + id="size-empty" + ), + pytest.param( + TrimmingTuple(size="-1", dscp=None, tc=None, queue=None), + TrimmingTupleSai(size="100", dscpMode=None, dscp=None, tc=None, queueMode=None, queue=None), + id="size-min-1" + ), + pytest.param( + TrimmingTuple(size="4294967296", dscp=None, tc=None, queue=None), + TrimmingTupleSai(size="100", dscpMode=None, dscp=None, tc=None, queueMode=None, queue=None), + id="size-max+1" + ), + pytest.param( + TrimmingTuple(size=None, dscp="", tc=None, queue=None), + TrimmingTupleSai( + size=None, tc=None, queueMode=None, queue=None, + dscpMode=SAI_DSCP_MODE_DICT["dscp-value"], dscp="10" + ), + id="dscp-empty" + ), + pytest.param( + TrimmingTuple(size=None, dscp="-1", tc=None, queue=None), + TrimmingTupleSai( + size=None, tc=None, queueMode=None, queue=None, + dscpMode=SAI_DSCP_MODE_DICT["dscp-value"], dscp="10" + ), + id="dscp-min-1" + ), + pytest.param( + TrimmingTuple(size=None, dscp="64", tc=None, queue=None), + TrimmingTupleSai( + size=None, tc=None, queueMode=None, queue=None, + dscpMode=SAI_DSCP_MODE_DICT["dscp-value"], dscp="10" + ), + id="dscp-max+1" + ), + pytest.param( + TrimmingTuple(size=None, dscp=None, tc="", queue=None), + TrimmingTupleSai(size=None, dscpMode=None, dscp=None, tc="1", queueMode=None, queue=None), + id="tc-empty" + ), + pytest.param( + TrimmingTuple(size=None, dscp=None, tc="-1", queue=None), + TrimmingTupleSai(size=None, dscpMode=None, dscp=None, tc="1", queueMode=None, queue=None), + id="tc-min-1" + ), + pytest.param( + TrimmingTuple(size=None, dscp=None, tc="256", queue=None), + TrimmingTupleSai(size=None, dscpMode=None, dscp=None, tc="1", queueMode=None, queue=None), + id="tc-max+1" + ), + pytest.param( + TrimmingTuple(size=None, dscp=None, tc=None, queue=""), + TrimmingTupleSai( + size=None, dscpMode=None, dscp=None, tc=None, + queueMode=SAI_QUEUE_MODE_DICT["static"], queue="1" + ), + id="queue-empty" + ), + pytest.param( + TrimmingTuple(size=None, dscp=None, tc=None, queue="-1"), + TrimmingTupleSai( + size=None, dscpMode=None, dscp=None, tc=None, + queueMode=SAI_QUEUE_MODE_DICT["static"], queue="1" + ), + id="queue-min-1" + ), + pytest.param( + TrimmingTuple(size=None, dscp=None, tc=None, queue="256"), + TrimmingTupleSai( + size=None, dscpMode=None, dscp=None, tc=None, + queueMode=SAI_QUEUE_MODE_DICT["static"], queue="1" + ), + id="queue-max+1" + ) + ] + ) + def test_TrimNegValueOutOfBound(self, switchData, attrDict, saiAttrDict): + switchId = switchData["id"] + + attr_dict = {} + + if attrDict.size is not None: + attr_dict = { + "size": attrDict.size + } + + if attrDict.dscp is not None: + attr_dict = { + "dscp_value": attrDict.dscp + } + + if attrDict.tc is not None: + attr_dict = { + "tc_value": attrDict.tc + } + + if attrDict.queue is not None: + attr_dict = { + "queue_index": attrDict.queue + } + + trimlogger.info("Update trimming global") + self.dvs_switch.update_switch_trimming( + qualifiers=attr_dict + ) + + sai_attr_dict = {} + + if saiAttrDict.size is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_SIZE": saiAttrDict.size + } + + if saiAttrDict.dscp is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE": saiAttrDict.dscpMode, + "SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE": saiAttrDict.dscp + } + + if saiAttrDict.tc is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE": saiAttrDict.tc + } + + if saiAttrDict.queue is not None: + sai_attr_dict = { + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE": saiAttrDict.queueMode, + "SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX": saiAttrDict.queue + } + + trimlogger.info("Validate trimming global") + self.dvs_switch.verify_switch( + sai_switch_id=switchId, + sai_qualifiers=sai_attr_dict + ) + + @pytest.mark.usefixtures("dvs_buffer_manager") -@pytest.mark.usefixtures("dvs_queue_manager") @pytest.mark.usefixtures("testlog") class TrimmingBufferModel: PORT = "Ethernet0" - QUEUE = "0" MMU = "12766208" @pytest.fixture(scope="class") @@ -153,10 +601,18 @@ def dynamicBuffer(self, dvs, dynamicModel): trimlogger.info("Remove dynamic buffer configuration") + +@pytest.mark.usefixtures("dvs_queue_manager") +class TrimmingRegularBufferModel(TrimmingBufferModel): + QUEUE = "0" + @pytest.fixture(scope="class") def bufferData(self, queueCounters): trimlogger.info("Initialize buffer data") + trimlogger.info("Verify buffer profiles are loaded") + self.dvs_buffer.wait_for_buffer_profiles() + trimlogger.info("Get buffer profile name: port={}, queue={}".format(self.PORT, self.QUEUE)) bufferProfileName = self.dvs_queue.get_queue_buffer_profile_name(self.PORT, self.QUEUE) @@ -201,7 +657,8 @@ def verifyBufferProfileConfiguration(self, bufferData, action): sai_qualifiers=sai_attr_dict ) -class TestTrimmingTraditionalBufferModel(TrimmingBufferModel): + +class TestTrimmingTraditionalBufferModel(TrimmingRegularBufferModel): @pytest.mark.parametrize( "action", [ pytest.param("drop", id="drop-packet"), @@ -211,17 +668,658 @@ class TestTrimmingTraditionalBufferModel(TrimmingBufferModel): def test_TrimStaticBufferProfileConfiguration(self, bufferData, action): self.verifyBufferProfileConfiguration(bufferData, action) -class TestTrimmingDynamicBufferModel(TrimmingBufferModel): + +@pytest.mark.usefixtures("dynamicBuffer") +class TestTrimmingDynamicBufferModel(TrimmingRegularBufferModel): @pytest.mark.parametrize( "action", [ pytest.param("drop", id="drop-packet"), pytest.param("trim", id="trim-packet") ] ) - def test_TrimDynamicBufferProfileConfiguration(self, dynamicBuffer, bufferData, action): + def test_TrimDynamicBufferProfileConfiguration(self, bufferData, action): self.verifyBufferProfileConfiguration(bufferData, action) +@pytest.mark.usefixtures("dvs_port_manager") +class TrimmingNegativeBufferModel(TrimmingBufferModel): + INGRESS_TRIM_PROFILE = "ingress_trim_profile" + EGRESS_TRIM_PROFILE = "egress_trim_profile" + + INGRESS_DEFAULT_PROFILE = "ingress_default_profile" + EGRESS_DEFAULT_PROFILE = "egress_default_profile" + + PG = "0" + + def createBufferProfile(self, profileName, attrDict): + bufferProfileIds = self.dvs_buffer.get_buffer_profile_ids() + + self.dvs_buffer.create_buffer_profile( + buffer_profile_name=profileName, + qualifiers=attrDict + ) + + bufferProfileIdsExt = self.dvs_buffer.get_buffer_profile_ids(len(bufferProfileIds)+1) + + return (set(bufferProfileIdsExt) - set(bufferProfileIds)).pop() + + def getPgBufferKeyValuePair(self, portName, pgIdx): + keyList = self.dvs_buffer.get_buffer_pg_keys(portName, pgIdx) + assert len(keyList) <= 1, "Invalid BUFFER_PG table" + + if keyList: + key = keyList[0] + value = self.dvs_buffer.get_buffer_pg_value(key) + + return key, value + + return None, None + + @pytest.fixture(scope="class") + def portData(self, portCounters): + trimlogger.info("Initialize port data") + + trimlogger.info("Get port id: port={}".format(self.PORT)) + portId = self.dvs_port.get_port_id(self.PORT) + + meta_dict = { + "id": portId + } + + yield meta_dict + + trimlogger.info("Deinitialize port data") + + @pytest.fixture(scope="class") + def pgData(self, pgCounters): + trimlogger.info("Initialize priority group data") + + trimlogger.info("Get priority group id: port={}, pg={}".format(self.PORT, self.PG)) + pgId = self.dvs_buffer.get_priority_group_id(self.PORT, self.PG) + + trimlogger.info("Get priority group buffer profile: port={}, pg={}".format(self.PORT, self.PG)) + pgBufferKey, pgBufferProfile = self.getPgBufferKeyValuePair(self.PORT, self.PG) + + if pgBufferKey is not None: + trimlogger.info("Remove priority group buffer profile: key={}".format(pgBufferKey)) + self.dvs_buffer.remove_buffer_pg(pgBufferKey) + + meta_dict = { + "id": pgId + } + + yield meta_dict + + if pgBufferKey is not None: + trimlogger.info( + "Restore priority group buffer profile: key={}, profile={}".format( + pgBufferKey, pgBufferProfile + ) + ) + self.dvs_buffer.update_buffer_pg( + pg_buffer_key=pgBufferKey, + pg_buffer_profile=pgBufferProfile + ) + else: + if self.dvs_buffer.is_priority_group_exists(self.PORT, self.PG): + trimlogger.info("Remove priority group buffer profile: port={}, pg={}".format(self.PORT, self.PG)) + self.dvs_buffer.remove_priority_group(self.PORT, self.PG) + + trimlogger.info("Deinitialize priority group data") + + @pytest.fixture(scope="class") + def bufferData(self): + trimlogger.info("Initialize buffer data") + + trimlogger.info("Verify buffer profiles are loaded") + self.dvs_buffer.wait_for_buffer_profiles() + + attr_dict = { + "dynamic_th": "3", + "size": "0", + "pool": "ingress_lossless_pool" + } + + trimlogger.info("Create buffer profile: {}".format(self.INGRESS_TRIM_PROFILE)) + iBufferProfileTrimId = self.createBufferProfile(self.INGRESS_TRIM_PROFILE, attr_dict) + + trimlogger.info("Create buffer profile: {}".format(self.INGRESS_DEFAULT_PROFILE)) + iBufferProfileDefId = self.createBufferProfile(self.INGRESS_DEFAULT_PROFILE, attr_dict) + + attr_dict = { + "dynamic_th": "3", + "size": "1518", + "pool": "egress_lossy_pool" + } + + trimlogger.info("Create buffer profile: {}".format(self.EGRESS_TRIM_PROFILE)) + eBufferProfileTrimId = self.createBufferProfile(self.EGRESS_TRIM_PROFILE, attr_dict) + + trimlogger.info("Create buffer profile: {}".format(self.EGRESS_DEFAULT_PROFILE)) + eBufferProfileDefId = self.createBufferProfile(self.EGRESS_DEFAULT_PROFILE, attr_dict) + + iBufferProfileListOld = None + eBufferProfileListOld = None + + if self.dvs_port.is_buffer_profile_list_exists(self.PORT): + trimlogger.info("Get ingress buffer profile list: port={}".format(self.PORT)) + iBufferProfileListOld = self.dvs_port.get_buffer_profile_list(self.PORT) + + if self.dvs_port.is_buffer_profile_list_exists(self.PORT, False): + trimlogger.info("Get egress buffer profile list: port={}".format(self.PORT)) + eBufferProfileListOld = self.dvs_port.get_buffer_profile_list(self.PORT, False) + + meta_dict = { + "id": { + "profile": { + "trim": { + "ingress": iBufferProfileTrimId, + "egress": eBufferProfileTrimId + }, + "default": { + "ingress": iBufferProfileDefId, + "egress": eBufferProfileDefId + } + } + } + } + + yield meta_dict + + if iBufferProfileListOld is not None: + trimlogger.info( + "Restore ingress buffer profile list: port={}, profile_list={}".format( + self.PORT, ",".join(iBufferProfileListOld) + ) + ) + self.dvs_port.update_buffer_profile_list( + port_name=self.PORT, + profile_list=",".join(iBufferProfileListOld) + ) + else: + if self.dvs_port.is_buffer_profile_list_exists(self.PORT): + trimlogger.info("Remove ingress buffer profile list: port={}".format(self.PORT)) + self.dvs_port.remove_buffer_profile_list(self.PORT) + + if eBufferProfileListOld is not None: + trimlogger.info( + "Restore egress buffer profile list: port={}, profile_list={}".format( + self.PORT, ",".join(eBufferProfileListOld) + ) + ) + self.dvs_port.update_buffer_profile_list( + port_name=self.PORT, + profile_list=",".join(eBufferProfileListOld), + ingress=False + ) + else: + if self.dvs_port.is_buffer_profile_list_exists(self.PORT, False): + trimlogger.info("Remove egress buffer profile list: port={}".format(self.PORT)) + self.dvs_port.remove_buffer_profile_list(self.PORT, False) + + trimlogger.info("Remove buffer profile: {}".format(self.INGRESS_TRIM_PROFILE)) + self.dvs_buffer.remove_buffer_profile(self.INGRESS_TRIM_PROFILE) + + trimlogger.info("Remove buffer profile: {}".format(self.INGRESS_DEFAULT_PROFILE)) + self.dvs_buffer.remove_buffer_profile(self.INGRESS_DEFAULT_PROFILE) + + trimlogger.info("Remove buffer profile: {}".format(self.EGRESS_TRIM_PROFILE)) + self.dvs_buffer.remove_buffer_profile(self.EGRESS_TRIM_PROFILE) + + trimlogger.info("Remove buffer profile: {}".format(self.EGRESS_DEFAULT_PROFILE)) + self.dvs_buffer.remove_buffer_profile(self.EGRESS_DEFAULT_PROFILE) + + trimlogger.info("Deinitialize buffer data") + + def verifyPriorityGroupBufferAttachConfiguration(self, bufferData, pgData): + trimProfile = self.INGRESS_TRIM_PROFILE + defaultProfile = self.INGRESS_DEFAULT_PROFILE + + pgId = pgData["id"] + trimProfileId = bufferData["id"]["profile"]["trim"]["ingress"] + defaultProfileId = bufferData["id"]["profile"]["default"]["ingress"] + + # Update priority group with the default buffer profile + + trimlogger.info( + "Update priority group: port={}, pg={}, profile={}".format(self.PORT, self.PG, defaultProfile) + ) + self.dvs_buffer.update_priority_group( + port_name=self.PORT, + pg_index=self.PG, + buffer_profile_name=defaultProfile + ) + + sai_attr_dict = { + "SAI_INGRESS_PRIORITY_GROUP_ATTR_BUFFER_PROFILE": defaultProfileId + } + + trimlogger.info( + "Validate priority group: port={}, pg={}, profile={}".format(self.PORT, self.PG, defaultProfile) + ) + self.dvs_buffer.verify_priority_group( + sai_priority_group_id=pgId, + sai_qualifiers=sai_attr_dict + ) + + # Set buffer profile trimming eligibility + + attr_dict = { + "packet_discard_action": "trim" + } + + trimlogger.info("Update buffer profile: {}".format(trimProfile)) + self.dvs_buffer.update_buffer_profile( + buffer_profile_name=trimProfile, + qualifiers=attr_dict + ) + + sai_attr_dict = { + "SAI_BUFFER_PROFILE_ATTR_PACKET_ADMISSION_FAIL_ACTION": SAI_BUFFER_PROFILE_MODE_DICT["trim"] + } + + trimlogger.info("Validate buffer profile: {}".format(trimProfile)) + self.dvs_buffer.verify_buffer_profile( + sai_buffer_profile_id=trimProfileId, + sai_qualifiers=sai_attr_dict + ) + + # Update priority group with the trimming buffer profile + # and verify no update is done to ASIC DB + + trimlogger.info( + "Update priority group: port={}, pg={}, profile={}".format(self.PORT, self.PG, trimProfile) + ) + self.dvs_buffer.update_priority_group( + port_name=self.PORT, + pg_index=self.PG, + buffer_profile_name=trimProfile + ) + time.sleep(1) + + sai_attr_dict = { + "SAI_INGRESS_PRIORITY_GROUP_ATTR_BUFFER_PROFILE": defaultProfileId + } + + trimlogger.info( + "Validate priority group: port={}, pg={}, profile={}".format(self.PORT, self.PG, defaultProfile) + ) + self.dvs_buffer.verify_priority_group( + sai_priority_group_id=pgId, + sai_qualifiers=sai_attr_dict + ) + + def verifyPriorityGroupBufferEditConfiguration(self, bufferData, pgData): + trimProfile = self.INGRESS_TRIM_PROFILE + defaultProfile = self.INGRESS_DEFAULT_PROFILE + + pgId = pgData["id"] + trimProfileId = bufferData["id"]["profile"]["trim"]["ingress"] + defaultProfileId = bufferData["id"]["profile"]["default"]["ingress"] + + # Reset buffer profile trimming eligibility + + attr_dict = { + "packet_discard_action": "drop" + } + + trimlogger.info("Update buffer profile: {}".format(trimProfile)) + self.dvs_buffer.update_buffer_profile( + buffer_profile_name=trimProfile, + qualifiers=attr_dict + ) + + sai_attr_dict = { + "SAI_BUFFER_PROFILE_ATTR_PACKET_ADMISSION_FAIL_ACTION": SAI_BUFFER_PROFILE_MODE_DICT["drop"] + } + + trimlogger.info("Validate buffer profile: {}".format(trimProfile)) + self.dvs_buffer.verify_buffer_profile( + sai_buffer_profile_id=trimProfileId, + sai_qualifiers=sai_attr_dict + ) + + # Update priority group with the trimming buffer profile + + trimlogger.info( + "Update priority group: port={}, pg={}, profile={}".format(self.PORT, self.PG, trimProfile) + ) + self.dvs_buffer.update_priority_group( + port_name=self.PORT, + pg_index=self.PG, + buffer_profile_name=trimProfile + ) + + sai_attr_dict = { + "SAI_INGRESS_PRIORITY_GROUP_ATTR_BUFFER_PROFILE": trimProfileId + } + + trimlogger.info( + "Validate priority group: port={}, pg={}, profile={}".format(self.PORT, self.PG, trimProfile) + ) + self.dvs_buffer.verify_priority_group( + sai_priority_group_id=pgId, + sai_qualifiers=sai_attr_dict + ) + + # Set buffer profile trimming eligibility + # and verify no update is done to ASIC DB + + attr_dict = { + "packet_discard_action": "trim" + } + + trimlogger.info("Update buffer profile: {}".format(trimProfile)) + self.dvs_buffer.update_buffer_profile( + buffer_profile_name=trimProfile, + qualifiers=attr_dict + ) + time.sleep(1) + + sai_attr_dict = { + "SAI_BUFFER_PROFILE_ATTR_PACKET_ADMISSION_FAIL_ACTION": SAI_BUFFER_PROFILE_MODE_DICT["drop"] + } + + trimlogger.info("Validate buffer profile: {}".format(trimProfile)) + self.dvs_buffer.verify_buffer_profile( + sai_buffer_profile_id=trimProfileId, + sai_qualifiers=sai_attr_dict + ) + + # Update priority group with the default buffer profile + + trimlogger.info( + "Update priority group: port={}, pg={}, profile={}".format(self.PORT, self.PG, defaultProfile) + ) + self.dvs_buffer.update_priority_group( + port_name=self.PORT, + pg_index=self.PG, + buffer_profile_name=defaultProfile + ) + + sai_attr_dict = { + "SAI_INGRESS_PRIORITY_GROUP_ATTR_BUFFER_PROFILE": defaultProfileId + } + + trimlogger.info( + "Validate priority group: port={}, pg={}, profile={}".format(self.PORT, self.PG, defaultProfile) + ) + self.dvs_buffer.verify_priority_group( + sai_priority_group_id=pgId, + sai_qualifiers=sai_attr_dict + ) + + def verifyProfileListBufferAttachConfiguration(self, portData, bufferData, ingress): + trimProfile = self.INGRESS_TRIM_PROFILE if ingress else self.EGRESS_TRIM_PROFILE + defaultProfile = self.INGRESS_DEFAULT_PROFILE if ingress else self.EGRESS_DEFAULT_PROFILE + + direction = "ingress" if ingress else "egress" + + portId = portData["id"] + trimProfileId = bufferData["id"]["profile"]["trim"][direction] + defaultProfileId = bufferData["id"]["profile"]["default"][direction] + + # Update port ingress/egress buffer profile list with the default buffer profile + + trimlogger.info( + "Update {} buffer profile list: port={}, profile={}".format(direction, self.PORT, defaultProfile) + ) + self.dvs_port.update_buffer_profile_list( + port_name=self.PORT, + profile_list=defaultProfile, + ingress=ingress + ) + + sai_attr_dict = { + SAI_BUFFER_PROFILE_LIST_DICT[direction]: [ defaultProfileId ] + } + + trimlogger.info( + "Validate {} buffer profile list: port={}, profile={}".format(direction, self.PORT, defaultProfile) + ) + self.dvs_port.verify_port( + sai_port_id=portId, + sai_qualifiers=sai_attr_dict + ) + + # Set buffer profile trimming eligibility + + attr_dict = { + "packet_discard_action": "trim" + } + + trimlogger.info("Update buffer profile: {}".format(trimProfile)) + self.dvs_buffer.update_buffer_profile( + buffer_profile_name=trimProfile, + qualifiers=attr_dict + ) + + sai_attr_dict = { + "SAI_BUFFER_PROFILE_ATTR_PACKET_ADMISSION_FAIL_ACTION": SAI_BUFFER_PROFILE_MODE_DICT["trim"] + } + + trimlogger.info("Validate buffer profile: {}".format(trimProfile)) + self.dvs_buffer.verify_buffer_profile( + sai_buffer_profile_id=trimProfileId, + sai_qualifiers=sai_attr_dict + ) + + # Update port ingress/egress buffer profile list with the trimming buffer profile + # and verify no update is done to ASIC DB + + trimlogger.info( + "Update {} buffer profile list: port={}, profile={}".format(direction, self.PORT, trimProfile) + ) + self.dvs_port.update_buffer_profile_list( + port_name=self.PORT, + profile_list=trimProfile, + ingress=ingress + ) + time.sleep(1) + + sai_attr_dict = { + SAI_BUFFER_PROFILE_LIST_DICT[direction]: [ defaultProfileId ] + } + + trimlogger.info( + "Validate {} buffer profile list: port={}, profile={}".format(direction, self.PORT, trimProfile) + ) + self.dvs_port.verify_port( + sai_port_id=portId, + sai_qualifiers=sai_attr_dict + ) + + # Update port ingress/egress buffer profile list with the default buffer profile + + trimlogger.info( + "Update {} buffer profile list: port={}, profile={}".format(direction, self.PORT, defaultProfile) + ) + self.dvs_port.update_buffer_profile_list( + port_name=self.PORT, + profile_list=defaultProfile, + ingress=ingress + ) + + sai_attr_dict = { + SAI_BUFFER_PROFILE_LIST_DICT[direction]: [ defaultProfileId ] + } + + trimlogger.info( + "Validate {} buffer profile list: port={}, profile={}".format(direction, self.PORT, defaultProfile) + ) + self.dvs_port.verify_port( + sai_port_id=portId, + sai_qualifiers=sai_attr_dict + ) + + def verifyProfileListBufferEditConfiguration(self, portData, bufferData, ingress): + trimProfile = self.INGRESS_TRIM_PROFILE if ingress else self.EGRESS_TRIM_PROFILE + defaultProfile = self.INGRESS_DEFAULT_PROFILE if ingress else self.EGRESS_DEFAULT_PROFILE + + direction = "ingress" if ingress else "egress" + + portId = portData["id"] + trimProfileId = bufferData["id"]["profile"]["trim"][direction] + defaultProfileId = bufferData["id"]["profile"]["default"][direction] + + # Reset buffer profile trimming eligibility + + attr_dict = { + "packet_discard_action": "drop" + } + + trimlogger.info("Update buffer profile: {}".format(trimProfile)) + self.dvs_buffer.update_buffer_profile( + buffer_profile_name=trimProfile, + qualifiers=attr_dict + ) + + sai_attr_dict = { + "SAI_BUFFER_PROFILE_ATTR_PACKET_ADMISSION_FAIL_ACTION": SAI_BUFFER_PROFILE_MODE_DICT["drop"] + } + + trimlogger.info("Validate buffer profile: {}".format(trimProfile)) + self.dvs_buffer.verify_buffer_profile( + sai_buffer_profile_id=trimProfileId, + sai_qualifiers=sai_attr_dict + ) + + # Update port ingress/egress buffer profile list with the trimming buffer profile + + trimlogger.info( + "Update {} buffer profile list: port={}, profile={}".format(direction, self.PORT, trimProfile) + ) + self.dvs_port.update_buffer_profile_list( + port_name=self.PORT, + profile_list=trimProfile, + ingress=ingress + ) + + sai_attr_dict = { + SAI_BUFFER_PROFILE_LIST_DICT[direction]: [ trimProfileId ] + } + + trimlogger.info( + "Validate {} buffer profile list: port={}, profile={}".format(direction, self.PORT, trimProfile) + ) + self.dvs_port.verify_port( + sai_port_id=portId, + sai_qualifiers=sai_attr_dict + ) + + # Set buffer profile trimming eligibility + # and verify no update is done to ASIC DB + + attr_dict = { + "packet_discard_action": "trim" + } + + trimlogger.info("Update buffer profile: {}".format(trimProfile)) + self.dvs_buffer.update_buffer_profile( + buffer_profile_name=trimProfile, + qualifiers=attr_dict + ) + time.sleep(1) + + sai_attr_dict = { + "SAI_BUFFER_PROFILE_ATTR_PACKET_ADMISSION_FAIL_ACTION": SAI_BUFFER_PROFILE_MODE_DICT["drop"] + } + + trimlogger.info("Validate buffer profile: {}".format(trimProfile)) + self.dvs_buffer.verify_buffer_profile( + sai_buffer_profile_id=trimProfileId, + sai_qualifiers=sai_attr_dict + ) + + # Update port ingress/egress buffer profile list with the default buffer profile + + trimlogger.info( + "Update {} buffer profile list: port={}, profile={}".format(direction, self.PORT, defaultProfile) + ) + self.dvs_port.update_buffer_profile_list( + port_name=self.PORT, + profile_list=defaultProfile, + ingress=ingress + ) + + sai_attr_dict = { + SAI_BUFFER_PROFILE_LIST_DICT[direction]: [ defaultProfileId ] + } + + trimlogger.info( + "Validate {} buffer profile list: port={}, profile={}".format(direction, self.PORT, defaultProfile) + ) + self.dvs_port.verify_port( + sai_port_id=portId, + sai_qualifiers=sai_attr_dict + ) + + +class TestTrimmingNegativeTraditionalBufferModel(TrimmingNegativeBufferModel): + @pytest.mark.parametrize( + "target", [ + pytest.param("pg", id="priority-group"), + pytest.param("ibuf", id="ingress-buffer-profile-list"), + pytest.param("ebuf", id="egress-buffer-profile-list") + ] + ) + def test_TrimNegStaticBufferProfileAttach(self, bufferData, portData, pgData, target): + if target == "pg": + self.verifyPriorityGroupBufferAttachConfiguration(bufferData, pgData) + elif target == "ibuf": + self.verifyProfileListBufferAttachConfiguration(portData, bufferData, True) + elif target == "ebuf": + self.verifyProfileListBufferAttachConfiguration(portData, bufferData, False) + + @pytest.mark.parametrize( + "target", [ + pytest.param("pg", id="priority-group"), + pytest.param("ibuf", id="ingress-buffer-profile-list"), + pytest.param("ebuf", id="egress-buffer-profile-list") + ] + ) + def test_TrimNegStaticBufferProfileEdit(self, bufferData, portData, pgData, target): + if target == "pg": + self.verifyPriorityGroupBufferEditConfiguration(bufferData, pgData) + elif target == "ibuf": + self.verifyProfileListBufferEditConfiguration(portData, bufferData, True) + elif target == "ebuf": + self.verifyProfileListBufferEditConfiguration(portData, bufferData, False) + + +@pytest.mark.usefixtures("dynamicBuffer") +class TestTrimmingNegativeDynamicBufferModel(TrimmingNegativeBufferModel): + @pytest.mark.parametrize( + "target", [ + pytest.param("pg", id="priority-group"), + pytest.param("ibuf", id="ingress-buffer-profile-list"), + pytest.param("ebuf", id="egress-buffer-profile-list") + ] + ) + def test_TrimNegDynamicBufferProfileAttach(self, bufferData, portData, pgData, target): + if target == "pg": + self.verifyPriorityGroupBufferAttachConfiguration(bufferData, pgData) + elif target == "ibuf": + self.verifyProfileListBufferAttachConfiguration(portData, bufferData, True) + elif target == "ebuf": + self.verifyProfileListBufferAttachConfiguration(portData, bufferData, False) + + @pytest.mark.parametrize( + "target", [ + pytest.param("pg", id="priority-group"), + pytest.param("ibuf", id="ingress-buffer-profile-list"), + pytest.param("ebuf", id="egress-buffer-profile-list") + ] + ) + def test_TrimNegDynamicBufferProfileEdit(self, bufferData, portData, pgData, target): + if target == "pg": + self.verifyPriorityGroupBufferEditConfiguration(bufferData, pgData) + elif target == "ibuf": + self.verifyProfileListBufferEditConfiguration(portData, bufferData, True) + elif target == "ebuf": + self.verifyProfileListBufferEditConfiguration(portData, bufferData, False) + + @pytest.mark.usefixtures("dvs_port_manager") @pytest.mark.usefixtures("dvs_queue_manager") @pytest.mark.usefixtures("testlog") diff --git a/tests/test_virtual_chassis.py b/tests/test_virtual_chassis.py index fbedc611..402444cd 100644 --- a/tests/test_virtual_chassis.py +++ b/tests/test_virtual_chassis.py @@ -969,7 +969,7 @@ def test_chassis_add_remove_ports(self, vct): "awk STARTFILE/ENDFILE /var/log/syslog | grep 'removeDefaultVlanMembers: Remove 32 VLAN members from default VLAN' | wc -l"] ) assert logSeen.strip() == "1" - buffer_model.disable_dynamic_buffer(dvs.get_config_db(), dvs.runcmd) + buffer_model.disable_dynamic_buffer(dvs) def test_voq_egress_queue_counter(self, vct): if vct is None: