selftests: fib_nexthops: Test resilient nexthop groups
authorIdo Schimmel <idosch@nvidia.com>
Fri, 12 Mar 2021 16:50:23 +0000 (17:50 +0100)
committerDavid S. Miller <davem@davemloft.net>
Sat, 13 Mar 2021 01:44:10 +0000 (17:44 -0800)
Add test cases for resilient nexthop groups. Exhaustive forwarding tests
are added separately under net/forwarding/.

Examples:

 # ./fib_nexthops.sh -t basic_res

Basic resilient nexthop group functional tests
----------------------------------------------
TEST: Add a nexthop group with default parameters                   [ OK ]
TEST: Get a nexthop group with default parameters                   [ OK ]
TEST: Get a nexthop group with non-default parameters               [ OK ]
TEST: Add a nexthop group with 0 buckets                            [ OK ]
TEST: Replace nexthop group parameters                              [ OK ]
TEST: Get a nexthop group after replacing parameters                [ OK ]
TEST: Replace idle timer                                            [ OK ]
TEST: Get a nexthop group after replacing idle timer                [ OK ]
TEST: Replace unbalanced timer                                      [ OK ]
TEST: Get a nexthop group after replacing unbalanced timer          [ OK ]
TEST: Replace with no parameters                                    [ OK ]
TEST: Get a nexthop group after replacing no parameters             [ OK ]
TEST: Replace nexthop group type - implicit                         [ OK ]
TEST: Replace nexthop group type - explicit                         [ OK ]
TEST: Replace number of nexthop buckets                             [ OK ]
TEST: Get a nexthop group after replacing with invalid parameters   [ OK ]
TEST: Dump all nexthop buckets                                      [ OK ]
TEST: Dump all nexthop buckets in a group                           [ OK ]
TEST: Dump all nexthop buckets with a specific nexthop device       [ OK ]
TEST: Dump all nexthop buckets with a specific nexthop identifier   [ OK ]
TEST: Dump all nexthop buckets in a non-existent group              [ OK ]
TEST: Dump all nexthop buckets in a non-resilient group             [ OK ]
TEST: Dump all nexthop buckets using a non-existent device          [ OK ]
TEST: Dump all nexthop buckets with invalid 'groups' keyword        [ OK ]
TEST: Dump all nexthop buckets with invalid 'fdb' keyword           [ OK ]
TEST: Get a valid nexthop bucket                                    [ OK ]
TEST: Get a nexthop bucket with valid group, but invalid index      [ OK ]
TEST: Get a nexthop bucket from a non-resilient group               [ OK ]
TEST: Get a nexthop bucket from a non-existent group                [ OK ]

Tests passed:  29
Tests failed:   0

 # ./fib_nexthops.sh -t ipv4_large_res_grp

IPv4 large resilient group (128k buckets)
-----------------------------------------
TEST: Dump large (x131072) nexthop buckets                          [ OK ]

Tests passed:   1
Tests failed:   0

 # ./fib_nexthops.sh -t ipv6_large_res_grp

IPv6 large resilient group (128k buckets)
-----------------------------------------
TEST: Dump large (x131072) nexthop buckets                          [ OK ]

Tests passed:   1
Tests failed:   0

 # ./fib_nexthops.sh -t ipv4_res_torture

IPv4 runtime resilient nexthop group torture
--------------------------------------------
TEST: IPv4 resilient nexthop group torture test                     [ OK ]

Tests passed:   1
Tests failed:   0

 # ./fib_nexthops.sh -t ipv6_res_torture

IPv6 runtime resilient nexthop group torture
--------------------------------------------
TEST: IPv6 resilient nexthop group torture test                     [ OK ]

Tests passed:   1
Tests failed:   0

 # ./fib_nexthops.sh -t ipv4_res_grp_fcnal

IPv4 resilient groups functional
--------------------------------
TEST: Nexthop group updated when entry is deleted                   [ OK ]
TEST: Nexthop buckets updated when entry is deleted                 [ OK ]
TEST: Nexthop group updated after replace                           [ OK ]
TEST: Nexthop buckets updated after replace                         [ OK ]
TEST: Nexthop group updated when entry is deleted - nECMP           [ OK ]
TEST: Nexthop buckets updated when entry is deleted - nECMP         [ OK ]
TEST: Nexthop group updated after replace - nECMP                   [ OK ]
TEST: Nexthop buckets updated after replace - nECMP                 [ OK ]

Tests passed:   8
Tests failed:   0

 # ./fib_nexthops.sh -t ipv6_res_grp_fcnal

IPv6 resilient groups functional
--------------------------------
TEST: Nexthop group updated when entry is deleted                   [ OK ]
TEST: Nexthop buckets updated when entry is deleted                 [ OK ]
TEST: Nexthop group updated after replace                           [ OK ]
TEST: Nexthop buckets updated after replace                         [ OK ]
TEST: Nexthop group updated when entry is deleted - nECMP           [ OK ]
TEST: Nexthop buckets updated when entry is deleted - nECMP         [ OK ]
TEST: Nexthop group updated after replace - nECMP                   [ OK ]
TEST: Nexthop buckets updated after replace - nECMP                 [ OK ]

Tests passed:   8
Tests failed:   0

Signed-off-by: Ido Schimmel <idosch@nvidia.com>
Co-developed-by: Petr Machata <petrm@nvidia.com>
Signed-off-by: Petr Machata <petrm@nvidia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
tools/testing/selftests/net/fib_nexthops.sh

index c840aa8..56dd0c6 100755 (executable)
@@ -22,26 +22,33 @@ ksft_skip=4
 IPV4_TESTS="
        ipv4_fcnal
        ipv4_grp_fcnal
+       ipv4_res_grp_fcnal
        ipv4_withv6_fcnal
        ipv4_fcnal_runtime
        ipv4_large_grp
+       ipv4_large_res_grp
        ipv4_compat_mode
        ipv4_fdb_grp_fcnal
        ipv4_torture
+       ipv4_res_torture
 "
 
 IPV6_TESTS="
        ipv6_fcnal
        ipv6_grp_fcnal
+       ipv6_res_grp_fcnal
        ipv6_fcnal_runtime
        ipv6_large_grp
+       ipv6_large_res_grp
        ipv6_compat_mode
        ipv6_fdb_grp_fcnal
        ipv6_torture
+       ipv6_res_torture
 "
 
 ALL_TESTS="
        basic
+       basic_res
        ${IPV4_TESTS}
        ${IPV6_TESTS}
 "
@@ -254,6 +261,19 @@ check_nexthop()
        check_output "${out}" "${expected}"
 }
 
+check_nexthop_bucket()
+{
+       local nharg="$1"
+       local expected="$2"
+       local out
+
+       # remove the idle time since we cannot match it
+       out=$($IP nexthop bucket ${nharg} \
+               | sed s/idle_time\ [0-9.]*\ // 2>/dev/null)
+
+       check_output "${out}" "${expected}"
+}
+
 check_route()
 {
        local pfx="$1"
@@ -330,6 +350,25 @@ check_large_grp()
        log_test $? 0 "Dump large (x$ecmp) ecmp groups"
 }
 
+check_large_res_grp()
+{
+       local ipv=$1
+       local buckets=$2
+       local ipstr=""
+
+       if [ $ipv -eq 4 ]; then
+               ipstr="172.16.1.2"
+       else
+               ipstr="2001:db8:91::2"
+       fi
+
+       # create a resilient group with $buckets buckets and dump them
+       run_cmd "$IP nexthop add id 100 via $ipstr dev veth1"
+       run_cmd "$IP nexthop add id 1000 group 100 type resilient buckets $buckets"
+       run_cmd "$IP nexthop bucket list"
+       log_test $? 0 "Dump large (x$buckets) nexthop buckets"
+}
+
 start_ip_monitor()
 {
        local mtype=$1
@@ -366,6 +405,15 @@ check_nexthop_fdb_support()
        fi
 }
 
+check_nexthop_res_support()
+{
+       $IP nexthop help 2>&1 | grep -q resilient
+       if [ $? -ne 0 ]; then
+               echo "SKIP: iproute2 too old, missing resilient nexthop group support"
+               return $ksft_skip
+       fi
+}
+
 ipv6_fdb_grp_fcnal()
 {
        local rc
@@ -688,6 +736,70 @@ ipv6_grp_fcnal()
        log_test $? 2 "Nexthop group can not have a blackhole and another nexthop"
 }
 
+ipv6_res_grp_fcnal()
+{
+       local rc
+
+       echo
+       echo "IPv6 resilient groups functional"
+       echo "--------------------------------"
+
+       check_nexthop_res_support
+       if [ $? -eq $ksft_skip ]; then
+               return $ksft_skip
+       fi
+
+       #
+       # migration of nexthop buckets - equal weights
+       #
+       run_cmd "$IP nexthop add id 62 via 2001:db8:91::2 dev veth1"
+       run_cmd "$IP nexthop add id 63 via 2001:db8:91::3 dev veth1"
+       run_cmd "$IP nexthop add id 102 group 62/63 type resilient buckets 2 idle_timer 0"
+
+       run_cmd "$IP nexthop del id 63"
+       check_nexthop "id 102" \
+               "id 102 group 62 type resilient buckets 2 idle_timer 0 unbalanced_timer 0 unbalanced_time 0"
+       log_test $? 0 "Nexthop group updated when entry is deleted"
+       check_nexthop_bucket "list id 102" \
+               "id 102 index 0 nhid 62 id 102 index 1 nhid 62"
+       log_test $? 0 "Nexthop buckets updated when entry is deleted"
+
+       run_cmd "$IP nexthop add id 63 via 2001:db8:91::3 dev veth1"
+       run_cmd "$IP nexthop replace id 102 group 62/63 type resilient buckets 2 idle_timer 0"
+       check_nexthop "id 102" \
+               "id 102 group 62/63 type resilient buckets 2 idle_timer 0 unbalanced_timer 0 unbalanced_time 0"
+       log_test $? 0 "Nexthop group updated after replace"
+       check_nexthop_bucket "list id 102" \
+               "id 102 index 0 nhid 63 id 102 index 1 nhid 62"
+       log_test $? 0 "Nexthop buckets updated after replace"
+
+       $IP nexthop flush >/dev/null 2>&1
+
+       #
+       # migration of nexthop buckets - unequal weights
+       #
+       run_cmd "$IP nexthop add id 62 via 2001:db8:91::2 dev veth1"
+       run_cmd "$IP nexthop add id 63 via 2001:db8:91::3 dev veth1"
+       run_cmd "$IP nexthop add id 102 group 62,3/63,1 type resilient buckets 4 idle_timer 0"
+
+       run_cmd "$IP nexthop del id 63"
+       check_nexthop "id 102" \
+               "id 102 group 62,3 type resilient buckets 4 idle_timer 0 unbalanced_timer 0 unbalanced_time 0"
+       log_test $? 0 "Nexthop group updated when entry is deleted - nECMP"
+       check_nexthop_bucket "list id 102" \
+               "id 102 index 0 nhid 62 id 102 index 1 nhid 62 id 102 index 2 nhid 62 id 102 index 3 nhid 62"
+       log_test $? 0 "Nexthop buckets updated when entry is deleted - nECMP"
+
+       run_cmd "$IP nexthop add id 63 via 2001:db8:91::3 dev veth1"
+       run_cmd "$IP nexthop replace id 102 group 62,3/63,1 type resilient buckets 4 idle_timer 0"
+       check_nexthop "id 102" \
+               "id 102 group 62,3/63 type resilient buckets 4 idle_timer 0 unbalanced_timer 0 unbalanced_time 0"
+       log_test $? 0 "Nexthop group updated after replace - nECMP"
+       check_nexthop_bucket "list id 102" \
+               "id 102 index 0 nhid 63 id 102 index 1 nhid 62 id 102 index 2 nhid 62 id 102 index 3 nhid 62"
+       log_test $? 0 "Nexthop buckets updated after replace - nECMP"
+}
+
 ipv6_fcnal_runtime()
 {
        local rc
@@ -846,6 +958,22 @@ ipv6_large_grp()
        $IP nexthop flush >/dev/null 2>&1
 }
 
+ipv6_large_res_grp()
+{
+       echo
+       echo "IPv6 large resilient group (128k buckets)"
+       echo "-----------------------------------------"
+
+       check_nexthop_res_support
+       if [ $? -eq $ksft_skip ]; then
+               return $ksft_skip
+       fi
+
+       check_large_res_grp 6 $((128 * 1024))
+
+       $IP nexthop flush >/dev/null 2>&1
+}
+
 ipv6_del_add_loop1()
 {
        while :; do
@@ -902,6 +1030,61 @@ ipv6_torture()
        log_test 0 0 "IPv6 torture test"
 }
 
+ipv6_res_grp_replace_loop()
+{
+       while :; do
+               $IP nexthop replace id 102 group 100/101 type resilient
+       done >/dev/null 2>&1
+}
+
+ipv6_res_torture()
+{
+       local pid1
+       local pid2
+       local pid3
+       local pid4
+       local pid5
+
+       echo
+       echo "IPv6 runtime resilient nexthop group torture"
+       echo "--------------------------------------------"
+
+       check_nexthop_res_support
+       if [ $? -eq $ksft_skip ]; then
+               return $ksft_skip
+       fi
+
+       if [ ! -x "$(command -v mausezahn)" ]; then
+               echo "SKIP: Could not run test; need mausezahn tool"
+               return
+       fi
+
+       run_cmd "$IP nexthop add id 100 via 2001:db8:91::2 dev veth1"
+       run_cmd "$IP nexthop add id 101 via 2001:db8:92::2 dev veth3"
+       run_cmd "$IP nexthop add id 102 group 100/101 type resilient buckets 512 idle_timer 0"
+       run_cmd "$IP route add 2001:db8:101::1 nhid 102"
+       run_cmd "$IP route add 2001:db8:101::2 nhid 102"
+
+       ipv6_del_add_loop1 &
+       pid1=$!
+       ipv6_res_grp_replace_loop &
+       pid2=$!
+       ip netns exec me ping -f 2001:db8:101::1 >/dev/null 2>&1 &
+       pid3=$!
+       ip netns exec me ping -f 2001:db8:101::2 >/dev/null 2>&1 &
+       pid4=$!
+       ip netns exec me mausezahn -6 veth1 \
+                           -B 2001:db8:101::2 -A 2001:db8:91::1 -c 0 \
+                           -t tcp "dp=1-1023, flags=syn" >/dev/null 2>&1 &
+       pid5=$!
+
+       sleep 300
+       kill -9 $pid1 $pid2 $pid3 $pid4 $pid5
+       wait $pid1 $pid2 $pid3 $pid4 $pid5 2>/dev/null
+
+       # if we did not crash, success
+       log_test 0 0 "IPv6 resilient nexthop group torture test"
+}
 
 ipv4_fcnal()
 {
@@ -1061,6 +1244,70 @@ ipv4_grp_fcnal()
        log_test $? 2 "Nexthop group can not have a blackhole and another nexthop"
 }
 
+ipv4_res_grp_fcnal()
+{
+       local rc
+
+       echo
+       echo "IPv4 resilient groups functional"
+       echo "--------------------------------"
+
+       check_nexthop_res_support
+       if [ $? -eq $ksft_skip ]; then
+               return $ksft_skip
+       fi
+
+       #
+       # migration of nexthop buckets - equal weights
+       #
+       run_cmd "$IP nexthop add id 12 via 172.16.1.2 dev veth1"
+       run_cmd "$IP nexthop add id 13 via 172.16.1.3 dev veth1"
+       run_cmd "$IP nexthop add id 102 group 12/13 type resilient buckets 2 idle_timer 0"
+
+       run_cmd "$IP nexthop del id 13"
+       check_nexthop "id 102" \
+               "id 102 group 12 type resilient buckets 2 idle_timer 0 unbalanced_timer 0 unbalanced_time 0"
+       log_test $? 0 "Nexthop group updated when entry is deleted"
+       check_nexthop_bucket "list id 102" \
+               "id 102 index 0 nhid 12 id 102 index 1 nhid 12"
+       log_test $? 0 "Nexthop buckets updated when entry is deleted"
+
+       run_cmd "$IP nexthop add id 13 via 172.16.1.3 dev veth1"
+       run_cmd "$IP nexthop replace id 102 group 12/13 type resilient buckets 2 idle_timer 0"
+       check_nexthop "id 102" \
+               "id 102 group 12/13 type resilient buckets 2 idle_timer 0 unbalanced_timer 0 unbalanced_time 0"
+       log_test $? 0 "Nexthop group updated after replace"
+       check_nexthop_bucket "list id 102" \
+               "id 102 index 0 nhid 13 id 102 index 1 nhid 12"
+       log_test $? 0 "Nexthop buckets updated after replace"
+
+       $IP nexthop flush >/dev/null 2>&1
+
+       #
+       # migration of nexthop buckets - unequal weights
+       #
+       run_cmd "$IP nexthop add id 12 via 172.16.1.2 dev veth1"
+       run_cmd "$IP nexthop add id 13 via 172.16.1.3 dev veth1"
+       run_cmd "$IP nexthop add id 102 group 12,3/13,1 type resilient buckets 4 idle_timer 0"
+
+       run_cmd "$IP nexthop del id 13"
+       check_nexthop "id 102" \
+               "id 102 group 12,3 type resilient buckets 4 idle_timer 0 unbalanced_timer 0 unbalanced_time 0"
+       log_test $? 0 "Nexthop group updated when entry is deleted - nECMP"
+       check_nexthop_bucket "list id 102" \
+               "id 102 index 0 nhid 12 id 102 index 1 nhid 12 id 102 index 2 nhid 12 id 102 index 3 nhid 12"
+       log_test $? 0 "Nexthop buckets updated when entry is deleted - nECMP"
+
+       run_cmd "$IP nexthop add id 13 via 172.16.1.3 dev veth1"
+       run_cmd "$IP nexthop replace id 102 group 12,3/13,1 type resilient buckets 4 idle_timer 0"
+       check_nexthop "id 102" \
+               "id 102 group 12,3/13 type resilient buckets 4 idle_timer 0 unbalanced_timer 0 unbalanced_time 0"
+       log_test $? 0 "Nexthop group updated after replace - nECMP"
+       check_nexthop_bucket "list id 102" \
+               "id 102 index 0 nhid 13 id 102 index 1 nhid 12 id 102 index 2 nhid 12 id 102 index 3 nhid 12"
+       log_test $? 0 "Nexthop buckets updated after replace - nECMP"
+}
+
 ipv4_withv6_fcnal()
 {
        local lladdr
@@ -1282,6 +1529,22 @@ ipv4_large_grp()
        $IP nexthop flush >/dev/null 2>&1
 }
 
+ipv4_large_res_grp()
+{
+       echo
+       echo "IPv4 large resilient group (128k buckets)"
+       echo "-----------------------------------------"
+
+       check_nexthop_res_support
+       if [ $? -eq $ksft_skip ]; then
+               return $ksft_skip
+       fi
+
+       check_large_res_grp 4 $((128 * 1024))
+
+       $IP nexthop flush >/dev/null 2>&1
+}
+
 sysctl_nexthop_compat_mode_check()
 {
        local sysctlname="net.ipv4.nexthop_compat_mode"
@@ -1505,6 +1768,62 @@ ipv4_torture()
        log_test 0 0 "IPv4 torture test"
 }
 
+ipv4_res_grp_replace_loop()
+{
+       while :; do
+               $IP nexthop replace id 102 group 100/101 type resilient
+       done >/dev/null 2>&1
+}
+
+ipv4_res_torture()
+{
+       local pid1
+       local pid2
+       local pid3
+       local pid4
+       local pid5
+
+       echo
+       echo "IPv4 runtime resilient nexthop group torture"
+       echo "--------------------------------------------"
+
+       check_nexthop_res_support
+       if [ $? -eq $ksft_skip ]; then
+               return $ksft_skip
+       fi
+
+       if [ ! -x "$(command -v mausezahn)" ]; then
+               echo "SKIP: Could not run test; need mausezahn tool"
+               return
+       fi
+
+       run_cmd "$IP nexthop add id 100 via 172.16.1.2 dev veth1"
+       run_cmd "$IP nexthop add id 101 via 172.16.2.2 dev veth3"
+       run_cmd "$IP nexthop add id 102 group 100/101 type resilient buckets 512 idle_timer 0"
+       run_cmd "$IP route add 172.16.101.1 nhid 102"
+       run_cmd "$IP route add 172.16.101.2 nhid 102"
+
+       ipv4_del_add_loop1 &
+       pid1=$!
+       ipv4_res_grp_replace_loop &
+       pid2=$!
+       ip netns exec me ping -f 172.16.101.1 >/dev/null 2>&1 &
+       pid3=$!
+       ip netns exec me ping -f 172.16.101.2 >/dev/null 2>&1 &
+       pid4=$!
+       ip netns exec me mausezahn veth1 \
+                               -B 172.16.101.2 -A 172.16.1.1 -c 0 \
+                               -t tcp "dp=1-1023, flags=syn" >/dev/null 2>&1 &
+       pid5=$!
+
+       sleep 300
+       kill -9 $pid1 $pid2 $pid3 $pid4 $pid5
+       wait $pid1 $pid2 $pid3 $pid4 $pid5 2>/dev/null
+
+       # if we did not crash, success
+       log_test 0 0 "IPv4 resilient nexthop group torture test"
+}
+
 basic()
 {
        echo
@@ -1616,6 +1935,204 @@ basic()
        $IP nexthop flush >/dev/null 2>&1
 }
 
+check_nexthop_buckets_balance()
+{
+       local nharg=$1; shift
+       local ret
+
+       while (($# > 0)); do
+               local selector=$1; shift
+               local condition=$1; shift
+               local count
+
+               count=$($IP -j nexthop bucket ${nharg} ${selector} | jq length)
+               (( $count $condition ))
+               ret=$?
+               if ((ret != 0)); then
+                       return $ret
+               fi
+       done
+
+       return 0
+}
+
+basic_res()
+{
+       echo
+       echo "Basic resilient nexthop group functional tests"
+       echo "----------------------------------------------"
+
+       check_nexthop_res_support
+       if [ $? -eq $ksft_skip ]; then
+               return $ksft_skip
+       fi
+
+       run_cmd "$IP nexthop add id 1 dev veth1"
+
+       #
+       # resilient nexthop group addition
+       #
+
+       run_cmd "$IP nexthop add id 101 group 1 type resilient buckets 8"
+       log_test $? 0 "Add a nexthop group with default parameters"
+
+       run_cmd "$IP nexthop get id 101"
+       check_nexthop "id 101" \
+               "id 101 group 1 type resilient buckets 8 idle_timer 120 unbalanced_timer 0 unbalanced_time 0"
+       log_test $? 0 "Get a nexthop group with default parameters"
+
+       run_cmd "$IP nexthop add id 102 group 1 type resilient
+                       buckets 4 idle_timer 100 unbalanced_timer 5"
+       run_cmd "$IP nexthop get id 102"
+       check_nexthop "id 102" \
+               "id 102 group 1 type resilient buckets 4 idle_timer 100 unbalanced_timer 5 unbalanced_time 0"
+       log_test $? 0 "Get a nexthop group with non-default parameters"
+
+       run_cmd "$IP nexthop add id 103 group 1 type resilient buckets 0"
+       log_test $? 2 "Add a nexthop group with 0 buckets"
+
+       #
+       # resilient nexthop group replacement
+       #
+
+       run_cmd "$IP nexthop replace id 101 group 1 type resilient
+                       buckets 8 idle_timer 240 unbalanced_timer 80"
+       log_test $? 0 "Replace nexthop group parameters"
+       check_nexthop "id 101" \
+               "id 101 group 1 type resilient buckets 8 idle_timer 240 unbalanced_timer 80 unbalanced_time 0"
+       log_test $? 0 "Get a nexthop group after replacing parameters"
+
+       run_cmd "$IP nexthop replace id 101 group 1 type resilient idle_timer 512"
+       log_test $? 0 "Replace idle timer"
+       check_nexthop "id 101" \
+               "id 101 group 1 type resilient buckets 8 idle_timer 512 unbalanced_timer 80 unbalanced_time 0"
+       log_test $? 0 "Get a nexthop group after replacing idle timer"
+
+       run_cmd "$IP nexthop replace id 101 group 1 type resilient unbalanced_timer 256"
+       log_test $? 0 "Replace unbalanced timer"
+       check_nexthop "id 101" \
+               "id 101 group 1 type resilient buckets 8 idle_timer 512 unbalanced_timer 256 unbalanced_time 0"
+       log_test $? 0 "Get a nexthop group after replacing unbalanced timer"
+
+       run_cmd "$IP nexthop replace id 101 group 1 type resilient"
+       log_test $? 0 "Replace with no parameters"
+       check_nexthop "id 101" \
+               "id 101 group 1 type resilient buckets 8 idle_timer 512 unbalanced_timer 256 unbalanced_time 0"
+       log_test $? 0 "Get a nexthop group after replacing no parameters"
+
+       run_cmd "$IP nexthop replace id 101 group 1"
+       log_test $? 2 "Replace nexthop group type - implicit"
+
+       run_cmd "$IP nexthop replace id 101 group 1 type mpath"
+       log_test $? 2 "Replace nexthop group type - explicit"
+
+       run_cmd "$IP nexthop replace id 101 group 1 type resilient buckets 1024"
+       log_test $? 2 "Replace number of nexthop buckets"
+
+       check_nexthop "id 101" \
+               "id 101 group 1 type resilient buckets 8 idle_timer 512 unbalanced_timer 256 unbalanced_time 0"
+       log_test $? 0 "Get a nexthop group after replacing with invalid parameters"
+
+       #
+       # resilient nexthop buckets dump
+       #
+
+       $IP nexthop flush >/dev/null 2>&1
+       run_cmd "$IP nexthop add id 1 dev veth1"
+       run_cmd "$IP nexthop add id 2 dev veth3"
+       run_cmd "$IP nexthop add id 101 group 1/2 type resilient buckets 4"
+       run_cmd "$IP nexthop add id 201 group 1/2"
+
+       check_nexthop_bucket "" \
+               "id 101 index 0 nhid 2 id 101 index 1 nhid 2 id 101 index 2 nhid 1 id 101 index 3 nhid 1"
+       log_test $? 0 "Dump all nexthop buckets"
+
+       check_nexthop_bucket "list id 101" \
+               "id 101 index 0 nhid 2 id 101 index 1 nhid 2 id 101 index 2 nhid 1 id 101 index 3 nhid 1"
+       log_test $? 0 "Dump all nexthop buckets in a group"
+
+       (( $($IP -j nexthop bucket list id 101 |
+            jq '[.[] | select(.bucket.idle_time > 0 and
+                              .bucket.idle_time < 2)] | length') == 4 ))
+       log_test $? 0 "All nexthop buckets report a positive near-zero idle time"
+
+       check_nexthop_bucket "list dev veth1" \
+               "id 101 index 2 nhid 1 id 101 index 3 nhid 1"
+       log_test $? 0 "Dump all nexthop buckets with a specific nexthop device"
+
+       check_nexthop_bucket "list nhid 2" \
+               "id 101 index 0 nhid 2 id 101 index 1 nhid 2"
+       log_test $? 0 "Dump all nexthop buckets with a specific nexthop identifier"
+
+       run_cmd "$IP nexthop bucket list id 111"
+       log_test $? 2 "Dump all nexthop buckets in a non-existent group"
+
+       run_cmd "$IP nexthop bucket list id 201"
+       log_test $? 2 "Dump all nexthop buckets in a non-resilient group"
+
+       run_cmd "$IP nexthop bucket list dev bla"
+       log_test $? 255 "Dump all nexthop buckets using a non-existent device"
+
+       run_cmd "$IP nexthop bucket list groups"
+       log_test $? 255 "Dump all nexthop buckets with invalid 'groups' keyword"
+
+       run_cmd "$IP nexthop bucket list fdb"
+       log_test $? 255 "Dump all nexthop buckets with invalid 'fdb' keyword"
+
+       #
+       # resilient nexthop buckets get requests
+       #
+
+       check_nexthop_bucket "get id 101 index 0" "id 101 index 0 nhid 2"
+       log_test $? 0 "Get a valid nexthop bucket"
+
+       run_cmd "$IP nexthop bucket get id 101 index 999"
+       log_test $? 2 "Get a nexthop bucket with valid group, but invalid index"
+
+       run_cmd "$IP nexthop bucket get id 201 index 0"
+       log_test $? 2 "Get a nexthop bucket from a non-resilient group"
+
+       run_cmd "$IP nexthop bucket get id 999 index 0"
+       log_test $? 2 "Get a nexthop bucket from a non-existent group"
+
+       #
+       # tests for bucket migration
+       #
+
+       $IP nexthop flush >/dev/null 2>&1
+
+       run_cmd "$IP nexthop add id 1 dev veth1"
+       run_cmd "$IP nexthop add id 2 dev veth3"
+       run_cmd "$IP nexthop add id 101
+                       group 1/2 type resilient buckets 10
+                       idle_timer 1 unbalanced_timer 20"
+
+       check_nexthop_buckets_balance "list id 101" \
+                                     "nhid 1" "== 5" \
+                                     "nhid 2" "== 5"
+       log_test $? 0 "Initial bucket allocation"
+
+       run_cmd "$IP nexthop replace id 101
+                       group 1,2/2,3 type resilient"
+       check_nexthop_buckets_balance "list id 101" \
+                                     "nhid 1" "== 4" \
+                                     "nhid 2" "== 6"
+       log_test $? 0 "Bucket allocation after replace"
+
+       # Check that increase in idle timer does not make buckets appear busy.
+       run_cmd "$IP nexthop replace id 101
+                       group 1,2/2,3 type resilient
+                       idle_timer 10"
+       run_cmd "$IP nexthop replace id 101
+                       group 1/2 type resilient"
+       check_nexthop_buckets_balance "list id 101" \
+                                     "nhid 1" "== 5" \
+                                     "nhid 2" "== 5"
+       log_test $? 0 "Buckets migrated after idle timer change"
+
+       $IP nexthop flush >/dev/null 2>&1
+}
+
 ################################################################################
 # usage