From 12f980c8c494e7a1f716001670a2fc48d44bf055 Mon Sep 17 00:00:00 2001 From: Stephen Sun <5379172+stephenxs@users.noreply.github.com> Date: Wed, 9 Mar 2022 15:08:41 +0800 Subject: [PATCH] Fix issue config qos reload causing orchagent aborted via tracking dependencies among QoS tables (#2116) - What I did Fix issue config qos reload causing orchagent aborted via tracking dependencies among QoS tables 1. Track dependencies among QoS tables. 2. Won't call SAI remove API for an object if it is still referenced. 3. Support removing/replacing one field in PORT_QOS_MAP and QUEUE tables. 4. Optimize logic to handle QUEUE table. 5. Remove switch level DSCP_TO_TC map before the map is removed. 6. Add mock test - Why I did it Fix issue. - How I verified it Manually test and mock test. Signed-off-by: Stephen Sun --- orchagent/orch.cpp | 43 +- orchagent/orch.h | 3 +- orchagent/orchdaemon.cpp | 5 +- orchagent/qosorch.cpp | 296 +++++++--- orchagent/qosorch.h | 7 + tests/mock_tests/Makefile.am | 1 + tests/mock_tests/mock_orchagent_main.h | 6 + tests/mock_tests/qosorch_ut.cpp | 789 +++++++++++++++++++++++++ tests/mock_tests/ut_saihelper.cpp | 4 + 9 files changed, 1070 insertions(+), 84 deletions(-) create mode 100644 tests/mock_tests/qosorch_ut.cpp diff --git a/orchagent/orch.cpp b/orchagent/orch.cpp index 0992e329a436..0330414c99f2 100644 --- a/orchagent/orch.cpp +++ b/orchagent/orch.cpp @@ -410,7 +410,8 @@ void Orch::removeMeFromObjsReferencedByMe( const string &table, const string &obj_name, const string &field, - const string &old_referenced_obj_name) + const string &old_referenced_obj_name, + bool remove_field) { vector objects = tokenize(old_referenced_obj_name, list_item_delimiter); for (auto &obj : objects) @@ -426,6 +427,12 @@ void Orch::removeMeFromObjsReferencedByMe( referenced_table.c_str(), ref_obj_name.c_str(), to_string(old_referenced_obj.m_objsDependingOnMe.size()).c_str()); } + + if (remove_field) + { + auto &referencing_object = (*type_maps[table])[obj_name]; + referencing_object.m_objsReferencingByMe.erase(field); + } } void Orch::setObjectReference( @@ -439,7 +446,7 @@ void Orch::setObjectReference( auto field_ref = obj.m_objsReferencingByMe.find(field); if (field_ref != obj.m_objsReferencingByMe.end()) - removeMeFromObjsReferencedByMe(type_maps, table, obj_name, field, field_ref->second); + removeMeFromObjsReferencedByMe(type_maps, table, obj_name, field, field_ref->second, false); obj.m_objsReferencingByMe[field] = referenced_obj; @@ -459,16 +466,44 @@ void Orch::setObjectReference( } } +bool Orch::doesObjectExist( + type_map &type_maps, + const string &table, + const string &obj_name, + const string &field, + string &referenced_obj) +{ + auto &&searchRef = (*type_maps[table]).find(obj_name); + if (searchRef != (*type_maps[table]).end()) + { + auto &obj = searchRef->second; + auto &&searchReferencingObjectRef = obj.m_objsReferencingByMe.find(field); + if (searchReferencingObjectRef != obj.m_objsReferencingByMe.end()) + { + referenced_obj = searchReferencingObjectRef->second; + return true; + } + } + + return false; +} + void Orch::removeObject( type_map &type_maps, const string &table, const string &obj_name) { - auto &obj = (*type_maps[table])[obj_name]; + auto &&searchRef = (*type_maps[table]).find(obj_name); + if (searchRef == (*type_maps[table]).end()) + { + return; + } + + auto &obj = searchRef->second; for (auto field_ref : obj.m_objsReferencingByMe) { - removeMeFromObjsReferencedByMe(type_maps, table, obj_name, field_ref.first, field_ref.second); + removeMeFromObjsReferencedByMe(type_maps, table, obj_name, field_ref.first, field_ref.second, false); } // Update the field store diff --git a/orchagent/orch.h b/orchagent/orch.h index 46a5d446ce92..ea5b48278475 100644 --- a/orchagent/orch.h +++ b/orchagent/orch.h @@ -233,9 +233,11 @@ class Orch bool parseReference(type_map &type_maps, std::string &ref, const std::string &table_name, std::string &object_name); ref_resolve_status resolveFieldRefArray(type_map&, const std::string&, const std::string&, swss::KeyOpFieldsValuesTuple&, std::vector&, std::string&); void setObjectReference(type_map&, const std::string&, const std::string&, const std::string&, const std::string&); + bool doesObjectExist(type_map&, const std::string&, const std::string&, const std::string&, std::string&); void removeObject(type_map&, const std::string&, const std::string&); bool isObjectBeingReferenced(type_map&, const std::string&, const std::string&); std::string objectReferenceInfo(type_map&, const std::string&, const std::string&); + void removeMeFromObjsReferencedByMe(type_map &type_maps, const std::string &table, const std::string &obj_name, const std::string &field, const std::string &old_referenced_obj_name, bool remove_field=true); /* Note: consumer will be owned by this class */ void addExecutor(Executor* executor); @@ -250,7 +252,6 @@ class Orch ResponsePublisher m_publisher; private: - void removeMeFromObjsReferencedByMe(type_map &type_maps, const std::string &table, const std::string &obj_name, const std::string &field, const std::string &old_referenced_obj_name); void addConsumer(swss::DBConnector *db, std::string tableName, int pri = default_orch_pri); }; diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 0341f69ea94b..0d6147248db0 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -41,6 +41,7 @@ PbhOrch *gPbhOrch; MirrorOrch *gMirrorOrch; CrmOrch *gCrmOrch; BufferOrch *gBufferOrch; +QosOrch *gQosOrch; SwitchOrch *gSwitchOrch; Directory gDirectory; NatOrch *gNatOrch; @@ -216,7 +217,7 @@ bool OrchDaemon::init() CFG_DSCP_TO_FC_MAP_TABLE_NAME, CFG_EXP_TO_FC_MAP_TABLE_NAME }; - QosOrch *qos_orch = new QosOrch(m_configDb, qos_tables); + gQosOrch = new QosOrch(m_configDb, qos_tables); vector buffer_tables = { APP_BUFFER_POOL_TABLE_NAME, @@ -325,7 +326,7 @@ bool OrchDaemon::init() * when iterating ConsumerMap. This is ensured implicitly by the order of keys in ordered map. * For cases when Orch has to process tables in specific order, like PortsOrch during warm start, it has to override Orch::doTask() */ - m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, mux_orch, mux_cb_orch, gIntfsOrch, gNeighOrch, gNhgMapOrch, gNhgOrch, gCbfNhgOrch, gRouteOrch, gCoppOrch, qos_orch, wm_orch, policer_orch, tunnel_decap_orch, sflow_orch, debug_counter_orch, gMacsecOrch, gBfdOrch, gSrv6Orch}; + m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, mux_orch, mux_cb_orch, gIntfsOrch, gNeighOrch, gNhgMapOrch, gNhgOrch, gCbfNhgOrch, gRouteOrch, gCoppOrch, gQosOrch, wm_orch, policer_orch, tunnel_decap_orch, sflow_orch, debug_counter_orch, gMacsecOrch, gBfdOrch, gSrv6Orch}; bool initialize_dtel = false; if (platform == BFN_PLATFORM_SUBSTRING || platform == VS_PLATFORM_SUBSTRING) diff --git a/orchagent/qosorch.cpp b/orchagent/qosorch.cpp index 5a48b6996958..d1a24cb5c9a8 100644 --- a/orchagent/qosorch.cpp +++ b/orchagent/qosorch.cpp @@ -25,6 +25,7 @@ extern sai_acl_api_t* sai_acl_api; extern SwitchOrch *gSwitchOrch; extern PortsOrch *gPortsOrch; +extern QosOrch *gQosOrch; extern sai_object_id_t gSwitchId; extern CrmOrch *gCrmOrch; @@ -150,6 +151,12 @@ task_process_status QosMapHandler::processWorkItem(Consumer& consumer) SWSS_LOG_ERROR("Object with name:%s not found.", qos_object_name.c_str()); return task_process_status::task_invalid_entry; } + if (gQosOrch->isObjectBeingReferenced(QosOrch::getTypeMap(), qos_map_type_name, qos_object_name)) + { + auto hint = gQosOrch->objectReferenceInfo(QosOrch::getTypeMap(), qos_map_type_name, qos_object_name); + SWSS_LOG_NOTICE("Can't remove object %s due to being referenced (%s)", qos_object_name.c_str(), hint.c_str()); + return task_process_status::task_need_retry; + } if (!removeQosItem(sai_object)) { SWSS_LOG_ERROR("Failed to remove dscp_to_tc map. db name:%s sai object:%" PRIx64, qos_object_name.c_str(), sai_object); @@ -243,6 +250,9 @@ void DscpToTcMapHandler::applyDscpToTcMapToSwitch(sai_attr_id_t attr_id, sai_obj return; } + if (map_id != gQosOrch->m_globalDscpToTcMap) + gQosOrch->m_globalDscpToTcMap = map_id; + SWSS_LOG_NOTICE("Applied DSCP_TO_TC QoS map to switch successfully"); } @@ -276,6 +286,41 @@ sai_object_id_t DscpToTcMapHandler::addQosItem(const vector &at return sai_object; } +bool DscpToTcMapHandler::removeQosItem(sai_object_id_t sai_object) +{ + SWSS_LOG_ENTER(); + + if (sai_object == gQosOrch->m_globalDscpToTcMap) + { + // The current global dscp to tc map is about to be removed. + // Find another one to set to the switch or NULL in case this is the last one + const auto &dscpToTcObjects = (*QosOrch::getTypeMap()[CFG_DSCP_TO_TC_MAP_TABLE_NAME]); + bool found = false; + for (const auto &ref : dscpToTcObjects) + { + if (ref.second.m_saiObjectId == sai_object) + continue; + SWSS_LOG_NOTICE("Current global dscp_to_tc map is about to be removed, set it to %s %" PRIx64, ref.first.c_str(), ref.second.m_saiObjectId); + applyDscpToTcMapToSwitch(SAI_SWITCH_ATTR_QOS_DSCP_TO_TC_MAP, ref.second.m_saiObjectId); + found = true; + break; + } + if (!found) + { + applyDscpToTcMapToSwitch(SAI_SWITCH_ATTR_QOS_DSCP_TO_TC_MAP, SAI_NULL_OBJECT_ID); + } + } + + SWSS_LOG_DEBUG("Removing DscpToTcMap object:%" PRIx64, sai_object); + sai_status_t sai_status = sai_qos_map_api->remove_qos_map(sai_object); + if (SAI_STATUS_SUCCESS != sai_status) + { + SWSS_LOG_ERROR("Failed to remove DSCP_TO_TC map, status:%d", sai_status); + return false; + } + return true; +} + task_process_status QosOrch::handleDscpToTcTable(Consumer& consumer) { SWSS_LOG_ENTER(); @@ -1195,6 +1240,12 @@ task_process_status QosOrch::handleSchedulerTable(Consumer& consumer) SWSS_LOG_ERROR("Object with name:%s not found.", qos_object_name.c_str()); return task_process_status::task_invalid_entry; } + if (gQosOrch->isObjectBeingReferenced(QosOrch::getTypeMap(), qos_map_type_name, qos_object_name)) + { + auto hint = gQosOrch->objectReferenceInfo(QosOrch::getTypeMap(), qos_map_type_name, qos_object_name); + SWSS_LOG_NOTICE("Can't remove object %s due to being referenced (%s)", qos_object_name.c_str(), hint.c_str()); + return task_process_status::task_need_retry; + } sai_status = sai_scheduler_api->remove_scheduler(sai_object); if (SAI_STATUS_SUCCESS != sai_status) { @@ -1435,6 +1486,94 @@ task_process_status QosOrch::handleQueueTable(Consumer& consumer) SWSS_LOG_ERROR("Failed to parse range:%s", tokens[1].c_str()); return task_process_status::task_invalid_entry; } + + bool donotChangeScheduler = false; + bool donotChangeWredProfile = false; + sai_object_id_t sai_scheduler_profile; + sai_object_id_t sai_wred_profile; + + if (op == SET_COMMAND) + { + string scheduler_profile_name; + resolve_result = resolveFieldRefValue(m_qos_maps, scheduler_field_name, + qos_to_ref_table_map.at(scheduler_field_name), tuple, + sai_scheduler_profile, scheduler_profile_name); + if (ref_resolve_status::success != resolve_result) + { + if (resolve_result != ref_resolve_status::field_not_found) + { + if(ref_resolve_status::not_resolved == resolve_result) + { + SWSS_LOG_INFO("Missing or invalid scheduler reference"); + return task_process_status::task_need_retry; + } + SWSS_LOG_ERROR("Resolving scheduler reference failed"); + return task_process_status::task_failed; + } + + if (doesObjectExist(m_qos_maps, CFG_QUEUE_TABLE_NAME, key, scheduler_field_name, scheduler_profile_name)) + { + SWSS_LOG_NOTICE("QUEUE|%s %s was configured but is not any more. Remove it", key.c_str(), scheduler_field_name.c_str()); + removeMeFromObjsReferencedByMe(m_qos_maps, CFG_QUEUE_TABLE_NAME, key, scheduler_field_name, scheduler_profile_name); + sai_scheduler_profile = SAI_NULL_OBJECT_ID; + } + else + { + // Did not exist and do not exist. No action + donotChangeScheduler = true; + } + } + else + { + setObjectReference(m_qos_maps, CFG_QUEUE_TABLE_NAME, key, scheduler_field_name, scheduler_profile_name); + SWSS_LOG_INFO("QUEUE %s Field %s %s has been resolved to %" PRIx64 , key.c_str(), scheduler_field_name.c_str(), scheduler_profile_name.c_str(), sai_scheduler_profile); + } + + string wred_profile_name; + resolve_result = resolveFieldRefValue(m_qos_maps, wred_profile_field_name, + qos_to_ref_table_map.at(wred_profile_field_name), tuple, + sai_wred_profile, wred_profile_name); + if (ref_resolve_status::success != resolve_result) + { + if (resolve_result != ref_resolve_status::field_not_found) + { + if(ref_resolve_status::not_resolved == resolve_result) + { + SWSS_LOG_INFO("Missing or invalid wred profile reference"); + return task_process_status::task_need_retry; + } + SWSS_LOG_ERROR("Resolving wred profile reference failed"); + return task_process_status::task_failed; + } + + if (doesObjectExist(m_qos_maps, CFG_QUEUE_TABLE_NAME, key, wred_profile_field_name, wred_profile_name)) + { + SWSS_LOG_NOTICE("QUEUE|%s %s was configured but is not any more. Remove it", key.c_str(), wred_profile_field_name.c_str()); + removeMeFromObjsReferencedByMe(m_qos_maps, CFG_QUEUE_TABLE_NAME, key, wred_profile_field_name, wred_profile_name); + sai_wred_profile = SAI_NULL_OBJECT_ID; + } + else + { + donotChangeWredProfile = true; + } + } + else + { + setObjectReference(m_qos_maps, CFG_QUEUE_TABLE_NAME, key, wred_profile_field_name, wred_profile_name); + } + } + else if (op == DEL_COMMAND) + { + removeObject(QosOrch::getTypeMap(), CFG_QUEUE_TABLE_NAME, key); + sai_scheduler_profile = SAI_NULL_OBJECT_ID; + sai_wred_profile = SAI_NULL_OBJECT_ID; + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + return task_process_status::task_invalid_entry; + } + for (string port_name : port_names) { Port port; @@ -1449,27 +1588,11 @@ task_process_status QosOrch::handleQueueTable(Consumer& consumer) { queue_ind = ind; SWSS_LOG_DEBUG("processing queue:%zd", queue_ind); - sai_object_id_t sai_scheduler_profile; - string scheduler_profile_name; - resolve_result = resolveFieldRefValue(m_qos_maps, scheduler_field_name, - qos_to_ref_table_map.at(scheduler_field_name), tuple, - sai_scheduler_profile, scheduler_profile_name); - if (ref_resolve_status::success == resolve_result) + + if (!donotChangeScheduler) { - if (op == SET_COMMAND) - { - result = applySchedulerToQueueSchedulerGroup(port, queue_ind, sai_scheduler_profile); - } - else if (op == DEL_COMMAND) - { - // NOTE: The map is un-bound from the port. But the map itself still exists. - result = applySchedulerToQueueSchedulerGroup(port, queue_ind, SAI_NULL_OBJECT_ID); - } - else - { - SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); - return task_process_status::task_invalid_entry; - } + result = applySchedulerToQueueSchedulerGroup(port, queue_ind, sai_scheduler_profile); + if (!result) { SWSS_LOG_ERROR("Failed setting field:%s to port:%s, queue:%zd, line:%d", scheduler_field_name.c_str(), port.m_alias.c_str(), queue_ind, __LINE__); @@ -1477,38 +1600,11 @@ task_process_status QosOrch::handleQueueTable(Consumer& consumer) } SWSS_LOG_DEBUG("Applied scheduler to port:%s", port_name.c_str()); } - else if (resolve_result != ref_resolve_status::field_not_found) - { - if(ref_resolve_status::not_resolved == resolve_result) - { - SWSS_LOG_INFO("Missing or invalid scheduler reference"); - return task_process_status::task_need_retry; - } - SWSS_LOG_ERROR("Resolving scheduler reference failed"); - return task_process_status::task_failed; - } - sai_object_id_t sai_wred_profile; - string wred_profile_name; - resolve_result = resolveFieldRefValue(m_qos_maps, wred_profile_field_name, - qos_to_ref_table_map.at(wred_profile_field_name), tuple, - sai_wred_profile, wred_profile_name); - if (ref_resolve_status::success == resolve_result) + if (!donotChangeWredProfile) { - if (op == SET_COMMAND) - { - result = applyWredProfileToQueue(port, queue_ind, sai_wred_profile); - } - else if (op == DEL_COMMAND) - { - // NOTE: The map is un-bound from the port. But the map itself still exists. - result = applyWredProfileToQueue(port, queue_ind, SAI_NULL_OBJECT_ID); - } - else - { - SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); - return task_process_status::task_invalid_entry; - } + result = applyWredProfileToQueue(port, queue_ind, sai_wred_profile); + if (!result) { SWSS_LOG_ERROR("Failed setting field:%s to port:%s, queue:%zd, line:%d", wred_profile_field_name.c_str(), port.m_alias.c_str(), queue_ind, __LINE__); @@ -1516,31 +1612,6 @@ task_process_status QosOrch::handleQueueTable(Consumer& consumer) } SWSS_LOG_DEBUG("Applied wred profile to port:%s", port_name.c_str()); } - else if (resolve_result != ref_resolve_status::field_not_found) - { - if (ref_resolve_status::empty == resolve_result) - { - SWSS_LOG_INFO("Missing wred reference. Unbind wred profile from queue"); - // NOTE: The wred profile is un-bound from the port. But the wred profile itself still exists - // and stays untouched. - result = applyWredProfileToQueue(port, queue_ind, SAI_NULL_OBJECT_ID); - if (!result) - { - SWSS_LOG_ERROR("Failed unbinding field:%s from port:%s, queue:%zd, line:%d", wred_profile_field_name.c_str(), port.m_alias.c_str(), queue_ind, __LINE__); - return task_process_status::task_failed; - } - } - else if (ref_resolve_status::not_resolved == resolve_result) - { - SWSS_LOG_INFO("Invalid wred reference"); - return task_process_status::task_need_retry; - } - else - { - SWSS_LOG_ERROR("Resolving wred reference failed"); - return task_process_status::task_failed; - } - } } } SWSS_LOG_DEBUG("finished"); @@ -1626,6 +1697,60 @@ task_process_status QosOrch::handlePortQosMapTable(Consumer& consumer) KeyOpFieldsValuesTuple tuple = consumer.m_toSync.begin()->second; string key = kfvKey(tuple); string op = kfvOp(tuple); + vector port_names = tokenize(key, list_item_delimiter); + + if (op == DEL_COMMAND) + { + /* Handle DEL command. Just set all the maps to oid:0x0 */ + for (string port_name : port_names) + { + Port port; + + /* Skip port which is not found */ + if (!gPortsOrch->getPort(port_name, port)) + { + SWSS_LOG_ERROR("Failed to apply QoS maps to port %s. Port is not found.", port_name.c_str()); + continue; + } + + for (auto &mapRef : qos_to_attr_map) + { + string referenced_obj; + if (!doesObjectExist(m_qos_maps, CFG_PORT_QOS_MAP_TABLE_NAME, key, mapRef.first, referenced_obj)) + { + continue; + } + + sai_attribute_t attr; + attr.id = mapRef.second; + attr.value.oid = SAI_NULL_OBJECT_ID; + + sai_status_t status = sai_port_api->set_port_attribute(port.m_port_id, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove %s on port %s, rv:%d", + mapRef.first.c_str(), port_name.c_str(), status); + task_process_status handle_status = handleSaiSetStatus(SAI_API_PORT, status); + if (handle_status != task_process_status::task_success) + { + return task_process_status::task_invalid_entry; + } + } + SWSS_LOG_INFO("Removed %s on port %s", mapRef.first.c_str(), port_name.c_str()); + } + + if (!gPortsOrch->setPortPfc(port.m_port_id, 0)) + { + SWSS_LOG_ERROR("Failed to disable PFC on port %s", port_name.c_str()); + } + + SWSS_LOG_INFO("Disabled PFC on port %s", port_name.c_str()); + } + + removeObject(m_qos_maps, CFG_PORT_QOS_MAP_TABLE_NAME, key); + + return task_process_status::task_success; + } sai_uint8_t pfc_enable = 0; map> update_list; @@ -1636,7 +1761,7 @@ task_process_status QosOrch::handlePortQosMapTable(Consumer& consumer) { sai_object_id_t id; string object_name; - string map_type_name = fvField(*it), map_name = fvValue(*it); + string &map_type_name = fvField(*it), &map_name = fvValue(*it); ref_resolve_status status = resolveFieldRefValue(m_qos_maps, map_type_name, qos_to_ref_table_map.at(map_type_name), tuple, id, object_name); if (status != ref_resolve_status::success) @@ -1646,6 +1771,7 @@ task_process_status QosOrch::handlePortQosMapTable(Consumer& consumer) } update_list[qos_to_attr_map[map_type_name]] = make_pair(map_name, id); + setObjectReference(m_qos_maps, CFG_PORT_QOS_MAP_TABLE_NAME, key, map_type_name, object_name); } if (fvField(*it) == pfc_enable_name) @@ -1660,7 +1786,23 @@ task_process_status QosOrch::handlePortQosMapTable(Consumer& consumer) } } - vector port_names = tokenize(key, list_item_delimiter); + /* Remove any map that was configured but isn't there any longer. */ + for (auto &mapRef : qos_to_attr_map) + { + auto &sai_attribute = mapRef.second; + if (update_list.find(sai_attribute) == update_list.end()) + { + string referenced_obj; + if (!doesObjectExist(m_qos_maps, CFG_PORT_QOS_MAP_TABLE_NAME, key, mapRef.first, referenced_obj)) + { + continue; + } + SWSS_LOG_NOTICE("PORT_QOS_MAP|%s %s was configured but is not any more. Remove it", key.c_str(), mapRef.first.c_str()); + removeMeFromObjsReferencedByMe(m_qos_maps, CFG_PORT_QOS_MAP_TABLE_NAME, key, mapRef.first, referenced_obj); + update_list[mapRef.second] = make_pair("NULL", SAI_NULL_OBJECT_ID); + } + } + for (string port_name : port_names) { Port port; diff --git a/orchagent/qosorch.h b/orchagent/qosorch.h index cd265d59ece0..613bc7437ede 100644 --- a/orchagent/qosorch.h +++ b/orchagent/qosorch.h @@ -72,6 +72,7 @@ class DscpToTcMapHandler : public QosMapHandler public: bool convertFieldValuesToAttributes(KeyOpFieldsValuesTuple &tuple, vector &attributes) override; sai_object_id_t addQosItem(const vector &attributes) override; + bool removeQosItem(sai_object_id_t sai_object); protected: void applyDscpToTcMapToSwitch(sai_attr_id_t attr_id, sai_object_id_t sai_dscp_to_tc_map); }; @@ -196,5 +197,11 @@ class QosOrch : public Orch }; std::unordered_map m_scheduler_group_port_info; + + // SAI OID of the global dscp to tc map + sai_object_id_t m_globalDscpToTcMap; + + friend QosMapHandler; + friend DscpToTcMapHandler; }; #endif /* SWSS_QOSORCH_H */ diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index f82b556e4721..77639d2cb660 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -24,6 +24,7 @@ LDADD_GTEST = -L/usr/src/gtest tests_SOURCES = aclorch_ut.cpp \ portsorch_ut.cpp \ routeorch_ut.cpp \ + qosorch_ut.cpp \ saispy_ut.cpp \ consumer_ut.cpp \ ut_saihelper.cpp \ diff --git a/tests/mock_tests/mock_orchagent_main.h b/tests/mock_tests/mock_orchagent_main.h index 04e82e74eba1..a194d3ea0aa5 100644 --- a/tests/mock_tests/mock_orchagent_main.h +++ b/tests/mock_tests/mock_orchagent_main.h @@ -10,6 +10,7 @@ #include "fdborch.h" #include "mirrororch.h" #include "bufferorch.h" +#include "qosorch.h" #include "vrforch.h" #include "vnetorch.h" #include "vxlanorch.h" @@ -46,6 +47,7 @@ extern NeighOrch *gNeighOrch; extern FdbOrch *gFdbOrch; extern MirrorOrch *gMirrorOrch; extern BufferOrch *gBufferOrch; +extern QosOrch *gQosOrch; extern VRFOrch *gVrfOrch; extern NhgOrch *gNhgOrch; extern Srv6Orch *gSrv6Orch; @@ -65,6 +67,10 @@ extern sai_tunnel_api_t *sai_tunnel_api; extern sai_next_hop_api_t *sai_next_hop_api; extern sai_hostif_api_t *sai_hostif_api; extern sai_buffer_api_t *sai_buffer_api; +extern sai_qos_map_api_t *sai_qos_map_api; +extern sai_scheduler_api_t *sai_scheduler_api; +extern sai_scheduler_group_api_t *sai_scheduler_group_api; +extern sai_wred_api_t *sai_wred_api; extern sai_queue_api_t *sai_queue_api; extern sai_udf_api_t* sai_udf_api; extern sai_mpls_api_t* sai_mpls_api; diff --git a/tests/mock_tests/qosorch_ut.cpp b/tests/mock_tests/qosorch_ut.cpp new file mode 100644 index 000000000000..a77d19b38bf0 --- /dev/null +++ b/tests/mock_tests/qosorch_ut.cpp @@ -0,0 +1,789 @@ +#define private public // make Directory::m_values available to clean it. +#include "directory.h" +#undef private +#define protected public +#include "orch.h" +#undef protected +#include "ut_helper.h" +#include "mock_orchagent_main.h" +#include "mock_table.h" + +extern string gMySwitchType; + + +namespace qosorch_test +{ + using namespace std; + + shared_ptr m_app_db; + shared_ptr m_config_db; + shared_ptr m_state_db; + shared_ptr m_chassis_app_db; + + int sai_remove_qos_map_count; + int sai_remove_wred_profile_count; + int sai_remove_scheduler_count; + sai_object_id_t switch_dscp_to_tc_map_id; + + sai_remove_scheduler_fn old_remove_scheduler; + sai_scheduler_api_t ut_sai_scheduler_api, *pold_sai_scheduler_api; + sai_remove_wred_fn old_remove_wred; + sai_wred_api_t ut_sai_wred_api, *pold_sai_wred_api; + sai_remove_qos_map_fn old_remove_qos_map; + sai_qos_map_api_t ut_sai_qos_map_api, *pold_sai_qos_map_api; + sai_set_switch_attribute_fn old_set_switch_attribute_fn; + sai_switch_api_t ut_sai_switch_api, *pold_sai_switch_api; + + sai_status_t _ut_stub_sai_set_switch_attribute(sai_object_id_t switch_id, const sai_attribute_t *attr) + { + auto rc = old_set_switch_attribute_fn(switch_id, attr); + if (rc == SAI_STATUS_SUCCESS && attr->id == SAI_SWITCH_ATTR_QOS_DSCP_TO_TC_MAP) + switch_dscp_to_tc_map_id = attr->value.oid; + return rc; + } + + sai_status_t _ut_stub_sai_remove_qos_map(sai_object_id_t qos_map_id) + { + auto rc = old_remove_qos_map(qos_map_id); + if (rc == SAI_STATUS_SUCCESS) + sai_remove_qos_map_count++; + return rc; + } + + sai_status_t _ut_stub_sai_remove_wred(sai_object_id_t wred_id) + { + auto rc = old_remove_wred(wred_id); + if (rc == SAI_STATUS_SUCCESS) + sai_remove_wred_profile_count++; + return rc; + } + + sai_status_t _ut_stub_sai_remove_scheduler(sai_object_id_t scheduler_id) + { + auto rc = old_remove_scheduler(scheduler_id); + if (rc == SAI_STATUS_SUCCESS) + sai_remove_scheduler_count++; + return rc; + } + + struct QosOrchTest : public ::testing::Test + { + QosOrchTest() + { + } + + void CheckDependency(const string &referencingTableName, const string &referencingObjectName, const string &field, const string &dependentTableName, const string &dependentObjectName="") + { + auto &qosTypeMaps = QosOrch::getTypeMap(); + auto &referencingTable = (*qosTypeMaps[referencingTableName]); + auto &dependentTable = (*qosTypeMaps[dependentTableName]); + + if (dependentObjectName.empty()) + { + ASSERT_TRUE(referencingTable[referencingObjectName].m_objsReferencingByMe[field].empty()); + ASSERT_EQ(dependentTable[dependentObjectName].m_objsDependingOnMe.count(referencingObjectName), 0); + } + else + { + ASSERT_EQ(referencingTable[referencingObjectName].m_objsReferencingByMe[field], dependentTableName + ":" + dependentObjectName); + ASSERT_EQ(dependentTable[dependentObjectName].m_objsDependingOnMe.count(referencingObjectName), 1); + } + } + + void RemoveItem(const string &table, const string &key) + { + std::deque entries; + entries.push_back({key, "DEL", {}}); + auto consumer = dynamic_cast(gQosOrch->getExecutor(table)); + consumer->addToSync(entries); + } + + template void ReplaceSaiRemoveApi(sai_api_t* &sai_api, + sai_api_t &ut_sai_api, + sai_api_t* &pold_sai_api, + sai_remove_func ut_remove, + sai_remove_func &sai_remove, + sai_remove_func &old_remove, + sai_remove_func &put_remove) + { + old_remove = sai_remove; + pold_sai_api = sai_api; + ut_sai_api = *pold_sai_api; + sai_api = &ut_sai_api; + put_remove = ut_remove; + } + + void SetUp() override + { + ASSERT_EQ(sai_route_api, nullptr); + map profile = { + { "SAI_VS_SWITCH_TYPE", "SAI_VS_SWITCH_TYPE_BCM56850" }, + { "KV_DEVICE_MAC_ADDRESS", "20:03:04:05:06:00" } + }; + + ut_helper::initSaiApi(profile); + + // Hack SAI APIs + ReplaceSaiRemoveApi(sai_qos_map_api, ut_sai_qos_map_api, pold_sai_qos_map_api, + _ut_stub_sai_remove_qos_map, sai_qos_map_api->remove_qos_map, + old_remove_qos_map, ut_sai_qos_map_api.remove_qos_map); + ReplaceSaiRemoveApi(sai_scheduler_api, ut_sai_scheduler_api, pold_sai_scheduler_api, + _ut_stub_sai_remove_scheduler, sai_scheduler_api->remove_scheduler, + old_remove_scheduler, ut_sai_scheduler_api.remove_scheduler); + ReplaceSaiRemoveApi(sai_wred_api, ut_sai_wred_api, pold_sai_wred_api, + _ut_stub_sai_remove_wred, sai_wred_api->remove_wred, + old_remove_wred, ut_sai_wred_api.remove_wred); + pold_sai_switch_api = sai_switch_api; + ut_sai_switch_api = *pold_sai_switch_api; + old_set_switch_attribute_fn = pold_sai_switch_api->set_switch_attribute; + sai_switch_api = &ut_sai_switch_api; + ut_sai_switch_api.set_switch_attribute = _ut_stub_sai_set_switch_attribute; + + // Init switch and create dependencies + m_app_db = make_shared("APPL_DB", 0); + m_config_db = make_shared("CONFIG_DB", 0); + m_state_db = make_shared("STATE_DB", 0); + if(gMySwitchType == "voq") + m_chassis_app_db = make_shared("CHASSIS_APP_DB", 0); + + sai_attribute_t attr; + + attr.id = SAI_SWITCH_ATTR_INIT_SWITCH; + attr.value.booldata = true; + + auto status = sai_switch_api->create_switch(&gSwitchId, 1, &attr); + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + // Get switch source MAC address + attr.id = SAI_SWITCH_ATTR_SRC_MAC_ADDRESS; + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + gMacAddress = attr.value.mac; + + // Get the default virtual router ID + attr.id = SAI_SWITCH_ATTR_DEFAULT_VIRTUAL_ROUTER_ID; + status = sai_switch_api->get_switch_attribute(gSwitchId, 1, &attr); + + ASSERT_EQ(status, SAI_STATUS_SUCCESS); + + gVirtualRouterId = attr.value.oid; + + ASSERT_EQ(gCrmOrch, nullptr); + gCrmOrch = new CrmOrch(m_config_db.get(), CFG_CRM_TABLE_NAME); + + TableConnector stateDbSwitchTable(m_state_db.get(), "SWITCH_CAPABILITY"); + TableConnector conf_asic_sensors(m_config_db.get(), CFG_ASIC_SENSORS_TABLE_NAME); + TableConnector app_switch_table(m_app_db.get(), APP_SWITCH_TABLE_NAME); + + vector switch_tables = { + conf_asic_sensors, + app_switch_table + }; + + ASSERT_EQ(gSwitchOrch, nullptr); + gSwitchOrch = new SwitchOrch(m_app_db.get(), switch_tables, stateDbSwitchTable); + + // Create dependencies ... + + const int portsorch_base_pri = 40; + + vector ports_tables = { + { APP_PORT_TABLE_NAME, portsorch_base_pri + 5 }, + { APP_VLAN_TABLE_NAME, portsorch_base_pri + 2 }, + { APP_VLAN_MEMBER_TABLE_NAME, portsorch_base_pri }, + { APP_LAG_TABLE_NAME, portsorch_base_pri + 4 }, + { APP_LAG_MEMBER_TABLE_NAME, portsorch_base_pri } + }; + + vector flex_counter_tables = { + CFG_FLEX_COUNTER_TABLE_NAME + }; + auto* flexCounterOrch = new FlexCounterOrch(m_config_db.get(), flex_counter_tables); + gDirectory.set(flexCounterOrch); + + ASSERT_EQ(gPortsOrch, nullptr); + gPortsOrch = new PortsOrch(m_app_db.get(), m_state_db.get(), ports_tables, m_chassis_app_db.get()); + + ASSERT_EQ(gVrfOrch, nullptr); + gVrfOrch = new VRFOrch(m_app_db.get(), APP_VRF_TABLE_NAME, m_state_db.get(), STATE_VRF_OBJECT_TABLE_NAME); + + ASSERT_EQ(gIntfsOrch, nullptr); + gIntfsOrch = new IntfsOrch(m_app_db.get(), APP_INTF_TABLE_NAME, gVrfOrch, m_chassis_app_db.get()); + + const int fdborch_pri = 20; + + vector app_fdb_tables = { + { APP_FDB_TABLE_NAME, FdbOrch::fdborch_pri}, + { APP_VXLAN_FDB_TABLE_NAME, FdbOrch::fdborch_pri}, + { APP_MCLAG_FDB_TABLE_NAME, fdborch_pri} + }; + + TableConnector stateDbFdb(m_state_db.get(), STATE_FDB_TABLE_NAME); + TableConnector stateMclagDbFdb(m_state_db.get(), STATE_MCLAG_REMOTE_FDB_TABLE_NAME); + ASSERT_EQ(gFdbOrch, nullptr); + gFdbOrch = new FdbOrch(m_app_db.get(), app_fdb_tables, stateDbFdb, stateMclagDbFdb, gPortsOrch); + + ASSERT_EQ(gNeighOrch, nullptr); + gNeighOrch = new NeighOrch(m_app_db.get(), APP_NEIGH_TABLE_NAME, gIntfsOrch, gFdbOrch, gPortsOrch, m_chassis_app_db.get()); + + vector qos_tables = { + CFG_TC_TO_QUEUE_MAP_TABLE_NAME, + CFG_SCHEDULER_TABLE_NAME, + CFG_DSCP_TO_TC_MAP_TABLE_NAME, + CFG_MPLS_TC_TO_TC_MAP_TABLE_NAME, + CFG_DOT1P_TO_TC_MAP_TABLE_NAME, + CFG_QUEUE_TABLE_NAME, + CFG_PORT_QOS_MAP_TABLE_NAME, + CFG_WRED_PROFILE_TABLE_NAME, + CFG_TC_TO_PRIORITY_GROUP_MAP_TABLE_NAME, + CFG_PFC_PRIORITY_TO_PRIORITY_GROUP_MAP_TABLE_NAME, + CFG_PFC_PRIORITY_TO_QUEUE_MAP_TABLE_NAME, + CFG_DSCP_TO_FC_MAP_TABLE_NAME, + CFG_EXP_TO_FC_MAP_TABLE_NAME + }; + gQosOrch = new QosOrch(m_config_db.get(), qos_tables); + + // Recreate buffer orch to read populated data + vector buffer_tables = { APP_BUFFER_POOL_TABLE_NAME, + APP_BUFFER_PROFILE_TABLE_NAME, + APP_BUFFER_QUEUE_TABLE_NAME, + APP_BUFFER_PG_TABLE_NAME, + APP_BUFFER_PORT_INGRESS_PROFILE_LIST_NAME, + APP_BUFFER_PORT_EGRESS_PROFILE_LIST_NAME }; + + gBufferOrch = new BufferOrch(m_app_db.get(), m_config_db.get(), m_state_db.get(), buffer_tables); + + Table portTable = Table(m_app_db.get(), APP_PORT_TABLE_NAME); + + // Get SAI default ports to populate DB + auto ports = ut_helper::getInitialSaiPorts(); + + // Populate pot table with SAI ports + for (const auto &it : ports) + { + portTable.set(it.first, it.second); + } + + // Set PortConfigDone + portTable.set("PortConfigDone", { { "count", to_string(ports.size()) } }); + gPortsOrch->addExistingData(&portTable); + static_cast(gPortsOrch)->doTask(); + + portTable.set("PortInitDone", { { "lanes", "0" } }); + gPortsOrch->addExistingData(&portTable); + static_cast(gPortsOrch)->doTask(); + + Table tcToQueueMapTable = Table(m_config_db.get(), CFG_TC_TO_QUEUE_MAP_TABLE_NAME); + Table scheduleTable = Table(m_config_db.get(), CFG_SCHEDULER_TABLE_NAME); + Table dscpToTcMapTable = Table(m_config_db.get(), CFG_DSCP_TO_TC_MAP_TABLE_NAME); + Table dot1pToTcMapTable = Table(m_config_db.get(), CFG_DOT1P_TO_TC_MAP_TABLE_NAME); + Table queueTable = Table(m_config_db.get(), CFG_QUEUE_TABLE_NAME); + Table portQosMapTable = Table(m_config_db.get(), CFG_PORT_QOS_MAP_TABLE_NAME); + Table wredProfileTable = Table(m_config_db.get(), CFG_WRED_PROFILE_TABLE_NAME); + Table tcToPgMapTable = Table(m_config_db.get(), CFG_TC_TO_PRIORITY_GROUP_MAP_TABLE_NAME); + Table pfcPriorityToPgMapTable = Table(m_config_db.get(), CFG_PFC_PRIORITY_TO_PRIORITY_GROUP_MAP_TABLE_NAME); + Table pfcPriorityToQueueMapTable = Table(m_config_db.get(), CFG_PFC_PRIORITY_TO_QUEUE_MAP_TABLE_NAME); + Table dscpToFcMapTable = Table(m_config_db.get(), CFG_DSCP_TO_FC_MAP_TABLE_NAME); + Table expToFcMapTable = Table(m_config_db.get(), CFG_EXP_TO_FC_MAP_TABLE_NAME); + + scheduleTable.set("scheduler.1", + { + {"type", "DWRR"}, + {"weight", "15"} + }); + + scheduleTable.set("scheduler.0", + { + {"type", "DWRR"}, + {"weight", "14"} + }); + + wredProfileTable.set("AZURE_LOSSLESS", + { + {"ecn", "ecn_all"}, + {"green_drop_probability", "5"}, + {"green_max_threshold", "2097152"}, + {"green_min_threshold", "1048576"}, + {"wred_green_enable", "true"}, + {"yellow_drop_probability", "5"}, + {"yellow_max_threshold", "2097152"}, + {"yellow_min_threshold", "1048576"}, + {"wred_yellow_enable", "true"}, + {"red_drop_probability", "5"}, + {"red_max_threshold", "2097152"}, + {"red_min_threshold", "1048576"}, + {"wred_red_enable", "true"} + }); + + tcToQueueMapTable.set("AZURE", + { + {"0", "0"}, + {"1", "1"} + }); + + dscpToTcMapTable.set("AZURE", + { + {"0", "0"}, + {"1", "1"} + }); + + tcToPgMapTable.set("AZURE", + { + {"0", "0"}, + {"1", "1"} + }); + + dot1pToTcMapTable.set("AZURE", + { + {"0", "0"}, + {"1", "1"} + }); + + pfcPriorityToPgMapTable.set("AZURE", + { + {"0", "0"}, + {"1", "1"} + }); + + pfcPriorityToQueueMapTable.set("AZURE", + { + {"0", "0"}, + {"1", "1"} + }); + + dot1pToTcMapTable.set("AZURE", + { + {"0", "0"}, + {"1", "1"} + }); + + gQosOrch->addExistingData(&tcToQueueMapTable); + gQosOrch->addExistingData(&dscpToTcMapTable); + gQosOrch->addExistingData(&tcToPgMapTable); + gQosOrch->addExistingData(&pfcPriorityToPgMapTable); + gQosOrch->addExistingData(&pfcPriorityToQueueMapTable); + gQosOrch->addExistingData(&scheduleTable); + gQosOrch->addExistingData(&wredProfileTable); + + static_cast(gQosOrch)->doTask(); + } + + void TearDown() override + { + auto qos_maps = QosOrch::getTypeMap(); + for (auto &i : qos_maps) + { + i.second->clear(); + } + + gDirectory.m_values.clear(); + + delete gCrmOrch; + gCrmOrch = nullptr; + + delete gSwitchOrch; + gSwitchOrch = nullptr; + + delete gVrfOrch; + gVrfOrch = nullptr; + + delete gIntfsOrch; + gIntfsOrch = nullptr; + + delete gNeighOrch; + gNeighOrch = nullptr; + + delete gFdbOrch; + gFdbOrch = nullptr; + + delete gPortsOrch; + gPortsOrch = nullptr; + + delete gQosOrch; + gQosOrch = nullptr; + + sai_qos_map_api = pold_sai_qos_map_api; + sai_scheduler_api = pold_sai_scheduler_api; + sai_wred_api = pold_sai_wred_api; + sai_switch_api = pold_sai_switch_api; + ut_helper::uninitSaiApi(); + } + }; + + TEST_F(QosOrchTest, QosOrchTestPortQosMapRemoveOneField) + { + Table portQosMapTable = Table(m_config_db.get(), CFG_PORT_QOS_MAP_TABLE_NAME); + + portQosMapTable.set("Ethernet0", + { + {"dscp_to_tc_map", "AZURE"}, + {"pfc_to_pg_map", "AZURE"}, + {"pfc_to_queue_map", "AZURE"}, + {"tc_to_pg_map", "AZURE"}, + {"tc_to_queue_map", "AZURE"}, + {"pfc_enable", "3,4"} + }); + gQosOrch->addExistingData(&portQosMapTable); + static_cast(gQosOrch)->doTask(); + + // Check whether the dependencies have been recorded + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "dscp_to_tc_map", CFG_DSCP_TO_TC_MAP_TABLE_NAME, "AZURE"); + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "pfc_to_pg_map", CFG_PFC_PRIORITY_TO_PRIORITY_GROUP_MAP_TABLE_NAME, "AZURE"); + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "pfc_to_queue_map", CFG_PFC_PRIORITY_TO_QUEUE_MAP_TABLE_NAME, "AZURE"); + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "tc_to_pg_map", CFG_TC_TO_PRIORITY_GROUP_MAP_TABLE_NAME, "AZURE"); + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "tc_to_queue_map", CFG_TC_TO_QUEUE_MAP_TABLE_NAME, "AZURE"); + + // Try removing AZURE from DSCP_TO_TC_MAP while it is still referenced + RemoveItem(CFG_DSCP_TO_TC_MAP_TABLE_NAME, "AZURE"); + auto current_sai_remove_qos_map_count = sai_remove_qos_map_count; + static_cast(gQosOrch)->doTask(); + ASSERT_EQ(current_sai_remove_qos_map_count, sai_remove_qos_map_count); + // Dependency is not cleared + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "dscp_to_tc_map", CFG_DSCP_TO_TC_MAP_TABLE_NAME, "AZURE"); + + // Remove dscp_to_tc_map from Ethernet0 via resetting the entry with field dscp_to_tc_map removed + std::deque entries; + entries.push_back({"Ethernet0", "SET", + { + {"pfc_to_pg_map", "AZURE"}, + {"pfc_to_queue_map", "AZURE"}, + {"tc_to_pg_map", "AZURE"}, + {"tc_to_queue_map", "AZURE"}, + {"pfc_enable", "3,4"} + }}); + auto consumer = dynamic_cast(gQosOrch->getExecutor(CFG_PORT_QOS_MAP_TABLE_NAME)); + consumer->addToSync(entries); + entries.clear(); + // Drain PORT_QOS_MAP table + static_cast(gQosOrch)->doTask(); + // Drain DSCP_TO_TC_MAP table + static_cast(gQosOrch)->doTask(); + ASSERT_EQ(current_sai_remove_qos_map_count + 1, sai_remove_qos_map_count); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_DSCP_TO_TC_MAP_TABLE_NAME]).count("AZURE"), 0); + // Dependency of dscp_to_tc_map should be cleared + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "dscp_to_tc_map", CFG_DSCP_TO_TC_MAP_TABLE_NAME); + // Dependencies of other items are not touched + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "pfc_to_pg_map", CFG_PFC_PRIORITY_TO_PRIORITY_GROUP_MAP_TABLE_NAME, "AZURE"); + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "pfc_to_queue_map", CFG_PFC_PRIORITY_TO_QUEUE_MAP_TABLE_NAME, "AZURE"); + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "tc_to_pg_map", CFG_TC_TO_PRIORITY_GROUP_MAP_TABLE_NAME, "AZURE"); + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "tc_to_queue_map", CFG_TC_TO_QUEUE_MAP_TABLE_NAME, "AZURE"); + } + + TEST_F(QosOrchTest, QosOrchTestQueueRemoveWredProfile) + { + std::deque entries; + Table queueTable = Table(m_config_db.get(), CFG_QUEUE_TABLE_NAME); + + queueTable.set("Ethernet0|3", + { + {"scheduler", "scheduler.1"}, + {"wred_profile", "AZURE_LOSSLESS"} + }); + gQosOrch->addExistingData(&queueTable); + static_cast(gQosOrch)->doTask(); + + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "scheduler", CFG_SCHEDULER_TABLE_NAME, "scheduler.1"); + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "wred_profile", CFG_WRED_PROFILE_TABLE_NAME, "AZURE_LOSSLESS"); + + // Try removing scheduler from WRED_PROFILE table while it is still referenced + RemoveItem(CFG_WRED_PROFILE_TABLE_NAME, "AZURE_LOSSLESS"); + auto current_sai_remove_wred_profile_count = sai_remove_wred_profile_count; + static_cast(gQosOrch)->doTask(); + ASSERT_EQ(current_sai_remove_wred_profile_count, sai_remove_wred_profile_count); + // Make sure the dependency is untouched + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "wred_profile", CFG_WRED_PROFILE_TABLE_NAME, "AZURE_LOSSLESS"); + + // Remove wred_profile from Ethernet0 queue 3 + entries.push_back({"Ethernet0|3", "SET", + { + {"scheduler", "scheduler.1"} + }}); + auto consumer = dynamic_cast(gQosOrch->getExecutor(CFG_QUEUE_TABLE_NAME)); + consumer->addToSync(entries); + entries.clear(); + // Drain QUEUE table + static_cast(gQosOrch)->doTask(); + // Drain WRED_PROFILE table + static_cast(gQosOrch)->doTask(); + // Make sure the dependency is cleared + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "wred_profile", CFG_WRED_PROFILE_TABLE_NAME); + // And the sai remove API has been called + ASSERT_EQ(current_sai_remove_wred_profile_count + 1, sai_remove_wred_profile_count); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_WRED_PROFILE_TABLE_NAME]).count("AZURE_LOSSLESS"), 0); + // Other field should be untouched + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "scheduler", CFG_SCHEDULER_TABLE_NAME, "scheduler.1"); + } + + TEST_F(QosOrchTest, QosOrchTestQueueRemoveScheduler) + { + std::deque entries; + Table queueTable = Table(m_config_db.get(), CFG_QUEUE_TABLE_NAME); + + queueTable.set("Ethernet0|3", + { + {"scheduler", "scheduler.1"}, + {"wred_profile", "AZURE_LOSSLESS"} + }); + gQosOrch->addExistingData(&queueTable); + static_cast(gQosOrch)->doTask(); + + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "scheduler", CFG_SCHEDULER_TABLE_NAME, "scheduler.1"); + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "wred_profile", CFG_WRED_PROFILE_TABLE_NAME, "AZURE_LOSSLESS"); + + // Try removing scheduler from QUEUE table while it is still referenced + RemoveItem(CFG_SCHEDULER_TABLE_NAME, "scheduler.1"); + auto current_sai_remove_scheduler_count = sai_remove_scheduler_count; + static_cast(gQosOrch)->doTask(); + ASSERT_EQ(current_sai_remove_scheduler_count, sai_remove_scheduler_count); + // Make sure the dependency is untouched + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "scheduler", CFG_SCHEDULER_TABLE_NAME, "scheduler.1"); + + // Remove scheduler from Ethernet0 queue 3 + entries.push_back({"Ethernet0|3", "SET", + { + {"wred_profile", "AZURE_LOSSLESS"} + }}); + auto consumer = dynamic_cast(gQosOrch->getExecutor(CFG_QUEUE_TABLE_NAME)); + consumer->addToSync(entries); + entries.clear(); + // Drain QUEUE table + static_cast(gQosOrch)->doTask(); + // Drain SCHEDULER table + static_cast(gQosOrch)->doTask(); + // Make sure the dependency is cleared + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "scheduler", CFG_SCHEDULER_TABLE_NAME); + // And the sai remove API has been called + ASSERT_EQ(current_sai_remove_scheduler_count + 1, sai_remove_scheduler_count); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_SCHEDULER_TABLE_NAME]).count("scheduler.1"), 0); + // Other field should be untouched + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "wred_profile", CFG_WRED_PROFILE_TABLE_NAME, "AZURE_LOSSLESS"); + } + + TEST_F(QosOrchTest, QosOrchTestQueueReplaceFieldAndRemoveObject) + { + std::deque entries; + Table queueTable = Table(m_config_db.get(), CFG_QUEUE_TABLE_NAME); + auto queueConsumer = dynamic_cast(gQosOrch->getExecutor(CFG_QUEUE_TABLE_NAME)); + auto wredProfileConsumer = dynamic_cast(gQosOrch->getExecutor(CFG_WRED_PROFILE_TABLE_NAME)); + auto schedulerConsumer = dynamic_cast(gQosOrch->getExecutor(CFG_SCHEDULER_TABLE_NAME)); + + queueTable.set("Ethernet0|3", + { + {"scheduler", "scheduler.1"}, + {"wred_profile", "AZURE_LOSSLESS"} + }); + gQosOrch->addExistingData(&queueTable); + static_cast(gQosOrch)->doTask(); + + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "scheduler", CFG_SCHEDULER_TABLE_NAME, "scheduler.1"); + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "wred_profile", CFG_WRED_PROFILE_TABLE_NAME, "AZURE_LOSSLESS"); + + // Try replacing scheduler in QUEUE table: scheduler.1 => scheduler.0 + entries.push_back({"Ethernet0|3", "SET", + { + {"scheduler", "scheduler.0"}, + {"wred_profile", "AZURE_LOSSLESS"} + }}); + queueConsumer->addToSync(entries); + entries.clear(); + // Drain QUEUE table + static_cast(gQosOrch)->doTask(); + // Make sure the dependency is updated + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "scheduler", CFG_SCHEDULER_TABLE_NAME, "scheduler.0"); + // And the other field is not touched + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "wred_profile", CFG_WRED_PROFILE_TABLE_NAME, "AZURE_LOSSLESS"); + + RemoveItem(CFG_SCHEDULER_TABLE_NAME, "scheduler.1"); + auto current_sai_remove_scheduler_count = sai_remove_scheduler_count; + static_cast(gQosOrch)->doTask(); + ASSERT_EQ(++current_sai_remove_scheduler_count, sai_remove_scheduler_count); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_SCHEDULER_TABLE_NAME]).count("scheduler.1"), 0); + + entries.push_back({"AZURE_LOSSLESS_1", "SET", + { + {"ecn", "ecn_all"}, + {"green_drop_probability", "5"}, + {"green_max_threshold", "2097152"}, + {"green_min_threshold", "1048576"}, + {"wred_green_enable", "true"}, + {"yellow_drop_probability", "5"}, + {"yellow_max_threshold", "2097152"}, + {"yellow_min_threshold", "1048576"}, + {"wred_yellow_enable", "true"}, + {"red_drop_probability", "5"}, + {"red_max_threshold", "2097152"}, + {"red_min_threshold", "1048576"}, + {"wred_red_enable", "true"} + }}); + wredProfileConsumer->addToSync(entries); + entries.clear(); + // Drain WRED_PROFILE table + static_cast(gQosOrch)->doTask(); + + // Replace wred_profile from Ethernet0 queue 3 + entries.push_back({"Ethernet0|3", "SET", + { + {"scheduler", "scheduler.0"}, + {"wred_profile", "AZURE_LOSSLESS_1"} + }}); + queueConsumer->addToSync(entries); + entries.clear(); + // Drain QUEUE table + static_cast(gQosOrch)->doTask(); + // Make sure the dependency is updated + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "wred_profile", CFG_WRED_PROFILE_TABLE_NAME, "AZURE_LOSSLESS_1"); + // And the other field is not touched + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "scheduler", CFG_SCHEDULER_TABLE_NAME, "scheduler.0"); + + RemoveItem(CFG_WRED_PROFILE_TABLE_NAME, "AZURE_LOSSLESS"); + // Drain WRED_PROFILE table + auto current_sai_remove_wred_profile_count = sai_remove_wred_profile_count; + static_cast(gQosOrch)->doTask(); + ASSERT_EQ(++current_sai_remove_wred_profile_count, sai_remove_wred_profile_count); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_WRED_PROFILE_TABLE_NAME]).count("AZURE_LOSSLESS"), 0); + + // Remove object + entries.push_back({"Ethernet0|3", "DEL", {}}); + queueConsumer->addToSync(entries); + entries.clear(); + static_cast(gQosOrch)->doTask(); + + // Make sure the dependency is updated + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "wred_profile", CFG_WRED_PROFILE_TABLE_NAME); + CheckDependency(CFG_QUEUE_TABLE_NAME, "Ethernet0|3", "scheduler", CFG_SCHEDULER_TABLE_NAME); + + // Remove scheduler object + entries.push_back({"scheduler.0", "DEL", {}}); + schedulerConsumer->addToSync(entries); + entries.clear(); + static_cast(gQosOrch)->doTask(); + ASSERT_EQ(++current_sai_remove_scheduler_count, sai_remove_scheduler_count); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_SCHEDULER_TABLE_NAME]).count("scheduler.0"), 0); + + // Remove wred profile object + entries.push_back({"AZURE_LOSSLESS_1", "DEL", {}}); + wredProfileConsumer->addToSync(entries); + entries.clear(); + static_cast(gQosOrch)->doTask(); + ASSERT_EQ(++current_sai_remove_wred_profile_count, sai_remove_wred_profile_count); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_WRED_PROFILE_TABLE_NAME]).count("AZURE_LOSSLESS_1"), 0); + } + + TEST_F(QosOrchTest, QosOrchTestPortQosMapReplaceOneFieldAndRemoveObject) + { + std::deque entries; + Table portQosMapTable = Table(m_config_db.get(), CFG_PORT_QOS_MAP_TABLE_NAME); + + portQosMapTable.set("Ethernet0", + { + {"dscp_to_tc_map", "AZURE"}, + {"pfc_to_pg_map", "AZURE"}, + {"pfc_to_queue_map", "AZURE"}, + {"tc_to_pg_map", "AZURE"}, + {"tc_to_queue_map", "AZURE"}, + {"pfc_enable", "3,4"} + }); + + static_cast(gQosOrch)->doTask(); + + entries.push_back({"AZURE_1", "SET", + { + {"1", "0"}, + {"0", "1"} + }}); + + auto consumer = dynamic_cast(gQosOrch->getExecutor(CFG_DSCP_TO_TC_MAP_TABLE_NAME)); + consumer->addToSync(entries); + entries.clear(); + // Drain DSCP_TO_TC_MAP table + static_cast(gQosOrch)->doTask(); + + entries.push_back({"Ethernet0", "SET", + { + {"dscp_to_tc_map", "AZURE_1"}, + {"pfc_to_pg_map", "AZURE"}, + {"pfc_to_queue_map", "AZURE"}, + {"tc_to_pg_map", "AZURE"}, + {"tc_to_queue_map", "AZURE"}, + {"pfc_enable", "3,4"} + }}); + consumer = dynamic_cast(gQosOrch->getExecutor(CFG_PORT_QOS_MAP_TABLE_NAME)); + consumer->addToSync(entries); + entries.clear(); + // Drain PORT_QOS_MAP table + static_cast(gQosOrch)->doTask(); + // Dependency is updated + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "dscp_to_tc_map", CFG_DSCP_TO_TC_MAP_TABLE_NAME, "AZURE_1"); + + // Try removing AZURE from DSCP_TO_TC_MAP + RemoveItem(CFG_DSCP_TO_TC_MAP_TABLE_NAME, "AZURE"); + auto current_sai_remove_qos_map_count = sai_remove_qos_map_count; + static_cast(gQosOrch)->doTask(); + ASSERT_EQ(++current_sai_remove_qos_map_count, sai_remove_qos_map_count); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_DSCP_TO_TC_MAP_TABLE_NAME]).count("AZURE"), 0); + // Global dscp to tc map should not be cleared + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_DSCP_TO_TC_MAP_TABLE_NAME])["AZURE_1"].m_saiObjectId, switch_dscp_to_tc_map_id); + + // Make sure other dependencies are not touched + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "pfc_to_pg_map", CFG_PFC_PRIORITY_TO_PRIORITY_GROUP_MAP_TABLE_NAME, "AZURE"); + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "pfc_to_queue_map", CFG_PFC_PRIORITY_TO_QUEUE_MAP_TABLE_NAME, "AZURE"); + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "tc_to_pg_map", CFG_TC_TO_PRIORITY_GROUP_MAP_TABLE_NAME, "AZURE"); + CheckDependency(CFG_PORT_QOS_MAP_TABLE_NAME, "Ethernet0", "tc_to_queue_map", CFG_TC_TO_QUEUE_MAP_TABLE_NAME, "AZURE"); + + // Remove port from PORT_QOS_MAP table + entries.push_back({"Ethernet0", "DEL", {}}); + consumer->addToSync(entries); + entries.clear(); + static_cast(gQosOrch)->doTask(); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_PORT_QOS_MAP_TABLE_NAME]).count("Ethernet0"), 0); + + // Make sure the maps can be removed now. Checking anyone should suffice since all the maps are handled in the same way. + entries.push_back({"AZURE", "DEL", {}}); + consumer = dynamic_cast(gQosOrch->getExecutor(CFG_PFC_PRIORITY_TO_PRIORITY_GROUP_MAP_TABLE_NAME)); + consumer->addToSync(entries); + entries.clear(); + static_cast(gQosOrch)->doTask(); + ASSERT_EQ(++current_sai_remove_qos_map_count, sai_remove_qos_map_count); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_PFC_PRIORITY_TO_PRIORITY_GROUP_MAP_TABLE_NAME]).count("AZURE"), 0); + + entries.push_back({"AZURE_1", "DEL", {}}); + consumer = dynamic_cast(gQosOrch->getExecutor(CFG_DSCP_TO_TC_MAP_TABLE_NAME)); + consumer->addToSync(entries); + entries.clear(); + static_cast(gQosOrch)->doTask(); + ASSERT_EQ(++current_sai_remove_qos_map_count, sai_remove_qos_map_count); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_DSCP_TO_TC_MAP_TABLE_NAME]).count("AZURE_1"), 0); + // Global dscp to tc map should be cleared + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_DSCP_TO_TC_MAP_TABLE_NAME])["AZURE_1"].m_saiObjectId, SAI_NULL_OBJECT_ID); + } + + TEST_F(QosOrchTest, QosOrchTestGlobalDscpToTcMap) + { + // Make sure dscp to tc map is correct + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_DSCP_TO_TC_MAP_TABLE_NAME])["AZURE"].m_saiObjectId, switch_dscp_to_tc_map_id); + + // Create a new dscp to tc map + std::deque entries; + entries.push_back({"AZURE_1", "SET", + { + {"1", "0"}, + {"0", "1"} + }}); + + auto consumer = dynamic_cast(gQosOrch->getExecutor(CFG_DSCP_TO_TC_MAP_TABLE_NAME)); + consumer->addToSync(entries); + entries.clear(); + // Drain DSCP_TO_TC_MAP table + static_cast(gQosOrch)->doTask(); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_DSCP_TO_TC_MAP_TABLE_NAME])["AZURE_1"].m_saiObjectId, switch_dscp_to_tc_map_id); + + entries.push_back({"AZURE_1", "DEL", {}}); + consumer->addToSync(entries); + entries.clear(); + // Drain DSCP_TO_TC_MAP table + static_cast(gQosOrch)->doTask(); + ASSERT_EQ((*QosOrch::getTypeMap()[CFG_DSCP_TO_TC_MAP_TABLE_NAME])["AZURE"].m_saiObjectId, switch_dscp_to_tc_map_id); + } +} diff --git a/tests/mock_tests/ut_saihelper.cpp b/tests/mock_tests/ut_saihelper.cpp index e16a217e71fb..70eb96c99f8a 100644 --- a/tests/mock_tests/ut_saihelper.cpp +++ b/tests/mock_tests/ut_saihelper.cpp @@ -77,6 +77,10 @@ namespace ut_helper sai_api_query(SAI_API_ACL, (void **)&sai_acl_api); sai_api_query(SAI_API_HOSTIF, (void **)&sai_hostif_api); sai_api_query(SAI_API_BUFFER, (void **)&sai_buffer_api); + sai_api_query(SAI_API_QOS_MAP, (void **)&sai_qos_map_api); + sai_api_query(SAI_API_SCHEDULER_GROUP, (void **)&sai_scheduler_group_api); + sai_api_query(SAI_API_SCHEDULER, (void **)&sai_scheduler_api); + sai_api_query(SAI_API_WRED, (void **)&sai_wred_api); sai_api_query(SAI_API_QUEUE, (void **)&sai_queue_api); sai_api_query(SAI_API_MPLS, (void**)&sai_mpls_api);