-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[bgp] Add 'allow list' manager feature #5309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
pavel-shirshov
merged 20 commits into
sonic-net:master
from
pavel-shirshov:pavelsh/allow_list_feature
Sep 27, 2020
Merged
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
5434edd
Add 'allow list' manager feature
5c460be
Fix tests
5da789c
Enhance ipv6 nexthopset test logic
9187764
Use correct dependancy
883a54b
Add LIBSWSSCOMMON into deps
4d2d6dc
Load python-swsscommin as debian dependency
pavel-shirshov 5b59929
Use old version of pyyaml
33b9327
Don't make any warning on default route-map entry
d616046
Merge remote-tracking branch 'origin' into pavelsh/allow_list_feature
1d41ec8
Remove 'exit' from test frr output
0a3c0f5
Add constants for bgp allow list community ranges
c3ab85a
Add tests with updates
8b3f044
Restart only required peer-groups
65f73c3
Rename current_config text to current_config_raw
aa344f5
Add coverage support
ef88970
Extract a common part of set del tests
1c823a1
Add tests to check validation of ipv4 and ipv6
e7c126b
Remove bgpcfgd from coverage
e18e9be
Merge remote-tracking branch 'origin' into pavelsh/allow_list_feature
d74865d
Merge remote-tracking branch 'origin' into pavelsh/allow_list_feature
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.