forked from zhenggen-xu/sonic-buildimage
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[bgp] Add 'allow list' manager feature (sonic-net#5309)
implements a new feature: "BGP Allow list." This feature allows us to control which IP prefixes are going to be advertised via ebgp from the routes received from EBGP neighbors.
- Loading branch information
1 parent
4006ce7
commit 6eed082
Showing
21 changed files
with
1,515 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,4 @@ app/*.pyc | |
tests/*.pyc | ||
tests/__pycache__/ | ||
.idea | ||
.coverage |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
from collections import defaultdict | ||
|
||
from app.log import log_err | ||
|
||
|
||
class Directory(object): | ||
""" This class stores values and notifies callbacks which were registered to be executed as soon | ||
as some value is changed. This class works as DB cache mostly """ | ||
def __init__(self): | ||
self.data = defaultdict(dict) # storage. A key is a slot name, a value is a dictionary with data | ||
self.notify = defaultdict(lambda: defaultdict(list)) # registered callbacks: slot -> path -> handlers[] | ||
|
||
@staticmethod | ||
def get_slot_name(db, table): | ||
""" Convert db, table pair into a slot name """ | ||
return db + "__" + table | ||
|
||
def path_traverse(self, slot, path): | ||
""" | ||
Traverse a path in the storage. | ||
If the path is an empty string, it returns a value as it is. | ||
If the path is not an empty string, the method will traverse through the dictionary value. | ||
Example: | ||
self.data["key_1"] = { "abc": { "cde": { "fgh": "val_1", "ijk": "val_2" } } } | ||
self.path_traverse("key_1", "abc/cde") will return True, { "fgh": "val_1", "ijk": "val_2" } | ||
:param slot: storage key | ||
:param path: storage path as a string where each internal key is separated by '/' | ||
:return: a pair: True if the path was found, object if it was found | ||
""" | ||
if slot not in self.data: | ||
return False, None | ||
elif path == '': | ||
return True, self.data[slot] | ||
d = self.data[slot] | ||
for p in path.split("/"): | ||
if p not in d: | ||
return False, None | ||
d = d[p] | ||
return True, d | ||
|
||
def path_exist(self, db, table, path): | ||
""" | ||
Check if the path exists in the storage | ||
:param db: db name | ||
:param table: table name | ||
:param path: requested path | ||
:return: True if the path is available, False otherwise | ||
""" | ||
slot = self.get_slot_name(db, table) | ||
return self.path_traverse(slot, path)[0] | ||
|
||
def get_path(self, db, table, path): | ||
""" | ||
Return the requested path from the storage | ||
:param db: db name | ||
:param table: table name | ||
:param path: requested path | ||
:return: object if the path was found, None otherwise | ||
""" | ||
slot = self.get_slot_name(db, table) | ||
return self.path_traverse(slot, path)[1] | ||
|
||
def put(self, db, table, key, value): | ||
""" | ||
Put information into the storage. Notify handlers which are dependant to the information | ||
:param db: db name | ||
:param table: table name | ||
:param key: key to change | ||
:param value: value to put | ||
:return: | ||
""" | ||
slot = self.get_slot_name(db, table) | ||
self.data[slot][key] = value | ||
if slot in self.notify: | ||
for path in self.notify[slot].keys(): | ||
if self.path_exist(db, table, path): | ||
for handler in self.notify[slot][path]: | ||
handler() | ||
|
||
def get(self, db, table, key): | ||
""" | ||
Get a value from the storage | ||
:param db: db name | ||
:param table: table name | ||
:param key: ket to get | ||
:return: value for the key | ||
""" | ||
slot = self.get_slot_name(db, table) | ||
return self.data[slot][key] | ||
|
||
def get_slot(self, db, table): | ||
""" | ||
Get an object from the storage | ||
:param db: db name | ||
:param table: table name | ||
:return: object for the slot | ||
""" | ||
slot = self.get_slot_name(db, table) | ||
return self.data[slot] | ||
|
||
def remove(self, db, table, key): | ||
""" | ||
Remove a value from the storage | ||
:param db: db name | ||
:param table: table name | ||
:param key: key to remove | ||
""" | ||
slot = self.get_slot_name(db, table) | ||
if slot in self.data: | ||
if key in self.data[slot]: | ||
del self.data[slot][key] | ||
else: | ||
log_err("Directory: Can't remove key '%s' from slot '%s'. The key doesn't exist" % (key, slot)) | ||
else: | ||
log_err("Directory: Can't remove key '%s' from slot '%s'. The slot doesn't exist" % (key, slot)) | ||
|
||
def remove_slot(self, db, table): | ||
""" | ||
Remove an object from the storage | ||
:param db: db name | ||
:param table: table name | ||
""" | ||
slot = self.get_slot_name(db, table) | ||
if slot in self.data: | ||
del self.data[slot] | ||
else: | ||
log_err("Directory: Can't remove slot '%s'. The slot doesn't exist" % slot) | ||
|
||
def available(self, db, table): | ||
""" | ||
Check if the table is available | ||
:param db: db name | ||
:param table: table name | ||
:return: True if the slot is available, False if not | ||
""" | ||
slot = self.get_slot_name(db, table) | ||
return slot in self.data | ||
|
||
def available_deps(self, deps): | ||
""" | ||
Check if all items from the deps list is available in the storage | ||
:param deps: list of dependencies | ||
:return: True if all dependencies are presented, False otherwise | ||
""" | ||
res = True | ||
for db, table, path in deps: | ||
res = res and self.path_exist(db, table, path) | ||
return res | ||
|
||
def subscribe(self, deps, handler): | ||
""" | ||
Subscribe the handler to be run as soon as all dependencies are presented | ||
:param deps: | ||
:param handler: | ||
:return: | ||
""" | ||
for db, table, path in deps: | ||
slot = self.get_slot_name(db, table) | ||
self.notify[slot][path].append(handler) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
from swsscommon import swsscommon | ||
|
||
from app.log import log_debug, log_err | ||
|
||
|
||
class Manager(object): | ||
""" This class represents a SONiC DB table """ | ||
def __init__(self, common_objs, deps, database, table_name): | ||
""" | ||
Initialize class | ||
:param common_objs: common object dictionary | ||
:param deps: dependencies list | ||
:param database: database name | ||
:param table_name: table name | ||
""" | ||
self.directory = common_objs['directory'] | ||
self.cfg_mgr = common_objs['cfg_mgr'] | ||
self.constants = common_objs['constants'] | ||
self.deps = deps | ||
self.db_name = database | ||
self.table_name = table_name | ||
self.set_queue = [] | ||
self.directory.subscribe(deps, self.on_deps_change) # subscribe this class method on directory changes | ||
|
||
def get_database(self): | ||
""" Return associated database """ | ||
return self.db_name | ||
|
||
def get_table_name(self): | ||
""" Return associated table name""" | ||
return self.table_name | ||
|
||
def handler(self, key, op, data): | ||
""" | ||
This method is executed on each add/remove event on the table. | ||
:param key: key of the table entry | ||
:param op: operation on the table entry. Could be either 'SET' or 'DEL' | ||
:param data: associated data of the event. Empty for 'DEL' operation. | ||
""" | ||
if op == swsscommon.SET_COMMAND: | ||
if self.directory.available_deps(self.deps): # all required dependencies are set in the Directory? | ||
res = self.set_handler(key, data) | ||
if not res: # set handler returned False, which means it is not ready to process is. Save it for later. | ||
log_debug("'SET' handler returned NOT_READY for the Manager: %s" % self.__class__) | ||
self.set_queue.append((key, data)) | ||
else: | ||
log_debug("Not all dependencies are met for the Manager: %s" % self.__class__) | ||
self.set_queue.append((key, data)) | ||
elif op == swsscommon.DEL_COMMAND: | ||
self.del_handler(key) | ||
else: | ||
log_err("Invalid operation '%s' for key '%s'" % (op, key)) | ||
|
||
def on_deps_change(self): | ||
""" This method is being executed on every dependency change """ | ||
if not self.directory.available_deps(self.deps): | ||
return | ||
new_queue = [] | ||
for key, data in self.set_queue: | ||
res = self.set_handler(key, data) | ||
if not res: | ||
new_queue.append((key, data)) | ||
self.set_queue = new_queue | ||
|
||
def set_handler(self, key, data): | ||
""" Placeholder for 'SET' command """ | ||
log_err("set_handler() wasn't implemented for %s" % self.__class__.__name__) | ||
|
||
def del_handler(self, key): | ||
""" Placeholder for 'DEL' command """ | ||
log_err("del_handler wasn't implemented for %s" % self.__class__.__name__) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
g_debug = False | ||
g_debug = True # FIXME: read from env variable, or from constants |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[pytest] | ||
addopts = --cov=app --cov-report term |
Oops, something went wrong.