From e566f7a187f1ff8d52f125b29634b49c71164249 Mon Sep 17 00:00:00 2001 From: Kamil Cudnik Date: Fri, 10 Apr 2020 20:47:33 +0200 Subject: [PATCH] [saiplayer] Convert saiplayer to static library (#600) --- saiplayer/CommandLineOptions.cpp | 35 + saiplayer/CommandLineOptions.h | 36 + saiplayer/CommandLineOptionsParser.cpp | 113 ++ saiplayer/CommandLineOptionsParser.h | 25 + saiplayer/Makefile.am | 13 +- saiplayer/SaiPlayer.cpp | 1543 ++++++++++++++++++++++ saiplayer/SaiPlayer.h | 189 +++ saiplayer/saiplayer.cpp | 1629 +----------------------- 8 files changed, 1962 insertions(+), 1621 deletions(-) create mode 100644 saiplayer/CommandLineOptions.cpp create mode 100644 saiplayer/CommandLineOptions.h create mode 100644 saiplayer/CommandLineOptionsParser.cpp create mode 100644 saiplayer/CommandLineOptionsParser.h create mode 100644 saiplayer/SaiPlayer.cpp create mode 100644 saiplayer/SaiPlayer.h diff --git a/saiplayer/CommandLineOptions.cpp b/saiplayer/CommandLineOptions.cpp new file mode 100644 index 000000000000..869c432b823d --- /dev/null +++ b/saiplayer/CommandLineOptions.cpp @@ -0,0 +1,35 @@ +#include "CommandLineOptions.h" + +#include "swss/logger.h" + +#include + +using namespace saiplayer; + +CommandLineOptions::CommandLineOptions() +{ + SWSS_LOG_ENTER(); + + // default values for command line options + + m_useTempView = false; + m_inspectAsic = false; + m_skipNotifySyncd = false; + m_enableDebug = false; + m_sleep = false; +} + +std::string CommandLineOptions::getCommandLineString() const +{ + SWSS_LOG_ENTER(); + + std::stringstream ss; + + ss << " UseTempView=" << (m_useTempView ? "YES" : "NO"); + ss << " InspectAsic=" << (m_inspectAsic ? "YES" : "NO"); + ss << " SkipNotifySyncd=" << (m_skipNotifySyncd ? "YES" : "NO"); + ss << " EnableDebug=" << (m_enableDebug ? "YES" : "NO"); + ss << " Sleep=" << (m_sleep ? "YES" : "NO"); + + return ss.str(); +} diff --git a/saiplayer/CommandLineOptions.h b/saiplayer/CommandLineOptions.h new file mode 100644 index 000000000000..539eb1602d38 --- /dev/null +++ b/saiplayer/CommandLineOptions.h @@ -0,0 +1,36 @@ +#pragma once + +#include "swss/sal.h" + +#include +#include + +namespace saiplayer +{ + class CommandLineOptions + { + public: + + CommandLineOptions(); + + virtual ~CommandLineOptions() = default; + + public: + + virtual std::string getCommandLineString() const; + + public: + + bool m_useTempView; + + bool m_inspectAsic; + + bool m_skipNotifySyncd; + + bool m_enableDebug; + + bool m_sleep; + + std::vector m_files; + }; +} diff --git a/saiplayer/CommandLineOptionsParser.cpp b/saiplayer/CommandLineOptionsParser.cpp new file mode 100644 index 000000000000..80c7ceb107ed --- /dev/null +++ b/saiplayer/CommandLineOptionsParser.cpp @@ -0,0 +1,113 @@ +#include "CommandLineOptionsParser.h" + +#include "swss/logger.h" + +#include + +#include + +using namespace saiplayer; + +std::shared_ptr CommandLineOptionsParser::parseCommandLine( + _In_ int argc, + _In_ char **argv) +{ + SWSS_LOG_ENTER(); + + auto options = std::make_shared(); + + const char* const optstring = "uiCdsh"; + + while(true) + { + static struct option long_options[] = + { + { "useTempView", no_argument, 0, 'u' }, + { "inspectAsic", no_argument, 0, 'i' }, + { "skipNotifySyncd", no_argument, 0, 'C' }, + { "enableDebug", no_argument, 0, 'd' }, + { "sleep", no_argument, 0, 's' }, + { "help", no_argument, 0, 'h' }, + }; + + int option_index = 0; + + int c = getopt_long(argc, argv, optstring, long_options, &option_index); + + if (c == -1) + { + break; + } + + switch (c) + { + case 'u': + options->m_useTempView = true; + break; + + case 'i': + options->m_inspectAsic = true; + break; + + case 'C': + options->m_skipNotifySyncd = true; + break; + + case 'd': + options->m_enableDebug = true; + break; + + case 's': + options->m_sleep = true; + break; + + case 'h': + printUsage(); + exit(EXIT_SUCCESS); + + case '?': + SWSS_LOG_WARN("unknown option %c", optopt); + printUsage(); + exit(EXIT_FAILURE); + + default: + SWSS_LOG_ERROR("getopt_long failure"); + exit(EXIT_FAILURE); + } + } + + + for (int i = optind; i < argc; i++) + { + options->m_files.push_back(argv[i]); + } + + if (options->m_files.size() == 0) + { + SWSS_LOG_ERROR("no files to replay"); + exit(EXIT_FAILURE); + } + + return options; +} + +void CommandLineOptionsParser::printUsage() +{ + SWSS_LOG_ENTER(); + + std::cout << "Usage: saiplayer [-u] [-i] [-C] [-d] [-s] [-h] recordfile" << std::endl << std::endl; + + std::cout << " -u --useTempView:" << std::endl; + std::cout << " Enable temporary view between init and apply" << std::endl << std::endl; + std::cout << " -i --inspectAsic:" << std::endl; + std::cout << " Inspect ASIC by ASIC DB" << std::endl << std::endl; + std::cout << " -C --skipNotifySyncd:" << std::endl; + std::cout << " Will not send notify init/apply view to syncd" << std::endl << std::endl; + std::cout << " -d --enableDebug:" << std::endl; + std::cout << " Enable syslog debug messages" << std::endl << std::endl; + std::cout << " -s --sleep:" << std::endl; + std::cout << " Sleep after success reply, to notice any switch notifications" << std::endl << std::endl; + + std::cout << " -h --help:" << std::endl; + std::cout << " Print out this message" << std::endl << std::endl; +} diff --git a/saiplayer/CommandLineOptionsParser.h b/saiplayer/CommandLineOptionsParser.h new file mode 100644 index 000000000000..dea92af3b253 --- /dev/null +++ b/saiplayer/CommandLineOptionsParser.h @@ -0,0 +1,25 @@ +#pragma once + +#include "CommandLineOptions.h" + +#include + +namespace saiplayer +{ + class CommandLineOptionsParser + { + private: + + CommandLineOptionsParser() = delete; + + ~CommandLineOptionsParser() = delete; + + public: + + static std::shared_ptr parseCommandLine( + _In_ int argc, + _In_ char **argv); + + static void printUsage(); + }; +} diff --git a/saiplayer/Makefile.am b/saiplayer/Makefile.am index a64bf7b958a2..350ed851b2c9 100644 --- a/saiplayer/Makefile.am +++ b/saiplayer/Makefile.am @@ -8,6 +8,15 @@ else DBGFLAGS = -g endif +noinst_LIBRARIES = libSaiPlayer.a +libSaiPlayer_a_SOURCES = \ + CommandLineOptions.cpp \ + CommandLineOptionsParser.cpp \ + SaiPlayer.cpp + + +libSaiPlayer_a_CPPFLAGS = $(DBGFLAGS) $(AM_CPPFLAGS) $(CFLAGS_COMMON) -std=c++14 + saiplayer_SOURCES = saiplayer.cpp -saiplayer_CPPFLAGS = $(DBGFLAGS) $(AM_CPPFLAGS) $(CFLAGS_COMMON) -saiplayer_LDADD = -lhiredis -lswsscommon -lpthread -L$(top_srcdir)/meta/.libs -lsaimetadata -lsaimeta -L$(top_srcdir)/lib/src/.libs -lsairedis +saiplayer_CPPFLAGS = $(DBGFLAGS) $(AM_CPPFLAGS) $(CFLAGS_COMMON) -std=c++14 +saiplayer_LDADD = libSaiPlayer.a ../syncd/libSyncd.a ../lib/src/libSaiRedis.a -lhiredis -lswsscommon -lpthread -L$(top_srcdir)/meta/.libs -lsaimetadata -lsaimeta diff --git a/saiplayer/SaiPlayer.cpp b/saiplayer/SaiPlayer.cpp new file mode 100644 index 000000000000..95ad5f06e4ae --- /dev/null +++ b/saiplayer/SaiPlayer.cpp @@ -0,0 +1,1543 @@ +#include "SaiPlayer.h" + +#include "sairedis.h" +#include "sairediscommon.h" + +#include "meta/sai_serialize.h" +#include "meta/SaiAttributeList.h" + +#include "swss/logger.h" +#include "swss/tokenize.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * Since this is player, we record actions from orchagent. No special case + * should be needed for switch in case it contains some oid values (like in + * syncd cold restart) since orchagent should never create switch with oid + * values set at creation time. + */ + +using namespace saiplayer; +using namespace saimeta; +using namespace std::placeholders; + +SaiPlayer::SaiPlayer( + _In_ std::shared_ptr sai, + _In_ std::shared_ptr cmd): + m_sai(sai), + m_commandLineOptions(cmd) +{ + SWSS_LOG_ENTER(); + + SWSS_LOG_NOTICE("cmd: %s", cmd->getCommandLineString().c_str()); + + m_profileIter = m_profileMap.begin(); + + m_smt.profileGetValue = std::bind(&SaiPlayer::profileGetValue, this, _1, _2); + m_smt.profileGetNextValue = std::bind(&SaiPlayer::profileGetNextValue, this, _1, _2, _3); + + m_sn.onFdbEvent = std::bind(&SaiPlayer::onFdbEvent, this, _1, _2); + m_sn.onPortStateChange = std::bind(&SaiPlayer::onPortStateChange, this, _1, _2); + m_sn.onQueuePfcDeadlock = std::bind(&SaiPlayer::onQueuePfcDeadlock, this, _1, _2); + m_sn.onSwitchShutdownRequest = std::bind(&SaiPlayer::onSwitchShutdownRequest, this, _1); + m_sn.onSwitchStateChange = std::bind(&SaiPlayer::onSwitchStateChange, this, _1, _2); + + m_switchNotifications= m_sn.getSwitchNotifications(); +} + +SaiPlayer::~SaiPlayer() +{ + SWSS_LOG_ENTER(); + + // empty +} + +void SaiPlayer::onFdbEvent( + _In_ uint32_t count, + _In_ const sai_fdb_event_notification_data_t *data) +{ + SWSS_LOG_ENTER(); + + // empty +} + +void SaiPlayer::onPortStateChange( + _In_ uint32_t count, + _In_ const sai_port_oper_status_notification_t *data) +{ + SWSS_LOG_ENTER(); + + // empty +} + +void SaiPlayer::onQueuePfcDeadlock( + _In_ uint32_t count, + _In_ const sai_queue_deadlock_notification_data_t *data) +{ + SWSS_LOG_ENTER(); + + // empty +} + +void SaiPlayer::onSwitchShutdownRequest( + _In_ sai_object_id_t switch_id) +{ + SWSS_LOG_ENTER(); + + SWSS_LOG_ERROR("got shutdown request, syncd failed!"); + exit(EXIT_FAILURE); +} + +void SaiPlayer::onSwitchStateChange( + _In_ sai_object_id_t switch_id, + _In_ sai_switch_oper_status_t switch_oper_status) +{ + SWSS_LOG_ENTER(); + + // empty +} + +#define EXIT_ON_ERROR(x)\ +{\ + sai_status_t s = (x);\ + if (s != SAI_STATUS_SUCCESS)\ + {\ + SWSS_LOG_THROW("fail status: %s", sai_serialize_status(s).c_str());\ + }\ +} + +sai_object_id_t SaiPlayer::translate_local_to_redis( + _In_ sai_object_id_t rid) +{ + SWSS_LOG_ENTER(); + + SWSS_LOG_DEBUG("translating local RID %s", + sai_serialize_object_id(rid).c_str()); + + if (rid == SAI_NULL_OBJECT_ID) + { + return SAI_NULL_OBJECT_ID; + } + + auto it = m_local_to_redis.find(rid); + + if (it == m_local_to_redis.end()) + { + SWSS_LOG_THROW("failed to translate local RID %s", + sai_serialize_object_id(rid).c_str()); + } + + return it->second; +} + +void SaiPlayer::translate_local_to_redis( + _Inout_ sai_object_list_t& element) +{ + SWSS_LOG_ENTER(); + + for (uint32_t i = 0; i < element.count; i++) + { + element.list[i] = translate_local_to_redis(element.list[i]); + } +} + +void SaiPlayer::translate_local_to_redis( + _In_ sai_object_type_t object_type, + _In_ uint32_t attr_count, + _In_ sai_attribute_t *attr_list) +{ + SWSS_LOG_ENTER(); + + for (uint32_t i = 0; i < attr_count; i++) + { + sai_attribute_t &attr = attr_list[i]; + + auto meta = sai_metadata_get_attr_metadata(object_type, attr.id); + + if (meta == NULL) + { + SWSS_LOG_THROW("unable to get metadata for object type %s, attribute %d", + sai_serialize_object_type(object_type).c_str(), + attr.id); + } + + switch (meta->attrvaluetype) + { + case SAI_ATTR_VALUE_TYPE_OBJECT_ID: + attr.value.oid = translate_local_to_redis(attr.value.oid); + break; + + case SAI_ATTR_VALUE_TYPE_OBJECT_LIST: + translate_local_to_redis(attr.value.objlist); + break; + + case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_ID: + if (attr.value.aclfield.enable) + attr.value.aclfield.data.oid = translate_local_to_redis(attr.value.aclfield.data.oid); + break; + + case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_LIST: + if (attr.value.aclfield.enable) + translate_local_to_redis( attr.value.aclfield.data.objlist); + break; + + case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_ID: + if (attr.value.aclaction.enable) + attr.value.aclaction.parameter.oid = translate_local_to_redis(attr.value.aclaction.parameter.oid); + break; + + case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_LIST: + if (attr.value.aclaction.enable) + translate_local_to_redis(attr.value.aclaction.parameter.objlist); + break; + + default: + + if (meta->isoidattribute) + { + SWSS_LOG_THROW("attribute %s is oid attribute but not handled, FIXME", meta->attridname); + } + + break; + } + } +} + +sai_object_type_t SaiPlayer::deserialize_object_type( + _In_ const std::string& s) +{ + SWSS_LOG_ENTER(); + + sai_object_type_t object_type; + + sai_deserialize_object_type(s, object_type); + + return object_type; +} + +const std::vector SaiPlayer::get_values( + _In_ const std::vector& items) +{ + SWSS_LOG_ENTER(); + + std::vector values; + + // timestamp|action|objecttype:objectid|attrid=value,... + for (size_t i = 3; i attrvaluetype) + { + case SAI_ATTR_VALUE_TYPE_OBJECT_LIST: + CHECK_LIST(value.objlist); + break; + + case SAI_ATTR_VALUE_TYPE_UINT8_LIST: + CHECK_LIST(value.u8list); + break; + + case SAI_ATTR_VALUE_TYPE_INT8_LIST: + CHECK_LIST(value.s8list); + break; + + case SAI_ATTR_VALUE_TYPE_UINT16_LIST: + CHECK_LIST(value.u16list); + break; + + case SAI_ATTR_VALUE_TYPE_INT16_LIST: + CHECK_LIST(value.s16list); + break; + + case SAI_ATTR_VALUE_TYPE_UINT32_LIST: + CHECK_LIST(value.u32list); + break; + + case SAI_ATTR_VALUE_TYPE_INT32_LIST: + CHECK_LIST(value.s32list); + break; + + case SAI_ATTR_VALUE_TYPE_VLAN_LIST: + CHECK_LIST(value.vlanlist); + break; + + case SAI_ATTR_VALUE_TYPE_QOS_MAP_LIST: + CHECK_LIST(value.qosmap); + break; + + case SAI_ATTR_VALUE_TYPE_IP_ADDRESS_LIST: + CHECK_LIST(value.ipaddrlist); + break; + + case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_LIST: + CHECK_LIST(value.aclfield.data.objlist); + break; + + case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_UINT8_LIST: + CHECK_LIST(value.aclfield.data.u8list); + CHECK_LIST(value.aclfield.mask.u8list); + break; + + case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_LIST: + CHECK_LIST(value.aclaction.parameter.objlist); + break; + + default: + break; + } + } +} + +void SaiPlayer::match_redis_with_rec( + _In_ sai_object_id_t get_oid, + _In_ sai_object_id_t oid) +{ + SWSS_LOG_ENTER(); + + auto it = m_redis_to_local.find(get_oid); + + if (it == m_redis_to_local.end()) + { + m_redis_to_local[get_oid] = oid; + m_local_to_redis[oid] = get_oid; + } + + if (oid != m_redis_to_local[get_oid]) + { + SWSS_LOG_THROW("match failed, oid order is mismatch :( oid 0x%" PRIx64 " get_oid 0x%" PRIx64 " second 0x%" PRIx64, + oid, + get_oid, + m_redis_to_local[get_oid]); + } + + SWSS_LOG_DEBUG("map size: %zu", m_local_to_redis.size()); +} + +void SaiPlayer::match_redis_with_rec( + _In_ sai_object_list_t get_objlist, + _In_ sai_object_list_t objlist) +{ + SWSS_LOG_ENTER(); + + for (uint32_t i = 0 ; i < get_objlist.count; ++i) + { + match_redis_with_rec(get_objlist.list[i], objlist.list[i]); + } +} + +void SaiPlayer::match_redis_with_rec( + _In_ sai_object_type_t object_type, + _In_ uint32_t get_attr_count, + _In_ sai_attribute_t* get_attr_list, + _In_ uint32_t attr_count, + _In_ sai_attribute_t* attr_list) +{ + SWSS_LOG_ENTER(); + + if (get_attr_count != attr_count) + { + SWSS_LOG_THROW("list number don't match %u != %u", get_attr_count, attr_count); + } + + for (uint32_t i = 0; i < attr_count; ++i) + { + sai_attribute_t &get_attr = get_attr_list[i]; + sai_attribute_t &attr = attr_list[i]; + + auto meta = sai_metadata_get_attr_metadata(object_type, attr.id); + + if (meta == NULL) + { + SWSS_LOG_THROW("unable to get metadata for object type %s, attribute %d", + sai_serialize_object_type(object_type).c_str(), + attr.id); + } + + switch (meta->attrvaluetype) + { + case SAI_ATTR_VALUE_TYPE_OBJECT_ID: + match_redis_with_rec(get_attr.value.oid, attr.value.oid); + break; + + case SAI_ATTR_VALUE_TYPE_OBJECT_LIST: + match_redis_with_rec(get_attr.value.objlist, attr.value.objlist); + break; + + case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_ID: + if (attr.value.aclfield.enable) + match_redis_with_rec(get_attr.value.aclfield.data.oid, attr.value.aclfield.data.oid); + break; + + case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_LIST: + if (attr.value.aclfield.enable) + match_redis_with_rec(get_attr.value.aclfield.data.objlist, attr.value.aclfield.data.objlist); + break; + + case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_ID: + if (attr.value.aclaction.enable) + match_redis_with_rec(get_attr.value.aclaction.parameter.oid, attr.value.aclaction.parameter.oid); + break; + + case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_LIST: + if (attr.value.aclaction.enable) + match_redis_with_rec(get_attr.value.aclaction.parameter.objlist, attr.value.aclaction.parameter.objlist); + break; + + default: + + // XXX if (meta->isoidattribute) + if (meta->allowedobjecttypeslength > 0) + { + SWSS_LOG_THROW("attribute %s is oid attribute but not handled, FIXME", meta->attridname); + } + + break; + } + } +} + +sai_status_t SaiPlayer::handle_fdb( + _In_ const std::string &str_object_id, + _In_ sai_common_api_t api, + _In_ uint32_t attr_count, + _In_ sai_attribute_t *attr_list) +{ + SWSS_LOG_ENTER(); + + sai_fdb_entry_t fdb_entry; + sai_deserialize_fdb_entry(str_object_id, fdb_entry); + + fdb_entry.switch_id = translate_local_to_redis(fdb_entry.switch_id); + fdb_entry.bv_id = translate_local_to_redis(fdb_entry.bv_id); + + switch (api) + { + case SAI_COMMON_API_CREATE: + return m_sai->create(&fdb_entry, attr_count, attr_list); + + case SAI_COMMON_API_REMOVE: + return m_sai->remove(&fdb_entry); + + case SAI_COMMON_API_SET: + return m_sai->set(&fdb_entry, attr_list); + + case SAI_COMMON_API_GET: + return m_sai->get(&fdb_entry, attr_count, attr_list); + + default: + SWSS_LOG_THROW("fdb other apis not implemented"); + } + + return SAI_STATUS_SUCCESS; +} + +sai_status_t SaiPlayer::handle_neighbor( + _In_ const std::string &str_object_id, + _In_ sai_common_api_t api, + _In_ uint32_t attr_count, + _In_ sai_attribute_t *attr_list) +{ + SWSS_LOG_ENTER(); + + sai_neighbor_entry_t neighbor_entry; + sai_deserialize_neighbor_entry(str_object_id, neighbor_entry); + + neighbor_entry.switch_id = translate_local_to_redis(neighbor_entry.switch_id); + neighbor_entry.rif_id = translate_local_to_redis(neighbor_entry.rif_id); + + switch(api) + { + case SAI_COMMON_API_CREATE: + return m_sai->create(&neighbor_entry, attr_count, attr_list); + + case SAI_COMMON_API_REMOVE: + return m_sai->remove(&neighbor_entry); + + case SAI_COMMON_API_SET: + return m_sai->set(&neighbor_entry, attr_list); + + case SAI_COMMON_API_GET: + return m_sai->get(&neighbor_entry, attr_count, attr_list); + + default: + SWSS_LOG_THROW("neighbor other apis not implemented"); + } +} + +sai_status_t SaiPlayer::handle_route( + _In_ const std::string &str_object_id, + _In_ sai_common_api_t api, + _In_ uint32_t attr_count, + _In_ sai_attribute_t *attr_list) +{ + SWSS_LOG_ENTER(); + + sai_route_entry_t route_entry; + sai_deserialize_route_entry(str_object_id, route_entry); + + route_entry.switch_id = translate_local_to_redis(route_entry.switch_id); + route_entry.vr_id = translate_local_to_redis(route_entry.vr_id); + + switch(api) + { + case SAI_COMMON_API_CREATE: + return m_sai->create(&route_entry, attr_count, attr_list); + + case SAI_COMMON_API_REMOVE: + return m_sai->remove(&route_entry); + + case SAI_COMMON_API_SET: + return m_sai->set(&route_entry, attr_list); + + case SAI_COMMON_API_GET: + return m_sai->get(&route_entry, attr_count, attr_list); + + default: + SWSS_LOG_THROW("route other apis not implemented"); + } +} + +void SaiPlayer::update_notifications_pointers( + _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list) +{ + SWSS_LOG_ENTER(); + + /* + * Sairedis is updating notifications pointers based on attribute, so when + * we will do replay it will have invalid pointers from orchagent, so we + * need to override them after create, and after set. + * + * NOTE: This needs to be updated every time new pointer will be added. + */ + + for (uint32_t index = 0; index < attr_count; ++index) + { + sai_attribute_t &attr = attr_list[index]; + + auto meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_SWITCH, attr.id); + + if (meta->attrvaluetype != SAI_ATTR_VALUE_TYPE_POINTER) + { + continue; + } + + if (attr.value.ptr == nullptr) // allow nulls + continue; + + switch (attr.id) + { + case SAI_SWITCH_ATTR_SWITCH_STATE_CHANGE_NOTIFY: + attr.value.ptr = (void*)&m_switchNotifications.on_switch_state_change; + break; + + case SAI_SWITCH_ATTR_SHUTDOWN_REQUEST_NOTIFY: + attr.value.ptr = (void*)&m_switchNotifications.on_switch_shutdown_request; + break; + + case SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY: + attr.value.ptr = (void*)&m_switchNotifications.on_fdb_event; + break; + + case SAI_SWITCH_ATTR_PORT_STATE_CHANGE_NOTIFY: + attr.value.ptr = (void*)&m_switchNotifications.on_port_state_change; + break; + + case SAI_SWITCH_ATTR_QUEUE_PFC_DEADLOCK_NOTIFY: + attr.value.ptr = (void*)&m_switchNotifications.on_queue_pfc_deadlock; + break; + + default: + SWSS_LOG_ERROR("pointer for %s is not handled, FIXME!", meta->attridname); + break; + } + } +} + +sai_status_t SaiPlayer::handle_generic( + _In_ sai_object_type_t object_type, + _In_ const std::string &str_object_id, + _In_ sai_common_api_t api, + _In_ uint32_t attr_count, + _In_ sai_attribute_t *attr_list) +{ + SWSS_LOG_ENTER(); + + sai_object_id_t local_id; + sai_deserialize_object_id(str_object_id, local_id); + + SWSS_LOG_DEBUG("generic %s for %s:%s", + sai_serialize_common_api(api).c_str(), + sai_serialize_object_type(object_type).c_str(), + str_object_id.c_str()); + + sai_object_meta_key_t meta_key; + + meta_key.objecttype = object_type; + + switch (api) + { + case SAI_COMMON_API_CREATE: + + { + sai_object_id_t switch_id = m_sai->switchIdQuery(local_id); + + if (switch_id == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_THROW("invalid switch_id translated from VID %s", + sai_serialize_object_id(local_id).c_str()); + } + + if (object_type == SAI_OBJECT_TYPE_SWITCH) + { + update_notifications_pointers(attr_count, attr_list); + + /* + * We are creating switch, in both cases local and redis + * switch id is deterministic and should be the same. + */ + } + else + { + /* + * When we creating switch, then switch_id parameter is + * ignored, but we can't convert it using vid to rid map, + * since rid don't exist yet, so skip translate for switch, + * but use translate for all other objects. + */ + + switch_id = translate_local_to_redis(switch_id); + } + + sai_status_t status = m_sai->create(meta_key, switch_id, attr_count, attr_list); + + if (status == SAI_STATUS_SUCCESS) + { + sai_object_id_t rid = meta_key.objectkey.key.object_id; + + match_redis_with_rec(rid, local_id); + + SWSS_LOG_INFO("saved VID %s to RID %s", + sai_serialize_object_id(local_id).c_str(), + sai_serialize_object_id(rid).c_str()); + } + else + { + SWSS_LOG_ERROR("failed to create %s", + sai_serialize_status(status).c_str()); + } + + return status; + } + + case SAI_COMMON_API_REMOVE: + + { + meta_key.objectkey.key.object_id = translate_local_to_redis(local_id); + + return m_sai->remove(meta_key); + } + + case SAI_COMMON_API_SET: + + { + if (object_type == SAI_OBJECT_TYPE_SWITCH) + { + update_notifications_pointers(1, attr_list); + } + + meta_key.objectkey.key.object_id = translate_local_to_redis(local_id); + + return m_sai->set(meta_key, attr_list); + } + + case SAI_COMMON_API_GET: + + { + meta_key.objectkey.key.object_id = translate_local_to_redis(local_id); + + return m_sai->get(meta_key, attr_count, attr_list); + } + + default: + SWSS_LOG_THROW("generic other apis not implemented"); + } +} + +void SaiPlayer::handle_get_response( + _In_ sai_object_type_t object_type, + _In_ uint32_t get_attr_count, + _In_ sai_attribute_t* get_attr_list, + _In_ const std::string& response, + _In_ sai_status_t status) +{ + SWSS_LOG_ENTER(); + + // timestamp|action|objecttype:objectid|attrid=value,... + auto v = swss::tokenize(response, '|'); + + if (status != SAI_STATUS_SUCCESS) + { + sai_status_t expectedStatus; + sai_deserialize_status(v.at(2), expectedStatus); + + if (status == expectedStatus) + { + // GET api was not successful but status is equal to recording + return; + } + + SWSS_LOG_WARN("status is: %s but expected: %s", + sai_serialize_status(status).c_str(), + sai_serialize_status(expectedStatus).c_str()); + return; + } + + //std::cout << "processing " << response << std::endl; + + auto values = get_values(v); + + SaiAttributeList list(object_type, values, false); + + sai_attribute_t *attr_list = list.get_attr_list(); + uint32_t attr_count = list.get_attr_count(); + + match_list_lengths(object_type, get_attr_count, get_attr_list, attr_count, attr_list); + + SWSS_LOG_DEBUG("list match"); + + match_redis_with_rec(object_type, get_attr_count, get_attr_list, attr_count, attr_list); + + // NOTE: Primitive values are not matched (recording vs switch/vs), we can add that check +} + +void SaiPlayer::performSleep( + _In_ const std::string& line) +{ + SWSS_LOG_ENTER(); + + // timestamp|action|sleeptime + auto v = swss::tokenize(line, '|'); + + if (v.size() < 3) + { + SWSS_LOG_THROW("invalid line %s", line.c_str()); + } + + uint32_t useconds; + sai_deserialize_number(v[2], useconds); + + if (useconds > 0) + { + useconds *= 1000; // 1ms resolution is enough for sleep + + SWSS_LOG_NOTICE("usleep(%u)", useconds); + usleep(useconds); + } +} + +void SaiPlayer::performNotifySyncd( + _In_ const std::string& request, + _In_ const std::string& response) +{ + SWSS_LOG_ENTER(); + + // timestamp|action|data + auto r = swss::tokenize(request, '|'); + auto R = swss::tokenize(response, '|'); + + if (r[1] != "a" || R[1] != "A") + { + SWSS_LOG_THROW("invalid syncd notify request/response %s/%s", request.c_str(), response.c_str()); + } + + if (m_commandLineOptions->m_skipNotifySyncd) + { + SWSS_LOG_NOTICE("skipping notify syncd, selected by user"); + return; + } + + // tell syncd that we are compiling new view + sai_attribute_t attr; + attr.id = SAI_REDIS_SWITCH_ATTR_NOTIFY_SYNCD; + attr.value.s32 = sai_deserialize_redis_notify_syncd(r[2]); + + /* + * NOTE: We don't need actual switch to set those attributes. + */ + + sai_object_id_t switch_id = SAI_NULL_OBJECT_ID; + + sai_status_t status = m_sai->set(SAI_OBJECT_TYPE_SWITCH, switch_id, &attr); + + const std::string& responseStatus = R[2]; + + sai_status_t response_status; + sai_deserialize_status(responseStatus, response_status); + + if (status != response_status) + { + SWSS_LOG_THROW("response status %s is different than syncd status %s", + responseStatus.c_str(), + sai_serialize_status(status).c_str()); + } + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_THROW("failed to notify syncd %s", + sai_serialize_status(status).c_str()); + } + + // OK +} + +void SaiPlayer::performFdbFlush( + _In_ const std::string& request, + _In_ const std::string response) +{ + SWSS_LOG_ENTER(); + + // 2017-05-13.20:47:24.883499|f|SAI_OBJECT_TYPE_FDB_FLUSH:oid:0x21000000000000| + // 2017-05-13.20:47:24.883499|F|SAI_STATUS_SUCCESS + + // timestamp|action|data + auto r = swss::tokenize(request, '|'); + auto R = swss::tokenize(response, '|'); + + if (r[1] != "f" || R[1] != "F") + { + SWSS_LOG_THROW("invalid fdb flush request/response %s/%s", request.c_str(), response.c_str()); + } + + if (r.size() > 3 && r[3].size() != 0) + { + SWSS_LOG_NOTICE("%zu %zu, %s", r.size(), r[3].size(), r[3].c_str()); + // TODO currently we support only flush fdb entries with no attributes + SWSS_LOG_THROW("currently fdb flush supports only no attributes, but some given: %s", request.c_str()); + } + + // objecttype:objectid (object id may contain ':') + auto& data = r[2]; + auto start = data.find_first_of(":"); + auto str_object_type = data.substr(0, start); + auto str_object_id = data.substr(start + 1); + + sai_object_type_t ot = deserialize_object_type(str_object_type); + + if (ot != SAI_OBJECT_TYPE_FDB_FLUSH) + { + SWSS_LOG_THROW("expected object type %s, but got: %s on %s", + sai_serialize_object_type(SAI_OBJECT_TYPE_FDB_FLUSH).c_str(), + str_object_type.c_str(), + request.c_str()); + } + + sai_object_id_t local_switch_id; + sai_deserialize_object_id(str_object_id, local_switch_id); + + if (m_sai->switchIdQuery(local_switch_id) != local_switch_id) + { + SWSS_LOG_THROW("fdb flush object is not switch id: %s, switch_id_query: %s", + str_object_id.c_str(), + sai_serialize_object_id(m_sai->switchIdQuery(local_switch_id)).c_str()); + } + + auto switch_id = translate_local_to_redis(local_switch_id); + + // TODO currently we support only flush fdb entries with no attributes + sai_status_t status = m_sai->flushFdbEntries(switch_id, 0, NULL); + + // check status + sai_status_t expected_status; + sai_deserialize_status(R[2], expected_status); + + if (status != expected_status) + { + SWSS_LOG_THROW("fdb flush got status %s, but expecting: %s", + sai_serialize_status(status).c_str(), + R[2].c_str()); + } + + // fdb flush OK +} + +std::vector SaiPlayer::tokenize( + _In_ std::string input, + _In_ const std::string &delim) +{ + SWSS_LOG_ENTER(); + + /* + * input is modified so it can't be passed as reference + */ + + std::vector tokens; + + size_t pos = 0; + + while ((pos = input.find(delim)) != std::string::npos) + { + std::string token = input.substr(0, pos); + + input.erase(0, pos + delim.length()); + tokens.push_back(token); + } + + tokens.push_back(input); + + return tokens; +} + +sai_status_t SaiPlayer::handle_bulk_route( + _In_ const std::vector &object_ids, + _In_ sai_common_api_t api, + _In_ const std::vector> &attributes, + _In_ const std::vector &recorded_statuses) +{ + SWSS_LOG_ENTER(); + + std::vector routes; + + for (size_t i = 0; i < object_ids.size(); ++i) + { + sai_route_entry_t route_entry; + sai_deserialize_route_entry(object_ids[i], route_entry); + + route_entry.vr_id = translate_local_to_redis(route_entry.vr_id); + + routes.push_back(route_entry); + + SWSS_LOG_DEBUG("route: %s", object_ids[i].c_str()); + } + + std::vector statuses; + + statuses.resize(recorded_statuses.size()); + + if (api == SAI_COMMON_API_BULK_SET) + { + /* + * TODO: since SDK don't support bulk route api yet, we just use our + * implementation, and later on we can switch to SDK api. + * + * TODO: we need to get operation type from recording, currently is not + * serialized and it is hard coded here. + */ + + std::vector attrs; + + for (const auto &a: attributes) + { + /* + * Set has only 1 attribute, so we can just join them nicely here. + */ + + attrs.push_back(a->get_attr_list()[0]); + } + + sai_status_t status = m_sai->bulkSet( + (uint32_t)routes.size(), + routes.data(), + attrs.data(), + SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR, // TODO we need to get that from recording + statuses.data()); + + if (status != SAI_STATUS_SUCCESS) + { + /* + * Entire API fails, so no need to compare statuses. + */ + + return status; + } + + for (size_t i = 0; i < statuses.size(); ++i) + { + if (statuses[i] != recorded_statuses[i]) + { + /* + * If recorded statuses are different than received, throw + * exception since data don't match. + */ + + SWSS_LOG_THROW("recorded status is %s but returned is %s on %s", + sai_serialize_status(recorded_statuses[i]).c_str(), + sai_serialize_status(statuses[i]).c_str(), + object_ids[i].c_str()); + } + } + + return status; + } + else if (api == SAI_COMMON_API_BULK_CREATE) + { + std::vector attr_count; + + std::vector attr_list; + + // route can have multiple attributes, so we need to handle them all + for (const auto &alist: attributes) + { + attr_list.push_back(alist->get_attr_list()); + attr_count.push_back(alist->get_attr_count()); + } + + SWSS_LOG_NOTICE("executing BULK route create with %zu routes", attr_count.size()); + + sai_status_t status = m_sai->bulkCreate( + (uint32_t)routes.size(), + routes.data(), + attr_count.data(), + attr_list.data(), + SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR, // TODO we need to get that from recording + statuses.data()); + + if (status != SAI_STATUS_SUCCESS) + { + // Entire API fails, so no need to compare statuses. + return status; + } + + for (size_t i = 0; i < statuses.size(); ++i) + { + if (statuses[i] != recorded_statuses[i]) + { + /* + * If recorded statuses are different than received, throw + * exception since data don't match. + */ + + SWSS_LOG_THROW("recorded status is %s but returned is %s on %s", + sai_serialize_status(recorded_statuses[i]).c_str(), + sai_serialize_status(statuses[i]).c_str(), + object_ids[i].c_str()); + } + } + + return status; + + } + else + { + SWSS_LOG_THROW("api %d is not supported in bulk route", api); + } +} + +void SaiPlayer::processBulk( + _In_ sai_common_api_t api, + _In_ const std::string &line) +{ + SWSS_LOG_ENTER(); + + if (!line.size()) + { + return; + } + + if (api != SAI_COMMON_API_BULK_SET && + api != SAI_COMMON_API_BULK_CREATE) + { + SWSS_LOG_THROW("bulk common api %d is not supported yet, FIXME", api); + } + + /* + * Here we know we have bulk SET api + */ + + // timestamp|action|objecttype||objectid|attrid=value|...|status||objectid||objectid|attrid=value|...|status||... + auto fields = tokenize(line, "||"); + + auto first = fields.at(0); // timestamp|action|objecttype + + std::string str_object_type = swss::tokenize(first, '|').at(2); + + sai_object_type_t object_type = deserialize_object_type(str_object_type); + + std::vector object_ids; + + std::vector> attributes; + + std::vector statuses; + + for (size_t idx = 1; idx < fields.size(); ++idx) + { + // object_id|attr=value|...|status + const std::string &joined = fields[idx]; + + auto split = swss::tokenize(joined, '|'); + + std::string str_object_id = split.front(); + + object_ids.push_back(str_object_id); + + std::string str_status = split.back(); + + sai_status_t status; + + sai_deserialize_status(str_status, status); + + statuses.push_back(status); + + std::vector entries; // attributes per object id + + // skip front object_id and back status + + SWSS_LOG_DEBUG("processing: %s", joined.c_str()); + + for (size_t i = 1; i < split.size() - 1; ++i) + { + const auto &item = split[i]; + + auto start = item.find_first_of("="); + + auto field = item.substr(0, start); + auto value = item.substr(start + 1); + + swss::FieldValueTuple entry(field, value); + + entries.push_back(entry); + } + + // since now we converted this to proper list, we can extract attributes + + std::shared_ptr list = + std::make_shared(object_type, entries, false); + + sai_attribute_t *attr_list = list->get_attr_list(); + + uint32_t attr_count = list->get_attr_count(); + + if (api != SAI_COMMON_API_BULK_GET) + { + translate_local_to_redis(object_type, attr_count, attr_list); + } + + attributes.push_back(list); + } + + sai_status_t status; + + switch (object_type) + { + case SAI_OBJECT_TYPE_ROUTE_ENTRY: + status = handle_bulk_route(object_ids, api, attributes, statuses); + break; + + default: + + SWSS_LOG_THROW("bulk op for %s is not supported yet, FIXME", + sai_serialize_object_type(object_type).c_str()); + } + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_THROW("failed to execute bulk api, FIXME"); + } +} + +int SaiPlayer::replay() +{ + //swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_DEBUG); + + SWSS_LOG_ENTER(); + + if (m_commandLineOptions->m_files.size() == 0) + { + fprintf(stderr, "ERR: need to specify filename\n"); + + return -1; + } + + auto filename = m_commandLineOptions->m_files.at(0); + + SWSS_LOG_NOTICE("using file: %s", filename.c_str()); + + std::ifstream infile(filename); + + if (!infile.is_open()) + { + SWSS_LOG_ERROR("failed to open file %s", filename.c_str()); + return -1; + } + + std::string line; + + while (std::getline(infile, line)) + { + // std::cout << "processing " << line << std::endl; + + sai_common_api_t api = SAI_COMMON_API_CREATE; + + auto p = line.find_first_of("|"); + + char op = line[p+1]; + + switch (op) + { + case 'a': + { + std::string response; + + do + { + // this line may be notification, we need to skip + if (!std::getline(infile, response)) + { + SWSS_LOG_THROW("failed to read next file from file, previous: %s", line.c_str()); + } + } + while (response[response.find_first_of("|") + 1] == 'n'); + + performNotifySyncd(line, response); + } + continue; + + case 'f': + { + std::string response; + + do + { + // this line may be notification, we need to skip + if (!std::getline(infile, response)) + { + SWSS_LOG_THROW("failed to read next file from file, previous: %s", line.c_str()); + } + } + while (response[response.find_first_of("|") + 1] == 'n'); + + performFdbFlush(line, response); + } + continue; + + case '@': + performSleep(line); + continue; + case 'c': + api = SAI_COMMON_API_CREATE; + break; + case 'r': + api = SAI_COMMON_API_REMOVE; + break; + case 's': + api = SAI_COMMON_API_SET; + break; + case 'S': + processBulk(SAI_COMMON_API_BULK_SET, line); + continue; + case 'C': + processBulk(SAI_COMMON_API_BULK_CREATE, line); + continue; + case 'g': + api = SAI_COMMON_API_GET; + break; + case 'q': + // TODO: implement SAI player support for query commands + continue; + case 'Q': + continue; // skip over query responses + case '#': + case 'n': + SWSS_LOG_INFO("skipping op %c line %s", op, line.c_str()); + continue; // skip comment and notification + + default: + SWSS_LOG_THROW("unknown op %c on line %s", op, line.c_str()); + } + + // timestamp|action|objecttype:objectid|attrid=value,... + auto fields = swss::tokenize(line, '|'); + + // objecttype:objectid (object id may contain ':') + auto start = fields[2].find_first_of(":"); + + auto str_object_type = fields[2].substr(0, start); + auto str_object_id = fields[2].substr(start + 1); + + sai_object_type_t object_type = deserialize_object_type(str_object_type); + + auto values = get_values(fields); + + SaiAttributeList list(object_type, values, false); + + sai_attribute_t *attr_list = list.get_attr_list(); + + uint32_t attr_count = list.get_attr_count(); + + SWSS_LOG_DEBUG("attr count: %u", list.get_attr_count()); + + if (api != SAI_COMMON_API_GET) + { + translate_local_to_redis(object_type, attr_count, attr_list); + } + + sai_status_t status; + + auto info = sai_metadata_get_object_type_info(object_type); + + switch (object_type) + { + case SAI_OBJECT_TYPE_FDB_ENTRY: + status = handle_fdb(str_object_id, api, attr_count, attr_list); + break; + + case SAI_OBJECT_TYPE_NEIGHBOR_ENTRY: + status = handle_neighbor(str_object_id, api, attr_count, attr_list); + break; + + case SAI_OBJECT_TYPE_ROUTE_ENTRY: + status = handle_route(str_object_id, api, attr_count, attr_list); + break; + + default: + + if (info->isnonobjectid) + { + SWSS_LOG_THROW("object %s:%s is non object id, but not handled, FIXME", + sai_serialize_object_type(object_type).c_str(), + str_object_id.c_str()); + } + + status = handle_generic(object_type, str_object_id, api, attr_count, attr_list); + break; + } + + if (status != SAI_STATUS_SUCCESS) + { + if (api == SAI_COMMON_API_GET) + { + // GET status is checked in handle response + } + else + SWSS_LOG_THROW("failed to execute api: %c: %s", op, sai_serialize_status(status).c_str()); + } + + if (api == SAI_COMMON_API_GET) + { + std::string response; + + do + { + // this line may be notification, we need to skip + std::getline(infile, response); + } + while (response[response.find_first_of("|") + 1] == 'n'); + + try + { + handle_get_response(object_type, attr_count, attr_list, response, status); + } + catch (const std::exception &e) + { + SWSS_LOG_NOTICE("line: %s", line.c_str()); + SWSS_LOG_NOTICE("resp (expected): %s", response.c_str()); + SWSS_LOG_NOTICE("got: %s", sai_serialize_status(status).c_str()); + + if (api == SAI_COMMON_API_GET && (status == SAI_STATUS_SUCCESS || status == SAI_STATUS_BUFFER_OVERFLOW)) + { + // log each get parameter + for (uint32_t i = 0; i < attr_count; ++i) + { + auto meta = sai_metadata_get_attr_metadata(object_type, attr_list[i].id); + + auto val = sai_serialize_attr_value(*meta, attr_list[i]); + + SWSS_LOG_NOTICE(" - %s:%s", meta->attridname, val.c_str()); + } + } + + exit(EXIT_FAILURE); + } + + if (api == SAI_COMMON_API_GET && (status == SAI_STATUS_SUCCESS || status == SAI_STATUS_BUFFER_OVERFLOW)) + { + // log each get parameter + for (uint32_t i = 0; i < attr_count; ++i) + { + auto meta = sai_metadata_get_attr_metadata(object_type, attr_list[i].id); + + auto val = sai_serialize_attr_value(*meta, attr_list[i]); + + SWSS_LOG_NOTICE(" - %s:%s", meta->attridname, val.c_str()); + } + } + } + } + + infile.close(); + + SWSS_LOG_NOTICE("finished replaying %s with SUCCESS", filename.c_str()); + + if (m_commandLineOptions->m_sleep) + { + fprintf(stderr, "Reply SUCCESS, sleeping, watching for notifications\n"); + + sleep(-1); + } + + return 0; +} + +const char* SaiPlayer::profileGetValue( + _In_ sai_switch_profile_id_t profile_id, + _In_ const char* variable) +{ + SWSS_LOG_ENTER(); + + if (variable == NULL) + { + SWSS_LOG_WARN("variable is null"); + return NULL; + } + + auto it = m_profileMap.find(variable); + + if (it == m_profileMap.end()) + { + SWSS_LOG_NOTICE("%s: NULL", variable); + return NULL; + } + + SWSS_LOG_NOTICE("%s: %s", variable, it->second.c_str()); + + return it->second.c_str(); +} + +int SaiPlayer::profileGetNextValue( + _In_ sai_switch_profile_id_t profile_id, + _Out_ const char** variable, + _Out_ const char** value) +{ + SWSS_LOG_ENTER(); + + if (value == NULL) + { + SWSS_LOG_INFO("resetting profile map iterator"); + + m_profileIter = m_profileMap.begin(); + return 0; + } + + if (variable == NULL) + { + SWSS_LOG_WARN("variable is null"); + return -1; + } + + if (m_profileIter == m_profileMap.end()) + { + SWSS_LOG_INFO("iterator reached end"); + return -1; + } + + *variable = m_profileIter->first.c_str(); + *value = m_profileIter->second.c_str(); + + SWSS_LOG_INFO("key: %s:%s", *variable, *value); + + m_profileIter++; + + return 0; +} + +int SaiPlayer::run() +{ + SWSS_LOG_ENTER(); + + if (m_commandLineOptions->m_enableDebug) + { + swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_NOTICE); + } + + m_test_services = m_smt.getServiceMethodTable(); + + EXIT_ON_ERROR(m_sai->initialize(0, &m_test_services)); + + sai_attribute_t attr; + + /* + * Notice that we use null object id as switch id, which is fine since + * those attributes don't need switch. + */ + + sai_object_id_t switch_id = SAI_NULL_OBJECT_ID; + + if (m_commandLineOptions->m_inspectAsic) + { + attr.id = SAI_REDIS_SWITCH_ATTR_NOTIFY_SYNCD; + attr.value.s32 = SAI_REDIS_NOTIFY_SYNCD_INSPECT_ASIC; + + EXIT_ON_ERROR(m_sai->set(SAI_OBJECT_TYPE_SWITCH, switch_id, &attr)); + } + + int exitcode = 0; + + if (m_commandLineOptions->m_files.size() > 0) + { + attr.id = SAI_REDIS_SWITCH_ATTR_USE_TEMP_VIEW; + attr.value.booldata = m_commandLineOptions->m_useTempView; + + EXIT_ON_ERROR(m_sai->set(SAI_OBJECT_TYPE_SWITCH, switch_id, &attr)); + + exitcode = replay(); + } + + m_sai->uninitialize(); + + return exitcode; + +} diff --git a/saiplayer/SaiPlayer.h b/saiplayer/SaiPlayer.h new file mode 100644 index 000000000000..a7e8ef902895 --- /dev/null +++ b/saiplayer/SaiPlayer.h @@ -0,0 +1,189 @@ +#pragma once + +#include "CommandLineOptions.h" + +#include "../lib/inc/SaiInterface.h" +#include "../meta/SaiAttributeList.h" +#include "../syncd/ServiceMethodTable.h" +#include "../syncd/SwitchNotifications.h" + +#include +#include + +namespace saiplayer +{ + class SaiPlayer + { + private: + + SaiPlayer(const SaiPlayer&) = delete; + SaiPlayer& operator=(const SaiPlayer&) = delete; + + public: + + SaiPlayer( + _In_ std::shared_ptr sai, + _In_ std::shared_ptr cmd); + + virtual ~SaiPlayer(); + + public: + + int run(); + + private: + + int replay(); + + void processBulk( + _In_ sai_common_api_t api, + _In_ const std::string &line); + + sai_status_t handle_bulk_route( + _In_ const std::vector &object_ids, + _In_ sai_common_api_t api, + _In_ const std::vector> &attributes, + _In_ const std::vector &recorded_statuses); + + std::vector tokenize( + _In_ std::string input, + _In_ const std::string &delim); + + void performFdbFlush( + _In_ const std::string& request, + _In_ const std::string response); + + void performNotifySyncd( + _In_ const std::string& request, + _In_ const std::string& response); + + void performSleep( + _In_ const std::string& line); + + void handle_get_response( + _In_ sai_object_type_t object_type, + _In_ uint32_t get_attr_count, + _In_ sai_attribute_t* get_attr_list, + _In_ const std::string& response, + _In_ sai_status_t status); + + sai_status_t handle_generic( + _In_ sai_object_type_t object_type, + _In_ const std::string &str_object_id, + _In_ sai_common_api_t api, + _In_ uint32_t attr_count, + _In_ sai_attribute_t *attr_list); + + void update_notifications_pointers( + _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list); + + sai_status_t handle_route( + _In_ const std::string &str_object_id, + _In_ sai_common_api_t api, + _In_ uint32_t attr_count, + _In_ sai_attribute_t *attr_list); + + sai_status_t handle_neighbor( + _In_ const std::string& str_object_id, + _In_ sai_common_api_t api, + _In_ uint32_t attr_count, + _In_ sai_attribute_t *attr_list); + + sai_status_t handle_fdb( + _In_ const std::string &str_object_id, + _In_ sai_common_api_t api, + _In_ uint32_t attr_count, + _In_ sai_attribute_t *attr_list); + + void match_redis_with_rec( + _In_ sai_object_type_t object_type, + _In_ uint32_t get_attr_count, + _In_ sai_attribute_t* get_attr_list, + _In_ uint32_t attr_count, + _In_ sai_attribute_t* attr_list); + + void match_redis_with_rec( + _In_ sai_object_list_t get_objlist, + _In_ sai_object_list_t objlist); + + void match_redis_with_rec( + _In_ sai_object_id_t get_oid, + _In_ sai_object_id_t oid); + + void match_list_lengths( + _In_ sai_object_type_t object_type, + _In_ uint32_t get_attr_count, + _In_ sai_attribute_t* get_attr_list, + _In_ uint32_t attr_count, + _In_ sai_attribute_t* attr_list); + + const std::vector get_values( + _In_ const std::vector& items); + + sai_object_type_t deserialize_object_type( + _In_ const std::string& s); + + void translate_local_to_redis( + _In_ sai_object_type_t object_type, + _In_ uint32_t attr_count, + _In_ sai_attribute_t *attr_list); + + sai_object_id_t translate_local_to_redis( + _In_ sai_object_id_t rid); + + void translate_local_to_redis( + _Inout_ sai_object_list_t& element); + + const char* profileGetValue( + _In_ sai_switch_profile_id_t profile_id, + _In_ const char* variable); + + int profileGetNextValue( + _In_ sai_switch_profile_id_t profile_id, + _Out_ const char** variable, + _Out_ const char** value); + + private: // notification handlers + + void onFdbEvent( + _In_ uint32_t count, + _In_ const sai_fdb_event_notification_data_t *data); + + void onPortStateChange( + _In_ uint32_t count, + _In_ const sai_port_oper_status_notification_t *data); + + void onQueuePfcDeadlock( + _In_ uint32_t count, + _In_ const sai_queue_deadlock_notification_data_t *data); + + void onSwitchShutdownRequest( + _In_ sai_object_id_t switch_id) __attribute__ ((noreturn)); + + void onSwitchStateChange( + _In_ sai_object_id_t switch_id, + _In_ sai_switch_oper_status_t switch_oper_status); + + private: + + std::shared_ptr m_sai; + + std::shared_ptr m_commandLineOptions; + + std::map m_local_to_redis; + std::map m_redis_to_local; + + syncd::ServiceMethodTable m_smt; + + syncd::SwitchNotifications m_sn; + + sai_switch_notifications_t m_switchNotifications; + + sai_service_method_table_t m_test_services; + + std::map m_profileMap; + + std::map::iterator m_profileIter; + }; +} diff --git a/saiplayer/saiplayer.cpp b/saiplayer/saiplayer.cpp index 88094f35adaa..381c0cab5c27 100644 --- a/saiplayer/saiplayer.cpp +++ b/saiplayer/saiplayer.cpp @@ -1,1587 +1,12 @@ -extern "C" { -#include "sai.h" -} - -#include "sairedis.h" -#include "sairediscommon.h" +#include "SaiPlayer.h" +#include "CommandLineOptionsParser.h" -#include "meta/sai_serialize.h" -#include "meta/SaiAttributeList.h" +#include "../lib/inc/Sai.h" +#include "../syncd/MetadataLogger.h" #include "swss/logger.h" -#include "swss/tokenize.h" - -#include -#include -#include -#include - -#include -#include -#include -#include - -using namespace saimeta; - -/* - * Since this is player, we record actions from orchagent. No special case - * should be needed for switch in case it contains some oid values (like in - * syncd cold restart) since orchagent should never create switch with oid - * values set at creation time. - */ - -sai_apis_t apis; - -std::map profile_map; - -const char *test_profile_get_value ( - _In_ sai_switch_profile_id_t profile_id, - _In_ const char *variable) -{ - SWSS_LOG_ENTER(); - - auto it = profile_map.find(variable); - - if (it == profile_map.end()) - { - return NULL; - } - - return it->second.c_str(); -} - -int test_profile_get_next_value ( - _In_ sai_switch_profile_id_t profile_id, - _Out_ const char **variable, - _Out_ const char **value) -{ - SWSS_LOG_ENTER(); - - return -1; -} - -const sai_service_method_table_t test_services = { - test_profile_get_value, - test_profile_get_next_value -}; - -void on_switch_state_change( - _In_ sai_switch_oper_status_t switch_oper_status) -{ - SWSS_LOG_ENTER(); -} - -void on_fdb_event( - _In_ uint32_t count, - _In_ sai_fdb_event_notification_data_t *data) -{ - SWSS_LOG_ENTER(); -} - -void on_port_state_change( - _In_ uint32_t count, - _In_ sai_port_oper_status_notification_t *data) -{ - SWSS_LOG_ENTER(); -} - -void on_switch_shutdown_request_notification( - _In_ sai_object_id_t switch_id) __attribute__ ((noreturn)); - -void on_switch_shutdown_request_notification( - _In_ sai_object_id_t switch_id) -{ - SWSS_LOG_ENTER(); - - SWSS_LOG_ERROR("got shutdown request, syncd failed!"); - exit(EXIT_FAILURE); -} - -void on_packet_event( - _In_ const void *buffer, - _In_ sai_size_t buffer_size, - _In_ uint32_t attr_count, - _In_ const sai_attribute_t *attr_list) -{ - SWSS_LOG_ENTER(); -} - -#define EXIT_ON_ERROR(x)\ -{\ - sai_status_t s = (x);\ - if (s != SAI_STATUS_SUCCESS)\ - {\ - SWSS_LOG_THROW("fail status: %s", sai_serialize_status(s).c_str());\ - }\ -} - -// to recorded -std::map local_to_redis; -std::map redis_to_local; - -sai_object_id_t translate_local_to_redis( - _In_ sai_object_id_t rid) -{ - SWSS_LOG_ENTER(); - - SWSS_LOG_DEBUG("translating local RID %s", - sai_serialize_object_id(rid).c_str()); - - if (rid == SAI_NULL_OBJECT_ID) - { - return SAI_NULL_OBJECT_ID; - } - - auto it = local_to_redis.find(rid); - - if (it == local_to_redis.end()) - { - SWSS_LOG_THROW("failed to translate local RID %s", - sai_serialize_object_id(rid).c_str()); - } - - return it->second; -} - - template -void translate_local_to_redis( - _In_ T &element) -{ - SWSS_LOG_ENTER(); - - for (uint32_t i = 0; i < element.count; i++) - { - element.list[i] = translate_local_to_redis(element.list[i]); - } -} - -void translate_local_to_redis( - _In_ sai_object_type_t object_type, - _In_ uint32_t attr_count, - _In_ sai_attribute_t *attr_list) -{ - SWSS_LOG_ENTER(); - - for (uint32_t i = 0; i < attr_count; i++) - { - sai_attribute_t &attr = attr_list[i]; - - auto meta = sai_metadata_get_attr_metadata(object_type, attr.id); - - if (meta == NULL) - { - SWSS_LOG_THROW("unable to get metadata for object type %s, attribute %d", - sai_serialize_object_type(object_type).c_str(), - attr.id); - } - - switch (meta->attrvaluetype) - { - case SAI_ATTR_VALUE_TYPE_OBJECT_ID: - attr.value.oid = translate_local_to_redis(attr.value.oid); - break; - - case SAI_ATTR_VALUE_TYPE_OBJECT_LIST: - translate_local_to_redis(attr.value.objlist); - break; - - case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_ID: - if (attr.value.aclfield.enable) - attr.value.aclfield.data.oid = translate_local_to_redis(attr.value.aclfield.data.oid); - break; - - case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_LIST: - if (attr.value.aclfield.enable) - translate_local_to_redis(attr.value.aclfield.data.objlist); - break; - - case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_ID: - if (attr.value.aclaction.enable) - attr.value.aclaction.parameter.oid = translate_local_to_redis(attr.value.aclaction.parameter.oid); - break; - - case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_LIST: - if (attr.value.aclaction.enable) - translate_local_to_redis(attr.value.aclaction.parameter.objlist); - break; - - default: - - // XXX if (meta->isoidattribute) - if (meta->allowedobjecttypeslength > 0) - { - SWSS_LOG_THROW("attribute %s is oid attribute but not handled, FIXME", meta->attridname); - } - - break; - } - } -} - -sai_object_type_t deserialize_object_type(const std::string& s) -{ - SWSS_LOG_ENTER(); - - sai_object_type_t object_type; - - sai_deserialize_object_type(s, object_type); - - return object_type; -} - -const std::vector get_values(const std::vector& items) -{ - SWSS_LOG_ENTER(); - - std::vector values; - - // timestamp|action|objecttype:objectid|attrid=value,... - for (size_t i = 3; i attrvaluetype) - { - case SAI_ATTR_VALUE_TYPE_OBJECT_LIST: - CHECK_LIST(value.objlist); - break; - - case SAI_ATTR_VALUE_TYPE_UINT8_LIST: - CHECK_LIST(value.u8list); - break; - - case SAI_ATTR_VALUE_TYPE_INT8_LIST: - CHECK_LIST(value.s8list); - break; - - case SAI_ATTR_VALUE_TYPE_UINT16_LIST: - CHECK_LIST(value.u16list); - break; - - case SAI_ATTR_VALUE_TYPE_INT16_LIST: - CHECK_LIST(value.s16list); - break; - - case SAI_ATTR_VALUE_TYPE_UINT32_LIST: - CHECK_LIST(value.u32list); - break; - - case SAI_ATTR_VALUE_TYPE_INT32_LIST: - CHECK_LIST(value.s32list); - break; - - case SAI_ATTR_VALUE_TYPE_VLAN_LIST: - CHECK_LIST(value.vlanlist); - break; - - case SAI_ATTR_VALUE_TYPE_QOS_MAP_LIST: - CHECK_LIST(value.qosmap); - break; - - case SAI_ATTR_VALUE_TYPE_IP_ADDRESS_LIST: - CHECK_LIST(value.ipaddrlist); - break; - - case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_LIST: - CHECK_LIST(value.aclfield.data.objlist); - break; - - case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_UINT8_LIST: - CHECK_LIST(value.aclfield.data.u8list); - CHECK_LIST(value.aclfield.mask.u8list); - break; - - case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_LIST: - CHECK_LIST(value.aclaction.parameter.objlist); - break; - - default: - break; - } - } -} - -void match_redis_with_rec( - sai_object_id_t get_oid, - sai_object_id_t oid) -{ - SWSS_LOG_ENTER(); - - auto it = redis_to_local.find(get_oid); - - if (it == redis_to_local.end()) - { - redis_to_local[get_oid] = oid; - local_to_redis[oid] = get_oid; - } - - if (oid != redis_to_local[get_oid]) - { - SWSS_LOG_THROW("match failed, oid order is mismatch :( oid 0x%" PRIx64 " get_oid 0x%" PRIx64 " second 0x%" PRIx64, - oid, - get_oid, - redis_to_local[get_oid]); - } - - SWSS_LOG_DEBUG("map size: %zu", local_to_redis.size()); -} - -void match_redis_with_rec( - sai_object_list_t get_objlist, - sai_object_list_t objlist) -{ - SWSS_LOG_ENTER(); - - for (uint32_t i = 0 ; i < get_objlist.count; ++i) - { - match_redis_with_rec(get_objlist.list[i], objlist.list[i]); - } -} - -void match_redis_with_rec( - sai_object_type_t object_type, - uint32_t get_attr_count, - sai_attribute_t* get_attr_list, - uint32_t attr_count, - sai_attribute_t* attr_list) -{ - SWSS_LOG_ENTER(); - - if (get_attr_count != attr_count) - { - SWSS_LOG_THROW("list number don't match %u != %u", get_attr_count, attr_count); - } - - for (uint32_t i = 0; i < attr_count; ++i) - { - sai_attribute_t &get_attr = get_attr_list[i]; - sai_attribute_t &attr = attr_list[i]; - - auto meta = sai_metadata_get_attr_metadata(object_type, attr.id); - - if (meta == NULL) - { - SWSS_LOG_THROW("unable to get metadata for object type %s, attribute %d", - sai_serialize_object_type(object_type).c_str(), - attr.id); - } - - switch (meta->attrvaluetype) - { - case SAI_ATTR_VALUE_TYPE_OBJECT_ID: - match_redis_with_rec(get_attr.value.oid, attr.value.oid); - break; - - case SAI_ATTR_VALUE_TYPE_OBJECT_LIST: - match_redis_with_rec(get_attr.value.objlist, attr.value.objlist); - break; - - case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_ID: - if (attr.value.aclfield.enable) - match_redis_with_rec(get_attr.value.aclfield.data.oid, attr.value.aclfield.data.oid); - break; - - case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_LIST: - if (attr.value.aclfield.enable) - match_redis_with_rec(get_attr.value.aclfield.data.objlist, attr.value.aclfield.data.objlist); - break; - - case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_ID: - if (attr.value.aclaction.enable) - match_redis_with_rec(get_attr.value.aclaction.parameter.oid, attr.value.aclaction.parameter.oid); - break; - - case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_LIST: - if (attr.value.aclaction.enable) - match_redis_with_rec(get_attr.value.aclaction.parameter.objlist, attr.value.aclaction.parameter.objlist); - break; - - default: - - // XXX if (meta->isoidattribute) - if (meta->allowedobjecttypeslength > 0) - { - SWSS_LOG_THROW("attribute %s is oid attribute but not handled, FIXME", meta->attridname); - } - - break; - } - } -} - -sai_status_t handle_fdb( - _In_ const std::string &str_object_id, - _In_ sai_common_api_t api, - _In_ uint32_t attr_count, - _In_ sai_attribute_t *attr_list) -{ - SWSS_LOG_ENTER(); - - sai_fdb_entry_t fdb_entry; - sai_deserialize_fdb_entry(str_object_id, fdb_entry); - - fdb_entry.switch_id = translate_local_to_redis(fdb_entry.switch_id); - fdb_entry.bv_id = translate_local_to_redis(fdb_entry.bv_id); - - switch (api) - { - case SAI_COMMON_API_CREATE: - return sai_metadata_sai_fdb_api->create_fdb_entry(&fdb_entry, attr_count, attr_list); - - case SAI_COMMON_API_REMOVE: - return sai_metadata_sai_fdb_api->remove_fdb_entry(&fdb_entry); - - case SAI_COMMON_API_SET: - return sai_metadata_sai_fdb_api->set_fdb_entry_attribute(&fdb_entry, attr_list); - - case SAI_COMMON_API_GET: - return sai_metadata_sai_fdb_api->get_fdb_entry_attribute(&fdb_entry, attr_count, attr_list); - - default: - SWSS_LOG_THROW("fdb other apis not implemented"); - } - - return SAI_STATUS_SUCCESS; -} - -sai_status_t handle_neighbor( - _In_ const std::string &str_object_id, - _In_ sai_common_api_t api, - _In_ uint32_t attr_count, - _In_ sai_attribute_t *attr_list) -{ - SWSS_LOG_ENTER(); - - sai_neighbor_entry_t neighbor_entry; - sai_deserialize_neighbor_entry(str_object_id, neighbor_entry); - - neighbor_entry.switch_id = translate_local_to_redis(neighbor_entry.switch_id); - neighbor_entry.rif_id = translate_local_to_redis(neighbor_entry.rif_id); - - switch(api) - { - case SAI_COMMON_API_CREATE: - return sai_metadata_sai_neighbor_api->create_neighbor_entry(&neighbor_entry, attr_count, attr_list); - - case SAI_COMMON_API_REMOVE: - return sai_metadata_sai_neighbor_api->remove_neighbor_entry(&neighbor_entry); - - case SAI_COMMON_API_SET: - return sai_metadata_sai_neighbor_api->set_neighbor_entry_attribute(&neighbor_entry, attr_list); - - case SAI_COMMON_API_GET: - return sai_metadata_sai_neighbor_api->get_neighbor_entry_attribute(&neighbor_entry, attr_count, attr_list); - - default: - SWSS_LOG_THROW("neighbor other apis not implemented"); - } -} - -sai_status_t handle_route( - _In_ const std::string &str_object_id, - _In_ sai_common_api_t api, - _In_ uint32_t attr_count, - _In_ sai_attribute_t *attr_list) -{ - SWSS_LOG_ENTER(); - - sai_route_entry_t route_entry; - sai_deserialize_route_entry(str_object_id, route_entry); - - route_entry.switch_id = translate_local_to_redis(route_entry.switch_id); - route_entry.vr_id = translate_local_to_redis(route_entry.vr_id); - - switch(api) - { - case SAI_COMMON_API_CREATE: - return sai_metadata_sai_route_api->create_route_entry(&route_entry, attr_count, attr_list); - - case SAI_COMMON_API_REMOVE: - return sai_metadata_sai_route_api->remove_route_entry(&route_entry); - - case SAI_COMMON_API_SET: - return sai_metadata_sai_route_api->set_route_entry_attribute(&route_entry, attr_list); - - case SAI_COMMON_API_GET: - return sai_metadata_sai_route_api->get_route_entry_attribute(&route_entry, attr_count, attr_list); - - default: - SWSS_LOG_THROW("route other apis not implemented"); - } -} - -void update_notifications_pointers( - _In_ uint32_t attr_count, - _Inout_ sai_attribute_t *attr_list) -{ - SWSS_LOG_ENTER(); - - /* - * Sairedis is updating notifications pointers based on attribute, so when - * we will do replay it will have invalid pointers from orchagent, so we - * need to override them after create, and after set. - * - * NOTE: This needs to be updated every time new pointer will be added. - */ - - for (uint32_t index = 0; index < attr_count; ++index) - { - sai_attribute_t &attr = attr_list[index]; - - auto meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_SWITCH, attr.id); - - if (meta->attrvaluetype != SAI_ATTR_VALUE_TYPE_POINTER) - { - continue; - } - - if (attr.value.ptr == nullptr) // allow nulls - continue; - - switch (attr.id) - { - case SAI_SWITCH_ATTR_SWITCH_STATE_CHANGE_NOTIFY: - attr.value.ptr = (void*)&on_switch_state_change; - break; - - case SAI_SWITCH_ATTR_SHUTDOWN_REQUEST_NOTIFY: - attr.value.ptr = (void*)&on_switch_shutdown_request_notification; - break; - - case SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY: - attr.value.ptr = (void*)&on_fdb_event; - break; - - case SAI_SWITCH_ATTR_PORT_STATE_CHANGE_NOTIFY: - attr.value.ptr = (void*)&on_port_state_change; - break; - - case SAI_SWITCH_ATTR_PACKET_EVENT_NOTIFY: - attr.value.ptr = (void*)&on_packet_event; - break; - - default: - SWSS_LOG_ERROR("pointer for %s is not handled, FIXME!", meta->attridname); - break; - } - } -} - -sai_status_t handle_generic( - _In_ sai_object_type_t object_type, - _In_ const std::string &str_object_id, - _In_ sai_common_api_t api, - _In_ uint32_t attr_count, - _In_ sai_attribute_t *attr_list) -{ - SWSS_LOG_ENTER(); - - sai_object_id_t local_id; - sai_deserialize_object_id(str_object_id, local_id); - - SWSS_LOG_DEBUG("generic %s for %s:%s", - sai_serialize_common_api(api).c_str(), - sai_serialize_object_type(object_type).c_str(), - str_object_id.c_str()); - - auto info = sai_metadata_get_object_type_info(object_type); - - sai_object_meta_key_t meta_key; - - meta_key.objecttype = object_type; - - switch (api) - { - case SAI_COMMON_API_CREATE: - - { - sai_object_id_t switch_id = sai_switch_id_query(local_id); - - if (switch_id == SAI_NULL_OBJECT_ID) - { - SWSS_LOG_THROW("invalid switch_id translated from VID %s", - sai_serialize_object_id(local_id).c_str()); - } - - if (object_type == SAI_OBJECT_TYPE_SWITCH) - { - update_notifications_pointers(attr_count, attr_list); - - /* - * We are creating switch, in both cases local and redis - * switch id is deterministic and should be the same. - */ - } - else - { - /* - * When we creating switch, then switch_id parameter is - * ignored, but we can't convert it using vid to rid map, - * since rid don't exist yet, so skip translate for switch, - * but use translate for all other objects. - */ - - switch_id = translate_local_to_redis(switch_id); - } - - sai_status_t status = info->create(&meta_key, switch_id, attr_count, attr_list); - - if (status == SAI_STATUS_SUCCESS) - { - sai_object_id_t rid = meta_key.objectkey.key.object_id; - - match_redis_with_rec(rid, local_id); - - SWSS_LOG_INFO("saved VID %s to RID %s", - sai_serialize_object_id(local_id).c_str(), - sai_serialize_object_id(rid).c_str()); - } - else - { - SWSS_LOG_ERROR("failed to create %s", - sai_serialize_status(status).c_str()); - } - - return status; - } - - case SAI_COMMON_API_REMOVE: - - { - meta_key.objectkey.key.object_id = translate_local_to_redis(local_id); - - return info->remove(&meta_key); - } - - case SAI_COMMON_API_SET: - - { - if (object_type == SAI_OBJECT_TYPE_SWITCH) - { - update_notifications_pointers(1, attr_list); - } - - meta_key.objectkey.key.object_id = translate_local_to_redis(local_id); - - return info->set(&meta_key, attr_list); - } - - case SAI_COMMON_API_GET: - - { - meta_key.objectkey.key.object_id = translate_local_to_redis(local_id); - - return info->get(&meta_key, attr_count, attr_list); - } - - default: - SWSS_LOG_THROW("generic other apis not implemented"); - } -} - -void handle_get_response( - sai_object_type_t object_type, - uint32_t get_attr_count, - sai_attribute_t* get_attr_list, - const std::string& response, - sai_status_t status) -{ - SWSS_LOG_ENTER(); - - // timestamp|action|objecttype:objectid|attrid=value,... - auto v = swss::tokenize(response, '|'); - - if (status != SAI_STATUS_SUCCESS) - { - sai_status_t expectedStatus; - sai_deserialize_status(v.at(2), expectedStatus); - - if (status == expectedStatus) - { - // GET api was not successful but status is equal to recording - return; - } - - SWSS_LOG_WARN("status is: %s but expected: %s", - sai_serialize_status(status).c_str(), - sai_serialize_status(expectedStatus).c_str()); - return; - } - - //std::cout << "processing " << response << std::endl; - auto values = get_values(v); - - SaiAttributeList list(object_type, values, false); - - sai_attribute_t *attr_list = list.get_attr_list(); - uint32_t attr_count = list.get_attr_count(); - - match_list_lengths(object_type, get_attr_count, get_attr_list, attr_count, attr_list); - - SWSS_LOG_DEBUG("list match"); - - match_redis_with_rec(object_type, get_attr_count, get_attr_list, attr_count, attr_list); - - // NOTE: Primitive values are not matched (recording vs switch/vs), we can add that check -} - -void performSleep(const std::string& line) -{ - SWSS_LOG_ENTER(); - - // timestamp|action|sleeptime - auto v = swss::tokenize(line, '|'); - - if (v.size() < 3) - { - SWSS_LOG_THROW("invalid line %s", line.c_str()); - } - - uint32_t useconds; - sai_deserialize_number(v[2], useconds); - - if (useconds > 0) - { - useconds *= 1000; // 1ms resolution is enough for sleep - - SWSS_LOG_NOTICE("usleep(%u)", useconds); - usleep(useconds); - } -} - -bool g_notifySyncd = true; - -void performNotifySyncd(const std::string& request, const std::string response) -{ - SWSS_LOG_ENTER(); - - // timestamp|action|data - auto r = swss::tokenize(request, '|'); - auto R = swss::tokenize(response, '|'); - - if (r[1] != "a" || R[1] != "A") - { - SWSS_LOG_THROW("invalid syncd notify request/response %s/%s", request.c_str(), response.c_str()); - } - - if (g_notifySyncd == false) - { - SWSS_LOG_NOTICE("skipping notify syncd, selected by user"); - return; - } - - // tell syncd that we are compiling new view - sai_attribute_t attr; - attr.id = SAI_REDIS_SWITCH_ATTR_NOTIFY_SYNCD; - attr.value.s32 = sai_deserialize_redis_notify_syncd(r[2]); - - /* - * NOTE: We don't need actual switch to set those attributes. - */ - - sai_object_id_t switch_id = SAI_NULL_OBJECT_ID; - - sai_status_t status = sai_metadata_sai_switch_api->set_switch_attribute(switch_id, &attr); - - const std::string& responseStatus = R[2]; - - sai_status_t response_status; - sai_deserialize_status(responseStatus, response_status); - - if (status != response_status) - { - SWSS_LOG_THROW("response status %s is different than syncd status %s", - responseStatus.c_str(), - sai_serialize_status(status).c_str()); - } - - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_THROW("failed to notify syncd %s", - sai_serialize_status(status).c_str()); - } - - // OK -} - -void performFdbFlush( - _In_ const std::string& request, - _In_ const std::string response) -{ - SWSS_LOG_ENTER(); - - // 2017-05-13.20:47:24.883499|f|SAI_OBJECT_TYPE_FDB_FLUSH:oid:0x21000000000000| - // 2017-05-13.20:47:24.883499|F|SAI_STATUS_SUCCESS - - // timestamp|action|data - auto r = swss::tokenize(request, '|'); - auto R = swss::tokenize(response, '|'); - - if (r[1] != "f" || R[1] != "F") - { - SWSS_LOG_THROW("invalid fdb flush request/response %s/%s", request.c_str(), response.c_str()); - } - - if (r.size() > 3 && r[3].size() != 0) - { - SWSS_LOG_NOTICE("%zu %zu, %s", r.size(), r[3].size(), r[3].c_str()); - // TODO currently we support only flush fdb entries with no attributes - SWSS_LOG_THROW("currently fdb flush supports only no attributes, but some given: %s", request.c_str()); - } - - // objecttype:objectid (object id may contain ':') - auto& data = r[2]; - auto start = data.find_first_of(":"); - auto str_object_type = data.substr(0, start); - auto str_object_id = data.substr(start + 1); - - sai_object_type_t ot = deserialize_object_type(str_object_type); - - if (ot != SAI_OBJECT_TYPE_FDB_FLUSH) - { - SWSS_LOG_THROW("expected object type %s, but got: %s on %s", - sai_serialize_object_type(SAI_OBJECT_TYPE_FDB_FLUSH).c_str(), - str_object_type.c_str(), - request.c_str()); - } - - sai_object_id_t local_switch_id; - sai_deserialize_object_id(str_object_id, local_switch_id); - - if (sai_switch_id_query(local_switch_id) != local_switch_id) - { - SWSS_LOG_THROW("fdb flush object is not switch id: %s, switch_id_query: %s", - str_object_id.c_str(), - sai_serialize_object_id(sai_switch_id_query(local_switch_id)).c_str()); - } - - auto switch_id = translate_local_to_redis(local_switch_id); - - // TODO currently we support only flush fdb entries with no attributes - sai_status_t status = sai_metadata_sai_fdb_api->flush_fdb_entries(switch_id, 0, NULL); - - // check status - sai_status_t expected_status; - sai_deserialize_status(R[2], expected_status); - - if (status != expected_status) - { - SWSS_LOG_THROW("fdb flush got status %s, but expecting: %s", - sai_serialize_status(status).c_str(), - R[2].c_str()); - } - - // fdb flush OK -} - -std::vector tokenize( - _In_ std::string input, - _In_ const std::string &delim) -{ - SWSS_LOG_ENTER(); - - /* - * input is modified so it can't be passed as reference - */ - - std::vector tokens; - - size_t pos = 0; - - while ((pos = input.find(delim)) != std::string::npos) - { - std::string token = input.substr(0, pos); - - input.erase(0, pos + delim.length()); - tokens.push_back(token); - } - - tokens.push_back(input); - - return tokens; -} - -sai_status_t handle_bulk_route( - _In_ const std::vector &object_ids, - _In_ sai_common_api_t api, - _In_ const std::vector> &attributes, - _In_ const std::vector &recorded_statuses) -{ - SWSS_LOG_ENTER(); - - sai_route_api_t *route_api = NULL; - - sai_api_query(SAI_API_ROUTE, (void**)&route_api); - - std::vector routes; - - for (size_t i = 0; i < object_ids.size(); ++i) - { - sai_route_entry_t route_entry; - sai_deserialize_route_entry(object_ids[i], route_entry); - - route_entry.vr_id = translate_local_to_redis(route_entry.vr_id); - - routes.push_back(route_entry); - - SWSS_LOG_DEBUG("route: %s", object_ids[i].c_str()); - } - - std::vector statuses; - - statuses.resize(recorded_statuses.size()); - - if (api == SAI_COMMON_API_BULK_SET) - { - /* - * TODO: since SDK don't support bulk route api yet, we just use our - * implementation, and later on we can switch to SDK api. - * - * TODO: we need to get operation type from recording, currently is not - * serialized and it is hard coded here. - */ - - std::vector attrs; - - for (const auto &a: attributes) - { - /* - * Set has only 1 attribute, so we can just join them nicely here. - */ - - attrs.push_back(a->get_attr_list()[0]); - } - - sai_status_t status = route_api->set_route_entries_attribute( - (uint32_t)routes.size(), - routes.data(), - attrs.data(), - SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR, // TODO we need to get that from recording - statuses.data()); - - if (status != SAI_STATUS_SUCCESS) - { - /* - * Entire API fails, so no need to compare statuses. - */ - - return status; - } - - for (size_t i = 0; i < statuses.size(); ++i) - { - if (statuses[i] != recorded_statuses[i]) - { - /* - * If recorded statuses are different than received, throw - * exception since data don't match. - */ - - SWSS_LOG_THROW("recorded status is %s but returned is %s on %s", - sai_serialize_status(recorded_statuses[i]).c_str(), - sai_serialize_status(statuses[i]).c_str(), - object_ids[i].c_str()); - } - } - - return status; - } - else if (api == SAI_COMMON_API_BULK_CREATE) - { - std::vector attr_count; - - std::vector attr_list; - - // route can have multiple attributes, so we need to handle them all - for (const auto &alist: attributes) - { - attr_list.push_back(alist->get_attr_list()); - attr_count.push_back(alist->get_attr_count()); - } - - SWSS_LOG_NOTICE("executing BULK route create with %zu routes", attr_count.size()); - - sai_status_t status = route_api->create_route_entries( - (uint32_t)routes.size(), - routes.data(), - attr_count.data(), - attr_list.data(), - SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR, // TODO we need to get that from recording - statuses.data()); - - if (status != SAI_STATUS_SUCCESS) - { - // Entire API fails, so no need to compare statuses. - return status; - } - - for (size_t i = 0; i < statuses.size(); ++i) - { - if (statuses[i] != recorded_statuses[i]) - { - /* - * If recorded statuses are different than received, throw - * exception since data don't match. - */ - - SWSS_LOG_THROW("recorded status is %s but returned is %s on %s", - sai_serialize_status(recorded_statuses[i]).c_str(), - sai_serialize_status(statuses[i]).c_str(), - object_ids[i].c_str()); - } - } - - return status; - - } - else - { - SWSS_LOG_THROW("api %d is not supported in bulk route", api); - } -} - -void processBulk( - _In_ sai_common_api_t api, - _In_ const std::string &line) -{ - SWSS_LOG_ENTER(); - - if (!line.size()) - { - return; - } - - if (api != SAI_COMMON_API_BULK_SET && - api != SAI_COMMON_API_BULK_CREATE) - { - SWSS_LOG_THROW("bulk common api %d is not supported yet, FIXME", api); - } - - /* - * Here we know we have bulk SET api - */ - - // timestamp|action|objecttype||objectid|attrid=value|...|status||objectid||objectid|attrid=value|...|status||... - auto fields = tokenize(line, "||"); - - auto first = fields.at(0); // timestamp|action|objecttype - - std::string str_object_type = swss::tokenize(first, '|').at(2); - - sai_object_type_t object_type = deserialize_object_type(str_object_type); - - std::vector object_ids; - - std::vector> attributes; - - std::vector statuses; - - for (size_t idx = 1; idx < fields.size(); ++idx) - { - // object_id|attr=value|...|status - const std::string &joined = fields[idx]; - - auto split = swss::tokenize(joined, '|'); - - std::string str_object_id = split.front(); - - object_ids.push_back(str_object_id); - - std::string str_status = split.back(); - - sai_status_t status; - - sai_deserialize_status(str_status, status); - - statuses.push_back(status); - - std::vector entries; // attributes per object id - - // skip front object_id and back status - - SWSS_LOG_DEBUG("processing: %s", joined.c_str()); - - for (size_t i = 1; i < split.size() - 1; ++i) - { - const auto &item = split[i]; - - auto start = item.find_first_of("="); - - auto field = item.substr(0, start); - auto value = item.substr(start + 1); - - swss::FieldValueTuple entry(field, value); - - entries.push_back(entry); - } - - // since now we converted this to proper list, we can extract attributes - - std::shared_ptr list = - std::make_shared(object_type, entries, false); - - sai_attribute_t *attr_list = list->get_attr_list(); - - uint32_t attr_count = list->get_attr_count(); - - if (api != SAI_COMMON_API_BULK_GET) - { - translate_local_to_redis(object_type, attr_count, attr_list); - } - - attributes.push_back(list); - } - - sai_status_t status; - - switch (object_type) - { - case SAI_OBJECT_TYPE_ROUTE_ENTRY: - status = handle_bulk_route(object_ids, api, attributes, statuses); - break; - - default: - - SWSS_LOG_THROW("bulk op for %s is not supported yet, FIXME", - sai_serialize_object_type(object_type).c_str()); - } - - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_THROW("failed to execute bulk api, FIXME"); - } -} - -bool g_sleep = false; - -int replay(int argc, char **argv) -{ - //swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_DEBUG); - - SWSS_LOG_ENTER(); - - if (argc == 0) - { - fprintf(stderr, "ERR: need to specify filename\n"); - - return -1; - } - - char* filename = argv[argc - 1]; - - SWSS_LOG_NOTICE("using file: %s", filename); - - std::ifstream infile(filename); - - if (!infile.is_open()) - { - SWSS_LOG_ERROR("failed to open file %s", filename); - return -1; - } - - std::string line; - - while (std::getline(infile, line)) - { - // std::cout << "processing " << line << std::endl; - - sai_common_api_t api = SAI_COMMON_API_CREATE; - - auto p = line.find_first_of("|"); - - char op = line[p+1]; - - switch (op) - { - case 'a': - { - std::string response; - - do - { - // this line may be notification, we need to skip - if (!std::getline(infile, response)) - { - SWSS_LOG_THROW("failed to read next file from file, previous: %s", line.c_str()); - } - } - while (response[response.find_first_of("|") + 1] == 'n'); - - performNotifySyncd(line, response); - } - continue; - - case 'f': - { - std::string response; - - do - { - // this line may be notification, we need to skip - if (!std::getline(infile, response)) - { - SWSS_LOG_THROW("failed to read next file from file, previous: %s", line.c_str()); - } - } - while (response[response.find_first_of("|") + 1] == 'n'); - - performFdbFlush(line, response); - } - continue; - - case '@': - performSleep(line); - continue; - case 'c': - api = SAI_COMMON_API_CREATE; - break; - case 'r': - api = SAI_COMMON_API_REMOVE; - break; - case 's': - api = SAI_COMMON_API_SET; - break; - case 'S': - processBulk(SAI_COMMON_API_BULK_SET, line); - continue; - case 'C': - processBulk(SAI_COMMON_API_BULK_CREATE, line); - continue; - case 'g': - api = SAI_COMMON_API_GET; - break; - case 'q': - // TODO: implement SAI player support for query commands - continue; - case 'Q': - continue; // skip over query responses - case '#': - case 'n': - SWSS_LOG_INFO("skipping op %c line %s", op, line.c_str()); - continue; // skip comment and notification - - default: - SWSS_LOG_THROW("unknown op %c on line %s", op, line.c_str()); - } - - // timestamp|action|objecttype:objectid|attrid=value,... - auto fields = swss::tokenize(line, '|'); - - // objecttype:objectid (object id may contain ':') - auto start = fields[2].find_first_of(":"); - - auto str_object_type = fields[2].substr(0, start); - auto str_object_id = fields[2].substr(start + 1); - - sai_object_type_t object_type = deserialize_object_type(str_object_type); - - auto values = get_values(fields); - - SaiAttributeList list(object_type, values, false); - - sai_attribute_t *attr_list = list.get_attr_list(); - - uint32_t attr_count = list.get_attr_count(); - - SWSS_LOG_DEBUG("attr count: %u", list.get_attr_count()); - - if (api != SAI_COMMON_API_GET) - { - translate_local_to_redis(object_type, attr_count, attr_list); - } - - sai_status_t status; - - auto info = sai_metadata_get_object_type_info(object_type); - - switch (object_type) - { - case SAI_OBJECT_TYPE_FDB_ENTRY: - status = handle_fdb(str_object_id, api, attr_count, attr_list); - break; - - case SAI_OBJECT_TYPE_NEIGHBOR_ENTRY: - status = handle_neighbor(str_object_id, api, attr_count, attr_list); - break; - - case SAI_OBJECT_TYPE_ROUTE_ENTRY: - status = handle_route(str_object_id, api, attr_count, attr_list); - break; - - default: - - if (info->isnonobjectid) - { - SWSS_LOG_THROW("object %s:%s is non object id, but not handled, FIXME", - sai_serialize_object_type(object_type).c_str(), - str_object_id.c_str()); - } - - status = handle_generic(object_type, str_object_id, api, attr_count, attr_list); - break; - } - - if (status != SAI_STATUS_SUCCESS) - { - if (api == SAI_COMMON_API_GET) - { - // GET status is checked in handle response - } - else - SWSS_LOG_THROW("failed to execute api: %c: %s", op, sai_serialize_status(status).c_str()); - } - - if (api == SAI_COMMON_API_GET) - { - std::string response; - - do - { - // this line may be notification, we need to skip - std::getline(infile, response); - } - while (response[response.find_first_of("|") + 1] == 'n'); - - try - { - handle_get_response(object_type, attr_count, attr_list, response, status); - } - catch (const std::exception &e) - { - SWSS_LOG_NOTICE("line: %s", line.c_str()); - SWSS_LOG_NOTICE("resp (expected): %s", response.c_str()); - SWSS_LOG_NOTICE("got: %s", sai_serialize_status(status).c_str()); - - if (api == SAI_COMMON_API_GET && (status == SAI_STATUS_SUCCESS || status == SAI_STATUS_BUFFER_OVERFLOW)) - { - // log each get parameter - for (uint32_t i = 0; i < attr_count; ++i) - { - auto meta = sai_metadata_get_attr_metadata(object_type, attr_list[i].id); - - auto val = sai_serialize_attr_value(*meta, attr_list[i]); - - SWSS_LOG_NOTICE(" - %s:%s", meta->attridname, val.c_str()); - } - } - - exit(EXIT_FAILURE); - } - - if (api == SAI_COMMON_API_GET && (status == SAI_STATUS_SUCCESS || status == SAI_STATUS_BUFFER_OVERFLOW)) - { - // log each get parameter - for (uint32_t i = 0; i < attr_count; ++i) - { - auto meta = sai_metadata_get_attr_metadata(object_type, attr_list[i].id); - - auto val = sai_serialize_attr_value(*meta, attr_list[i]); - - SWSS_LOG_NOTICE(" - %s:%s", meta->attridname, val.c_str()); - } - } - } - } - - infile.close(); - - SWSS_LOG_NOTICE("finished replaying %s with SUCCESS", filename); - - if (g_sleep) - { - fprintf(stderr, "Reply SUCCESS, sleeping, watching for notifications\n"); - - sleep(-1); - } - - return 0; -} - -void printUsage() -{ - SWSS_LOG_ENTER(); - - std::cout << "Usage: saiplayer [-h] recordfile" << std::endl << std::endl; - std::cout << " -C --skipNotifySyncd:" << std::endl; - std::cout << " Will not send notify init/apply view to syncd" << std::endl << std::endl; - std::cout << " -d --enableDebug:" << std::endl; - std::cout << " Enable syslog debug messages" << std::endl << std::endl; - std::cout << " -u --useTempView:" << std::endl; - std::cout << " Enable temporary view between init and apply" << std::endl << std::endl; - std::cout << " -i --inspectAsic:" << std::endl; - std::cout << " Inspect ASIC by ASIC DB" << std::endl << std::endl; - std::cout << " -s --sleep:" << std::endl; - std::cout << " Sleep after success reply, to notice any switch notifications" << std::endl << std::endl; - std::cout << " -h --help:" << std::endl; - std::cout << " Print out this message" << std::endl << std::endl; -} - -bool g_useTempView = false; -bool g_inspectAsic = false; - -int handleCmdLine(int argc, char **argv) -{ - SWSS_LOG_ENTER(); - - while(true) - { - static struct option long_options[] = - { - { "useTempView", no_argument, 0, 'u' }, - { "help", no_argument, 0, 'h' }, - { "skipNotifySyncd", no_argument, 0, 'C' }, - { "enableDebug", no_argument, 0, 'd' }, - { "inspectAsic", no_argument, 0, 'i' }, - { "sleep", no_argument, 0, 's' }, - { 0, 0, 0, 0 } - }; - - const char* const optstring = "hCduis"; - - int c = getopt_long(argc, argv, optstring, long_options, 0); - - if (c == -1) - break; - - switch (c) - { - case 'd': - swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_DEBUG); - break; - - case 'u': - g_useTempView = false; - break; - - case 'C': - g_notifySyncd = false; - break; - - case 'i': - g_inspectAsic = true; - break; - - case 's': - g_sleep = true; - break; - - case 'h': - printUsage(); - exit(EXIT_SUCCESS); - - case '?': - SWSS_LOG_WARN("unknown option %c", optopt); - printUsage(); - exit(EXIT_FAILURE); - - default: - SWSS_LOG_ERROR("getopt_long failure"); - exit(EXIT_FAILURE); - } - } - - return optind; -} - -void sai_meta_log_syncd( - _In_ sai_log_level_t log_level, - _In_ const char *file, - _In_ int line, - _In_ const char *func, - _In_ const char *format, - ...) - __attribute__ ((format (printf, 5, 6))); - -void sai_meta_log_syncd( - _In_ sai_log_level_t log_level, - _In_ const char *file, - _In_ int line, - _In_ const char *func, - _In_ const char *format, - ...) -{ - char buffer[0x1000]; - - va_list ap; - va_start(ap, format); - vsnprintf(buffer, 0x1000, format, ap); - va_end(ap); - - swss::Logger::Priority p = swss::Logger::SWSS_NOTICE; - - switch (log_level) - { - case SAI_LOG_LEVEL_DEBUG: - p = swss::Logger::SWSS_DEBUG; - break; - case SAI_LOG_LEVEL_INFO: - p = swss::Logger::SWSS_INFO; - break; - case SAI_LOG_LEVEL_ERROR: - p = swss::Logger::SWSS_ERROR; - fprintf(stderr, "ERROR: %s: %s\n", func, buffer); - break; - case SAI_LOG_LEVEL_WARN: - p = swss::Logger::SWSS_WARN; - fprintf(stderr, "WARN: %s: %s\n", func, buffer); - break; - case SAI_LOG_LEVEL_CRITICAL: - p = swss::Logger::SWSS_CRIT; - break; - - default: - p = swss::Logger::SWSS_NOTICE; - break; - } - - swss::Logger::getInstance().write(p, ":- %s: %s", func, buffer); -} +using namespace saiplayer; int main(int argc, char **argv) { @@ -1591,47 +16,13 @@ int main(int argc, char **argv) swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_NOTICE); - int handled = handleCmdLine(argc, argv); - argc -= handled; - argv += handled; - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsuggest-attribute=format" - sai_metadata_log = &sai_meta_log_syncd; -#pragma GCC diagnostic pop - - EXIT_ON_ERROR(sai_api_initialize(0, (const sai_service_method_table_t *)&test_services)); - - sai_metadata_apis_query(sai_api_query, &apis); - - sai_attribute_t attr; - - /* - * Notice that we use null object id as switch id, which is fine since - * those attributes don't need switch. - */ - - sai_object_id_t switch_id = SAI_NULL_OBJECT_ID; - - if (g_inspectAsic) - { - attr.id = SAI_REDIS_SWITCH_ATTR_NOTIFY_SYNCD; - attr.value.s32 = SAI_REDIS_NOTIFY_SYNCD_INSPECT_ASIC; - EXIT_ON_ERROR(sai_metadata_sai_switch_api->set_switch_attribute(switch_id, &attr)); - } - - int exitcode = 0; - if (argc > 0) - { - attr.id = SAI_REDIS_SWITCH_ATTR_USE_TEMP_VIEW; - attr.value.booldata = g_useTempView; + syncd::MetadataLogger::initialize(); - EXIT_ON_ERROR(sai_metadata_sai_switch_api->set_switch_attribute(switch_id, &attr)); + auto commandLineOptions = CommandLineOptionsParser::parseCommandLine(argc, argv); - exitcode = replay(argc, argv); - } + auto sai = std::make_shared(); - sai_api_uninitialize(); + auto player = std::make_shared(sai, commandLineOptions); - return exitcode; + return player->run(); }