diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 43005e6d1fd..8c29e542354 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -31,7 +31,7 @@ orchagent_SOURCES = \ orchdaemon.cpp \ orch.cpp \ notifications.cpp \ - nhgorch.cpp \ + nhgorch.cpp \ routeorch.cpp \ neighorch.cpp \ intfsorch.cpp \ @@ -63,7 +63,8 @@ orchagent_SOURCES = \ debugcounterorch.cpp \ natorch.cpp \ muxorch.cpp \ - macsecorch.cpp + macsecorch.cpp \ + cbforch.cpp orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp orchagent_SOURCES += debug_counter/debug_counter.cpp debug_counter/drop_counter.cpp diff --git a/orchagent/cbforch.cpp b/orchagent/cbforch.cpp new file mode 100644 index 00000000000..e43404654c4 --- /dev/null +++ b/orchagent/cbforch.cpp @@ -0,0 +1,324 @@ +#include "cbforch.h" + +extern sai_qos_map_api_t *sai_qos_map_api; +extern sai_object_id_t gSwitchId; + +/* + * Purpose: Constructor. + * + * Description: Initialize Orch base and members. + * + * Params: IN db - The DB connection to Redis. + * IN tableNames - The Redis tables to handle. + * + * Returns: Nothing. + */ +CbfOrch::CbfOrch(swss::DBConnector *db, const vector &tableNames) : + Orch(db, tableNames), + m_dscp_map(MapHandler::Type::DSCP), + m_exp_map(MapHandler::Type::EXP) +{ + SWSS_LOG_ENTER(); +} + +/* + * Purpose: Perform the operations requested by CONFIG_DB users. + * + * Description: Redirect the operations to the appropriate table handling + * method. + * + * Params: IN consumer - The cosumer object. + * + * Returns: Nothing. + */ +void CbfOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + string table_name = consumer.getTableName(); + + if (table_name == CFG_DSCP_TO_FC_MAP_TABLE_NAME) + { + m_dscp_map.doTask(consumer); + } + else if (table_name == CFG_EXP_TO_FC_MAP_TABLE_NAME) + { + m_exp_map.doTask(consumer); + } +} + +/* + * Purpose: Perform the map table operations. + * + * Description: Iterate over the untreated operations list and resolve them. + * The operations supported are SET and DEL. If an operation + * could not be resolved, it will either remain in the list, or be + * removed, depending on the case. + * + * Params: IN consumer - The cosumer object. + * + * Returns: Nothing. + */ +void MapHandler::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + swss::KeyOpFieldsValuesTuple t = it->second; + + string map_id = kfvKey(t); + string op = kfvOp(t); + auto map_it = find(map_id); + bool success; + + if (op == SET_COMMAND) + { + SWSS_LOG_INFO("Set operator for %s map %s", + getMapName(), + map_id.c_str()); + + auto map_list = extractMap(t); + + if (map_it == end()) + { + SWSS_LOG_NOTICE("Creating %s map %s", + getMapName(), + map_id.c_str()); + + auto sai_oid = createMap(map_list); + + if (sai_oid != SAI_NULL_OBJECT_ID) + { + SWSS_LOG_INFO("Successfully created %s map", getMapName()); + emplace(map_id, sai_oid); + success = true; + } + else + { + SWSS_LOG_ERROR("Failed to create %s map %s", + getMapName(), + map_id.c_str()); + success = false; + } + } + else + { + SWSS_LOG_NOTICE("Updating existing %s map %s", + getMapName(), + map_id.c_str()); + + success = updateMap(map_it->second, map_list); + + if (success) + { + SWSS_LOG_INFO("Successfully updated %s map", getMapName()); + } + else + { + SWSS_LOG_ERROR("Failed to update %s map %s", + getMapName(), + map_id.c_str()); + } + } + + // Remove the allocated list + delete[] map_list.list; + } + else if (op == DEL_COMMAND) + { + if (map_it != end()) + { + SWSS_LOG_NOTICE("Deleting %s map %s", + getMapName(), + map_id.c_str()); + + success = removeMap(map_it->second); + + if (success) + { + SWSS_LOG_INFO("Successfully removed %s map", getMapName()); + erase(map_it); + } + else + { + SWSS_LOG_ERROR("Failed to remove %s map %s", + getMapName(), + map_id.c_str()); + } + } + else + { + SWSS_LOG_WARN("Tried to delete inexistent %s map %s", + getMapName(), + map_id.c_str()); + // Mark it as success to remove it from the consumer + success = true; + } + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + // Set success to true to remove the operation from the consumer + success = true; + } + + if (success) + { + SWSS_LOG_DEBUG("Removing consumer item"); + it = consumer.m_toSync.erase(it); + } + else + { + SWSS_LOG_DEBUG("Keeping consumer item"); + ++it; + } + } +} + +/* + * Purpose: Extract the QoS map from the Redis's field-value pairs. + * + * Description: Iterate over the field-value pairs and create a SAI QoS map. + * The QoS map object allocates memory on the heap and it is the + * caller's responsibility to free that memory when it no longer + * needs it. + * + * Params: IN t - The field-value pairs. + * + * Returns: The SAI QoS map object. + */ +sai_qos_map_list_t MapHandler::extractMap( + const swss::KeyOpFieldsValuesTuple &t) const +{ + SWSS_LOG_ENTER(); + + sai_qos_map_list_t map_list; + const auto &fields_values = kfvFieldsValues(t); + map_list.count = (uint32_t)fields_values.size(); + map_list.list = new sai_qos_map_t[map_list.count](); + + uint32_t ind = 0; + for (auto i = fields_values.begin(); + i != fields_values.end(); + i++, ind++) + { + if (m_type == Type::DSCP) + { + map_list.list[ind].key.dscp = (uint8_t)stoi(fvField(*i)); + } + else + { + map_list.list[ind].key.mpls_exp = (uint8_t)stoi(fvField(*i)); + } + + map_list.list[ind].value.fc = (uint8_t)stoi(fvValue(*i)); + } + + return map_list; +} + +/* + * Purpose: Create a QoS map. + * + * Description: Create the QoS map over the SAI interface. + * + * Params: IN map_list - The QoS map to create. + * + * Returns: The SAI ID for the newly created object or SAI_NULL_OBJECt_ID + * if something goes wrong. + */ +sai_object_id_t MapHandler::createMap(const sai_qos_map_list_t &map_list) +{ + SWSS_LOG_ENTER(); + + vector map_attrs; + + sai_attribute_t map_attr; + map_attr.id = SAI_QOS_MAP_ATTR_TYPE; + map_attr.value.u32 = (m_type == Type::DSCP) ? + SAI_QOS_MAP_TYPE_DSCP_TO_FORWARDING_CLASS : + SAI_QOS_MAP_TYPE_MPLS_EXP_TO_FORWARDING_CLASS; + map_attrs.push_back(map_attr); + + map_attr.id = SAI_QOS_MAP_ATTR_MAP_TO_VALUE_LIST; + map_attr.value.qosmap.count = map_list.count; + map_attr.value.qosmap.list = map_list.list; + map_attrs.push_back(map_attr); + + sai_object_id_t sai_oid; + auto sai_status = sai_qos_map_api->create_qos_map( + &sai_oid, + gSwitchId, + (uint32_t)map_attrs.size(), + map_attrs.data()); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create map. status: %d", sai_status); + return SAI_NULL_OBJECT_ID; + } + + return sai_oid; +} + +/* + * Purpose: Update a QoS map. + * + * Description: Update a QoS map over the SAI interface. + * + * Params: IN sai_oid - The SAI ID of the object to update. + * IN map_list - The QoS map to update the object with. + * + * Returns: true, if the update was successful, + * false, otherwise + */ +bool MapHandler::updateMap(sai_object_id_t sai_oid, + const sai_qos_map_list_t &map_list) +{ + SWSS_LOG_ENTER(); + + assert(sai_oid != SAI_NULL_OBJECT_ID); + sai_attribute_t map_attr; + map_attr.id = SAI_QOS_MAP_ATTR_MAP_TO_VALUE_LIST; + map_attr.value.qosmap.count = map_list.count; + map_attr.value.qosmap.list = map_list.list; + + auto sai_status = sai_qos_map_api->set_qos_map_attribute(sai_oid, + &map_attr); + + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to update map. status: %d", sai_status); + return false; + } + + return true; +} + +/* + * Purpose: Delete a QoS map. + * + * Description: Delete a QoS map over the SAI interface. + * + * Params: IN sai_oid - The SAI ID of the object to delete. + * + * Returns: true, if the delete was successful, + * false, otherwise + */ +bool MapHandler::removeMap(sai_object_id_t sai_oid) +{ + SWSS_LOG_ENTER(); + + assert(sai_oid != SAI_NULL_OBJECT_ID); + auto sai_status = sai_qos_map_api->remove_qos_map(sai_oid); + + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove map. status: %d", sai_status); + return false; + } + + return true; +} diff --git a/orchagent/cbforch.h b/orchagent/cbforch.h new file mode 100644 index 00000000000..d85faaa45ea --- /dev/null +++ b/orchagent/cbforch.h @@ -0,0 +1,49 @@ +#ifndef SWSS_CBFORCH_H +#define SWSS_CBFORCH_H + +#include "orch.h" + +using namespace std; + +/* Class to handle QoS map tasks. */ +class MapHandler : public unordered_map +{ +public: + /* The map type handled by this handler. */ + enum class Type + { + DSCP, + EXP + }; + + MapHandler(Type type) : m_type(type) {} + + void doTask(Consumer &consumer); + +private: + Type m_type; + + /* Get the map name based on map type. */ + const char* getMapName() const + { return m_type == Type::DSCP ? "DSCP_TO_FC" : "EXP_TO_FC"; } + + sai_qos_map_list_t extractMap(const swss::KeyOpFieldsValuesTuple &t) const; + sai_object_id_t createMap(const sai_qos_map_list_t &map_list); + bool updateMap(sai_object_id_t sai_oid, + const sai_qos_map_list_t &map_list); + bool removeMap(sai_object_id_t sai_oid); +}; + +class CbfOrch : public Orch +{ +public: + CbfOrch(swss::DBConnector *db, const vector &tableNames); + + void doTask(Consumer& consumer) override; + +private: + MapHandler m_dscp_map; + MapHandler m_exp_map; +}; + +#endif /* SWSS_CBFORCH_H */ diff --git a/orchagent/orch.cpp b/orchagent/orch.cpp index f1b0089ad70..67f5774bb60 100644 --- a/orchagent/orch.cpp +++ b/orchagent/orch.cpp @@ -741,4 +741,3 @@ void Orch2::doTask(Consumer &consumer) } } } - diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 517cabc69a8..93b9304cae7 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -40,6 +40,7 @@ SwitchOrch *gSwitchOrch; Directory gDirectory; NatOrch *gNatOrch; MACsecOrch *gMacsecOrch; +CbfOrch *gCbfOrch; bool gIsNatSupported = false; @@ -280,6 +281,10 @@ bool OrchDaemon::init() gMacsecOrch = new MACsecOrch(m_applDb, m_stateDb, macsec_app_tables, gPortsOrch); + vector cbf_tables = { CFG_DSCP_TO_FC_MAP_TABLE_NAME, + CFG_EXP_TO_FC_MAP_TABLE_NAME }; + gCbfOrch = new CbfOrch(m_configDb, cbf_tables); + /* * The order of the orch list is important for state restore of warm start and * the queued processing in m_toSync map after gPortsOrch->allPortsReady() is set. @@ -288,7 +293,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, gIntfsOrch, gNeighOrch, gRouteOrch, gNhgOrch, copp_orch, tunnel_decap_orch, qos_orch, wm_orch, policer_orch, sflow_orch, debug_counter_orch, gMacsecOrch}; + m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, gIntfsOrch, gNeighOrch, gRouteOrch, gNhgOrch, copp_orch, tunnel_decap_orch, qos_orch, wm_orch, policer_orch, sflow_orch, debug_counter_orch, gMacsecOrch, gCbfOrch}; bool initialize_dtel = false; if (platform == BFN_PLATFORM_SUBSTRING || platform == VS_PLATFORM_SUBSTRING) diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index ab38a2c9f71..9bdf460768e 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -34,6 +34,7 @@ #include "natorch.h" #include "muxorch.h" #include "macsecorch.h" +#include "cbforch.h" using namespace swss; diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 433425c6ae3..a233a72df41 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -67,7 +67,8 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/debugcounterorch.cpp \ $(top_srcdir)/orchagent/natorch.cpp \ $(top_srcdir)/orchagent/muxorch.cpp \ - $(top_srcdir)/orchagent/macsecorch.cpp + $(top_srcdir)/orchagent/macsecorch.cpp \ + $(top_srcdir)/orchagent/cbforch.cpp tests_SOURCES += $(FLEX_CTR_DIR)/flex_counter_manager.cpp $(FLEX_CTR_DIR)/flex_counter_stat_manager.cpp tests_SOURCES += $(DEBUG_CTR_DIR)/debug_counter.cpp $(DEBUG_CTR_DIR)/drop_counter.cpp diff --git a/tests/test_qos_map.py b/tests/test_qos_map.py index 32a8b396aa4..4e207779d3d 100644 --- a/tests/test_qos_map.py +++ b/tests/test_qos_map.py @@ -3,9 +3,8 @@ import sys import time -from swsscommon import swsscommon +from swsscommon import swsscommon as swss -CFG_DOT1P_TO_TC_MAP_TABLE_NAME = "DOT1P_TO_TC_MAP" CFG_DOT1P_TO_TC_MAP_KEY = "AZURE" DOT1P_TO_TC_MAP = { "0": "0", @@ -18,29 +17,26 @@ "7": "7", } -CFG_PORT_QOS_MAP_TABLE_NAME = "PORT_QOS_MAP" CFG_PORT_QOS_MAP_FIELD = "dot1p_to_tc_map" -CFG_PORT_TABLE_NAME = "PORT" class TestDot1p(object): def connect_dbs(self, dvs): - self.asic_db = swsscommon.DBConnector(1, dvs.redis_sock, 0) - self.config_db = swsscommon.DBConnector(4, dvs.redis_sock, 0) + self.asic_db = swss.DBConnector(swss.ASIC_DB, dvs.redis_sock, 0) + self.config_db = swss.DBConnector(swss.CONFIG_DB, dvs.redis_sock, 0) def create_dot1p_profile(self): - tbl = swsscommon.Table(self.config_db, CFG_DOT1P_TO_TC_MAP_TABLE_NAME) - fvs = swsscommon.FieldValuePairs(list(DOT1P_TO_TC_MAP.items())) + tbl = swss.Table(self.config_db, swss.CFG_DOT1P_TO_TC_MAP_TABLE_NAME) + fvs = swss.FieldValuePairs(list(DOT1P_TO_TC_MAP.items())) tbl.set(CFG_DOT1P_TO_TC_MAP_KEY, fvs) time.sleep(1) - def find_dot1p_profile(self): found = False dot1p_tc_map_raw = None dot1p_tc_map_key = None - tbl = swsscommon.Table(self.asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP") + tbl = swss.Table(self.asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP") keys = tbl.getKeys() for key in keys: (status, fvs) = tbl.get(key) @@ -62,9 +58,9 @@ def find_dot1p_profile(self): def apply_dot1p_profile_on_all_ports(self): - tbl = swsscommon.Table(self.config_db, CFG_PORT_QOS_MAP_TABLE_NAME) - fvs = swsscommon.FieldValuePairs([(CFG_PORT_QOS_MAP_FIELD, "[" + CFG_DOT1P_TO_TC_MAP_TABLE_NAME + "|" + CFG_DOT1P_TO_TC_MAP_KEY + "]")]) - ports = swsscommon.Table(self.config_db, CFG_PORT_TABLE_NAME).getKeys() + tbl = swss.Table(self.config_db, swss.CFG_PORT_QOS_MAP_TABLE_NAME) + fvs = swss.FieldValuePairs([(CFG_PORT_QOS_MAP_FIELD, "[" + swss.CFG_DOT1P_TO_TC_MAP_TABLE_NAME + "|" + CFG_DOT1P_TO_TC_MAP_KEY + "]")]) + ports = swss.Table(self.config_db, swss.CFG_PORT_TABLE_NAME).getKeys() for port in ports: tbl.set(port, fvs) @@ -76,7 +72,7 @@ def test_dot1p_cfg(self, dvs): self.create_dot1p_profile() oid, dot1p_tc_map_raw = self.find_dot1p_profile() - dot1p_tc_map = json.loads(dot1p_tc_map_raw); + dot1p_tc_map = json.loads(dot1p_tc_map_raw) for dot1p2tc in dot1p_tc_map['list']: dot1p = str(dot1p2tc['key']['dot1p']) tc = str(dot1p2tc['value']['tc']) @@ -91,7 +87,7 @@ def test_port_dot1p(self, dvs): self.apply_dot1p_profile_on_all_ports() cnt = 0 - tbl = swsscommon.Table(self.asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + tbl = swss.Table(self.asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") keys = tbl.getKeys() for key in keys: (status, fvs) = tbl.get(key) @@ -102,9 +98,101 @@ def test_port_dot1p(self, dvs): cnt += 1 assert fv[1] == oid - port_cnt = len(swsscommon.Table(self.config_db, CFG_PORT_TABLE_NAME).getKeys()) + port_cnt = len(swss.Table(self.config_db, swss.CFG_PORT_TABLE_NAME).getKeys()) assert port_cnt == cnt + def test_cbf(self, dvs): + cfg_db = dvs.get_config_db() + asic_db = dvs.get_asic_db() + + dscp_ps = swss.Table(cfg_db.db_connection, swss.CFG_DSCP_TO_FC_MAP_TABLE_NAME) + exp_ps = swss.Table(cfg_db.db_connection, swss.CFG_EXP_TO_FC_MAP_TABLE_NAME) + + asic_qos_map_ids = asic_db.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP") + asic_qos_map_count = len(asic_qos_map_ids) + + # Create a DSCP_TO_FC map + dscp_map = [(str(i), str(i)) for i in range(0, 64)] + dscp_ps.set("AZURE", swss.FieldValuePairs(dscp_map)) + + asic_db.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP", asic_qos_map_count + 1) + + # Get the DSCP map ID + dscp_map_id = None + for qos_id in asic_db.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP"): + if qos_id not in asic_qos_map_ids: + dscp_map_id = qos_id + break + assert(dscp_map_id is not None) + + # Assert the expected values + fvs = asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP", dscp_map_id) + assert(fvs is not None) + + dscp_map_value = fvs.get("SAI_QOS_MAP_ATTR_MAP_TO_VALUE_LIST") + assert(dscp_map_value is not None) + + qos_map_type = fvs.get("SAI_QOS_MAP_ATTR_TYPE") + assert(fvs.get("SAI_QOS_MAP_ATTR_TYPE") == "SAI_QOS_MAP_TYPE_DSCP_TO_FORWARDING_CLASS") + + # Update the map to different values + dscp_map = [(str(i + 1), str(i + 1)) for i in range(0, 64)] + dscp_ps.set("AZURE", swss.FieldValuePairs(dscp_map)) + + asic_db.wait_for_field_negative_match("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP", + dscp_map_id, + {'SAI_QOS_MAP_ATTR_MAP_TO_VALUE_LIST': dscp_map_value}) + + # Delete the map + dscp_ps._del("AZURE") + asic_db.wait_for_deleted_entry("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP", dscp_map_id) + + # Delete a map that does not exist. Nothing should happen + dscp_ps._del("AZURE") + time.sleep(1) + assert(len(asic_db.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP")) == asic_qos_map_count) + + # Create a EXP_TO_FC map + exp_map = [(str(i), str(i)) for i in range(0, 8)] + exp_ps.set("AZURE", swss.FieldValuePairs(exp_map)) + + asic_db.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP", asic_qos_map_count + 1) + + # Get the EXP map ID + exp_map_id = None + for qos_id in asic_db.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP"): + if qos_id not in asic_qos_map_ids + (dscp_map_id,): + exp_map_id = qos_id + break + assert(exp_map_id is not None) + + # Assert the expected values + fvs = asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP", exp_map_id) + assert(fvs is not None) + + exp_map_value = fvs.get("SAI_QOS_MAP_ATTR_MAP_TO_VALUE_LIST") + assert(exp_map_value is not None) + + qos_map_type = fvs.get("SAI_QOS_MAP_ATTR_TYPE") + assert(fvs.get("SAI_QOS_MAP_ATTR_TYPE") == "SAI_QOS_MAP_TYPE_MPLS_EXP_TO_FORWARDING_CLASS") + + # Update the map to different values + exp_map = [(str(i + 1), str(i + 1)) for i in range(0, 8)] + exp_ps.set("AZURE", swss.FieldValuePairs(exp_map)) + + asic_db.wait_for_field_negative_match("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP", + exp_map_id, + {'SAI_QOS_MAP_ATTR_MAP_TO_VALUE_LIST': exp_map_value}) + + # Delete the map + exp_ps._del("AZURE") + asic_db.wait_for_deleted_entry("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP", exp_map_id) + + # Delete a map that does not exist. Nothing should happen + exp_ps._del("AZURE") + time.sleep(1) + assert(len(asic_db.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_QOS_MAP")) == asic_qos_map_count) + # Add Dummy always-pass test at end as workaroud # for issue when Flaky fail on final test it invokes module tear-down before retrying