Commit d2ddf7cfec for frr

commit d2ddf7cfece7c6a912f0ce45208c12d5e72477bb
Author: Chirag Shah <chirag@nvidia.com>
Date:   Sat Nov 7 13:37:05 2026 -0800

    tests: [EVPN] wait for the ES before the MAC move baseline

    test_evpn_mac_move_to_es_nhg_inactive[ipv6] failed in move_baseline()
    with "ES ... VTEPs on torm11 are [], expected [...]".

    The test before it, test_evpn_uplink_tracking, drops torm11's two uplinks
    and brings them back. That resets torm11's BGP sessions and withdraws
    every ES it learned. zebra frees a remote ES once its EAD and MAC routes
    are gone and rebuilds it only after bgpd re-advertises them, so for a
    short window "show evpn es <esi>" is empty. The uplink test only waited
    for the protodown reason to clear, which happens as soon as the links are
    up, and move_baseline() then read the ES exactly once. IPv4 read it after
    zebra had rebuilt it; IPv6 read it inside the window.

    Fix both ends:

    - move_baseline() polls check_move_es_nhg_active() instead of reading
      once. It is the check stage 4 already uses: bgpd and zebra both list
      every VTEP and the NHG is active. The nhg_active control case relies on
      an active NHG from its first stage, so this also makes it start from the
      state it claims to.
    - test_evpn_uplink_tracking waits for check_es() after restoring the
      uplinks, so the next test starts from a settled network and a failure
      is reported against the test that caused it.

    Signed-off-by: Chirag Shah <chirag@nvidia.com>

diff --git a/tests/topotests/bgp_evpn_mh_v4_v6_num/test_evpn_mh_v4_v6_num.py b/tests/topotests/bgp_evpn_mh_v4_v6_num/test_evpn_mh_v4_v6_num.py
index a192b66aff..4661cbc5ec 100644
--- a/tests/topotests/bgp_evpn_mh_v4_v6_num/test_evpn_mh_v4_v6_num.py
+++ b/tests/topotests/bgp_evpn_mh_v4_v6_num/test_evpn_mh_v4_v6_num.py
@@ -1474,6 +1474,17 @@ def test_evpn_uplink_tracking(tgen_and_ip_version):
     assertmsg = '"{}" protodown rc incorrect'.format(dut_name)
     assert result is None, assertmsg

+    # The uplink flap reset the BGP sessions, which withdrew every ES on the
+    # DUT. Protodown clears as soon as the links are up, long before the ESs
+    # are back, so wait for them here. Otherwise the next test starts from a
+    # half-built network and fails on a race this test caused.
+    test_fn = partial(check_es, dut)
+    _, result = topotest.run_and_expect(test_fn, None, count=30, wait=3)
+    assertmsg = '"{}" ES content incorrect after uplink restore. Result: {}'.format(
+        dut_name, result
+    )
+    assert result is None, assertmsg
+

 #####################################################
 ##
@@ -1692,12 +1703,26 @@ def delete_mac_from_port(leaf, port):

 def move_baseline(tgen):
     """
-    Read-only: record the state the cleanup must restore, and check the test
-    starts from a clean network (ES fully known on the observer, MAC nowhere).
+    Wait for the ES to be fully known on the observer, then record the state
+    the cleanup must restore and check the MAC is nowhere. Changes nothing.
     """
     obs = tgen.gears[MOVE_OBSERVER]
     leaves = [tgen.gears[MOVE_LEARN_LEAF], tgen.gears[MOVE_PEER_LEAF]]
     esi = host_es_map[MOVE_ES_HOST]
+    expected_vteps = sorted(tor_ips[n] for n in (MOVE_LEARN_LEAF, MOVE_PEER_LEAF))
+
+    # Wait for the ES instead of reading it once. zebra frees a remote ES when
+    # its EAD and MAC routes are withdrawn and rebuilds it only after bgpd
+    # re-advertises them, so a session reset leaves a window where
+    # "show evpn es" is empty. The same check proves the NHG is active, which
+    # the nhg_active control case relies on from its first stage.
+    _, result = topotest.run_and_expect(
+        partial(check_move_es_nhg_active, obs, esi, expected_vteps),
+        None,
+        count=30,
+        wait=3,
+    )
+    assert result is None, "baseline: %s" % result

     saved = {
         "esi": esi,
@@ -1715,15 +1740,6 @@ def move_baseline(tgen):
         "obs_es_vteps": evpn_lib.evpn_zebra_es_state(obs, esi)["vteps"],
     }

-    expected_vteps = sorted(tor_ips[n] for n in (MOVE_LEARN_LEAF, MOVE_PEER_LEAF))
-    assert (
-        saved["obs_es_vteps"] == expected_vteps
-    ), "baseline: ES %s VTEPs on %s are %s, expected %s" % (
-        esi,
-        obs.name,
-        saved["obs_es_vteps"],
-        expected_vteps,
-    )
     for router in [obs] + leaves:
         result = evpn_lib.evpn_check_mac_absent(router, MOVE_VNI, MOVE_MAC)
         assert result is None, "baseline: %s" % result