From 2afbae5dba7b233c927cb078744299925fe2bdcb Mon Sep 17 00:00:00 2001 From: Kamil Cudnik Date: Wed, 10 Jan 2018 11:47:26 +0100 Subject: [PATCH] Add virtual switch unittests channel via redis (#274) For easier testing when using python framework --- vslib/inc/sai_vs.h | 23 ++- vslib/src/sai_vs_interfacequery.cpp | 208 ++++++++++++++++++++++++++++ vslib/src/tests.cpp | 71 +++++++++- 3 files changed, 300 insertions(+), 2 deletions(-) diff --git a/vslib/inc/sai_vs.h b/vslib/inc/sai_vs.h index 9bb1339d01..dd2dc39bda 100644 --- a/vslib/inc/sai_vs.h +++ b/vslib/inc/sai_vs.h @@ -21,12 +21,33 @@ extern "C" { * * By default this flag is set to false. */ -#define SAI_KEY_VS_HOSTIF_USE_TAP_DEVICE "SAI_VS_HOSTIF_USE_TAP_DEVICE" +#define SAI_KEY_VS_HOSTIF_USE_TAP_DEVICE "SAI_VS_HOSTIF_USE_TAP_DEVICE" // TODO probaby should be per switch #define SAI_VALUE_VS_SWITCH_TYPE_BCM56850 "SAI_VS_SWITCH_TYPE_BCM56850" #define SAI_VALUE_VS_SWITCH_TYPE_MLNX2700 "SAI_VS_SWITCH_TYPE_MLNX2700" +/** + * @def SAI_VS_UNITTEST_CHANNEL + * + * Notification channel for redis database. + */ +#define SAI_VS_UNITTEST_CHANNEL "SAI_VS_UNITTEST_CHANNEL" + +/** + * @def SAI_VS_UNITTEST_SET_RO_OP + * + * Notification operation for "SET" READ_ONLY attribute. + */ +#define SAI_VS_UNITTEST_SET_RO_OP "set_ro" + +/** + * @def SAI_VS_UNITTEST_ENABLE + * + * Notificatio operation for enabling unittests. + */ +#define SAI_VS_UNITTEST_ENABLE_UNITTESTS "enable_unittests" + #define SAI_VS_VETH_PREFIX "v" typedef enum _sai_vs_switch_type_t diff --git a/vslib/src/sai_vs_interfacequery.cpp b/vslib/src/sai_vs_interfacequery.cpp index 3650fc9fa7..e55319cc4f 100644 --- a/vslib/src/sai_vs_interfacequery.cpp +++ b/vslib/src/sai_vs_interfacequery.cpp @@ -3,11 +3,205 @@ #include "sai_vs_state.h" #include +#include "swss/notificationconsumer.h" +#include "swss/select.h" + bool g_api_initialized = false; bool g_vs_hostif_use_tap_device = false; sai_vs_switch_type_t g_vs_switch_type = SAI_VS_SWITCH_TYPE_NONE; std::recursive_mutex g_recursive_mutex; +bool g_unittestChannelRun; +swss::SelectableEvent g_unittestChannelThreadEvent; +std::shared_ptr g_unittestChannelThread; +std::shared_ptr g_unittestChannelNotificationConsumer; +std::shared_ptr g_dbNtf; + +void handleUnittestChannelOp( + _In_ const std::string &op, + _In_ const std::string &key, + _In_ const std::vector &values) +{ + /* + * 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. + */ + + MUTEX(); + + SWSS_LOG_ENTER(); + + SWSS_LOG_NOTICE("read only SET: op = %s, key = %s", op.c_str(), key.c_str()); + + if (op == SAI_VS_UNITTEST_ENABLE_UNITTESTS) + { + bool enable = (key == "true"); + + meta_unittests_enable(enable); + } + else if (op == SAI_VS_UNITTEST_SET_RO_OP) + { + for (const auto &v: values) + { + SWSS_LOG_DEBUG("attr: %s: %s", fvField(v).c_str(), fvValue(v).c_str()); + } + + if (values.size() != 1) + { + SWSS_LOG_ERROR("expected 1 value only, but given: %zu", values.size()); + return; + } + + 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) + { + SWSS_LOG_ERROR("invalid object type: %d", object_type); + return; + } + + auto info = sai_metadata_get_object_type_info(object_type); + + if (info->isnonobjectid) + { + SWSS_LOG_ERROR("non object id %s is not supported yet", str_object_type.c_str()); + return; + } + + 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) + { + 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; + } + + sai_object_id_t switch_id = sai_switch_id_query(object_id); + + if (switch_id == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("failed to find switch id for oid %s", str_object_id.c_str()); + return; + } + + // oid is validated and we got switch id + + const std::string &str_attr_id = fvField(values.at(0)); + const std::string &str_attr_value = fvValue(values.at(0)); + + auto meta = sai_metadata_get_attr_metadata_by_attr_id_name(str_attr_id.c_str()); + + if (meta == NULL) + { + SWSS_LOG_ERROR("failed to find attr %s", str_attr_id.c_str()); + return; + } + + 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; + } + + // we got attr metadata + + sai_attribute_t attr; + + attr.id = meta->attrid; + + sai_deserialize_attr_value(str_attr_value, *meta, attr); + + SWSS_LOG_NOTICE("switch id is %s", sai_serialize_object_id(switch_id).c_str()); + + sai_status_t status = meta_unittests_allow_readonly_set_once(meta->objecttype, meta->attrid); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("failed to enable SET readonly attribute once: %s", sai_serialize_status(status).c_str()); + return; + } + + sai_object_meta_key_t meta_key = { .objecttype = ot, .objectkey = { .key = { .object_id = object_id } } }; + + status = info->set(&meta_key, &attr); + + 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()); + } + + sai_deserialize_free_attribute_value(meta->attrvaluetype, attr); + } + else + { + SWSS_LOG_ERROR("unknown unittest operation: %s", op.c_str()); + } +} + +void unittestChannelThreadProc() +{ + SWSS_LOG_ENTER(); + + SWSS_LOG_NOTICE("enter VS unittest channel thread"); + + swss::Select s; + + s.addSelectable(g_unittestChannelNotificationConsumer.get()); + s.addSelectable(&g_unittestChannelThreadEvent); + + while (g_unittestChannelRun) + { + swss::Selectable *sel; + + int fd; + + int result = s.select(&sel, &fd); + + if (sel == &g_unittestChannelThreadEvent) + { + // user requested shutdown_switch + break; + } + + if (result == swss::Select::OBJECT) + { + swss::KeyOpFieldsValuesTuple kco; + + std::string op; + std::string data; + std::vector values; + + g_unittestChannelNotificationConsumer->pop(op, data, values); + + SWSS_LOG_DEBUG("notification: op = %s, data = %s", op.c_str(), data.c_str()); + + handleUnittestChannelOp(op, data, values); + } + } + + SWSS_LOG_NOTICE("exit VS unittest channel thread"); +} + /** * @brief Serviec method table. * @@ -109,6 +303,13 @@ sai_status_t sai_api_initialize( clear_local_state(); + g_dbNtf = std::make_shared(ASIC_DB, swss::DBConnector::DEFAULT_UNIXSOCKET, 0); + g_unittestChannelNotificationConsumer = std::make_shared(g_dbNtf.get(), SAI_VS_UNITTEST_CHANNEL); + + g_unittestChannelRun = true; + + g_unittestChannelThread = std::make_shared(std::thread(unittestChannelThreadProc)); + g_api_initialized = true; return SAI_STATUS_SUCCESS; @@ -129,6 +330,13 @@ sai_status_t sai_api_uninitialize(void) clear_local_state(); + g_unittestChannelRun = false; + + //// notify thread that it should end + g_unittestChannelThreadEvent.notify(); + + g_unittestChannelThread->join(); + g_api_initialized = false; return SAI_STATUS_SUCCESS; diff --git a/vslib/src/tests.cpp b/vslib/src/tests.cpp index 6f1d8d64ec..8473f99b65 100644 --- a/vslib/src/tests.cpp +++ b/vslib/src/tests.cpp @@ -2,7 +2,14 @@ #include #include +#include + #include "swss/logger.h" +#include "swss/dbconnector.h" +#include "swss/schema.h" +#include "swss/notificationproducer.h" + +#include "../../meta/saiserialize.h" extern "C" { #include @@ -174,6 +181,63 @@ void test_set_readonly_attribute() ASSERT_TRUE(sai_metadata_sai_switch_api->set_switch_attribute(switch_id, &attr) != SAI_STATUS_SUCCESS); } +void test_set_readonly_attribute_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)); + + attr.id = SAI_SWITCH_ATTR_PORT_MAX_MTU; + attr.value.u32 = 42; + + // this set should fail + ASSERT_TRUE(sai_metadata_sai_switch_api->set_switch_attribute(switch_id, &attr) != SAI_STATUS_SUCCESS); + + // this scope contains all operations needed to perform set operation on readonly attribute + { + 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_SWITCH_ATTR_PORT_MAX_MTU"; + std::string value = "42"; // NOTE: normally we need sai_serialize_value() + + swss::FieldValueTuple fvt(field, value); + + entry.push_back(fvt); + + // when using tests from python, user will be required to translate VID2RID here + // need RID here in form 0xYYYYYYYYYYYYYYY + std::string data = "SAI_OBJECT_TYPE_SWITCH:" + sai_serialize_object_id(switch_id); + + vsntf.send(SAI_VS_UNITTEST_SET_RO_OP, data, entry); + } + + // give some time for notification to propagate to vs via redis + usleep(200*1000); + + // just scramble value to make sure that GET will succeed + attr.value.u32 = 1; + + SUCCESS(sai_metadata_sai_switch_api->get_switch_attribute(switch_id, 1, &attr)); + + ASSERT_TRUE(attr.value.u32 == 42); + + // second SET should fail + ASSERT_TRUE(sai_metadata_sai_switch_api->set_switch_attribute(switch_id, &attr) != SAI_STATUS_SUCCESS); +} + int main() { swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_DEBUG); @@ -186,11 +250,16 @@ int main() sai_metadata_apis_query(sai_api_query); - //swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_DEBUG); + //swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_INFO); test_ports(); test_set_readonly_attribute(); + test_set_readonly_attribute_via_redis(); + + // make proper unitinialize to close unittest thread + sai_api_uninitialize(); + return 0; }