From cbfe521edb27ba509886da9af1da2a8ab0df5d2a Mon Sep 17 00:00:00 2001 From: pavel-shirshov Date: Thu, 18 Jun 2020 19:21:47 -0700 Subject: [PATCH] Implement telemetry daemon for teamd (#1317) * Implement telemetry daemon for teamd Every second all presented teamd daemons is going to be requested for the state dump in json format. After that the json dump will be converted to the memory structure represented the desired format. The database is going to be updated only for entries which were changed since the last poll. The daemon will track a case when a LAG has been added or removed. * Fix typos * Fix order in the class declaration * Make the code more c++ * Add more changes to .gitignore Co-authored-by: PS --- .gitignore | 10 + Makefile.am | 2 +- configure.ac | 3 + tlm_teamd/Makefile.am | 15 ++ tlm_teamd/main.cpp | 128 +++++++++++++ tlm_teamd/teamdctl_mgr.cpp | 146 ++++++++++++++ tlm_teamd/teamdctl_mgr.h | 27 +++ tlm_teamd/values_store.cpp | 378 +++++++++++++++++++++++++++++++++++++ tlm_teamd/values_store.h | 73 +++++++ 9 files changed, 781 insertions(+), 1 deletion(-) create mode 100644 tlm_teamd/Makefile.am create mode 100644 tlm_teamd/main.cpp create mode 100644 tlm_teamd/teamdctl_mgr.cpp create mode 100644 tlm_teamd/teamdctl_mgr.h create mode 100644 tlm_teamd/values_store.cpp create mode 100644 tlm_teamd/values_store.h diff --git a/.gitignore b/.gitignore index 8a0982bb0fda..5c13719c9685 100644 --- a/.gitignore +++ b/.gitignore @@ -30,10 +30,12 @@ libtool Makefile.in stamp-h1 **/Makefile +autom4te.cache # Dependency Folder # ##################### deps/ +**/.deps # Executables # ############### @@ -47,6 +49,9 @@ cfgmgr/teammgrd cfgmgr/vlanmgrd cfgmgr/vrfmgrd cfgmgr/vxlanmgrd +cfgmgr/natmgrd +cfgmgr/sflowmgrd +portsyncd/portsyncd fpmsyncd/fpmsyncd mclagsyncd/mclagsyncd natsyncd/natsyncd @@ -57,11 +62,16 @@ orchagent/routeresync portsyncd/portsyncd swssconfig/swssconfig swssconfig/swssplayer +tlm_teamd/tlm_teamd teamsyncd/teamsyncd tests/tests # Test Files # ############## +tests/log +tests/mock_tests/test-suite.log +tests/mock_tests/tests.log +tests/mock_tests/tests.trs tests/test-suite.log tests/tests.log tests/tests.trs diff --git a/Makefile.am b/Makefile.am index b2e8154888c6..c966f44aa135 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,5 +2,5 @@ SUBDIRS = fpmsyncd neighsyncd portsyncd mclagsyncd natsyncd orchagent swssconfig cfgmgr tests if HAVE_LIBTEAM -SUBDIRS += teamsyncd +SUBDIRS += teamsyncd tlm_teamd endif diff --git a/configure.ac b/configure.ac index 2d5c1fc10671..974744a4f88a 100644 --- a/configure.ac +++ b/configure.ac @@ -20,6 +20,8 @@ AC_CHECK_LIB([team], [team_alloc], [AC_MSG_WARN([libteam is not installed.]) AM_CONDITIONAL(HAVE_LIBTEAM, false)]) +PKG_CHECK_MODULES([JANSSON], [jansson]) + AC_CHECK_LIB([sai], [sai_object_type_query], AM_CONDITIONAL(HAVE_SAI, true), [AC_MSG_WARN([libsai is not installed.]) @@ -92,6 +94,7 @@ AC_CONFIG_FILES([ natsyncd/Makefile portsyncd/Makefile teamsyncd/Makefile + tlm_teamd/Makefile mclagsyncd/Makefile swssconfig/Makefile cfgmgr/Makefile diff --git a/tlm_teamd/Makefile.am b/tlm_teamd/Makefile.am new file mode 100644 index 000000000000..32855b32b6e8 --- /dev/null +++ b/tlm_teamd/Makefile.am @@ -0,0 +1,15 @@ +INCLUDES = -I $(top_srcdir) + +bin_PROGRAMS = tlm_teamd + +if DEBUG +DBGFLAGS = -ggdb -DDEBUG +else +DBGFLAGS = -g +endif + +tlm_teamd_SOURCES = main.cpp teamdctl_mgr.cpp values_store.cpp + +tlm_teamd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) +tlm_teamd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(JANSSON_CFLAGS) +tlm_teamd_LDADD = -lhiredis -lswsscommon -lteamdctl $(JANSSON_LIBS) diff --git a/tlm_teamd/main.cpp b/tlm_teamd/main.cpp new file mode 100644 index 000000000000..70c1a8f5f8a5 --- /dev/null +++ b/tlm_teamd/main.cpp @@ -0,0 +1,128 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "teamdctl_mgr.h" +#include "values_store.h" + + +bool g_run = true; + + +/// This function extract all available updates from the table +/// and add or remove LAG interfaces from the TeamdCtlMgr +/// +/// @param table reference to the SubscriberStateTable +/// @param mgr reference to the TeamdCtlMgr +/// +void update_interfaces(swss::SubscriberStateTable & table, TeamdCtlMgr & mgr) +{ + std::deque entries; + + table.pops(entries); + for (const auto & entry: entries) + { + const auto & lag_name = kfvKey(entry); + const auto & op = kfvOp(entry); + + if (op == "SET") + { + mgr.add_lag(lag_name); + } + else if (op == "DEL") + { + mgr.remove_lag(lag_name); + } + else + { + SWSS_LOG_WARN("Got invalid operation: '%s' with key '%s'", op.c_str(), lag_name.c_str()); + } + } +} + +/// +/// Signal handler +/// +void sig_handler(int signo) +{ + (void)signo; + g_run = false; +} + +/// +/// main function +/// +int main() +{ + const int ms_select_timeout = 1000; + + sighandler_t sig_res; + + sig_res = signal(SIGTERM, sig_handler); + if (sig_res == SIG_ERR) + { + std::cerr << "Can't set signal handler for SIGTERM\n"; + return -1; + } + + sig_res = signal(SIGINT, sig_handler); + if (sig_res == SIG_ERR) + { + std::cerr << "Can't set signal handler for SIGINT\n"; + return -1; + } + + int rc = 0; + try + { + swss::Logger::linkToDbNative("tlm_teamd"); + SWSS_LOG_NOTICE("Starting"); + swss::DBConnector db("STATE_DB", 0); + + ValuesStore values_store(&db); + TeamdCtlMgr teamdctl_mgr; + + swss::Select s; + swss::Selectable * event; + swss::SubscriberStateTable sst_lag(&db, STATE_LAG_TABLE_NAME); + s.addSelectable(&sst_lag); + + while (g_run && rc == 0) + { + int res = s.select(&event, ms_select_timeout); + if (res == swss::Select::OBJECT) + { + update_interfaces(sst_lag, teamdctl_mgr); + values_store.update(teamdctl_mgr.get_dumps()); + } + else if (res == swss::Select::ERROR) + { + SWSS_LOG_ERROR("Select returned ERROR"); + rc = -2; + } + else if (res == swss::Select::TIMEOUT) + { + values_store.update(teamdctl_mgr.get_dumps()); + } + else + { + SWSS_LOG_ERROR("Select returned unknown value"); + rc = -3; + } + } + SWSS_LOG_NOTICE("Exiting"); + } + catch (const std::exception & e) + { + std::cerr << "Exception \"" << e.what() << "\" had been thrown" << std::endl; + SWSS_LOG_ERROR("Exception '%s' had been thrown", e.what()); + rc = -1; + } + + return rc; +} diff --git a/tlm_teamd/teamdctl_mgr.cpp b/tlm_teamd/teamdctl_mgr.cpp new file mode 100644 index 000000000000..f608019a1f39 --- /dev/null +++ b/tlm_teamd/teamdctl_mgr.cpp @@ -0,0 +1,146 @@ +#include + +#include + +#include "teamdctl_mgr.h" + +/// +/// The destructor clean up handlers to teamds +/// +TeamdCtlMgr::~TeamdCtlMgr() +{ + for (const auto & p: m_handlers) + { + const auto & lag_name = p.first; + const auto & tdc = m_handlers[lag_name]; + teamdctl_disconnect(tdc); + teamdctl_free(tdc); + SWSS_LOG_NOTICE("Exiting. Disconnecting from teamd. LAG '%s'", lag_name.c_str()); + } +} + +/// +/// Returns true, if we have LAG with name lag_name +/// in the manager. +/// @param lag_name a name for LAG interface +/// @return true if has key, false if doesn't +/// +bool TeamdCtlMgr::has_key(const std::string & lag_name) const +{ + return m_handlers.find(lag_name) != m_handlers.end(); +} + +/// +/// Adds a LAG interface with lag_name to the manager +/// This method allocates structures to connect to teamd +/// @param lag_name a name for LAG interface +/// @return true if the lag was added successfully, false otherwise +/// +bool TeamdCtlMgr::add_lag(const std::string & lag_name) +{ + if (has_key(lag_name)) + { + SWSS_LOG_DEBUG("The LAG '%s' was already added. Skip adding it.", lag_name.c_str()); + return true; + } + else + { + auto tdc = teamdctl_alloc(); + if (!tdc) + { + SWSS_LOG_ERROR("Can't allocate memory for teamdctl handler. LAG='%s'", lag_name.c_str()); + return false; + } + + int err = teamdctl_connect(tdc, lag_name.c_str(), nullptr, nullptr); + if (err) + { + SWSS_LOG_ERROR("Can't connect to teamd LAG='%s', error='%s'", lag_name.c_str(), strerror(-err)); + teamdctl_free(tdc); + return false; + } + m_handlers.emplace(lag_name, tdc); + SWSS_LOG_NOTICE("The LAG '%s' has been added.", lag_name.c_str()); + } + + return true; +} + +/// +/// Removes a LAG interface with lag_name from the manager +/// This method deallocates teamd structures +/// @param lag_name a name for LAG interface +/// @return true if the lag was removed successfully, false otherwise +/// +bool TeamdCtlMgr::remove_lag(const std::string & lag_name) +{ + if (has_key(lag_name)) + { + auto tdc = m_handlers[lag_name]; + teamdctl_disconnect(tdc); + teamdctl_free(tdc); + m_handlers.erase(lag_name); + SWSS_LOG_NOTICE("The LAG '%s' has been removed.", lag_name.c_str()); + } + else + { + SWSS_LOG_WARN("The LAG '%s' hasn't been added. Can't remove it", lag_name.c_str()); + } + return true; +} + +/// +/// Get json dump from teamd for LAG interface with name lag_name +/// @param lag_name a name for LAG interface +/// @return a pair. First element of the pair is true, if the method is successful +/// false otherwise. If the first element is true, the second element has a dump +/// otherwise the second element is an empty string +/// +TeamdCtlDump TeamdCtlMgr::get_dump(const std::string & lag_name) +{ + TeamdCtlDump res = { false, "" }; + if (has_key(lag_name)) + { + auto tdc = m_handlers[lag_name]; + char * dump; + int r = teamdctl_state_get_raw_direct(tdc, &dump); + if (r == 0) + { + res = { true, std::string(dump) }; + } + else + { + SWSS_LOG_ERROR("Can't get dump for LAG '%s'. Skipping", lag_name.c_str()); + } + } + else + { + SWSS_LOG_ERROR("Can't update state. LAG not found. LAG='%s'", lag_name.c_str()); + } + + return res; +} + +/// +/// Get dumps for all registered LAG interfaces +/// @return vector of pairs. Each pair first value is a name of LAG, second value is a dump +/// +TeamdCtlDumps TeamdCtlMgr::get_dumps() +{ + TeamdCtlDumps res; + + for (const auto & p: m_handlers) + { + const auto & lag_name = p.first; + const auto & result = get_dump(lag_name); + const auto & status = result.first; + const auto & dump = result.second; + if (status) + { + res.push_back({ lag_name, dump }); + } + } + + return res; +} + diff --git a/tlm_teamd/teamdctl_mgr.h b/tlm_teamd/teamdctl_mgr.h new file mode 100644 index 000000000000..07eaba25dd0d --- /dev/null +++ b/tlm_teamd/teamdctl_mgr.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +#include + +using TeamdCtlDump = std::pair; +using TeamdCtlDumpsEntry = std::pair; +using TeamdCtlDumps = std::vector; + +class TeamdCtlMgr +{ +public: + TeamdCtlMgr() = default; + ~TeamdCtlMgr(); + bool add_lag(const std::string & kag_name); + bool remove_lag(const std::string & kag_name); + TeamdCtlDump get_dump(const std::string & lag_name); + TeamdCtlDumps get_dumps(); + +private: + bool has_key(const std::string & lag_name) const; + + std::unordered_map m_handlers; +}; diff --git a/tlm_teamd/values_store.cpp b/tlm_teamd/values_store.cpp new file mode 100644 index 000000000000..53af40e946a2 --- /dev/null +++ b/tlm_teamd/values_store.cpp @@ -0,0 +1,378 @@ +#include + +#include +#include + +#include "values_store.h" + +/// +/// Extract port names from teamd status json dump. +/// @return vector of LAG member port names from the dump +/// +std::vector ValuesStore::get_ports(json_t * root) +{ + std::vector result; + json_t * ports = nullptr; + int err = json_unpack(root, "{s:o}", "ports", &ports); + if (err != 0) + { + throw std::runtime_error("Can't find 'ports' in the json dump object"); + } + + const char * key; + json_t * value; + json_object_foreach(ports, key, value) + { + result.emplace_back(std::string(key)); + } + + return result; +} + +/// +/// Split the json path in format "key1.key2.key3" to the internal format +/// where all elements except last are put to the vector, and last key +/// extract separately. +/// @return a pair. The first element in the pair is the vector of the path elements +/// The second element in the pair is the last element of the path elements +/// +std::pair, std::string> ValuesStore::convert_path(const std::string & path) +{ + size_t last = 0, next = 0; + std::vector result; + while ((next = path.find('.', last)) != std::string::npos) + { + result.emplace_back(path.substr(last, next-last)); + last = next + 1; + } + return { result, path.substr(last) }; +} + +/// +/// Traverse parsed json structure to find correct object to extract the value +/// @param root parsed json structure +/// @param path_array path array as a first element from ValuesStore::convert_path() +/// @param path canonical path +/// +json_t * ValuesStore::traverse(json_t * root, const std::vector & path_array, const std::string & path) +{ + json_t * cur_root = root; + for (const auto & key: path_array) + { + json_t * cur = nullptr; + int err = json_unpack(cur_root, "{s:o}", key.c_str(), &cur); + if (err != 0) + { + throw std::runtime_error("Can't traverse through the path '" + path + "'. not found key = " + key); + } + cur_root = cur; + } + + return cur_root; +} + +/// +/// Extract json string value from json structure +/// @return extracted string +/// +std::string ValuesStore::unpack_string(json_t * root, const std::string & key, const std::string & path) +{ + const auto & c_key = key.c_str(); + const char * str = nullptr; + int err = json_unpack(root, "{s:s}", c_key, &str); + if (err != 0) + { + throw std::runtime_error("Can't unpack a string. key='" + path + "' json='" + json_dumps(root, 0) + "'"); + } + return std::string(str); +} + +/// +/// Extract json boolean value from json structure +/// @return extracted boolean as a string +/// +std::string ValuesStore::unpack_boolean(json_t * root, const std::string & key, const std::string & path) +{ + const auto & c_key = key.c_str(); + int value; + int err = json_unpack(root, "{s:b}", c_key, &value); + if (err != 0) + { + throw std::runtime_error("Can't unpack a boolean. key='" + path + "' json='" + json_dumps(root, 0) + "'"); + } + return std::string(value ? "true" : "false"); +} + +/// +/// Extract json integer value from json structure +/// @return extracted integer as a string +/// +std::string ValuesStore::unpack_integer(json_t * root, const std::string & key, const std::string & path) +{ + const auto & c_key = key.c_str(); + int value; + int err = json_unpack(root, "{s:i}", c_key, &value); + if (err != 0) + { + throw std::runtime_error("Can't unpack an integer. key='" + path + "' json='" + json_dumps(root, 0) + "'"); + } + return std::to_string(value); +} + +/// +/// Extract a value from the parsed json. Path to the value is defined by path, and type of the value +/// is defined by type. +/// @param root a pointer to parsed json structure +/// @param path a canonical path to the value +/// @param type a type of the value +/// +std::string ValuesStore::get_value(json_t * root, const std::string & path, ValuesStore::json_type type) +{ + auto path_pair = convert_path(path); + + json_t * found_object = traverse(root, path_pair.first, path); + + const auto & key = path_pair.second; + + switch (type) + { + case ValuesStore::json_type::string: return unpack_string(found_object, key, path); + case ValuesStore::json_type::boolean: return unpack_boolean(found_object, key, path); + case ValuesStore::json_type::integer: return unpack_integer(found_object, key, path); + } + + throw std::runtime_error("Reach the end of the ValuesStore::get_value. Path=" + path); +} + +/// +/// Extract values for LAG with name lag_name, from the parsed json tree with root, to the temporary storage +/// @param lag_name a name of the LAG +/// @param root a pointer to the parsed json tree +/// @param storage a reference to the temporary storage +/// +void ValuesStore::extract_values(const std::string & lag_name, json_t * root, HashOfRecords & storage) +{ + + const std::string key = "LAG_TABLE|" + lag_name; + Records lag_values; + for (const auto & p: m_lag_paths) + { + const auto & path = p.first; + const auto & type = p.second; + const auto & value = get_value(root, path, type); + lag_values.emplace(path, value); + } + storage.emplace(key, lag_values); + + const auto & ports = get_ports(root); + for (const auto & port: ports) + { + const std::string key = "LAG_MEMBER_TABLE|" + lag_name + "|" + port; + Records member_values; + for (const auto & p: m_member_paths) + { + const auto & path = p.first; + const auto & type = p.second; + const std::string full_path = "ports." + port + "." + path; + const auto & value = get_value(root, full_path, type); + member_values.emplace(path, value); + } + storage.emplace(key, member_values); + } + + return; +} + +/// +/// Parse json from the data +/// @return a pointer to the parsed json tree +/// +json_t * ValuesStore::load_json(const std::string & data) +{ + json_t * root; + json_error_t error; + root = json_loads(data.c_str(), 0, &error); + if (!root) + { + throw std::runtime_error("Can't parse json dump = '" + data + "'"); + } + + return root; +} + + +/// +/// Convert json input from all teamds to the temporary storage +/// @param dumps dumps from all teamds. It is a vector of pairs. Each pair +/// has a first element - name of the LAG and a second element +/// - json dump +/// @return temporary storage +/// +HashOfRecords ValuesStore::from_json(const std::vector & dumps) +{ + HashOfRecords storage; + for (const auto & p: dumps) + { + const auto & lag_name = p.first; + const auto & json_dump = p.second; + json_t * root = load_json(json_dump); + extract_values(lag_name, root, storage); + json_decref(root); + } + + return storage; +} + +/// +/// Extract a list of stale keys from the storage. +/// The stale key is a key which a presented in the storage, but not presented +/// in the temporary storage. That means that the key must be removed +/// @param storage a reference to the temporary storage +/// @return list of stale keys +/// +std::vector ValuesStore::get_old_keys(const HashOfRecords & storage) +{ + std::vector old_keys; + for (const auto & p: m_storage) + { + const auto & db_key = p.first; + if (storage.find(db_key) == storage.end()) + { + old_keys.push_back(db_key); + } + } + + return old_keys; +} + +/// +/// Remove keys from vector keys from the storage +/// @param keys a list of keys to remove from the storage +/// +void ValuesStore::remove_keys_storage(const std::vector & keys) +{ + for (const auto & key: keys) + { + m_storage.erase(key); + } +} + +/// +/// Split a full key to the database key and entry key +/// For example" TABLE|entry_key would return { "TABLE", "entry_key" } +/// @param key a database key. +/// @return a pair for keys +/// +StringPair ValuesStore::split_key(const std::string & key) +{ + auto sep_pos = key.find('|'); + assert(sep_pos != std::string::npos); + return std::make_pair(key.substr(0, sep_pos), key.substr(sep_pos + 1)); +} + +/// +/// Remove keys from the db +/// @param keys a list of keys to remove +/// +void ValuesStore::remove_keys_db(const std::vector & keys) +{ + for (const auto & key: keys) + { + const auto & p = split_key(key); + const auto & table_name = p.first; + const auto & table_key = p.second; + swss::Table table(m_db, table_name); + table.del(table_key); + } +} + +/// +/// Update the storage with values from the temporary storage +/// The update is the following: +/// 1. For each key in the temporary storage we check that we have that key in the storage +/// 2. if not, we insert the key and value to the storage +/// 3. if yes, we check that value of the key is not changed. If the value is changed we +/// replace that value with the value from the temporary storage +/// This method returns a list of keys which should be updated in the database +/// @param storage the temporary storage +/// @retorun a list of keys which must be updated in the storage +/// +std::vector ValuesStore::update_storage(const HashOfRecords & storage) +{ + std::vector to_update; + + for (const auto & entry_pair: storage) + { + const auto & entry_key = entry_pair.first; + const auto & entry_values = entry_pair.second; + if (m_storage.find(entry_key) == m_storage.end()) + { + m_storage.emplace(entry_pair); + to_update.emplace_back(entry_key); + } + else + { + bool is_changed = false; + for (const auto & row_pair: entry_values) + { + const auto & row_key = row_pair.first; + const auto & row_value = row_pair.second; + if (m_storage[entry_key][row_key] != row_value) + { + is_changed = true; + break; + } + } + + if (is_changed) + { + m_storage.erase(entry_key); + m_storage.emplace(entry_pair); + to_update.emplace_back(entry_key); + } + } + } + + return to_update; +} + +/// +/// Update values in the db with values from the temporary storage +/// @param storage a reference to the temporary storage +/// @param keys_to_refresh a list of keys which must be refreshed in the db +/// +void ValuesStore::update_db(const HashOfRecords & storage, const std::vector & keys_to_refresh) +{ + for (const auto & key: keys_to_refresh) + { + std::vector fvp; + for (const auto & row_pair: storage.at(key)) + { + fvp.emplace_back(row_pair); + } + const auto & table_pair = split_key(key); + swss::Table table(m_db, table_pair.first); + table.set(table_pair.second, fvp); + } +} + + +/// +/// Update the storage with json dumps for every registered LAG interface. +/// +void ValuesStore::update(const std::vector & dumps) +{ + try + { + const auto & storage = from_json(dumps); + const auto & old_keys = get_old_keys(storage); + remove_keys_db(old_keys); + remove_keys_storage(old_keys); + const auto & keys_to_refresh = update_storage(storage); + update_db(storage, keys_to_refresh); + } + catch (const std::exception & e) + { + SWSS_LOG_WARN("Exception '%s' had been thrown in ValuesStore", e.what()); + } +} diff --git a/tlm_teamd/values_store.h b/tlm_teamd/values_store.h new file mode 100644 index 000000000000..7ad353d8ffa0 --- /dev/null +++ b/tlm_teamd/values_store.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include + +#include + +#include + +using StringPair = std::pair; +using Records = std::unordered_map; +using HashOfRecords = std::unordered_map; + +class ValuesStore +{ +public: + ValuesStore(const swss::DBConnector * db) : m_db(db) {}; + void update(const std::vector & dumps); + +private: + enum class json_type + { + string, + boolean, + integer, + }; + + json_t * load_json(const std::string & data); + std::vector get_ports(json_t * root); + std::pair, std::string> convert_path(const std::string & path); + json_t * traverse(json_t * root, const std::vector & path_array, const std::string & path); + std::string unpack_string(json_t * root, const std::string & key, const std::string & path); + std::string unpack_boolean(json_t * root, const std::string & key, const std::string & path); + std::string unpack_integer(json_t * root, const std::string & key, const std::string & path); + std::string get_value(json_t * root, const std::string & path, ValuesStore::json_type type); + HashOfRecords from_json(const std::vector & dumps); + std::vector get_old_keys(const HashOfRecords & storage); + void remove_keys_storage(const std::vector & keys); + void remove_keys_db(const std::vector & keys); + StringPair split_key(const std::string & key); + std::vector update_storage(const HashOfRecords & storage); + void update_db(const HashOfRecords & storage, const std::vector & keys_to_refresh); + void extract_values(const std::string & lag_name, json_t * root, HashOfRecords & storage); + + HashOfRecords m_storage; // our main storage + const swss::DBConnector * m_db; + + const std::vector> m_lag_paths = { + { "setup.kernel_team_mode_name", ValuesStore::json_type::string }, + { "setup.pid", ValuesStore::json_type::integer }, + { "runner.active", ValuesStore::json_type::boolean }, + { "runner.fallback", ValuesStore::json_type::boolean }, + { "runner.fast_rate", ValuesStore::json_type::boolean }, + { "team_device.ifinfo.dev_addr", ValuesStore::json_type::string }, + { "team_device.ifinfo.ifindex", ValuesStore::json_type::integer }, + }; + const std::vector> m_member_paths = { + { "ifinfo.dev_addr", ValuesStore::json_type::string }, + { "ifinfo.ifindex", ValuesStore::json_type::integer }, + { "link.up", ValuesStore::json_type::boolean }, + { "link_watches.list.link_watch_0.up", ValuesStore::json_type::boolean }, + { "runner.actor_lacpdu_info.port", ValuesStore::json_type::integer }, + { "runner.actor_lacpdu_info.state", ValuesStore::json_type::integer }, + { "runner.actor_lacpdu_info.system", ValuesStore::json_type::string }, + { "runner.partner_lacpdu_info.port", ValuesStore::json_type::integer }, + { "runner.partner_lacpdu_info.state", ValuesStore::json_type::integer }, + { "runner.partner_lacpdu_info.system", ValuesStore::json_type::string }, + { "runner.aggregator.id", ValuesStore::json_type::integer }, + { "runner.aggregator.selected", ValuesStore::json_type::boolean }, + { "runner.selected", ValuesStore::json_type::boolean }, + { "runner.state", ValuesStore::json_type::string }, + }; +};