Skip to content

Commit

Permalink
Namespace support in SonicV2Connector (sonic-net#63)
Browse files Browse the repository at this point in the history
This PR is the changes needed to support multiple namespaces for the Multi-ASIC devices. Multi-DB namespace PR --> (sonic-net/SONiC#567)

The changes are mainly to these classes

SonicDBConfig
SonicV2Connector/ConfigDBConnector

A new parameter "namespace" is added to the SonicV2Connector class init , to pass the namespace name. The default value is None representing empty namespace.

class SonicV2Connector(DBInterface):
def init(self, use_unix_socket_path=False, namespace=None, **kwargs):

If the user don't explicitly set this parameter, namespace takes None as value, and connects to the db_name in the local context, which refers to the database docker running in the current namespace wherever you are running this application/script …. (It could be either Linux host or any specific network namespace ). In this way it is backward compatible and the existing behavior of APIs in these utility classes are maintained.

In the SonicDBConfig, a new API is introduced load_sonic_global_db_config() which loads the /var/run/redis/sonic-db/database_global.json file if present. This file has the mapping between the namespace name and the corresponding database_config.json file.
  • Loading branch information
judyjoseph committed Apr 23, 2020
1 parent 154f381 commit b9cee36
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 50 deletions.
10 changes: 10 additions & 0 deletions src/swsssdk/configdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ def __init__(self, **kwargs):
# By default, connect to Redis through TCP, which does not requires root.
if len(kwargs) == 0:
kwargs['host'] = '127.0.0.1'

"""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.
When connecting to a different namespace set the use_unix_socket_path flag to true.
Eg. ConfigDBConnector(use_unix_socket_path=True, namespace=namespace)
'namespace' is implicitly passed to the parent SonicV2Connector class.
"""
super(ConfigDBConnector, self).__init__(**kwargs)
self.handlers = {}

Expand Down
204 changes: 165 additions & 39 deletions src/swsssdk/dbconnector.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,76 @@
# FIXME: Convert to metaclasses when Py2 support is removed. Metaclasses have unique interfaces to Python2/Python3.

class SonicDBConfig(object):
SONIC_DB_GLOBAL_CONFIG_FILE = "/var/run/redis/sonic-db/database_global.json"
SONIC_DB_CONFIG_FILE = "/var/run/redis/sonic-db/database_config.json"
_sonic_db_config_dir = "/var/run/redis/sonic-db"
_sonic_db_global_config_init = False
_sonic_db_config_init = False
_sonic_db_config = {}

"""This is the database_global.json parse and load API. This file has the namespace name and
the corresponding database_config.json file. The global file is significant for the
applications running in the linux host namespace, like eg: config/show cli, snmp etc which
needs to connect to databases running in other namespaces. If the "namespace" attribute is not
specified for an "include" attribute, it refers to the linux host namespace.
If the user passes namespace parameter, this API loads json file for that namespace alone.
"""
@staticmethod
def load_sonic_global_db_config(global_db_file_path=SONIC_DB_GLOBAL_CONFIG_FILE, namespace=None):
"""
Parse and load the global database config json file
"""
if SonicDBConfig._sonic_db_global_config_init == True:
return

if os.path.isfile(global_db_file_path) == True:
global_db_config_dir = os.path.dirname(global_db_file_path)
with open(global_db_file_path, "r") as read_file:
all_ns_dbs = json.load(read_file)
for entry in all_ns_dbs['INCLUDES']:
if 'namespace' not in entry.keys():
# If the user already invoked load_sonic_db_config() explicitly to load the
# database_config.json file for current namesapce, skip loading the file
# referenced here in the global config file.
if SonicDBConfig._sonic_db_config_init == True:
continue
ns = ''
else:
ns = entry['namespace']

# If API is called with a namespace parameter, load the json file only for that namespace.
if namespace is not None and ns != namespace:
continue;

# Check if _sonic_db_config already have this namespace present
if ns in SonicDBConfig._sonic_db_config:
msg = "The database_config for this namespace '{}' is already parsed. !!".format(ns)
logger.warning(msg)
continue

db_include_file = os.path.join(global_db_config_dir, entry['include'])

# Not finding the database_config.json file for the namespace
if os.path.isfile(db_include_file) == False:
msg = "'{}' file is not found !!".format(db_include_file)
logger.warning(msg)
continue

# As we load the database_config.json file for current namesapce,
# set the _sonic_db_config_init flag to True to prevent loading again
# by the API load_sonic_db_config()
if ns is '':
SonicDBConfig._sonic_db_config_init = True

with open(db_include_file, "r") as inc_file:
SonicDBConfig._sonic_db_config[ns] = json.load(inc_file)

# If API is called with a namespace parameter,we break here as we loaded the json file.
if namespace is not None and ns == namespace:
break;

SonicDBConfig._sonic_db_global_config_init = True

@staticmethod
def load_sonic_db_config(sonic_db_file_path=SONIC_DB_CONFIG_FILE):
"""
Expand All @@ -27,91 +93,151 @@ def load_sonic_db_config(sonic_db_file_path=SONIC_DB_CONFIG_FILE):
logger.warning(msg)
sonic_db_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config', 'database_config.json')
with open(sonic_db_file_path, "r") as read_file:
SonicDBConfig._sonic_db_config = json.load(read_file)
# The database_config.json is loaded with '' as key. This refers to the local namespace.
SonicDBConfig._sonic_db_config[''] = json.load(read_file)
except (OSError, IOError):
msg = "Could not open sonic database config file '{}'".format(sonic_db_file_path)
logger.exception(msg)
raise RuntimeError(msg)
SonicDBConfig._sonic_db_config_init = True

@staticmethod
def db_name_validation(db_name):
def namespace_validation(namespace):
# Check the namespace is valid.
if namespace is None:
msg = "invalid namespace name given as input"
logger.warning(msg)
raise RuntimeError(msg)

# Check if the global config is loaded entirely or for the namespace
if namespace != '' and SonicDBConfig._sonic_db_global_config_init == False:
msg = "Load the global DB config first using API load_sonic_global_db_config"
logger.warning(msg)
raise RuntimeError(msg)

if SonicDBConfig._sonic_db_config_init == False:
SonicDBConfig.load_sonic_db_config()
if db_name not in SonicDBConfig._sonic_db_config["DATABASES"]:

if namespace not in SonicDBConfig._sonic_db_config:
msg = "{} is not a valid namespace name in configuration file".format(namespace)
logger.warning(msg)
raise RuntimeError(msg)

@staticmethod
def EMPTY_NAMESPACE(ns):
if ns is None:
return ''
else:
return ns

@staticmethod
def db_name_validation(db_name, namespace=None):
namespace = SonicDBConfig.EMPTY_NAMESPACE(namespace)
if SonicDBConfig._sonic_db_config_init == False:
SonicDBConfig.load_sonic_db_config()
SonicDBConfig.namespace_validation(namespace)
db=SonicDBConfig._sonic_db_config[namespace]["DATABASES"]
if db_name not in db:
msg = "{} is not a valid database name in configuration file".format(db_name)
logger.exception(msg)
logger.warning(msg)
raise RuntimeError(msg)

@staticmethod
def inst_name_validation(inst_name):
def inst_name_validation(inst_name, namespace=None):
namespace = SonicDBConfig.EMPTY_NAMESPACE(namespace)
if SonicDBConfig._sonic_db_config_init == False:
SonicDBConfig.load_sonic_db_config()
if inst_name not in SonicDBConfig._sonic_db_config["INSTANCES"]:
SonicDBConfig.namespace_validation(namespace)
instances = SonicDBConfig._sonic_db_config[namespace]["INSTANCES"]
if inst_name not in instances:
msg = "{} is not a valid instance name in configuration file".format(inst_name)
logger.exception(msg)
logger.warning(msg)
raise RuntimeError(msg)

@staticmethod
def get_dblist():
def get_dblist(namespace=None):
namespace = SonicDBConfig.EMPTY_NAMESPACE(namespace)
if SonicDBConfig._sonic_db_config_init == False:
SonicDBConfig.load_sonic_db_config()
SonicDBConfig.namespace_validation(namespace)
return SonicDBConfig._sonic_db_config[namespace]["DATABASES"].keys()

@staticmethod
def get_ns_list():
if SonicDBConfig._sonic_db_config_init == False:
SonicDBConfig.load_sonic_db_config()
return SonicDBConfig._sonic_db_config["DATABASES"].keys()
return SonicDBConfig._sonic_db_config.keys()

@staticmethod
def get_instance(db_name):
def get_instance(db_name, namespace=None):
namespace = SonicDBConfig.EMPTY_NAMESPACE(namespace)
if SonicDBConfig._sonic_db_config_init == False:
SonicDBConfig.load_sonic_db_config()
SonicDBConfig.db_name_validation(db_name)
inst_name = SonicDBConfig._sonic_db_config["DATABASES"][db_name]["instance"]
SonicDBConfig.inst_name_validation(inst_name)
return SonicDBConfig._sonic_db_config["INSTANCES"][inst_name]
SonicDBConfig.db_name_validation(db_name, namespace)
inst_name = SonicDBConfig._sonic_db_config[namespace]["DATABASES"][db_name]["instance"]
SonicDBConfig.inst_name_validation(inst_name, namespace)
return SonicDBConfig._sonic_db_config[namespace]["INSTANCES"][inst_name]

@staticmethod
def get_instancelist():
def get_instancelist(namespace=None):
namespace = SonicDBConfig.EMPTY_NAMESPACE(namespace)
if SonicDBConfig._sonic_db_config_init == False:
SonicDBConfig.load_sonic_db_config()
return SonicDBConfig._sonic_db_config["INSTANCES"]
SonicDBConfig.namespace_validation(namespace)
return SonicDBConfig._sonic_db_config[namespace]["INSTANCES"]

@staticmethod
def get_socket(db_name):
def get_socket(db_name, namespace=None):
namespace = SonicDBConfig.EMPTY_NAMESPACE(namespace)
if SonicDBConfig._sonic_db_config_init == False:
SonicDBConfig.load_sonic_db_config()
SonicDBConfig.db_name_validation(db_name)
return SonicDBConfig.get_instance(db_name)["unix_socket_path"]
return SonicDBConfig.get_instance(db_name, namespace)["unix_socket_path"]

@staticmethod
def get_hostname(db_name):
def get_hostname(db_name, namespace=None):
namespace = SonicDBConfig.EMPTY_NAMESPACE(namespace)
if SonicDBConfig._sonic_db_config_init == False:
SonicDBConfig.load_sonic_db_config()
SonicDBConfig.db_name_validation(db_name)
return SonicDBConfig.get_instance(db_name)["hostname"]
return SonicDBConfig.get_instance(db_name, namespace)["hostname"]

@staticmethod
def get_port(db_name):
def get_port(db_name, namespace=None):
namespace = SonicDBConfig.EMPTY_NAMESPACE(namespace)
if SonicDBConfig._sonic_db_config_init == False:
SonicDBConfig.load_sonic_db_config()
SonicDBConfig.db_name_validation(db_name)
return SonicDBConfig.get_instance(db_name)["port"]
return SonicDBConfig.get_instance(db_name, namespace)["port"]

@staticmethod
def get_dbid(db_name):
def get_dbid(db_name, namespace=None):
namespace = SonicDBConfig.EMPTY_NAMESPACE(namespace)
if SonicDBConfig._sonic_db_config_init == False:
SonicDBConfig.load_sonic_db_config()
SonicDBConfig.db_name_validation(db_name)
return SonicDBConfig._sonic_db_config["DATABASES"][db_name]["id"]
SonicDBConfig.db_name_validation(db_name, namespace)
return SonicDBConfig._sonic_db_config[namespace]["DATABASES"][db_name]["id"]

@staticmethod
def get_separator(db_name):
def get_separator(db_name, namespace=None):
namespace = SonicDBConfig.EMPTY_NAMESPACE(namespace)
if SonicDBConfig._sonic_db_config_init == False:
SonicDBConfig.load_sonic_db_config()
SonicDBConfig.db_name_validation(db_name)
return SonicDBConfig._sonic_db_config["DATABASES"][db_name]["separator"]
SonicDBConfig.db_name_validation(db_name, namespace)
return SonicDBConfig._sonic_db_config[namespace]["DATABASES"][db_name]["separator"]

class SonicV2Connector(DBInterface):
def __init__(self, use_unix_socket_path=False, **kwargs):
def __init__(self, use_unix_socket_path=False, namespace=None, **kwargs):
super(SonicV2Connector, self).__init__(**kwargs)
self.use_unix_socket_path = use_unix_socket_path

"""If the user don't give the namespace as input, it refers to the local namespace
where this application is run. (It could be a network namespace or linux host namesapce)
"""
self.namespace = namespace

# The TCP connection to a DB in another namespace in not supported.
if namespace is not None and use_unix_socket_path == False:
message = "TCP connectivity to the DB instance in a different namespace is not implemented!"
raise NotImplementedError(message)

for db_name in self.get_db_list():
# set a database name as a constant value attribute.
setattr(self, db_name, db_name)
Expand All @@ -133,25 +259,25 @@ def close(self, db_name):
super(SonicV2Connector, self).close(db_id)

def get_db_list(self):
return SonicDBConfig.get_dblist()
return SonicDBConfig.get_dblist(self.namespace)

def get_db_instance(self, db_name):
return SonicDBConfig.get_instance(db_name)
return SonicDBConfig.get_instance(db_name, self.namespace)

def get_db_socket(self, db_name):
return SonicDBConfig.get_socket(db_name)
return SonicDBConfig.get_socket(db_name, self.namespace)

def get_db_hostname(self, db_name):
return SonicDBConfig.get_hostname(db_name)
return SonicDBConfig.get_hostname(db_name, self.namespace)

def get_db_port(self, db_name):
return SonicDBConfig.get_port(db_name)
return SonicDBConfig.get_port(db_name, self.namespace)

def get_dbid(self, db_name):
return SonicDBConfig.get_dbid(db_name)
return SonicDBConfig.get_dbid(db_name, self.namespace)

def get_db_separator(self, db_name):
return SonicDBConfig.get_separator(db_name)
return SonicDBConfig.get_separator(db_name, self.namespace)

def get_redis_client(self, db_name):
db_id = self.get_dbid(db_name)
Expand Down
Loading

0 comments on commit b9cee36

Please sign in to comment.