Skip to content

Commit

Permalink
[configdb] Support hierarchical keys (sonic-net#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
taoyl-ms authored Sep 8, 2017
1 parent d476a25 commit 7f8e7c5
Showing 1 changed file with 49 additions and 16 deletions.
65 changes: 49 additions & 16 deletions src/swsssdk/configdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@
"""

import sys
import time
from .dbconnector import SonicV2Connector

class ConfigDBConnector(SonicV2Connector):

INIT_INDICATOR = 'CONFIG_DB_INITIALIZED'
TABLE_NAME_SEPARATOR = '|'
KEY_SEPARATOR = '|'

def __init__(self):
# Connect to Redis through TCP, which does not requires root.
Expand Down Expand Up @@ -84,7 +87,7 @@ def listen(self):
if item['type'] == 'pmessage':
key = item['channel'].split(':', 1)[1]
try:
(table, row) = key.split(':', 1)
(table, row) = key.split(self.TABLE_NAME_SEPARATOR, 1)
if self.handlers.has_key(table):
client = self.redis_clients[self.CONFIG_DB]
data = self.__raw_to_typed(client.hgetall(key))
Expand All @@ -94,12 +97,15 @@ def listen(self):

def __raw_to_typed(self, raw_data):
if raw_data == None:
return {}
return None
typed_data = {}
for key in raw_data:
# "NULL:NULL" is used as a placeholder for objects with no attributes
if key == "NULL":
pass
# A column key with ending '@' is used to mark list-typed table items
# TODO: Replace this with a schema-based typing mechanism.
if key.endswith("@"):
elif key.endswith("@"):
typed_data[key[:-1]] = raw_data[key].split(',')
else:
typed_data[key] = raw_data[key]
Expand All @@ -108,6 +114,8 @@ def __raw_to_typed(self, raw_data):
def __typed_to_raw(self, typed_data):
if typed_data == None:
return None
elif typed_data == {}:
return { "NULL": "NULL" }
raw_data = {}
for key in typed_data:
value = typed_data[key]
Expand All @@ -117,28 +125,50 @@ def __typed_to_raw(self, typed_data):
raw_data[key] = value
return raw_data

@staticmethod
def serialize_key(key):
if type(key) is tuple:
return ConfigDBConnector.KEY_SEPARATOR.join(key)
else:
return key

@staticmethod
def deserialize_key(key):
tokens = key.split(ConfigDBConnector.KEY_SEPARATOR)
if len(tokens) > 1:
return tuple(tokens)
else:
return key

def set_entry(self, table, key, data):
"""Write a table entry to config db.
Args:
table: Table name.
key: Key of table entry.
data: Table row data in a form of dictionary {'column_key': 'value', ...}
key: Key of table entry, or a tuple of keys if it is a multi-key table.
data: Table row data in a form of dictionary {'column_key': 'value', ...}.
Pass {} as data will create an entry with no column if not already existed.
Pass None as data will delete the entry.
"""
key = self.serialize_key(key)
client = self.redis_clients[self.CONFIG_DB]
_hash = '{}:{}'.format(table.upper(), key)
client.hmset(_hash, self.__typed_to_raw(data))
_hash = '{}{}{}'.format(table.upper(), self.TABLE_NAME_SEPARATOR, key)
if data == None:
client.delete(_hash)
else:
client.hmset(_hash, self.__typed_to_raw(data))

def get_entry(self, table, key):
"""Read a table entry from config db.
Args:
table: Table name.
key: Key of table entry.
key: Key of table entry, or a tuple of keys if it is a multi-key table.
Returns:
Table row data in a form of dictionary {'column_key': 'value', ...}
Empty dictionary if table does not exist or entry does not exist.
"""
key = self.serialize_key(key)
client = self.redis_clients[self.CONFIG_DB]
_hash = '{}:{}'.format(table.upper(), key)
_hash = '{}{}{}'.format(table.upper(), self.TABLE_NAME_SEPARATOR, key)
return self.__raw_to_typed(client.hgetall(_hash))

def get_table(self, table):
Expand All @@ -147,19 +177,20 @@ def get_table(self, table):
table: Table name.
Returns:
Table data in a dictionary form of
{ 'row_key': {'column_key': 'value', ...}, ...}
{ 'row_key': {'column_key': value, ...}, ...}
or { ('l1_key', 'l2_key', ...): {'column_key': value, ...}, ...} for a multi-key table.
Empty dictionary if table does not exist.
"""
client = self.redis_clients[self.CONFIG_DB]
pattern = '{}:*'.format(table.upper())
pattern = '{}{}*'.format(table.upper(), self.TABLE_NAME_SEPARATOR)
keys = client.keys(pattern)
data = {}
for key in keys:
try:
(_, row) = key.split(':', 1)
(_, row) = key.split(self.TABLE_NAME_SEPARATOR, 1)
entry = self.__raw_to_typed(client.hgetall(key))
if entry:
data[row] = entry
data[self.deserialize_key(row)] = entry
except ValueError:
pass #Ignore non table-formated redis entries
return data
Expand All @@ -170,6 +201,7 @@ def set_config(self, data):
data: config data in a dictionary form
{
'TABLE_NAME': { 'row_key': {'column_key': 'value', ...}, ...},
'MULTI_KEY_TABLE_NAME': { ('l1_key', 'l2_key', ...) : {'column_key': 'value', ...}, ...},
...
}
"""
Expand All @@ -184,6 +216,7 @@ def get_config(self):
Config data in a dictionary form of
{
'TABLE_NAME': { 'row_key': {'column_key': 'value', ...}, ...},
'MULTI_KEY_TABLE_NAME': { ('l1_key', 'l2_key', ...) : {'column_key': 'value', ...}, ...},
...
}
"""
Expand All @@ -192,10 +225,10 @@ def get_config(self):
data = {}
for key in keys:
try:
(table_name, row) = key.split(':', 1)
(table_name, row) = key.split(self.TABLE_NAME_SEPARATOR, 1)
entry = self.__raw_to_typed(client.hgetall(key))
if entry:
data.setdefault(table_name, {})[row] = entry
if entry != None:
data.setdefault(table_name, {})[self.deserialize_key(row)] = entry
except ValueError:
pass #Ignore non table-formated redis entries
return data
Expand Down

0 comments on commit 7f8e7c5

Please sign in to comment.