mirror of
https://github.com/FRRouting/frr.git
synced 2025-04-30 13:37:17 +02:00
Merge 4cd214b229
into 3dd4d417be
This commit is contained in:
commit
69282c4d93
|
@ -3708,6 +3708,79 @@ DEFUN (no_bgp_neighbor_graceful_restart_disable,
|
|||
return bgp_vty_return(vty, ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Function to announce route to peer
|
||||
*/
|
||||
static void bgp_peer_announce_routes(struct peer *peer)
|
||||
{
|
||||
afi_t afi;
|
||||
safi_t safi;
|
||||
struct peer_af *paf = NULL;
|
||||
|
||||
FOREACH_AFI_SAFI (afi, safi) {
|
||||
if (!peer->afc[afi][safi])
|
||||
continue;
|
||||
paf = peer_af_find(peer, afi, safi);
|
||||
if (paf) {
|
||||
update_group_adjust_peer(paf);
|
||||
bgp_announce_route(peer, afi, safi, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Function to perform a soft reset of BGP neighborship on a peer or peer group
|
||||
*/
|
||||
static int bgp_peer_reset(struct vty *vty, const char *peer_str, enum clear_sort sort)
|
||||
{
|
||||
struct peer *peer = NULL;
|
||||
struct listnode *node, *nnode;
|
||||
struct peer_group *group;
|
||||
|
||||
VTY_DECLVAR_CONTEXT(bgp, bgp);
|
||||
|
||||
peer = peer_and_group_lookup_vty(vty, peer_str);
|
||||
bgp_clear(vty, bgp, AFI_UNSPEC, SAFI_UNSPEC, sort, BGP_CLEAR_SOFT_IN, peer_str);
|
||||
|
||||
if (sort == clear_group) {
|
||||
group = peer->group;
|
||||
for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer))
|
||||
bgp_peer_announce_routes(peer);
|
||||
} else {
|
||||
bgp_peer_announce_routes(peer);
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Function to enable or disable graceful shutdown configuration
|
||||
* on a peer or peer group
|
||||
*/
|
||||
static int bgp_peer_gshut_set_vty(struct vty *vty, const char *peer_str, bool set)
|
||||
{
|
||||
struct peer *peer = NULL;
|
||||
int ret = 0;
|
||||
|
||||
peer = peer_and_group_lookup_vty(vty, peer_str);
|
||||
if (!peer)
|
||||
return CMD_WARNING_CONFIG_FAILED;
|
||||
|
||||
if (!set)
|
||||
ret = peer_flag_unset_vty(vty, peer_str, PEER_FLAG_GRACEFUL_SHUTDOWN);
|
||||
else
|
||||
ret = peer_flag_set_vty(vty, peer_str, PEER_FLAG_GRACEFUL_SHUTDOWN);
|
||||
|
||||
if (ret == 0) {
|
||||
if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP))
|
||||
ret = bgp_peer_reset(vty, peer_str, clear_peer);
|
||||
else
|
||||
ret = bgp_peer_reset(vty, peer_str, clear_group);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
DEFPY (neighbor_graceful_shutdown,
|
||||
neighbor_graceful_shutdown_cmd,
|
||||
"[no$no] neighbor <A.B.C.D|X:X::X:X|WORD>$neighbor graceful-shutdown",
|
||||
|
@ -3716,32 +3789,12 @@ DEFPY (neighbor_graceful_shutdown,
|
|||
NEIGHBOR_ADDR_STR2
|
||||
"Graceful shutdown\n")
|
||||
{
|
||||
afi_t afi;
|
||||
safi_t safi;
|
||||
struct peer *peer;
|
||||
int ret;
|
||||
|
||||
VTY_DECLVAR_CONTEXT(bgp, bgp);
|
||||
|
||||
peer = peer_and_group_lookup_vty(vty, neighbor);
|
||||
if (!peer)
|
||||
return CMD_WARNING_CONFIG_FAILED;
|
||||
|
||||
if (no)
|
||||
ret = peer_flag_unset_vty(vty, neighbor,
|
||||
PEER_FLAG_GRACEFUL_SHUTDOWN);
|
||||
ret = bgp_peer_gshut_set_vty(vty, neighbor, false);
|
||||
else
|
||||
ret = peer_flag_set_vty(vty, neighbor,
|
||||
PEER_FLAG_GRACEFUL_SHUTDOWN);
|
||||
|
||||
FOREACH_AFI_SAFI (afi, safi) {
|
||||
if (!peer->afc[afi][safi])
|
||||
continue;
|
||||
|
||||
bgp_clear(vty, bgp, afi, safi, clear_peer, BGP_CLEAR_SOFT_IN,
|
||||
neighbor);
|
||||
}
|
||||
|
||||
ret = bgp_peer_gshut_set_vty(vty, neighbor, true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
16
tests/topotests/bgp_peergroup_gshut/r1/frr.conf
Normal file
16
tests/topotests/bgp_peergroup_gshut/r1/frr.conf
Normal file
|
@ -0,0 +1,16 @@
|
|||
!
|
||||
interface lo
|
||||
ip address 10.1.1.1/32
|
||||
!
|
||||
interface r1-eth0
|
||||
ip address 172.16.1.1/24
|
||||
!
|
||||
router bgp 65001
|
||||
no bgp ebgp-requires-policy
|
||||
neighbor 172.16.1.2 remote-as 65002
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.1.1.1/32
|
||||
neighbor 172.16.1.2 activate
|
||||
exit-address-family
|
||||
!
|
21
tests/topotests/bgp_peergroup_gshut/r2/frr.conf
Normal file
21
tests/topotests/bgp_peergroup_gshut/r2/frr.conf
Normal file
|
@ -0,0 +1,21 @@
|
|||
!
|
||||
interface lo
|
||||
ip address 10.0.1.2/32
|
||||
!
|
||||
interface r2-eth0
|
||||
ip address 172.16.1.2/24
|
||||
!
|
||||
interface r2-eth1
|
||||
ip address 172.16.2.1/24
|
||||
!
|
||||
router bgp 65002
|
||||
no bgp ebgp-requires-policy
|
||||
neighbor PEER-GROUP1 peer-group
|
||||
neighbor PEER-GROUP1 remote-as external
|
||||
neighbor 172.16.1.1 peer-group PEER-GROUP1
|
||||
neighbor 172.16.2.2 peer-group PEER-GROUP1
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
neighbor PEER-GROUP1 activate
|
||||
exit-address-family
|
||||
!
|
16
tests/topotests/bgp_peergroup_gshut/r3/frr.conf
Normal file
16
tests/topotests/bgp_peergroup_gshut/r3/frr.conf
Normal file
|
@ -0,0 +1,16 @@
|
|||
!
|
||||
interface lo
|
||||
ip address 10.3.3.3/32
|
||||
!
|
||||
interface r3-eth0
|
||||
ip address 172.16.2.2/24
|
||||
!
|
||||
router bgp 65003
|
||||
no bgp ebgp-requires-policy
|
||||
neighbor 172.16.2.1 remote-as 65002
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.3.3.3/32
|
||||
neighbor 172.16.2.1 activate
|
||||
exit-address-family
|
||||
!
|
636
tests/topotests/bgp_peergroup_gshut/test_bgp_peergroup_gshut.py
Normal file
636
tests/topotests/bgp_peergroup_gshut/test_bgp_peergroup_gshut.py
Normal file
|
@ -0,0 +1,636 @@
|
|||
#!/usr/bin/env python
|
||||
# SPDX-License-Identifier: ISC
|
||||
#
|
||||
# Copyright (c) 2025 by
|
||||
# NVIDIA CORPORATION ("NVIDIA"). All rights reserved.
|
||||
#
|
||||
|
||||
"""
|
||||
test_bgp_peergroup_gshut.py: Test BGP peer group graceful shutdown functionality
|
||||
|
||||
Test BGP peer group graceful shutdown functionality:
|
||||
|
||||
+------------+ +------------+ +------------+
|
||||
| R1 | | R2 | | R3 |
|
||||
| | | | | |
|
||||
| 10.0.1.1/24| |10.0.1.2/24 | |10.0.1.3/24 |
|
||||
| | | | | |
|
||||
+------------+ +------------+ +------------+
|
||||
| | |
|
||||
| 172.16.1.0/24 | 172.16.2.0/24 |
|
||||
| | |
|
||||
+------------------+------------------+
|
||||
|
|
||||
BGP Peer Group
|
||||
PEER-GROUP1
|
||||
|
||||
Topology:
|
||||
- All routers running BGP
|
||||
- R1 and R3 are members of peer group PEER-GROUP1 on R2
|
||||
- R2 will initiate graceful shutdown for the peer group
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import pytest
|
||||
import functools
|
||||
from lib import topotest
|
||||
from lib.topogen import Topogen, TopoRouter, get_topogen
|
||||
from lib.topolog import logger
|
||||
from lib.topotest import json_cmp
|
||||
|
||||
# Save the Current Working Directory to find configuration files.
|
||||
CWD = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.append(os.path.dirname(CWD))
|
||||
|
||||
# Mark test with bgpd
|
||||
pytestmark = [pytest.mark.bgpd]
|
||||
|
||||
# Helper Functions
|
||||
def check_route_has_gshut_community(path):
|
||||
"""Check if a route has the graceful-shutdown community."""
|
||||
if 'community' not in path:
|
||||
return False
|
||||
return (path['community'].get('string') == 'graceful-shutdown' and
|
||||
'gracefulShutdown' in path['community'].get('list', []))
|
||||
|
||||
def check_route_gshut_attributes(path, expect_gshut=False):
|
||||
"""Check route attributes specific to graceful shutdown state.
|
||||
|
||||
Args:
|
||||
path: BGP path information
|
||||
expect_gshut: Whether to expect graceful shutdown attributes
|
||||
|
||||
Returns:
|
||||
bool: True if route attributes match expected graceful shutdown state
|
||||
"""
|
||||
if not path.get('valid', False):
|
||||
return False
|
||||
|
||||
has_gshut = check_route_has_gshut_community(path)
|
||||
if expect_gshut:
|
||||
if not has_gshut or path.get('locPrf', 100) != 0:
|
||||
return False
|
||||
else:
|
||||
if has_gshut:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_route_info(router, prefix):
|
||||
"""Get route information in JSON format."""
|
||||
output = json.loads(router.vtysh_cmd(f"show bgp ipv4 unicast {prefix} json"))
|
||||
logger.info(f"BGP route {prefix} on {router.name}: %s", output)
|
||||
if 'paths' not in output:
|
||||
return None
|
||||
return output['paths'][0]
|
||||
|
||||
def check_prefix_gshut_enabled(router, prefix):
|
||||
"""Check if a router has GSHUT community enabled for a specific prefix."""
|
||||
path = get_route_info(router, prefix)
|
||||
return path is not None and check_route_gshut_attributes(path, expect_gshut=True)
|
||||
|
||||
def check_prefix_gshut_disabled(router, prefix):
|
||||
"""Check if a router has GSHUT community disabled for a specific prefix."""
|
||||
path = get_route_info(router, prefix)
|
||||
return path is not None and check_route_gshut_attributes(path, expect_gshut=False)
|
||||
|
||||
def check_no_session_flap(router, peer_ip, initial_uptime_msec, initial_established_epoch):
|
||||
"""Check if BGP session has not flapped."""
|
||||
current_neighbor_info = json.loads(router.vtysh_cmd(f"show bgp neighbors {peer_ip} json"))
|
||||
current_uptime_msec = current_neighbor_info[peer_ip]['bgpTimerUpMsec']
|
||||
current_established_epoch = current_neighbor_info[peer_ip]['bgpTimerUpEstablishedEpoch']
|
||||
return (current_uptime_msec >= initial_uptime_msec and
|
||||
current_established_epoch == initial_established_epoch)
|
||||
|
||||
def is_bgp_session_established(router, peer_ip):
|
||||
"""Check if a BGP session is in the Established state.
|
||||
|
||||
Args:
|
||||
router: Router instance to check
|
||||
peer_ip: IP address of the BGP peer
|
||||
|
||||
Returns:
|
||||
bool: True if the BGP session is in Established state, False otherwise
|
||||
"""
|
||||
current_state = json.loads(router.vtysh_cmd("show ip bgp summary json"))
|
||||
return current_state['ipv4Unicast']['peers'][peer_ip]['state'] == 'Established'
|
||||
|
||||
def get_session_timing_info(router, peer_ip):
|
||||
"""Get BGP session timing information."""
|
||||
neighbor_info = json.loads(router.vtysh_cmd(f"show bgp neighbors {peer_ip} json"))
|
||||
return {
|
||||
'bgpTimerUpMsec': neighbor_info[peer_ip]['bgpTimerUpMsec'],
|
||||
'bgpTimerUpEstablishedEpoch': neighbor_info[peer_ip]['bgpTimerUpEstablishedEpoch']
|
||||
}
|
||||
|
||||
def wait_for_bgp_convergence(router, expected_state, count=12, wait=5):
|
||||
"""Wait for BGP to converge with expected state."""
|
||||
|
||||
test_func = functools.partial(
|
||||
topotest.router_json_cmp,
|
||||
router,
|
||||
"show ip bgp summary json",
|
||||
{'ipv4Unicast': {'peers': expected_state}}
|
||||
)
|
||||
success, result = topotest.run_and_expect(test_func, None, count=count, wait=wait)
|
||||
return success
|
||||
|
||||
# Topology Building Functions
|
||||
def build_topo(tgen):
|
||||
"""Build function."""
|
||||
# Add Routers
|
||||
for routern in range(1, 4):
|
||||
tgen.add_router(f'r{routern}')
|
||||
|
||||
# Add Switches for each subnet
|
||||
switch1 = tgen.add_switch('s1') # For 172.16.1.0/24
|
||||
switch2 = tgen.add_switch('s2') # For 172.16.2.0/24
|
||||
|
||||
# Add links
|
||||
# R1 - R2 subnet (172.16.1.0/24)
|
||||
switch1.add_link(tgen.gears['r1'], nodeif="r1-eth0")
|
||||
switch1.add_link(tgen.gears['r2'], nodeif="r2-eth0")
|
||||
|
||||
# R2 - R3 subnet (172.16.2.0/24)
|
||||
switch2.add_link(tgen.gears['r2'], nodeif="r2-eth1")
|
||||
switch2.add_link(tgen.gears['r3'], nodeif="r3-eth0")
|
||||
|
||||
def setup_module(mod):
|
||||
"""Sets up the pytest environment."""
|
||||
tgen = Topogen(build_topo, mod.__name__)
|
||||
tgen.start_topology()
|
||||
|
||||
router_list = tgen.routers()
|
||||
for _, (rname, router) in enumerate(router_list.items(), 1):
|
||||
router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
|
||||
|
||||
tgen.start_router()
|
||||
|
||||
def teardown_module(mod):
|
||||
"""Tears down the pytest environment."""
|
||||
tgen = get_topogen()
|
||||
tgen.stop_topology()
|
||||
|
||||
# Test Functions
|
||||
def test_peer_group_graceful_shutdown():
|
||||
"""Test graceful shutdown functionality for peer group:
|
||||
1. Verify initial route state without GSHUT
|
||||
2. Enable GSHUT on peer group and verify routes
|
||||
3. Disable GSHUT and verify routes return to normal
|
||||
"""
|
||||
tgen = get_topogen()
|
||||
if tgen.routers_have_failure():
|
||||
pytest.skip(tgen.errors)
|
||||
|
||||
logger.info("Testing BGP peer group graceful shutdown")
|
||||
r1 = tgen.gears['r1']
|
||||
r2 = tgen.gears['r2']
|
||||
r3 = tgen.gears['r3']
|
||||
|
||||
# Step 1: Wait for initial BGP convergence
|
||||
logger.info("Waiting for initial BGP convergence")
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: wait_for_bgp_convergence(r2, {
|
||||
'172.16.1.1': {'state': 'Established'},
|
||||
'172.16.2.2': {'state': 'Established'}
|
||||
}),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "BGP did not converge initially"
|
||||
|
||||
# Step 2: Verify initial route state without GSHUT
|
||||
logger.info("Checking routes before enabling graceful-shutdown")
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_disabled(r1, "10.3.3.3/32") and
|
||||
check_prefix_gshut_disabled(r3, "10.1.1.1/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "Routes should not have graceful-shutdown community initially"
|
||||
|
||||
# Step 3: Enable GSHUT on peer group
|
||||
logger.info("Enabling graceful-shutdown for peer group on R2")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
neighbor PEER-GROUP1 graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
# Verify GSHUT is enabled on both peers
|
||||
logger.info("Verifying routes have graceful-shutdown community on peers")
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_enabled(r1, "10.3.3.3/32") and
|
||||
check_prefix_gshut_enabled(r3, "10.1.1.1/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "Routes do not have graceful-shutdown community on peers"
|
||||
|
||||
# Step 4: Disable GSHUT
|
||||
logger.info("Disabling graceful-shutdown for peer group")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
no neighbor PEER-GROUP1 graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
# Verify GSHUT is disabled on both peers
|
||||
logger.info("Verifying routes no longer have graceful-shutdown community")
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_disabled(r1, "10.3.3.3/32") and
|
||||
check_prefix_gshut_disabled(r3, "10.1.1.1/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "Routes still have graceful-shutdown community after disabling"
|
||||
|
||||
def test_peer_group_graceful_shutdown_hierarchy():
|
||||
"""Test graceful shutdown hierarchy behavior:
|
||||
1. Enable GSHUT at global level and verify
|
||||
2. Enable GSHUT at neighbor level and verify still enabled
|
||||
3. Disable GSHUT at global level and verify still enabled
|
||||
4. Enable GSHUT at peer-group level and verify still enabled
|
||||
5. Disable GSHUT at neighbor level and verify still enabled
|
||||
6. Disable GSHUT at peer-group level and verify disabled
|
||||
"""
|
||||
tgen = get_topogen()
|
||||
if tgen.routers_have_failure():
|
||||
pytest.skip(tgen.errors)
|
||||
|
||||
logger.info("Testing BGP peer group graceful shutdown hierarchy")
|
||||
r1 = tgen.gears['r1']
|
||||
r2 = tgen.gears['r2']
|
||||
r3 = tgen.gears['r3']
|
||||
|
||||
# Wait for initial convergence
|
||||
logger.info("Waiting for initial BGP convergence")
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: wait_for_bgp_convergence(r2, {
|
||||
'172.16.1.1': {'state': 'Established'},
|
||||
'172.16.2.2': {'state': 'Established'}
|
||||
}),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "BGP did not converge initially"
|
||||
|
||||
# Step 1: Enable GSHUT at global level
|
||||
logger.info("Step 1: Enabling GSHUT at global level")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
bgp graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
# Verify R1 receives GSHUT community
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_enabled(r1, "10.3.3.3/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "R1 did not receive GSHUT community after global-level enable"
|
||||
|
||||
# Step 2: Enable GSHUT at neighbor level
|
||||
logger.info("Step 2: Enabling GSHUT at neighbor level")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
neighbor 172.16.1.1 graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
# Verify R1 still receives GSHUT community
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_enabled(r1, "10.3.3.3/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "R1 should still receive GSHUT community after neighbor-level enable"
|
||||
|
||||
# Step 3: Disable GSHUT at global level
|
||||
logger.info("Step 3: Disabling GSHUT at global level")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
no bgp graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
# Verify R1 still receives GSHUT community (enabled at neighbor level)
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_enabled(r1, "10.3.3.3/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "R1 should still receive GSHUT community after global-level disable"
|
||||
|
||||
# Step 4: Enable GSHUT at peer-group level
|
||||
logger.info("Step 4: Enabling GSHUT at peer-group level")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
neighbor PEER-GROUP1 graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
# Verify R1 still receives GSHUT community
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_enabled(r1, "10.3.3.3/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "R1 should still receive GSHUT community after peer-group-level enable"
|
||||
|
||||
# Step 5: Disable GSHUT at neighbor level
|
||||
logger.info("Step 5: Disabling GSHUT at neighbor level")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
no neighbor 172.16.1.1 graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
# Verify R1 still receives GSHUT community (enabled at peer-group level)
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_enabled(r1, "10.3.3.3/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "R1 should still receive GSHUT community after neighbor-level disable"
|
||||
|
||||
# Step 6: Disable GSHUT at peer-group level
|
||||
logger.info("Step 6: Disabling GSHUT at peer-group level")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
no neighbor PEER-GROUP1 graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
# Verify R1 no longer receives GSHUT community (disabled at all levels)
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_disabled(r1, "10.3.3.3/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "R1 should not receive GSHUT community after disabling at all levels"
|
||||
|
||||
def test_peer_group_graceful_shutdown_move_peer():
|
||||
"""Test graceful shutdown behavior when moving peers between peer groups."""
|
||||
tgen = get_topogen()
|
||||
if tgen.routers_have_failure():
|
||||
pytest.skip(tgen.errors)
|
||||
|
||||
r1 = tgen.gears["r1"]
|
||||
r2 = tgen.gears["r2"]
|
||||
r3 = tgen.gears["r3"]
|
||||
|
||||
# Step 1: Create PEER-GROUP2 without GSHUT
|
||||
logger.info("Creating PEER-GROUP2 without GSHUT")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
neighbor PEER-GROUP2 peer-group
|
||||
neighbor PEER-GROUP2 remote-as external
|
||||
address-family ipv4 unicast
|
||||
neighbor PEER-GROUP2 activate
|
||||
exit-address-family
|
||||
end
|
||||
""")
|
||||
|
||||
# Wait for BGP to converge
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: wait_for_bgp_convergence(r2, {
|
||||
'172.16.1.1': {'state': 'Established'},
|
||||
'172.16.2.2': {'state': 'Established'}
|
||||
}),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "BGP did not converge after creating PEER-GROUP2"
|
||||
|
||||
# Step 2: Enable GSHUT on PEER-GROUP1
|
||||
logger.info("Enabling GSHUT on PEER-GROUP1")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
neighbor PEER-GROUP1 graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
# Verify R1 receives GSHUT community (member of PEER-GROUP1)
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_enabled(r1, "10.3.3.3/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "R1 does not receive GSHUT community as member of PEER-GROUP1"
|
||||
|
||||
# Step 3: Move both R1 and R2 from PEER-GROUP1 to PEER-GROUP2
|
||||
logger.info("Moving both R1 and R2 from PEER-GROUP1 to PEER-GROUP2")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
no neighbor 172.16.1.1 peer-group PEER-GROUP1
|
||||
no neighbor 172.16.2.2 peer-group PEER-GROUP1
|
||||
neighbor 172.16.1.1 peer-group PEER-GROUP2
|
||||
neighbor 172.16.2.2 peer-group PEER-GROUP2
|
||||
end
|
||||
""")
|
||||
|
||||
# Wait for BGP to converge after peer group change
|
||||
logger.info("Waiting for BGP to converge after peer group change")
|
||||
|
||||
# First verify peer group configuration
|
||||
def _check_peer_group_config():
|
||||
output = json.loads(r2.vtysh_cmd("show bgp peer-group json"))
|
||||
if 'PEER-GROUP2' not in output:
|
||||
return False
|
||||
if '172.16.1.1' not in output['PEER-GROUP2'].get('members', []):
|
||||
return False
|
||||
if '172.16.2.2' not in output['PEER-GROUP2'].get('members', []):
|
||||
return False
|
||||
return True
|
||||
|
||||
success, result = topotest.run_and_expect(_check_peer_group_config, True, count=12, wait=5)
|
||||
assert success, "Peer group configuration not updated correctly"
|
||||
|
||||
# Then verify BGP convergence
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: wait_for_bgp_convergence(r2, {
|
||||
'172.16.1.1': {'state': 'Established'},
|
||||
'172.16.2.2': {'state': 'Established'}
|
||||
}),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "BGP did not converge after peer group change"
|
||||
|
||||
# Verify R1 no longer receives GSHUT community (now member of PEER-GROUP2)
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_disabled(r1, "10.3.3.3/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "R1 still receives GSHUT community after moving to PEER-GROUP2"
|
||||
|
||||
# Step 4: Move both R1 and R2 back to PEER-GROUP1
|
||||
logger.info("Moving both R1 and R2 back to PEER-GROUP1")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
no neighbor 172.16.1.1 peer-group PEER-GROUP2
|
||||
no neighbor 172.16.2.2 peer-group PEER-GROUP2
|
||||
neighbor 172.16.1.1 peer-group PEER-GROUP1
|
||||
neighbor 172.16.2.2 peer-group PEER-GROUP1
|
||||
end
|
||||
""")
|
||||
|
||||
# Wait for BGP to converge
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: wait_for_bgp_convergence(r2, {
|
||||
'172.16.1.1': {'state': 'Established'},
|
||||
'172.16.2.2': {'state': 'Established'}
|
||||
}),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "BGP did not converge after moving R1 and R2 back to PEER-GROUP1"
|
||||
|
||||
# Verify R1 receives GSHUT community again (back in PEER-GROUP1)
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_enabled(r1, "10.3.3.3/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "R1 does not receive GSHUT community after moving back to PEER-GROUP1"
|
||||
|
||||
# Step 5: Cleanup
|
||||
logger.info("Cleaning up GSHUT configurations")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
no neighbor PEER-GROUP1 graceful-shutdown
|
||||
no neighbor PEER-GROUP2 peer-group
|
||||
end
|
||||
""")
|
||||
|
||||
def test_peer_group_graceful_shutdown_preserve_after_restart():
|
||||
"""Test that GSHUT configuration is preserved on peer group after FRR restart."""
|
||||
tgen = get_topogen()
|
||||
if tgen.routers_have_failure():
|
||||
pytest.skip(tgen.errors)
|
||||
|
||||
r1 = tgen.gears["r1"]
|
||||
r2 = tgen.gears["r2"]
|
||||
|
||||
# Step 1: Enable GSHUT on PEER-GROUP1
|
||||
logger.info("Enabling GSHUT on PEER-GROUP1")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
neighbor PEER-GROUP1 graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
# Verify R1 receives GSHUT community
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_enabled(r1, "10.3.3.3/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "R1 does not receive GSHUT community after enabling GSHUT"
|
||||
|
||||
# Step 2: Restart FRR on R2
|
||||
logger.info("Saving configuration before restart")
|
||||
r2.vtysh_cmd("write memory")
|
||||
|
||||
logger.info("Restarting FRR on R2")
|
||||
r2.stop()
|
||||
r2.start()
|
||||
|
||||
# Wait for BGP to converge after restart
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: wait_for_bgp_convergence(r2, {
|
||||
'172.16.1.1': {'state': 'Established'},
|
||||
'172.16.2.2': {'state': 'Established'}
|
||||
}),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "BGP did not converge after FRR restart"
|
||||
|
||||
# Verify GSHUT configuration is preserved on PEER-GROUP1
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_enabled(r1, "10.3.3.3/32"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "GSHUT configuration not preserved on PEER-GROUP1 after restart"
|
||||
|
||||
# Cleanup
|
||||
logger.info("Cleaning up GSHUT configurations")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
no neighbor PEER-GROUP1 graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
def test_peer_group_graceful_shutdown_session_stability():
|
||||
"""Test BGP session stability when GSHUT is added/removed from peer group:
|
||||
1. Monitor BGP session state before GSHUT
|
||||
2. Enable GSHUT on peer group and verify session remains stable
|
||||
3. Disable GSHUT on peer group and verify session remains stable
|
||||
4. Verify GSHUT community is properly added/removed
|
||||
"""
|
||||
tgen = get_topogen()
|
||||
if tgen.routers_have_failure():
|
||||
pytest.skip(tgen.errors)
|
||||
|
||||
r1 = tgen.gears["r1"]
|
||||
r2 = tgen.gears["r2"]
|
||||
r3 = tgen.gears["r3"]
|
||||
|
||||
# Step 1: Get initial BGP session state
|
||||
logger.info("Getting initial BGP session state")
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: is_bgp_session_established(r2, "172.16.1.1"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "Initial BGP session not established"
|
||||
|
||||
# Get initial session timing info
|
||||
timing_info = get_session_timing_info(r2, "172.16.1.1")
|
||||
initial_uptime_msec = timing_info['bgpTimerUpMsec']
|
||||
initial_established_epoch = timing_info['bgpTimerUpEstablishedEpoch']
|
||||
|
||||
# Step 2: Enable GSHUT and verify stability
|
||||
logger.info("Enabling GSHUT on PEER-GROUP1")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
neighbor PEER-GROUP1 graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
# Verify GSHUT is enabled and session is stable
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_enabled(r1, "10.3.3.3/32") and
|
||||
check_no_session_flap(r2, "172.16.1.1", initial_uptime_msec, initial_established_epoch),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "GSHUT community not added or session flapped after enabling GSHUT"
|
||||
|
||||
# Step 3: Disable GSHUT and verify stability
|
||||
logger.info("Disabling GSHUT on PEER-GROUP1")
|
||||
r2.vtysh_cmd("""
|
||||
configure terminal
|
||||
router bgp 65002
|
||||
no neighbor PEER-GROUP1 graceful-shutdown
|
||||
end
|
||||
""")
|
||||
|
||||
# Verify GSHUT is disabled and session is stable
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: check_prefix_gshut_disabled(r1, "10.3.3.3/32") and
|
||||
check_no_session_flap(r2, "172.16.1.1", initial_uptime_msec, initial_established_epoch),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "GSHUT community not removed or session flapped after disabling GSHUT"
|
||||
|
||||
# Step 4: Verify final BGP session state
|
||||
success, result = topotest.run_and_expect(
|
||||
lambda: is_bgp_session_established(r2, "172.16.1.1"),
|
||||
True, count=12, wait=5
|
||||
)
|
||||
assert success, "Final BGP session not established"
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = ["-s"] + sys.argv[1:]
|
||||
sys.exit(pytest.main(args))
|
Loading…
Reference in a new issue