syncd/Syncd.cpp (3,791 lines of code) (raw):

#include "Syncd.h" #include "VidManager.h" #include "NotificationHandler.h" #include "Workaround.h" #include "ComparisonLogic.h" #include "HardReiniter.h" #include "RedisClient.h" #include "RequestShutdown.h" #include "WarmRestartTable.h" #include "ContextConfigContainer.h" #include "BreakConfigParser.h" #include "RedisNotificationProducer.h" #include "ZeroMQNotificationProducer.h" #include "WatchdogScope.h" #include "sairediscommon.h" #include "swss/logger.h" #include "swss/select.h" #include "swss/tokenize.h" #include "swss/notificationproducer.h" #include "swss/exec.h" #include "meta/sai_serialize.h" #include "meta/ZeroMQSelectableChannel.h" #include "meta/RedisSelectableChannel.h" #include "meta/PerformanceIntervalTimer.h" #include "meta/Globals.h" #include "vslib/saivs.h" #include "config.h" #include <unistd.h> #include <inttypes.h> #include <iterator> #include <algorithm> #define DEF_SAI_WARM_BOOT_DATA_FILE "/var/warmboot/sai-warmboot.bin" #define SAI_FAILURE_DUMP_SCRIPT "/usr/bin/sai_failure_dump.sh" using namespace syncd; using namespace saimeta; using namespace sairediscommon; using namespace std::placeholders; #ifdef ASAN_ENABLED #define WD_DELAY_FACTOR 2 #else #define WD_DELAY_FACTOR 1 #endif Syncd::Syncd( _In_ std::shared_ptr<sairedis::SaiInterface> vendorSai, _In_ std::shared_ptr<CommandLineOptions> cmd, _In_ bool isWarmStart): m_commandLineOptions(cmd), m_isWarmStart(isWarmStart), m_firstInitWasPerformed(false), m_asicInitViewMode(false), // by default we are in APPLY view mode m_vendorSai(vendorSai), m_veryFirstRun(false), m_enableSyncMode(false), m_timerWatchdog(cmd->m_watchdogWarnTimeSpan * WD_DELAY_FACTOR) { SWSS_LOG_ENTER(); SWSS_LOG_NOTICE("sairedis git revision %s, SAI git revision: %s", SAIREDIS_GIT_REVISION, SAI_GIT_REVISION); setSaiApiLogLevel(); SWSS_LOG_NOTICE("command line: %s", m_commandLineOptions->getCommandLineString().c_str()); auto ccc = sairedis::ContextConfigContainer::loadFromFile(m_commandLineOptions->m_contextConfig.c_str()); m_contextConfig = ccc->get(m_commandLineOptions->m_globalContext); if (m_contextConfig == nullptr) { SWSS_LOG_THROW("no context config defined at global context %u", m_commandLineOptions->m_globalContext); } if (m_contextConfig->m_zmqEnable && m_commandLineOptions->m_enableSyncMode) { SWSS_LOG_NOTICE("disabling command line sync mode, since context zmq enabled"); m_commandLineOptions->m_enableSyncMode = false; m_commandLineOptions->m_redisCommunicationMode = SAI_REDIS_COMMUNICATION_MODE_ZMQ_SYNC; } if (m_commandLineOptions->m_enableSyncMode) { SWSS_LOG_WARN("enable sync mode is deprecated, please use communication mode, FORCING redis sync mode"); m_enableSyncMode = true; m_contextConfig->m_zmqEnable = false; m_commandLineOptions->m_redisCommunicationMode = SAI_REDIS_COMMUNICATION_MODE_REDIS_SYNC; } if (m_commandLineOptions->m_redisCommunicationMode == SAI_REDIS_COMMUNICATION_MODE_ZMQ_SYNC) { SWSS_LOG_NOTICE("zmq sync mode enabled via cmd line"); m_contextConfig->m_zmqEnable = true; m_enableSyncMode = true; } m_manager = std::make_shared<FlexCounterManager>(m_vendorSai, m_contextConfig->m_dbCounters, m_commandLineOptions->m_supportingBulkCounterGroups); loadProfileMap(); m_profileIter = m_profileMap.begin(); // we need STATE_DB ASIC_DB and COUNTERS_DB m_dbAsic = std::make_shared<swss::DBConnector>(m_contextConfig->m_dbAsic, 0); m_mdioIpcServer = std::make_shared<MdioIpcServer>(m_vendorSai, m_commandLineOptions->m_globalContext); if (m_contextConfig->m_zmqEnable) { m_notifications = std::make_shared<ZeroMQNotificationProducer>(m_contextConfig->m_zmqNtfEndpoint); SWSS_LOG_NOTICE("zmq enabled, forcing sync mode"); m_enableSyncMode = true; m_selectableChannel = std::make_shared<sairedis::ZeroMQSelectableChannel>(m_contextConfig->m_zmqEndpoint); } else { m_notifications = std::make_shared<RedisNotificationProducer>(m_contextConfig->m_dbAsic); m_enableSyncMode = m_commandLineOptions->m_redisCommunicationMode == SAI_REDIS_COMMUNICATION_MODE_REDIS_SYNC; bool modifyRedis = m_enableSyncMode ? false : true; m_selectableChannel = std::make_shared<sairedis::RedisSelectableChannel>( m_dbAsic, ASIC_STATE_TABLE, REDIS_TABLE_GETRESPONSE, TEMP_PREFIX, modifyRedis); } m_client = std::make_shared<RedisClient>(m_dbAsic); m_processor = std::make_shared<NotificationProcessor>(m_notifications, m_client, std::bind(&Syncd::syncProcessNotification, this, _1)); m_handler = std::make_shared<NotificationHandler>(m_processor); m_sn.onFdbEvent = std::bind(&NotificationHandler::onFdbEvent, m_handler.get(), _1, _2); m_sn.onNatEvent = std::bind(&NotificationHandler::onNatEvent, m_handler.get(), _1, _2); m_sn.onPortStateChange = std::bind(&NotificationHandler::onPortStateChange, m_handler.get(), _1, _2); m_sn.onQueuePfcDeadlock = std::bind(&NotificationHandler::onQueuePfcDeadlock, m_handler.get(), _1, _2); m_sn.onSwitchAsicSdkHealthEvent = std::bind(&NotificationHandler::onSwitchAsicSdkHealthEvent, m_handler.get(), _1, _2, _3, _4, _5, _6); m_sn.onSwitchShutdownRequest = std::bind(&NotificationHandler::onSwitchShutdownRequest, m_handler.get(), _1); m_sn.onSwitchStateChange = std::bind(&NotificationHandler::onSwitchStateChange, m_handler.get(), _1, _2); m_sn.onBfdSessionStateChange = std::bind(&NotificationHandler::onBfdSessionStateChange, m_handler.get(), _1, _2); m_sn.onPortHostTxReady = std::bind(&NotificationHandler::onPortHostTxReady, m_handler.get(), _1, _2, _3); m_sn.onTwampSessionEvent = std::bind(&NotificationHandler::onTwampSessionEvent, m_handler.get(), _1, _2); m_handler->setSwitchNotifications(m_sn.getSwitchNotifications()); m_restartQuery = std::make_shared<swss::NotificationConsumer>(m_dbAsic.get(), SYNCD_NOTIFICATION_CHANNEL_RESTARTQUERY_PER_DB(m_contextConfig->m_dbAsic)); // TODO to be moved to ASIC_DB m_dbFlexCounter = std::make_shared<swss::DBConnector>(m_contextConfig->m_dbFlex, 0); m_flexCounter = std::make_shared<swss::ConsumerTable>(m_dbFlexCounter.get(), FLEX_COUNTER_TABLE); m_flexCounterGroup = std::make_shared<swss::ConsumerTable>(m_dbFlexCounter.get(), FLEX_COUNTER_GROUP_TABLE); m_flexCounterTable = std::make_shared<swss::Table>(m_dbFlexCounter.get(), FLEX_COUNTER_TABLE); m_flexCounterGroupTable = std::make_shared<swss::Table>(m_dbFlexCounter.get(), FLEX_COUNTER_GROUP_TABLE); m_switchConfigContainer = std::make_shared<sairedis::SwitchConfigContainer>(); m_redisVidIndexGenerator = std::make_shared<sairedis::RedisVidIndexGenerator>(m_dbAsic, REDIS_KEY_VIDCOUNTER); m_virtualObjectIdManager = std::make_shared<sairedis::VirtualObjectIdManager>( m_commandLineOptions->m_globalContext, m_switchConfigContainer, m_redisVidIndexGenerator); // TODO move to syncd object m_translator = std::make_shared<VirtualOidTranslator>(m_client, m_virtualObjectIdManager, vendorSai); m_processor->m_translator = m_translator; // TODO as param m_veryFirstRun = isVeryFirstRun(); performStartupLogic(); m_smt.profileGetValue = std::bind(&Syncd::profileGetValue, this, _1, _2); m_smt.profileGetNextValue = std::bind(&Syncd::profileGetNextValue, this, _1, _2, _3); m_test_services = m_smt.getServiceMethodTable(); sai_status_t status = vendorSai->apiInitialize(0, &m_test_services); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("FATAL: failed to sai_api_initialize: %s", sai_serialize_status(status).c_str()); abort(); } sai_api_version_t apiVersion = SAI_VERSION(0,0,0); // invalid version status = m_vendorSai->queryApiVersion(&apiVersion); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_WARN("failed to obtain libsai api version: %s", sai_serialize_status(status).c_str()); } else { SWSS_LOG_NOTICE("libsai api version: %lu", apiVersion); } m_handler->setApiVersion(apiVersion); m_breakConfig = BreakConfigParser::parseBreakConfig(m_commandLineOptions->m_breakConfig); #ifdef SKIP_SAI_PORT_DISCOVERY SWSS_LOG_WARN("SAI discovery is skipped on ports"); #endif SWSS_LOG_NOTICE("syncd started"); } Syncd::~Syncd() { SWSS_LOG_ENTER(); // empty } void Syncd::performStartupLogic() { SWSS_LOG_ENTER(); // ignore warm logic here if syncd starts in fast-boot, express-boot or Mellanox fastfast boot mode if (m_isWarmStart && m_commandLineOptions->m_startType != SAI_START_TYPE_FASTFAST_BOOT && m_commandLineOptions->m_startType != SAI_START_TYPE_EXPRESS_BOOT && m_commandLineOptions->m_startType != SAI_START_TYPE_FAST_BOOT) { SWSS_LOG_WARN("override command line startType=%s via SAI_START_TYPE_WARM_BOOT", CommandLineOptions::startTypeToString(m_commandLineOptions->m_startType).c_str()); m_commandLineOptions->m_startType = SAI_START_TYPE_WARM_BOOT; } if (m_commandLineOptions->m_startType == SAI_START_TYPE_WARM_BOOT) { const char *warmBootReadFile = profileGetValue(0, SAI_KEY_WARM_BOOT_READ_FILE); SWSS_LOG_NOTICE("using warmBootReadFile: '%s'", warmBootReadFile); if (warmBootReadFile == NULL || access(warmBootReadFile, F_OK) == -1) { SWSS_LOG_WARN("user requested warmStart but warmBootReadFile is not specified or not accessible, forcing cold start"); m_commandLineOptions->m_startType = SAI_START_TYPE_COLD_BOOT; } } if (m_commandLineOptions->m_startType == SAI_START_TYPE_WARM_BOOT && m_veryFirstRun) { SWSS_LOG_WARN("warm start requested, but this is very first syncd start, forcing cold start"); /* * We force cold start since if it's first run then redis db is not * complete so redis asic view will not reflect warm boot asic state, * if this happen then orch agent needs to be restarted as well to * repopulate asic view. */ m_commandLineOptions->m_startType = SAI_START_TYPE_COLD_BOOT; } if (m_commandLineOptions->m_startType == SAI_START_TYPE_FASTFAST_BOOT) { /* * Mellanox SAI requires to pass SAI_WARM_BOOT as SAI_BOOT_KEY * to start 'fastfast' */ m_profileMap[SAI_KEY_BOOT_TYPE] = std::to_string(SAI_START_TYPE_WARM_BOOT); } else { m_profileMap[SAI_KEY_BOOT_TYPE] = std::to_string(m_commandLineOptions->m_startType); // number value is needed } } bool Syncd::getAsicInitViewMode() const { SWSS_LOG_ENTER(); return m_asicInitViewMode; } void Syncd::setAsicInitViewMode( _In_ bool enable) { SWSS_LOG_ENTER(); m_asicInitViewMode = enable; } bool Syncd::isInitViewMode() const { SWSS_LOG_ENTER(); return m_asicInitViewMode && m_commandLineOptions->m_enableTempView; } void Syncd::processEvent( _In_ sairedis::SelectableChannel& consumer) { SWSS_LOG_ENTER(); std::lock_guard<std::mutex> lock(m_mutex); do { swss::KeyOpFieldsValuesTuple kco; /* * In init mode we put all data to TEMP view and we snoop. We need * to specify temporary view prefix in consumer since consumer puts * data to redis db. */ consumer.pop(kco, isInitViewMode()); processSingleEvent(kco); } while (!consumer.empty()); } sai_status_t Syncd::processSingleEvent( _In_ const swss::KeyOpFieldsValuesTuple &kco) { SWSS_LOG_ENTER(); auto& key = kfvKey(kco); auto& op = kfvOp(kco); SWSS_LOG_INFO("key: %s op: %s", key.c_str(), op.c_str()); if (key.length() == 0) { SWSS_LOG_DEBUG("no elements in m_buffer"); return SAI_STATUS_SUCCESS; } WatchdogScope ws(m_timerWatchdog, op + ":" + key, &kco); if (op == REDIS_ASIC_STATE_COMMAND_CREATE) return processQuadEvent(SAI_COMMON_API_CREATE, kco); if (op == REDIS_ASIC_STATE_COMMAND_REMOVE) return processQuadEvent(SAI_COMMON_API_REMOVE, kco); if (op == REDIS_ASIC_STATE_COMMAND_SET) return processQuadEvent(SAI_COMMON_API_SET, kco); if (op == REDIS_ASIC_STATE_COMMAND_GET) return processQuadEvent(SAI_COMMON_API_GET, kco); if (op == REDIS_ASIC_STATE_COMMAND_BULK_CREATE) return processBulkQuadEvent(SAI_COMMON_API_BULK_CREATE, kco); if (op == REDIS_ASIC_STATE_COMMAND_BULK_REMOVE) return processBulkQuadEvent(SAI_COMMON_API_BULK_REMOVE, kco); if (op == REDIS_ASIC_STATE_COMMAND_BULK_SET) return processBulkQuadEvent(SAI_COMMON_API_BULK_SET, kco); if (op == REDIS_ASIC_STATE_COMMAND_BULK_GET) return processBulkQuadEvent(SAI_COMMON_API_BULK_GET, kco); if (op == REDIS_ASIC_STATE_COMMAND_NOTIFY) return processNotifySyncd(kco); if (op == REDIS_ASIC_STATE_COMMAND_GET_STATS) return processGetStatsEvent(kco); if (op == REDIS_ASIC_STATE_COMMAND_CLEAR_STATS) return processClearStatsEvent(kco); if (op == REDIS_ASIC_STATE_COMMAND_FLUSH) return processFdbFlush(kco); if (op == REDIS_ASIC_STATE_COMMAND_ATTR_CAPABILITY_QUERY) return processAttrCapabilityQuery(kco); if (op == REDIS_ASIC_STATE_COMMAND_ATTR_ENUM_VALUES_CAPABILITY_QUERY) return processAttrEnumValuesCapabilityQuery(kco); if (op == REDIS_ASIC_STATE_COMMAND_OBJECT_TYPE_GET_AVAILABILITY_QUERY) return processObjectTypeGetAvailabilityQuery(kco); if (op == REDIS_FLEX_COUNTER_COMMAND_START_POLL) return processFlexCounterEvent(key, SET_COMMAND, kfvFieldsValues(kco)); if (op == REDIS_FLEX_COUNTER_COMMAND_STOP_POLL) return processFlexCounterEvent(key, DEL_COMMAND, kfvFieldsValues(kco)); if (op == REDIS_FLEX_COUNTER_COMMAND_SET_GROUP) return processFlexCounterGroupEvent(key, SET_COMMAND, kfvFieldsValues(kco)); if (op == REDIS_FLEX_COUNTER_COMMAND_DEL_GROUP) return processFlexCounterGroupEvent(key, DEL_COMMAND, kfvFieldsValues(kco)); if (op == REDIS_ASIC_STATE_COMMAND_STATS_CAPABILITY_QUERY) return processStatsCapabilityQuery(kco); SWSS_LOG_THROW("event op '%s' is not implemented, FIXME", op.c_str()); } sai_status_t Syncd::processAttrCapabilityQuery( _In_ const swss::KeyOpFieldsValuesTuple &kco) { SWSS_LOG_ENTER(); auto& strSwitchVid = kfvKey(kco); sai_object_id_t switchVid; sai_deserialize_object_id(strSwitchVid, switchVid); sai_object_id_t switchRid = m_translator->translateVidToRid(switchVid); auto& values = kfvFieldsValues(kco); if (values.size() != 2) { SWSS_LOG_ERROR("Invalid input: expected 2 arguments, received %zu", values.size()); m_selectableChannel->set(sai_serialize_status(SAI_STATUS_INVALID_PARAMETER), {}, REDIS_ASIC_STATE_COMMAND_ATTR_CAPABILITY_RESPONSE); return SAI_STATUS_INVALID_PARAMETER; } sai_object_type_t objectType; sai_deserialize_object_type(fvValue(values[0]), objectType); sai_attr_id_t attrId; sai_deserialize_attr_id(fvValue(values[1]), attrId); sai_attr_capability_t capability; sai_status_t status = m_vendorSai->queryAttributeCapability(switchRid, objectType, attrId, &capability); std::vector<swss::FieldValueTuple> entry; if (status == SAI_STATUS_SUCCESS) { entry = { swss::FieldValueTuple("CREATE_IMPLEMENTED", (capability.create_implemented ? "true" : "false")), swss::FieldValueTuple("SET_IMPLEMENTED", (capability.set_implemented ? "true" : "false")), swss::FieldValueTuple("GET_IMPLEMENTED", (capability.get_implemented ? "true" : "false")) }; SWSS_LOG_INFO("Sending response: create_implemented:%d, set_implemented:%d, get_implemented:%d", capability.create_implemented, capability.set_implemented, capability.get_implemented); } m_selectableChannel->set(sai_serialize_status(status), entry, REDIS_ASIC_STATE_COMMAND_ATTR_CAPABILITY_RESPONSE); return status; } sai_status_t Syncd::processAttrEnumValuesCapabilityQuery( _In_ const swss::KeyOpFieldsValuesTuple &kco) { SWSS_LOG_ENTER(); auto& strSwitchVid = kfvKey(kco); sai_object_id_t switchVid; sai_deserialize_object_id(strSwitchVid, switchVid); sai_object_id_t switchRid = m_translator->translateVidToRid(switchVid); auto& values = kfvFieldsValues(kco); if (values.size() != 3) { SWSS_LOG_ERROR("Invalid input: expected 3 arguments, received %zu", values.size()); m_selectableChannel->set(sai_serialize_status(SAI_STATUS_INVALID_PARAMETER), {}, REDIS_ASIC_STATE_COMMAND_ATTR_ENUM_VALUES_CAPABILITY_RESPONSE); return SAI_STATUS_INVALID_PARAMETER; } sai_object_type_t objectType; sai_deserialize_object_type(fvValue(values[0]), objectType); sai_attr_id_t attrId; sai_deserialize_attr_id(fvValue(values[1]), attrId); uint32_t list_size = std::stoi(fvValue(values[2])); std::vector<int32_t> enum_capabilities_list(list_size); sai_s32_list_t enumCapList; enumCapList.count = list_size; enumCapList.list = enum_capabilities_list.data(); sai_status_t status = m_vendorSai->queryAttributeEnumValuesCapability(switchRid, objectType, attrId, &enumCapList); std::vector<swss::FieldValueTuple> entry; if (status == SAI_STATUS_SUCCESS) { std::vector<std::string> vec; std::transform(enumCapList.list, enumCapList.list + enumCapList.count, std::back_inserter(vec), [](auto&e) { return std::to_string(e); }); std::ostringstream join; std::copy(vec.begin(), vec.end(), std::ostream_iterator<std::string>(join, ",")); auto strCap = join.str(); entry = { swss::FieldValueTuple("ENUM_CAPABILITIES", strCap), swss::FieldValueTuple("ENUM_COUNT", std::to_string(enumCapList.count)) }; SWSS_LOG_DEBUG("Sending response: capabilities = '%s', count = %d", strCap.c_str(), enumCapList.count); } else if (status == SAI_STATUS_BUFFER_OVERFLOW) { entry = { swss::FieldValueTuple("ENUM_COUNT", std::to_string(enumCapList.count)) }; SWSS_LOG_DEBUG("Sending response: count = %u", enumCapList.count); } m_selectableChannel->set(sai_serialize_status(status), entry, REDIS_ASIC_STATE_COMMAND_ATTR_ENUM_VALUES_CAPABILITY_RESPONSE); return status; } sai_status_t Syncd::processObjectTypeGetAvailabilityQuery( _In_ const swss::KeyOpFieldsValuesTuple &kco) { SWSS_LOG_ENTER(); auto& strSwitchVid = kfvKey(kco); sai_object_id_t switchVid; sai_deserialize_object_id(strSwitchVid, switchVid); const sai_object_id_t switchRid = m_translator->translateVidToRid(switchVid); std::vector<swss::FieldValueTuple> values = kfvFieldsValues(kco); // Syncd needs to pop the object type off the end of the list in order to // retrieve the attribute list sai_object_type_t objectType; sai_deserialize_object_type(fvValue(values.back()), objectType); values.pop_back(); SaiAttributeList list(objectType, values, false); sai_attribute_t *attr_list = list.get_attr_list(); uint32_t attr_count = list.get_attr_count(); m_translator->translateVidToRid(objectType, attr_count, attr_list); uint64_t count; sai_status_t status = m_vendorSai->objectTypeGetAvailability( switchRid, objectType, attr_count, attr_list, &count); std::vector<swss::FieldValueTuple> entry; if (status == SAI_STATUS_SUCCESS) { entry.push_back(swss::FieldValueTuple("OBJECT_COUNT", std::to_string(count))); SWSS_LOG_DEBUG("Sending response: count = %lu", count); } m_selectableChannel->set(sai_serialize_status(status), entry, REDIS_ASIC_STATE_COMMAND_OBJECT_TYPE_GET_AVAILABILITY_RESPONSE); return status; } sai_status_t Syncd::processStatsCapabilityQuery( _In_ const swss::KeyOpFieldsValuesTuple &kco) { SWSS_LOG_ENTER(); auto& strSwitchVid = kfvKey(kco); sai_object_id_t switchVid; sai_deserialize_object_id(strSwitchVid, switchVid); sai_object_id_t switchRid = m_translator->translateVidToRid(switchVid); auto& values = kfvFieldsValues(kco); if (values.size() != 2) { SWSS_LOG_ERROR("Invalid input: expected 2 arguments, received %zu", values.size()); m_selectableChannel->set(sai_serialize_status(SAI_STATUS_INVALID_PARAMETER), {}, REDIS_ASIC_STATE_COMMAND_STATS_CAPABILITY_RESPONSE); return SAI_STATUS_INVALID_PARAMETER; } sai_object_type_t objectType; sai_deserialize_object_type(fvValue(values[0]), objectType); uint32_t list_size = std::stoi(fvValue(values[1])); std::vector<sai_stat_capability_t> stat_capability_list(list_size); sai_stat_capability_list_t statCapList; statCapList.count = list_size; statCapList.list = stat_capability_list.data(); sai_status_t status = m_vendorSai->queryStatsCapability(switchRid, objectType, &statCapList); std::vector<swss::FieldValueTuple> entry; if (status == SAI_STATUS_SUCCESS) { std::vector<std::string> vec_stat_enum; std::vector<std::string> vec_stat_modes; for (uint32_t it = 0; it < statCapList.count; it++) { vec_stat_enum.push_back(std::to_string(statCapList.list[it].stat_enum)); vec_stat_modes.push_back(std::to_string(statCapList.list[it].stat_modes)); } std::ostringstream join_stat_enum; std::copy(vec_stat_enum.begin(), vec_stat_enum.end(), std::ostream_iterator<std::string>(join_stat_enum, ",")); auto strCapEnum = join_stat_enum.str(); std::ostringstream join_stat_modes; std::copy(vec_stat_modes.begin(), vec_stat_modes.end(), std::ostream_iterator<std::string>(join_stat_modes, ",")); auto strCapModes = join_stat_modes.str(); entry = { swss::FieldValueTuple("STAT_ENUM", strCapEnum), swss::FieldValueTuple("STAT_MODES", strCapModes), swss::FieldValueTuple("STAT_COUNT", std::to_string(statCapList.count)) }; SWSS_LOG_DEBUG("Sending response: stat_enums = '%s', stat_modes = '%s', count = %d", strCapEnum.c_str(), strCapModes.c_str(), statCapList.count); } else if (status == SAI_STATUS_BUFFER_OVERFLOW) { entry = { swss::FieldValueTuple("STAT_COUNT", std::to_string(statCapList.count)) }; SWSS_LOG_DEBUG("Sending response: count = %u", statCapList.count); } m_selectableChannel->set(sai_serialize_status(status), entry, REDIS_ASIC_STATE_COMMAND_STATS_CAPABILITY_RESPONSE); return status; } sai_status_t Syncd::processFdbFlush( _In_ const swss::KeyOpFieldsValuesTuple &kco) { SWSS_LOG_ENTER(); auto& key = kfvKey(kco); auto strSwitchVid = key.substr(key.find(":") + 1); sai_object_id_t switchVid; sai_deserialize_object_id(strSwitchVid, switchVid); sai_object_id_t switchRid = m_translator->translateVidToRid(switchVid); auto& values = kfvFieldsValues(kco); for (const auto &v: values) { SWSS_LOG_DEBUG("attr: %s: %s", fvField(v).c_str(), fvValue(v).c_str()); } SaiAttributeList list(SAI_OBJECT_TYPE_FDB_FLUSH, values, false); SaiAttributeList vidlist(SAI_OBJECT_TYPE_FDB_FLUSH, values, false); /* * Attribute list can't be const since we will use it to translate VID to * RID in place. */ sai_attribute_t *attr_list = list.get_attr_list(); uint32_t attr_count = list.get_attr_count(); m_translator->translateVidToRid(SAI_OBJECT_TYPE_FDB_FLUSH, attr_count, attr_list); sai_status_t status = m_vendorSai->flushFdbEntries(switchRid, attr_count, attr_list); m_selectableChannel->set(sai_serialize_status(status), {} , REDIS_ASIC_STATE_COMMAND_FLUSHRESPONSE); if (status == SAI_STATUS_SUCCESS) { SWSS_LOG_NOTICE("fdb flush succeeded, updating redis database"); // update database right after fdb flush success (not in notification) // build artificial notification here to reuse code auto *md = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_FDB_FLUSH, SAI_FDB_FLUSH_ATTR_ENTRY_TYPE); auto *dv = md ? md->defaultvalue : nullptr; sai_fdb_flush_entry_type_t type = dv ? (sai_fdb_flush_entry_type_t)dv->s32 : SAI_FDB_FLUSH_ENTRY_TYPE_DYNAMIC; sai_object_id_t bvId = SAI_NULL_OBJECT_ID; sai_object_id_t bridgePortId = SAI_NULL_OBJECT_ID; attr_list = vidlist.get_attr_list(); attr_count = vidlist.get_attr_count(); for (uint32_t i = 0; i < attr_count; i++) { switch (attr_list[i].id) { case SAI_FDB_FLUSH_ATTR_BRIDGE_PORT_ID: bridgePortId = attr_list[i].value.oid; break; case SAI_FDB_FLUSH_ATTR_BV_ID: bvId = attr_list[i].value.oid; break; case SAI_FDB_FLUSH_ATTR_ENTRY_TYPE: type = (sai_fdb_flush_entry_type_t)attr_list[i].value.s32; break; default: SWSS_LOG_ERROR("unsupported attribute: %d, skipping", attr_list[i].id); break; } } m_client->processFlushEvent(switchVid, bridgePortId, bvId, type); } return status; } sai_status_t Syncd::processClearStatsEvent( _In_ const swss::KeyOpFieldsValuesTuple &kco) { SWSS_LOG_ENTER(); const std::string &key = kfvKey(kco); sai_object_meta_key_t metaKey; sai_deserialize_object_meta_key(key, metaKey); if (isInitViewMode() && m_createdInInitView.find(metaKey.objectkey.key.object_id) != m_createdInInitView.end()) { SWSS_LOG_WARN("CLEAR STATS api can't be used on %s since it's created in INIT_VIEW mode", key.c_str()); sai_status_t status = SAI_STATUS_INVALID_OBJECT_ID; m_selectableChannel->set(sai_serialize_status(status), {}, REDIS_ASIC_STATE_COMMAND_GETRESPONSE); return status; } if (!m_translator->tryTranslateVidToRid(metaKey)) { SWSS_LOG_WARN("VID to RID translation failure: %s", key.c_str()); sai_status_t status = SAI_STATUS_INVALID_OBJECT_ID; m_selectableChannel->set(sai_serialize_status(status), {}, REDIS_ASIC_STATE_COMMAND_GETRESPONSE); return status; } auto info = sai_metadata_get_object_type_info(metaKey.objecttype); if (info->isnonobjectid) { SWSS_LOG_THROW("non object id not supported on clear stats: %s, FIXME", key.c_str()); } std::vector<sai_stat_id_t> counter_ids; for (auto&v: kfvFieldsValues(kco)) { int32_t val; sai_deserialize_enum(fvField(v), info->statenum, val); counter_ids.push_back(val); } auto status = m_vendorSai->clearStats( metaKey.objecttype, metaKey.objectkey.key.object_id, (uint32_t)counter_ids.size(), counter_ids.data()); m_selectableChannel->set(sai_serialize_status(status), {}, REDIS_ASIC_STATE_COMMAND_GETRESPONSE); return status; } sai_status_t Syncd::processGetStatsEvent( _In_ const swss::KeyOpFieldsValuesTuple &kco) { SWSS_LOG_ENTER(); const std::string &key = kfvKey(kco); sai_object_meta_key_t metaKey; sai_deserialize_object_meta_key(key, metaKey); if (isInitViewMode() && m_createdInInitView.find(metaKey.objectkey.key.object_id) != m_createdInInitView.end()) { SWSS_LOG_WARN("GET STATS api can't be used on %s since it's created in INIT_VIEW mode", key.c_str()); sai_status_t status = SAI_STATUS_INVALID_OBJECT_ID; m_selectableChannel->set(sai_serialize_status(status), {}, REDIS_ASIC_STATE_COMMAND_GETRESPONSE); return status; } m_translator->translateVidToRid(metaKey); auto info = sai_metadata_get_object_type_info(metaKey.objecttype); if (info->isnonobjectid) { SWSS_LOG_THROW("non object id not supported on clear stats: %s, FIXME", key.c_str()); } std::vector<sai_stat_id_t> counter_ids; for (auto&v: kfvFieldsValues(kco)) { int32_t val; sai_deserialize_enum(fvField(v), info->statenum, val); counter_ids.push_back(val); } std::vector<uint64_t> result(counter_ids.size()); auto status = m_vendorSai->getStats( metaKey.objecttype, metaKey.objectkey.key.object_id, (uint32_t)counter_ids.size(), counter_ids.data(), result.data()); std::vector<swss::FieldValueTuple> entry; if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_NOTICE("Getting stats error: %s", sai_serialize_status(status).c_str()); } else { const auto& values = kfvFieldsValues(kco); for (size_t i = 0; i < values.size(); i++) { entry.emplace_back(fvField(values[i]), std::to_string(result[i])); } } m_selectableChannel->set(sai_serialize_status(status), entry, REDIS_ASIC_STATE_COMMAND_GETRESPONSE); return status; } sai_status_t Syncd::processBulkQuadEvent( _In_ sai_common_api_t api, _In_ const swss::KeyOpFieldsValuesTuple &kco) { SWSS_LOG_ENTER(); const std::string& key = kfvKey(kco); // objectType:count std::string strObjectType = key.substr(0, key.find(":")); sai_object_type_t objectType; sai_deserialize_object_type(strObjectType, objectType); const std::vector<swss::FieldValueTuple> &values = kfvFieldsValues(kco); std::vector<std::vector<swss::FieldValueTuple>> strAttributes; // field = objectId // value = attrid=attrvalue|... std::vector<std::string> objectIds; std::vector<std::shared_ptr<SaiAttributeList>> attributes; for (const auto &fvt: values) { std::string strObjectId = fvField(fvt); std::string joined = fvValue(fvt); // decode values auto v = swss::tokenize(joined, '|'); objectIds.push_back(strObjectId); std::vector<swss::FieldValueTuple> entries; // attributes per object id for (size_t i = 0; i < v.size(); ++i) { const std::string item = v.at(i); auto start = item.find_first_of("="); auto field = item.substr(0, start); auto value = item.substr(start + 1); entries.emplace_back(field, value); } strAttributes.push_back(entries); // since now we converted this to proper list, we can extract attributes auto list = std::make_shared<SaiAttributeList>(objectType, entries, false); attributes.push_back(list); } SWSS_LOG_INFO("bulk %s executing with %zu items", strObjectType.c_str(), objectIds.size()); if (isInitViewMode()) { return processBulkQuadEventInInitViewMode(objectType, objectIds, api, attributes, strAttributes); } if (api != SAI_COMMON_API_BULK_GET) { // translate attributes for all objects for (auto &list: attributes) { sai_attribute_t *attr_list = list->get_attr_list(); uint32_t attr_count = list->get_attr_count(); m_translator->translateVidToRid(objectType, attr_count, attr_list); } } auto info = sai_metadata_get_object_type_info(objectType); if (info->isobjectid) { return processBulkOid(objectType, objectIds, api, attributes, strAttributes); } else { return processBulkEntry(objectType, objectIds, api, attributes, strAttributes); } } sai_status_t Syncd::processBulkQuadEventInInitViewMode( _In_ sai_object_type_t objectType, _In_ const std::vector<std::string>& objectIds, _In_ sai_common_api_t api, _In_ const std::vector<std::shared_ptr<saimeta::SaiAttributeList>>& attributes, _In_ const std::vector<std::vector<swss::FieldValueTuple>>& strAttributes) { SWSS_LOG_ENTER(); const auto objectCount = static_cast<uint32_t>(objectIds.size()); std::vector<sai_status_t> statuses(objectIds.size()); const sai_status_t initialObjectStatus = api != SAI_COMMON_API_BULK_GET ? SAI_STATUS_SUCCESS : SAI_STATUS_NOT_EXECUTED; statuses.assign(statuses.size(), initialObjectStatus); auto info = sai_metadata_get_object_type_info(objectType); switch (api) { case SAI_COMMON_API_BULK_CREATE: case SAI_COMMON_API_BULK_REMOVE: if (info->isnonobjectid) { sendApiResponse(api, SAI_STATUS_SUCCESS, (uint32_t)statuses.size(), statuses.data()); syncUpdateRedisBulkQuadEvent(api, statuses, objectType, objectIds, strAttributes); return SAI_STATUS_SUCCESS; } switch (objectType) { case SAI_OBJECT_TYPE_SWITCH: case SAI_OBJECT_TYPE_PORT: case SAI_OBJECT_TYPE_SCHEDULER_GROUP: case SAI_OBJECT_TYPE_INGRESS_PRIORITY_GROUP: SWSS_LOG_THROW("%s is not supported in init view mode", sai_serialize_object_type(objectType).c_str()); default: sendApiResponse(api, SAI_STATUS_SUCCESS, (uint32_t)statuses.size(), statuses.data()); syncUpdateRedisBulkQuadEvent(api, statuses, objectType, objectIds, strAttributes); for (auto& str: objectIds) { sai_object_id_t objectVid; sai_deserialize_object_id(str, objectVid); // in init view mode insert every created object except switch m_createdInInitView.insert(objectVid); } return SAI_STATUS_SUCCESS; } case SAI_COMMON_API_BULK_SET: switch (objectType) { case SAI_OBJECT_TYPE_SWITCH: case SAI_OBJECT_TYPE_SCHEDULER_GROUP: SWSS_LOG_THROW("%s is not supported in init view mode", sai_serialize_object_type(objectType).c_str()); default: break; } sendApiResponse(api, SAI_STATUS_SUCCESS, (uint32_t)statuses.size(), statuses.data()); syncUpdateRedisBulkQuadEvent(api, statuses, objectType, objectIds, strAttributes); return SAI_STATUS_SUCCESS; case SAI_COMMON_API_BULK_GET: if (info->isnonobjectid) { /* * Those objects are user created, so if user created ROUTE he * passed some attributes, there is no sense to support GET * since user explicitly know what attributes were set, similar * for other non object id types. */ SWSS_LOG_ERROR("get is not supported on %s in init view mode", sai_serialize_object_type(objectType).c_str()); const sai_status_t status = SAI_STATUS_NOT_SUPPORTED; sendBulkGetResponse(objectType, objectIds, status, attributes, statuses); return status; } else { for (size_t idx = 0; idx < objectCount; idx++) { const auto& strObjectId = objectIds[idx]; sai_object_id_t objectVid; sai_deserialize_object_id(strObjectId, objectVid); if (isInitViewMode() && m_createdInInitView.find(objectVid) != m_createdInInitView.end()) { SWSS_LOG_WARN("GET api can't be used on %s (%s) since it's created in INIT_VIEW mode", strObjectId.c_str(), sai_serialize_object_type(objectType).c_str()); const sai_status_t status = SAI_STATUS_INVALID_OBJECT_ID; sendBulkGetResponse(objectType, objectIds, status, attributes, statuses); return status; } } return processBulkOid(objectType, objectIds, SAI_COMMON_API_BULK_GET, attributes, strAttributes); } default: SWSS_LOG_THROW("common bulk api (%s) is not implemented in init view mode", sai_serialize_common_api(api).c_str()); } } sai_status_t Syncd::processBulkCreateEntry( _In_ sai_object_type_t objectType, _In_ const std::vector<std::string>& objectIds, _In_ const std::vector<std::shared_ptr<SaiAttributeList>>& attributes, _Out_ std::vector<sai_status_t>& statuses) { SWSS_LOG_ENTER(); sai_status_t status = SAI_STATUS_SUCCESS; uint32_t object_count = (uint32_t) objectIds.size(); if (!object_count) { SWSS_LOG_ERROR("container with objectIds is empty in processBulkCreateEntry"); return SAI_STATUS_FAILURE; } sai_bulk_op_error_mode_t mode = SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR; std::vector<uint32_t> attr_counts(object_count); std::vector<const sai_attribute_t*> attr_lists(object_count); for (uint32_t idx = 0; idx < object_count; idx++) { attr_counts[idx] = attributes[idx]->get_attr_count(); attr_lists[idx] = attributes[idx]->get_attr_list(); } switch ((int)objectType) { case SAI_OBJECT_TYPE_ROUTE_ENTRY: { std::vector<sai_route_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_route_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].vr_id = m_translator->translateVidToRid(entries[it].vr_id); } static PerformanceIntervalTimer timer("Syncd::processBulkCreateEntry(route_entry) CREATE"); timer.start(); status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); timer.stop(); timer.inc(object_count); } break; case SAI_OBJECT_TYPE_NEIGHBOR_ENTRY: { std::vector<sai_neighbor_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_neighbor_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].rif_id = m_translator->translateVidToRid(entries[it].rif_id); } status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_FDB_ENTRY: { std::vector<sai_fdb_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_fdb_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].bv_id = m_translator->translateVidToRid(entries[it].bv_id); } status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_NAT_ENTRY: { std::vector<sai_nat_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_nat_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].vr_id = m_translator->translateVidToRid(entries[it].vr_id); } status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_INSEG_ENTRY: { std::vector<sai_inseg_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_inseg_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); } status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_MY_SID_ENTRY: { std::vector<sai_my_sid_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_my_sid_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].vr_id = m_translator->translateVidToRid(entries[it].vr_id); } status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_DIRECTION_LOOKUP_ENTRY: { std::vector<sai_direction_lookup_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_direction_lookup_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); } status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_ENI_ETHER_ADDRESS_MAP_ENTRY: { std::vector<sai_eni_ether_address_map_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_eni_ether_address_map_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); } status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_VIP_ENTRY: { std::vector<sai_vip_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_vip_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); } status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_INBOUND_ROUTING_ENTRY: { std::vector<sai_inbound_routing_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_inbound_routing_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].eni_id = m_translator->translateVidToRid(entries[it].eni_id); } status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_PA_VALIDATION_ENTRY: { std::vector<sai_pa_validation_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_pa_validation_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].vnet_id = m_translator->translateVidToRid(entries[it].vnet_id); } status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_OUTBOUND_ROUTING_ENTRY: { std::vector<sai_outbound_routing_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_outbound_routing_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].outbound_routing_group_id = m_translator->translateVidToRid(entries[it].outbound_routing_group_id); } status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_OUTBOUND_CA_TO_PA_ENTRY: { std::vector<sai_outbound_ca_to_pa_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_outbound_ca_to_pa_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].dst_vnet_id = m_translator->translateVidToRid(entries[it].dst_vnet_id); } status = m_vendorSai->bulkCreate( object_count, entries.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); } break; default: return SAI_STATUS_NOT_SUPPORTED; } return status; } sai_status_t Syncd::processBulkRemoveEntry( _In_ sai_object_type_t objectType, _In_ const std::vector<std::string>& objectIds, _Out_ std::vector<sai_status_t>& statuses) { SWSS_LOG_ENTER(); sai_status_t status = SAI_STATUS_SUCCESS; uint32_t object_count = (uint32_t) objectIds.size(); if (!object_count) { SWSS_LOG_ERROR("container with objectIds is empty in processBulkRemoveEntry"); return SAI_STATUS_FAILURE; } sai_bulk_op_error_mode_t mode = SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR; switch ((int)objectType) { case SAI_OBJECT_TYPE_ROUTE_ENTRY: { std::vector<sai_route_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_route_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].vr_id = m_translator->translateVidToRid(entries[it].vr_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_NEIGHBOR_ENTRY: { std::vector<sai_neighbor_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_neighbor_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].rif_id = m_translator->translateVidToRid(entries[it].rif_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_FDB_ENTRY: { std::vector<sai_fdb_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_fdb_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].bv_id = m_translator->translateVidToRid(entries[it].bv_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_NAT_ENTRY: { std::vector<sai_nat_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_nat_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].vr_id = m_translator->translateVidToRid(entries[it].vr_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_MY_SID_ENTRY: { std::vector<sai_my_sid_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_my_sid_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].vr_id = m_translator->translateVidToRid(entries[it].vr_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_INSEG_ENTRY: { std::vector<sai_inseg_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_inseg_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_DIRECTION_LOOKUP_ENTRY: { std::vector<sai_direction_lookup_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_direction_lookup_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_ENI_ETHER_ADDRESS_MAP_ENTRY: { std::vector<sai_eni_ether_address_map_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_eni_ether_address_map_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_VIP_ENTRY: { std::vector<sai_vip_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_vip_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_INBOUND_ROUTING_ENTRY: { std::vector<sai_inbound_routing_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_inbound_routing_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].eni_id = m_translator->translateVidToRid(entries[it].eni_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_PA_VALIDATION_ENTRY: { std::vector<sai_pa_validation_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_pa_validation_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].vnet_id = m_translator->translateVidToRid(entries[it].vnet_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_OUTBOUND_ROUTING_ENTRY: { std::vector<sai_outbound_routing_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_outbound_routing_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].outbound_routing_group_id = m_translator->translateVidToRid(entries[it].outbound_routing_group_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_OUTBOUND_CA_TO_PA_ENTRY: { std::vector<sai_outbound_ca_to_pa_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_outbound_ca_to_pa_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].dst_vnet_id = m_translator->translateVidToRid(entries[it].dst_vnet_id); } status = m_vendorSai->bulkRemove( object_count, entries.data(), mode, statuses.data()); } break; default: return SAI_STATUS_NOT_SUPPORTED; } return status; } sai_status_t Syncd::processBulkSetEntry( _In_ sai_object_type_t objectType, _In_ const std::vector<std::string>& objectIds, _In_ const std::vector<std::shared_ptr<SaiAttributeList>>& attributes, _Out_ std::vector<sai_status_t>& statuses) { SWSS_LOG_ENTER(); sai_status_t status = SAI_STATUS_SUCCESS; std::vector<sai_attribute_t> attr_lists; uint32_t object_count = (uint32_t) objectIds.size(); if (!object_count) { SWSS_LOG_ERROR("container with objectIds is empty in processBulkSetEntry"); return SAI_STATUS_FAILURE; } sai_bulk_op_error_mode_t mode = SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR; for (uint32_t it = 0; it < object_count; it++) { attr_lists.push_back(attributes[it]->get_attr_list()[0]); } switch ((int)objectType) { case SAI_OBJECT_TYPE_ROUTE_ENTRY: { std::vector<sai_route_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_route_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].vr_id = m_translator->translateVidToRid(entries[it].vr_id); } status = m_vendorSai->bulkSet( object_count, entries.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_NEIGHBOR_ENTRY: { std::vector<sai_neighbor_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_neighbor_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].rif_id = m_translator->translateVidToRid(entries[it].rif_id); } status = m_vendorSai->bulkSet( object_count, entries.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_FDB_ENTRY: { std::vector<sai_fdb_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_fdb_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].bv_id = m_translator->translateVidToRid(entries[it].bv_id); } status = m_vendorSai->bulkSet( object_count, entries.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_NAT_ENTRY: { std::vector<sai_nat_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_nat_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].vr_id = m_translator->translateVidToRid(entries[it].vr_id); } status = m_vendorSai->bulkSet( object_count, entries.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_MY_SID_ENTRY: { std::vector<sai_my_sid_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_my_sid_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); entries[it].vr_id = m_translator->translateVidToRid(entries[it].vr_id); } status = m_vendorSai->bulkSet( object_count, entries.data(), attr_lists.data(), mode, statuses.data()); } break; case SAI_OBJECT_TYPE_INSEG_ENTRY: { std::vector<sai_inseg_entry_t> entries(object_count); for (uint32_t it = 0; it < object_count; it++) { sai_deserialize_inseg_entry(objectIds[it], entries[it]); entries[it].switch_id = m_translator->translateVidToRid(entries[it].switch_id); } status = m_vendorSai->bulkSet( object_count, entries.data(), attr_lists.data(), mode, statuses.data()); } break; default: return SAI_STATUS_NOT_SUPPORTED; } return status; } sai_status_t Syncd::processBulkEntry( _In_ sai_object_type_t objectType, _In_ const std::vector<std::string>& objectIds, _In_ sai_common_api_t api, _In_ const std::vector<std::shared_ptr<SaiAttributeList>>& attributes, _In_ const std::vector<std::vector<swss::FieldValueTuple>>& strAttributes) { SWSS_LOG_ENTER(); auto info = sai_metadata_get_object_type_info(objectType); if (info->isobjectid) { SWSS_LOG_THROW("passing oid object to bulk non object id operation"); } std::vector<sai_status_t> statuses(objectIds.size()); sai_status_t all = SAI_STATUS_SUCCESS; if (m_commandLineOptions->m_enableSaiBulkSupport) { switch (api) { case SAI_COMMON_API_BULK_CREATE: all = processBulkCreateEntry(objectType, objectIds, attributes, statuses); break; case SAI_COMMON_API_BULK_REMOVE: all = processBulkRemoveEntry(objectType, objectIds, statuses); break; case SAI_COMMON_API_BULK_SET: all = processBulkSetEntry(objectType, objectIds, attributes, statuses); break; default: SWSS_LOG_ERROR("api %s is not supported in bulk", sai_serialize_common_api(api).c_str()); all = SAI_STATUS_NOT_SUPPORTED; } if (all != SAI_STATUS_NOT_SUPPORTED && all != SAI_STATUS_NOT_IMPLEMENTED) { sendApiResponse(api, all, (uint32_t)objectIds.size(), statuses.data()); syncUpdateRedisBulkQuadEvent(api, statuses, objectType, objectIds, strAttributes); return all; } } // vendor SAI don't bulk API yet, so execute one by one all = SAI_STATUS_SUCCESS; for (size_t idx = 0; idx < objectIds.size(); ++idx) { sai_object_meta_key_t metaKey; metaKey.objecttype = objectType; switch ((int)objectType) { case SAI_OBJECT_TYPE_ROUTE_ENTRY: sai_deserialize_route_entry(objectIds[idx], metaKey.objectkey.key.route_entry); break; case SAI_OBJECT_TYPE_NEIGHBOR_ENTRY: sai_deserialize_neighbor_entry(objectIds[idx], metaKey.objectkey.key.neighbor_entry); break; case SAI_OBJECT_TYPE_NAT_ENTRY: sai_deserialize_nat_entry(objectIds[idx], metaKey.objectkey.key.nat_entry); break; case SAI_OBJECT_TYPE_FDB_ENTRY: sai_deserialize_fdb_entry(objectIds[idx], metaKey.objectkey.key.fdb_entry); break; case SAI_OBJECT_TYPE_INSEG_ENTRY: sai_deserialize_inseg_entry(objectIds[idx], metaKey.objectkey.key.inseg_entry); break; case SAI_OBJECT_TYPE_DIRECTION_LOOKUP_ENTRY: sai_deserialize_direction_lookup_entry(objectIds[idx], metaKey.objectkey.key.direction_lookup_entry); break; case SAI_OBJECT_TYPE_ENI_ETHER_ADDRESS_MAP_ENTRY: sai_deserialize_eni_ether_address_map_entry(objectIds[idx], metaKey.objectkey.key.eni_ether_address_map_entry); break; case SAI_OBJECT_TYPE_VIP_ENTRY: sai_deserialize_vip_entry(objectIds[idx], metaKey.objectkey.key.vip_entry); break; case SAI_OBJECT_TYPE_INBOUND_ROUTING_ENTRY: sai_deserialize_inbound_routing_entry(objectIds[idx], metaKey.objectkey.key.inbound_routing_entry); break; case SAI_OBJECT_TYPE_PA_VALIDATION_ENTRY: sai_deserialize_pa_validation_entry(objectIds[idx], metaKey.objectkey.key.pa_validation_entry); break; case SAI_OBJECT_TYPE_OUTBOUND_ROUTING_ENTRY: sai_deserialize_outbound_routing_entry(objectIds[idx], metaKey.objectkey.key.outbound_routing_entry); break; case SAI_OBJECT_TYPE_OUTBOUND_CA_TO_PA_ENTRY: sai_deserialize_outbound_ca_to_pa_entry(objectIds[idx], metaKey.objectkey.key.outbound_ca_to_pa_entry); break; default: SWSS_LOG_THROW("object %s not implemented, FIXME", sai_serialize_object_type(objectType).c_str()); } sai_status_t status = SAI_STATUS_FAILURE; auto& list = attributes[idx]; sai_attribute_t *attr_list = list->get_attr_list(); uint32_t attr_count = list->get_attr_count(); if (api == SAI_COMMON_API_BULK_CREATE) { if (objectType == SAI_OBJECT_TYPE_ROUTE_ENTRY) { static PerformanceIntervalTimer timer("Syncd::processBulkEntry::processEntry(route_entry) CREATE"); timer.start(); status = processEntry(metaKey, SAI_COMMON_API_CREATE, attr_count, attr_list); timer.stop(); timer.inc(); } else { status = processEntry(metaKey, SAI_COMMON_API_CREATE, attr_count, attr_list); } } else if (api == SAI_COMMON_API_BULK_REMOVE) { status = processEntry(metaKey, SAI_COMMON_API_REMOVE, attr_count, attr_list); } else if (api == SAI_COMMON_API_BULK_SET) { status = processEntry(metaKey, SAI_COMMON_API_SET, attr_count, attr_list); } else { SWSS_LOG_THROW("api %d is not supported in bulk mode", api); } if (api != SAI_COMMON_API_BULK_GET && status != SAI_STATUS_SUCCESS) { if (!m_enableSyncMode) { SWSS_LOG_THROW("operation %s for %s failed in async mode!", sai_serialize_common_api(api).c_str(), sai_serialize_object_type(objectType).c_str()); } all = SAI_STATUS_FAILURE; // all can be success if all has been success } statuses[idx] = status; } sendApiResponse(api, all, (uint32_t)objectIds.size(), statuses.data()); syncUpdateRedisBulkQuadEvent(api, statuses, objectType, objectIds, strAttributes); return all; } sai_status_t Syncd::processEntry( _In_ sai_object_meta_key_t metaKey, _In_ sai_common_api_t api, _In_ uint32_t attr_count, _In_ sai_attribute_t *attr_list) { SWSS_LOG_ENTER(); m_translator->translateVidToRid(metaKey); switch (api) { case SAI_COMMON_API_CREATE: return m_vendorSai->create(metaKey, SAI_NULL_OBJECT_ID, attr_count, attr_list); case SAI_COMMON_API_REMOVE: return m_vendorSai->remove(metaKey); case SAI_COMMON_API_SET: return m_vendorSai->set(metaKey, attr_list); case SAI_COMMON_API_GET: return m_vendorSai->get(metaKey, attr_count, attr_list); default: SWSS_LOG_THROW("api %s not supported", sai_serialize_common_api(api).c_str()); } } sai_status_t Syncd::processBulkOidCreate( _In_ sai_object_type_t objectType, _In_ sai_bulk_op_error_mode_t mode, _In_ const std::vector<std::string>& objectIds, _In_ const std::vector<std::shared_ptr<saimeta::SaiAttributeList>>& attributes, _Out_ std::vector<sai_status_t>& statuses) { SWSS_LOG_ENTER(); sai_status_t status = SAI_STATUS_SUCCESS; uint32_t object_count = (uint32_t)objectIds.size(); if (!object_count) { SWSS_LOG_ERROR("container with objectIds is empty in processBulkOidCreate"); return SAI_STATUS_FAILURE; } std::vector<sai_object_id_t> objectVids(object_count); std::vector<uint32_t> attr_counts(object_count); std::vector<const sai_attribute_t*> attr_lists(object_count); for (size_t idx = 0; idx < object_count; idx++) { sai_deserialize_object_id(objectIds[idx], objectVids[idx]); attr_counts[idx] = attributes[idx]->get_attr_count(); attr_lists[idx] = attributes[idx]->get_attr_list(); } sai_object_id_t switchRid = SAI_NULL_OBJECT_ID; sai_object_id_t switchVid = VidManager::switchIdQuery(objectVids.front()); switchRid = m_translator->translateVidToRid(switchVid); std::vector<sai_object_id_t> objectRids(object_count); status = m_vendorSai->bulkCreate( objectType, switchRid, object_count, attr_counts.data(), attr_lists.data(), mode, objectRids.data(), statuses.data()); if (status == SAI_STATUS_NOT_IMPLEMENTED || status == SAI_STATUS_NOT_SUPPORTED) { SWSS_LOG_WARN("bulkCreate api is not implemented or not supported, object_type = %s", sai_serialize_object_type(objectType).c_str()); return status; } /* * Object was created so new object id was generated we need to save * virtual id's to redis db. */ for (size_t idx = 0; idx < object_count; idx++) { if (statuses[idx] == SAI_STATUS_SUCCESS) { m_translator->insertRidAndVid(objectRids[idx], objectVids[idx]); SWSS_LOG_INFO("saved VID %s to RID %s", sai_serialize_object_id(objectVids[idx]).c_str(), sai_serialize_object_id(objectRids[idx]).c_str()); if (objectType == SAI_OBJECT_TYPE_PORT) { m_switches.at(switchVid)->onPostPortCreate(objectRids[idx], objectVids[idx]); } } } return status; } sai_status_t Syncd::processBulkOidSet( _In_ sai_object_type_t objectType, _In_ sai_bulk_op_error_mode_t mode, _In_ const std::vector<std::string>& objectIds, _In_ const std::vector<std::shared_ptr<saimeta::SaiAttributeList>>& attributes, _Out_ std::vector<sai_status_t>& statuses) { SWSS_LOG_ENTER(); sai_status_t status = SAI_STATUS_SUCCESS; uint32_t object_count = static_cast<uint32_t>(objectIds.size()); if (!object_count) { SWSS_LOG_ERROR("container with objectIds is empty in processBulkOidSet"); return SAI_STATUS_FAILURE; } std::vector<sai_object_id_t> objectVids(object_count); std::vector<sai_object_id_t> objectRids(object_count); std::vector<sai_attribute_t> attr_list(object_count); for (size_t idx = 0; idx < object_count; idx++) { sai_deserialize_object_id(objectIds[idx], objectVids[idx]); objectRids[idx] = m_translator->translateVidToRid(objectVids[idx]); const auto attr_count = attributes[idx]->get_attr_count(); if (attr_count != 1) { SWSS_LOG_THROW("bulkSet api requires one attribute per object"); } attr_list[idx] = *attributes[idx]->get_attr_list(); } status = m_vendorSai->bulkSet( objectType, object_count, objectRids.data(), attr_list.data(), mode, statuses.data()); if (status == SAI_STATUS_NOT_IMPLEMENTED || status == SAI_STATUS_NOT_SUPPORTED) { SWSS_LOG_WARN("bulkSet api is not implemented or not supported, object_type = %s", sai_serialize_object_type(objectType).c_str()); } return status; } sai_status_t Syncd::processBulkOidGet( _In_ sai_object_type_t objectType, _In_ sai_bulk_op_error_mode_t mode, _In_ const std::vector<std::string>& objectIds, _In_ const std::vector<std::shared_ptr<saimeta::SaiAttributeList>>& attributes, _Out_ std::vector<sai_status_t>& statuses) { SWSS_LOG_ENTER(); const auto object_count = static_cast<uint32_t>(objectIds.size()); if (!object_count) { SWSS_LOG_ERROR("container with objectIds is empty in processBulkOidGet"); return SAI_STATUS_FAILURE; } std::vector<sai_object_id_t> objectVids(object_count); std::vector<sai_object_id_t> objectRids(object_count); std::vector<uint32_t> attr_counts(object_count); std::vector<sai_attribute_t*> attr_lists(object_count); for (size_t idx = 0; idx < object_count; idx++) { sai_deserialize_object_id(objectIds[idx], objectVids[idx]); objectRids[idx] = m_translator->translateVidToRid(objectVids[idx]); attr_counts[idx] = attributes[idx]->get_attr_count(); attr_lists[idx] = attributes[idx]->get_attr_list(); } const auto status = m_vendorSai->bulkGet(objectType, object_count, objectRids.data(), attr_counts.data(), attr_lists.data(), mode, statuses.data()); if (status == SAI_STATUS_NOT_IMPLEMENTED || status == SAI_STATUS_NOT_SUPPORTED) { SWSS_LOG_WARN("bulkGet api is not implemented or not supported, object_type = %s", sai_serialize_object_type(objectType).c_str()); return status; } return status; } sai_status_t Syncd::processBulkOidRemove( _In_ sai_object_type_t objectType, _In_ sai_bulk_op_error_mode_t mode, _In_ const std::vector<std::string>& objectIds, _Out_ std::vector<sai_status_t>& statuses) { SWSS_LOG_ENTER(); sai_status_t status = SAI_STATUS_SUCCESS; uint32_t object_count = (uint32_t)objectIds.size(); if (!object_count) { SWSS_LOG_ERROR("container with objectIds is empty in processBulkOidRemove"); return SAI_STATUS_FAILURE; } std::vector<sai_object_id_t> objectVids(object_count); std::vector<sai_object_id_t> objectRids(object_count); for (size_t idx = 0; idx < object_count; idx++) { sai_deserialize_object_id(objectIds[idx], objectVids[idx]); objectRids[idx] = m_translator->translateVidToRid(objectVids[idx]); if (objectType == SAI_OBJECT_TYPE_PORT) { sai_object_id_t switchVid = VidManager::switchIdQuery(objectVids[idx]); m_switches.at(switchVid)->collectPortRelatedObjects(objectRids[idx]); } } status = m_vendorSai->bulkRemove( objectType, (uint32_t)object_count, objectRids.data(), mode, statuses.data()); if (status == SAI_STATUS_NOT_IMPLEMENTED || status == SAI_STATUS_NOT_SUPPORTED) { SWSS_LOG_WARN("bulkRemove api is not implemented or not supported, object_type = %s", sai_serialize_object_type(objectType).c_str()); return status; } /* * remove all related objects from REDIS DB and also from existing * object references since at this point they are no longer valid */ sai_object_id_t switchVid; for (size_t idx = 0; idx < object_count; idx++) { if (statuses[idx] == SAI_STATUS_SUCCESS) { m_translator->eraseRidAndVid(objectRids[idx], objectVids[idx]); switchVid = VidManager::switchIdQuery(objectVids[idx]); if (m_switches.at(switchVid)->isDiscoveredRid(objectRids[idx])) { m_switches.at(switchVid)->removeExistingObjectReference(objectRids[idx]); } if (objectType == SAI_OBJECT_TYPE_PORT) { m_switches.at(switchVid)->postPortRemove(objectRids[idx]); } } } return status; } sai_status_t Syncd::processBulkOid( _In_ sai_object_type_t objectType, _In_ const std::vector<std::string>& objectIds, _In_ sai_common_api_t api, _In_ const std::vector<std::shared_ptr<SaiAttributeList>>& attributes, _In_ const std::vector<std::vector<swss::FieldValueTuple>>& strAttributes) { SWSS_LOG_ENTER(); auto info = sai_metadata_get_object_type_info(objectType); if (info->isnonobjectid) { SWSS_LOG_THROW("passing non object id to bulk oid object operation"); } std::vector<sai_status_t> statuses(objectIds.size()); sai_status_t all = SAI_STATUS_SUCCESS; if (m_commandLineOptions->m_enableSaiBulkSupport) { sai_bulk_op_error_mode_t mode = SAI_BULK_OP_ERROR_MODE_IGNORE_ERROR; switch (api) { case SAI_COMMON_API_BULK_CREATE: all = processBulkOidCreate(objectType, mode, objectIds, attributes, statuses); break; case SAI_COMMON_API_BULK_SET: all = processBulkOidSet(objectType, mode, objectIds, attributes, statuses); break; case SAI_COMMON_API_BULK_GET: all = processBulkOidGet(objectType, mode, objectIds, attributes, statuses); break; case SAI_COMMON_API_BULK_REMOVE: all = processBulkOidRemove(objectType, mode, objectIds, statuses); break; default: all = SAI_STATUS_NOT_SUPPORTED; SWSS_LOG_ERROR("api %s is not supported in bulk mode", sai_serialize_common_api(api).c_str()); } if (all != SAI_STATUS_NOT_SUPPORTED && all != SAI_STATUS_NOT_IMPLEMENTED) { switch (api) { case SAI_COMMON_API_BULK_GET: sendBulkGetResponse(objectType, objectIds, all, attributes, statuses); break; default: sendApiResponse(api, all, (uint32_t)objectIds.size(), statuses.data()); break; } syncUpdateRedisBulkQuadEvent(api, statuses, objectType, objectIds, strAttributes); return all; } } // vendor SAI don't bulk API yet, so execute one by one all = SAI_STATUS_SUCCESS; for (size_t idx = 0; idx < objectIds.size(); ++idx) { sai_status_t status = SAI_STATUS_FAILURE; auto& list = attributes[idx]; sai_attribute_t *attr_list = list->get_attr_list(); uint32_t attr_count = list->get_attr_count(); if (api == SAI_COMMON_API_BULK_CREATE) { status = processOid(objectType, objectIds[idx], SAI_COMMON_API_CREATE, attr_count, attr_list); } else if (api == SAI_COMMON_API_BULK_REMOVE) { status = processOid(objectType, objectIds[idx], SAI_COMMON_API_REMOVE, attr_count, attr_list); } else if (api == SAI_COMMON_API_BULK_SET) { status = processOid(objectType, objectIds[idx], SAI_COMMON_API_SET, attr_count, attr_list); } else if (api == SAI_COMMON_API_BULK_GET) { status = processOid(objectType, objectIds[idx], SAI_COMMON_API_GET, attr_count, attr_list); } else { SWSS_LOG_THROW("api %s is not supported in bulk mode", sai_serialize_common_api(api).c_str()); } if (status != SAI_STATUS_SUCCESS) { if (!m_enableSyncMode) { SWSS_LOG_THROW("operation %s for %s failed in async mode!", sai_serialize_common_api(api).c_str(), sai_serialize_object_type(objectType).c_str()); } all = SAI_STATUS_FAILURE; // all can be success if all has been success } statuses[idx] = status; } switch (api) { case SAI_COMMON_API_BULK_GET: sendBulkGetResponse(objectType, objectIds, all, attributes, statuses); break; default: sendApiResponse(api, all, (uint32_t)objectIds.size(), statuses.data()); break; } syncUpdateRedisBulkQuadEvent(api, statuses, objectType, objectIds, strAttributes); return all; } sai_status_t Syncd::processQuadEventInInitViewMode( _In_ sai_object_type_t objectType, _In_ const std::string& strObjectId, _In_ sai_common_api_t api, _In_ uint32_t attr_count, _In_ sai_attribute_t *attr_list) { SWSS_LOG_ENTER(); /* * Since attributes are not checked, it may happen that user will send some * invalid VID in object id/list in attribute, metadata should handle that, * but if that happen, this id will be treated as "new" object instead of * existing one. */ switch (api) { case SAI_COMMON_API_CREATE: return processQuadInInitViewModeCreate(objectType, strObjectId, attr_count, attr_list); case SAI_COMMON_API_REMOVE: return processQuadInInitViewModeRemove(objectType, strObjectId); case SAI_COMMON_API_SET: return processQuadInInitViewModeSet(objectType, strObjectId, attr_list); case SAI_COMMON_API_GET: return processQuadInInitViewModeGet(objectType, strObjectId, attr_count, attr_list); default: SWSS_LOG_THROW("common api (%s) is not implemented in init view mode", sai_serialize_common_api(api).c_str()); } } sai_status_t Syncd::processQuadInInitViewModeCreate( _In_ sai_object_type_t objectType, _In_ const std::string& strObjectId, _In_ uint32_t attr_count, _In_ sai_attribute_t *attr_list) { SWSS_LOG_ENTER(); if (objectType == SAI_OBJECT_TYPE_PORT) { /* * Reason for this is that if user will create port, new port is not * actually created so when for example querying new queues for new * created port, there are not there, since no actual port create was * issued on the ASIC. */ SWSS_LOG_THROW("port object can't be created in init view mode"); } auto info = sai_metadata_get_object_type_info(objectType); // we assume create of those non object id object types will succeed if (info->isobjectid) { sai_object_id_t objectVid; sai_deserialize_object_id(strObjectId, objectVid); /* * Object ID here is actual VID returned from redis during * creation this is floating VID in init view mode. */ SWSS_LOG_DEBUG("generic create (init view) for %s, floating VID: %s", sai_serialize_object_type(objectType).c_str(), sai_serialize_object_id(objectVid).c_str()); if (objectType == SAI_OBJECT_TYPE_SWITCH) { onSwitchCreateInInitViewMode(objectVid, attr_count, attr_list); } else { // in init view mode insert every created object except switch m_createdInInitView.insert(objectVid); } } sendApiResponse(SAI_COMMON_API_CREATE, SAI_STATUS_SUCCESS); return SAI_STATUS_SUCCESS; } sai_status_t Syncd::processQuadInInitViewModeRemove( _In_ sai_object_type_t objectType, _In_ const std::string& strObjectId) { SWSS_LOG_ENTER(); if (objectType == SAI_OBJECT_TYPE_PORT) { /* * Reason for this is that if user will remove port, actual resources * for it won't be released, lanes would be still occupied and there is * extra logic required in post port remove which clears OIDs * (ipgs,queues,SGs) from redis db that are automatically removed by * vendor SAI, and comparison logic don't support that. */ SWSS_LOG_THROW("port object (%s) can't be removed in init view mode", strObjectId.c_str()); } if (objectType == SAI_OBJECT_TYPE_SWITCH) { /* * NOTE: Special care needs to be taken to clear all this switch id's * from all db's currently we skip this since we assume that orchagent * will not be removing switches, just creating. But it may happen * when asic will fail etc. * * To support multiple switches this case must be refactored. */ SWSS_LOG_THROW("remove switch (%s) is not supported in init view mode yet! FIXME", strObjectId.c_str()); } // NOTE: we should also prevent removing some other non removable objects auto info = sai_metadata_get_object_type_info(objectType); if (info->isobjectid) { /* * If object is existing object (like bridge port, vlan member) user * may want to remove them, but this is temporary view, and when we * receive apply view, we will populate existing objects to temporary * view (since not all of them user may query) and this will produce * conflict, since some of those objects user could explicitly remove. * So to solve that we need to have a list of removed objects, and then * only populate objects which not exist on removed list. */ sai_object_id_t objectVid; sai_deserialize_object_id(strObjectId, objectVid); // this set may contain removed objects from multiple switches m_initViewRemovedVidSet.insert(objectVid); } sendApiResponse(SAI_COMMON_API_REMOVE, SAI_STATUS_SUCCESS); return SAI_STATUS_SUCCESS; } sai_status_t Syncd::processQuadInInitViewModeSet( _In_ sai_object_type_t objectType, _In_ const std::string& strObjectId, _In_ sai_attribute_t *attr) { SWSS_LOG_ENTER(); // we support SET api on all objects in init view mode sendApiResponse(SAI_COMMON_API_SET, SAI_STATUS_SUCCESS); return SAI_STATUS_SUCCESS; } sai_status_t Syncd::processQuadInInitViewModeGet( _In_ sai_object_type_t objectType, _In_ const std::string& strObjectId, _In_ uint32_t attr_count, _In_ sai_attribute_t *attr_list) { SWSS_LOG_ENTER(); sai_status_t status; auto info = sai_metadata_get_object_type_info(objectType); sai_object_id_t switchVid = SAI_NULL_OBJECT_ID; if (info->isnonobjectid) { /* * Those objects are user created, so if user created ROUTE he * passed some attributes, there is no sense to support GET * since user explicitly know what attributes were set, similar * for other non object id types. */ SWSS_LOG_ERROR("get is not supported on %s in init view mode", sai_serialize_object_type(objectType).c_str()); status = SAI_STATUS_NOT_SUPPORTED; } else { sai_object_id_t objectVid; sai_deserialize_object_id(strObjectId, objectVid); if (isInitViewMode() && m_createdInInitView.find(objectVid) != m_createdInInitView.end()) { SWSS_LOG_WARN("GET api can't be used on %s (%s) since it's created in INIT_VIEW mode", strObjectId.c_str(), sai_serialize_object_type(objectType).c_str()); status = SAI_STATUS_INVALID_OBJECT_ID; sendGetResponse(objectType, strObjectId, switchVid, status, attr_count, attr_list); return status; } switchVid = VidManager::switchIdQuery(objectVid); SWSS_LOG_DEBUG("generic get (init view) for object type %s:%s", sai_serialize_object_type(objectType).c_str(), strObjectId.c_str()); /* * Object must exists, we can't call GET on created object * in init view mode, get here can be called on existing * objects like default trap group to get some vendor * specific values. * * Exception here is switch, since all switches must be * created, when user will create switch on init view mode, * switch will be matched with existing switch, or it will * be explicitly created so user can query it properties. * * Translate vid to rid will make sure that object exist * and it have RID defined, so we can query it. */ sai_object_id_t rid = m_translator->translateVidToRid(objectVid); sai_object_meta_key_t metaKey; metaKey.objecttype = objectType; metaKey.objectkey.key.object_id = rid; status = m_vendorSai->get(metaKey, attr_count, attr_list); } /* * We are in init view mode, but ether switch already existed or first * command was creating switch and user created switch. * * We could change that later on, depends on object type we can extract * switch id, we could also have this method inside metadata to get meta * key. */ sendGetResponse(objectType, strObjectId, switchVid, status, attr_count, attr_list); return status; } void Syncd::sendApiResponse( _In_ sai_common_api_t api, _In_ sai_status_t status, _In_ uint32_t object_count, _In_ sai_status_t* object_statuses) { SWSS_LOG_ENTER(); /* * By default synchronous mode is disabled and can be enabled by command * line on syncd start. This will also require to enable synchronous mode * in OA/sairedis because same GET RESPONSE channel is used to generate * response for sairedis quad API. */ if (!m_enableSyncMode) { return; } switch (api) { case SAI_COMMON_API_CREATE: case SAI_COMMON_API_REMOVE: case SAI_COMMON_API_SET: case SAI_COMMON_API_BULK_CREATE: case SAI_COMMON_API_BULK_REMOVE: case SAI_COMMON_API_BULK_SET: break; default: SWSS_LOG_THROW("api %s not supported by this function", sai_serialize_common_api(api).c_str()); } if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("api %s failed in syncd mode: %s", sai_serialize_common_api(api).c_str(), sai_serialize_status(status).c_str()); } std::vector<swss::FieldValueTuple> entry; for (uint32_t idx = 0; idx < object_count; idx++) { swss::FieldValueTuple fvt(sai_serialize_status(object_statuses[idx]), ""); entry.push_back(fvt); } std::string strStatus = sai_serialize_status(status); SWSS_LOG_INFO("sending response for %s api with status: %s", sai_serialize_common_api(api).c_str(), strStatus.c_str()); m_selectableChannel->set(strStatus, entry, REDIS_ASIC_STATE_COMMAND_GETRESPONSE); SWSS_LOG_INFO("response for %s api was send", sai_serialize_common_api(api).c_str()); } void Syncd::processFlexCounterGroupEvent( // TODO must be moved to go via ASIC channel queue _In_ swss::ConsumerTable& consumer) { SWSS_LOG_ENTER(); std::lock_guard<std::mutex> lock(m_mutex); swss::KeyOpFieldsValuesTuple kco; consumer.pop(kco); auto& groupName = kfvKey(kco); auto& op = kfvOp(kco); auto& values = kfvFieldsValues(kco); WatchdogScope ws(m_timerWatchdog, op + ":" + groupName, &kco); processFlexCounterGroupEvent(groupName, op, values, false); } sai_status_t Syncd::processFlexCounterGroupEvent( _In_ const std::string &groupName, _In_ const std::string &op, _In_ const std::vector<swss::FieldValueTuple> &values, _In_ bool fromAsicChannel) { SWSS_LOG_ENTER(); if (op == SET_COMMAND) { m_manager->addCounterPlugin(groupName, values); if (fromAsicChannel) { m_flexCounterGroupTable->set(groupName, values); } } else if (op == DEL_COMMAND) { if (fromAsicChannel) { m_flexCounterGroupTable->del(groupName); } m_manager->removeCounterPlugins(groupName); } else { SWSS_LOG_ERROR("unknown command: %s", op.c_str()); } if (fromAsicChannel) { sendApiResponse(SAI_COMMON_API_SET, SAI_STATUS_SUCCESS); } return SAI_STATUS_SUCCESS; } void Syncd::processFlexCounterEvent( // TODO must be moved to go via ASIC channel queue _In_ swss::ConsumerTable& consumer) { SWSS_LOG_ENTER(); std::lock_guard<std::mutex> lock(m_mutex); swss::KeyOpFieldsValuesTuple kco; consumer.pop(kco); auto& key = kfvKey(kco); auto& op = kfvOp(kco); auto& values = kfvFieldsValues(kco); WatchdogScope ws(m_timerWatchdog, op + ":" + key, &kco); processFlexCounterEvent(key, op, values, false); } sai_status_t Syncd::processFlexCounterEvent( _In_ const std::string &key, _In_ const std::string &op, _In_ const std::vector<swss::FieldValueTuple> &values, _In_ bool fromAsicChannel) { SWSS_LOG_ENTER(); auto delimiter = key.find_first_of(":"); if (delimiter == std::string::npos) { SWSS_LOG_ERROR("Failed to parse the key %s", key.c_str()); if (fromAsicChannel) { sendApiResponse(SAI_COMMON_API_SET, SAI_STATUS_FAILURE); } return SAI_STATUS_FAILURE; // if key is invalid there is no need to process this event again } auto groupName = key.substr(0, delimiter); auto strVids = key.substr(delimiter + 1); auto vidStringVector = swss::tokenize(strVids, ','); if (fromAsicChannel && op == SET_COMMAND && (!vidStringVector.empty())) { std::vector<sai_object_id_t> vids; std::vector<sai_object_id_t> rids; std::vector<std::string> keys; vids.reserve(vidStringVector.size()); rids.reserve(vidStringVector.size()); keys.reserve(vidStringVector.size()); for (auto &strVid: vidStringVector) { sai_object_id_t vid, rid; sai_deserialize_object_id(strVid, vid); vids.emplace_back(vid); if (!m_translator->tryTranslateVidToRid(vid, rid)) { SWSS_LOG_ERROR("port VID %s, was not found (probably port was removed/splitted) and will remove from counters now", sai_serialize_object_id(vid).c_str()); } rids.emplace_back(rid); keys.emplace_back(groupName + ":" + strVid); } m_manager->bulkAddCounter(vids, rids, groupName, values); for (auto &singleKey: keys) { m_flexCounterTable->set(singleKey, values); } if (fromAsicChannel) { sendApiResponse(SAI_COMMON_API_SET, SAI_STATUS_SUCCESS); } return SAI_STATUS_SUCCESS; } for(auto &strVid : vidStringVector) { auto effective_op = op; auto singleKey = groupName + ":" + strVid; sai_object_id_t vid; sai_deserialize_object_id(strVid, vid); sai_object_id_t rid; if (!m_translator->tryTranslateVidToRid(vid, rid)) { if (fromAsicChannel) { SWSS_LOG_ERROR("port VID %s, was not found (probably port was removed/splitted) and will remove from counters now", sai_serialize_object_id(vid).c_str()); } else { SWSS_LOG_WARN("port VID %s, was not found (probably port was removed/splitted) and will remove from counters now", sai_serialize_object_id(vid).c_str()); } effective_op = DEL_COMMAND; } if (effective_op == SET_COMMAND) { m_manager->addCounter(vid, rid, groupName, values); if (fromAsicChannel) { m_flexCounterTable->set(singleKey, values); } } else if (effective_op == DEL_COMMAND) { if (fromAsicChannel) { m_flexCounterTable->del(singleKey); } m_manager->removeCounter(vid, groupName); } else { SWSS_LOG_ERROR("unknown command: %s", op.c_str()); } } if (fromAsicChannel) { sendApiResponse(SAI_COMMON_API_SET, SAI_STATUS_SUCCESS); } return SAI_STATUS_SUCCESS; } void Syncd::syncUpdateRedisQuadEvent( _In_ sai_status_t status, _In_ sai_common_api_t api, _In_ const swss::KeyOpFieldsValuesTuple &kco) { SWSS_LOG_ENTER(); if (!m_enableSyncMode) { return; } if (status != SAI_STATUS_SUCCESS) { return; } // When in synchronous mode, we need to modify redis database when status // is success, since consumer table on synchronous mode is not making redis // changes and we only want to apply changes when api succeeded. This // applies to init view mode and apply view mode. const std::string& key = kfvKey(kco); auto& values = kfvFieldsValues(kco); sai_object_meta_key_t metaKey; sai_deserialize_object_meta_key(key, metaKey); const bool initView = isInitViewMode(); static PerformanceIntervalTimer timer("Syncd::syncUpdateRedisQuadEvent"); timer.start(); switch (api) { case SAI_COMMON_API_CREATE: { if (initView) m_client->createTempAsicObject(metaKey, values); else m_client->createAsicObject(metaKey, values); break; } case SAI_COMMON_API_REMOVE: { if (initView) m_client->removeTempAsicObject(metaKey); else m_client->removeAsicObject(metaKey); break; } case SAI_COMMON_API_SET: { auto& first = values.at(0); auto& attr = fvField(first); auto& value = fvValue(first); if (initView) m_client->setTempAsicObject(metaKey, attr, value); else m_client->setAsicObject(metaKey, attr, value); break; } case SAI_COMMON_API_GET: break; // ignore get since get is not modifying db default: SWSS_LOG_THROW("api %d is not supported", api); } timer.stop(); timer.inc(); } void Syncd::syncUpdateRedisBulkQuadEvent( _In_ sai_common_api_t api, _In_ const std::vector<sai_status_t>& statuses, _In_ sai_object_type_t objectType, _In_ const std::vector<std::string>& objectIds, _In_ const std::vector<std::vector<swss::FieldValueTuple>>& strAttributes) { SWSS_LOG_ENTER(); if (!m_enableSyncMode) { return; } // When in synchronous mode, we need to modify redis database when status // is success, since consumer table on synchronous mode is not making redis // changes and we only want to apply changes when api succeeded. This // applies to init view mode and apply view mode. static PerformanceIntervalTimer timer("Syncd::syncUpdateRedisBulkQuadEvent"); timer.start(); const std::string strObjectType = sai_serialize_object_type(objectType); std::unordered_map<std::string, std::vector<swss::FieldValueTuple>> multiHash; std::vector<std::string> keys; for (size_t idx = 0; idx < statuses.size(); idx++) { sai_status_t status = statuses[idx]; if (status != SAI_STATUS_SUCCESS) { // in case of failure, don't modify database continue; } auto key = strObjectType + ":" + objectIds.at(idx); keys.push_back(key); if (api == SAI_COMMON_API_BULK_SET) { // in case of bulk set operation, it can happen that multiple // attributes will be set for the same key, then when we want to // push them to redis database, we need to combine all attributes // to a single vector of attributes multiHash[key].push_back(strAttributes.at(idx).at(0)); } else { multiHash[key] = strAttributes.at(idx); } } const bool initView = isInitViewMode(); switch (api) { case SAI_COMMON_API_BULK_CREATE: { if (initView) m_client->createTempAsicObjects(multiHash); else m_client->createAsicObjects(multiHash); break; } case SAI_COMMON_API_BULK_REMOVE: { if (initView) m_client->removeTempAsicObjects(keys); else m_client->removeAsicObjects(keys); break; } case SAI_COMMON_API_BULK_SET: { // SET is the same as create if (initView) m_client->createTempAsicObjects(multiHash); else m_client->createAsicObjects(multiHash); break; } case SAI_COMMON_API_BULK_GET: break; // ignore get since get is not modifying db default: SWSS_LOG_THROW("api %d is not supported", api); } timer.stop(); timer.inc(statuses.size()); } sai_status_t Syncd::processQuadEvent( _In_ sai_common_api_t api, _In_ const swss::KeyOpFieldsValuesTuple &kco) { SWSS_LOG_ENTER(); const std::string& key = kfvKey(kco); const std::string& op = kfvOp(kco); const std::string& strObjectId = key.substr(key.find(":") + 1); sai_object_meta_key_t metaKey; sai_deserialize_object_meta_key(key, metaKey); if (!sai_metadata_is_object_type_valid(metaKey.objecttype)) { SWSS_LOG_THROW("invalid object type %s", key.c_str()); } auto& values = kfvFieldsValues(kco); for (auto& v: values) { SWSS_LOG_DEBUG("attr: %s: %s", fvField(v).c_str(), fvValue(v).c_str()); } SaiAttributeList list(metaKey.objecttype, values, false); /* * Attribute list can't be const since we will use it to translate VID to * RID in place. */ sai_attribute_t *attr_list = list.get_attr_list(); uint32_t attr_count = list.get_attr_count(); /* * NOTE: This check pointers must be executed before init view mode, since * this methods replaces pointers from orchagent memory space to syncd * memory space. */ if (metaKey.objecttype == SAI_OBJECT_TYPE_SWITCH && (api == SAI_COMMON_API_CREATE || api == SAI_COMMON_API_SET)) { /* * We don't need to clear those pointers on switch remove (even last), * since those pointers will reside inside attributes, also sairedis * will internally check whether pointer is null or not, so we here * will receive all notifications, but redis only those that were set. * * TODO: must be done per switch, and switch may not exists yet */ m_handler->updateNotificationsPointers(attr_count, attr_list); } if (isInitViewMode()) { sai_status_t status = processQuadEventInInitViewMode(metaKey.objecttype, strObjectId, api, attr_count, attr_list); syncUpdateRedisQuadEvent(status, api, kco); return status; } if (api != SAI_COMMON_API_GET) { /* * NOTE: we can also call translate on get, if sairedis will clean * buffer so then all OIDs will be NULL, and translation will also * convert them to NULL. */ SWSS_LOG_DEBUG("translating VID to RIDs on all attributes"); m_translator->translateVidToRid(metaKey.objecttype, attr_count, attr_list); } auto info = sai_metadata_get_object_type_info(metaKey.objecttype); sai_status_t status; if (info->isnonobjectid) { if (info->objecttype == SAI_OBJECT_TYPE_ROUTE_ENTRY) { static PerformanceIntervalTimer timer("Syncd::processQuadEvent::processEntry(route_entry)"); timer.start(); status = processEntry(metaKey, api, attr_count, attr_list); timer.stop(); timer.inc(); } else { status = processEntry(metaKey, api, attr_count, attr_list); } } else { status = processOid(metaKey.objecttype, strObjectId, api, attr_count, attr_list); } if (api == SAI_COMMON_API_GET) { if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_INFO("get API for key: %s op: %s returned status: %s", key.c_str(), op.c_str(), sai_serialize_status(status).c_str()); } // extract switch VID from any object type sai_object_id_t switchVid = VidManager::switchIdQuery(metaKey.objectkey.key.object_id); sendGetResponse(metaKey.objecttype, strObjectId, switchVid, status, attr_count, attr_list); } else if (status != SAI_STATUS_SUCCESS) { sendApiResponse(api, status); if (info->isobjectid && api == SAI_COMMON_API_SET) { sai_object_id_t vid = metaKey.objectkey.key.object_id; sai_object_id_t rid = m_translator->translateVidToRid(vid); SWSS_LOG_ERROR("VID: %s RID: %s", sai_serialize_object_id(vid).c_str(), sai_serialize_object_id(rid).c_str()); } for (const auto &v: values) { SWSS_LOG_ERROR("attr: %s: %s", fvField(v).c_str(), fvValue(v).c_str()); } if (!m_enableSyncMode) { // throw only when sync mode is not enabled SWSS_LOG_THROW("failed to execute api: %s, key: %s, status: %s", op.c_str(), key.c_str(), sai_serialize_status(status).c_str()); } } else // non GET api, status is SUCCESS { sendApiResponse(api, status); } syncUpdateRedisQuadEvent(status, api, kco); return status; } sai_status_t Syncd::processOid( _In_ sai_object_type_t objectType, _In_ const std::string &strObjectId, _In_ sai_common_api_t api, _In_ uint32_t attr_count, _In_ sai_attribute_t *attr_list) { SWSS_LOG_ENTER(); sai_object_id_t object_id; sai_deserialize_object_id(strObjectId, object_id); SWSS_LOG_DEBUG("calling %s for %s", sai_serialize_common_api(api).c_str(), sai_serialize_object_type(objectType).c_str()); /* * We need to do translate vid/rid except for create, since create will * create new RID value, and we will have to map them to VID we received in * create query. */ auto info = sai_metadata_get_object_type_info(objectType); if (info->isnonobjectid) { SWSS_LOG_THROW("passing non object id %s as generic object", info->objecttypename); } switch (api) { case SAI_COMMON_API_CREATE: return processOidCreate(objectType, strObjectId, attr_count, attr_list); case SAI_COMMON_API_REMOVE: return processOidRemove(objectType, strObjectId); case SAI_COMMON_API_SET: return processOidSet(objectType, strObjectId, attr_list); case SAI_COMMON_API_GET: return processOidGet(objectType, strObjectId, attr_count, attr_list); default: SWSS_LOG_THROW("common api (%s) is not implemented", sai_serialize_common_api(api).c_str()); } } sai_status_t Syncd::processOidCreate( _In_ sai_object_type_t objectType, _In_ const std::string &strObjectId, _In_ uint32_t attr_count, _In_ sai_attribute_t *attr_list) { SWSS_LOG_ENTER(); sai_object_id_t objectVid; sai_deserialize_object_id(strObjectId, objectVid); // Object id is VID, we can use it to extract switch id. sai_object_id_t switchVid = VidManager::switchIdQuery(objectVid); sai_object_id_t switchRid = SAI_NULL_OBJECT_ID; if (objectType == SAI_OBJECT_TYPE_SWITCH) { SWSS_LOG_NOTICE("creating switch number %zu", m_switches.size() + 1); } else { /* * When we are creating switch, then switchId parameter is ignored, but * we can't convert it using vid to rid map, since rid doesn't exist * yet, so skip translate for switch, but use translate for all other * objects. */ switchRid = m_translator->translateVidToRid(switchVid); } sai_object_id_t objectRid; sai_status_t status = m_vendorSai->create(objectType, &objectRid, switchRid, attr_count, attr_list); if (status == SAI_STATUS_SUCCESS) { /* * Object was created so new object id was generated we need to save * virtual id's to redis db. */ m_translator->insertRidAndVid(objectRid, objectVid); SWSS_LOG_INFO("saved VID %s to RID %s", sai_serialize_object_id(objectVid).c_str(), sai_serialize_object_id(objectRid).c_str()); if (objectType == SAI_OBJECT_TYPE_SWITCH) { /* * All needed data to populate switch should be obtained inside SaiSwitch * constructor, like getting all queues, ports, etc. */ m_switches[switchVid] = std::make_shared<SaiSwitch>(switchVid, objectRid, m_client, m_translator, m_vendorSai); m_mdioIpcServer->setSwitchId(objectRid); startDiagShell(objectRid); } if (objectType == SAI_OBJECT_TYPE_PORT) { m_switches.at(switchVid)->onPostPortCreate(objectRid, objectVid); } } return status; } sai_status_t Syncd::processOidRemove( _In_ sai_object_type_t objectType, _In_ const std::string &strObjectId) { SWSS_LOG_ENTER(); sai_object_id_t objectVid; sai_deserialize_object_id(strObjectId, objectVid); sai_object_id_t rid = m_translator->translateVidToRid(objectVid); if (objectType == SAI_OBJECT_TYPE_PORT) { sai_object_id_t switchVid = VidManager::switchIdQuery(objectVid); m_switches.at(switchVid)->collectPortRelatedObjects(rid); } sai_status_t status = m_vendorSai->remove(objectType, rid); if (status == SAI_STATUS_SUCCESS) { // remove all related objects from REDIS DB and also from existing // object references since at this point they are no longer valid m_translator->eraseRidAndVid(rid, objectVid); if (objectType == SAI_OBJECT_TYPE_SWITCH) { /* * On remove switch there should be extra action all local objects * and redis object should be removed on remove switch local and * redis db objects should be cleared. * * Currently we don't want to remove switch so we don't need this * method, but lets put this as a safety check. */ SWSS_LOG_THROW("remove switch is not implemented, FIXME"); } else { /* * Removing some object succeeded. Let's check if that * object was default created object, eg. vlan member. * Then we need to update default created object map in * SaiSwitch to be in sync, and be prepared for apply * view to transfer those synced default created * objects to temporary view when it will be created, * since that will be out basic switch state. * * TODO: there can be some issues with reference count * like for schedulers on scheduler groups since they * should have internal references, and we still need * to create dependency tree from saiDiscovery and * update those references to track them, this is * printed in metadata sanitycheck as "default value * needs to be stored". * * TODO lets add SAI metadata flag for that this will * also needs to be of internal/vendor default but we * can already deduce that. */ sai_object_id_t switchVid = VidManager::switchIdQuery(objectVid); if (m_switches.at(switchVid)->isDiscoveredRid(rid)) { m_switches.at(switchVid)->removeExistingObjectReference(rid); } if (objectType == SAI_OBJECT_TYPE_PORT) { m_switches.at(switchVid)->postPortRemove(rid); } } } return status; } sai_status_t Syncd::processOidSet( _In_ sai_object_type_t objectType, _In_ const std::string &strObjectId, _In_ sai_attribute_t *attr) { SWSS_LOG_ENTER(); sai_object_id_t objectVid; sai_deserialize_object_id(strObjectId, objectVid); sai_object_id_t rid = m_translator->translateVidToRid(objectVid); sai_status_t status = m_vendorSai->set(objectType, rid, attr); if (Workaround::isSetAttributeWorkaround(objectType, attr->id, status)) { return SAI_STATUS_SUCCESS; } return status; } sai_status_t Syncd::processOidGet( _In_ sai_object_type_t objectType, _In_ const std::string &strObjectId, _In_ uint32_t attr_count, _In_ sai_attribute_t *attr_list) { SWSS_LOG_ENTER(); sai_object_id_t objectVid; sai_deserialize_object_id(strObjectId, objectVid); sai_object_id_t rid = m_translator->translateVidToRid(objectVid); return m_vendorSai->get(objectType, rid, attr_count, attr_list); } const char* Syncd::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 Syncd::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; } void Syncd::loadProfileMap() { SWSS_LOG_ENTER(); // in case of virtual switch, populate context config m_profileMap[SAI_KEY_VS_GLOBAL_CONTEXT] = std::to_string(m_commandLineOptions->m_globalContext); m_profileMap[SAI_KEY_VS_CONTEXT_CONFIG] = m_commandLineOptions->m_contextConfig; if (m_commandLineOptions->m_profileMapFile.size() == 0) { SWSS_LOG_NOTICE("profile map file not specified"); return; } std::ifstream profile(m_commandLineOptions->m_profileMapFile); if (!profile.is_open()) { SWSS_LOG_ERROR("failed to open profile map file: %s: %s", m_commandLineOptions->m_profileMapFile.c_str(), strerror(errno)); exit(EXIT_FAILURE); } // Provide default value at boot up time and let sai profile value // Override following values if existing. // SAI reads these values at start up time. It would be too late to // set these values later when WARM BOOT is detected. m_profileMap[SAI_KEY_WARM_BOOT_WRITE_FILE] = DEF_SAI_WARM_BOOT_DATA_FILE; m_profileMap[SAI_KEY_WARM_BOOT_READ_FILE] = DEF_SAI_WARM_BOOT_DATA_FILE; std::string line; while (getline(profile, line)) { if (line.size() > 0 && (line[0] == '#' || line[0] == ';')) { continue; } size_t pos = line.find("="); if (pos == std::string::npos) { SWSS_LOG_WARN("not found '=' in line %s", line.c_str()); continue; } std::string key = line.substr(0, pos); std::string value = line.substr(pos + 1); m_profileMap[key] = value; SWSS_LOG_INFO("insert: %s:%s", key.c_str(), value.c_str()); } } void Syncd::sendGetResponse( _In_ sai_object_type_t objectType, _In_ const std::string& strObjectId, _In_ sai_object_id_t switchVid, _In_ sai_status_t status, _In_ uint32_t attr_count, _In_ sai_attribute_t *attr_list) { SWSS_LOG_ENTER(); std::vector<swss::FieldValueTuple> entry; if (status == SAI_STATUS_SUCCESS) { m_translator->translateRidToVid(objectType, switchVid, attr_count, attr_list); /* * Normal serialization + translate RID to VID. */ entry = SaiAttributeList::serialize_attr_list( objectType, attr_count, attr_list, false); /* * All oid values here are VIDs. */ snoopGetResponse(objectType, strObjectId, attr_count, attr_list); } else if (status == SAI_STATUS_BUFFER_OVERFLOW) { /* * In this case we got correct values for list, but list was too small * so serialize only count without list itself, sairedis will need to * take this into account when deserialize. * * If there was a list somewhere, count will be changed to actual value * different attributes can have different lists, many of them may * serialize only count, and will need to support that on the receiver. */ entry = SaiAttributeList::serialize_attr_list( objectType, attr_count, attr_list, true); } else { /* * Some other error, don't send attributes at all. */ } for (const auto &e: entry) { SWSS_LOG_DEBUG("attr: %s: %s", fvField(e).c_str(), fvValue(e).c_str()); } std::string strStatus = sai_serialize_status(status); SWSS_LOG_INFO("sending response for GET api with status: %s", strStatus.c_str()); /* * Since we have only one get at a time, we don't have to serialize object * type and object id, only get status is required to be returned. Get * response will not put any data to table, only queue is used. */ m_selectableChannel->set(strStatus, entry, REDIS_ASIC_STATE_COMMAND_GETRESPONSE); SWSS_LOG_INFO("response for GET api was send"); } void Syncd::sendBulkGetResponse( _In_ sai_object_type_t objectType, _In_ const std::vector<std::string>& strObjectIds, _In_ sai_status_t status, _In_ const std::vector<std::shared_ptr<saimeta::SaiAttributeList>>& attributes, _In_ const std::vector<sai_status_t>& statuses) { SWSS_LOG_ENTER(); std::vector<swss::FieldValueTuple> entries; entries.reserve(strObjectIds.size()); for (uint32_t idx = 0; idx < strObjectIds.size(); idx++) { const auto objectStatus = statuses[idx]; const auto objectStatusStr = sai_serialize_status(statuses[idx]); if (objectStatus == SAI_STATUS_SUCCESS) { sai_object_id_t objectId{}; sai_deserialize_object_id(strObjectIds[idx], objectId); const auto switchVid = VidManager::switchIdQuery(objectId); m_translator->translateRidToVid(objectType, switchVid, attributes[idx]->get_attr_count(), attributes[idx]->get_attr_list()); const auto entry = SaiAttributeList::serialize_attr_list(objectType, attributes[idx]->get_attr_count(), attributes[idx]->get_attr_list(), false); const auto joined = Globals::joinFieldValues(entry); // Object IDs are not serialized. The attributes are assumed to be in order the object IDs were passed. // Essentially, only status and attribute list is needed to be serialized and sent. swss::FieldValueTuple fvt(objectStatusStr, joined); entries.push_back(fvt); /* * All oid values here are VIDs. */ snoopGetResponse(objectType, strObjectIds[idx], attributes[idx]->get_attr_count(), attributes[idx]->get_attr_list()); } else if (objectStatus == SAI_STATUS_BUFFER_OVERFLOW) { const auto entry = SaiAttributeList::serialize_attr_list(objectType, attributes[idx]->get_attr_count(), attributes[idx]->get_attr_list(), true); const auto joined = Globals::joinFieldValues(entry); swss::FieldValueTuple fvt(objectStatusStr, joined); entries.push_back(fvt); } else { swss::FieldValueTuple fvt(objectStatusStr, Globals::joinFieldValues({})); entries.push_back(fvt); } } for (const auto &e: entries) { SWSS_LOG_DEBUG("attr: %s: %s", fvField(e).c_str(), fvValue(e).c_str()); } const auto strStatus = sai_serialize_status(status); SWSS_LOG_INFO("sending response for bulk GET api with status: %s", strStatus.c_str()); m_selectableChannel->set(strStatus, entries, REDIS_ASIC_STATE_COMMAND_GETRESPONSE); SWSS_LOG_INFO("response for bulk GET api was send"); } void Syncd::snoopGetResponse( _In_ sai_object_type_t object_type, _In_ const std::string& strObjectId, // can be non object id _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) { SWSS_LOG_ENTER(); /* * NOTE: this method is operating on VIDs, all RIDs were translated outside * this method. */ /* * Vlan (including vlan 1) will need to be put into TEMP view this should * also be valid for all objects that were queried. */ for (uint32_t idx = 0; idx < attr_count; ++idx) { const sai_attribute_t &attr = attr_list[idx]; auto meta = sai_metadata_get_attr_metadata(object_type, attr.id); if (meta == NULL) { SWSS_LOG_THROW("unable to get metadata for object type %d, attribute %d", object_type, attr.id); } /* * We should snoop oid values even if they are readonly we just note in * temp view that those objects exist on switch. */ switch (meta->attrvaluetype) { case SAI_ATTR_VALUE_TYPE_OBJECT_ID: snoopGetOid(attr.value.oid); break; case SAI_ATTR_VALUE_TYPE_OBJECT_LIST: snoopGetOidList(attr.value.objlist); break; case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_ID: if (attr.value.aclfield.enable) snoopGetOid(attr.value.aclfield.data.oid); break; case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_LIST: if (attr.value.aclfield.enable) snoopGetOidList(attr.value.aclfield.data.objlist); break; case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_ID: if (attr.value.aclaction.enable) snoopGetOid(attr.value.aclaction.parameter.oid); break; case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_LIST: if (attr.value.aclaction.enable) snoopGetOidList(attr.value.aclaction.parameter.objlist); break; default: /* * If in future new attribute with object id will be added this * will make sure that we will need to add handler here. */ if (meta->isoidattribute) { SWSS_LOG_THROW("attribute %s is object id, but not processed, FIXME", meta->attridname); } break; } if (SAI_HAS_FLAG_READ_ONLY(meta->flags)) { /* * If value is read only, we skip it, since after syncd restart we * won't be able to set/create it anyway. */ continue; } if (meta->objecttype == SAI_OBJECT_TYPE_PORT && meta->attrid == SAI_PORT_ATTR_HW_LANE_LIST) { /* * Skip port lanes for now since we don't create ports. */ SWSS_LOG_INFO("skipping %s for %s", meta->attridname, strObjectId.c_str()); continue; } /* * Put non readonly, and non oid attribute value to temp view. * * NOTE: This will also put create-only attributes to view, and after * syncd hard reinit we will not be able to do "SET" on that attribute. * * Similar action can happen when we will do this on asicSet during * apply view. */ snoopGetAttrValue(strObjectId, meta, attr); } } void Syncd::snoopGetAttr( _In_ sai_object_type_t objectType, _In_ const std::string& strObjectId, _In_ const std::string& attrId, _In_ const std::string& attrValue) { SWSS_LOG_ENTER(); std::string mk = sai_serialize_object_type(objectType) + ":" + strObjectId; sai_object_meta_key_t metaKey; sai_deserialize_object_meta_key(mk, metaKey); if (isInitViewMode()) { m_client->setTempAsicObject(metaKey, attrId, attrValue); } else { m_client->setAsicObject(metaKey, attrId, attrValue); } } void Syncd::snoopGetOid( _In_ sai_object_id_t vid) { SWSS_LOG_ENTER(); if (vid == SAI_NULL_OBJECT_ID) { // if snooped oid is NULL then we don't need take any action return; } /* * We need use redis version of object type query here since we are * operating on VID value, and syncd is compiled against real SAI * implementation which has different function m_vendorSai->objectTypeQuery. */ sai_object_type_t objectType = VidManager::objectTypeQuery(vid); std::string strVid = sai_serialize_object_id(vid); snoopGetAttr(objectType, strVid, "NULL", "NULL"); } void Syncd::snoopGetOidList( _In_ const sai_object_list_t& list) { SWSS_LOG_ENTER(); for (uint32_t i = 0; i < list.count; i++) { snoopGetOid(list.list[i]); } } void Syncd::snoopGetAttrValue( _In_ const std::string& strObjectId, _In_ const sai_attr_metadata_t *meta, _In_ const sai_attribute_t& attr) { SWSS_LOG_ENTER(); std::string value = sai_serialize_attr_value(*meta, attr); SWSS_LOG_DEBUG("%s:%s", meta->attridname, value.c_str()); snoopGetAttr(meta->objecttype, strObjectId, meta->attridname, value); } void Syncd::inspectAsic() { SWSS_LOG_ENTER(); // Fetch all the keys from ASIC DB // Loop through all the keys in ASIC DB for (const auto &key: m_client->getAsicStateKeys()) { // ASIC_STATE:objecttype:objectid (object id may contain ':') auto start = key.find_first_of(":"); if (start == std::string::npos) { SWSS_LOG_ERROR("invalid ASIC_STATE_TABLE %s: no start :", key.c_str()); break; } auto mk = key.substr(start + 1); sai_object_meta_key_t metaKey; sai_deserialize_object_meta_key(mk, metaKey); // Find all the attrid from ASIC DB, and use them to query ASIC auto hash = m_client->getAttributesFromAsicKey(key); std::vector<swss::FieldValueTuple> values; for (auto &kv: hash) { const std::string &skey = kv.first; const std::string &svalue = kv.second; swss::FieldValueTuple fvt(skey, svalue); values.push_back(fvt); } SaiAttributeList list(metaKey.objecttype, 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 (attr_count == 0) { // TODO: how to check ASIC on ASIC DB key with NULL:NULL hash // just ignore for now continue; } m_translator->translateVidToRid(metaKey); sai_status_t status = m_vendorSai->get(metaKey, attr_count, attr_list); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("failed to execute get api on %s: %s", sai_serialize_object_meta_key(metaKey).c_str(), sai_serialize_status(status).c_str()); continue; } SaiAttributeList redis_list(metaKey.objecttype, values, false); sai_attribute_t *redis_attr_list = redis_list.get_attr_list(); m_translator->translateVidToRid(metaKey.objecttype, attr_count, redis_attr_list); // compare fields and values from ASIC_DB and SAI response and log the difference for (uint32_t index = 0; index < attr_count; ++index) { const sai_attribute_t& attr = attr_list[index]; auto meta = sai_metadata_get_attr_metadata(metaKey.objecttype, attr.id); if (meta == NULL) { SWSS_LOG_ERROR("FATAL: failed to find metadata for object type %s and attr id %d", sai_serialize_object_type(metaKey.objecttype).c_str(), attr.id); break; } std::string strSaiAttrValue = sai_serialize_attr_value(*meta, attr, false); std::string strRedisAttrValue = sai_serialize_attr_value(*meta, redis_attr_list[index], false); if (strRedisAttrValue == strSaiAttrValue) { SWSS_LOG_INFO("matched %s REDIS and ASIC attr value '%s' with on %s", meta->attridname, strRedisAttrValue.c_str(), sai_serialize_object_meta_key(metaKey).c_str()); } else { SWSS_LOG_ERROR("failed to match %s REDIS attr '%s' with ASIC attr '%s' for %s", meta->attridname, strRedisAttrValue.c_str(), strSaiAttrValue.c_str(), sai_serialize_object_meta_key(metaKey).c_str()); } } } } sai_status_t Syncd::processNotifySyncd( _In_ const swss::KeyOpFieldsValuesTuple &kco) { SWSS_LOG_ENTER(); auto& key = kfvKey(kco); sai_status_t status = SAI_STATUS_SUCCESS; auto redisNotifySyncd = sai_deserialize_redis_notify_syncd(key); if (redisNotifySyncd == SAI_REDIS_NOTIFY_SYNCD_INVOKE_DUMP) { SWSS_LOG_NOTICE("Invoking SAI failure dump"); std::string ret_str; int ret = swss::exec(SAI_FAILURE_DUMP_SCRIPT, ret_str); if (ret != 0) { SWSS_LOG_ERROR("Error in executing SAI failure dump %s", ret_str.c_str()); status = SAI_STATUS_FAILURE; } sendNotifyResponse(status); return status; } if (!m_commandLineOptions->m_enableTempView) { SWSS_LOG_NOTICE("received %s, ignored since TEMP VIEW is not used, returning success", key.c_str()); sendNotifyResponse(SAI_STATUS_SUCCESS); return SAI_STATUS_SUCCESS; } if (m_veryFirstRun && m_firstInitWasPerformed && redisNotifySyncd == SAI_REDIS_NOTIFY_SYNCD_INIT_VIEW) { /* * Make sure that when second INIT view arrives, then we will jump to * next section, since second init view may create switch that already * exists and will fail with creating multiple switches error. */ m_veryFirstRun = false; } else if (m_veryFirstRun) { SWSS_LOG_NOTICE("very first run is TRUE, op = %s", key.c_str()); /* * On the very first start of syncd, "compile" view is directly applied * on device, since it will make it easier to switch to new asic state * later on when we restart orch agent. */ if (redisNotifySyncd == SAI_REDIS_NOTIFY_SYNCD_INIT_VIEW) { /* * On first start we just do "apply" directly on asic so we set * init to false instead of true. */ m_asicInitViewMode = false; m_firstInitWasPerformed = true; // we need to clear current temp view to make space for new one clearTempView(); } else if (redisNotifySyncd == SAI_REDIS_NOTIFY_SYNCD_APPLY_VIEW) { m_veryFirstRun = false; m_asicInitViewMode = false; #ifdef MELLANOX bool applyViewInFastFastBoot = m_commandLineOptions->m_startType == SAI_START_TYPE_FASTFAST_BOOT || m_commandLineOptions->m_startType == SAI_START_TYPE_EXPRESS_BOOT || m_commandLineOptions->m_startType == SAI_START_TYPE_FAST_BOOT; #else bool applyViewInFastFastBoot = m_commandLineOptions->m_startType == SAI_START_TYPE_FASTFAST_BOOT || m_commandLineOptions->m_startType == SAI_START_TYPE_EXPRESS_BOOT; #endif if (applyViewInFastFastBoot) { // express/fastfast boot configuration end status = onApplyViewInFastFastBoot(); } SWSS_LOG_NOTICE("setting very first run to FALSE, op = %s", key.c_str()); } else if (redisNotifySyncd == SAI_REDIS_NOTIFY_SYNCD_INSPECT_ASIC) { SWSS_LOG_NOTICE("syncd switched to INSPECT ASIC mode"); inspectAsic(); sendNotifyResponse(SAI_STATUS_SUCCESS); } else { SWSS_LOG_THROW("unknown operation: %s", key.c_str()); } sendNotifyResponse(status); return status; } if (redisNotifySyncd == SAI_REDIS_NOTIFY_SYNCD_INIT_VIEW) { if (m_asicInitViewMode) { SWSS_LOG_WARN("syncd is already in asic INIT VIEW mode, but received init again, orchagent restarted before apply?"); } m_asicInitViewMode = true; clearTempView(); m_createdInInitView.clear(); // NOTE: Currently as WARN to be easier to spot, later should be NOTICE. SWSS_LOG_WARN("syncd switched to INIT VIEW mode, all op will be saved to TEMP view"); sendNotifyResponse(SAI_STATUS_SUCCESS); } else if (redisNotifySyncd == SAI_REDIS_NOTIFY_SYNCD_APPLY_VIEW) { m_asicInitViewMode = false; // NOTE: Currently as WARN to be easier to spot, later should be NOTICE. SWSS_LOG_WARN("syncd received APPLY VIEW, will translate"); try { status = applyView(); } catch(...) { /* * If apply view will fail with exception, try to send fail * response to sairedis, since later there can be switch shutdown * notification sent, and it will be synchronized with mutex, and * it will not be processed until get response timeout will hit. */ sendNotifyResponse(SAI_STATUS_FAILURE); throw; } sendNotifyResponse(status); if (status == SAI_STATUS_SUCCESS) { /* * We successfully applied new view, VID mapping could change, so * we need to clear local db, and all new VIDs will be queried * using redis. * * TODO possible race condition - get notification when new view is * applied and cache have old values, and notification start's * translating vid/rid, we need to stop processing notifications * for transition (queue can still grow), possible fdb * notifications but fdb learning was disabled on warm boot, so * there should be no issue. */ m_translator->clearLocalCache(); m_createdInInitView.clear(); } else { /* * Apply view failed. It can fail in 2 ways, ether nothing was * executed, on asic, or asic is inconsistent state then we should * die or hang. */ return status; } } else if (redisNotifySyncd == SAI_REDIS_NOTIFY_SYNCD_INSPECT_ASIC) { SWSS_LOG_NOTICE("syncd switched to INSPECT ASIC mode"); inspectAsic(); sendNotifyResponse(SAI_STATUS_SUCCESS); } else { SWSS_LOG_ERROR("unknown operation: %s", key.c_str()); sendNotifyResponse(SAI_STATUS_NOT_IMPLEMENTED); SWSS_LOG_THROW("notify syncd %s operation failed", key.c_str()); } return SAI_STATUS_SUCCESS; } void Syncd::sendNotifyResponse( _In_ sai_status_t status) { SWSS_LOG_ENTER(); std::string strStatus = sai_serialize_status(status); std::vector<swss::FieldValueTuple> entry; SWSS_LOG_INFO("sending response: %s", strStatus.c_str()); m_selectableChannel->set(strStatus, entry, REDIS_ASIC_STATE_COMMAND_NOTIFY); } void Syncd::clearTempView() { SWSS_LOG_ENTER(); SWSS_LOG_NOTICE("clearing current TEMP VIEW"); SWSS_LOG_TIMER("clear temp view"); m_client->removeTempAsicStateTable(); // Also clear list of objects removed in init view mode. m_initViewRemovedVidSet.clear(); } sai_status_t Syncd::onApplyViewInFastFastBoot() { SWSS_LOG_ENTER(); sai_status_t all = SAI_STATUS_SUCCESS; for (auto& kvp: m_switches) { sai_attribute_t attr; attr.id = SAI_SWITCH_ATTR_FAST_API_ENABLE; attr.value.booldata = false; sai_status_t status = m_vendorSai->set(SAI_OBJECT_TYPE_SWITCH, kvp.second->getRid(), &attr); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("Failed to set SAI_SWITCH_ATTR_FAST_API_ENABLE=false: %s for switch RID: %s", sai_serialize_status(status).c_str(), sai_serialize_object_id(kvp.second->getRid()).c_str()); all = status; } } return all; } sai_status_t Syncd::applyView() { SWSS_LOG_ENTER(); SWSS_LOG_TIMER("apply"); /* * We assume that there will be no case that we will move from 1 to 0, also * if at the beginning there is no switch, then when user will send create, * and it will be actually created (real call) so there should be no case * when we are moving from 0 -> 1. */ /* * This method contains 2 stages. * * First stage is non destructive, when orchagent will build new view, and * there will be bug in comparison logic in first stage, then syncd will * send failure when doing apply view to orchagent but it will still be * running. No asic operations are performed during this stage. * * Second stage is destructive, so if there will be bug in comparison logic * or any asic operation will fail, then syncd will crash, since asic will * be in inconsistent state. */ /* * Initialize rand for future candidate object selection if necessary. * * NOTE: Should this be deterministic? So we could repeat random choice * when something bad happen or we hit a bug, so in that case it will be * easier for reproduce, we could at least log value returned from time(). * * TODO: To make it stable, we also need to make stable redisGetAsicView * since now order of items is random. Also redis result needs to be * sorted. */ // Read current and temporary views from REDIS. auto currentMap = m_client->getAsicView(); auto temporaryMap = m_client->getTempAsicView(); if (currentMap.size() != temporaryMap.size()) { SWSS_LOG_THROW("current view switches: %zu != temporary view switches: %zu, FATAL", currentMap.size(), temporaryMap.size()); } if (currentMap.size() != m_switches.size()) { SWSS_LOG_THROW("current asic view switches %zu != defined switches %zu, FATAL", currentMap.size(), m_switches.size()); } // VID of switches must match for each map for (auto& kvp: currentMap) { if (temporaryMap.find(kvp.first) == temporaryMap.end()) { SWSS_LOG_THROW("switch VID %s missing from temporary view!, FATAL", sai_serialize_object_id(kvp.first).c_str()); } if (m_switches.find(kvp.first) == m_switches.end()) { SWSS_LOG_THROW("switch VID %s missing from ASIC, FATAL", sai_serialize_object_id(kvp.first).c_str()); } } std::vector<std::shared_ptr<AsicView>> currentViews; std::vector<std::shared_ptr<AsicView>> tempViews; std::vector<std::shared_ptr<ComparisonLogic>> cls; try { for (auto& kvp: m_switches) { auto switchVid = kvp.first; auto sw = m_switches.at(switchVid); /* * We are starting first stage here, it still can throw exceptions * but it's non destructive for ASIC, so just catch and return in * case of failure. * * Each ASIC view at this point will contain only 1 switch. */ auto current = std::make_shared<AsicView>(currentMap.at(switchVid)); auto temp = std::make_shared<AsicView>(temporaryMap.at(switchVid)); auto cl = std::make_shared<ComparisonLogic>(m_vendorSai, sw, m_handler, m_initViewRemovedVidSet, current, temp, m_breakConfig); cl->compareViews(); currentViews.push_back(current); tempViews.push_back(temp); cls.push_back(cl); } } catch (const std::exception &e) { /* * Exception was thrown in first stage, those were non destructive * actions so just log exception and let syncd running. */ SWSS_LOG_ERROR("Exception: %s", e.what()); return SAI_STATUS_FAILURE; } /* * This is second stage. Those operations are destructive, if any of them * fail, then we will have inconsistent state in ASIC. */ if (m_commandLineOptions->m_enableUnittests) { dumpComparisonLogicOutput(currentViews); } for (auto& cl: cls) { cl->executeOperationsOnAsic(); // can throw, if so asic will be in inconsistent state } updateRedisDatabase(tempViews); for (auto& cl: cls) { if (m_commandLineOptions->m_enableConsistencyCheck) { bool consistent = cl->checkAsicVsDatabaseConsistency(m_translator); if (!consistent && m_commandLineOptions->m_enableUnittests) { SWSS_LOG_THROW("ASIC content is different than DB content!"); } } } return SAI_STATUS_SUCCESS; } void Syncd::dumpComparisonLogicOutput( _In_ const std::vector<std::shared_ptr<AsicView>>& currentViews) { SWSS_LOG_ENTER(); std::stringstream ss; size_t total = 0; // total operations from all switches for (auto& c: currentViews) { total += c->asicGetOperationsCount(); } ss << "ASIC_OPERATIONS: " << total << std::endl; for (auto& c: currentViews) { ss << "ASIC_OPERATIONS on " << sai_serialize_object_id(c->getSwitchVid()) << " : " << c->asicGetOperationsCount() << std::endl; for (const auto &op: c->asicGetWithOptimizedRemoveOperations()) { const std::string &key = kfvKey(*op.m_op); const std::string &opp = kfvOp(*op.m_op); ss << "o " << opp << ": " << key << std::endl; const auto &values = kfvFieldsValues(*op.m_op); for (auto v: values) ss << "a: " << fvField(v) << " " << fvValue(v) << std::endl; } } std::ofstream log("applyview.log"); if (log.is_open()) { log << ss.str(); log.close(); SWSS_LOG_NOTICE("wrote apply_view asic operations to applyview.log"); } else { SWSS_LOG_ERROR("failed to open applyview.log"); } } void Syncd::updateRedisDatabase( _In_ const std::vector<std::shared_ptr<AsicView>>& temporaryViews) { SWSS_LOG_ENTER(); // TODO: We can make LUA script for this which will be much faster. // // TODO: Needs to be revisited if ASIC views will be across multiple redis // database indexes. SWSS_LOG_TIMER("redis update"); m_client->removeAsicStateTable(); m_client->removeTempAsicStateTable(); // Save temporary views as current view in redis database. for (auto& tv: temporaryViews) { for (const auto &pair: tv->m_soAll) { const auto &obj = pair.second; const auto &attr = obj->getAllAttributes(); std::vector<swss::FieldValueTuple> entry; for (const auto &ap: attr) { const auto saiAttr = ap.second; entry.emplace_back(saiAttr->getStrAttrId(), saiAttr->getStrAttrValue()); } m_client->createAsicObject(obj->m_meta_key, entry); } } /* * Remove previous RID2VID maps and apply new map. * * NOTE: This needs to be done per switch, we can't remove all maps. */ // TODO check if those 2 maps are consistent std::unordered_map<sai_object_id_t, sai_object_id_t> allVid2Rid; for (auto& tv: temporaryViews) { for (auto &kv: tv->m_ridToVid) { allVid2Rid[kv.second] = kv.first; } } m_client->setVidAndRidMap(allVid2Rid); SWSS_LOG_NOTICE("updated redis database"); } // TODO for future we can have each switch in separate redis db index or even // some switches in the same db index and some in separate. Current redis get // asic view is assuming all switches are in the same db index an also some // operations per switch are accessing data base in SaiSwitch class. This // needs to be reorganised to access database per switch basis and get only // data that corresponds to each particular switch and access correct db index. void Syncd::onSyncdStart( _In_ bool warmStart) { SWSS_LOG_ENTER(); std::lock_guard<std::mutex> lock(m_mutex); /* * It may happen that after initialize we will receive some port * notifications with port'ids that are not in redis db yet, so after * checking VIDTORID map there will be entries and translate_vid_to_rid * will generate new id's for ports, this may cause race condition so we * need to use a lock here to prevent that. */ SWSS_LOG_TIMER("on syncd start"); if (warmStart) { /* * Switch was warm started, so switches map is empty, we need to * recreate it based on existing entries inside database. * * Currently we expect only one switch, then we need to call it. * * Also this will make sure that current switch id is the same as * before restart. * * If we want to support multiple switches, this needs to be adjusted. */ performWarmRestart(); SWSS_LOG_NOTICE("skipping hard reinit since WARM start was performed"); return; } SWSS_LOG_NOTICE("performing hard reinit since COLD start was performed"); /* * Switch was restarted in hard way, we need to perform hard reinit and * recreate switches map. */ if (m_switches.size()) { SWSS_LOG_THROW("performing hard reinit, but there are %zu switches defined, bug!", m_switches.size()); } HardReiniter hr(m_client, m_translator, m_vendorSai, m_handler); m_switches = hr.hardReinit(); for (auto& sw: m_switches) { startDiagShell(sw.second->getRid()); } SWSS_LOG_NOTICE("hard reinit succeeded"); } void Syncd::onSwitchCreateInInitViewMode( _In_ sai_object_id_t switchVid, _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) { SWSS_LOG_ENTER(); /* * We can have multiple switches here, but each switch is identified by * SAI_SWITCH_ATTR_SWITCH_HARDWARE_INFO. This attribute is treated as key, * so each switch will have different hardware info. * * Currently we assume that we have only one switch. * * We can have 2 scenarios here: * * - we have multiple switches already existing, and in init view mode user * will create the same switches, then since switch id are deterministic * we can match them by hardware info and by switch id, it may happen * that switch id will be different if user will create switches in * different order, this case will be not supported unless special logic * will be written to handle that case. This case is solved by bounding * hardware info to switch index in context config file. * * - if user created switches but non of switch has the same hardware info * then it means we need to create actual switch here, since user will * want to query switch ports etc values, that's why on create switch is * special case, and that's why we need to keep track of all switches. * This case is also solved bu allowing creation of only switches defined * in context config which bounds hardware info and switch index making * switch VID deterministic. * * Since we are creating switch here, we are sure that this switch don't * have any oid attributes set, so we can pass all attributes. * * Hardware info attribute must be passed and all non OID attributes * including create only and conditionals. */ /* * Multiple switches scenario with changed order: * * If orchagent will create the same switch with the same hardware info but * with different order since switch id is deterministic, then VID of both * switches will always match since they are bound to hardware info using * context config file. */ if (m_switches.find(switchVid) == m_switches.end()) { /* * Switch with particular VID don't exists yet, so lets create it. We * need to create this switch so user in init mode could query switch * properties using GET api. * * We assume that none of attributes is object id attribute. * * This scenario can happen when you start syncd on empty database and * then you quit and restart it again. */ sai_object_id_t switchRid; sai_status_t status; { SWSS_LOG_TIMER("cold boot: create switch"); status = m_vendorSai->create(SAI_OBJECT_TYPE_SWITCH, &switchRid, 0, attr_count, attr_list); } if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_THROW("failed to create switch in init view mode: %s", sai_serialize_status(status).c_str()); } /* * Object was created so new RID was generated we need to save virtual * id's to redis db. */ SWSS_LOG_NOTICE("created switch VID %s to RID %s in init view mode", sai_serialize_object_id(switchVid).c_str(), sai_serialize_object_id(switchRid).c_str()); m_translator->insertRidAndVid(switchRid, switchVid); // make switch initialization and get all default data m_switches[switchVid] = std::make_shared<SaiSwitch>(switchVid, switchRid, m_client, m_translator, m_vendorSai); m_mdioIpcServer->setSwitchId(switchRid); startDiagShell(switchRid); } else { /* * There is already switch defined, we need to match it by hardware * info and we need to know that current switch VID also should match * since it's deterministic created. */ auto sw = m_switches.at(switchVid); // switches VID must match, since it's deterministic if (switchVid != sw->getVid()) { SWSS_LOG_THROW("created switch VID don't match: previous %s, current: %s", sai_serialize_object_id(switchVid).c_str(), sai_serialize_object_id(sw->getVid()).c_str()); } // also hardware info also must match std::string currentHw = sw->getHardwareInfo(); std::string newHw; auto attr = sai_metadata_get_attr_by_id(SAI_SWITCH_ATTR_SWITCH_HARDWARE_INFO, attr_count, attr_list); if (attr == NULL) { // this is ok, attribute doesn't exist, so assumption is empty string } else { newHw = std::string((char*)attr->value.s8list.list, attr->value.s8list.count); } SWSS_LOG_NOTICE("new switch %s contains hardware info: '%s'", sai_serialize_object_id(switchVid).c_str(), newHw.c_str()); /* * The line below is added due to a behavior change of SAI call. * * TODO: remove the line when SAI vendor agrees fix on their end. */ currentHw = currentHw == "none"? "" : currentHw; if (currentHw != newHw) { SWSS_LOG_THROW("hardware info mismatch: current '%s' vs new '%s'", currentHw.c_str(), newHw.c_str()); } SWSS_LOG_NOTICE("current %s switch hardware info: '%s'", sai_serialize_object_id(switchVid).c_str(), currentHw.c_str()); /* * Some attributes on new switch could be different then on existing * one, but we are in init view mode so comparison logic will be * executed on apply view and those attributes will be compared and * actions will be generated if any of them are different. */ } } void Syncd::performWarmRestartSingleSwitch( _In_ const std::string& key) { SWSS_LOG_ENTER(); // key should be in format ASIC_STATE:SAI_OBJECT_TYPE_SWITCH:oid:0xYYYY /* * Since multiple switches can be defined on warm boot, then we need to * correctly identify each switch by passing hardware info. * * TODO: do we also need to pass any other attributes, like create only etc? */ auto start = key.find_first_of(":") + 1; auto end = key.find(":", start); std::string strSwitchVid = key.substr(end + 1); std::vector<swss::FieldValueTuple> values; auto hash = m_client->getAttributesFromAsicKey(key); SWSS_LOG_NOTICE("switch %s", strSwitchVid.c_str()); for (auto &kv: hash) { const std::string& skey = kv.first; const std::string& svalue = kv.second; if (skey == "NULL") continue; SWSS_LOG_NOTICE(" - attr: %s:%s", skey.c_str(), svalue.c_str()); swss::FieldValueTuple fvt(skey, svalue); values.push_back(fvt); } SaiAttributeList list(SAI_OBJECT_TYPE_SWITCH, values, false); sai_object_id_t switchVid; sai_deserialize_object_id(strSwitchVid, switchVid); sai_object_id_t originalSwitchRid = m_translator->translateVidToRid(switchVid); sai_object_id_t switchRid; std::vector<sai_attribute_t> attrs; sai_attribute_t attr; attr.id = SAI_SWITCH_ATTR_INIT_SWITCH; attr.value.booldata = true; attrs.push_back(attr); sai_attribute_t *attrList = list.get_attr_list(); uint32_t attrCount = list.get_attr_count(); for (uint32_t idx = 0; idx < attrCount; idx++) { auto id = attrList[idx].id; if (id == SAI_SWITCH_ATTR_INIT_SWITCH) continue; auto meta = sai_metadata_get_attr_metadata(SAI_OBJECT_TYPE_SWITCH, id); /* * If we want to handle multiple switches, then during warm boot switch * create we need to pass hardware info so vendor sai could know which * switch to initialize. We also need to update pointer values since * new process could be loaded at different address space. */ if (id == SAI_SWITCH_ATTR_SWITCH_HARDWARE_INFO || meta->attrvaluetype == SAI_ATTR_VALUE_TYPE_POINTER) { attrs.push_back(attrList[idx]); continue; } SWSS_LOG_NOTICE("skipping warm boot: %s", meta->attridname); } // TODO support multiple notification handlers m_handler->updateNotificationsPointers((uint32_t)attrs.size(), attrs.data()); sai_status_t status; { SWSS_LOG_TIMER("Warm boot: create switch VID: %s", sai_serialize_object_id(switchVid).c_str()); status = m_vendorSai->create(SAI_OBJECT_TYPE_SWITCH, &switchRid, 0, (uint32_t)attrs.size(), attrs.data()); } if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_THROW("failed to create switch RID: %s for VID %s", sai_serialize_status(status).c_str(), sai_serialize_object_id(switchVid).c_str()); } if (originalSwitchRid != switchRid) { SWSS_LOG_THROW("Unexpected RID 0x%" PRIx64 " (expected 0x%" PRIx64 " )", switchRid, originalSwitchRid); } // perform all get operations on existing switch auto sw = m_switches[switchVid] = std::make_shared<SaiSwitch>(switchVid, switchRid, m_client, m_translator, m_vendorSai, true); startDiagShell(switchRid); } void Syncd::performWarmRestart() { SWSS_LOG_ENTER(); /* * There should be no case when we are doing warm restart and there is no * switch defined, we will throw at such a case. * * This case could be possible when no switches were created and only api * was initialized, but we will skip this scenario and address is when we * will have need for it. */ auto entries = m_client->getAsicStateSwitchesKeys(); if (entries.size() == 0) { SWSS_LOG_THROW("on warm restart there is no switches defined in DB, not supported yet, FIXME"); } SWSS_LOG_NOTICE("switches defined in warm restart: %zu", entries.size()); // here we could have multiple switches defined, let's process them one by one for (auto& entry: entries) { performWarmRestartSingleSwitch(entry); } } void Syncd::startDiagShell( _In_ sai_object_id_t switchRid) { SWSS_LOG_ENTER(); if (m_commandLineOptions->m_enableDiagShell) { SWSS_LOG_NOTICE("starting diag shell thread for switch RID %s", sai_serialize_object_id(switchRid).c_str()); std::thread thread = std::thread(&Syncd::diagShellThreadProc, this, switchRid); thread.detach(); } } void Syncd::diagShellThreadProc( _In_ sai_object_id_t switchRid) { SWSS_LOG_ENTER(); sai_status_t status; /* * This is currently blocking API on broadcom, it will block until we exit * shell. */ while (true) { sai_attribute_t attr; attr.id = SAI_SWITCH_ATTR_SWITCH_SHELL_ENABLE; attr.value.booldata = true; status = m_vendorSai->set(SAI_OBJECT_TYPE_SWITCH, switchRid, &attr); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("Failed to enable switch shell: %s", sai_serialize_status(status).c_str()); return; } sleep(1); } } void Syncd::sendShutdownRequest( _In_ sai_object_id_t switchVid) { SWSS_LOG_ENTER(); if (m_notifications == nullptr) { SWSS_LOG_WARN("notifications pointer is NULL"); return; } auto s = sai_serialize_object_id(switchVid); SWSS_LOG_NOTICE("sending switch_shutdown_request notification to OA for switch: %s", s.c_str()); std::vector<swss::FieldValueTuple> entry; // TODO use m_handler->onSwitchShutdownRequest(switchVid); (but this should be per switch) s = sai_serialize_switch_shutdown_request(switchVid); m_notifications->send(SAI_SWITCH_NOTIFICATION_NAME_SWITCH_SHUTDOWN_REQUEST, s, entry); } void Syncd::sendShutdownRequestAfterException() { SWSS_LOG_ENTER(); std::lock_guard<std::mutex> lock(m_mutex); try { if (m_switches.size()) { for (auto& kvp: m_switches) { sendShutdownRequest(kvp.second->getVid()); } } else { sendShutdownRequest(SAI_NULL_OBJECT_ID); } SWSS_LOG_NOTICE("notification send successfully"); } catch(const std::exception &e) { SWSS_LOG_ERROR("Runtime error: %s", e.what()); } catch(...) { SWSS_LOG_ERROR("Unknown runtime error"); } } void Syncd::saiLoglevelNotify( _In_ std::string strApi, _In_ std::string strLogLevel) { SWSS_LOG_ENTER(); try { sai_log_level_t logLevel; sai_deserialize_log_level(strLogLevel, logLevel); sai_api_t api; sai_deserialize_api(strApi, api); sai_status_t status = m_vendorSai->logSet(api, logLevel); if (status == SAI_STATUS_SUCCESS) { SWSS_LOG_NOTICE("Setting SAI loglevel %s on %s", strLogLevel.c_str(), strApi.c_str()); } else { SWSS_LOG_INFO("set loglevel failed: %s", sai_serialize_status(status).c_str()); } } catch (const std::exception& e) { SWSS_LOG_ERROR("Failed to set loglevel to %s on %s: %s", strLogLevel.c_str(), strApi.c_str(), e.what()); } } void Syncd::setSaiApiLogLevel() { SWSS_LOG_ENTER(); // We start from 1 since 0 is SAI_API_UNSPECIFIED. for (uint32_t idx = 1; idx < sai_metadata_enum_sai_api_t.valuescount; ++idx) { // NOTE: link to db is singleton, so if we would want multiple Syncd // instances running at the same process, we need to have logger // registrar similar to net link messages swss::Logger::linkToDb( sai_metadata_enum_sai_api_t.valuesnames[idx], std::bind(&Syncd::saiLoglevelNotify, this, _1, _2), sai_serialize_log_level(SAI_LOG_LEVEL_NOTICE)); } } sai_status_t Syncd::removeAllSwitches() { SWSS_LOG_ENTER(); SWSS_LOG_NOTICE("Removing all switches"); // TODO mutex ? sai_status_t result = SAI_STATUS_SUCCESS; for (auto& sw: m_switches) { auto rid = sw.second->getRid(); auto strRid = sai_serialize_object_id(rid); SWSS_LOG_TIMER("removing switch RID %s", strRid.c_str()); auto status = m_vendorSai->remove(SAI_OBJECT_TYPE_SWITCH, rid); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_NOTICE("Can't delete a switch RID %s: %s", strRid.c_str(), sai_serialize_status(status).c_str()); result = status; } } return result; } sai_status_t Syncd::setRestartWarmOnAllSwitches( _In_ bool flag) { SWSS_LOG_ENTER(); sai_status_t result = SAI_STATUS_SUCCESS; sai_attribute_t attr; attr.id = SAI_SWITCH_ATTR_RESTART_WARM; attr.value.booldata = flag; for (auto& sw: m_switches) { auto rid = sw.second->getRid(); auto strRid = sai_serialize_object_id(rid); auto status = m_vendorSai->set(SAI_OBJECT_TYPE_SWITCH, rid, &attr); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("Failed to set SAI_SWITCH_ATTR_RESTART_WARM=%s: %s:%s", (flag ? "true" : "false"), strRid.c_str(), sai_serialize_status(status).c_str()); result = status; } } return result; } sai_status_t Syncd::setFastAPIEnableOnAllSwitches() { SWSS_LOG_ENTER(); sai_status_t result = SAI_STATUS_SUCCESS; sai_attribute_t attr; attr.id = SAI_SWITCH_ATTR_FAST_API_ENABLE; attr.value.booldata = true; for (auto& sw: m_switches) { auto rid = sw.second->getRid(); auto strRid = sai_serialize_object_id(rid); auto status = m_vendorSai->set(SAI_OBJECT_TYPE_SWITCH, rid, &attr); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("Failed to set SAI_SWITCH_ATTR_PRE_SHUTDOWN=true: %s:%s", strRid.c_str(), sai_serialize_status(status).c_str()); result = status; break; } } return result; } sai_status_t Syncd::setPreShutdownOnAllSwitches() { SWSS_LOG_ENTER(); sai_status_t result = SAI_STATUS_SUCCESS; sai_attribute_t attr; attr.id = SAI_SWITCH_ATTR_PRE_SHUTDOWN; attr.value.booldata = true; for (auto& sw: m_switches) { auto rid = sw.second->getRid(); auto strRid = sai_serialize_object_id(rid); auto status = m_vendorSai->set(SAI_OBJECT_TYPE_SWITCH, rid, &attr); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("Failed to set SAI_SWITCH_ATTR_PRE_SHUTDOWN=true: %s:%s", strRid.c_str(), sai_serialize_status(status).c_str()); result = status; } } return result; } sai_status_t Syncd::setUninitDataPlaneOnRemovalOnAllSwitches() { SWSS_LOG_ENTER(); SWSS_LOG_NOTICE("Fast/warm reboot requested, keeping data plane running"); sai_status_t result = SAI_STATUS_SUCCESS; sai_attribute_t attr; attr.id = SAI_SWITCH_ATTR_UNINIT_DATA_PLANE_ON_REMOVAL; attr.value.booldata = false; for (auto& sw: m_switches) { auto rid = sw.second->getRid(); auto strRid = sai_serialize_object_id(rid); sai_attr_capability_t attr_capability = {}; sai_status_t queryStatus; queryStatus = m_vendorSai->queryAttributeCapability(rid, SAI_OBJECT_TYPE_SWITCH, SAI_SWITCH_ATTR_UNINIT_DATA_PLANE_ON_REMOVAL, &attr_capability); if (queryStatus != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("Failed to get SAI_SWITCH_ATTR_UNINIT_DATA_PLANE_ON_REMOVAL capabilities: %s:%s", strRid.c_str(), sai_serialize_status(queryStatus).c_str()); result = queryStatus; continue; } if (attr_capability.set_implemented) { auto status = m_vendorSai->set(SAI_OBJECT_TYPE_SWITCH, rid, &attr); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("Failed to set SAI_SWITCH_ATTR_UNINIT_DATA_PLANE_ON_REMOVAL=false: %s:%s", strRid.c_str(), sai_serialize_status(status).c_str()); result = status; } } } return result; } void Syncd::syncProcessNotification( _In_ const swss::KeyOpFieldsValuesTuple& item) { std::lock_guard<std::mutex> lock(m_mutex); SWSS_LOG_ENTER(); m_processor->syncProcessNotification(item); } bool Syncd::isVeryFirstRun() { SWSS_LOG_ENTER(); /* * If lane map is not defined in redis db then we assume this is very first * start of syncd later on we can add additional checks here. * * TODO: if we add more switches then we need lane maps per switch. * TODO: we also need other way to check if this is first start * * We could use VIDCOUNTER also, but if something is defined in the DB then * we assume this is not the first start. * * TODO we need to fix this, since when there will be queue, it will still think * this is first run, let's query HIDDEN ? */ bool firstRun = m_client->hasNoHiddenKeysDefined(); SWSS_LOG_NOTICE("First Run: %s", firstRun ? "True" : "False"); return firstRun; } static void timerWatchdogCallback( _In_ int64_t span) { SWSS_LOG_ENTER(); SWSS_LOG_ERROR("main loop execution exceeded %ld ms", span/1000); } void Syncd::run() { SWSS_LOG_ENTER(); WarmRestartTable warmRestartTable("STATE_DB"); // TODO from config syncd_restart_type_t shutdownType = SYNCD_RESTART_TYPE_COLD; volatile bool runMainLoop = true; std::shared_ptr<swss::Select> s = std::make_shared<swss::Select>(); try { onSyncdStart(m_commandLineOptions->m_startType == SAI_START_TYPE_WARM_BOOT); // create notifications processing thread after we create_switch to // make sure, we have switch_id translated to VID before we start // processing possible quick fdb notifications, and pointer for // notification queue is created before we create switch m_processor->startNotificationsProcessingThread(); for (auto& sw: m_switches) { m_mdioIpcServer->setSwitchId(sw.second->getRid()); } m_mdioIpcServer->startMdioThread(); SWSS_LOG_NOTICE("syncd listening for events"); s->addSelectable(m_selectableChannel.get()); s->addSelectable(m_restartQuery.get()); s->addSelectable(m_flexCounter.get()); s->addSelectable(m_flexCounterGroup.get()); SWSS_LOG_NOTICE("starting main loop"); } catch(const std::exception &e) { SWSS_LOG_ERROR("Runtime error during syncd init: %s", e.what()); sendShutdownRequestAfterException(); s = std::make_shared<swss::Select>(); s->addSelectable(m_restartQuery.get()); SWSS_LOG_NOTICE("starting main loop, ONLY restart query"); if (m_commandLineOptions->m_disableExitSleep) runMainLoop = false; } m_timerWatchdog.setCallback(timerWatchdogCallback); while (runMainLoop) { try { swss::Selectable *sel = NULL; int result = s->select(&sel); if (sel == m_restartQuery.get()) { /* * This is actual a bad design, since selectable may pick up * multiple events from the queue, and after restart those * events will be forgotten since they were consumed already and * this may lead to forget populate object table which will * lead to unable to find some objects. */ SWSS_LOG_NOTICE("is asic queue empty: %d", m_selectableChannel->empty()); while (!m_selectableChannel->empty()) { processEvent(*m_selectableChannel.get()); } SWSS_LOG_NOTICE("drained queue"); WatchdogScope ws(m_timerWatchdog, "restart query"); shutdownType = handleRestartQuery(*m_restartQuery); if (shutdownType != SYNCD_RESTART_TYPE_PRE_SHUTDOWN && shutdownType != SYNCD_RESTART_TYPE_PRE_EXPRESS_SHUTDOWN) { // break out the event handling loop to shutdown syncd runMainLoop = false; break; } // Handle switch pre-shutdown and wait for the final shutdown // event SWSS_LOG_TIMER("%s pre-shutdown", (shutdownType == SYNCD_RESTART_TYPE_PRE_SHUTDOWN) ? "warm" : "express"); m_manager->removeAllCounters(); sai_status_t status = setRestartWarmOnAllSwitches(true); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("Failed to set SAI_SWITCH_ATTR_RESTART_WARM=true: %s for pre-shutdown", sai_serialize_status(status).c_str()); shutdownType = SYNCD_RESTART_TYPE_COLD; warmRestartTable.setFlagFailed(); continue; } if (shutdownType == SYNCD_RESTART_TYPE_PRE_EXPRESS_SHUTDOWN) { SWSS_LOG_NOTICE("express boot, enable fast API pre-shutdown"); status = setFastAPIEnableOnAllSwitches(); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("Failed to set SAI_SWITCH_ATTR_FAST_API_ENABLE=true: %s for express pre-shutdown. Fall back to cold restart", sai_serialize_status(status).c_str()); shutdownType = SYNCD_RESTART_TYPE_COLD; warmRestartTable.setFlagFailed(); continue; } } status = setPreShutdownOnAllSwitches(); if (status == SAI_STATUS_SUCCESS) { warmRestartTable.setPreShutdown(true); s = std::make_shared<swss::Select>(); // make sure previous select is destroyed s->addSelectable(m_restartQuery.get()); SWSS_LOG_NOTICE("switched to PRE_SHUTDOWN, from now on accepting only shutdown requests"); } else { SWSS_LOG_ERROR("Failed to set SAI_SWITCH_ATTR_PRE_SHUTDOWN=true: %s", sai_serialize_status(status).c_str()); warmRestartTable.setPreShutdown(false); // Restore cold shutdown. setRestartWarmOnAllSwitches(false); } } else if (sel == m_flexCounter.get()) { processFlexCounterEvent(*(swss::ConsumerTable*)sel); } else if (sel == m_flexCounterGroup.get()) { processFlexCounterGroupEvent(*(swss::ConsumerTable*)sel); } else if (sel == m_selectableChannel.get()) { processEvent(*m_selectableChannel.get()); } else { SWSS_LOG_ERROR("select failed: %d", result); } } catch(const std::exception &e) { SWSS_LOG_ERROR("Runtime error: %s", e.what()); sendShutdownRequestAfterException(); s = std::make_shared<swss::Select>(); s->addSelectable(m_restartQuery.get()); if (m_commandLineOptions->m_disableExitSleep) runMainLoop = false; // make sure that if second exception will arise, then we break the loop m_commandLineOptions->m_disableExitSleep = true; } } WatchdogScope ws(m_timerWatchdog, "shutting down syncd"); if (shutdownType == SYNCD_RESTART_TYPE_WARM) { const char *warmBootWriteFile = profileGetValue(0, SAI_KEY_WARM_BOOT_WRITE_FILE); SWSS_LOG_NOTICE("using warmBootWriteFile: '%s'", warmBootWriteFile); if (warmBootWriteFile == NULL) { SWSS_LOG_WARN("user requested warm shutdown but warmBootWriteFile is not specified, forcing cold shutdown"); shutdownType = SYNCD_RESTART_TYPE_COLD; warmRestartTable.setWarmShutdown(false); } else { SWSS_LOG_NOTICE("Warm Reboot requested, keeping data plane running"); sai_status_t status = setRestartWarmOnAllSwitches(true); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("Failed to set SAI_SWITCH_ATTR_RESTART_WARM=true: %s, fall back to cold restart", sai_serialize_status(status).c_str()); shutdownType = SYNCD_RESTART_TYPE_COLD; warmRestartTable.setFlagFailed(); } } } if (shutdownType == SYNCD_RESTART_TYPE_FAST || shutdownType == SYNCD_RESTART_TYPE_WARM || shutdownType == SYNCD_RESTART_TYPE_EXPRESS) { setUninitDataPlaneOnRemovalOnAllSwitches(); } m_manager->removeAllCounters(); m_mdioIpcServer->stopMdioThread(); sai_status_t status = removeAllSwitches(); // Stop notification thread after removing switch m_processor->stopNotificationsProcessingThread(); if (shutdownType == SYNCD_RESTART_TYPE_WARM || shutdownType == SYNCD_RESTART_TYPE_EXPRESS) { warmRestartTable.setWarmShutdown(status == SAI_STATUS_SUCCESS); } SWSS_LOG_NOTICE("calling api uninitialize"); status = m_vendorSai->apiUninitialize(); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("failed to uninitialize api: %s", sai_serialize_status(status).c_str()); } SWSS_LOG_NOTICE("uninitialize finished"); } syncd_restart_type_t Syncd::handleRestartQuery( _In_ swss::NotificationConsumer &restartQuery) { SWSS_LOG_ENTER(); std::string op; std::string data; std::vector<swss::FieldValueTuple> values; restartQuery.pop(op, data, values); m_timerWatchdog.setEventData(op + ":" + data); SWSS_LOG_NOTICE("received %s switch shutdown event", op.c_str()); return RequestShutdownCommandLineOptions::stringToRestartType(op); }