saidiscovery/saidiscovery.cpp (491 lines of code) (raw):

#include <stdlib.h> #include <string.h> #include <stdio.h> #include <errno.h> #include <getopt.h> extern "C" { #include <sai.h> #include "saimetadata.h" } #include "swss/logger.h" #include "meta/sai_serialize.h" #include "meta/Meta.h" #include "syncd/VendorSai.h" #include <iostream> #include <map> #include <vector> #include <set> #include <chrono> // TODO split to multiple cpp /** * @def MAX_ELEMENTS * * Defines maximum elements that will be queried when performing * sai_object_list_t attribute query. */ #define MAX_ELEMENTS 1024 struct cmdOptions { cmdOptions() { logWarnings = false; initSwitch = true; enableDebugLogs = false; dumpObjects = false; fullDiscovery = false; saiApiLogLevel = SAI_LOG_LEVEL_NOTICE; } std::string profileMapFile; bool logWarnings; bool initSwitch; bool enableDebugLogs; bool fullDiscovery; bool dumpObjects; sai_log_level_t saiApiLogLevel; }; static cmdOptions gOptions; typedef std::chrono::duration<double, std::ratio<1>> second_t; /** * @brief Discover all objects on given object ID. * * This method is only good after switch init since we are making * assumptions that there are no user created objects after initialization, * like ACL and other objects we can discover using this approach. If * vendor will support sai_get_object_count and sai_get_object_key then * alter on we can use those methods. * * @param[in] id Object ID to be examined. * @param[inout] discovered Map of already discovered objects. Map will be * updated if new object will be found. * * @return Number of calls performed to SAI. */ static int discover( _In_ std::shared_ptr<sairedis::SaiInterface> sai, _In_ sai_object_id_t id, _Inout_ std::map<sai_object_id_t, std::map<std::string, std::string>> &discovered) { SWSS_LOG_ENTER(); int callCount = 0; if (id == SAI_NULL_OBJECT_ID) { return callCount; } if (discovered.find(id) != discovered.end()) { return callCount; } sai_object_type_t ot = sai->objectTypeQuery(id); if (ot == SAI_OBJECT_TYPE_NULL) { SWSS_LOG_ERROR("oid %s returned NULL object type", sai_serialize_object_id(id).c_str()); return callCount; } SWSS_LOG_INFO("processing %s: %s", sai_serialize_object_type(ot).c_str(), sai_serialize_object_id(id).c_str()); discovered[id] = {}; const sai_object_type_info_t *info = sai_metadata_all_object_type_infos[ot]; for (int idx = 0; info->attrmetadata[idx] != NULL; ++idx) { const sai_attr_metadata_t *md = info->attrmetadata[idx]; if (md->objecttype == SAI_OBJECT_TYPE_PORT && md->attrid == SAI_PORT_ATTR_HW_LANE_LIST) { // XXX workaround for brcm continue; } /* * Note that we don't care about ACL object id's since we assume that * there are no ACLs on switch after init. */ sai_attribute_t attr; attr.id = md->attrid; if (md->attrvaluetype == SAI_ATTR_VALUE_TYPE_OBJECT_ID) { if (md->objecttype == SAI_OBJECT_TYPE_STP && md->attrid == SAI_STP_ATTR_BRIDGE_ID) { SWSS_LOG_WARN("skipping %s since it causes crash", md->attridname); continue; } SWSS_LOG_DEBUG("getting %s for %s", md->attridname, sai_serialize_object_id(id).c_str()); callCount++; sai_status_t status = sai->get(ot, id, 1, &attr); if (status != SAI_STATUS_SUCCESS) { if (gOptions.logWarnings) { SWSS_LOG_WARN("%s: %s", md->attridname, sai_serialize_status(status).c_str()); } continue; } if (md->defaultvaluetype == SAI_DEFAULT_VALUE_TYPE_CONST && attr.value.oid != SAI_NULL_OBJECT_ID) { if (gOptions.logWarnings) { SWSS_LOG_WARN("const null, but got value %s on %s", sai_serialize_object_id(attr.value.oid).c_str(), md->attridname); } } if (!md->allownullobjectid && attr.value.oid == SAI_NULL_OBJECT_ID) { if (gOptions.logWarnings) { SWSS_LOG_WARN("dont allow null, but got null on %s", md->attridname); } } discovered[id][md->attridname] = sai_serialize_attr_value(*md, attr); SWSS_LOG_DEBUG("result on %s: %s: %s", sai_serialize_object_id(id).c_str(), md->attridname, discovered[id][md->attridname].c_str()); callCount += discover(sai, attr.value.oid, discovered); // recursion } else if (md->attrvaluetype == SAI_ATTR_VALUE_TYPE_OBJECT_LIST) { SWSS_LOG_DEBUG("getting %s for %s", md->attridname, sai_serialize_object_id(id).c_str()); sai_object_id_t list[MAX_ELEMENTS]; attr.value.objlist.count = MAX_ELEMENTS; attr.value.objlist.list = list; callCount++; sai_status_t status = sai->get(ot, id, 1, &attr); if (status != SAI_STATUS_SUCCESS) { if (gOptions.logWarnings) { SWSS_LOG_WARN("%s: %s", md->attridname, sai_serialize_status(status).c_str()); } continue; } if (md->defaultvaluetype == SAI_DEFAULT_VALUE_TYPE_EMPTY_LIST && attr.value.objlist.count != 0) { if (gOptions.logWarnings) { SWSS_LOG_WARN("default is empty list, but got count %u on %s", attr.value.objlist.count, md->attridname); } } discovered[id][md->attridname] = sai_serialize_attr_value(*md, attr); SWSS_LOG_INFO("list count %s: %u", md->attridname, attr.value.objlist.count); SWSS_LOG_DEBUG("result on %s: %s: %s", sai_serialize_object_id(id).c_str(), md->attridname, discovered[id][md->attridname].c_str()); for (uint32_t i = 0; i < attr.value.objlist.count; ++i) { callCount += discover(sai, attr.value.objlist.list[i], discovered); // recursion } } else { if (!gOptions.fullDiscovery) { continue; } if ((md->objecttype == SAI_OBJECT_TYPE_PORT && md->attrid == SAI_PORT_ATTR_FEC_MODE) || (md->objecttype == SAI_OBJECT_TYPE_PORT && md->attrid == SAI_PORT_ATTR_GLOBAL_FLOW_CONTROL_MODE) || (md->objecttype == SAI_OBJECT_TYPE_SWITCH && md->attrid == SAI_SWITCH_ATTR_INIT_SWITCH)) { // workaround since return invalid values continue; } /* * Discover non oid attributes as well. * * TODO lists! */ sai_object_id_t list[MAX_ELEMENTS]; switch (md->attrvaluetype) { case SAI_ATTR_VALUE_TYPE_INT8: case SAI_ATTR_VALUE_TYPE_INT16: case SAI_ATTR_VALUE_TYPE_INT32: case SAI_ATTR_VALUE_TYPE_INT64: case SAI_ATTR_VALUE_TYPE_UINT8: case SAI_ATTR_VALUE_TYPE_UINT16: case SAI_ATTR_VALUE_TYPE_UINT32: case SAI_ATTR_VALUE_TYPE_UINT64: case SAI_ATTR_VALUE_TYPE_POINTER: case SAI_ATTR_VALUE_TYPE_BOOL: case SAI_ATTR_VALUE_TYPE_UINT32_RANGE: case SAI_ATTR_VALUE_TYPE_MAC: break; case SAI_ATTR_VALUE_TYPE_INT8_LIST: case SAI_ATTR_VALUE_TYPE_INT32_LIST: case SAI_ATTR_VALUE_TYPE_UINT32_LIST: case SAI_ATTR_VALUE_TYPE_VLAN_LIST: attr.value.objlist.count = MAX_ELEMENTS; attr.value.objlist.list = list; break; case SAI_ATTR_VALUE_TYPE_ACL_CAPABILITY: attr.value.aclcapability.action_list.count = MAX_ELEMENTS; attr.value.aclcapability.action_list.list = (int32_t*)list; break; default: SWSS_LOG_WARN("attr value: %s not supported", sai_serialize_attr_value_type(md->attrvaluetype).c_str()); continue; } SWSS_LOG_DEBUG("getting %s for %s", md->attridname, sai_serialize_object_id(id).c_str()); callCount++; sai_status_t status = sai->get(ot, id, 1, &attr); if (status == SAI_STATUS_SUCCESS) { discovered[id][md->attridname] = sai_serialize_attr_value(*md, attr); SWSS_LOG_DEBUG("result on %s: %s: %s", sai_serialize_object_id(id).c_str(), md->attridname, discovered[id][md->attridname].c_str()); } else { if (gOptions.logWarnings) { SWSS_LOG_WARN("%s: %s", md->attridname, sai_serialize_status(status).c_str()); } } } } return callCount; } static std::map<std::string, std::string> gProfileMap; static std::map<std::string, std::string>::iterator gProfileIter = gProfileMap.begin(); static const char* profile_get_value( _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 = gProfileMap.find(variable); if (it == gProfileMap.end()) { SWSS_LOG_NOTICE("%s: NULL", variable); return NULL; } SWSS_LOG_NOTICE("%s: %s", variable, it->second.c_str()); return it->second.c_str(); } static int profile_get_next_value( _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"); gProfileIter = gProfileMap.begin(); return 0; } if (variable == NULL) { SWSS_LOG_WARN("variable is null"); return -1; } if (gProfileIter == gProfileMap.end()) { SWSS_LOG_INFO("iterator reached end"); return -1; } *variable = gProfileIter->first.c_str(); *value = gProfileIter->second.c_str(); SWSS_LOG_INFO("key: %s:%s", *variable, *value); gProfileIter++; return 0; } static sai_service_method_table_t test_services = { profile_get_value, profile_get_next_value }; static void handleProfileMap( _In_ const std::string& profileMapFile) { SWSS_LOG_ENTER(); if (profileMapFile.size() == 0) { return; } std::ifstream profile(profileMapFile); if (!profile.is_open()) { SWSS_LOG_ERROR("failed to open profile map file: '%s' : %s", profileMapFile.c_str(), strerror(errno)); exit(EXIT_FAILURE); } 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); gProfileMap[key] = value; SWSS_LOG_INFO("inserted: %s:%s", key.c_str(), value.c_str()); } } static void printUsage() { SWSS_LOG_ENTER(); std::cout << std::endl; std::cout << "Usage: saidiscovery [-I] [-D] [-f] [-d] [-p profile] [-w] [-h]" << std::endl << std::endl; std::cout << " -I --noInitSwitch:" << std::endl; std::cout << " Try connect to SDK instead of performing init" << std::endl; std::cout << " -D --dumpObjects:" << std::endl; std::cout << " Print each objects and it's attributes" << std::endl; std::cout << " -d --enableDebugLogs:" << std::endl; std::cout << " Enable debug logs" << std::endl; std::cout << " -f --fullDiscovery:" << std::endl; std::cout << " Discover all attributes, not only OIDs" << std::endl; std::cout << " -p --profile profile:" << std::endl; std::cout << " Provide profile map file" << std::endl; std::cout << " -w --logWarnings" << std::endl; std::cout << " Logs all warnings" << std::endl; std::cout << " -h --help:" << std::endl; std::cout << " Print out this message" << std::endl << std::endl; } static void handleCmdLine(int argc, char **argv) { SWSS_LOG_ENTER(); static struct option long_options[] = { { "dumpObjects", no_argument, 0, 'D' }, { "fullDiscovery", no_argument, 0, 'f' }, { "enableDebugLogs", no_argument, 0, 'd' }, { "logWarnings", no_argument, 0, 'w' }, { "noInitSwitch", no_argument, 0, 'I' }, { "profile", required_argument, 0, 'p' }, { "help", no_argument, 0, 'h' }, { 0, 0, 0, 0 } }; const char* const optstring = "DdwIp:hf"; while (true) { int option_index = 0; int c = getopt_long(argc, argv, optstring, long_options, &option_index); if (c == -1) { break; } switch (c) { case 'f': gOptions.fullDiscovery = true; break; case 'D': gOptions.dumpObjects = true; break; case 'd': gOptions.enableDebugLogs = true; swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_DEBUG); gOptions.saiApiLogLevel = SAI_LOG_LEVEL_DEBUG; break; case 'w': gOptions.logWarnings = true; break; case 'I': gOptions.initSwitch = false; break; case 'p': gOptions.profileMapFile = std::string(optarg); break; case 'h': printUsage(); exit(EXIT_SUCCESS); case '?': SWSS_LOG_WARN("unknown option %c", optopt); printUsage(); exit(EXIT_FAILURE); default: SWSS_LOG_ERROR("getopt_long failure"); exit(EXIT_FAILURE); } } } int main(int argc, char **argv) { swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_DEBUG); SWSS_LOG_ENTER(); swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_NOTICE); handleCmdLine(argc, argv); handleProfileMap(gOptions.profileMapFile); auto vendorSai = std::make_shared<syncd::VendorSai>(); auto sai = std::make_shared<saimeta::Meta>(vendorSai); sai_status_t status = sai->apiInitialize(0, (sai_service_method_table_t*)&test_services); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("failed to initialize api: %s", sai_serialize_status(status).c_str()); exit(EXIT_FAILURE); } for (int api = 1; api < SAI_API_MAX; api++) { sai->logSet((sai_api_t)api, gOptions.saiApiLogLevel); } sai_object_id_t switch_id; const uint32_t AttributesCount = 1; sai_attribute_t attrs[AttributesCount]; attrs[0].id = SAI_SWITCH_ATTR_INIT_SWITCH; attrs[0].value.booldata = gOptions.initSwitch; SWSS_LOG_NOTICE("creating switch"); status = sai->create(SAI_OBJECT_TYPE_SWITCH, &switch_id, SAI_NULL_OBJECT_ID, AttributesCount, attrs); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("failed to create a switch: %s", sai_serialize_status(status).c_str()); exit(EXIT_FAILURE); } if (sai->objectTypeQuery(switch_id) != SAI_OBJECT_TYPE_SWITCH) { SWSS_LOG_ERROR("create switch returned invalid oid: %s", sai_serialize_object_id(switch_id).c_str()); exit(EXIT_FAILURE); } std::map<sai_object_id_t, std::map<std::string, std::string>> discovered; auto m_start = std::chrono::high_resolution_clock::now(); int callCount = discover(sai, switch_id, discovered); auto end = std::chrono::high_resolution_clock::now(); double duration = std::chrono::duration_cast<second_t>(end - m_start).count(); SWSS_LOG_NOTICE("discovered objects: %zu took %.3lf sec, call count: %d", discovered.size(), duration, callCount); std::map<sai_object_type_t, int> map; for (const auto &p: discovered) { map[sai->objectTypeQuery(p.first)]++; } for (const auto &p: map) { SWSS_LOG_NOTICE("%s: %d", sai_serialize_object_type(p.first).c_str(), p.second); printf("%s: %d\n", sai_serialize_object_type(p.first).c_str(), p.second); } if (gOptions.dumpObjects) { printf("\n"); for (const auto &o: map) { for (const auto &p: discovered) { sai_object_type_t ot = sai->objectTypeQuery(p.first); if (ot != o.first) { continue; } printf("%s: %s\n", sai_serialize_object_type(ot).c_str(), sai_serialize_object_id(p.first).c_str()); for (const auto &m: p.second) { printf(" %s: %s\n", m.first.c_str(), m.second.c_str()); } printf("\n"); } } } SWSS_LOG_NOTICE("remove switch"); status = sai->remove(SAI_OBJECT_TYPE_SWITCH, switch_id); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("remove switch %s failed: %s", sai_serialize_object_id(switch_id).c_str(), sai_serialize_status(status).c_str()); } status = sai->apiUninitialize(); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("sai_api_uninitialize failed: %s", sai_serialize_status(status).c_str()); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }