From 1ea30d2e27bf4e9c9127611e72f5b3bd74025731 Mon Sep 17 00:00:00 2001 From: Qi Luo Date: Thu, 29 Oct 2020 17:54:56 -0700 Subject: [PATCH] Fix bug: ConfigDBConnector.get_table does not work in python3 (#92) `ConfigDBConnector` is not working well under python3, for example: ``` Oct 29 21:56:58.496505 sonic INFO hostcfgd[20662]: Traceback (most recent call last): Oct 29 21:56:58.496724 sonic INFO hostcfgd[20662]: File "/usr/bin/hostcfgd", line 387, in Oct 29 21:56:58.496873 sonic INFO hostcfgd[20662]: main() Oct 29 21:56:58.497014 sonic INFO hostcfgd[20662]: File "/usr/bin/hostcfgd", line 382, in main Oct 29 21:56:58.497147 sonic INFO hostcfgd[20662]: daemon = HostConfigDaemon() Oct 29 21:56:58.497939 sonic INFO hostcfgd[20662]: File "/usr/bin/hostcfgd", line 237, in __init__ Oct 29 21:56:58.498103 sonic INFO hostcfgd[20662]: tacacs_server = self.config_db.get_table('TACPLUS_SERVER') Oct 29 21:56:58.498236 sonic INFO hostcfgd[20662]: File "/usr/local/lib/python3.7/dist-packages/swsssdk/configdb.py", line 269, in get_table Oct 29 21:56:58.498388 sonic INFO hostcfgd[20662]: entry = self.raw_to_typed(client.hgetall(key)) Oct 29 21:56:58.498523 sonic INFO hostcfgd[20662]: File "/usr/local/lib/python3.7/dist-packages/swsssdk/configdb.py", line 123, in raw_to_typed Oct 29 21:56:58.498649 sonic INFO hostcfgd[20662]: key = raw_key.decode() Oct 29 21:56:58.498779 sonic INFO hostcfgd[20662]: AttributeError: 'str' object has no attribute 'decode' ``` So I will force parent class `SonicV2Connector` to handle all the `encode` / `decode` by specify `decode_responses=True` in its constructor parameters. But the class `ConfigDBConnector` will refuse contructed by `decode_responses=False`, which is okay to existing code base. Tested in DUT: ``` Python 3.7.3 (default, Jul 25 2020, 13:03:44) [GCC 8.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from swsssdk import ConfigDBConnector >>> db = ConfigDBConnector() >>> db.dbintf.redis_kwargs {'host': '127.0.0.1', 'decode_responses': True} >>> db.connect() >>> db.get_table('TACPLUS_SERVER') {'10.10.10.10': {'priority': '1', 'tcp_port': '49'}} ``` --- src/swsssdk/configdb.py | 45 +++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/src/swsssdk/configdb.py b/src/swsssdk/configdb.py index c5d7570c086a..85764ad38fee 100644 --- a/src/swsssdk/configdb.py +++ b/src/swsssdk/configdb.py @@ -1,5 +1,5 @@ """ -SONiC ConfigDB connection module +SONiC ConfigDB connection module Example: # Write to config DB @@ -29,11 +29,16 @@ class ConfigDBConnector(SonicV2Connector): TABLE_NAME_SEPARATOR = '|' KEY_SEPARATOR = '|' - def __init__(self, **kwargs): + def __init__(self, decode_responses=True, **kwargs): # By default, connect to Redis through TCP, which does not requires root. if len(kwargs) == 0: kwargs['host'] = '127.0.0.1' + if PY3K: + if not decode_responses: + raise NotImplementedError('ConfigDBConnector with decode_responses=False is not supported in python3') + kwargs['decode_responses'] = True + """The ConfigDBConnector class will accept the parameter 'namespace' which is used to load the database_config and connect to the redis DB instances in that namespace. By default namespace is set to None, which means it connects to local redis DB instances. @@ -88,11 +93,11 @@ def unsubscribe(self, table): Args: table: Table name. """ - if self.handlers.has_key(table): + if table in self.handlers: self.handlers.pop(table) def __fire(self, table, key, data): - if self.handlers.has_key(table): + if table in self.handlers: handler = self.handlers[table] handler(table, key, data) @@ -106,7 +111,7 @@ def listen(self): key = item['channel'].split(':', 1)[1] try: (table, row) = key.split(self.TABLE_NAME_SEPARATOR, 1) - if self.handlers.has_key(table): + if table in self.handlers: client = self.get_redis_client(self.db_name) data = self.raw_to_typed(client.hgetall(key)) self.__fire(table, row, data) @@ -119,8 +124,6 @@ def raw_to_typed(self, raw_data): typed_data = {} for raw_key in raw_data: key = raw_key - if PY3K: - key = raw_key.decode() # "NULL:NULL" is used as a placeholder for objects with no attributes if key == "NULL": @@ -128,17 +131,10 @@ def raw_to_typed(self, raw_data): # A column key with ending '@' is used to mark list-typed table items # TODO: Replace this with a schema-based typing mechanism. elif key.endswith("@"): - value = "" - if PY3K: - value = raw_data[raw_key].decode("utf-8").split(',') - else: - value = raw_data[raw_key].split(',') + value = raw_data[raw_key].split(',') typed_data[key[:-1]] = value else: - if PY3K: - typed_data[key] = raw_data[raw_key].decode() - else: - typed_data[key] = raw_data[raw_key] + typed_data[key] = raw_data[raw_key] return typed_data def typed_to_raw(self, typed_data): @@ -188,7 +184,7 @@ def set_entry(self, table, key, data): else: original = self.get_entry(table, key) client.hmset(_hash, self.typed_to_raw(data)) - for k in [ k for k in original.keys() if k not in data.keys() ]: + for k in [ k for k in original if k not in data ]: if type(original[k]) == list: k = k + '@' client.hdel(_hash, self.serialize_key(k)) @@ -239,8 +235,6 @@ def get_keys(self, table, split=True): data = [] for key in keys: try: - if PY3K: - key = key.decode() if split: (_, row) = key.split(self.TABLE_NAME_SEPARATOR, 1) data.append(self.deserialize_key(row)) @@ -267,14 +261,9 @@ def get_table(self, table): for key in keys: try: entry = self.raw_to_typed(client.hgetall(key)) - if entry != None: - if PY3K: - key = key.decode() - (_, row) = key.split(self.TABLE_NAME_SEPARATOR, 1) - data[self.deserialize_key(row)] = entry - else: - (_, row) = key.split(self.TABLE_NAME_SEPARATOR, 1) - data[self.deserialize_key(row)] = entry + if entry is not None: + (_, row) = key.split(self.TABLE_NAME_SEPARATOR, 1) + data[self.deserialize_key(row)] = entry except ValueError: pass #Ignore non table-formated redis entries return data @@ -324,8 +313,6 @@ def get_config(self): keys = client.keys('*') data = {} for key in keys: - if PY3K: - key = key.decode() try: (table_name, row) = key.split(self.TABLE_NAME_SEPARATOR, 1) entry = self.raw_to_typed(client.hgetall(key))