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