Skip to content

Commit 7f8e7c5

Browse files
authored
[configdb] Support hierarchical keys (#12)
1 parent d476a25 commit 7f8e7c5

File tree

1 file changed

+49
-16
lines changed

1 file changed

+49
-16
lines changed

src/swsssdk/configdb.py

+49-16
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818
1919
"""
2020

21+
import sys
2122
import time
2223
from .dbconnector import SonicV2Connector
2324

2425
class ConfigDBConnector(SonicV2Connector):
2526

2627
INIT_INDICATOR = 'CONFIG_DB_INITIALIZED'
28+
TABLE_NAME_SEPARATOR = '|'
29+
KEY_SEPARATOR = '|'
2730

2831
def __init__(self):
2932
# Connect to Redis through TCP, which does not requires root.
@@ -84,7 +87,7 @@ def listen(self):
8487
if item['type'] == 'pmessage':
8588
key = item['channel'].split(':', 1)[1]
8689
try:
87-
(table, row) = key.split(':', 1)
90+
(table, row) = key.split(self.TABLE_NAME_SEPARATOR, 1)
8891
if self.handlers.has_key(table):
8992
client = self.redis_clients[self.CONFIG_DB]
9093
data = self.__raw_to_typed(client.hgetall(key))
@@ -94,12 +97,15 @@ def listen(self):
9497

9598
def __raw_to_typed(self, raw_data):
9699
if raw_data == None:
97-
return {}
100+
return None
98101
typed_data = {}
99102
for key in raw_data:
103+
# "NULL:NULL" is used as a placeholder for objects with no attributes
104+
if key == "NULL":
105+
pass
100106
# A column key with ending '@' is used to mark list-typed table items
101107
# TODO: Replace this with a schema-based typing mechanism.
102-
if key.endswith("@"):
108+
elif key.endswith("@"):
103109
typed_data[key[:-1]] = raw_data[key].split(',')
104110
else:
105111
typed_data[key] = raw_data[key]
@@ -108,6 +114,8 @@ def __raw_to_typed(self, raw_data):
108114
def __typed_to_raw(self, typed_data):
109115
if typed_data == None:
110116
return None
117+
elif typed_data == {}:
118+
return { "NULL": "NULL" }
111119
raw_data = {}
112120
for key in typed_data:
113121
value = typed_data[key]
@@ -117,28 +125,50 @@ def __typed_to_raw(self, typed_data):
117125
raw_data[key] = value
118126
return raw_data
119127

128+
@staticmethod
129+
def serialize_key(key):
130+
if type(key) is tuple:
131+
return ConfigDBConnector.KEY_SEPARATOR.join(key)
132+
else:
133+
return key
134+
135+
@staticmethod
136+
def deserialize_key(key):
137+
tokens = key.split(ConfigDBConnector.KEY_SEPARATOR)
138+
if len(tokens) > 1:
139+
return tuple(tokens)
140+
else:
141+
return key
142+
120143
def set_entry(self, table, key, data):
121144
"""Write a table entry to config db.
122145
Args:
123146
table: Table name.
124-
key: Key of table entry.
125-
data: Table row data in a form of dictionary {'column_key': 'value', ...}
147+
key: Key of table entry, or a tuple of keys if it is a multi-key table.
148+
data: Table row data in a form of dictionary {'column_key': 'value', ...}.
149+
Pass {} as data will create an entry with no column if not already existed.
150+
Pass None as data will delete the entry.
126151
"""
152+
key = self.serialize_key(key)
127153
client = self.redis_clients[self.CONFIG_DB]
128-
_hash = '{}:{}'.format(table.upper(), key)
129-
client.hmset(_hash, self.__typed_to_raw(data))
154+
_hash = '{}{}{}'.format(table.upper(), self.TABLE_NAME_SEPARATOR, key)
155+
if data == None:
156+
client.delete(_hash)
157+
else:
158+
client.hmset(_hash, self.__typed_to_raw(data))
130159

131160
def get_entry(self, table, key):
132161
"""Read a table entry from config db.
133162
Args:
134163
table: Table name.
135-
key: Key of table entry.
164+
key: Key of table entry, or a tuple of keys if it is a multi-key table.
136165
Returns:
137166
Table row data in a form of dictionary {'column_key': 'value', ...}
138167
Empty dictionary if table does not exist or entry does not exist.
139168
"""
169+
key = self.serialize_key(key)
140170
client = self.redis_clients[self.CONFIG_DB]
141-
_hash = '{}:{}'.format(table.upper(), key)
171+
_hash = '{}{}{}'.format(table.upper(), self.TABLE_NAME_SEPARATOR, key)
142172
return self.__raw_to_typed(client.hgetall(_hash))
143173

144174
def get_table(self, table):
@@ -147,19 +177,20 @@ def get_table(self, table):
147177
table: Table name.
148178
Returns:
149179
Table data in a dictionary form of
150-
{ 'row_key': {'column_key': 'value', ...}, ...}
180+
{ 'row_key': {'column_key': value, ...}, ...}
181+
or { ('l1_key', 'l2_key', ...): {'column_key': value, ...}, ...} for a multi-key table.
151182
Empty dictionary if table does not exist.
152183
"""
153184
client = self.redis_clients[self.CONFIG_DB]
154-
pattern = '{}:*'.format(table.upper())
185+
pattern = '{}{}*'.format(table.upper(), self.TABLE_NAME_SEPARATOR)
155186
keys = client.keys(pattern)
156187
data = {}
157188
for key in keys:
158189
try:
159-
(_, row) = key.split(':', 1)
190+
(_, row) = key.split(self.TABLE_NAME_SEPARATOR, 1)
160191
entry = self.__raw_to_typed(client.hgetall(key))
161192
if entry:
162-
data[row] = entry
193+
data[self.deserialize_key(row)] = entry
163194
except ValueError:
164195
pass #Ignore non table-formated redis entries
165196
return data
@@ -170,6 +201,7 @@ def set_config(self, data):
170201
data: config data in a dictionary form
171202
{
172203
'TABLE_NAME': { 'row_key': {'column_key': 'value', ...}, ...},
204+
'MULTI_KEY_TABLE_NAME': { ('l1_key', 'l2_key', ...) : {'column_key': 'value', ...}, ...},
173205
...
174206
}
175207
"""
@@ -184,6 +216,7 @@ def get_config(self):
184216
Config data in a dictionary form of
185217
{
186218
'TABLE_NAME': { 'row_key': {'column_key': 'value', ...}, ...},
219+
'MULTI_KEY_TABLE_NAME': { ('l1_key', 'l2_key', ...) : {'column_key': 'value', ...}, ...},
187220
...
188221
}
189222
"""
@@ -192,10 +225,10 @@ def get_config(self):
192225
data = {}
193226
for key in keys:
194227
try:
195-
(table_name, row) = key.split(':', 1)
228+
(table_name, row) = key.split(self.TABLE_NAME_SEPARATOR, 1)
196229
entry = self.__raw_to_typed(client.hgetall(key))
197-
if entry:
198-
data.setdefault(table_name, {})[row] = entry
230+
if entry != None:
231+
data.setdefault(table_name, {})[self.deserialize_key(row)] = entry
199232
except ValueError:
200233
pass #Ignore non table-formated redis entries
201234
return data

0 commit comments

Comments
 (0)