From e373161b52608c27cfe831eec516f3b1197fae19 Mon Sep 17 00:00:00 2001 From: harini Date: Wed, 25 Feb 2026 07:44:07 -0800 Subject: [PATCH 1/3] zebra: show nexthop-group rib brief json Commands supported: 1)show nexthop-group rib brief json 2)show nexthop-group rib 117 brief json 3)show nexthop-group rib singleton ip brief json 4)show nexthop-group rib singleton ipv6 brief json 5)show nexthop-group rib zebra brief json Signed-off-by: harini --- lib/nexthop.c | 64 ++++++++------- lib/nexthop.h | 5 +- zebra/zebra_rnh.c | 11 ++- zebra/zebra_rnh.h | 7 +- zebra/zebra_vty.c | 197 ++++++++++++++++++++++++++-------------------- 5 files changed, 156 insertions(+), 128 deletions(-) diff --git a/lib/nexthop.c b/lib/nexthop.c index 78da420f26a8..b5eebecc9891 100644 --- a/lib/nexthop.c +++ b/lib/nexthop.c @@ -1203,9 +1203,8 @@ bool nexthop_is_blackhole(const struct nexthop *nh) * Render a nexthop into a json object; the caller allocates and owns * the json object memory. */ -void nexthop_json_helper(json_object *json_nexthop, - const struct nexthop *nexthop, bool display_vrfid, - uint8_t rn_family) +void nexthop_json_helper(json_object *json_nexthop, const struct nexthop *nexthop, + bool display_vrfid, uint8_t rn_family, bool brief) { json_object *json_labels = NULL; json_object *json_backups = NULL; @@ -1216,20 +1215,23 @@ void nexthop_json_helper(json_object *json_nexthop, json_object *json_segs = NULL; int i; - json_object_int_add(json_nexthop, "flags", nexthop->flags); + if (!brief) { + json_object_int_add(json_nexthop, "flags", nexthop->flags); - if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_DUPLICATE)) - json_object_boolean_true_add(json_nexthop, "duplicate"); + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_DUPLICATE)) + json_object_boolean_true_add(json_nexthop, "duplicate"); - if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)) - json_object_boolean_true_add(json_nexthop, "fib"); + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)) + json_object_boolean_true_add(json_nexthop, "fib"); + } switch (nexthop->type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: json_object_string_addf(json_nexthop, "ip", "%pI4", &nexthop->gate.ipv4); - json_object_string_add(json_nexthop, "afi", "ipv4"); + if (!brief) + json_object_string_add(json_nexthop, "afi", "ipv4"); if (nexthop->ifindex) { json_object_int_add(json_nexthop, "interfaceIndex", @@ -1243,7 +1245,8 @@ void nexthop_json_helper(json_object *json_nexthop, case NEXTHOP_TYPE_IPV6_IFINDEX: json_object_string_addf(json_nexthop, "ip", "%pI6", &nexthop->gate.ipv6); - json_object_string_add(json_nexthop, "afi", "ipv6"); + if (!brief) + json_object_string_add(json_nexthop, "afi", "ipv6"); if (nexthop->ifindex) { json_object_int_add(json_nexthop, "interfaceIndex", @@ -1255,7 +1258,8 @@ void nexthop_json_helper(json_object *json_nexthop, break; case NEXTHOP_TYPE_IFINDEX: - json_object_boolean_true_add(json_nexthop, "directlyConnected"); + if (!brief) + json_object_boolean_true_add(json_nexthop, "directlyConnected"); json_object_int_add(json_nexthop, "interfaceIndex", nexthop->ifindex); json_object_string_add(json_nexthop, "interfaceName", @@ -1263,24 +1267,31 @@ void nexthop_json_helper(json_object *json_nexthop, nexthop->vrf_id)); break; case NEXTHOP_TYPE_BLACKHOLE: - json_object_boolean_true_add(json_nexthop, "unreachable"); - switch (nexthop->bh_type) { - case BLACKHOLE_REJECT: - json_object_boolean_true_add(json_nexthop, "reject"); - break; - case BLACKHOLE_ADMINPROHIB: - json_object_boolean_true_add(json_nexthop, - "adminProhibited"); - break; - case BLACKHOLE_NULL: - json_object_boolean_true_add(json_nexthop, "blackhole"); - break; - case BLACKHOLE_UNSPEC: - break; + if (!brief) { + json_object_boolean_true_add(json_nexthop, "unreachable"); + switch (nexthop->bh_type) { + case BLACKHOLE_REJECT: + json_object_boolean_true_add(json_nexthop, "reject"); + break; + case BLACKHOLE_ADMINPROHIB: + json_object_boolean_true_add(json_nexthop, "adminProhibited"); + break; + case BLACKHOLE_NULL: + json_object_boolean_true_add(json_nexthop, "blackhole"); + break; + case BLACKHOLE_UNSPEC: + break; + } } break; } + if (display_vrfid) + json_object_string_add(json_nexthop, "vrf", vrf_id_to_name(nexthop->vrf_id)); + + if (brief) + return; + /* This nexthop is a resolver for the parent nexthop. * Set resolver flag for better clarity and delimiter * in flat list of nexthops in json. @@ -1288,9 +1299,6 @@ void nexthop_json_helper(json_object *json_nexthop, if (nexthop->rparent) json_object_boolean_true_add(json_nexthop, "resolver"); - if (display_vrfid) - json_object_string_add(json_nexthop, "vrf", - vrf_id_to_name(nexthop->vrf_id)); if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_DUPLICATE)) json_object_boolean_true_add(json_nexthop, "duplicate"); diff --git a/lib/nexthop.h b/lib/nexthop.h index 82d045217b87..c154714dfdc7 100644 --- a/lib/nexthop.h +++ b/lib/nexthop.h @@ -292,9 +292,8 @@ extern bool nexthop_is_blackhole(const struct nexthop *nh); int nexthop_str2backups(const char *str, int *num_backups, uint8_t *backups); -void nexthop_json_helper(struct json_object *json_nexthop, - const struct nexthop *nexthop, bool display_vrfid, - uint8_t rn_family); +void nexthop_json_helper(struct json_object *json_nexthop, const struct nexthop *nexthop, + bool display_vrfid, uint8_t rn_family, bool brief); void nexthop_vty_helper(struct vty *vty, const struct nexthop *nexthop, bool display_vrfid, uint8_t rn_family); diff --git a/zebra/zebra_rnh.c b/zebra/zebra_rnh.c index 281f19555f06..10ad37c1dfcf 100644 --- a/zebra/zebra_rnh.c +++ b/zebra/zebra_rnh.c @@ -1254,10 +1254,8 @@ int zebra_send_rnh_update(struct rnh *rnh, struct zserv *client, * Render a nexthop into a json object; the caller allocates and owns * the json object memory. */ -void show_nexthop_json_helper(json_object *json_nexthop, - const struct nexthop *nexthop, - const struct route_node *rn, - const struct route_entry *re) +void show_nexthop_json_helper(json_object *json_nexthop, const struct nexthop *nexthop, + const struct route_node *rn, const struct route_entry *re, bool brief) { bool display_vrfid = false; uint8_t rn_family; @@ -1270,7 +1268,7 @@ void show_nexthop_json_helper(json_object *json_nexthop, else rn_family = AF_UNSPEC; - nexthop_json_helper(json_nexthop, nexthop, display_vrfid, rn_family); + nexthop_json_helper(json_nexthop, nexthop, display_vrfid, rn_family, brief); } /* @@ -1381,7 +1379,8 @@ static void print_rnh(struct route_node *rn, struct vty *vty, json_object *json) if (json) { json_nexthop = json_object_new_object(); json_object_array_add(json_nexthop_array, json_nexthop); - show_nexthop_json_helper(json_nexthop, nexthop, rn, NULL); + show_nexthop_json_helper(json_nexthop, nexthop, rn, NULL, + false); } else { show_route_nexthop_helper(vty, rn, NULL, nexthop); vty_out(vty, "\n"); diff --git a/zebra/zebra_rnh.h b/zebra/zebra_rnh.h index 13c9e9068a77..cfbf25bae1e8 100644 --- a/zebra/zebra_rnh.h +++ b/zebra/zebra_rnh.h @@ -51,10 +51,9 @@ extern int rnh_resolve_via_default(struct zebra_vrf *zvrf, int family); extern bool rnh_nexthop_valid(const struct route_entry *re, const struct nexthop *nh); -void show_nexthop_json_helper(struct json_object *json_nexthop, - const struct nexthop *nexthop, - const struct route_node *rn, - const struct route_entry *re); +void show_nexthop_json_helper(struct json_object *json_nexthop, const struct nexthop *nexthop, + const struct route_node *rn, const struct route_entry *re, + bool brief); void show_route_nexthop_helper(struct vty *vty, const struct route_node *rn, const struct route_entry *re, const struct nexthop *nexthop); diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c index ffd2c831fd91..edb3827fe614 100644 --- a/zebra/zebra_vty.c +++ b/zebra/zebra_vty.c @@ -643,7 +643,8 @@ static void vty_show_ip_route(struct vty *vty, struct route_node *rn, struct rou json_nexthops = json_object_new_array(); for (ALL_NEXTHOPS_PTR(nhg, nexthop)) { json_nexthop = json_object_new_object(); - show_nexthop_json_helper(json_nexthop, nexthop, rn, re); + show_nexthop_json_helper(json_nexthop, nexthop, rn, re, + false); json_object_array_add(json_nexthops, json_nexthop); } @@ -660,7 +661,7 @@ static void vty_show_ip_route(struct vty *vty, struct route_node *rn, struct rou json_nexthop = json_object_new_object(); show_nexthop_json_helper(json_nexthop, nexthop, rn, - re); + re, false); json_object_array_add(json_nexthops, json_nexthop); } @@ -1140,7 +1141,7 @@ DEFPY (show_ip_nht, } static void show_nexthop_group_out(struct vty *vty, struct nhg_hash_entry *nhe, - json_object *json_nhe_hdr) + json_object *json_nhe_hdr, bool brief) { struct nexthop *nexthop = NULL; struct nhg_connected *rb_node_dep = NULL; @@ -1165,15 +1166,15 @@ static void show_nexthop_group_out(struct vty *vty, struct nhg_hash_entry *nhe, nexthop_count = nexthop_group_nexthop_num_no_recurse(&nhe->nhg); if (json) { - json_object_string_add(json, "type", - zebra_route_string(nhe->type)); - json_object_int_add(json, "refCount", nhe->refcnt); - if (event_is_scheduled(nhe->timer)) - json_object_string_add( - json, "timeToDeletion", - event_timer_to_hhmmss(time_left, - sizeof(time_left), - nhe->timer)); + if (!brief) { + json_object_string_add(json, "type", zebra_route_string(nhe->type)); + json_object_int_add(json, "refCount", nhe->refcnt); + if (event_is_scheduled(nhe->timer)) + json_object_string_add(json, "timeToDeletion", + event_timer_to_hhmmss(time_left, + sizeof(time_left), + nhe->timer)); + } json_object_string_add(json, "uptime", up_str); json_object_string_add(json, "vrf", vrf_id_to_name(nhe->vrf_id)); @@ -1261,66 +1262,78 @@ static void show_nexthop_group_out(struct vty *vty, struct nhg_hash_entry *nhe, /* Output nexthops */ if (json) json_nexthop_array = json_object_new_array(); + /* Skip outputting nexthops in brief mode for groups with dependencies */ + if (!(json && brief && !zebra_nhg_depends_is_empty(nhe))) { + for (ALL_NEXTHOPS(nhe->nhg, nexthop)) { + if (json_nexthop_array) { + json_nexthops = json_object_new_object(); + if (brief) { + if (zebra_nhg_depends_is_empty(nhe)) + show_nexthop_json_helper(json_nexthops, nexthop, + NULL, NULL, brief); + } else { + show_nexthop_json_helper(json_nexthops, nexthop, NULL, + NULL, false); + } + } else { + outdent_p = (nexthop->rparent == NULL); + if (outdent_p) + vty_out(vty, " "); + else + /* Make recursive nexthops a bit more clear */ + vty_out(vty, " "); + show_route_nexthop_helper(vty, NULL, NULL, nexthop); + } - for (ALL_NEXTHOPS(nhe->nhg, nexthop)) { - if (json_nexthop_array) { - json_nexthops = json_object_new_object(); - show_nexthop_json_helper(json_nexthops, nexthop, NULL, - NULL); - } else { - outdent_p = (nexthop->rparent == NULL); - if (outdent_p) - vty_out(vty, " "); - else - /* Make recursive nexthops a bit more clear */ - vty_out(vty, " "); - - show_route_nexthop_helper(vty, NULL, NULL, nexthop); - } + if (nhe->backup_info == NULL || nhe->backup_info->nhe == NULL) { + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_HAS_BACKUP)) { + if (json) + json_object_int_add(json_nexthops, "backup", + nexthop->backup_idx[0]); + else + vty_out(vty, " [backup %d]", + nexthop->backup_idx[0]); + } - if (nhe->backup_info == NULL || nhe->backup_info->nhe == NULL) { - if (CHECK_FLAG(nexthop->flags, - NEXTHOP_FLAG_HAS_BACKUP)) { - if (json) - json_object_int_add( - json_nexthops, "backup", - nexthop->backup_idx[0]); + if (!json) + vty_out(vty, "\n"); else - vty_out(vty, " [backup %d]", - nexthop->backup_idx[0]); + json_object_array_add(json_nexthop_array, json_nexthops); + + continue; } - if (!json) + if (!json) { + /* TODO -- print more useful backup info */ + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_HAS_BACKUP)) { + int i; + + vty_out(vty, "[backup"); + for (i = 0; i < nexthop->backup_num; i++) + vty_out(vty, " %d", nexthop->backup_idx[i]); + vty_out(vty, "]"); + } vty_out(vty, "\n"); - else + } else { json_object_array_add(json_nexthop_array, json_nexthops); - - continue; - } - - if (!json) { - /* TODO -- print more useful backup info */ - if (CHECK_FLAG(nexthop->flags, - NEXTHOP_FLAG_HAS_BACKUP)) { - int i; - - vty_out(vty, "[backup"); - for (i = 0; i < nexthop->backup_num; i++) - vty_out(vty, " %d", - nexthop->backup_idx[i]); - vty_out(vty, "]"); } - vty_out(vty, "\n"); - } else { - json_object_array_add(json_nexthop_array, - json_nexthops); } } - if (json) + if (json) { + if (brief) { + if (zebra_nhg_depends_is_empty(nhe)) + json_object_object_add(json, "nexthops", json_nexthop_array); + else + json_object_put(json_nexthop_array); + if (json_nhe_hdr) + json_object_object_addf(json_nhe_hdr, json, "%u", nhe->id); + return; + } json_object_object_add(json, "nexthops", json_nexthop_array); + } /* Output backup nexthops (if any) */ backup_nhg = zebra_nhg_get_backup_nhg(nhe); @@ -1333,8 +1346,8 @@ static void show_nexthop_group_out(struct vty *vty, struct nhg_hash_entry *nhe, for (ALL_NEXTHOPS_PTR(backup_nhg, nexthop)) { if (json_backup_nexthop_array) { json_backup_nexthops = json_object_new_object(); - show_nexthop_json_helper(json_backup_nexthops, - nexthop, NULL, NULL); + show_nexthop_json_helper(json_backup_nexthops, nexthop, NULL, NULL, + false); json_object_array_add(json_backup_nexthop_array, json_backup_nexthops); } else { @@ -1401,15 +1414,15 @@ static void show_nexthop_group_out(struct vty *vty, struct nhg_hash_entry *nhe, json_object_object_addf(json_nhe_hdr, json, "%u", nhe->id); } -static int show_nexthop_group_id_cmd_helper(struct vty *vty, uint32_t id, - json_object *json) +static int show_nexthop_group_id_cmd_helper(struct vty *vty, uint32_t id, json_object *json, + bool brief) { struct nhg_hash_entry *nhe = NULL; nhe = zebra_nhg_lookup_id(id); if (nhe) - show_nexthop_group_out(vty, nhe, json); + show_nexthop_group_out(vty, nhe, json, brief); else { if (json) vty_json(vty, json); @@ -1434,6 +1447,7 @@ struct nhe_show_context { int type; int counter; json_object *json; + bool brief; json_object *json_top; }; @@ -1453,11 +1467,12 @@ static int nhe_show_walker(struct hash_bucket *bucket, void *arg) if (ctx->type && nhe->type != ctx->type) goto done; - show_nexthop_group_out(ctx->vty, nhe, ctx->json); + show_nexthop_group_out(ctx->vty, nhe, ctx->json, ctx->brief); if (ctx->json) { ctx->counter++; - if (ctx->counter > 5) { + /* Only batch-output when not brief; when brief we output once at DEFPY level */ + if (!ctx->brief && ctx->counter > 5) { /* Output and reset counter */ frr_json_vty_out(ctx->vty, ctx->json_top); ctx->counter = 0; @@ -1468,9 +1483,8 @@ static int nhe_show_walker(struct hash_bucket *bucket, void *arg) return HASHWALK_CONTINUE; } -static void show_nexthop_group_cmd_helper(struct vty *vty, - struct zebra_vrf *zvrf, afi_t afi, - int type, json_object *json) +static void show_nexthop_group_cmd_helper(struct vty *vty, struct zebra_vrf *zvrf, afi_t afi, + int type, json_object *json, bool brief) { struct nhe_show_context ctx; struct json_object *jvrf = NULL; @@ -1478,7 +1492,8 @@ static void show_nexthop_group_cmd_helper(struct vty *vty, if (json) { jvrf = json_object_new_object(); - frr_json_set_open(jvrf); + if (!brief) + frr_json_set_open(jvrf); json_object_object_add(json, zvrf->vrf->name, jvrf); } @@ -1487,6 +1502,7 @@ static void show_nexthop_group_cmd_helper(struct vty *vty, ctx.afi = afi; ctx.vrf_id = zvrf->vrf->vrf_id; ctx.type = type; + ctx.brief = brief; ctx.json = jvrf; ctx.json_top = json; ctx.counter = 0; @@ -1495,8 +1511,9 @@ static void show_nexthop_group_cmd_helper(struct vty *vty, /* Finish with the json vrf object */ if (json) { - frr_json_set_complete(jvrf); - frr_json_vty_out(vty, json); + if (!brief) + frr_json_set_complete(jvrf); + /* Output only at DEFPY level; do not free json here */ } } @@ -1515,7 +1532,7 @@ static void if_nexthop_group_dump_vty(struct vty *vty, struct interface *ifp) } vty_out(vty, " "); - show_nexthop_group_out(vty, rb_node_dep->nhe, NULL); + show_nexthop_group_out(vty, rb_node_dep->nhe, NULL, false); } } @@ -1553,9 +1570,8 @@ DEFPY (show_interface_nexthop_group, return CMD_SUCCESS; } -DEFPY(show_nexthop_group, - show_nexthop_group_cmd, - "show nexthop-group rib <(0-4294967295)$id|[singleton ] [$type_str] [vrf ]> [json]", +DEFPY(show_nexthop_group, show_nexthop_group_cmd, + "show nexthop-group rib [<(0-4294967295)$id|[singleton ] [$type_str] [vrf ]>] [json$uj [brief$brief]]", SHOW_STR "Show Nexthop Groups\n" "RIB information\n" @@ -1568,20 +1584,20 @@ DEFPY(show_nexthop_group, "Border Gateway Protocol (BGP)\n" "Super Happy Advanced Routing Protocol (SHARP)\n" VRF_FULL_CMD_HELP_STR - JSON_STR) + JSON_STR + "Brief\n") { struct zebra_vrf *zvrf = NULL; afi_t afi = AFI_UNSPEC; uint8_t type = 0; - bool uj = use_json(argc, argv); json_object *json = NULL; if (uj) json = json_object_new_object(); if (id) - return show_nexthop_group_id_cmd_helper(vty, id, json); + return show_nexthop_group_id_cmd_helper(vty, id, json, brief); if (v4) afi = AFI_IP; @@ -1608,7 +1624,7 @@ DEFPY(show_nexthop_group, if (vrf_all) { struct vrf *vrf; - if (json) + if (json && !brief) frr_json_set_open(json); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { @@ -1618,13 +1634,16 @@ DEFPY(show_nexthop_group, if (!uj) vty_out(vty, "VRF: %s\n", vrf->name); - show_nexthop_group_cmd_helper(vty, zvrf, afi, type, - json); + show_nexthop_group_cmd_helper(vty, zvrf, afi, type, json, brief); } if (uj) { - frr_json_set_complete(json); - frr_json_vty_out(vty, json); + if (brief) + vty_json_no_pretty(vty, json); + else { + frr_json_set_complete(json); + frr_json_vty_out(vty, json); + } } return CMD_SUCCESS; @@ -1644,14 +1663,18 @@ DEFPY(show_nexthop_group, return CMD_WARNING; } - if (json) + if (json && !brief) frr_json_set_open(json); - show_nexthop_group_cmd_helper(vty, zvrf, afi, type, json); + show_nexthop_group_cmd_helper(vty, zvrf, afi, type, json, brief); if (uj) { - frr_json_set_complete(json); - frr_json_vty_out(vty, json); + if (brief) + vty_json_no_pretty(vty, json); + else { + frr_json_set_complete(json); + frr_json_vty_out(vty, json); + } } return CMD_SUCCESS; } From 7ed8a356aa4df65f9f05df0c8d0acd453086e821 Mon Sep 17 00:00:00 2001 From: harini Date: Fri, 27 Feb 2026 03:05:33 -0800 Subject: [PATCH 2/3] tests: Enhanced to capture show nexthop-group rib brief command Signed-off-by: harini --- .../test_all_protocol_startup.py | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/tests/topotests/all_protocol_startup/test_all_protocol_startup.py b/tests/topotests/all_protocol_startup/test_all_protocol_startup.py index 1e7a3785358a..9bf223283712 100644 --- a/tests/topotests/all_protocol_startup/test_all_protocol_startup.py +++ b/tests/topotests/all_protocol_startup/test_all_protocol_startup.py @@ -1649,6 +1649,153 @@ def test_nexthop_group_replace(): verify_route_nexthop_group("3.3.3.1/32", False, 3) +def test_show_nexthop_group_rib_brief_json(): + """Check 'show nexthop-group rib ... json brief' return valid brief JSON. + + Commands covered: + 1) show nexthop-group rib json brief + 2) show nexthop-group rib json brief + 3) show nexthop-group rib singleton ip json brief + 4) show nexthop-group rib singleton ipv6 json brief + 5) show nexthop-group rib zebra json brief + """ + global fatal_error + + if fatal_error != "": + pytest.skip(fatal_error) + + print("\n\n** Verifying show nexthop-group rib json brief commands") + print("******************************************\n") + + tgen = get_topogen() + r1 = tgen.routers()["r1"] + full_only_fields = {"type", "refCount", "timeToDeletion"} + + def _extract_json(s): + """Extract first complete JSON object from string (handles trailing).""" + s = s.strip() + if not s: + return None, "empty output" + start = s.find("{") + if start == -1: + return None, "no JSON object in output" + depth = 0 + for i in range(start, len(s)): + if s[i] == "{": + depth += 1 + elif s[i] == "}": + depth -= 1 + if depth == 0: + try: + return json.loads(s[start : i + 1]), None + except json.JSONDecodeError as e: + return None, str(e) + return None, "unclosed JSON object" + + def _check_brief_nhg_entry(obj): + """Brief entries have uptime, vrf; no type/refCount/timeToDeletion.""" + if not isinstance(obj, dict): + return "NHG entry is not a dict" + if "uptime" not in obj: + return "Brief entry missing 'uptime'" + if "vrf" not in obj: + return "Brief entry missing 'vrf'" + for key in full_only_fields: + if key in obj: + return f"Brief entry should not have '{key}'" + return None + + # 1) show nexthop-group rib json brief + out = r1.vtysh_cmd("show nexthop-group rib json brief").strip() + err = None + if not out: + data = {} + else: + data, parse_err = _extract_json(out) + if parse_err: + err = f"show nexthop-group rib json brief: invalid JSON: {parse_err}" + elif data is not None and not isinstance(data, dict): + err = "show nexthop-group rib json brief: top-level is not a dict" + elif data is None: + data = {} + if err is None: + for vrfname, vrfdata in data.items(): + if not isinstance(vrfdata, dict): + continue + for nhg_id_str, nhg_obj in vrfdata.items(): + chk = _check_brief_nhg_entry(nhg_obj) + if chk: + err = f"show nexthop-group rib json brief (vrf={vrfname}, id={nhg_id_str}): {chk}" + break + if err: + break + assert err is None, err + print("r1 show nexthop-group rib json brief ok") + + # 2) show nexthop-group rib json brief (use first available id) + nhg_id_used = None + for vrfname, vrfdata in data.items(): + if isinstance(vrfdata, dict) and vrfdata: + nhg_id_used = next(iter(vrfdata.keys())) + break + if nhg_id_used is not None: + out = r1.vtysh_cmd( + f"show nexthop-group rib {nhg_id_used} json brief" + ).strip() + data_id, parse_err = _extract_json(out) + if parse_err: + assert ( + False + ), f"show nexthop-group rib {nhg_id_used} json brief: invalid JSON: {parse_err}" + if data_id is None: + data_id = {} + # Output is {"": { brief NHG object }} + if not isinstance(data_id, dict) or len(data_id) != 1: + assert ( + False + ), f"show nexthop-group rib {nhg_id_used} json brief: expected single key" + nhg_obj = next(iter(data_id.values())) + err = _check_brief_nhg_entry(nhg_obj) + assert err is None, f"show nexthop-group rib {nhg_id_used} json brief: {err}" + print(f"r1 show nexthop-group rib {nhg_id_used} json brief ok") + + # 3) show nexthop-group rib singleton ip json brief + out = r1.vtysh_cmd("show nexthop-group rib singleton ip json brief").strip() + data3, parse_err = _extract_json(out) + if parse_err: + assert ( + False + ), f"show nexthop-group rib singleton ip json brief: invalid JSON: {parse_err}" + assert isinstance( + data3, dict + ), "show nexthop-group rib singleton ip json brief: top-level is not a dict" + print("r1 show nexthop-group rib singleton ip json brief ok") + + # 4) show nexthop-group rib singleton ipv6 json brief + out = r1.vtysh_cmd("show nexthop-group rib singleton ipv6 json brief").strip() + data4, parse_err = _extract_json(out) + if parse_err: + assert ( + False + ), f"show nexthop-group rib singleton ipv6 json brief: invalid JSON: {parse_err}" + assert isinstance( + data4, dict + ), "show nexthop-group rib singleton ipv6 json brief: top-level is not a dict" + print("r1 show nexthop-group rib singleton ipv6 json brief ok") + + # 5) show nexthop-group rib zebra json brief + out = r1.vtysh_cmd("show nexthop-group rib zebra json brief").strip() + data5, parse_err = _extract_json(out) + if parse_err: + assert ( + False + ), f"show nexthop-group rib zebra json brief: invalid JSON: {parse_err}" + assert isinstance( + data5, dict + ), "show nexthop-group rib zebra json brief: top-level is not a dict" + print("r1 show nexthop-group rib zebra json brief ok") + + def test_mpls_interfaces(): global fatal_error net = get_topogen().net From a62a3a3be4f26d19c977fdbc629f3e8e7d171dac Mon Sep 17 00:00:00 2001 From: harini Date: Fri, 27 Feb 2026 03:05:38 -0800 Subject: [PATCH 3/3] doc: document show nexthop-group rib brief and brief json Signed-off-by: harini --- doc/user/zebra.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/user/zebra.rst b/doc/user/zebra.rst index 65350e796860..75046a92dc83 100644 --- a/doc/user/zebra.rst +++ b/doc/user/zebra.rst @@ -1820,7 +1820,7 @@ zebra Terminal Mode Commands total number of route nodes in the table. Which will be higher than the actual number of routes that are held. -.. clicmd:: show nexthop-group rib [ID] [vrf NAME] [singleton [ip|ip6]] [type] [json] +.. clicmd:: show nexthop-group rib [ID] [vrf NAME] [singleton [ip|ip6]] [type] [json [brief]] Display nexthop groups created by zebra. The [vrf NAME] option is only meaningful if you have started zebra with the --vrfwnetns @@ -1833,6 +1833,12 @@ zebra Terminal Mode Commands Zebra can delay installing this route until it is used by something else. + With **json**, the output is in JSON format. **brief** may only be + given together with **json** (after it); it omits the full-detail + fields (type, refCount, timeToDeletion) and shows a condensed nexthop + list; groups with dependencies list only ``depends`` and not + per-nexthop detail. + .. clicmd:: show zebra route dump [ VRFNAME] It dumps all the routes from RIB with detailed information including