[bgpcfgd]: Convert bgpcfgd and bgpmon to python3 (#5746)

* Convert bgpcfgd to python3

Convert bgpmon to python3
Fix some issues in bgpmon

* Add python3-swsscommon as depends

* Install dependencies

* reorder deps

Co-authored-by: Pavel Shirshov <pavel.contrib@gmail.com>
This commit is contained in:
pavel-shirshov 2020-11-05 10:01:43 -08:00 committed by GitHub
parent 522a071ffb
commit 13f8e9ce5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 45 additions and 37 deletions

View File

@ -9,7 +9,7 @@
neighbor {{ neighbor_addr }} timers {{ bgp_session['keepalive'] | default("60") }} {{ bgp_session['holdtime'] | default("180") }} neighbor {{ neighbor_addr }} timers {{ bgp_session['keepalive'] | default("60") }} {{ bgp_session['holdtime'] | default("180") }}
{% endif %} {% endif %}
! !
{% if bgp_session.has_key('admin_status') and bgp_session['admin_status'] == 'down' or not bgp_session.has_key('admin_status') and CONFIG_DB__DEVICE_METADATA['localhost'].has_key('default_bgp_status') and CONFIG_DB__DEVICE_METADATA['localhost']['default_bgp_status'] == 'down' %} {% if 'admin_status' in bgp_session and bgp_session['admin_status'] == 'down' or 'admin_status' not in bgp_session and 'default_bgp_status' in CONFIG_DB__DEVICE_METADATA['localhost'] and CONFIG_DB__DEVICE_METADATA['localhost']['default_bgp_status'] == 'down' %}
neighbor {{ neighbor_addr }} shutdown neighbor {{ neighbor_addr }} shutdown
{% endif %} {% endif %}
! !
@ -23,11 +23,11 @@
! !
{% endif %} {% endif %}
! !
{% if bgp_session.has_key('rrclient') and bgp_session['rrclient'] | int != 0 %} {% if 'rrclient' in bgp_session and bgp_session['rrclient'] | int != 0 %}
neighbor {{ neighbor_addr }} route-reflector-client neighbor {{ neighbor_addr }} route-reflector-client
{% endif %} {% endif %}
! !
{% if bgp_session.has_key('nhopself') and bgp_session['nhopself'] | int != 0 %} {% if 'nhopself' in bgp_session and bgp_session['nhopself'] | int != 0 %}
neighbor {{ neighbor_addr }} next-hop-self neighbor {{ neighbor_addr }} next-hop-self
{% endif %} {% endif %}
! !

View File

@ -3,6 +3,7 @@
DOCKER_CONFIG_ENGINE_BUSTER = docker-config-engine-buster.gz DOCKER_CONFIG_ENGINE_BUSTER = docker-config-engine-buster.gz
$(DOCKER_CONFIG_ENGINE_BUSTER)_PATH = $(DOCKERS_PATH)/docker-config-engine-buster $(DOCKER_CONFIG_ENGINE_BUSTER)_PATH = $(DOCKERS_PATH)/docker-config-engine-buster
$(DOCKER_CONFIG_ENGINE_BUSTER)_DEPENDS += $(LIBSWSSCOMMON) $(PYTHON3_SWSSCOMMON)
$(DOCKER_CONFIG_ENGINE_BUSTER)_PYTHON_WHEELS += $(SWSSSDK_PY2) $(DOCKER_CONFIG_ENGINE_BUSTER)_PYTHON_WHEELS += $(SWSSSDK_PY2)
$(DOCKER_CONFIG_ENGINE_BUSTER)_PYTHON_WHEELS += $(SWSSSDK_PY3) $(DOCKER_CONFIG_ENGINE_BUSTER)_PYTHON_WHEELS += $(SWSSSDK_PY3)
$(DOCKER_CONFIG_ENGINE_BUSTER)_PYTHON_WHEELS += $(SONIC_PY_COMMON_PY2) $(DOCKER_CONFIG_ENGINE_BUSTER)_PYTHON_WHEELS += $(SONIC_PY_COMMON_PY2)

View File

@ -1,11 +1,13 @@
# sonic-bgpcfgd package # sonic-bgpcfgd package
SONIC_BGPCFGD = sonic_bgpcfgd-1.0-py2-none-any.whl SONIC_BGPCFGD = sonic_bgpcfgd-1.0-py3-none-any.whl
$(SONIC_BGPCFGD)_SRC_PATH = $(SRC_PATH)/sonic-bgpcfgd $(SONIC_BGPCFGD)_SRC_PATH = $(SRC_PATH)/sonic-bgpcfgd
# These dependencies are only needed becuase they are dependencies # These dependencies are only needed because they are dependencies
# of sonic-config-engine and bgpcfgd explicitly calls sonic-cfggen # of sonic-config-engine and bgpcfgd explicitly calls sonic-cfggen
# as part of its unit tests. # as part of its unit tests.
# TODO: Refactor unit tests so that these dependencies are not needed # TODO: Refactor unit tests so that these dependencies are not needed
$(SONIC_BGPCFGD)_DEPENDS += $(SONIC_PY_COMMON_PY2) $(SONIC_BGPCFGD)_DEPENDS += $(SONIC_PY_COMMON_PY2)
$(SONIC_BGPCFGD)_PYTHON_VERSION = 2 $(SONIC_BGPCFGD)_DEBS_DEPENDS += $(PYTHON3_SWSSCOMMON)
$(SONIC_BGPCFGD)_PYTHON_VERSION = 3
SONIC_PYTHON_WHEELS += $(SONIC_BGPCFGD) SONIC_PYTHON_WHEELS += $(SONIC_BGPCFGD)

View File

@ -7,3 +7,6 @@ tests/*.pyc
tests/__pycache__/ tests/__pycache__/
.idea .idea
.coverage .coverage
bgpcfgd/__pycache__/
venv
tests/.coverage*

View File

@ -308,7 +308,7 @@ class BGPPeerMgrBase(Manager):
:return: ipv4 address for Loopback0, None if nothing found :return: ipv4 address for Loopback0, None if nothing found
""" """
loopback0_ipv4 = None loopback0_ipv4 = None
for loopback in self.directory.get_slot("CONFIG_DB", swsscommon.CFG_LOOPBACK_INTERFACE_TABLE_NAME).iterkeys(): for loopback in self.directory.get_slot("CONFIG_DB", swsscommon.CFG_LOOPBACK_INTERFACE_TABLE_NAME).keys():
if loopback.startswith("Loopback0|"): if loopback.startswith("Loopback0|"):
loopback0_prefix_str = loopback.replace("Loopback0|", "") loopback0_prefix_str = loopback.replace("Loopback0|", "")
loopback0_ip_str = loopback0_prefix_str[:loopback0_prefix_str.find('/')] loopback0_ip_str = loopback0_prefix_str[:loopback0_prefix_str.find('/')]
@ -333,7 +333,7 @@ class BGPPeerMgrBase(Manager):
local_address = local_addresses[local_addr] local_address = local_addresses[local_addr]
interfaces = self.directory.get_slot("LOCAL", "interfaces") interfaces = self.directory.get_slot("LOCAL", "interfaces")
# Check if the information for the interface of this local address has been set # Check if the information for the interface of this local address has been set
if local_address.has_key("interface") and local_address["interface"] in interfaces: if "interface" in local_address and local_address["interface"] in interfaces:
return interfaces[local_address["interface"]] return interfaces[local_address["interface"]]
else: else:
return None return None
@ -346,7 +346,7 @@ class BGPPeerMgrBase(Manager):
:return: Return the vnet name of the interface if this interface belongs to a vnet, :return: Return the vnet name of the interface if this interface belongs to a vnet,
Otherwise return None Otherwise return None
""" """
if interface.has_key("vnet_name") and interface["vnet_name"]: if "vnet_name" in interface and interface["vnet_name"]:
return interface["vnet_name"] return interface["vnet_name"]
else: else:
return None return None

View File

@ -16,7 +16,7 @@ def run_command(command, shell=False, hide_errors=False):
:return: Tuple: integer exit code from the command, stdout as a string, stderr as a string :return: Tuple: integer exit code from the command, stdout as a string, stderr as a string
""" """
log_debug("execute command '%s'." % str(command)) log_debug("execute command '%s'." % str(command))
p = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8')
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
if p.returncode != 0: if p.returncode != 0:
if not hide_errors: if not hide_errors:

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
"""" """"
Description: bgpmon.py -- populating bgp related information in stateDB. Description: bgpmon.py -- populating bgp related information in stateDB.
@ -6,7 +6,7 @@ Description: bgpmon.py -- populating bgp related information in stateDB.
Initial creation of this daemon is to assist SNMP agent in obtaining the Initial creation of this daemon is to assist SNMP agent in obtaining the
BGP related information for its MIB support. The MIB that this daemon is BGP related information for its MIB support. The MIB that this daemon is
assiting is for the CiscoBgp4MIB (Neighbor state only). If there are other assisting is for the CiscoBgp4MIB (Neighbor state only). If there are other
BGP related items that needs to be updated in a periodic manner in the BGP related items that needs to be updated in a periodic manner in the
future, then more can be added into this process. future, then more can be added into this process.
@ -23,7 +23,7 @@ Description: bgpmon.py -- populating bgp related information in stateDB.
is a need to perform update or the peer is stale to be removed from the is a need to perform update or the peer is stale to be removed from the
state DB state DB
""" """
import commands import subprocess
import json import json
import os import os
import syslog import syslog
@ -32,7 +32,7 @@ import time
PIPE_BATCH_MAX_COUNT = 50 PIPE_BATCH_MAX_COUNT = 50
class BgpStateGet(): class BgpStateGet:
def __init__(self): def __init__(self):
# list peer_l stores the Neighbor peer Ip address # list peer_l stores the Neighbor peer Ip address
# dic peer_state stores the Neighbor peer state entries # dic peer_state stores the Neighbor peer state entries
@ -74,13 +74,13 @@ class BgpStateGet():
# Get a new snapshot of BGP neighbors and store them in the "new" location # Get a new snapshot of BGP neighbors and store them in the "new" location
def get_all_neigh_states(self): def get_all_neigh_states(self):
cmd = "vtysh -c 'show bgp summary json'" cmd = "vtysh -c 'show bgp summary json'"
rc, output = commands.getstatusoutput(cmd) rc, output = subprocess.getstatusoutput(cmd)
if rc: if rc:
syslog.syslog(syslog.LOG_ERR, "*ERROR* Failed with rc:{} when execute: {}".format(rc, cmd)) syslog.syslog(syslog.LOG_ERR, "*ERROR* Failed with rc:{} when execute: {}".format(rc, cmd))
return return
peer_info = json.loads(output) peer_info = json.loads(output)
# cmd ran successfully, safe to Clean the "new" lists/dic for new sanpshot # cmd ran successfully, safe to Clean the "new" lists/dic for new snapshot
del self.new_peer_l[:] del self.new_peer_l[:]
self.new_peer_state.clear() self.new_peer_state.clear()
for key, value in peer_info.items(): for key, value in peer_info.items():
@ -136,7 +136,7 @@ class BgpStateGet():
self.flush_pipe(data) self.flush_pipe(data)
# Check for stale state entries to be cleaned up # Check for stale state entries to be cleaned up
while len(self.peer_l) > 0: while len(self.peer_l) > 0:
# remove this from the stateDB and the current nighbor state entry # remove this from the stateDB and the current neighbor state entry
peer = self.peer_l.pop(0) peer = self.peer_l.pop(0)
del_key = "NEIGH_STATE_TABLE|%s" % peer del_key = "NEIGH_STATE_TABLE|%s" % peer
data[del_key] = None data[del_key] = None
@ -151,15 +151,15 @@ class BgpStateGet():
def main(): def main():
print "bgpmon service started" syslog.syslog(syslog.LOG_INFO, "bgpmon service started")
bgp_state_get = None
try: try:
bgp_state_get = BgpStateGet() bgp_state_get = BgpStateGet()
except Exception as e: except Exception as e:
syslog.syslog(syslog.LOG_ERR, "{}: error exit 1, reason {}".format(THIS_MODULE, str(e))) syslog.syslog(syslog.LOG_ERR, "{}: error exit 1, reason {}".format("THIS_MODULE", str(e)))
exit(1) exit(1)
# periodically obtain the new neighbor infomraton and update if necessary # periodically obtain the new neighbor information and update if necessary
while True: while True:
time.sleep(15) time.sleep(15)
if bgp_state_get.bgp_activity_detected(): if bgp_state_get.bgp_activity_detected():

View File

@ -1,4 +1,4 @@
from mock import MagicMock from unittest.mock import MagicMock
swsscommon = MagicMock(CFG_DEVICE_METADATA_TABLE_NAME = "DEVICE_METADATA") swsscommon = MagicMock(CFG_DEVICE_METADATA_TABLE_NAME = "DEVICE_METADATA")

View File

@ -1,7 +1,8 @@
from unittest.mock import MagicMock, patch
from bgpcfgd.directory import Directory from bgpcfgd.directory import Directory
from bgpcfgd.template import TemplateFabric from bgpcfgd.template import TemplateFabric
import bgpcfgd import bgpcfgd
from mock import MagicMock, patch
swsscommon_module_mock = MagicMock() swsscommon_module_mock = MagicMock()
@ -429,7 +430,7 @@ def test___find_peer_group_by_deployment_id():
} }
mgr = BGPAllowListMgr(common_objs, "CONFIG_DB", "BGP_ALLOWED_PREFIXES") mgr = BGPAllowListMgr(common_objs, "CONFIG_DB", "BGP_ALLOWED_PREFIXES")
values = mgr._BGPAllowListMgr__find_peer_group_by_deployment_id(0) values = mgr._BGPAllowListMgr__find_peer_group_by_deployment_id(0)
assert values == ['PEER_V4_INT', 'PEER_V6_INT', 'PEER_V6', 'PEER_V4'] assert set(values) == set(['PEER_V4_INT', 'PEER_V6_INT', 'PEER_V6', 'PEER_V4'])
@patch.dict("sys.modules", swsscommon=swsscommon_module_mock) @patch.dict("sys.modules", swsscommon=swsscommon_module_mock)
def test___restart_peers_found_deployment_id(): def test___restart_peers_found_deployment_id():

View File

@ -1,10 +1,12 @@
from unittest.mock import MagicMock, patch
from bgpcfgd.directory import Directory from bgpcfgd.directory import Directory
from bgpcfgd.template import TemplateFabric from bgpcfgd.template import TemplateFabric
from mock import MagicMock, patch
from copy import deepcopy from copy import deepcopy
import swsscommon_test from . import swsscommon_test
import bgpcfgd import bgpcfgd
with patch.dict("sys.modules", swsscommon=swsscommon_test): with patch.dict("sys.modules", swsscommon=swsscommon_test):
from bgpcfgd.managers_bbr import BBRMgr from bgpcfgd.managers_bbr import BBRMgr

View File

@ -19,7 +19,7 @@ def parse_instance_conf(filename):
if TemplateFabric.is_ipv6(neighbor): if TemplateFabric.is_ipv6(neighbor):
neighbors[neighbor] = {} neighbors[neighbor] = {}
# Extract peer-groups and route-maps # Extract peer-groups and route-maps
for neighbor, neighbor_data in neighbors.iteritems(): for neighbor, neighbor_data in neighbors.items():
route_map_in_re = re.compile(r'^neighbor\s+%s\s+route-map\s+(\S+) in$' % neighbor) route_map_in_re = re.compile(r'^neighbor\s+%s\s+route-map\s+(\S+) in$' % neighbor)
peer_group_re = re.compile(r'^neighbor\s+%s\s+peer-group\s+(\S+)$' % neighbor) peer_group_re = re.compile(r'^neighbor\s+%s\s+peer-group\s+(\S+)$' % neighbor)
for line in lines: for line in lines:
@ -30,7 +30,7 @@ def parse_instance_conf(filename):
assert "peer-group" not in neighbor_data assert "peer-group" not in neighbor_data
neighbor_data["peer-group"] = peer_group_re.match(line).group(1) neighbor_data["peer-group"] = peer_group_re.match(line).group(1)
# Ensure that every ivp6 neighbor has either route-map or peer-group # Ensure that every ivp6 neighbor has either route-map or peer-group
for neighbor, neighbor_data in neighbors.iteritems(): for neighbor, neighbor_data in neighbors.items():
assert "route-map" in neighbor_data or "peer-group" in neighbor_data,\ assert "route-map" in neighbor_data or "peer-group" in neighbor_data,\
"IPv6 neighbor '%s' must have either route-map in or peer-group %s" % (neighbor, neighbor_data) "IPv6 neighbor '%s' must have either route-map in or peer-group %s" % (neighbor, neighbor_data)
return neighbors return neighbors

View File

@ -42,7 +42,7 @@ def test_pfx_filter_mixed_keys():
] ]
) )
res = TemplateFabric.pfx_filter(src) res = TemplateFabric.pfx_filter(src)
assert res == expected assert dict(res) == dict(expected)
def test_pfx_filter_pfx_v4_w_mask(): def test_pfx_filter_pfx_v4_w_mask():
@ -57,7 +57,7 @@ def test_pfx_filter_pfx_v4_w_mask():
] ]
) )
res = TemplateFabric.pfx_filter(src) res = TemplateFabric.pfx_filter(src)
assert res == expected assert dict(res) == dict(expected)
def test_pfx_filter_pfx_v6_w_mask(): def test_pfx_filter_pfx_v6_w_mask():
src = { src = {
@ -85,7 +85,7 @@ def test_pfx_filter_pfx_v4_no_mask():
] ]
) )
res = TemplateFabric.pfx_filter(src) res = TemplateFabric.pfx_filter(src)
assert res == expected assert dict(res) == dict(expected)
def test_pfx_filter_pfx_v6_no_mask(): def test_pfx_filter_pfx_v6_no_mask():
src = { src = {
@ -126,7 +126,7 @@ def test_pfx_filter_pfx_comprehensive():
] ]
) )
res = TemplateFabric.pfx_filter(src) res = TemplateFabric.pfx_filter(src)
assert res == expected assert dict(res) == dict(expected)
@pytest.fixture @pytest.fixture
def test_pfx_filter_wrong_ip(caplog): def test_pfx_filter_wrong_ip(caplog):

View File

@ -1,9 +1,7 @@
import os import os
import subprocess import subprocess
from bgpcfgd.config import ConfigMgr from bgpcfgd.config import ConfigMgr
from .test_templates import compress_comments, write_result
TEMPLATE_PATH = os.path.abspath('../../dockers/docker-fpm-frr/frr') TEMPLATE_PATH = os.path.abspath('../../dockers/docker-fpm-frr/frr')
@ -15,11 +13,11 @@ def run_test(name, template_path, json_path, match_path):
template_path = os.path.join(TEMPLATE_PATH, template_path) template_path = os.path.join(TEMPLATE_PATH, template_path)
json_path = os.path.join(DATA_PATH, json_path) json_path = os.path.join(DATA_PATH, json_path)
cfggen = os.path.abspath("../sonic-config-engine/sonic-cfggen") cfggen = os.path.abspath("../sonic-config-engine/sonic-cfggen")
command = [cfggen, "-T", TEMPLATE_PATH, "-t", template_path, "-y", json_path] command = ['/usr/bin/python2.7', cfggen, "-T", TEMPLATE_PATH, "-t", template_path, "-y", json_path]
p = subprocess.Popen(command, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p = subprocess.Popen(command, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={"PYTHONPATH": "."})
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
assert p.returncode == 0, "sonic-cfggen for %s test returned %d code. stderr='%s'" % (name, p.returncode, stderr) assert p.returncode == 0, "sonic-cfggen for %s test returned %d code. stderr='%s'" % (name, p.returncode, stderr)
raw_generated_result = stdout raw_generated_result = stdout.decode("ascii")
assert "None" not in raw_generated_result, "Test %s" % name assert "None" not in raw_generated_result, "Test %s" % name
canonical_generated_result = ConfigMgr.to_canonical(raw_generated_result) canonical_generated_result = ConfigMgr.to_canonical(raw_generated_result)
match_path = os.path.join(DATA_PATH, match_path) match_path = os.path.join(DATA_PATH, match_path)

View File

@ -6,6 +6,7 @@ from bgpcfgd.template import TemplateFabric
from bgpcfgd.config import ConfigMgr from bgpcfgd.config import ConfigMgr
from .util import load_constants from .util import load_constants
TEMPLATE_PATH = os.path.abspath('../../dockers/docker-fpm-frr/frr') TEMPLATE_PATH = os.path.abspath('../../dockers/docker-fpm-frr/frr')