]> git.proxmox.com Git - mirror_frr.git/blame - lib/northbound_cli.c
ripd: fix display of the "distance" command
[mirror_frr.git] / lib / northbound_cli.c
CommitLineData
1c2facd1
RW
1/*
2 * Copyright (C) 2018 NetDEF, Inc.
3 * Renato Westphal
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; see the file COPYING; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include <zebra.h>
21
22#include "libfrr.h"
23#include "version.h"
24#include "log.h"
25#include "lib_errors.h"
26#include "command.h"
27#include "termtable.h"
28#include "db.h"
29#include "yang_translator.h"
30#include "northbound.h"
31#include "northbound_cli.h"
32#include "northbound_db.h"
33#ifndef VTYSH_EXTRACT_PL
34#include "lib/northbound_cli_clippy.c"
35#endif
36
37int debug_northbound;
38struct nb_config *vty_shared_candidate_config;
39
40static void vty_show_libyang_errors(struct vty *vty, struct ly_ctx *ly_ctx)
41{
42 struct ly_err_item *ei;
43 const char *path;
44
45 ei = ly_err_first(ly_ctx);
46 if (!ei)
47 return;
48
49 for (; ei; ei = ei->next)
50 vty_out(vty, "%s\n", ei->msg);
51
52 path = ly_errpath(ly_ctx);
53 if (path)
54 vty_out(vty, "YANG path: %s\n", path);
55
56 ly_err_clean(ly_ctx, NULL);
57}
58
59int nb_cli_cfg_change(struct vty *vty, char *xpath_base,
60 struct cli_config_change changes[], size_t size)
61{
62 struct nb_config *candidate_transitory;
63 bool error = false;
64 int ret;
65
66 VTY_CHECK_XPATH;
67
68 /*
69 * Create a copy of the candidate configuration. For consistency, we
70 * need to ensure that either all changes made by the command are
71 * accepted or none are.
72 */
73 candidate_transitory = nb_config_dup(vty->candidate_config);
74
75 /* Edit candidate configuration. */
76 for (size_t i = 0; i < size; i++) {
77 struct cli_config_change *change = &changes[i];
78 struct nb_node *nb_node;
79 char xpath[XPATH_MAXLEN];
80 struct yang_data *data;
81
82 /* Handle relative XPaths. */
83 memset(xpath, 0, sizeof(xpath));
84 if (vty->xpath_index > 0
85 && ((xpath_base && xpath_base[0] == '.')
86 || change->xpath[0] == '.'))
87 strlcpy(xpath, VTY_CURR_XPATH, sizeof(xpath));
88 if (xpath_base) {
89 if (xpath_base[0] == '.')
90 xpath_base++;
91 strlcat(xpath, xpath_base, sizeof(xpath));
92 }
93 if (change->xpath[0] == '.')
94 strlcat(xpath, change->xpath + 1, sizeof(xpath));
95 else
96 strlcpy(xpath, change->xpath, sizeof(xpath));
97
98 nb_node = nb_node_find(xpath);
99 if (!nb_node) {
100 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
101 "%s: unknown data path: %s", __func__, xpath);
102 error = true;
103 break;
104 }
105
106 /* If the value is not set, get the default if it exists. */
107 if (change->value == NULL)
108 change->value = yang_snode_get_default(nb_node->snode);
109 data = yang_data_new(xpath, change->value);
110
111 /*
112 * Ignore "not found" errors when editing the candidate
113 * configuration.
114 */
115 ret = nb_candidate_edit(candidate_transitory, nb_node,
116 change->operation, xpath, NULL, data);
117 yang_data_free(data);
118 if (ret != NB_OK && ret != NB_ERR_NOT_FOUND) {
119 flog_warn(
120 EC_LIB_NB_CANDIDATE_EDIT_ERROR,
121 "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
122 __func__, nb_operation_name(change->operation),
123 xpath);
124 error = true;
125 break;
126 }
127 }
128
129 if (error) {
130 nb_config_free(candidate_transitory);
131
132 switch (frr_get_cli_mode()) {
133 case FRR_CLI_CLASSIC:
134 vty_out(vty, "%% Configuration failed.\n\n");
135 break;
136 case FRR_CLI_TRANSACTIONAL:
137 vty_out(vty,
138 "%% Failed to edit candidate configuration.\n\n");
139 break;
140 }
141 vty_show_libyang_errors(vty, ly_native_ctx);
142
143 return CMD_WARNING_CONFIG_FAILED;
144 }
145
146 nb_config_replace(vty->candidate_config, candidate_transitory, false);
147
148 /* Do an implicit "commit" when using the classic CLI mode. */
149 if (frr_get_cli_mode() == FRR_CLI_CLASSIC) {
150 ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI,
151 false, NULL, NULL);
152 if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) {
153 vty_out(vty, "%% Configuration failed: %s.\n\n",
154 nb_err_name(ret));
155 vty_out(vty,
156 "Please check the logs for more details.\n");
157
158 /* Regenerate candidate for consistency. */
159 nb_config_replace(vty->candidate_config, running_config,
160 true);
161 return CMD_WARNING_CONFIG_FAILED;
162 }
163 }
164
165 return CMD_SUCCESS;
166}
167
168int nb_cli_rpc(const char *xpath, struct list *input, struct list *output)
169{
170 struct nb_node *nb_node;
171 int ret;
172
173 nb_node = nb_node_find(xpath);
174 if (!nb_node) {
175 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
176 "%s: unknown data path: %s", __func__, xpath);
177 return CMD_WARNING;
178 }
179
180 ret = nb_node->cbs.rpc(xpath, input, output);
181 switch (ret) {
182 case NB_OK:
183 return CMD_SUCCESS;
184 default:
185 return CMD_WARNING;
186 }
187}
188
189static int nb_cli_commit(struct vty *vty, bool force, char *comment)
190{
191 uint32_t transaction_id;
192 int ret;
193
194 if (vty_exclusive_lock != NULL && vty_exclusive_lock != vty) {
195 vty_out(vty, "%% Configuration is locked by another VTY.\n\n");
196 return CMD_WARNING;
197 }
198
199 if (!force && nb_candidate_needs_update(vty->candidate_config)) {
200 vty_out(vty,
201 "%% Candidate configuration needs to be updated before commit.\n\n");
202 vty_out(vty,
203 "Use the \"update\" command or \"commit force\".\n");
204 return CMD_WARNING;
205 }
206
207 ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI, true,
208 comment, &transaction_id);
209
210 /* Map northbound return code to CLI return code. */
211 switch (ret) {
212 case NB_OK:
213 nb_config_replace(vty->candidate_config_base, running_config,
214 true);
215 vty_out(vty,
216 "%% Configuration committed successfully (Transaction ID #%u).\n\n",
217 transaction_id);
218 return CMD_SUCCESS;
219 case NB_ERR_NO_CHANGES:
220 vty_out(vty, "%% No configuration changes to commit.\n\n");
221 return CMD_SUCCESS;
222 default:
223 vty_out(vty,
224 "%% Failed to commit candidate configuration: %s.\n\n",
225 nb_err_name(ret));
226 vty_out(vty, "Please check the logs for more details.\n");
227 return CMD_WARNING;
228 }
229}
230
231static int nb_cli_candidate_load_file(struct vty *vty,
232 enum nb_cfg_format format,
233 struct yang_translator *translator,
234 const char *path, bool replace)
235{
236 struct nb_config *loaded_config = NULL;
237 struct lyd_node *dnode;
238 struct ly_ctx *ly_ctx;
239 int ly_format;
240
241 switch (format) {
242 case NB_CFG_FMT_CMDS:
243 loaded_config = nb_config_new(NULL);
244 if (!vty_read_config(loaded_config, path, config_default)) {
245 vty_out(vty, "%% Failed to load configuration.\n\n");
246 vty_out(vty,
247 "Please check the logs for more details.\n");
248 nb_config_free(loaded_config);
249 return CMD_WARNING;
250 }
251 break;
252 case NB_CFG_FMT_JSON:
253 case NB_CFG_FMT_XML:
254 ly_format = (format == NB_CFG_FMT_JSON) ? LYD_JSON : LYD_XML;
255
256 ly_ctx = translator ? translator->ly_ctx : ly_native_ctx;
257 dnode = lyd_parse_path(ly_ctx, path, ly_format, LYD_OPT_EDIT);
258 if (!dnode) {
259 flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_path() failed",
260 __func__);
261 vty_out(vty, "%% Failed to load configuration:\n\n");
262 vty_show_libyang_errors(vty, ly_ctx);
263 return CMD_WARNING;
264 }
265 if (translator
266 && yang_translate_dnode(translator,
267 YANG_TRANSLATE_TO_NATIVE, &dnode)
268 != YANG_TRANSLATE_SUCCESS) {
269 vty_out(vty, "%% Failed to translate configuration\n");
270 yang_dnode_free(dnode);
271 return CMD_WARNING;
272 }
273 loaded_config = nb_config_new(dnode);
274 break;
275 }
276
277 if (replace)
278 nb_config_replace(vty->candidate_config, loaded_config, false);
279 else if (nb_config_merge(vty->candidate_config, loaded_config, false)
280 != NB_OK) {
281 vty_out(vty,
282 "%% Failed to merge the loaded configuration:\n\n");
283 vty_show_libyang_errors(vty, ly_native_ctx);
284 return CMD_WARNING;
285 }
286
287 return CMD_SUCCESS;
288}
289
290static int nb_cli_candidate_load_transaction(struct vty *vty,
291 uint32_t transaction_id,
292 bool replace)
293{
294 struct nb_config *loaded_config;
295
296 loaded_config = nb_db_transaction_load(transaction_id);
297 if (!loaded_config) {
298 vty_out(vty, "%% Transaction %u does not exist.\n\n",
299 transaction_id);
300 return CMD_WARNING;
301 }
302
303 if (replace)
304 nb_config_replace(vty->candidate_config, loaded_config, false);
305 else if (nb_config_merge(vty->candidate_config, loaded_config, false)
306 != NB_OK) {
307 vty_out(vty,
308 "%% Failed to merge the loaded configuration:\n\n");
309 vty_show_libyang_errors(vty, ly_native_ctx);
310 return CMD_WARNING;
311 }
312
313 return CMD_SUCCESS;
314}
315
316void nb_cli_show_dnode_cmds(struct vty *vty, struct lyd_node *root,
317 bool with_defaults)
318{
319 struct lyd_node *next, *child;
320
321 LY_TREE_DFS_BEGIN (root, next, child) {
322 struct nb_node *nb_node;
323
324 nb_node = child->schema->priv;
325 if (!nb_node->cbs.cli_show)
326 goto next;
327
328 /* Skip default values. */
329 if (!with_defaults && yang_dnode_is_default_recursive(child))
330 goto next;
331
332 (*nb_node->cbs.cli_show)(vty, child, with_defaults);
333 next:
334 LY_TREE_DFS_END(root, next, child);
335 }
336}
337
338static void nb_cli_show_config_cmds(struct vty *vty, struct nb_config *config,
339 bool with_defaults)
340{
341 struct lyd_node *root;
342
343 vty_out(vty, "Configuration:\n");
344 vty_out(vty, "!\n");
345 vty_out(vty, "frr version %s\n", FRR_VER_SHORT);
346 vty_out(vty, "frr defaults %s\n", DFLT_NAME);
347
348 LY_TREE_FOR (config->dnode, root)
349 nb_cli_show_dnode_cmds(vty, root, with_defaults);
350
351 vty_out(vty, "!\n");
352 vty_out(vty, "end\n");
353}
354
355static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format,
356 struct nb_config *config,
357 struct yang_translator *translator,
358 bool with_defaults)
359{
360 struct lyd_node *dnode;
361 char *strp;
362 int options;
363
364 dnode = yang_dnode_dup(config->dnode);
365 if (translator
366 && yang_translate_dnode(translator, YANG_TRANSLATE_FROM_NATIVE,
367 &dnode)
368 != YANG_TRANSLATE_SUCCESS) {
369 vty_out(vty, "%% Failed to translate configuration\n");
370 yang_dnode_free(dnode);
371 return CMD_WARNING;
372 }
373
374 options = LYP_FORMAT | LYP_WITHSIBLINGS;
375 if (with_defaults)
376 options |= LYP_WD_ALL;
377 else
378 options |= LYP_WD_TRIM;
379
380 if (lyd_print_mem(&strp, dnode, format, options) == 0 && strp) {
381 vty_out(vty, "%s", strp);
382 free(strp);
383 }
384
385 yang_dnode_free(dnode);
386
387 return CMD_SUCCESS;
388}
389
390static int nb_cli_show_config(struct vty *vty, struct nb_config *config,
391 enum nb_cfg_format format,
392 struct yang_translator *translator,
393 bool with_defaults)
394{
395 switch (format) {
396 case NB_CFG_FMT_CMDS:
397 nb_cli_show_config_cmds(vty, config, with_defaults);
398 break;
399 case NB_CFG_FMT_JSON:
400 return nb_cli_show_config_libyang(vty, LYD_JSON, config,
401 translator, with_defaults);
402 case NB_CFG_FMT_XML:
403 return nb_cli_show_config_libyang(vty, LYD_XML, config,
404 translator, with_defaults);
405 }
406
407 return CMD_SUCCESS;
408}
409
410static int nb_write_config(struct nb_config *config, enum nb_cfg_format format,
411 struct yang_translator *translator, char *path,
412 size_t pathlen)
413{
414 int fd;
415 struct vty *file_vty;
416 int ret = 0;
417
418 snprintf(path, pathlen, "/tmp/frr.tmp.XXXXXXXX");
419 fd = mkstemp(path);
420 if (fd < 0) {
421 flog_warn(EC_LIB_SYSTEM_CALL, "%s: mkstemp() failed: %s",
422 __func__, safe_strerror(errno));
423 return -1;
424 }
425
426 /* Make vty for configuration file. */
427 file_vty = vty_new();
428 file_vty->wfd = fd;
429 file_vty->type = VTY_FILE;
430 if (config)
431 ret = nb_cli_show_config(file_vty, config, format, translator,
432 false);
433 vty_close(file_vty);
434
435 return ret;
436}
437
438static int nb_cli_show_config_compare(struct vty *vty,
439 struct nb_config *config1,
440 struct nb_config *config2,
441 enum nb_cfg_format format,
442 struct yang_translator *translator)
443{
444 char config1_path[256];
445 char config2_path[256];
446 char command[BUFSIZ];
447 FILE *fp;
448 char line[1024];
449 int lineno = 0;
450
451 if (nb_write_config(config1, format, translator, config1_path,
452 sizeof(config1_path))
453 != 0) {
454 vty_out(vty, "%% Failed to process configurations.\n\n");
455 return CMD_WARNING;
456 }
457 if (nb_write_config(config2, format, translator, config2_path,
458 sizeof(config2_path))
459 != 0) {
460 vty_out(vty, "%% Failed to process configurations.\n\n");
461 unlink(config1_path);
462 return CMD_WARNING;
463 }
464
465 snprintf(command, sizeof(command), "diff -u %s %s", config1_path,
466 config2_path);
467 fp = popen(command, "r");
468 if (!fp) {
469 vty_out(vty, "%% Failed to generate configuration diff.\n\n");
470 unlink(config1_path);
471 unlink(config2_path);
472 return CMD_WARNING;
473 }
474 /* Print diff line by line. */
475 while (fgets(line, sizeof(line), fp) != NULL) {
476 if (lineno++ < 2)
477 continue;
478 vty_out(vty, "%s", line);
479 }
480 pclose(fp);
481
482 unlink(config1_path);
483 unlink(config2_path);
484
485 return CMD_SUCCESS;
486}
487
488/* Configure exclusively from this terminal. */
489DEFUN (config_exclusive,
490 config_exclusive_cmd,
491 "configure exclusive",
492 "Configuration from vty interface\n"
493 "Configure exclusively from this terminal\n")
494{
495 if (vty_config_exclusive_lock(vty))
496 vty->node = CONFIG_NODE;
497 else {
498 vty_out(vty, "VTY configuration is locked by other VTY\n");
499 return CMD_WARNING_CONFIG_FAILED;
500 }
501
502 vty->private_config = true;
503 vty->candidate_config = nb_config_dup(running_config);
504 vty->candidate_config_base = nb_config_dup(running_config);
505 vty_out(vty,
506 "Warning: uncommitted changes will be discarded on exit.\n\n");
507
508 return CMD_SUCCESS;
509}
510
511/* Configure using a private candidate configuration. */
512DEFUN (config_private,
513 config_private_cmd,
514 "configure private",
515 "Configuration from vty interface\n"
516 "Configure using a private candidate configuration\n")
517{
518 if (vty_config_lock(vty))
519 vty->node = CONFIG_NODE;
520 else {
521 vty_out(vty, "VTY configuration is locked by other VTY\n");
522 return CMD_WARNING_CONFIG_FAILED;
523 }
524
525 vty->private_config = true;
526 vty->candidate_config = nb_config_dup(running_config);
527 vty->candidate_config_base = nb_config_dup(running_config);
528 vty_out(vty,
529 "Warning: uncommitted changes will be discarded on exit.\n\n");
530
531 return CMD_SUCCESS;
532}
533
534DEFPY (config_commit,
535 config_commit_cmd,
536 "commit [force$force]",
537 "Commit changes into the running configuration\n"
538 "Force commit even if the candidate is outdated\n")
539{
540 return nb_cli_commit(vty, !!force, NULL);
541}
542
543DEFPY (config_commit_comment,
544 config_commit_comment_cmd,
545 "commit [force$force] comment LINE...",
546 "Commit changes into the running configuration\n"
547 "Force commit even if the candidate is outdated\n"
548 "Assign a comment to this commit\n"
549 "Comment for this commit (Max 80 characters)\n")
550{
551 char *comment;
552 int idx = 0;
553 int ret;
554
555 argv_find(argv, argc, "LINE", &idx);
556 comment = argv_concat(argv, argc, idx);
557 ret = nb_cli_commit(vty, !!force, comment);
558 XFREE(MTYPE_TMP, comment);
559
560 return ret;
561}
562
563DEFPY (config_commit_check,
564 config_commit_check_cmd,
565 "commit check",
566 "Commit changes into the running configuration\n"
567 "Check if the configuration changes are valid\n")
568{
569 int ret;
570
571 ret = nb_candidate_validate(vty->candidate_config);
572 if (ret != NB_OK) {
573 vty_out(vty,
574 "%% Failed to validate candidate configuration.\n\n");
575 vty_show_libyang_errors(vty, ly_native_ctx);
576 return CMD_WARNING;
577 }
578
579 vty_out(vty, "%% Candidate configuration validated successfully.\n\n");
580
581 return CMD_SUCCESS;
582}
583
584DEFPY (config_update,
585 config_update_cmd,
586 "update",
587 "Update candidate configuration\n")
588{
589 if (!nb_candidate_needs_update(vty->candidate_config)) {
590 vty_out(vty, "%% Update is not necessary.\n\n");
591 return CMD_SUCCESS;
592 }
593
594 if (nb_candidate_update(vty->candidate_config) != NB_OK) {
595 vty_out(vty,
596 "%% Failed to update the candidate configuration.\n\n");
597 vty_out(vty, "Please check the logs for more details.\n");
598 return CMD_WARNING;
599 }
600
601 nb_config_replace(vty->candidate_config_base, running_config, true);
602
603 vty_out(vty, "%% Candidate configuration updated successfully.\n\n");
604
605 return CMD_SUCCESS;
606}
607
608DEFPY (config_discard,
609 config_discard_cmd,
610 "discard",
611 "Discard changes in the candidate configuration\n")
612{
613 nb_config_replace(vty->candidate_config, vty->candidate_config_base,
614 true);
615
616 return CMD_SUCCESS;
617}
618
619DEFPY (config_load,
620 config_load_cmd,
621 "configuration load\
622 <\
623 file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
624 |transaction (1-4294967296)$tid\
625 >\
626 [replace$replace]",
627 "Configuration related settings\n"
628 "Load configuration into candidate\n"
629 "Load configuration file into candidate\n"
630 "Load configuration file in JSON format\n"
631 "Load configuration file in XML format\n"
632 "Translate configuration file\n"
633 "YANG module translator\n"
634 "Configuration file name (full path)\n"
635 "Load configuration from transaction into candidate\n"
636 "Transaction ID\n"
637 "Replace instead of merge\n")
638{
639 if (filename) {
640 enum nb_cfg_format format;
641 struct yang_translator *translator = NULL;
642
643 if (json)
644 format = NB_CFG_FMT_JSON;
645 else if (xml)
646 format = NB_CFG_FMT_XML;
647 else
648 format = NB_CFG_FMT_CMDS;
649
650 if (translator_family) {
651 translator = yang_translator_find(translator_family);
652 if (!translator) {
653 vty_out(vty,
654 "%% Module translator \"%s\" not found\n",
655 translator_family);
656 return CMD_WARNING;
657 }
658 }
659
660 return nb_cli_candidate_load_file(vty, format, translator,
661 filename, !!replace);
662 }
663
664 return nb_cli_candidate_load_transaction(vty, tid, !!replace);
665}
666
667DEFPY (show_config_running,
668 show_config_running_cmd,
669 "show configuration running\
670 [<json$json|xml$xml> [translate WORD$translator_family]]\
671 [with-defaults$with_defaults]",
672 SHOW_STR
673 "Configuration information\n"
674 "Running configuration\n"
675 "Change output format to JSON\n"
676 "Change output format to XML\n"
677 "Translate output\n"
678 "YANG module translator\n"
679 "Show default values\n")
680
681{
682 enum nb_cfg_format format;
683 struct yang_translator *translator = NULL;
684
685 if (json)
686 format = NB_CFG_FMT_JSON;
687 else if (xml)
688 format = NB_CFG_FMT_XML;
689 else
690 format = NB_CFG_FMT_CMDS;
691
692 if (translator_family) {
693 translator = yang_translator_find(translator_family);
694 if (!translator) {
695 vty_out(vty, "%% Module translator \"%s\" not found\n",
696 translator_family);
697 return CMD_WARNING;
698 }
699 }
700
701 nb_cli_show_config(vty, running_config, format, translator,
702 !!with_defaults);
703
704 return CMD_SUCCESS;
705}
706
707DEFPY (show_config_candidate,
708 show_config_candidate_cmd,
709 "show configuration candidate\
710 [<json$json|xml$xml> [translate WORD$translator_family]]\
711 [<\
712 with-defaults$with_defaults\
713 |changes$changes\
714 >]",
715 SHOW_STR
716 "Configuration information\n"
717 "Candidate configuration\n"
718 "Change output format to JSON\n"
719 "Change output format to XML\n"
720 "Translate output\n"
721 "YANG module translator\n"
722 "Show default values\n"
723 "Show changes applied in the candidate configuration\n")
724
725{
726 enum nb_cfg_format format;
727 struct yang_translator *translator = NULL;
728
729 if (json)
730 format = NB_CFG_FMT_JSON;
731 else if (xml)
732 format = NB_CFG_FMT_XML;
733 else
734 format = NB_CFG_FMT_CMDS;
735
736 if (translator_family) {
737 translator = yang_translator_find(translator_family);
738 if (!translator) {
739 vty_out(vty, "%% Module translator \"%s\" not found\n",
740 translator_family);
741 return CMD_WARNING;
742 }
743 }
744
745 if (changes)
746 return nb_cli_show_config_compare(
747 vty, vty->candidate_config_base, vty->candidate_config,
748 format, translator);
749
750 nb_cli_show_config(vty, vty->candidate_config, format, translator,
751 !!with_defaults);
752
753 return CMD_SUCCESS;
754}
755
756DEFPY (show_config_compare,
757 show_config_compare_cmd,
758 "show configuration compare\
759 <\
760 candidate$c1_candidate\
761 |running$c1_running\
762 |transaction (1-4294967296)$c1_tid\
763 >\
764 <\
765 candidate$c2_candidate\
766 |running$c2_running\
767 |transaction (1-4294967296)$c2_tid\
768 >\
769 [<json$json|xml$xml> [translate WORD$translator_family]]",
770 SHOW_STR
771 "Configuration information\n"
772 "Compare two different configurations\n"
773 "Candidate configuration\n"
774 "Running configuration\n"
775 "Configuration transaction\n"
776 "Transaction ID\n"
777 "Candidate configuration\n"
778 "Running configuration\n"
779 "Configuration transaction\n"
780 "Transaction ID\n"
781 "Change output format to JSON\n"
782 "Change output format to XML\n"
783 "Translate output\n"
784 "YANG module translator\n")
785{
786 enum nb_cfg_format format;
787 struct yang_translator *translator = NULL;
788 struct nb_config *config1, *config_transaction1 = NULL;
789 struct nb_config *config2, *config_transaction2 = NULL;
790 int ret = CMD_WARNING;
791
792 if (c1_candidate)
793 config1 = vty->candidate_config;
794 else if (c1_running)
795 config1 = running_config;
796 else {
797 config_transaction1 = nb_db_transaction_load(c1_tid);
798 if (!config_transaction1) {
799 vty_out(vty, "%% Transaction %u does not exist\n\n",
800 (unsigned int)c1_tid);
801 goto exit;
802 }
803 config1 = config_transaction1;
804 }
805
806 if (c2_candidate)
807 config2 = vty->candidate_config;
808 else if (c2_running)
809 config2 = running_config;
810 else {
811 config_transaction2 = nb_db_transaction_load(c2_tid);
812 if (!config_transaction2) {
813 vty_out(vty, "%% Transaction %u does not exist\n\n",
814 (unsigned int)c2_tid);
815 goto exit;
816 }
817 config2 = config_transaction2;
818 }
819
820 if (json)
821 format = NB_CFG_FMT_JSON;
822 else if (xml)
823 format = NB_CFG_FMT_XML;
824 else
825 format = NB_CFG_FMT_CMDS;
826
827 if (translator_family) {
828 translator = yang_translator_find(translator_family);
829 if (!translator) {
830 vty_out(vty, "%% Module translator \"%s\" not found\n",
831 translator_family);
832 goto exit;
833 }
834 }
835
836 ret = nb_cli_show_config_compare(vty, config1, config2, format,
837 translator);
838exit:
839 if (config_transaction1)
840 nb_config_free(config_transaction1);
841 if (config_transaction2)
842 nb_config_free(config_transaction2);
843
844 return ret;
845}
846
847/*
848 * Stripped down version of the "show configuration compare" command.
849 * The "candidate" option is not present so the command can be installed in
850 * the enable node.
851 */
852ALIAS (show_config_compare,
853 show_config_compare_without_candidate_cmd,
854 "show configuration compare\
855 <\
856 running$c1_running\
857 |transaction (1-4294967296)$c1_tid\
858 >\
859 <\
860 running$c2_running\
861 |transaction (1-4294967296)$c2_tid\
862 >\
863 [<json$json|xml$xml> [translate WORD$translator_family]]",
864 SHOW_STR
865 "Configuration information\n"
866 "Compare two different configurations\n"
867 "Running configuration\n"
868 "Configuration transaction\n"
869 "Transaction ID\n"
870 "Running configuration\n"
871 "Configuration transaction\n"
872 "Transaction ID\n"
873 "Change output format to JSON\n"
874 "Change output format to XML\n"
875 "Translate output\n"
876 "YANG module translator\n")
877
878DEFPY (clear_config_transactions,
879 clear_config_transactions_cmd,
880 "clear configuration transactions oldest (1-100)$n",
881 CLEAR_STR
882 "Configuration activity\n"
883 "Delete transactions from the transactions log\n"
884 "Delete oldest <n> transactions\n"
885 "Number of transactions to delete\n")
886{
887#ifdef HAVE_CONFIG_ROLLBACKS
888 if (nb_db_clear_transactions(n) != NB_OK) {
889 vty_out(vty, "%% Failed to delete transactions.\n\n");
890 return CMD_WARNING;
891 }
892#else
893 vty_out(vty,
894 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
895#endif /* HAVE_CONFIG_ROLLBACKS */
896
897 return CMD_SUCCESS;
898}
899
900DEFPY (config_database_max_transactions,
901 config_database_max_transactions_cmd,
902 "configuration database max-transactions (1-100)$max",
903 "Configuration related settings\n"
904 "Configuration database\n"
905 "Set the maximum number of transactions to store\n"
906 "Number of transactions\n")
907{
908#ifdef HAVE_CONFIG_ROLLBACKS
909 if (nb_db_set_max_transactions(max) != NB_OK) {
910 vty_out(vty,
911 "%% Failed to update the maximum number of transactions.\n\n");
912 return CMD_WARNING;
913 }
914 vty_out(vty,
915 "%% Maximum number of transactions updated successfully.\n\n");
916#else
917 vty_out(vty,
918 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
919#endif /* HAVE_CONFIG_ROLLBACKS */
920
921 return CMD_SUCCESS;
922}
923
924DEFPY (yang_module_translator_load,
925 yang_module_translator_load_cmd,
926 "yang module-translator load FILENAME$filename",
927 "YANG related settings\n"
928 "YANG module translator\n"
929 "Load YANG module translator\n"
930 "File name (full path)\n")
931{
932 struct yang_translator *translator;
933
934 translator = yang_translator_load(filename);
935 if (!translator) {
936 vty_out(vty, "%% Failed to load \"%s\"\n\n", filename);
937 vty_out(vty, "Please check the logs for more details.\n");
938 return CMD_WARNING;
939 }
940
941 vty_out(vty, "%% Module translator \"%s\" loaded successfully.\n\n",
942 translator->family);
943
944 return CMD_SUCCESS;
945}
946
947DEFPY (yang_module_translator_unload_family,
948 yang_module_translator_unload_cmd,
949 "yang module-translator unload WORD$translator_family",
950 "YANG related settings\n"
951 "YANG module translator\n"
952 "Unload YANG module translator\n"
953 "Name of the module translator\n")
954{
955 struct yang_translator *translator;
956
957 translator = yang_translator_find(translator_family);
958 if (!translator) {
959 vty_out(vty, "%% Module translator \"%s\" not found\n",
960 translator_family);
961 return CMD_WARNING;
962 }
963
964 yang_translator_unload(translator);
965
966 return CMD_SUCCESS;
967}
968
969#ifdef HAVE_CONFIG_ROLLBACKS
970static void nb_cli_show_transactions_cb(void *arg, int transaction_id,
971 const char *client_name,
972 const char *date, const char *comment)
973{
974 struct ttable *tt = arg;
975
976 ttable_add_row(tt, "%d|%s|%s|%s", transaction_id, client_name, date,
977 comment);
978}
979
980static int nb_cli_show_transactions(struct vty *vty)
981{
982 struct ttable *tt;
983
984 /* Prepare table. */
985 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
986 ttable_add_row(tt, "Transaction ID|Client|Date|Comment");
987 tt->style.cell.rpad = 2;
988 tt->style.corner = '+';
989 ttable_restyle(tt);
990 ttable_rowseps(tt, 0, BOTTOM, true, '-');
991
992 /* Fetch transactions from the northbound database. */
993 if (nb_db_transactions_iterate(nb_cli_show_transactions_cb, tt)
994 != NB_OK) {
995 vty_out(vty,
996 "%% Failed to fetch configuration transactions.\n");
997 return CMD_WARNING;
998 }
999
1000 /* Dump the generated table. */
1001 if (tt->nrows > 1) {
1002 char *table;
1003
1004 table = ttable_dump(tt, "\n");
1005 vty_out(vty, "%s\n", table);
1006 XFREE(MTYPE_TMP, table);
1007 } else
1008 vty_out(vty, "No configuration transactions to display.\n\n");
1009
1010 ttable_del(tt);
1011
1012 return CMD_SUCCESS;
1013}
1014#endif /* HAVE_CONFIG_ROLLBACKS */
1015
1016DEFPY (show_config_transaction,
1017 show_config_transaction_cmd,
1018 "show configuration transaction\
1019 [\
1020 (1-4294967296)$transaction_id\
1021 [<json$json|xml$xml> [translate WORD$translator_family]]\
1022 [<\
1023 with-defaults$with_defaults\
1024 |changes$changes\
1025 >]\
1026 ]",
1027 SHOW_STR
1028 "Configuration information\n"
1029 "Configuration transaction\n"
1030 "Transaction ID\n"
1031 "Change output format to JSON\n"
1032 "Change output format to XML\n"
1033 "Translate output\n"
1034 "YANG module translator\n"
1035 "Show default values\n"
1036 "Show changes compared to the previous transaction\n")
1037{
1038#ifdef HAVE_CONFIG_ROLLBACKS
1039 if (transaction_id) {
1040 struct nb_config *config;
1041 enum nb_cfg_format format;
1042 struct yang_translator *translator = NULL;
1043
1044 if (json)
1045 format = NB_CFG_FMT_JSON;
1046 else if (xml)
1047 format = NB_CFG_FMT_XML;
1048 else
1049 format = NB_CFG_FMT_CMDS;
1050
1051 if (translator_family) {
1052 translator = yang_translator_find(translator_family);
1053 if (!translator) {
1054 vty_out(vty,
1055 "%% Module translator \"%s\" not found\n",
1056 translator_family);
1057 return CMD_WARNING;
1058 }
1059 }
1060
1061 config = nb_db_transaction_load(transaction_id);
1062 if (!config) {
1063 vty_out(vty, "%% Transaction %u does not exist.\n\n",
1064 (unsigned int)transaction_id);
1065 return CMD_WARNING;
1066 }
1067
1068 if (changes) {
1069 struct nb_config *prev_config;
1070 int ret;
1071
1072 /* NOTE: this can be NULL. */
1073 prev_config =
1074 nb_db_transaction_load(transaction_id - 1);
1075
1076 ret = nb_cli_show_config_compare(
1077 vty, prev_config, config, format, translator);
1078 if (prev_config)
1079 nb_config_free(prev_config);
1080 nb_config_free(config);
1081
1082 return ret;
1083 }
1084
1085 nb_cli_show_config(vty, config, format, translator,
1086 !!with_defaults);
1087 nb_config_free(config);
1088
1089 return CMD_SUCCESS;
1090 }
1091
1092 return nb_cli_show_transactions(vty);
1093#else
1094 vty_out(vty,
1095 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1096 return CMD_WARNING;
1097#endif /* HAVE_CONFIG_ROLLBACKS */
1098}
1099
1100DEFPY (show_yang_module,
1101 show_yang_module_cmd,
1102 "show yang module [module-translator WORD$translator_family]",
1103 SHOW_STR
1104 "YANG information\n"
1105 "Show loaded modules\n"
1106 "YANG module translator\n"
1107 "YANG module translator\n")
1108{
1109 struct ly_ctx *ly_ctx;
1110 struct yang_translator *translator = NULL;
1111 const struct lys_module *module;
1112 struct ttable *tt;
1113 uint32_t idx = 0;
1114
1115 if (translator_family) {
1116 translator = yang_translator_find(translator_family);
1117 if (!translator) {
1118 vty_out(vty, "%% Module translator \"%s\" not found\n",
1119 translator_family);
1120 return CMD_WARNING;
1121 }
1122 ly_ctx = translator->ly_ctx;
1123 } else
1124 ly_ctx = ly_native_ctx;
1125
1126 /* Prepare table. */
1127 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1128 ttable_add_row(tt, "Module|Version|Revision|Flags|Namespace");
1129 tt->style.cell.rpad = 2;
1130 tt->style.corner = '+';
1131 ttable_restyle(tt);
1132 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1133
1134 while ((module = ly_ctx_get_module_iter(ly_ctx, &idx))) {
1135 char flags[8];
1136
1137 snprintf(flags, sizeof(flags), "%c%c",
1138 module->implemented ? 'I' : ' ',
1139 (module->deviated == 1) ? 'D' : ' ');
1140
1141 ttable_add_row(tt, "%s|%s|%s|%s|%s", module->name,
1142 (module->version == 2) ? "1.1" : "1.0",
1143 (module->rev_size > 0) ? module->rev[0].date
1144 : "-",
1145 flags, module->ns);
1146 }
1147
1148 /* Dump the generated table. */
1149 if (tt->nrows > 1) {
1150 char *table;
1151
1152 vty_out(vty, " Flags: I - Implemented, D - Deviated\n\n");
1153
1154 table = ttable_dump(tt, "\n");
1155 vty_out(vty, "%s\n", table);
1156 XFREE(MTYPE_TMP, table);
1157 } else
1158 vty_out(vty, "No YANG modules to display.\n\n");
1159
1160 ttable_del(tt);
1161
1162 return CMD_SUCCESS;
1163}
1164
1165DEFPY (show_yang_module_detail,
1166 show_yang_module_detail_cmd,
1167 "show yang module\
1168 [module-translator WORD$translator_family]\
1169 WORD$module_name <summary|tree$tree|yang$yang|yin$yin>",
1170 SHOW_STR
1171 "YANG information\n"
1172 "Show loaded modules\n"
1173 "YANG module translator\n"
1174 "YANG module translator\n"
1175 "Module name\n"
1176 "Display summary information about the module\n"
1177 "Display module in the tree (RFC 8340) format\n"
1178 "Display module in the YANG format\n"
1179 "Display module in the YIN format\n")
1180{
1181 struct ly_ctx *ly_ctx;
1182 struct yang_translator *translator = NULL;
1183 const struct lys_module *module;
1184 LYS_OUTFORMAT format;
1185 char *strp;
1186
1187 if (translator_family) {
1188 translator = yang_translator_find(translator_family);
1189 if (!translator) {
1190 vty_out(vty, "%% Module translator \"%s\" not found\n",
1191 translator_family);
1192 return CMD_WARNING;
1193 }
1194 ly_ctx = translator->ly_ctx;
1195 } else
1196 ly_ctx = ly_native_ctx;
1197
1198 module = ly_ctx_get_module(ly_ctx, module_name, NULL, 0);
1199 if (!module) {
1200 vty_out(vty, "%% Module \"%s\" not found\n", module_name);
1201 return CMD_WARNING;
1202 }
1203
1204 if (yang)
1205 format = LYS_OUT_YANG;
1206 else if (yin)
1207 format = LYS_OUT_YIN;
1208 else if (tree)
1209 format = LYS_OUT_TREE;
1210 else
1211 format = LYS_OUT_INFO;
1212
1213 if (lys_print_mem(&strp, module, format, NULL, 0, 0) == 0) {
1214 vty_out(vty, "%s\n", strp);
1215 free(strp);
1216 } else {
1217 /* Unexpected. */
1218 vty_out(vty, "%% Error generating module information\n");
1219 return CMD_WARNING;
1220 }
1221
1222 return CMD_SUCCESS;
1223}
1224
1225DEFPY (show_yang_module_translator,
1226 show_yang_module_translator_cmd,
1227 "show yang module-translator",
1228 SHOW_STR
1229 "YANG information\n"
1230 "Show loaded YANG module translators\n")
1231{
1232 struct yang_translator *translator;
1233 struct ttable *tt;
1234
1235 /* Prepare table. */
1236 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1237 ttable_add_row(tt, "Family|Module|Deviations|Coverage (%%)");
1238 tt->style.cell.rpad = 2;
1239 tt->style.corner = '+';
1240 ttable_restyle(tt);
1241 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1242
1243 RB_FOREACH (translator, yang_translators, &yang_translators) {
1244 struct yang_tmodule *tmodule;
1245 struct listnode *ln;
1246
1247 for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
1248 ttable_add_row(tt, "%s|%s|%s|%.2f", translator->family,
1249 tmodule->module->name,
1250 tmodule->deviations->name,
1251 tmodule->coverage);
1252 }
1253 }
1254
1255 /* Dump the generated table. */
1256 if (tt->nrows > 1) {
1257 char *table;
1258
1259 table = ttable_dump(tt, "\n");
1260 vty_out(vty, "%s\n", table);
1261 XFREE(MTYPE_TMP, table);
1262 } else
1263 vty_out(vty, "No YANG module translators to display.\n\n");
1264
1265 ttable_del(tt);
1266
1267 return CMD_SUCCESS;
1268}
1269
1270#ifdef HAVE_CONFIG_ROLLBACKS
1271static int nb_cli_rollback_configuration(struct vty *vty,
1272 uint32_t transaction_id)
1273{
1274 struct nb_config *candidate;
1275 char comment[80];
1276 int ret;
1277
1278 candidate = nb_db_transaction_load(transaction_id);
1279 if (!candidate) {
1280 vty_out(vty, "%% Transaction %u does not exist.\n\n",
1281 transaction_id);
1282 return CMD_WARNING;
1283 }
1284
1285 snprintf(comment, sizeof(comment), "Rollback to transaction %u",
1286 transaction_id);
1287
1288 ret = nb_candidate_commit(candidate, NB_CLIENT_CLI, true, comment,
1289 NULL);
1290 nb_config_free(candidate);
1291 switch (ret) {
1292 case NB_OK:
1293 vty_out(vty,
1294 "%% Configuration was successfully rolled back.\n\n");
1295 return CMD_SUCCESS;
1296 case NB_ERR_NO_CHANGES:
1297 vty_out(vty,
1298 "%% Aborting - no configuration changes detected.\n\n");
1299 return CMD_WARNING;
1300 default:
1301 vty_out(vty, "%% Rollback failed.\n\n");
1302 vty_out(vty, "Please check the logs for more details.\n");
1303 return CMD_WARNING;
1304 }
1305}
1306#endif /* HAVE_CONFIG_ROLLBACKS */
1307
1308DEFPY (rollback_config,
1309 rollback_config_cmd,
1310 "rollback configuration (1-4294967296)$transaction_id",
1311 "Rollback to a previous state\n"
1312 "Running configuration\n"
1313 "Transaction ID\n")
1314{
1315#ifdef HAVE_CONFIG_ROLLBACKS
1316 return nb_cli_rollback_configuration(vty, transaction_id);
1317#else
1318 vty_out(vty,
1319 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1320 return CMD_SUCCESS;
1321#endif /* HAVE_CONFIG_ROLLBACKS */
1322}
1323
1324/* Debug CLI commands. */
1325DEFUN (debug_nb,
1326 debug_nb_cmd,
1327 "debug northbound",
1328 DEBUG_STR
1329 "Northbound Debugging\n")
1330{
1331 debug_northbound = 1;
1332
1333 return CMD_SUCCESS;
1334}
1335
1336DEFUN (no_debug_nb,
1337 no_debug_nb_cmd,
1338 "no debug northbound",
1339 NO_STR DEBUG_STR
1340 "Northbound Debugging\n")
1341{
1342 debug_northbound = 0;
1343
1344 return CMD_SUCCESS;
1345}
1346
1347static int nb_debug_config_write(struct vty *vty)
1348{
1349 if (debug_northbound)
1350 vty_out(vty, "debug northbound\n");
1351
1352 return 1;
1353}
1354
1355static struct cmd_node nb_debug_node = {NORTHBOUND_DEBUG_NODE, "", 1};
1356
1357void nb_cli_install_default(int node)
1358{
1359 if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL)
1360 return;
1361
1362 install_element(node, &config_commit_cmd);
1363 install_element(node, &config_commit_comment_cmd);
1364 install_element(node, &config_commit_check_cmd);
1365 install_element(node, &config_update_cmd);
1366 install_element(node, &config_discard_cmd);
1367 install_element(node, &show_config_running_cmd);
1368 install_element(node, &show_config_candidate_cmd);
1369 install_element(node, &show_config_compare_cmd);
1370 install_element(node, &show_config_transaction_cmd);
1371}
1372
1373/* YANG module autocomplete. */
1374static void yang_module_autocomplete(vector comps, struct cmd_token *token)
1375{
1376 const struct lys_module *module;
1377 struct yang_translator *module_tr;
1378 uint32_t idx;
1379
1380 idx = 0;
1381 while ((module = ly_ctx_get_module_iter(ly_native_ctx, &idx)))
1382 vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module->name));
1383
1384 RB_FOREACH (module_tr, yang_translators, &yang_translators) {
1385 idx = 0;
1386 while ((module = ly_ctx_get_module_iter(module_tr->ly_ctx,
1387 &idx)))
1388 vector_set(comps,
1389 XSTRDUP(MTYPE_COMPLETION, module->name));
1390 }
1391}
1392
1393/* YANG module translator autocomplete. */
1394static void yang_translator_autocomplete(vector comps, struct cmd_token *token)
1395{
1396 struct yang_translator *module_tr;
1397
1398 RB_FOREACH (module_tr, yang_translators, &yang_translators)
1399 vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module_tr->family));
1400}
1401
1402static const struct cmd_variable_handler yang_var_handlers[] = {
1403 {.varname = "module_name", .completions = yang_module_autocomplete},
1404 {.varname = "translator_family",
1405 .completions = yang_translator_autocomplete},
1406 {.completions = NULL}};
1407
1408void nb_cli_init(void)
1409{
1410 /* Initialize the shared candidate configuration. */
1411 vty_shared_candidate_config = nb_config_new(NULL);
1412
1413 /* Install debug commands */
1414 install_node(&nb_debug_node, nb_debug_config_write);
1415 install_element(ENABLE_NODE, &debug_nb_cmd);
1416 install_element(ENABLE_NODE, &no_debug_nb_cmd);
1417 install_element(CONFIG_NODE, &debug_nb_cmd);
1418 install_element(CONFIG_NODE, &no_debug_nb_cmd);
1419
1420 /* Install commands specific to the transaction-base mode. */
1421 if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
1422 install_element(ENABLE_NODE, &config_exclusive_cmd);
1423 install_element(ENABLE_NODE, &config_private_cmd);
1424 install_element(ENABLE_NODE, &show_config_running_cmd);
1425 install_element(ENABLE_NODE,
1426 &show_config_compare_without_candidate_cmd);
1427 install_element(ENABLE_NODE, &show_config_transaction_cmd);
1428 install_element(ENABLE_NODE, &rollback_config_cmd);
1429 install_element(ENABLE_NODE, &clear_config_transactions_cmd);
1430
1431 install_element(CONFIG_NODE, &config_load_cmd);
1432 install_element(CONFIG_NODE,
1433 &config_database_max_transactions_cmd);
1434 }
1435
1436 /* Other commands. */
1437 install_element(CONFIG_NODE, &yang_module_translator_load_cmd);
1438 install_element(CONFIG_NODE, &yang_module_translator_unload_cmd);
1439 install_element(ENABLE_NODE, &show_yang_module_cmd);
1440 install_element(ENABLE_NODE, &show_yang_module_detail_cmd);
1441 install_element(ENABLE_NODE, &show_yang_module_translator_cmd);
1442 cmd_variable_handler_register(yang_var_handlers);
1443}
1444
1445void nb_cli_terminate(void)
1446{
1447 nb_config_free(vty_shared_candidate_config);
1448}