diff --git a/common/configdb.cpp b/common/configdb.cpp index ececb2d3f..af49024a1 100644 --- a/common/configdb.cpp +++ b/common/configdb.cpp @@ -233,6 +233,30 @@ void ConfigDBConnector_Native::mod_config(const map>>& data, + const vector& order) +{ + for (auto const& table_name: order) + { + auto const& table_data_iter = data.find(table_name); + if (table_data_iter == data.end() || table_data_iter->second.empty() ) + { + delete_table(table_name); + continue; + } + for (auto const& ie: table_data_iter->second) + { + string key = ie.first; + auto const& fvs = ie.second; + mod_entry(table_name, key, fvs); + } + } +} + // Read all config data. // Returns: // Config data in a dictionary form of @@ -424,6 +448,35 @@ void ConfigDBPipeConnector_Native::mod_config(const map>>& data, + const vector& order) +{ + auto& client = get_redis_client(m_db_name); + DBConnector clientPipe(client); + RedisTransactioner pipe(&clientPipe); + pipe.multi(); + for (auto const& table_name: order) + { + auto const& table_data_iter = data.find(table_name); + if (table_data_iter == data.end() || table_data_iter->second.empty() ) + { + _delete_table(client, pipe, table_name); + continue; + } + for (auto const& it: table_data_iter->second) + { + auto& key = it.first; + _mod_entry(pipe, table_name, key, it.second); + } + } + pipe.exec(); +} + + // Read config data in batches of size REDIS_SCAN_BATCH_SIZE using Redis pipelines // Args: // client: Redis client diff --git a/common/configdb.h b/common/configdb.h index 1611117a8..dbd8cef1f 100644 --- a/common/configdb.h +++ b/common/configdb.h @@ -24,6 +24,8 @@ class ConfigDBConnector_Native : public SonicV2Connector_Native std::map> get_table(std::string table); void delete_table(std::string table); virtual void mod_config(const std::map>>& data); + virtual void mod_config(const std::unordered_map>>& data, + const std::vector& order); virtual std::map>> get_config(); std::string getKeySeparator() const; @@ -235,7 +237,7 @@ func NewConfigDBConnector(a ...interface{}) *ConfigDBConnector { raw_key = self.serialize_key(key) raw_data = self.typed_to_raw(data) raw_config[table_name][raw_key] = raw_data - super(ConfigDBConnector, self).mod_config(raw_config) + super(ConfigDBConnector, self).mod_config(raw_config, list(raw_config)) def mod_entry(self, table, key, data): key = self.serialize_key(key) @@ -331,6 +333,8 @@ class ConfigDBPipeConnector_Native: public ConfigDBConnector_Native void set_entry(std::string table, std::string key, const std::map& data) override; void mod_config(const std::map>>& data) override; + void mod_config(const std::unordered_map>>& data, + const std::vector& order) override; std::map>> get_config() override; private: diff --git a/pyext/swsscommon.i b/pyext/swsscommon.i index b3d015e03..49bba75da 100644 --- a/pyext/swsscommon.i +++ b/pyext/swsscommon.i @@ -67,6 +67,7 @@ %include %include #ifdef SWIGPYTHON +%include %include #endif %include @@ -81,6 +82,9 @@ %template(ScanResult) std::pair>; %template(GetTableResult) std::map>; %template(GetConfigResult) std::map>>; +#ifdef SWIGPYTHON +%template(GetUnorderedConfigResult) std::unordered_map>>; +#endif %template(GetInstanceListResult) std::map; %template(KeyOpFieldsValuesQueue) std::deque>>>; %template(VectorSonicDbKey) std::vector; diff --git a/tests/test_redis_ut.py b/tests/test_redis_ut.py index bf395a34f..7ee5b93e2 100644 --- a/tests/test_redis_ut.py +++ b/tests/test_redis_ut.py @@ -831,6 +831,64 @@ def test_ConfigDBConnector_with_statement(self): # check close() method called by with statement ConfigDBConnector.close.assert_called_once_with() +def _test_ConfigDBConnectorSetOrder(cls): + table_name_1 = "PREFIX_SET" + table_name_2 = "PREFIX" + inp = { + table_name_1: { + "test_prefix_set": { + "mode": "IPv4" + } + }, + table_name_2: { + "test_prefix_set|10.1.0.0/24|exact": { + "action": "permit" + } + } + } + + ret_queue = multiprocessing.Queue() + + def thread_listen(ret_queue): + config_db = ConfigDBConnector() + config_db.connect(wait_for_init=False) + + def handler(table, key, data): + print('thread_listen received', table, key, data) + ret_queue.put(table) + + config_db.subscribe(table_name_1, handler) + config_db.subscribe(table_name_2, handler) + config_db.listen() + + config_db = cls() + config_db.connect(wait_for_init=False) + client = config_db.get_redis_client(config_db.CONFIG_DB) + client.flushdb() + + listener = multiprocessing.Process(target=thread_listen, args=(ret_queue,)) + listener.start() + + # Wait until the listener has connected before modifying the config + time.sleep(2) + + try: + config_db.mod_config(inp) + # Check that items are added to the queue in order + assert ret_queue.get(timeout=5) == table_name_1 + assert ret_queue.get(timeout=5) == table_name_2 + finally: + listener.terminate() + +def test_ConfigDBConnectorSetOrder(): + """Check that ConfigDBConnector::mod_config updates tables in order of the + input Python dict""" + _test_ConfigDBConnectorSetOrder(ConfigDBConnector) + +def test_ConfigDBPipeConnectorSetOrder(): + """Check that ConfigDBPipeConnector::mod_config updates tables in order of the + input Python dict""" + _test_ConfigDBConnectorSetOrder(ConfigDBPipeConnector) def test_SmartSwitchDBConnector(): test_dir = os.path.dirname(os.path.abspath(__file__)) @@ -878,4 +936,3 @@ def test_TableOpsMemoryLeak(): t.set("long_data", fvs) t.get("long_data") assert psutil.Process(os.getpid()).memory_info().rss - rss < OP_COUNT -