diff options
| -rw-r--r-- | bgpd/bgp_evpn_vty.c | 11 | ||||
| -rw-r--r-- | bgpd/bgp_rpki.c | 27 | ||||
| -rw-r--r-- | doc/developer/cli.rst | 5 | ||||
| -rw-r--r-- | doc/user/rpki.rst | 8 | ||||
| -rw-r--r-- | lib/command.c | 1 | ||||
| -rw-r--r-- | lib/command_graph.c | 4 | ||||
| -rw-r--r-- | lib/command_graph.h | 1 | ||||
| -rw-r--r-- | lib/command_lex.l | 1 | ||||
| -rw-r--r-- | lib/command_match.c | 25 | ||||
| -rw-r--r-- | lib/command_parse.y | 16 | ||||
| -rw-r--r-- | lib/command_py.c | 39 | ||||
| -rw-r--r-- | ospfd/ospf_vty.c | 32 | ||||
| -rw-r--r-- | tests/lib/cli/test_cli.c | 4 | ||||
| -rw-r--r-- | tests/lib/cli/test_cli.in | 17 | ||||
| -rw-r--r-- | tests/lib/cli/test_cli.refout.in | 96 | ||||
| -rwxr-xr-x | tests/topotests/conftest.py | 70 | ||||
| -rw-r--r-- | tests/topotests/lib/micronet.py | 12 | ||||
| -rw-r--r-- | tests/topotests/lib/topotest.py | 12 | ||||
| -rw-r--r-- | tools/permutations.c | 16 | ||||
| -rw-r--r-- | zebra/router-id.c | 15 | ||||
| -rw-r--r-- | zebra/zapi_msg.c | 14 |
21 files changed, 334 insertions, 92 deletions
diff --git a/bgpd/bgp_evpn_vty.c b/bgpd/bgp_evpn_vty.c index 2bda5dbf9a..867efb6099 100644 --- a/bgpd/bgp_evpn_vty.c +++ b/bgpd/bgp_evpn_vty.c @@ -396,8 +396,6 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf, originator_ip, sizeof(originator_ip))); json_object_string_add(json, "advertiseGatewayMacip", "n/a"); json_object_string_add(json, "advertiseSviMacIp", "n/a"); - json_object_to_json_string_ext(json, - JSON_C_TO_STRING_NOSLASHESCAPE); json_object_string_add(json, "advertisePip", bgp_vrf->evpn_info->advertise_pip ? "Enabled" : "Disabled"); @@ -967,8 +965,6 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp, json_object_string_add(json_vni, "advertiseGatewayMacip", "n/a"); json_object_string_add(json_vni, "advertiseSviMacIp", "n/a"); - json_object_to_json_string_ext(json_vni, - JSON_C_TO_STRING_NOSLASHESCAPE); json_object_string_add( json_vni, "advertisePip", bgp->evpn_info->advertise_pip ? "Enabled" : "Disabled"); @@ -4413,8 +4409,11 @@ DEFUN(show_bgp_l2vpn_evpn_vni, } if (uj) { - vty_out(vty, "%s\n", json_object_to_json_string_ext( - json, JSON_C_TO_STRING_PRETTY)); + vty_out(vty, "%s\n", + json_object_to_json_string_ext( + json, + JSON_C_TO_STRING_PRETTY + | JSON_C_TO_STRING_NOSLASHESCAPE)); json_object_free(json); } diff --git a/bgpd/bgp_rpki.c b/bgpd/bgp_rpki.c index 648456ac49..6a89a7195c 100644 --- a/bgpd/bgp_rpki.c +++ b/bgpd/bgp_rpki.c @@ -923,25 +923,25 @@ static int config_write(struct vty *vty) #endif case TCP: tcp_config = cache->tr_config.tcp_config; - vty_out(vty, " rpki cache %s%s%s %s ", tcp_config->host, - tcp_config->bindaddr ? " source " : "", - tcp_config->bindaddr ? tcp_config->bindaddr - : "", + vty_out(vty, " rpki cache %s %s ", tcp_config->host, tcp_config->port); + if (tcp_config->bindaddr) + vty_out(vty, "source %s ", + tcp_config->bindaddr); break; #if defined(FOUND_SSH) case SSH: ssh_config = cache->tr_config.ssh_config; - vty_out(vty, " rpki cache %s%s%s %u %s %s %s ", - ssh_config->host, - ssh_config->bindaddr ? "source " : "", - ssh_config->bindaddr ? ssh_config->bindaddr - : "", - ssh_config->port, ssh_config->username, + vty_out(vty, " rpki cache %s %u %s %s %s ", + ssh_config->host, ssh_config->port, + ssh_config->username, ssh_config->client_privkey_path, ssh_config->server_hostkey_path != NULL ? ssh_config->server_hostkey_path : " "); + if (ssh_config->bindaddr) + vty_out(vty, "source %s ", + ssh_config->bindaddr); break; #endif default: @@ -1067,19 +1067,18 @@ DEFUN (no_rpki_retry_interval, } DEFPY(rpki_cache, rpki_cache_cmd, - "rpki cache <A.B.C.D|WORD> [source <A.B.C.D>$bindaddr] " - "<TCPPORT|(1-65535)$sshport SSH_UNAME SSH_PRIVKEY SSH_PUBKEY [SERVER_PUBKEY]> preference (1-255)", + "rpki cache <A.B.C.D|WORD> <TCPPORT|(1-65535)$sshport SSH_UNAME SSH_PRIVKEY SSH_PUBKEY [SERVER_PUBKEY]> [source <A.B.C.D>$bindaddr] preference (1-255)", RPKI_OUTPUT_STRING "Install a cache server to current group\n" "IP address of cache server\n Hostname of cache server\n" - "Configure source IP address of RPKI connection\n" - "Define a Source IP Address\n" "TCP port number\n" "SSH port number\n" "SSH user name\n" "Path to own SSH private key\n" "Path to own SSH public key\n" "Path to Public key of cache server\n" + "Configure source IP address of RPKI connection\n" + "Define a Source IP Address\n" "Preference of the cache server\n" "Preference value\n") { diff --git a/doc/developer/cli.rst b/doc/developer/cli.rst index edabe61d92..a24a4ecb7e 100644 --- a/doc/developer/cli.rst +++ b/doc/developer/cli.rst @@ -139,6 +139,7 @@ by the parser. selector: "<" `selector_seq_seq` ">" `varname_token` : "{" `selector_seq_seq` "}" `varname_token` : "[" `selector_seq_seq` "]" `varname_token` + : "![" `selector_seq_seq` "]" `varname_token` selector_seq_seq: `selector_seq_seq` "|" `selector_token_seq` : `selector_token_seq` selector_token_seq: `selector_token_seq` `selector_token` @@ -218,6 +219,10 @@ one-or-more selection and repetition. provide mutual exclusion. User input matches at most one option. - ``[square brackets]`` -- Contains sequences of tokens that can be omitted. ``[<a|b>]`` can be shortened to ``[a|b]``. +- ``![exclamation square brackets]`` -- same as ``[square brackets]``, but + only allow skipping the contents if the command input starts with ``no``. + (For cases where the positive command needs a parameter, but the parameter + is optional for the negative case.) - ``{curly|braces}`` -- similar to angle brackets, but instead of mutual exclusion, curly braces indicate that one or more of the pipe-separated sequences may be provided in any order. diff --git a/doc/user/rpki.rst b/doc/user/rpki.rst index 8d836bfc4b..235df56528 100644 --- a/doc/user/rpki.rst +++ b/doc/user/rpki.rst @@ -109,7 +109,7 @@ The following commands are independent of a specific cache server. The following commands configure one or multiple cache servers. -.. clicmd:: rpki cache (A.B.C.D|WORD) [source A.B.C.D] PORT [SSH_USERNAME] [SSH_PRIVKEY_PATH] [SSH_PUBKEY_PATH] [KNOWN_HOSTS_PATH] PREFERENCE +.. clicmd:: rpki cache (A.B.C.D|WORD) PORT [SSH_USERNAME] [SSH_PRIVKEY_PATH] [SSH_PUBKEY_PATH] [KNOWN_HOSTS_PATH] [source A.B.C.D] PREFERENCE Add a cache server to the socket. By default, the connection between router @@ -120,9 +120,6 @@ The following commands are independent of a specific cache server. A.B.C.D|WORD Address of the cache server. - source A.B.C.D - Source address of the RPKI connection to access cache server. - PORT Port number to connect to the cache server @@ -143,6 +140,9 @@ The following commands are independent of a specific cache server. on the configuration of the operating system environment, usually :file:`~/.ssh/known_hosts`. + source A.B.C.D + Source address of the RPKI connection to access cache server. + .. _validating-bgp-updates: diff --git a/lib/command.c b/lib/command.c index fcaf466c65..53aa064705 100644 --- a/lib/command.c +++ b/lib/command.c @@ -74,6 +74,7 @@ const struct message tokennames[] = { item(JOIN_TKN), item(START_TKN), item(END_TKN), + item(NEG_ONLY_TKN), {0}, }; /* clang-format on */ diff --git a/lib/command_graph.c b/lib/command_graph.c index c6c3840455..15c8302e63 100644 --- a/lib/command_graph.c +++ b/lib/command_graph.c @@ -388,6 +388,7 @@ static void cmd_node_names(struct graph_node *gn, struct graph_node *join, case START_TKN: case JOIN_TKN: + case NEG_ONLY_TKN: /* "<foo|bar> WORD" -> word is not "bar" or "foo" */ prevname = NULL; break; @@ -511,6 +512,9 @@ void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf) case JOIN_TKN: color = "#ddaaff"; break; + case NEG_ONLY_TKN: + color = "#ffddaa"; + break; case WORD_TKN: color = "#ffffff"; break; diff --git a/lib/command_graph.h b/lib/command_graph.h index 2754dca67d..c20c9874c2 100644 --- a/lib/command_graph.h +++ b/lib/command_graph.h @@ -64,6 +64,7 @@ enum cmd_token_type { JOIN_TKN, // marks subgraph end START_TKN, // first token in line END_TKN, // last token in line + NEG_ONLY_TKN, // filter token, match if "no ..." command SPECIAL_TKN = FORK_TKN, }; diff --git a/lib/command_lex.l b/lib/command_lex.l index 9c096995f5..ec366ce7e1 100644 --- a/lib/command_lex.l +++ b/lib/command_lex.l @@ -82,6 +82,7 @@ RANGE \({NUMBER}[ ]?\-[ ]?{NUMBER}\) {VARIABLE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return VARIABLE;} {WORD} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return WORD;} {RANGE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return RANGE;} +!\[ {yylval->string = NULL; return EXCL_BRACKET;} . {return yytext[0];} %% diff --git a/lib/command_match.c b/lib/command_match.c index 5703510148..56a7ae422b 100644 --- a/lib/command_match.c +++ b/lib/command_match.c @@ -42,7 +42,7 @@ DEFINE_MTYPE_STATIC(LIB, CMD_MATCHSTACK, "Command Match Stack"); /* matcher helper prototypes */ static int add_nexthops(struct list *, struct graph_node *, - struct graph_node **, size_t); + struct graph_node **, size_t, bool); static enum matcher_rv command_match_r(struct graph_node *, vector, unsigned int, struct graph_node **, @@ -79,6 +79,13 @@ static enum match_type match_variable(struct cmd_token *, const char *); static enum match_type match_mac(const char *, bool); +static bool is_neg(vector vline, size_t idx) +{ + if (idx >= vector_active(vline)) + return false; + return !strcmp(vector_slot(vline, idx), "no"); +} + enum matcher_rv command_match(struct graph *cmdgraph, vector vline, struct list **argv, const struct cmd_element **el) { @@ -248,7 +255,7 @@ static enum matcher_rv command_match_r(struct graph_node *start, vector vline, // get all possible nexthops struct list *next = list_new(); - add_nexthops(next, start, NULL, 0); + add_nexthops(next, start, NULL, 0, is_neg(vline, 1)); // determine the best match for (ALL_LIST_ELEMENTS_RO(next, ln, gn)) { @@ -349,6 +356,7 @@ enum matcher_rv command_complete(struct graph *graph, vector vline, { // pointer to next input token to match char *input_token; + bool neg = is_neg(vline, 0); struct list * current = @@ -363,7 +371,7 @@ enum matcher_rv command_complete(struct graph *graph, vector vline, // add all children of start node to list struct graph_node *start = vector_slot(graph->nodes, 0); - add_nexthops(next, start, &start, 0); + add_nexthops(next, start, &start, 0, neg); unsigned int idx; for (idx = 0; idx < vector_active(vline) && next->count > 0; idx++) { @@ -428,7 +436,7 @@ enum matcher_rv command_complete(struct graph *graph, vector vline, listnode_add(next, newstack); } else if (matchtype >= minmatch) add_nexthops(next, gstack[0], gstack, - idx + 1); + idx + 1, neg); break; default: trace_matcher("no_match\n"); @@ -478,7 +486,7 @@ enum matcher_rv command_complete(struct graph *graph, vector vline, * output, instead of direct node pointers! */ static int add_nexthops(struct list *list, struct graph_node *node, - struct graph_node **stack, size_t stackpos) + struct graph_node **stack, size_t stackpos, bool neg) { int added = 0; struct graph_node *child; @@ -494,8 +502,13 @@ static int add_nexthops(struct list *list, struct graph_node *node, if (j != stackpos) continue; } + + if (token->type == NEG_ONLY_TKN && !neg) + continue; + if (token->type >= SPECIAL_TKN && token->type != END_TKN) { - added += add_nexthops(list, child, stack, stackpos); + added += + add_nexthops(list, child, stack, stackpos, neg); } else { if (stack) { nextstack = XMALLOC( diff --git a/lib/command_parse.y b/lib/command_parse.y index f5e42cc304..3e2cdc79af 100644 --- a/lib/command_parse.y +++ b/lib/command_parse.y @@ -105,6 +105,9 @@ %token <string> MAC %token <string> MAC_PREFIX +/* special syntax, value is irrelevant */ +%token <string> EXCL_BRACKET + /* union types for parsed rules */ %type <node> start %type <node> literal_token @@ -372,6 +375,19 @@ selector: '[' selector_seq_seq ']' varname_token } ; +/* ![option] productions */ +selector: EXCL_BRACKET selector_seq_seq ']' varname_token +{ + struct graph_node *neg_only = new_token_node (ctx, NEG_ONLY_TKN, NULL, NULL); + + $$ = $2; + graph_add_edge ($$.start, neg_only); + graph_add_edge (neg_only, $$.end); + cmd_token_varname_set ($2.end->data, $4); + XFREE (MTYPE_LEX, $4); +} +; + %% #undef scanner diff --git a/lib/command_py.c b/lib/command_py.c index 7f19008fbf..90344ae1e5 100644 --- a/lib/command_py.c +++ b/lib/command_py.c @@ -197,21 +197,30 @@ static PyObject *graph_to_pyobj(struct wrap_graph *wgraph, if (gn->data) { struct cmd_token *tok = gn->data; switch (tok->type) { -#define item(x) case x: wrap->type = #x; break; - item(WORD_TKN) // words - item(VARIABLE_TKN) // almost anything - item(RANGE_TKN) // integer range - item(IPV4_TKN) // IPV4 addresses - item(IPV4_PREFIX_TKN) // IPV4 network prefixes - item(IPV6_TKN) // IPV6 prefixes - item(IPV6_PREFIX_TKN) // IPV6 network prefixes - item(MAC_TKN) // MAC address - item(MAC_PREFIX_TKN) // MAC address with mask - - /* plumbing types */ - item(FORK_TKN) item(JOIN_TKN) item(START_TKN) - item(END_TKN) default - : wrap->type = "???"; +#define item(x) \ + case x: \ + wrap->type = #x; \ + break /* no semicolon */ + + item(WORD_TKN); // words + item(VARIABLE_TKN); // almost anything + item(RANGE_TKN); // integer range + item(IPV4_TKN); // IPV4 addresses + item(IPV4_PREFIX_TKN); // IPV4 network prefixes + item(IPV6_TKN); // IPV6 prefixes + item(IPV6_PREFIX_TKN); // IPV6 network prefixes + item(MAC_TKN); // MAC address + item(MAC_PREFIX_TKN); // MAC address with mask + + /* plumbing types */ + item(FORK_TKN); + item(JOIN_TKN); + item(START_TKN); + item(END_TKN); + item(NEG_ONLY_TKN); +#undef item + default: + wrap->type = "???"; } wrap->deprecated = (tok->attr == CMD_ATTR_DEPRECATED); diff --git a/ospfd/ospf_vty.c b/ospfd/ospf_vty.c index 1d4aa65355..3ae9707f5f 100644 --- a/ospfd/ospf_vty.c +++ b/ospfd/ospf_vty.c @@ -10607,11 +10607,9 @@ static void show_ip_ospf_route_network(struct vty *vty, struct ospf *ospf, prefix2str(&rn->p, buf1, sizeof(buf1)); - json_route = json_object_new_object(); if (json) { + json_route = json_object_new_object(); json_object_object_add(json, buf1, json_route); - json_object_to_json_string_ext( - json, JSON_C_TO_STRING_NOSLASHESCAPE); } switch (or->path_type) { @@ -10733,8 +10731,6 @@ static void show_ip_ospf_route_network(struct vty *vty, struct ospf *ospf, } } } - if (!json) - json_object_free(json_route); } if (!json) vty_out(vty, "\n"); @@ -10762,8 +10758,8 @@ static void show_ip_ospf_route_router(struct vty *vty, struct ospf *ospf, continue; int flag = 0; - json_route = json_object_new_object(); if (json) { + json_route = json_object_new_object(); json_object_object_add( json, inet_ntop(AF_INET, &rn->p.u.prefix4, buf, sizeof(buf)), @@ -10878,8 +10874,6 @@ static void show_ip_ospf_route_router(struct vty *vty, struct ospf *ospf, } } } - if (!json) - json_object_free(json_route); } if (!json) vty_out(vty, "\n"); @@ -10908,11 +10902,9 @@ static void show_ip_ospf_route_external(struct vty *vty, struct ospf *ospf, char buf1[19]; snprintfrr(buf1, sizeof(buf1), "%pFX", &rn->p); - json_route = json_object_new_object(); if (json) { + json_route = json_object_new_object(); json_object_object_add(json, buf1, json_route); - json_object_to_json_string_ext( - json, JSON_C_TO_STRING_NOSLASHESCAPE); } switch (er->path_type) { @@ -11010,8 +11002,6 @@ static void show_ip_ospf_route_external(struct vty *vty, struct ospf *ospf, } } } - if (!json) - json_object_free(json_route); } if (!json) vty_out(vty, "\n"); @@ -11224,7 +11214,9 @@ DEFUN (show_ip_ospf_route, if (uj) { /* Keep Non-pretty format */ vty_out(vty, "%s\n", - json_object_to_json_string(json)); + json_object_to_json_string_ext( + json, + JSON_C_TO_STRING_NOSLASHESCAPE)); json_object_free(json); } else if (!ospf_output) vty_out(vty, "%% OSPF instance not found\n"); @@ -11236,7 +11228,9 @@ DEFUN (show_ip_ospf_route, if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( - json, JSON_C_TO_STRING_PRETTY)); + json, + JSON_C_TO_STRING_PRETTY + | JSON_C_TO_STRING_NOSLASHESCAPE)); json_object_free(json); } else vty_out(vty, "%% OSPF instance not found\n"); @@ -11250,7 +11244,9 @@ DEFUN (show_ip_ospf_route, if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( - json, JSON_C_TO_STRING_PRETTY)); + json, + JSON_C_TO_STRING_PRETTY + | JSON_C_TO_STRING_NOSLASHESCAPE)); json_object_free(json); } else vty_out(vty, "%% OSPF instance not found\n"); @@ -11263,7 +11259,9 @@ DEFUN (show_ip_ospf_route, ret = show_ip_ospf_route_common(vty, ospf, json, use_vrf); /* Keep Non-pretty format */ if (uj) - vty_out(vty, "%s\n", json_object_to_json_string(json)); + vty_out(vty, "%s\n", + json_object_to_json_string_ext( + json, JSON_C_TO_STRING_NOSLASHESCAPE)); } if (uj) diff --git a/tests/lib/cli/test_cli.c b/tests/lib/cli/test_cli.c index 8dba1e29f0..f8d74018dd 100644 --- a/tests/lib/cli/test_cli.c +++ b/tests/lib/cli/test_cli.c @@ -40,6 +40,8 @@ DUMMY_DEFUN(cmd12, "alt a A.B.C.D"); DUMMY_DEFUN(cmd13, "alt a X:X::X:X"); DUMMY_DEFUN(cmd14, "pat g { foo A.B.C.D$foo|foo|bar X:X::X:X$bar| baz } [final]"); +DUMMY_DEFUN(cmd15, "no pat g ![ WORD ]"); +DUMMY_DEFUN(cmd16, "[no] pat h {foo ![A.B.C.D$foo]|bar X:X::X:X$bar} final"); #include "tests/lib/cli/test_cli_clippy.c" @@ -81,5 +83,7 @@ void test_init(int argc, char **argv) install_element(ENABLE_NODE, &cmd13_cmd); } install_element(ENABLE_NODE, &cmd14_cmd); + install_element(ENABLE_NODE, &cmd15_cmd); + install_element(ENABLE_NODE, &cmd16_cmd); install_element(ENABLE_NODE, &magic_test_cmd); } diff --git a/tests/lib/cli/test_cli.in b/tests/lib/cli/test_cli.in index 5c146ef984..bd685a6231 100644 --- a/tests/lib/cli/test_cli.in +++ b/tests/lib/cli/test_cli.in @@ -74,6 +74,23 @@ pat f pat f foo pat f key +no pat g +no pat g test +no pat g test more + +pat h foo ?1.2.3.4 final +no pat h foo ?1.2.3.4 final +pat h foo final +no pat h foo final +pat h bar final +no pat h bar final +pat h bar 1::2 final +no pat h bar 1::2 final +pat h bar 1::2 foo final +no pat h bar 1::2 foo final +pat h bar 1::2 foo 1.2.3.4 final +no pat h bar 1::2 foo 1.2.3.4 final + alt a a?b alt a 1 .2?.3.4 alt a 1 :2? ::?3 diff --git a/tests/lib/cli/test_cli.refout.in b/tests/lib/cli/test_cli.refout.in index 1f38e08b20..84365810d5 100644 --- a/tests/lib/cli/test_cli.refout.in +++ b/tests/lib/cli/test_cli.refout.in @@ -147,7 +147,7 @@ test# papat % Command incomplete.
test# pat
a b c d e f
-g
+g h
test# pat
% Command incomplete.
test#
@@ -263,6 +263,100 @@ cmd10 with 3 args. [01] f@(null): f
[02] key@(null): key
test#
+test# no pat g
+cmd15 with 3 args.
+[00] no@(null): no
+[01] pat@(null): pat
+[02] g@(null): g
+test# no pat g test
+cmd15 with 4 args.
+[00] no@(null): no
+[01] pat@(null): pat
+[02] g@(null): g
+[03] WORD@g: test
+test# no pat g test more
+% [NONE] Unknown command: no pat g test more
+test#
+test# pat h foo
+ A.B.C.D 04
+test# pat h foo 1.2.3.4 final
+cmd16 with 5 args.
+[00] pat@(null): pat
+[01] h@(null): h
+[02] foo@(null): foo
+[03] A.B.C.D@foo: 1.2.3.4
+[04] final@(null): final
+test# no pat h foo
+ A.B.C.D 04
+ bar 05
+ final 07
+test# no pat h foo 1.2.3.4 final
+cmd16 with 6 args.
+[00] no@no: no
+[01] pat@(null): pat
+[02] h@(null): h
+[03] foo@(null): foo
+[04] A.B.C.D@foo: 1.2.3.4
+[05] final@(null): final
+test# pat h foo final
+% [NONE] Unknown command: pat h foo final
+test# no pat h foo final
+cmd16 with 5 args.
+[00] no@no: no
+[01] pat@(null): pat
+[02] h@(null): h
+[03] foo@(null): foo
+[04] final@(null): final
+test# pat h bar final
+% [NONE] Unknown command: pat h bar final
+test# no pat h bar final
+% [NONE] Unknown command: no pat h bar final
+test# pat h bar 1::2 final
+cmd16 with 5 args.
+[00] pat@(null): pat
+[01] h@(null): h
+[02] bar@(null): bar
+[03] X:X::X:X@bar: 1::2
+[04] final@(null): final
+test# no pat h bar 1::2 final
+cmd16 with 6 args.
+[00] no@no: no
+[01] pat@(null): pat
+[02] h@(null): h
+[03] bar@(null): bar
+[04] X:X::X:X@bar: 1::2
+[05] final@(null): final
+test# pat h bar 1::2 foo final
+% [NONE] Unknown command: pat h bar 1::2 foo final
+test# no pat h bar 1::2 foo final
+cmd16 with 7 args.
+[00] no@no: no
+[01] pat@(null): pat
+[02] h@(null): h
+[03] bar@(null): bar
+[04] X:X::X:X@bar: 1::2
+[05] foo@(null): foo
+[06] final@(null): final
+test# pat h bar 1::2 foo 1.2.3.4 final
+cmd16 with 7 args.
+[00] pat@(null): pat
+[01] h@(null): h
+[02] bar@(null): bar
+[03] X:X::X:X@bar: 1::2
+[04] foo@(null): foo
+[05] A.B.C.D@foo: 1.2.3.4
+[06] final@(null): final
+test# no pat h bar 1::2 foo 1.2.3.4 final
+cmd16 with 8 args.
+[00] no@no: no
+[01] pat@(null): pat
+[02] h@(null): h
+[03] bar@(null): bar
+[04] X:X::X:X@bar: 1::2
+[05] foo@(null): foo
+[06] A.B.C.D@foo: 1.2.3.4
+[07] final@(null): final
+test#
test# alt a
test# alt a a
WORD 02
diff --git a/tests/topotests/conftest.py b/tests/topotests/conftest.py index ed55490c09..7fe6a5aea1 100755 --- a/tests/topotests/conftest.py +++ b/tests/topotests/conftest.py @@ -6,13 +6,14 @@ import glob import os import pdb import re +import subprocess import sys import time import pytest import lib.fixtures from lib import topolog -from lib.micronet import Commander +from lib.micronet import Commander, proc_error from lib.micronet_cli import cli from lib.micronet_compat import Mininet, cleanup_current, cleanup_previous from lib.topogen import diagnose_env, get_topogen @@ -256,6 +257,23 @@ def pytest_configure(config): if cli_level is not None: config.option.log_cli_level = cli_level + have_tmux = bool(os.getenv("TMUX", "")) + have_screen = not have_tmux and bool(os.getenv("STY", "")) + have_xterm = not have_tmux and not have_screen and bool(os.getenv("DISPLAY", "")) + have_windows = have_tmux or have_screen or have_xterm + have_windows_pause = have_tmux or have_xterm + xdist_no_windows = is_xdist and not is_worker and not have_windows_pause + + def assert_feature_windows(b, feature): + if b and xdist_no_windows: + pytest.exit( + "{} use requires byobu/TMUX/XTerm under dist {}".format( + feature, os.environ["PYTEST_XDIST_MODE"] + ) + ) + elif b and not is_xdist and not have_windows: + pytest.exit("{} use requires byobu/TMUX/SCREEN/XTerm".format(feature)) + # --------------------------------------- # Record our options in global dictionary # --------------------------------------- @@ -272,6 +290,7 @@ def pytest_configure(config): gdb_daemons = config.getoption("--gdb-daemons") gdb_daemons = gdb_daemons.split(",") if gdb_daemons else [] topotest_extra_config["gdb_daemons"] = gdb_daemons + assert_feature_windows(gdb_routers or gdb_daemons, "GDB") gdb_breakpoints = config.getoption("--gdb-breakpoints") gdb_breakpoints = gdb_breakpoints.split(",") if gdb_breakpoints else [] @@ -279,31 +298,40 @@ def pytest_configure(config): cli_on_error = config.getoption("--cli-on-error") topotest_extra_config["cli_on_error"] = cli_on_error + assert_feature_windows(cli_on_error, "--cli-on-error") shell = config.getoption("--shell") topotest_extra_config["shell"] = shell.split(",") if shell else [] + assert_feature_windows(shell, "--shell") strace = config.getoption("--strace-daemons") topotest_extra_config["strace_daemons"] = strace.split(",") if strace else [] shell_on_error = config.getoption("--shell-on-error") topotest_extra_config["shell_on_error"] = shell_on_error + assert_feature_windows(shell_on_error, "--shell-on-error") topotest_extra_config["valgrind_extra"] = config.getoption("--valgrind-extra") topotest_extra_config["valgrind_memleaks"] = config.getoption("--valgrind-memleaks") vtysh = config.getoption("--vtysh") topotest_extra_config["vtysh"] = vtysh.split(",") if vtysh else [] + assert_feature_windows(vtysh, "--vtysh") vtysh_on_error = config.getoption("--vtysh-on-error") topotest_extra_config["vtysh_on_error"] = vtysh_on_error + assert_feature_windows(vtysh_on_error, "--vtysh-on-error") pause_on_error = vtysh or shell or config.getoption("--pause-on-error") if config.getoption("--no-pause-on-error"): pause_on_error = False topotest_extra_config["pause_on_error"] = pause_on_error - topotest_extra_config["pause"] = config.getoption("--pause") + assert_feature_windows(pause_on_error, "--pause-on-error") + + pause = config.getoption("--pause") + topotest_extra_config["pause"] = pause + assert_feature_windows(pause, "--pause") topotest_extra_config["topology_only"] = config.getoption("--topology-only") @@ -403,20 +431,27 @@ def pytest_runtest_makereport(item, call): error_cmd = os.getenv("SHELL", commander.get_exec_path(["bash"])) if error_cmd: - # Really would like something better than using this global here. - # Not all tests use topogen though so get_topogen() won't work. + is_tmux = bool(os.getenv("TMUX", "")) + is_screen = not is_tmux and bool(os.getenv("STY", "")) + is_xterm = not is_tmux and not is_screen and bool(os.getenv("DISPLAY", "")) + + channel = None win_info = None wait_for_channels = [] + wait_for_procs = [] + # Really would like something better than using this global here. + # Not all tests use topogen though so get_topogen() won't work. for node in Mininet.g_mnet_inst.hosts.values(): pause = True - channel = ( - "{}-{}".format(os.getpid(), Commander.tmux_wait_gen) - if not isatty - else None - ) - Commander.tmux_wait_gen += 1 - wait_for_channels.append(channel) + if is_tmux: + channel = ( + "{}-{}".format(os.getpid(), Commander.tmux_wait_gen) + if not isatty + else None + ) + Commander.tmux_wait_gen += 1 + wait_for_channels.append(channel) pane_info = node.run_in_window( error_cmd, @@ -427,13 +462,22 @@ def pytest_runtest_makereport(item, call): tmux_target=win_info, wait_for=channel, ) - if win_info is None: - win_info = pane_info + if is_tmux: + if win_info is None: + win_info = pane_info + elif is_xterm: + assert isinstance(pane_info, subprocess.Popen) + wait_for_procs.append(pane_info) # Now wait on any channels for channel in wait_for_channels: logger.debug("Waiting on TMUX channel %s", channel) commander.cmd_raises([commander.get_exec_path("tmux"), "wait", channel]) + for p in wait_for_procs: + logger.debug("Waiting on TMUX xterm process %s", p) + o, e = p.communicate() + if p.wait(): + logger.warning("xterm proc failed: %s:", proc_error(p, o, e)) if error and topotest_extra_config["cli_on_error"]: # Really would like something better than using this global here. diff --git a/tests/topotests/lib/micronet.py b/tests/topotests/lib/micronet.py index 0416c53c48..8567bd3b4b 100644 --- a/tests/topotests/lib/micronet.py +++ b/tests/topotests/lib/micronet.py @@ -369,13 +369,13 @@ class Commander(object): # pylint: disable=R0205 cmd = [self.get_exec_path("xterm")] if "SUDO_USER" in os.environ: cmd = [self.get_exec_path("sudo"), "-u", os.environ["SUDO_USER"]] + cmd - # if title: - # cmd.append("-T") - # cmd.append(title) + if title: + cmd.append("-T") + cmd.append(title) cmd.append("-e") cmd.append(sudo_path) cmd.extend(self.pre_cmd) - cmd.append(user_cmd) + cmd.extend(["bash", "-c", user_cmd]) # if channel: # return self.cmd_raises(cmd, skip_pre_cmd=True) # else: @@ -384,13 +384,11 @@ class Commander(object): # pylint: disable=R0205 skip_pre_cmd=True, stdin=None, shell=False, - # stdout=open("/dev/null", "w"), - # stderr=open("/dev/null", "w"), ) time_mod.sleep(2) if p.poll() is not None: self.logger.error("%s: Failed to launch xterm: %s", self, comm_error(p)) - return "" + return p else: self.logger.error( "DISPLAY, STY, and TMUX not in environment, can't open window" diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index b6f55664a6..1b26ddc1b5 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -1603,10 +1603,6 @@ class Router(Node): if "all" in shell_routers or self.name in shell_routers: self.run_in_window(os.getenv("SHELL", "bash")) - vtysh_routers = g_extra_config["vtysh"] - if "all" in vtysh_routers or self.name in vtysh_routers: - self.run_in_window("vtysh") - if self.daemons["eigrpd"] == 1: eigrpd_path = os.path.join(self.daemondir, "eigrpd") if not os.path.isfile(eigrpd_path): @@ -1619,7 +1615,13 @@ class Router(Node): logger.info("BFD Test, but no bfdd compiled or installed") return "BFD Test, but no bfdd compiled or installed" - return self.startRouterDaemons(tgen=tgen) + status = self.startRouterDaemons(tgen=tgen) + + vtysh_routers = g_extra_config["vtysh"] + if "all" in vtysh_routers or self.name in vtysh_routers: + self.run_in_window("vtysh") + + return status def getStdErr(self, daemon): return self.getLog("err", daemon) diff --git a/tools/permutations.c b/tools/permutations.c index f51d4a4ec9..b280cc15b1 100644 --- a/tools/permutations.c +++ b/tools/permutations.c @@ -61,9 +61,22 @@ void permute(struct graph_node *start) struct cmd_token *stok = start->data; struct graph_node *gnn; struct listnode *ln; + bool is_neg = false; // recursive dfs listnode_add(position, start); + + for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) { + struct cmd_token *tok = gnn->data; + + if (tok->type == WORD_TKN && !strcmp(tok->text, "no")) { + is_neg = true; + break; + } + if (tok->type < SPECIAL_TKN) + break; + } + for (unsigned int i = 0; i < vector_active(start->to); i++) { struct graph_node *gn = vector_slot(start->to, i); struct cmd_token *tok = gn->data; @@ -82,6 +95,9 @@ void permute(struct graph_node *start) fprintf(stdout, "\n"); } else { bool skip = false; + + if (tok->type == NEG_ONLY_TKN && !is_neg) + continue; if (stok->type == FORK_TKN && tok->type != FORK_TKN) for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) if (gnn == gn) { diff --git a/zebra/router-id.c b/zebra/router-id.c index 689b9787ee..ac81d537d0 100644 --- a/zebra/router-id.c +++ b/zebra/router-id.c @@ -120,10 +120,12 @@ int router_id_get(afi_t afi, struct prefix *p, struct zebra_vrf *zvrf) static int router_id_set(afi_t afi, struct prefix *p, struct zebra_vrf *zvrf) { - struct prefix p2; + struct prefix after, before; struct listnode *node; struct zserv *client; + router_id_get(afi, &before, zvrf); + switch (afi) { case AFI_IP: zvrf->rid_user_assigned.u.prefix4.s_addr = p->u.prefix4.s_addr; @@ -135,10 +137,17 @@ static int router_id_set(afi_t afi, struct prefix *p, struct zebra_vrf *zvrf) return -1; } - router_id_get(afi, &p2, zvrf); + router_id_get(afi, &after, zvrf); + + /* + * If we've been told that the router-id is exactly the same + * do we need to really do anything here? + */ + if (prefix_same(&before, &after)) + return 0; for (ALL_LIST_ELEMENTS_RO(zrouter.client_list, node, client)) - zsend_router_id_update(client, afi, &p2, zvrf->vrf->vrf_id); + zsend_router_id_update(client, afi, &after, zvrf->vrf->vrf_id); return 0; } diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c index 6666b3525e..72c7071502 100644 --- a/zebra/zapi_msg.c +++ b/zebra/zapi_msg.c @@ -2221,8 +2221,8 @@ stream_failure: static void zread_router_id_add(ZAPI_HANDLER_ARGS) { afi_t afi; - struct prefix p; + struct prefix zero; STREAM_GETW(msg, afi); @@ -2238,6 +2238,18 @@ static void zread_router_id_add(ZAPI_HANDLER_ARGS) router_id_get(afi, &p, zvrf); + /* + * If we have not officially setup a router-id let's not + * tell the upper level protocol about it yet. + */ + memset(&zero, 0, sizeof(zero)); + if ((p.family == AF_INET && p.u.prefix4.s_addr == INADDR_ANY) + || (p.family == AF_INET6 + && memcmp(&p.u.prefix6, &zero.u.prefix6, + sizeof(struct in6_addr)) + == 0)) + return; + zsend_router_id_update(client, afi, &p, zvrf_id(zvrf)); stream_failure: |
