Skip to content

Commit

Permalink
Not4Review
Browse files Browse the repository at this point in the history
  • Loading branch information
FengPan-Frank committed May 9, 2024
1 parent 9e6404c commit 0ad95c6
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 0 deletions.
116 changes: 116 additions & 0 deletions scripts/bmpcfgd
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
'''
bmpcfgd
Daemon which monitors bmp relevant table enablement from CONFIG_DB, and reset BMP states
'''

import os
import sys
import subprocess
import syslog
import signal
from shutil import copy2
from datetime import datetime
from sonic_py_common import device_info
from sonic_py_common.general import check_output_pipe
from swsscommon.swsscommon import ConfigDBConnector, DBConnector, Table
from swsscommon import swsscommon

CFG_DB = "CONFIG_DB"
STATE_DB = "STATE_DB"


class BMPCfg(object):
def __init__(self, state_db_conn):
self.bgp_neighbor_table = False
self.bgp_rib_in_table = False
self.bgp_rib_out_table = False
self.state_db_conn = state_db_conn


def load(self, data={}):
common_config = data.get('table', {})
if not common_config:
syslog.syslog(syslog.LOG_INFO, f'BMPCfg: skipped the bmp config, the BMP setting is empty.')
return

self.bgp_neighbor_table = is_true(common_config.get('bgp_neighbor_table', 'false'))
self.bgp_rib_in_table = is_true(common_config.get('bgp_rib_in_table', 'false'))
self.bgp_rib_out_table = is_true(common_config.get('bgp_rib_out_table', 'false'))
syslog.syslog(syslog.LOG_INFO, f'BMPCfg: update : {self.bgp_neighbor_table}, {self.bgp_rib_in_table}, {self.bgp_rib_out_table}.')

# reset bmp table state once config is changed.
self.stop_bmp()
self.reset_bmp_table()
self.start_bmp()

def cfg_handler(self, data):
self.load(data)

def stop_bmp(self):
syslog.syslog(syslog.LOG_INFO, 'stop bmp daemon')
subprocess.call(["service", "openbmpd", "stop"])

def reset_bmp_table(self):
syslog.syslog(syslog.LOG_INFO, 'Reset bmp table from state_db')
self.state_db_conn.delete_all_by_pattern('STATE_DB', 'BGP_NEIGHBOR*')
self.state_db_conn.delete_all_by_pattern('STATE_DB', 'BGP_RIB_IN_TABLE*')
self.state_db_conn.delete_all_by_pattern('STATE_DB', 'BGP_RIB_OUT_TABLE*')

def start_bmp(self):
syslog.syslog(syslog.LOG_INFO, 'start bmp daemon')
subprocess.call(["service", "openbmpd", "start"])


class BMPCfgDaemon:
def __init__(self):
self.state_db_conn = DBConnector(STATE_DB, 0)
self.config_db = ConfigDBConnector()
self.config_db.connect(wait_for_init=True, retry_on=True)
syslog.syslog(syslog.LOG_INFO, 'ConfigDB connect success')
self.bmpcfg = BMPCfg(self.state_db_conn)

def bmp_handler(self, key, op, data):
syslog.syslog(syslog.LOG_INFO, 'BMP table handler...')
data = self.config_db.get_table("BMP")
self.bmpcfg.cfg_handler(data)

def register_callbacks(self):

def make_callback(func):
def callback(table, key, data):
if data is None:
op = "DEL"
data = {}
else:
op = "SET"
return func(key, op, data)
return callback

self.config_db.subscribe('BMP', make_callback(self.bmp_handler))

def start(self):
self.config_db.listen(init_data_handler=self.load)

def signal_handler(sig, frame):
if sig == signal.SIGHUP:
syslog.syslog(syslog.LOG_INFO, "bmpcfgd: signal 'SIGHUP' is caught and ignoring..")
elif sig == signal.SIGINT:
syslog.syslog(syslog.LOG_INFO, "bmpcfgd: signal 'SIGINT' is caught and exiting...")
sys.exit(128 + sig)
elif sig == signal.SIGTERM:
syslog.syslog(syslog.LOG_INFO, "bmpcfgd: signal 'SIGTERM' is caught and exiting...")
sys.exit(128 + sig)
else:
syslog.syslog(syslog.LOG_INFO, "bmpcfgd: invalid signal - ignoring..")

def main():
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGHUP, signal_handler)
daemon = BMPCfgDaemon()
daemon.register_callbacks()
daemon.start()

if __name__ == "__main__":
main()
95 changes: 95 additions & 0 deletions tests/abmpcfgd_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import importlib.machinery
import importlib.util
import filecmp
import json
import shutil
import os
import sys
from swsscommon import swsscommon

from parameterized import parameterized
from unittest import TestCase, mock
from tests.common.mock_configdb import MockConfigDb, MockDBConnector
from tests.common.mock_bootloader import MockBootloader
from sonic_py_common.general import getstatusoutput_noshell


test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
modules_path = os.path.dirname(test_path)
scripts_path = os.path.join(modules_path, "scripts")
sys.path.insert(0, modules_path)

# Load the file under test
bmpcfgd_path = os.path.join(scripts_path, 'bmpcfgd')
loader = importlib.machinery.SourceFileLoader('bmpcfgd', bmpcfgd_path)
spec = importlib.util.spec_from_loader(loader.name, loader)
bmpcfgd = importlib.util.module_from_spec(spec)
loader.exec_module(bmpcfgd)
sys.modules['bmpcfgd'] = bmpcfgd


original_syslog = bmpcfgd.syslog

# Mock swsscommon classes
bmpcfgd.ConfigDBConnector = MockConfigDb
bmpcfgd.DBConnector = MockDBConnector
bmpcfgd.Table = mock.Mock()


class TestBMPCfgDaemon(TestCase):
"""
Test bmpcfgd daemon
"""
def setUp(self):
self.test_data['BMP']['table'] = {'bgp_neighbor_table': 'false', 'bgp_rib_in_table': 'false', 'bgp_rib_out_table': 'false'}

@patch('subprocess.call')
def test_start_bmp(self, mock_call):
obj = BMPCfg()
obj.start_bmp()
mock_call.assert_called_once_with(["service", "openbmpd", "start"])

@patch('subprocess.call')
def test_stop_bmp(self, mock_call):
obj = BMPCfg()
obj.stop_bmp()
mock_call.assert_called_once_with(["service", "openbmpd", "stop"])

@mock.patch('sonic_installer.bootloader.get_bootloader', side_effect=[MockBootloader()])
@mock.patch('syslog.syslog')
@mock.patch('subprocess.check_call')
def test_bmpcfgd_neighbor_enable(self, mock_check_call, mock_check_output, mock_syslog, mock_get_bootloader):
self.test_data['BMP']['table']['bgp_neighbor_table'] = 'true'
MockConfigDb.set_config_db(self.test_data)
bmp_config_daemon = bmpcfgd.BMPCfgDaemon()
bmp_config_daemon.bmp_handler("BMP", '', self.test_data)
mock_syslog.assert_any_call(original_syslog.LOG_INFO, 'BMPCfg: update : true, false, false')
mock_syslog.assert_any_call(original_syslog.LOG_INFO, 'BMPCfg: stop bmp daemon.')
mock_syslog.assert_any_call(original_syslog.LOG_INFO, 'BMPCfg: Reset bmp table from state_db.')
mock_syslog.assert_any_call(original_syslog.LOG_INFO, 'BMPCfg: start bmp daemon.')

@mock.patch('sonic_installer.bootloader.get_bootloader', side_effect=[MockBootloader()])
@mock.patch('syslog.syslog')
@mock.patch('subprocess.check_call')
def test_bmpcfgd_bgp_rib_in_enable(self, mock_check_call, mock_check_output, mock_syslog, mock_get_bootloader):
self.test_data['BMP']['table']['bgp_rib_in_table'] = 'true'
MockConfigDb.set_config_db(self.test_data)
bmp_config_daemon = bmpcfgd.BMPCfgDaemon()
bmp_config_daemon.bmp_handler("BMP", '', self.test_data)
mock_syslog.assert_any_call(original_syslog.LOG_INFO, 'BMPCfg: update : false, true, false')
mock_syslog.assert_any_call(original_syslog.LOG_INFO, 'BMPCfg: stop bmp daemon.')
mock_syslog.assert_any_call(original_syslog.LOG_INFO, 'BMPCfg: Reset bmp table from state_db.')
mock_syslog.assert_any_call(original_syslog.LOG_INFO, 'BMPCfg: start bmp daemon.')

@mock.patch('sonic_installer.bootloader.get_bootloader', side_effect=[MockBootloader()])
@mock.patch('syslog.syslog')
@mock.patch('subprocess.check_call')
def test_bmpcfgd_bgp_rib_out_enable(self, mock_check_call, mock_check_output, mock_syslog, mock_get_bootloader):
self.test_data['BMP']['table']['bgp_rib_out_table'] = 'true'
MockConfigDb.set_config_db(self.test_data)
bmp_config_daemon = bmpcfgd.BMPCfgDaemon()
bmp_config_daemon.bmp_handler("BMP", '', self.test_data)
mock_syslog.assert_any_call(original_syslog.LOG_INFO, 'BMPCfg: update : false, false, true')
mock_syslog.assert_any_call(original_syslog.LOG_INFO, 'BMPCfg: stop bmp daemon.')
mock_syslog.assert_any_call(original_syslog.LOG_INFO, 'BMPCfg: Reset bmp table from state_db.')
mock_syslog.assert_any_call(original_syslog.LOG_INFO, 'BMPCfg: start bmp daemon.')

0 comments on commit 0ad95c6

Please sign in to comment.