diff --git a/vslib/inc/sai_vs.h b/vslib/inc/sai_vs.h index a67b8ec3eb2c..31d65ea8f4f2 100644 --- a/vslib/inc/sai_vs.h +++ b/vslib/inc/sai_vs.h @@ -41,6 +41,13 @@ extern "C" { */ #define SAI_VS_UNITTEST_SET_RO_OP "set_ro" +/** + * @def SAI_VS_UNITTEST_SET_STATS + * + * Notification operation for "SET" stats on specific object. + */ +#define SAI_VS_UNITTEST_SET_STATS_OP "set_stats" + /** * @def SAI_VS_UNITTEST_ENABLE * diff --git a/vslib/src/sai_vs_interfacequery.cpp b/vslib/src/sai_vs_interfacequery.cpp index 1be3a3a12f11..d1e7bd9b496b 100644 --- a/vslib/src/sai_vs_interfacequery.cpp +++ b/vslib/src/sai_vs_interfacequery.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "swss/notificationconsumer.h" #include "swss/select.h" @@ -21,140 +23,293 @@ std::shared_ptr g_dbNtf; volatile bool g_fdbAgingThreadRun; std::shared_ptr g_fdbAgingThread; -void handleUnittestChannelOp( - _In_ const std::string &op, + +void channelOpEnableUnittests( _In_ const std::string &key, _In_ const std::vector &values) { - MUTEX(); + SWSS_LOG_ENTER(); + bool enable = (key == "true"); + + meta_unittests_enable(enable); +} + +void channelOpSetReadOnlyAttribute( + _In_ const std::string &key, + _In_ const std::vector &values) +{ SWSS_LOG_ENTER(); - /* - * Since we will access and modify DB we need to be under mutex. - * - * NOTE: since this unittest channel is handled in thread, then that means - * there is a DELAY from producer and consumer thread in VS, so if user - * will set value on the specific READ_ONLY value he should wait for some - * time until that value will be propagated to virtual switch. - */ + for (const auto &v: values) + { + SWSS_LOG_DEBUG("attr: %s: %s", fvField(v).c_str(), fvValue(v).c_str()); + } - SWSS_LOG_NOTICE("read only SET: op = %s, key = %s", op.c_str(), key.c_str()); + if (values.size() != 1) + { + SWSS_LOG_ERROR("expected 1 value only, but given: %zu", values.size()); + return; + } - if (op == SAI_VS_UNITTEST_ENABLE_UNITTESTS) + const std::string &str_object_type = key.substr(0, key.find(":")); + const std::string &str_object_id = key.substr(key.find(":") + 1); + + sai_object_type_t object_type; + sai_deserialize_object_type(str_object_type, object_type); + + if (object_type == SAI_OBJECT_TYPE_NULL || object_type >= SAI_OBJECT_TYPE_MAX) { - bool enable = (key == "true"); + SWSS_LOG_ERROR("invalid object type: %d", object_type); + return; + } + + auto info = sai_metadata_get_object_type_info(object_type); - meta_unittests_enable(enable); + if (info->isnonobjectid) + { + SWSS_LOG_ERROR("non object id %s is not supported yet", str_object_type.c_str()); + return; } - else if (op == SAI_VS_UNITTEST_SET_RO_OP) + + sai_object_id_t object_id; + + sai_deserialize_object_id(str_object_id, object_id); + + sai_object_type_t ot = sai_object_type_query(object_id); + + if (ot != object_type) { - for (const auto &v: values) - { - SWSS_LOG_DEBUG("attr: %s: %s", fvField(v).c_str(), fvValue(v).c_str()); - } + SWSS_LOG_ERROR("object type is differnt than provided %s, but oid is %s", + str_object_type.c_str(), sai_serialize_object_type(ot).c_str()); + return; + } - if (values.size() != 1) - { - SWSS_LOG_ERROR("expected 1 value only, but given: %zu", values.size()); - return; - } + sai_object_id_t switch_id = sai_switch_id_query(object_id); - const std::string &str_object_type = key.substr(0, key.find(":")); - const std::string &str_object_id = key.substr(key.find(":") + 1); + if (switch_id == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("failed to find switch id for oid %s", str_object_id.c_str()); + return; + } - sai_object_type_t object_type; - sai_deserialize_object_type(str_object_type, object_type); + // oid is validated and we got switch id - if (object_type == SAI_OBJECT_TYPE_NULL || object_type >= SAI_OBJECT_TYPE_MAX) - { - SWSS_LOG_ERROR("invalid object type: %d", object_type); - return; - } + const std::string &str_attr_id = fvField(values.at(0)); + const std::string &str_attr_value = fvValue(values.at(0)); - auto info = sai_metadata_get_object_type_info(object_type); + auto meta = sai_metadata_get_attr_metadata_by_attr_id_name(str_attr_id.c_str()); - if (info->isnonobjectid) - { - SWSS_LOG_ERROR("non object id %s is not supported yet", str_object_type.c_str()); - return; - } + if (meta == NULL) + { + SWSS_LOG_ERROR("failed to find attr %s", str_attr_id.c_str()); + return; + } - sai_object_id_t object_id; + if (meta->objecttype != ot) + { + SWSS_LOG_ERROR("attr %s belongs to differnt object type than oid: %s", + str_attr_id.c_str(), sai_serialize_object_type(ot).c_str()); + return; + } - sai_deserialize_object_id(str_object_id, object_id); + // we got attr metadata - sai_object_type_t ot = sai_object_type_query(object_id); + sai_attribute_t attr; - if (ot != object_type) - { - SWSS_LOG_ERROR("object type is differnt than provided %s, but oid is %s", - str_object_type.c_str(), sai_serialize_object_type(ot).c_str()); - return; - } + attr.id = meta->attrid; - sai_object_id_t switch_id = sai_switch_id_query(object_id); + sai_deserialize_attr_value(str_attr_value, *meta, attr); - if (switch_id == SAI_NULL_OBJECT_ID) - { - SWSS_LOG_ERROR("failed to find switch id for oid %s", str_object_id.c_str()); - return; - } + SWSS_LOG_NOTICE("switch id is %s", sai_serialize_object_id(switch_id).c_str()); - // oid is validated and we got switch id + sai_status_t status = meta_unittests_allow_readonly_set_once(meta->objecttype, meta->attrid); - const std::string &str_attr_id = fvField(values.at(0)); - const std::string &str_attr_value = fvValue(values.at(0)); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("failed to enable SET readonly attribute once: %s", sai_serialize_status(status).c_str()); + return; + } - auto meta = sai_metadata_get_attr_metadata_by_attr_id_name(str_attr_id.c_str()); + sai_object_meta_key_t meta_key = { .objecttype = ot, .objectkey = { .key = { .object_id = object_id } } }; - if (meta == NULL) - { - SWSS_LOG_ERROR("failed to find attr %s", str_attr_id.c_str()); - return; - } + status = info->set(&meta_key, &attr); - if (meta->objecttype != ot) - { - SWSS_LOG_ERROR("attr %s belongs to differnt object type than oid: %s", - str_attr_id.c_str(), sai_serialize_object_type(ot).c_str()); - return; - } + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("failed to set %s to %s on %s", + str_attr_id.c_str(), str_attr_value.c_str(), str_object_id.c_str()); + } + else + { + SWSS_LOG_NOTICE("SUCCESS to set %s to %s on %s", + str_attr_id.c_str(), str_attr_value.c_str(), str_object_id.c_str()); + } - // we got attr metadata + sai_deserialize_free_attribute_value(meta->attrvaluetype, attr); +} - sai_attribute_t attr; +void channelOpSetStats( + _In_ const std::string &key, + _In_ const std::vector &values) +{ + SWSS_LOG_ENTER(); - attr.id = meta->attrid; + // NOTE: we need to find stats for specific object, later SAI already have + // this feature and this search could be optimized here: + // https://github.com/opencomputeproject/SAI/commit/acc83933ff21c68e8ef10c9826de45807fdc0438 - sai_deserialize_attr_value(str_attr_value, *meta, attr); + sai_object_id_t oid; - SWSS_LOG_NOTICE("switch id is %s", sai_serialize_object_id(switch_id).c_str()); + sai_deserialize_object_id(key, oid); - sai_status_t status = meta_unittests_allow_readonly_set_once(meta->objecttype, meta->attrid); + sai_object_type_t ot = sai_object_type_query(oid); - if (status != SAI_STATUS_SUCCESS) + if (ot == SAI_OBJECT_TYPE_NULL) + { + SWSS_LOG_ERROR("invalid object id: %s", key.c_str()); + return; + } + + sai_object_id_t switch_id = sai_switch_id_query(oid); + + if (switch_id == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("unable to get switch_id from oid: %s", key.c_str()); + return; + } + + /* + * Check if object for statistics was created and exists on switch. + */ + + auto &objectHash = g_switch_state_map.at(switch_id)->objectHash.at(ot); + + auto it = objectHash.find(key.c_str()); + + if (it == objectHash.end()) + { + SWSS_LOG_ERROR("object not found: %s", key.c_str()); + return; + } + + /* + * Check if object for statistics have statistic map created, if not + * create empty map. + */ + + auto &countersMap = g_switch_state_map.at(switch_id)->countersMap; + + auto mapit = countersMap.find(key); + + if (mapit == countersMap.end()) + countersMap[key] = std::map(); + + /* + * Find stats enum based on object type. In new metadata we have enum on + * object type, but here we need to find it manually enum is in format + * "sai_" + object_type + "_stat_t" + */ + + std::string lower_ot = sai_serialize_object_type(ot).substr(16); // 16 = skip "SAI_OBJECT_TYPE_" + + std::transform(lower_ot.begin(), lower_ot.end(), lower_ot.begin(), ::tolower); + + std::string stat_enum_name = "sai_" + lower_ot + "_stat_t"; + + const sai_enum_metadata_t* statenum = NULL; + + for (size_t i = 0; i < sai_metadata_all_enums_count; ++i) + { + if (sai_metadata_all_enums[i]->name == stat_enum_name) { - SWSS_LOG_ERROR("failed to enable SET readonly attribute once: %s", sai_serialize_status(status).c_str()); - return; + SWSS_LOG_INFO("found enum %s", stat_enum_name.c_str()); + // found + statenum = sai_metadata_all_enums[i]; + break; } + } + + if (statenum == NULL) + { + SWSS_LOG_ERROR("failed to find stat enum: %s", stat_enum_name.c_str()); + return; + } - sai_object_meta_key_t meta_key = { .objecttype = ot, .objectkey = { .key = { .object_id = object_id } } }; + for (auto v: values) + { + // value format: stat_enum_name:uint64 - status = info->set(&meta_key, &attr); + auto name = fvField(v); - if (status != SAI_STATUS_SUCCESS) + uint64_t value; + + if (sscanf(fvValue(v).c_str(), "%lu", &value) != 1) { - SWSS_LOG_ERROR("failed to set %s to %s on %s", - str_attr_id.c_str(), str_attr_value.c_str(), str_object_id.c_str()); + SWSS_LOG_ERROR("failed to deserialize %s as couner value uint64_t", fvValue(v).c_str()); } - else + + // linear search + + int enumvalue = -1; + + for (size_t i = 0; i < statenum->valuescount; ++i) + { + if (statenum->valuesnames[i] == name) + { + enumvalue = statenum->values[i]; + break; + } + } + + if (enumvalue == -1) { - SWSS_LOG_NOTICE("SUCCESS to set %s to %s on %s", - str_attr_id.c_str(), str_attr_value.c_str(), str_object_id.c_str()); + SWSS_LOG_ERROR("failed to find enum value: %s", name.c_str()); + continue; } - sai_deserialize_free_attribute_value(meta->attrvaluetype, attr); + SWSS_LOG_DEBUG("writting %s = %lu on %s", name.c_str(), value, key.c_str()); + + countersMap.at(key)[enumvalue] = value; + } +} + +void handleUnittestChannelOp( + _In_ const std::string &op, + _In_ const std::string &key, + _In_ const std::vector &values) +{ + MUTEX(); + + SWSS_LOG_ENTER(); + + /* + * Since we will access and modify DB we need to be under mutex. + * + * NOTE: since this unittest channel is handled in thread, then that means + * there is a DELAY from producer and consumer thread in VS, so if user + * will set value on the specific READ_ONLY value he should wait for some + * time until that value will be propagated to virtual switch. + */ + + SWSS_LOG_NOTICE("op = %s, key = %s", op.c_str(), key.c_str()); + + for (const auto &v: values) + { + SWSS_LOG_DEBUG("attr: %s: %s", fvField(v).c_str(), fvValue(v).c_str()); + } + + if (op == SAI_VS_UNITTEST_ENABLE_UNITTESTS) + { + channelOpEnableUnittests(key, values); + } + else if (op == SAI_VS_UNITTEST_SET_RO_OP) + { + channelOpSetReadOnlyAttribute(key, values); + } + else if (op == SAI_VS_UNITTEST_SET_STATS_OP) + { + channelOpSetStats(key, values); } else { @@ -197,7 +352,14 @@ void unittestChannelThreadProc() SWSS_LOG_DEBUG("notification: op = %s, data = %s", op.c_str(), data.c_str()); - handleUnittestChannelOp(op, data, values); + try + { + handleUnittestChannelOp(op, data, values); + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Exception: op = %s, data = %s, %s", op.c_str(), data.c_str(), e.what()); + } } } diff --git a/vslib/src/tests.cpp b/vslib/src/tests.cpp index cb6a9e3627b4..bc70d74cffa0 100644 --- a/vslib/src/tests.cpp +++ b/vslib/src/tests.cpp @@ -244,6 +244,63 @@ void test_set_readonly_attribute_via_redis() ASSERT_TRUE(sai_metadata_sai_switch_api->set_switch_attribute(switch_id, &attr) != SAI_STATUS_SUCCESS); } +void test_set_stats_via_redis() +{ + SWSS_LOG_ENTER(); + + sai_attribute_t attr; + + sai_object_id_t switch_id; + + attr.id = SAI_SWITCH_ATTR_INIT_SWITCH; + attr.value.booldata = true; + + SUCCESS(sai_metadata_sai_switch_api->create_switch(&switch_id, 1, &attr)); + + std::vector ports; + + uint32_t expected_ports = 32; + + ports.resize(expected_ports); + + attr.id = SAI_SWITCH_ATTR_PORT_LIST; + attr.value.objlist.count = expected_ports; + attr.value.objlist.list = ports.data(); + + SUCCESS(sai_metadata_sai_switch_api->get_switch_attribute(switch_id, 1, &attr)); + + // this scope contains all operations needed to perform set stats on object + { + swss::DBConnector db(ASIC_DB, "localhost", 6379, 0); + swss::NotificationProducer vsntf(&db, SAI_VS_UNITTEST_CHANNEL); + + std::vector entry; + + // needs to be done only once + vsntf.send(SAI_VS_UNITTEST_ENABLE_UNITTESTS, "true", entry); + + std::string field = "SAI_PORT_STAT_IF_IN_ERRORS"; + std::string value = "42"; // uint64_t + + swss::FieldValueTuple fvt(field, value); + + entry.push_back(fvt); + + std::string data = sai_serialize_object_id(ports.at(0)); // set on 1st port + + vsntf.send(SAI_VS_UNITTEST_SET_STATS_OP, data, entry); + } + + // give some time for notification to propagate to vs via redis + usleep(200*1000); + + sai_port_stat_t ids[1] = { SAI_PORT_STAT_IF_IN_ERRORS }; + uint64_t counters[1] = { 0 } ; + + SUCCESS(sai_metadata_sai_port_api->get_port_stats(ports.at(0), 1, ids, counters)); + + ASSERT_TRUE(counters[0] == 42); +} void sai_fdb_event_notification( _In_ uint32_t count, @@ -701,6 +758,8 @@ int main() test_get_stats(); + test_set_stats_via_redis(); + // make proper unitinialize to close unittest thread sai_api_uninitialize();