]> git.proxmox.com Git - mirror_frr.git/blame - lib/northbound_cli.c
*: remove the configuration lock from all daemons
[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{
f344c66e 495 return vty_config_enter(vty, true, true);
1c2facd1
RW
496}
497
498/* Configure using a private candidate configuration. */
499DEFUN (config_private,
500 config_private_cmd,
501 "configure private",
502 "Configuration from vty interface\n"
503 "Configure using a private candidate configuration\n")
504{
f344c66e 505 return vty_config_enter(vty, true, false);
1c2facd1
RW
506}
507
508DEFPY (config_commit,
509 config_commit_cmd,
510 "commit [force$force]",
511 "Commit changes into the running configuration\n"
512 "Force commit even if the candidate is outdated\n")
513{
514 return nb_cli_commit(vty, !!force, NULL);
515}
516
517DEFPY (config_commit_comment,
518 config_commit_comment_cmd,
519 "commit [force$force] comment LINE...",
520 "Commit changes into the running configuration\n"
521 "Force commit even if the candidate is outdated\n"
522 "Assign a comment to this commit\n"
523 "Comment for this commit (Max 80 characters)\n")
524{
525 char *comment;
526 int idx = 0;
527 int ret;
528
529 argv_find(argv, argc, "LINE", &idx);
530 comment = argv_concat(argv, argc, idx);
531 ret = nb_cli_commit(vty, !!force, comment);
532 XFREE(MTYPE_TMP, comment);
533
534 return ret;
535}
536
537DEFPY (config_commit_check,
538 config_commit_check_cmd,
539 "commit check",
540 "Commit changes into the running configuration\n"
541 "Check if the configuration changes are valid\n")
542{
543 int ret;
544
545 ret = nb_candidate_validate(vty->candidate_config);
546 if (ret != NB_OK) {
547 vty_out(vty,
548 "%% Failed to validate candidate configuration.\n\n");
549 vty_show_libyang_errors(vty, ly_native_ctx);
550 return CMD_WARNING;
551 }
552
553 vty_out(vty, "%% Candidate configuration validated successfully.\n\n");
554
555 return CMD_SUCCESS;
556}
557
558DEFPY (config_update,
559 config_update_cmd,
560 "update",
561 "Update candidate configuration\n")
562{
563 if (!nb_candidate_needs_update(vty->candidate_config)) {
564 vty_out(vty, "%% Update is not necessary.\n\n");
565 return CMD_SUCCESS;
566 }
567
568 if (nb_candidate_update(vty->candidate_config) != NB_OK) {
569 vty_out(vty,
570 "%% Failed to update the candidate configuration.\n\n");
571 vty_out(vty, "Please check the logs for more details.\n");
572 return CMD_WARNING;
573 }
574
575 nb_config_replace(vty->candidate_config_base, running_config, true);
576
577 vty_out(vty, "%% Candidate configuration updated successfully.\n\n");
578
579 return CMD_SUCCESS;
580}
581
582DEFPY (config_discard,
583 config_discard_cmd,
584 "discard",
585 "Discard changes in the candidate configuration\n")
586{
587 nb_config_replace(vty->candidate_config, vty->candidate_config_base,
588 true);
589
590 return CMD_SUCCESS;
591}
592
593DEFPY (config_load,
594 config_load_cmd,
595 "configuration load\
596 <\
597 file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
598 |transaction (1-4294967296)$tid\
599 >\
600 [replace$replace]",
601 "Configuration related settings\n"
602 "Load configuration into candidate\n"
603 "Load configuration file into candidate\n"
604 "Load configuration file in JSON format\n"
605 "Load configuration file in XML format\n"
606 "Translate configuration file\n"
607 "YANG module translator\n"
608 "Configuration file name (full path)\n"
609 "Load configuration from transaction into candidate\n"
610 "Transaction ID\n"
611 "Replace instead of merge\n")
612{
613 if (filename) {
614 enum nb_cfg_format format;
615 struct yang_translator *translator = NULL;
616
617 if (json)
618 format = NB_CFG_FMT_JSON;
619 else if (xml)
620 format = NB_CFG_FMT_XML;
621 else
622 format = NB_CFG_FMT_CMDS;
623
624 if (translator_family) {
625 translator = yang_translator_find(translator_family);
626 if (!translator) {
627 vty_out(vty,
628 "%% Module translator \"%s\" not found\n",
629 translator_family);
630 return CMD_WARNING;
631 }
632 }
633
634 return nb_cli_candidate_load_file(vty, format, translator,
635 filename, !!replace);
636 }
637
638 return nb_cli_candidate_load_transaction(vty, tid, !!replace);
639}
640
641DEFPY (show_config_running,
642 show_config_running_cmd,
643 "show configuration running\
644 [<json$json|xml$xml> [translate WORD$translator_family]]\
645 [with-defaults$with_defaults]",
646 SHOW_STR
647 "Configuration information\n"
648 "Running configuration\n"
649 "Change output format to JSON\n"
650 "Change output format to XML\n"
651 "Translate output\n"
652 "YANG module translator\n"
653 "Show default values\n")
654
655{
656 enum nb_cfg_format format;
657 struct yang_translator *translator = NULL;
658
659 if (json)
660 format = NB_CFG_FMT_JSON;
661 else if (xml)
662 format = NB_CFG_FMT_XML;
663 else
664 format = NB_CFG_FMT_CMDS;
665
666 if (translator_family) {
667 translator = yang_translator_find(translator_family);
668 if (!translator) {
669 vty_out(vty, "%% Module translator \"%s\" not found\n",
670 translator_family);
671 return CMD_WARNING;
672 }
673 }
674
675 nb_cli_show_config(vty, running_config, format, translator,
676 !!with_defaults);
677
678 return CMD_SUCCESS;
679}
680
681DEFPY (show_config_candidate,
682 show_config_candidate_cmd,
683 "show configuration candidate\
684 [<json$json|xml$xml> [translate WORD$translator_family]]\
685 [<\
686 with-defaults$with_defaults\
687 |changes$changes\
688 >]",
689 SHOW_STR
690 "Configuration information\n"
691 "Candidate configuration\n"
692 "Change output format to JSON\n"
693 "Change output format to XML\n"
694 "Translate output\n"
695 "YANG module translator\n"
696 "Show default values\n"
697 "Show changes applied in the candidate configuration\n")
698
699{
700 enum nb_cfg_format format;
701 struct yang_translator *translator = NULL;
702
703 if (json)
704 format = NB_CFG_FMT_JSON;
705 else if (xml)
706 format = NB_CFG_FMT_XML;
707 else
708 format = NB_CFG_FMT_CMDS;
709
710 if (translator_family) {
711 translator = yang_translator_find(translator_family);
712 if (!translator) {
713 vty_out(vty, "%% Module translator \"%s\" not found\n",
714 translator_family);
715 return CMD_WARNING;
716 }
717 }
718
719 if (changes)
720 return nb_cli_show_config_compare(
721 vty, vty->candidate_config_base, vty->candidate_config,
722 format, translator);
723
724 nb_cli_show_config(vty, vty->candidate_config, format, translator,
725 !!with_defaults);
726
727 return CMD_SUCCESS;
728}
729
730DEFPY (show_config_compare,
731 show_config_compare_cmd,
732 "show configuration compare\
733 <\
734 candidate$c1_candidate\
735 |running$c1_running\
736 |transaction (1-4294967296)$c1_tid\
737 >\
738 <\
739 candidate$c2_candidate\
740 |running$c2_running\
741 |transaction (1-4294967296)$c2_tid\
742 >\
743 [<json$json|xml$xml> [translate WORD$translator_family]]",
744 SHOW_STR
745 "Configuration information\n"
746 "Compare two different configurations\n"
747 "Candidate configuration\n"
748 "Running configuration\n"
749 "Configuration transaction\n"
750 "Transaction ID\n"
751 "Candidate configuration\n"
752 "Running configuration\n"
753 "Configuration transaction\n"
754 "Transaction ID\n"
755 "Change output format to JSON\n"
756 "Change output format to XML\n"
757 "Translate output\n"
758 "YANG module translator\n")
759{
760 enum nb_cfg_format format;
761 struct yang_translator *translator = NULL;
762 struct nb_config *config1, *config_transaction1 = NULL;
763 struct nb_config *config2, *config_transaction2 = NULL;
764 int ret = CMD_WARNING;
765
766 if (c1_candidate)
767 config1 = vty->candidate_config;
768 else if (c1_running)
769 config1 = running_config;
770 else {
771 config_transaction1 = nb_db_transaction_load(c1_tid);
772 if (!config_transaction1) {
773 vty_out(vty, "%% Transaction %u does not exist\n\n",
774 (unsigned int)c1_tid);
775 goto exit;
776 }
777 config1 = config_transaction1;
778 }
779
780 if (c2_candidate)
781 config2 = vty->candidate_config;
782 else if (c2_running)
783 config2 = running_config;
784 else {
785 config_transaction2 = nb_db_transaction_load(c2_tid);
786 if (!config_transaction2) {
787 vty_out(vty, "%% Transaction %u does not exist\n\n",
788 (unsigned int)c2_tid);
789 goto exit;
790 }
791 config2 = config_transaction2;
792 }
793
794 if (json)
795 format = NB_CFG_FMT_JSON;
796 else if (xml)
797 format = NB_CFG_FMT_XML;
798 else
799 format = NB_CFG_FMT_CMDS;
800
801 if (translator_family) {
802 translator = yang_translator_find(translator_family);
803 if (!translator) {
804 vty_out(vty, "%% Module translator \"%s\" not found\n",
805 translator_family);
806 goto exit;
807 }
808 }
809
810 ret = nb_cli_show_config_compare(vty, config1, config2, format,
811 translator);
812exit:
813 if (config_transaction1)
814 nb_config_free(config_transaction1);
815 if (config_transaction2)
816 nb_config_free(config_transaction2);
817
818 return ret;
819}
820
821/*
822 * Stripped down version of the "show configuration compare" command.
823 * The "candidate" option is not present so the command can be installed in
824 * the enable node.
825 */
826ALIAS (show_config_compare,
827 show_config_compare_without_candidate_cmd,
828 "show configuration compare\
829 <\
830 running$c1_running\
831 |transaction (1-4294967296)$c1_tid\
832 >\
833 <\
834 running$c2_running\
835 |transaction (1-4294967296)$c2_tid\
836 >\
837 [<json$json|xml$xml> [translate WORD$translator_family]]",
838 SHOW_STR
839 "Configuration information\n"
840 "Compare two different configurations\n"
841 "Running configuration\n"
842 "Configuration transaction\n"
843 "Transaction ID\n"
844 "Running configuration\n"
845 "Configuration transaction\n"
846 "Transaction ID\n"
847 "Change output format to JSON\n"
848 "Change output format to XML\n"
849 "Translate output\n"
850 "YANG module translator\n")
851
852DEFPY (clear_config_transactions,
853 clear_config_transactions_cmd,
854 "clear configuration transactions oldest (1-100)$n",
855 CLEAR_STR
856 "Configuration activity\n"
857 "Delete transactions from the transactions log\n"
858 "Delete oldest <n> transactions\n"
859 "Number of transactions to delete\n")
860{
861#ifdef HAVE_CONFIG_ROLLBACKS
862 if (nb_db_clear_transactions(n) != NB_OK) {
863 vty_out(vty, "%% Failed to delete transactions.\n\n");
864 return CMD_WARNING;
865 }
866#else
867 vty_out(vty,
868 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
869#endif /* HAVE_CONFIG_ROLLBACKS */
870
871 return CMD_SUCCESS;
872}
873
874DEFPY (config_database_max_transactions,
875 config_database_max_transactions_cmd,
876 "configuration database max-transactions (1-100)$max",
877 "Configuration related settings\n"
878 "Configuration database\n"
879 "Set the maximum number of transactions to store\n"
880 "Number of transactions\n")
881{
882#ifdef HAVE_CONFIG_ROLLBACKS
883 if (nb_db_set_max_transactions(max) != NB_OK) {
884 vty_out(vty,
885 "%% Failed to update the maximum number of transactions.\n\n");
886 return CMD_WARNING;
887 }
888 vty_out(vty,
889 "%% Maximum number of transactions updated successfully.\n\n");
890#else
891 vty_out(vty,
892 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
893#endif /* HAVE_CONFIG_ROLLBACKS */
894
895 return CMD_SUCCESS;
896}
897
898DEFPY (yang_module_translator_load,
899 yang_module_translator_load_cmd,
900 "yang module-translator load FILENAME$filename",
901 "YANG related settings\n"
902 "YANG module translator\n"
903 "Load YANG module translator\n"
904 "File name (full path)\n")
905{
906 struct yang_translator *translator;
907
908 translator = yang_translator_load(filename);
909 if (!translator) {
910 vty_out(vty, "%% Failed to load \"%s\"\n\n", filename);
911 vty_out(vty, "Please check the logs for more details.\n");
912 return CMD_WARNING;
913 }
914
915 vty_out(vty, "%% Module translator \"%s\" loaded successfully.\n\n",
916 translator->family);
917
918 return CMD_SUCCESS;
919}
920
921DEFPY (yang_module_translator_unload_family,
922 yang_module_translator_unload_cmd,
923 "yang module-translator unload WORD$translator_family",
924 "YANG related settings\n"
925 "YANG module translator\n"
926 "Unload YANG module translator\n"
927 "Name of the module translator\n")
928{
929 struct yang_translator *translator;
930
931 translator = yang_translator_find(translator_family);
932 if (!translator) {
933 vty_out(vty, "%% Module translator \"%s\" not found\n",
934 translator_family);
935 return CMD_WARNING;
936 }
937
938 yang_translator_unload(translator);
939
940 return CMD_SUCCESS;
941}
942
943#ifdef HAVE_CONFIG_ROLLBACKS
944static void nb_cli_show_transactions_cb(void *arg, int transaction_id,
945 const char *client_name,
946 const char *date, const char *comment)
947{
948 struct ttable *tt = arg;
949
950 ttable_add_row(tt, "%d|%s|%s|%s", transaction_id, client_name, date,
951 comment);
952}
953
954static int nb_cli_show_transactions(struct vty *vty)
955{
956 struct ttable *tt;
957
958 /* Prepare table. */
959 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
960 ttable_add_row(tt, "Transaction ID|Client|Date|Comment");
961 tt->style.cell.rpad = 2;
962 tt->style.corner = '+';
963 ttable_restyle(tt);
964 ttable_rowseps(tt, 0, BOTTOM, true, '-');
965
966 /* Fetch transactions from the northbound database. */
967 if (nb_db_transactions_iterate(nb_cli_show_transactions_cb, tt)
968 != NB_OK) {
969 vty_out(vty,
970 "%% Failed to fetch configuration transactions.\n");
971 return CMD_WARNING;
972 }
973
974 /* Dump the generated table. */
975 if (tt->nrows > 1) {
976 char *table;
977
978 table = ttable_dump(tt, "\n");
979 vty_out(vty, "%s\n", table);
980 XFREE(MTYPE_TMP, table);
981 } else
982 vty_out(vty, "No configuration transactions to display.\n\n");
983
984 ttable_del(tt);
985
986 return CMD_SUCCESS;
987}
988#endif /* HAVE_CONFIG_ROLLBACKS */
989
990DEFPY (show_config_transaction,
991 show_config_transaction_cmd,
992 "show configuration transaction\
993 [\
994 (1-4294967296)$transaction_id\
995 [<json$json|xml$xml> [translate WORD$translator_family]]\
996 [<\
997 with-defaults$with_defaults\
998 |changes$changes\
999 >]\
1000 ]",
1001 SHOW_STR
1002 "Configuration information\n"
1003 "Configuration transaction\n"
1004 "Transaction ID\n"
1005 "Change output format to JSON\n"
1006 "Change output format to XML\n"
1007 "Translate output\n"
1008 "YANG module translator\n"
1009 "Show default values\n"
1010 "Show changes compared to the previous transaction\n")
1011{
1012#ifdef HAVE_CONFIG_ROLLBACKS
1013 if (transaction_id) {
1014 struct nb_config *config;
1015 enum nb_cfg_format format;
1016 struct yang_translator *translator = NULL;
1017
1018 if (json)
1019 format = NB_CFG_FMT_JSON;
1020 else if (xml)
1021 format = NB_CFG_FMT_XML;
1022 else
1023 format = NB_CFG_FMT_CMDS;
1024
1025 if (translator_family) {
1026 translator = yang_translator_find(translator_family);
1027 if (!translator) {
1028 vty_out(vty,
1029 "%% Module translator \"%s\" not found\n",
1030 translator_family);
1031 return CMD_WARNING;
1032 }
1033 }
1034
1035 config = nb_db_transaction_load(transaction_id);
1036 if (!config) {
1037 vty_out(vty, "%% Transaction %u does not exist.\n\n",
1038 (unsigned int)transaction_id);
1039 return CMD_WARNING;
1040 }
1041
1042 if (changes) {
1043 struct nb_config *prev_config;
1044 int ret;
1045
1046 /* NOTE: this can be NULL. */
1047 prev_config =
1048 nb_db_transaction_load(transaction_id - 1);
1049
1050 ret = nb_cli_show_config_compare(
1051 vty, prev_config, config, format, translator);
1052 if (prev_config)
1053 nb_config_free(prev_config);
1054 nb_config_free(config);
1055
1056 return ret;
1057 }
1058
1059 nb_cli_show_config(vty, config, format, translator,
1060 !!with_defaults);
1061 nb_config_free(config);
1062
1063 return CMD_SUCCESS;
1064 }
1065
1066 return nb_cli_show_transactions(vty);
1067#else
1068 vty_out(vty,
1069 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1070 return CMD_WARNING;
1071#endif /* HAVE_CONFIG_ROLLBACKS */
1072}
1073
1074DEFPY (show_yang_module,
1075 show_yang_module_cmd,
1076 "show yang module [module-translator WORD$translator_family]",
1077 SHOW_STR
1078 "YANG information\n"
1079 "Show loaded modules\n"
1080 "YANG module translator\n"
1081 "YANG module translator\n")
1082{
1083 struct ly_ctx *ly_ctx;
1084 struct yang_translator *translator = NULL;
1085 const struct lys_module *module;
1086 struct ttable *tt;
1087 uint32_t idx = 0;
1088
1089 if (translator_family) {
1090 translator = yang_translator_find(translator_family);
1091 if (!translator) {
1092 vty_out(vty, "%% Module translator \"%s\" not found\n",
1093 translator_family);
1094 return CMD_WARNING;
1095 }
1096 ly_ctx = translator->ly_ctx;
1097 } else
1098 ly_ctx = ly_native_ctx;
1099
1100 /* Prepare table. */
1101 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1102 ttable_add_row(tt, "Module|Version|Revision|Flags|Namespace");
1103 tt->style.cell.rpad = 2;
1104 tt->style.corner = '+';
1105 ttable_restyle(tt);
1106 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1107
1108 while ((module = ly_ctx_get_module_iter(ly_ctx, &idx))) {
1109 char flags[8];
1110
1111 snprintf(flags, sizeof(flags), "%c%c",
1112 module->implemented ? 'I' : ' ',
1113 (module->deviated == 1) ? 'D' : ' ');
1114
1115 ttable_add_row(tt, "%s|%s|%s|%s|%s", module->name,
1116 (module->version == 2) ? "1.1" : "1.0",
1117 (module->rev_size > 0) ? module->rev[0].date
1118 : "-",
1119 flags, module->ns);
1120 }
1121
1122 /* Dump the generated table. */
1123 if (tt->nrows > 1) {
1124 char *table;
1125
1126 vty_out(vty, " Flags: I - Implemented, D - Deviated\n\n");
1127
1128 table = ttable_dump(tt, "\n");
1129 vty_out(vty, "%s\n", table);
1130 XFREE(MTYPE_TMP, table);
1131 } else
1132 vty_out(vty, "No YANG modules to display.\n\n");
1133
1134 ttable_del(tt);
1135
1136 return CMD_SUCCESS;
1137}
1138
1139DEFPY (show_yang_module_detail,
1140 show_yang_module_detail_cmd,
1141 "show yang module\
1142 [module-translator WORD$translator_family]\
1143 WORD$module_name <summary|tree$tree|yang$yang|yin$yin>",
1144 SHOW_STR
1145 "YANG information\n"
1146 "Show loaded modules\n"
1147 "YANG module translator\n"
1148 "YANG module translator\n"
1149 "Module name\n"
1150 "Display summary information about the module\n"
1151 "Display module in the tree (RFC 8340) format\n"
1152 "Display module in the YANG format\n"
1153 "Display module in the YIN format\n")
1154{
1155 struct ly_ctx *ly_ctx;
1156 struct yang_translator *translator = NULL;
1157 const struct lys_module *module;
1158 LYS_OUTFORMAT format;
1159 char *strp;
1160
1161 if (translator_family) {
1162 translator = yang_translator_find(translator_family);
1163 if (!translator) {
1164 vty_out(vty, "%% Module translator \"%s\" not found\n",
1165 translator_family);
1166 return CMD_WARNING;
1167 }
1168 ly_ctx = translator->ly_ctx;
1169 } else
1170 ly_ctx = ly_native_ctx;
1171
1172 module = ly_ctx_get_module(ly_ctx, module_name, NULL, 0);
1173 if (!module) {
1174 vty_out(vty, "%% Module \"%s\" not found\n", module_name);
1175 return CMD_WARNING;
1176 }
1177
1178 if (yang)
1179 format = LYS_OUT_YANG;
1180 else if (yin)
1181 format = LYS_OUT_YIN;
1182 else if (tree)
1183 format = LYS_OUT_TREE;
1184 else
1185 format = LYS_OUT_INFO;
1186
1187 if (lys_print_mem(&strp, module, format, NULL, 0, 0) == 0) {
1188 vty_out(vty, "%s\n", strp);
1189 free(strp);
1190 } else {
1191 /* Unexpected. */
1192 vty_out(vty, "%% Error generating module information\n");
1193 return CMD_WARNING;
1194 }
1195
1196 return CMD_SUCCESS;
1197}
1198
1199DEFPY (show_yang_module_translator,
1200 show_yang_module_translator_cmd,
1201 "show yang module-translator",
1202 SHOW_STR
1203 "YANG information\n"
1204 "Show loaded YANG module translators\n")
1205{
1206 struct yang_translator *translator;
1207 struct ttable *tt;
1208
1209 /* Prepare table. */
1210 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1211 ttable_add_row(tt, "Family|Module|Deviations|Coverage (%%)");
1212 tt->style.cell.rpad = 2;
1213 tt->style.corner = '+';
1214 ttable_restyle(tt);
1215 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1216
1217 RB_FOREACH (translator, yang_translators, &yang_translators) {
1218 struct yang_tmodule *tmodule;
1219 struct listnode *ln;
1220
1221 for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
1222 ttable_add_row(tt, "%s|%s|%s|%.2f", translator->family,
1223 tmodule->module->name,
1224 tmodule->deviations->name,
1225 tmodule->coverage);
1226 }
1227 }
1228
1229 /* Dump the generated table. */
1230 if (tt->nrows > 1) {
1231 char *table;
1232
1233 table = ttable_dump(tt, "\n");
1234 vty_out(vty, "%s\n", table);
1235 XFREE(MTYPE_TMP, table);
1236 } else
1237 vty_out(vty, "No YANG module translators to display.\n\n");
1238
1239 ttable_del(tt);
1240
1241 return CMD_SUCCESS;
1242}
1243
1244#ifdef HAVE_CONFIG_ROLLBACKS
1245static int nb_cli_rollback_configuration(struct vty *vty,
1246 uint32_t transaction_id)
1247{
1248 struct nb_config *candidate;
1249 char comment[80];
1250 int ret;
1251
1252 candidate = nb_db_transaction_load(transaction_id);
1253 if (!candidate) {
1254 vty_out(vty, "%% Transaction %u does not exist.\n\n",
1255 transaction_id);
1256 return CMD_WARNING;
1257 }
1258
1259 snprintf(comment, sizeof(comment), "Rollback to transaction %u",
1260 transaction_id);
1261
1262 ret = nb_candidate_commit(candidate, NB_CLIENT_CLI, true, comment,
1263 NULL);
1264 nb_config_free(candidate);
1265 switch (ret) {
1266 case NB_OK:
1267 vty_out(vty,
1268 "%% Configuration was successfully rolled back.\n\n");
1269 return CMD_SUCCESS;
1270 case NB_ERR_NO_CHANGES:
1271 vty_out(vty,
1272 "%% Aborting - no configuration changes detected.\n\n");
1273 return CMD_WARNING;
1274 default:
1275 vty_out(vty, "%% Rollback failed.\n\n");
1276 vty_out(vty, "Please check the logs for more details.\n");
1277 return CMD_WARNING;
1278 }
1279}
1280#endif /* HAVE_CONFIG_ROLLBACKS */
1281
1282DEFPY (rollback_config,
1283 rollback_config_cmd,
1284 "rollback configuration (1-4294967296)$transaction_id",
1285 "Rollback to a previous state\n"
1286 "Running configuration\n"
1287 "Transaction ID\n")
1288{
1289#ifdef HAVE_CONFIG_ROLLBACKS
1290 return nb_cli_rollback_configuration(vty, transaction_id);
1291#else
1292 vty_out(vty,
1293 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1294 return CMD_SUCCESS;
1295#endif /* HAVE_CONFIG_ROLLBACKS */
1296}
1297
1298/* Debug CLI commands. */
1299DEFUN (debug_nb,
1300 debug_nb_cmd,
1301 "debug northbound",
1302 DEBUG_STR
1303 "Northbound Debugging\n")
1304{
1305 debug_northbound = 1;
1306
1307 return CMD_SUCCESS;
1308}
1309
1310DEFUN (no_debug_nb,
1311 no_debug_nb_cmd,
1312 "no debug northbound",
1313 NO_STR DEBUG_STR
1314 "Northbound Debugging\n")
1315{
1316 debug_northbound = 0;
1317
1318 return CMD_SUCCESS;
1319}
1320
1321static int nb_debug_config_write(struct vty *vty)
1322{
1323 if (debug_northbound)
1324 vty_out(vty, "debug northbound\n");
1325
1326 return 1;
1327}
1328
1329static struct cmd_node nb_debug_node = {NORTHBOUND_DEBUG_NODE, "", 1};
1330
1331void nb_cli_install_default(int node)
1332{
1333 if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL)
1334 return;
1335
1336 install_element(node, &config_commit_cmd);
1337 install_element(node, &config_commit_comment_cmd);
1338 install_element(node, &config_commit_check_cmd);
1339 install_element(node, &config_update_cmd);
1340 install_element(node, &config_discard_cmd);
1341 install_element(node, &show_config_running_cmd);
1342 install_element(node, &show_config_candidate_cmd);
1343 install_element(node, &show_config_compare_cmd);
1344 install_element(node, &show_config_transaction_cmd);
1345}
1346
1347/* YANG module autocomplete. */
1348static void yang_module_autocomplete(vector comps, struct cmd_token *token)
1349{
1350 const struct lys_module *module;
1351 struct yang_translator *module_tr;
1352 uint32_t idx;
1353
1354 idx = 0;
1355 while ((module = ly_ctx_get_module_iter(ly_native_ctx, &idx)))
1356 vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module->name));
1357
1358 RB_FOREACH (module_tr, yang_translators, &yang_translators) {
1359 idx = 0;
1360 while ((module = ly_ctx_get_module_iter(module_tr->ly_ctx,
1361 &idx)))
1362 vector_set(comps,
1363 XSTRDUP(MTYPE_COMPLETION, module->name));
1364 }
1365}
1366
1367/* YANG module translator autocomplete. */
1368static void yang_translator_autocomplete(vector comps, struct cmd_token *token)
1369{
1370 struct yang_translator *module_tr;
1371
1372 RB_FOREACH (module_tr, yang_translators, &yang_translators)
1373 vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module_tr->family));
1374}
1375
1376static const struct cmd_variable_handler yang_var_handlers[] = {
1377 {.varname = "module_name", .completions = yang_module_autocomplete},
1378 {.varname = "translator_family",
1379 .completions = yang_translator_autocomplete},
1380 {.completions = NULL}};
1381
1382void nb_cli_init(void)
1383{
1384 /* Initialize the shared candidate configuration. */
1385 vty_shared_candidate_config = nb_config_new(NULL);
1386
1387 /* Install debug commands */
1388 install_node(&nb_debug_node, nb_debug_config_write);
1389 install_element(ENABLE_NODE, &debug_nb_cmd);
1390 install_element(ENABLE_NODE, &no_debug_nb_cmd);
1391 install_element(CONFIG_NODE, &debug_nb_cmd);
1392 install_element(CONFIG_NODE, &no_debug_nb_cmd);
1393
1394 /* Install commands specific to the transaction-base mode. */
1395 if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
1396 install_element(ENABLE_NODE, &config_exclusive_cmd);
1397 install_element(ENABLE_NODE, &config_private_cmd);
1398 install_element(ENABLE_NODE, &show_config_running_cmd);
1399 install_element(ENABLE_NODE,
1400 &show_config_compare_without_candidate_cmd);
1401 install_element(ENABLE_NODE, &show_config_transaction_cmd);
1402 install_element(ENABLE_NODE, &rollback_config_cmd);
1403 install_element(ENABLE_NODE, &clear_config_transactions_cmd);
1404
1405 install_element(CONFIG_NODE, &config_load_cmd);
1406 install_element(CONFIG_NODE,
1407 &config_database_max_transactions_cmd);
1408 }
1409
1410 /* Other commands. */
1411 install_element(CONFIG_NODE, &yang_module_translator_load_cmd);
1412 install_element(CONFIG_NODE, &yang_module_translator_unload_cmd);
1413 install_element(ENABLE_NODE, &show_yang_module_cmd);
1414 install_element(ENABLE_NODE, &show_yang_module_detail_cmd);
1415 install_element(ENABLE_NODE, &show_yang_module_translator_cmd);
1416 cmd_variable_handler_register(yang_var_handlers);
1417}
1418
1419void nb_cli_terminate(void)
1420{
1421 nb_config_free(vty_shared_candidate_config);
1422}