2 * Copyright (C) 2018 NetDEF, Inc.
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)
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
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
25 #include "lib_errors.h"
27 #include "termtable.h"
30 #include "yang_translator.h"
31 #include "northbound.h"
32 #include "northbound_cli.h"
33 #include "northbound_db.h"
34 #ifndef VTYSH_EXTRACT_PL
35 #include "lib/northbound_cli_clippy.c"
38 struct debug nb_dbg_cbs_config
= {0, "Northbound callbacks: configuration"};
39 struct debug nb_dbg_cbs_state
= {0, "Northbound callbacks: state"};
40 struct debug nb_dbg_cbs_rpc
= {0, "Northbound callbacks: RPCs"};
41 struct debug nb_dbg_notif
= {0, "Northbound notifications"};
42 struct debug nb_dbg_events
= {0, "Northbound events"};
44 struct nb_config
*vty_shared_candidate_config
;
45 static struct thread_master
*master
;
47 static void vty_show_libyang_errors(struct vty
*vty
, struct ly_ctx
*ly_ctx
)
49 struct ly_err_item
*ei
;
52 ei
= ly_err_first(ly_ctx
);
56 for (; ei
; ei
= ei
->next
)
57 vty_out(vty
, "%s\n", ei
->msg
);
59 path
= ly_errpath(ly_ctx
);
61 vty_out(vty
, "YANG path: %s\n", path
);
63 ly_err_clean(ly_ctx
, NULL
);
66 void nb_cli_enqueue_change(struct vty
*vty
, const char *xpath
,
67 enum nb_operation operation
, const char *value
)
69 struct vty_cfg_change
*change
;
71 if (vty
->num_cfg_changes
== VTY_MAXCFGCHANGES
) {
72 /* Not expected to happen. */
74 "%% Exceeded the maximum number of changes (%u) for a single command\n\n",
79 change
= &vty
->cfg_changes
[vty
->num_cfg_changes
++];
80 strlcpy(change
->xpath
, xpath
, sizeof(change
->xpath
));
81 change
->operation
= operation
;
82 change
->value
= value
;
85 int nb_cli_apply_changes(struct vty
*vty
, const char *xpath_base_fmt
, ...)
87 struct nb_config
*candidate_transitory
;
88 char xpath_base
[XPATH_MAXLEN
] = {};
95 * Create a copy of the candidate configuration. For consistency, we
96 * need to ensure that either all changes made by the command are
97 * accepted or none are.
99 candidate_transitory
= nb_config_dup(vty
->candidate_config
);
101 /* Parse the base XPath format string. */
102 if (xpath_base_fmt
) {
105 va_start(ap
, xpath_base_fmt
);
106 vsnprintf(xpath_base
, sizeof(xpath_base
), xpath_base_fmt
, ap
);
110 /* Edit candidate configuration. */
111 for (size_t i
= 0; i
< vty
->num_cfg_changes
; i
++) {
112 struct vty_cfg_change
*change
= &vty
->cfg_changes
[i
];
113 struct nb_node
*nb_node
;
114 char xpath
[XPATH_MAXLEN
];
115 struct yang_data
*data
;
117 /* Handle relative XPaths. */
118 memset(xpath
, 0, sizeof(xpath
));
119 if (vty
->xpath_index
> 0
120 && ((xpath_base_fmt
&& xpath_base
[0] == '.')
121 || change
->xpath
[0] == '.'))
122 strlcpy(xpath
, VTY_CURR_XPATH
, sizeof(xpath
));
123 if (xpath_base_fmt
) {
124 if (xpath_base
[0] == '.')
125 strlcat(xpath
, xpath_base
+ 1, sizeof(xpath
));
127 strlcat(xpath
, xpath_base
, sizeof(xpath
));
129 if (change
->xpath
[0] == '.')
130 strlcat(xpath
, change
->xpath
+ 1, sizeof(xpath
));
132 strlcpy(xpath
, change
->xpath
, sizeof(xpath
));
134 /* Find the northbound node associated to the data path. */
135 nb_node
= nb_node_find(xpath
);
137 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
138 "%s: unknown data path: %s", __func__
, xpath
);
143 /* If the value is not set, get the default if it exists. */
144 if (change
->value
== NULL
)
145 change
->value
= yang_snode_get_default(nb_node
->snode
);
146 data
= yang_data_new(xpath
, change
->value
);
149 * Ignore "not found" errors when editing the candidate
152 ret
= nb_candidate_edit(candidate_transitory
, nb_node
,
153 change
->operation
, xpath
, NULL
, data
);
154 yang_data_free(data
);
155 if (ret
!= NB_OK
&& ret
!= NB_ERR_NOT_FOUND
) {
157 EC_LIB_NB_CANDIDATE_EDIT_ERROR
,
158 "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
159 __func__
, nb_operation_name(change
->operation
),
167 nb_config_free(candidate_transitory
);
169 switch (frr_get_cli_mode()) {
170 case FRR_CLI_CLASSIC
:
171 vty_out(vty
, "%% Configuration failed.\n\n");
173 case FRR_CLI_TRANSACTIONAL
:
175 "%% Failed to edit candidate configuration.\n\n");
178 vty_show_libyang_errors(vty
, ly_native_ctx
);
180 return CMD_WARNING_CONFIG_FAILED
;
183 nb_config_replace(vty
->candidate_config
, candidate_transitory
, false);
185 /* Do an implicit "commit" when using the classic CLI mode. */
186 if (frr_get_cli_mode() == FRR_CLI_CLASSIC
) {
187 ret
= nb_candidate_commit(vty
->candidate_config
, NB_CLIENT_CLI
,
189 if (ret
!= NB_OK
&& ret
!= NB_ERR_NO_CHANGES
) {
190 vty_out(vty
, "%% Configuration failed: %s.\n\n",
193 "Please check the logs for more details.\n");
195 /* Regenerate candidate for consistency. */
196 nb_config_replace(vty
->candidate_config
, running_config
,
198 return CMD_WARNING_CONFIG_FAILED
;
205 int nb_cli_rpc(const char *xpath
, struct list
*input
, struct list
*output
)
207 struct nb_node
*nb_node
;
210 nb_node
= nb_node_find(xpath
);
212 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
213 "%s: unknown data path: %s", __func__
, xpath
);
217 ret
= nb_callback_rpc(nb_node
, xpath
, input
, output
);
226 void nb_cli_confirmed_commit_clean(struct vty
*vty
)
228 THREAD_TIMER_OFF(vty
->t_confirmed_commit_timeout
);
229 nb_config_free(vty
->confirmed_commit_rollback
);
230 vty
->confirmed_commit_rollback
= NULL
;
233 int nb_cli_confirmed_commit_rollback(struct vty
*vty
)
235 uint32_t transaction_id
;
238 /* Perform the rollback. */
239 ret
= nb_candidate_commit(
240 vty
->confirmed_commit_rollback
, NB_CLIENT_CLI
, true,
241 "Rollback to previous configuration - confirmed commit has timed out",
245 "Rollback performed successfully (Transaction ID #%u).\n",
248 vty_out(vty
, "Failed to rollback to previous configuration.\n");
253 static int nb_cli_confirmed_commit_timeout(struct thread
*thread
)
255 struct vty
*vty
= THREAD_ARG(thread
);
257 /* XXX: broadcast this message to all logged-in users? */
259 "\nConfirmed commit has timed out, rolling back to previous configuration.\n\n");
261 nb_cli_confirmed_commit_rollback(vty
);
262 nb_cli_confirmed_commit_clean(vty
);
267 static int nb_cli_commit(struct vty
*vty
, bool force
,
268 unsigned int confirmed_timeout
, char *comment
)
270 uint32_t transaction_id
= 0;
273 /* Check if there's a pending confirmed commit. */
274 if (vty
->t_confirmed_commit_timeout
) {
275 if (confirmed_timeout
) {
276 /* Reset timeout if "commit confirmed" is used again. */
278 "%% Resetting confirmed-commit timeout to %u minute(s)\n\n",
281 THREAD_TIMER_OFF(vty
->t_confirmed_commit_timeout
);
282 thread_add_timer(master
,
283 nb_cli_confirmed_commit_timeout
, vty
,
284 confirmed_timeout
* 60,
285 &vty
->t_confirmed_commit_timeout
);
287 /* Accept commit confirmation. */
288 vty_out(vty
, "%% Commit complete.\n\n");
289 nb_cli_confirmed_commit_clean(vty
);
294 if (vty_exclusive_lock
!= NULL
&& vty_exclusive_lock
!= vty
) {
295 vty_out(vty
, "%% Configuration is locked by another VTY.\n\n");
299 /* "force" parameter. */
300 if (!force
&& nb_candidate_needs_update(vty
->candidate_config
)) {
302 "%% Candidate configuration needs to be updated before commit.\n\n");
304 "Use the \"update\" command or \"commit force\".\n");
308 /* "confirm" parameter. */
309 if (confirmed_timeout
) {
310 vty
->confirmed_commit_rollback
= nb_config_dup(running_config
);
312 vty
->t_confirmed_commit_timeout
= NULL
;
313 thread_add_timer(master
, nb_cli_confirmed_commit_timeout
, vty
,
314 confirmed_timeout
* 60,
315 &vty
->t_confirmed_commit_timeout
);
318 ret
= nb_candidate_commit(vty
->candidate_config
, NB_CLIENT_CLI
, true,
319 comment
, &transaction_id
);
321 /* Map northbound return code to CLI return code. */
324 nb_config_replace(vty
->candidate_config_base
, running_config
,
327 "%% Configuration committed successfully (Transaction ID #%u).\n\n",
330 case NB_ERR_NO_CHANGES
:
331 vty_out(vty
, "%% No configuration changes to commit.\n\n");
335 "%% Failed to commit candidate configuration: %s.\n\n",
337 vty_out(vty
, "Please check the logs for more details.\n");
342 static int nb_cli_candidate_load_file(struct vty
*vty
,
343 enum nb_cfg_format format
,
344 struct yang_translator
*translator
,
345 const char *path
, bool replace
)
347 struct nb_config
*loaded_config
= NULL
;
348 struct lyd_node
*dnode
;
349 struct ly_ctx
*ly_ctx
;
353 case NB_CFG_FMT_CMDS
:
354 loaded_config
= nb_config_new(NULL
);
355 if (!vty_read_config(loaded_config
, path
, config_default
)) {
356 vty_out(vty
, "%% Failed to load configuration.\n\n");
358 "Please check the logs for more details.\n");
359 nb_config_free(loaded_config
);
363 case NB_CFG_FMT_JSON
:
365 ly_format
= (format
== NB_CFG_FMT_JSON
) ? LYD_JSON
: LYD_XML
;
367 ly_ctx
= translator
? translator
->ly_ctx
: ly_native_ctx
;
368 dnode
= lyd_parse_path(ly_ctx
, path
, ly_format
, LYD_OPT_EDIT
);
370 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_parse_path() failed",
372 vty_out(vty
, "%% Failed to load configuration:\n\n");
373 vty_show_libyang_errors(vty
, ly_ctx
);
377 && yang_translate_dnode(translator
,
378 YANG_TRANSLATE_TO_NATIVE
, &dnode
)
379 != YANG_TRANSLATE_SUCCESS
) {
380 vty_out(vty
, "%% Failed to translate configuration\n");
381 yang_dnode_free(dnode
);
384 loaded_config
= nb_config_new(dnode
);
389 nb_config_replace(vty
->candidate_config
, loaded_config
, false);
390 else if (nb_config_merge(vty
->candidate_config
, loaded_config
, false)
393 "%% Failed to merge the loaded configuration:\n\n");
394 vty_show_libyang_errors(vty
, ly_native_ctx
);
401 static int nb_cli_candidate_load_transaction(struct vty
*vty
,
402 uint32_t transaction_id
,
405 struct nb_config
*loaded_config
;
407 loaded_config
= nb_db_transaction_load(transaction_id
);
408 if (!loaded_config
) {
409 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
415 nb_config_replace(vty
->candidate_config
, loaded_config
, false);
416 else if (nb_config_merge(vty
->candidate_config
, loaded_config
, false)
419 "%% Failed to merge the loaded configuration:\n\n");
420 vty_show_libyang_errors(vty
, ly_native_ctx
);
427 void nb_cli_show_dnode_cmds(struct vty
*vty
, struct lyd_node
*root
,
430 struct lyd_node
*next
, *child
;
432 LY_TREE_DFS_BEGIN (root
, next
, child
) {
433 struct nb_node
*nb_node
;
435 nb_node
= child
->schema
->priv
;
436 if (!nb_node
->cbs
.cli_show
)
439 /* Skip default values. */
440 if (!with_defaults
&& yang_dnode_is_default_recursive(child
))
443 (*nb_node
->cbs
.cli_show
)(vty
, child
, with_defaults
);
445 LY_TREE_DFS_END(root
, next
, child
);
449 static void nb_cli_show_config_cmds(struct vty
*vty
, struct nb_config
*config
,
452 struct lyd_node
*root
;
454 vty_out(vty
, "Configuration:\n");
456 vty_out(vty
, "frr version %s\n", FRR_VER_SHORT
);
457 vty_out(vty
, "frr defaults %s\n", DFLT_NAME
);
459 LY_TREE_FOR (config
->dnode
, root
)
460 nb_cli_show_dnode_cmds(vty
, root
, with_defaults
);
463 vty_out(vty
, "end\n");
466 static int nb_cli_show_config_libyang(struct vty
*vty
, LYD_FORMAT format
,
467 struct nb_config
*config
,
468 struct yang_translator
*translator
,
471 struct lyd_node
*dnode
;
475 dnode
= yang_dnode_dup(config
->dnode
);
477 && yang_translate_dnode(translator
, YANG_TRANSLATE_FROM_NATIVE
,
479 != YANG_TRANSLATE_SUCCESS
) {
480 vty_out(vty
, "%% Failed to translate configuration\n");
481 yang_dnode_free(dnode
);
485 SET_FLAG(options
, LYP_FORMAT
| LYP_WITHSIBLINGS
);
487 SET_FLAG(options
, LYP_WD_ALL
);
489 SET_FLAG(options
, LYP_WD_TRIM
);
491 if (lyd_print_mem(&strp
, dnode
, format
, options
) == 0 && strp
) {
492 vty_out(vty
, "%s", strp
);
496 yang_dnode_free(dnode
);
501 static int nb_cli_show_config(struct vty
*vty
, struct nb_config
*config
,
502 enum nb_cfg_format format
,
503 struct yang_translator
*translator
,
507 case NB_CFG_FMT_CMDS
:
508 nb_cli_show_config_cmds(vty
, config
, with_defaults
);
510 case NB_CFG_FMT_JSON
:
511 return nb_cli_show_config_libyang(vty
, LYD_JSON
, config
,
512 translator
, with_defaults
);
514 return nb_cli_show_config_libyang(vty
, LYD_XML
, config
,
515 translator
, with_defaults
);
521 static int nb_write_config(struct nb_config
*config
, enum nb_cfg_format format
,
522 struct yang_translator
*translator
, char *path
,
526 struct vty
*file_vty
;
529 snprintf(path
, pathlen
, "/tmp/frr.tmp.XXXXXXXX");
532 flog_warn(EC_LIB_SYSTEM_CALL
, "%s: mkstemp() failed: %s",
533 __func__
, safe_strerror(errno
));
537 /* Make vty for configuration file. */
538 file_vty
= vty_new();
540 file_vty
->type
= VTY_FILE
;
542 ret
= nb_cli_show_config(file_vty
, config
, format
, translator
,
549 static int nb_cli_show_config_compare(struct vty
*vty
,
550 struct nb_config
*config1
,
551 struct nb_config
*config2
,
552 enum nb_cfg_format format
,
553 struct yang_translator
*translator
)
555 char config1_path
[256];
556 char config2_path
[256];
557 char command
[BUFSIZ
];
562 if (nb_write_config(config1
, format
, translator
, config1_path
,
563 sizeof(config1_path
))
565 vty_out(vty
, "%% Failed to process configurations.\n\n");
568 if (nb_write_config(config2
, format
, translator
, config2_path
,
569 sizeof(config2_path
))
571 vty_out(vty
, "%% Failed to process configurations.\n\n");
572 unlink(config1_path
);
576 snprintf(command
, sizeof(command
), "diff -u %s %s", config1_path
,
578 fp
= popen(command
, "r");
580 vty_out(vty
, "%% Failed to generate configuration diff.\n\n");
581 unlink(config1_path
);
582 unlink(config2_path
);
585 /* Print diff line by line. */
586 while (fgets(line
, sizeof(line
), fp
) != NULL
) {
589 vty_out(vty
, "%s", line
);
593 unlink(config1_path
);
594 unlink(config2_path
);
599 /* Configure exclusively from this terminal. */
600 DEFUN (config_exclusive
,
601 config_exclusive_cmd
,
602 "configure exclusive",
603 "Configuration from vty interface\n"
604 "Configure exclusively from this terminal\n")
606 return vty_config_enter(vty
, true, true);
609 /* Configure using a private candidate configuration. */
610 DEFUN (config_private
,
613 "Configuration from vty interface\n"
614 "Configure using a private candidate configuration\n")
616 return vty_config_enter(vty
, true, false);
619 DEFPY (config_commit
,
621 "commit [{force$force|confirmed (1-60)}]",
622 "Commit changes into the running configuration\n"
623 "Force commit even if the candidate is outdated\n"
624 "Rollback this commit unless there is a confirming commit\n"
625 "Timeout in minutes for the commit to be confirmed\n")
627 return nb_cli_commit(vty
, !!force
, confirmed
, NULL
);
630 DEFPY (config_commit_comment
,
631 config_commit_comment_cmd
,
632 "commit [{force$force|confirmed (1-60)}] comment LINE...",
633 "Commit changes into the running configuration\n"
634 "Force commit even if the candidate is outdated\n"
635 "Rollback this commit unless there is a confirming commit\n"
636 "Timeout in minutes for the commit to be confirmed\n"
637 "Assign a comment to this commit\n"
638 "Comment for this commit (Max 80 characters)\n")
644 argv_find(argv
, argc
, "LINE", &idx
);
645 comment
= argv_concat(argv
, argc
, idx
);
646 ret
= nb_cli_commit(vty
, !!force
, confirmed
, comment
);
647 XFREE(MTYPE_TMP
, comment
);
652 DEFPY (config_commit_check
,
653 config_commit_check_cmd
,
655 "Commit changes into the running configuration\n"
656 "Check if the configuration changes are valid\n")
660 ret
= nb_candidate_validate(vty
->candidate_config
);
663 "%% Failed to validate candidate configuration.\n\n");
664 vty_show_libyang_errors(vty
, ly_native_ctx
);
668 vty_out(vty
, "%% Candidate configuration validated successfully.\n\n");
673 DEFPY (config_update
,
676 "Update candidate configuration\n")
678 if (!nb_candidate_needs_update(vty
->candidate_config
)) {
679 vty_out(vty
, "%% Update is not necessary.\n\n");
683 if (nb_candidate_update(vty
->candidate_config
) != NB_OK
) {
685 "%% Failed to update the candidate configuration.\n\n");
686 vty_out(vty
, "Please check the logs for more details.\n");
690 nb_config_replace(vty
->candidate_config_base
, running_config
, true);
692 vty_out(vty
, "%% Candidate configuration updated successfully.\n\n");
697 DEFPY (config_discard
,
700 "Discard changes in the candidate configuration\n")
702 nb_config_replace(vty
->candidate_config
, vty
->candidate_config_base
,
712 file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
713 |transaction (1-4294967296)$tid\
716 "Configuration related settings\n"
717 "Load configuration into candidate\n"
718 "Load configuration file into candidate\n"
719 "Load configuration file in JSON format\n"
720 "Load configuration file in XML format\n"
721 "Translate configuration file\n"
722 "YANG module translator\n"
723 "Configuration file name (full path)\n"
724 "Load configuration from transaction into candidate\n"
726 "Replace instead of merge\n")
729 enum nb_cfg_format format
;
730 struct yang_translator
*translator
= NULL
;
733 format
= NB_CFG_FMT_JSON
;
735 format
= NB_CFG_FMT_XML
;
737 format
= NB_CFG_FMT_CMDS
;
739 if (translator_family
) {
740 translator
= yang_translator_find(translator_family
);
743 "%% Module translator \"%s\" not found\n",
749 return nb_cli_candidate_load_file(vty
, format
, translator
,
750 filename
, !!replace
);
753 return nb_cli_candidate_load_transaction(vty
, tid
, !!replace
);
756 DEFPY (show_config_running
,
757 show_config_running_cmd
,
758 "show configuration running\
759 [<json$json|xml$xml> [translate WORD$translator_family]]\
760 [with-defaults$with_defaults]",
762 "Configuration information\n"
763 "Running configuration\n"
764 "Change output format to JSON\n"
765 "Change output format to XML\n"
767 "YANG module translator\n"
768 "Show default values\n")
771 enum nb_cfg_format format
;
772 struct yang_translator
*translator
= NULL
;
775 format
= NB_CFG_FMT_JSON
;
777 format
= NB_CFG_FMT_XML
;
779 format
= NB_CFG_FMT_CMDS
;
781 if (translator_family
) {
782 translator
= yang_translator_find(translator_family
);
784 vty_out(vty
, "%% Module translator \"%s\" not found\n",
790 nb_cli_show_config(vty
, running_config
, format
, translator
,
796 DEFPY (show_config_candidate
,
797 show_config_candidate_cmd
,
798 "show configuration candidate\
799 [<json$json|xml$xml> [translate WORD$translator_family]]\
801 with-defaults$with_defaults\
805 "Configuration information\n"
806 "Candidate configuration\n"
807 "Change output format to JSON\n"
808 "Change output format to XML\n"
810 "YANG module translator\n"
811 "Show default values\n"
812 "Show changes applied in the candidate configuration\n")
815 enum nb_cfg_format format
;
816 struct yang_translator
*translator
= NULL
;
819 format
= NB_CFG_FMT_JSON
;
821 format
= NB_CFG_FMT_XML
;
823 format
= NB_CFG_FMT_CMDS
;
825 if (translator_family
) {
826 translator
= yang_translator_find(translator_family
);
828 vty_out(vty
, "%% Module translator \"%s\" not found\n",
835 return nb_cli_show_config_compare(
836 vty
, vty
->candidate_config_base
, vty
->candidate_config
,
839 nb_cli_show_config(vty
, vty
->candidate_config
, format
, translator
,
845 DEFPY (show_config_candidate_section
,
846 show_config_candidate_section_cmd
,
850 struct lyd_node
*dnode
;
852 /* Top-level configuration node, display everything. */
853 if (vty
->xpath_index
== 0)
854 return nb_cli_show_config(vty
, vty
->candidate_config
,
855 NB_CFG_FMT_CMDS
, NULL
, false);
857 /* Display only the current section of the candidate configuration. */
858 dnode
= yang_dnode_get(vty
->candidate_config
->dnode
, VTY_CURR_XPATH
);
860 /* Shouldn't happen. */
863 nb_cli_show_dnode_cmds(vty
, dnode
, 0);
869 DEFPY (show_config_compare
,
870 show_config_compare_cmd
,
871 "show configuration compare\
873 candidate$c1_candidate\
875 |transaction (1-4294967296)$c1_tid\
878 candidate$c2_candidate\
880 |transaction (1-4294967296)$c2_tid\
882 [<json$json|xml$xml> [translate WORD$translator_family]]",
884 "Configuration information\n"
885 "Compare two different configurations\n"
886 "Candidate configuration\n"
887 "Running configuration\n"
888 "Configuration transaction\n"
890 "Candidate configuration\n"
891 "Running configuration\n"
892 "Configuration transaction\n"
894 "Change output format to JSON\n"
895 "Change output format to XML\n"
897 "YANG module translator\n")
899 enum nb_cfg_format format
;
900 struct yang_translator
*translator
= NULL
;
901 struct nb_config
*config1
, *config_transaction1
= NULL
;
902 struct nb_config
*config2
, *config_transaction2
= NULL
;
903 int ret
= CMD_WARNING
;
906 config1
= vty
->candidate_config
;
908 config1
= running_config
;
910 config_transaction1
= nb_db_transaction_load(c1_tid
);
911 if (!config_transaction1
) {
912 vty_out(vty
, "%% Transaction %u does not exist\n\n",
913 (unsigned int)c1_tid
);
916 config1
= config_transaction1
;
920 config2
= vty
->candidate_config
;
922 config2
= running_config
;
924 config_transaction2
= nb_db_transaction_load(c2_tid
);
925 if (!config_transaction2
) {
926 vty_out(vty
, "%% Transaction %u does not exist\n\n",
927 (unsigned int)c2_tid
);
930 config2
= config_transaction2
;
934 format
= NB_CFG_FMT_JSON
;
936 format
= NB_CFG_FMT_XML
;
938 format
= NB_CFG_FMT_CMDS
;
940 if (translator_family
) {
941 translator
= yang_translator_find(translator_family
);
943 vty_out(vty
, "%% Module translator \"%s\" not found\n",
949 ret
= nb_cli_show_config_compare(vty
, config1
, config2
, format
,
952 if (config_transaction1
)
953 nb_config_free(config_transaction1
);
954 if (config_transaction2
)
955 nb_config_free(config_transaction2
);
961 * Stripped down version of the "show configuration compare" command.
962 * The "candidate" option is not present so the command can be installed in
965 ALIAS (show_config_compare
,
966 show_config_compare_without_candidate_cmd
,
967 "show configuration compare\
970 |transaction (1-4294967296)$c1_tid\
974 |transaction (1-4294967296)$c2_tid\
976 [<json$json|xml$xml> [translate WORD$translator_family]]",
978 "Configuration information\n"
979 "Compare two different configurations\n"
980 "Running configuration\n"
981 "Configuration transaction\n"
983 "Running configuration\n"
984 "Configuration transaction\n"
986 "Change output format to JSON\n"
987 "Change output format to XML\n"
989 "YANG module translator\n")
991 DEFPY (clear_config_transactions
,
992 clear_config_transactions_cmd
,
993 "clear configuration transactions oldest (1-100)$n",
995 "Configuration activity\n"
996 "Delete transactions from the transactions log\n"
997 "Delete oldest <n> transactions\n"
998 "Number of transactions to delete\n")
1000 #ifdef HAVE_CONFIG_ROLLBACKS
1001 if (nb_db_clear_transactions(n
) != NB_OK
) {
1002 vty_out(vty
, "%% Failed to delete transactions.\n\n");
1007 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1008 #endif /* HAVE_CONFIG_ROLLBACKS */
1013 DEFPY (config_database_max_transactions
,
1014 config_database_max_transactions_cmd
,
1015 "configuration database max-transactions (1-100)$max",
1016 "Configuration related settings\n"
1017 "Configuration database\n"
1018 "Set the maximum number of transactions to store\n"
1019 "Number of transactions\n")
1021 #ifdef HAVE_CONFIG_ROLLBACKS
1022 if (nb_db_set_max_transactions(max
) != NB_OK
) {
1024 "%% Failed to update the maximum number of transactions.\n\n");
1028 "%% Maximum number of transactions updated successfully.\n\n");
1031 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1032 #endif /* HAVE_CONFIG_ROLLBACKS */
1037 DEFPY (yang_module_translator_load
,
1038 yang_module_translator_load_cmd
,
1039 "yang module-translator load FILENAME$filename",
1040 "YANG related settings\n"
1041 "YANG module translator\n"
1042 "Load YANG module translator\n"
1043 "File name (full path)\n")
1045 struct yang_translator
*translator
;
1047 translator
= yang_translator_load(filename
);
1049 vty_out(vty
, "%% Failed to load \"%s\"\n\n", filename
);
1050 vty_out(vty
, "Please check the logs for more details.\n");
1054 vty_out(vty
, "%% Module translator \"%s\" loaded successfully.\n\n",
1055 translator
->family
);
1060 DEFPY (yang_module_translator_unload_family
,
1061 yang_module_translator_unload_cmd
,
1062 "yang module-translator unload WORD$translator_family",
1063 "YANG related settings\n"
1064 "YANG module translator\n"
1065 "Unload YANG module translator\n"
1066 "Name of the module translator\n")
1068 struct yang_translator
*translator
;
1070 translator
= yang_translator_find(translator_family
);
1072 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1077 yang_translator_unload(translator
);
1082 #ifdef HAVE_CONFIG_ROLLBACKS
1083 static void nb_cli_show_transactions_cb(void *arg
, int transaction_id
,
1084 const char *client_name
,
1085 const char *date
, const char *comment
)
1087 struct ttable
*tt
= arg
;
1089 ttable_add_row(tt
, "%d|%s|%s|%s", transaction_id
, client_name
, date
,
1093 static int nb_cli_show_transactions(struct vty
*vty
)
1097 /* Prepare table. */
1098 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1099 ttable_add_row(tt
, "Transaction ID|Client|Date|Comment");
1100 tt
->style
.cell
.rpad
= 2;
1101 tt
->style
.corner
= '+';
1103 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1105 /* Fetch transactions from the northbound database. */
1106 if (nb_db_transactions_iterate(nb_cli_show_transactions_cb
, tt
)
1109 "%% Failed to fetch configuration transactions.\n");
1113 /* Dump the generated table. */
1114 if (tt
->nrows
> 1) {
1117 table
= ttable_dump(tt
, "\n");
1118 vty_out(vty
, "%s\n", table
);
1119 XFREE(MTYPE_TMP
, table
);
1121 vty_out(vty
, "No configuration transactions to display.\n\n");
1127 #endif /* HAVE_CONFIG_ROLLBACKS */
1129 DEFPY (show_config_transaction
,
1130 show_config_transaction_cmd
,
1131 "show configuration transaction\
1133 (1-4294967296)$transaction_id\
1134 [<json$json|xml$xml> [translate WORD$translator_family]]\
1136 with-defaults$with_defaults\
1141 "Configuration information\n"
1142 "Configuration transaction\n"
1144 "Change output format to JSON\n"
1145 "Change output format to XML\n"
1146 "Translate output\n"
1147 "YANG module translator\n"
1148 "Show default values\n"
1149 "Show changes compared to the previous transaction\n")
1151 #ifdef HAVE_CONFIG_ROLLBACKS
1152 if (transaction_id
) {
1153 struct nb_config
*config
;
1154 enum nb_cfg_format format
;
1155 struct yang_translator
*translator
= NULL
;
1158 format
= NB_CFG_FMT_JSON
;
1160 format
= NB_CFG_FMT_XML
;
1162 format
= NB_CFG_FMT_CMDS
;
1164 if (translator_family
) {
1165 translator
= yang_translator_find(translator_family
);
1168 "%% Module translator \"%s\" not found\n",
1174 config
= nb_db_transaction_load(transaction_id
);
1176 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
1177 (unsigned int)transaction_id
);
1182 struct nb_config
*prev_config
;
1185 /* NOTE: this can be NULL. */
1187 nb_db_transaction_load(transaction_id
- 1);
1189 ret
= nb_cli_show_config_compare(
1190 vty
, prev_config
, config
, format
, translator
);
1192 nb_config_free(prev_config
);
1193 nb_config_free(config
);
1198 nb_cli_show_config(vty
, config
, format
, translator
,
1200 nb_config_free(config
);
1205 return nb_cli_show_transactions(vty
);
1208 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1210 #endif /* HAVE_CONFIG_ROLLBACKS */
1213 static int nb_cli_oper_data_cb(const struct lys_node
*snode
,
1214 struct yang_translator
*translator
,
1215 struct yang_data
*data
, void *arg
)
1217 struct lyd_node
*dnode
= arg
;
1218 struct ly_ctx
*ly_ctx
;
1223 ret
= yang_translate_xpath(translator
,
1224 YANG_TRANSLATE_FROM_NATIVE
,
1225 data
->xpath
, sizeof(data
->xpath
));
1227 case YANG_TRANSLATE_SUCCESS
:
1229 case YANG_TRANSLATE_NOTFOUND
:
1231 case YANG_TRANSLATE_FAILURE
:
1235 ly_ctx
= translator
->ly_ctx
;
1237 ly_ctx
= ly_native_ctx
;
1240 dnode
= lyd_new_path(dnode
, ly_ctx
, data
->xpath
, (void *)data
->value
, 0,
1241 LYD_PATH_OPT_UPDATE
);
1242 if (!dnode
&& ly_errno
) {
1243 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_new_path() failed",
1249 yang_data_free(data
);
1253 yang_data_free(data
);
1257 DEFPY (show_yang_operational_data
,
1258 show_yang_operational_data_cmd
,
1259 "show yang operational-data XPATH$xpath\
1261 format <json$json|xml$xml>\
1262 |translate WORD$translator_family\
1265 "YANG information\n"
1266 "Show YANG operational data\n"
1267 "XPath expression specifying the YANG data path\n"
1268 "Set the output format\n"
1269 "JavaScript Object Notation\n"
1270 "Extensible Markup Language\n"
1271 "Translate operational data\n"
1272 "YANG module translator\n")
1275 struct yang_translator
*translator
= NULL
;
1276 struct ly_ctx
*ly_ctx
;
1277 struct lyd_node
*dnode
;
1285 if (translator_family
) {
1286 translator
= yang_translator_find(translator_family
);
1288 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1293 ly_ctx
= translator
->ly_ctx
;
1295 ly_ctx
= ly_native_ctx
;
1298 dnode
= yang_dnode_new(ly_ctx
, false);
1299 if (nb_oper_data_iterate(xpath
, translator
, 0, nb_cli_oper_data_cb
,
1302 vty_out(vty
, "%% Failed to fetch operational data.\n");
1303 yang_dnode_free(dnode
);
1306 lyd_validate(&dnode
, LYD_OPT_GET
, ly_ctx
);
1308 /* Display the data. */
1309 if (lyd_print_mem(&strp
, dnode
, format
,
1310 LYP_FORMAT
| LYP_WITHSIBLINGS
| LYP_WD_ALL
)
1313 vty_out(vty
, "%% Failed to display operational data.\n");
1314 yang_dnode_free(dnode
);
1317 vty_out(vty
, "%s", strp
);
1319 yang_dnode_free(dnode
);
1324 DEFPY (show_yang_module
,
1325 show_yang_module_cmd
,
1326 "show yang module [module-translator WORD$translator_family]",
1328 "YANG information\n"
1329 "Show loaded modules\n"
1330 "YANG module translator\n"
1331 "YANG module translator\n")
1333 struct ly_ctx
*ly_ctx
;
1334 struct yang_translator
*translator
= NULL
;
1335 const struct lys_module
*module
;
1339 if (translator_family
) {
1340 translator
= yang_translator_find(translator_family
);
1342 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1346 ly_ctx
= translator
->ly_ctx
;
1348 ly_ctx
= ly_native_ctx
;
1350 /* Prepare table. */
1351 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1352 ttable_add_row(tt
, "Module|Version|Revision|Flags|Namespace");
1353 tt
->style
.cell
.rpad
= 2;
1354 tt
->style
.corner
= '+';
1356 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1358 while ((module
= ly_ctx_get_module_iter(ly_ctx
, &idx
))) {
1361 snprintf(flags
, sizeof(flags
), "%c%c",
1362 module
->implemented
? 'I' : ' ',
1363 (module
->deviated
== 1) ? 'D' : ' ');
1365 ttable_add_row(tt
, "%s|%s|%s|%s|%s", module
->name
,
1366 (module
->version
== 2) ? "1.1" : "1.0",
1367 (module
->rev_size
> 0) ? module
->rev
[0].date
1372 /* Dump the generated table. */
1373 if (tt
->nrows
> 1) {
1376 vty_out(vty
, " Flags: I - Implemented, D - Deviated\n\n");
1378 table
= ttable_dump(tt
, "\n");
1379 vty_out(vty
, "%s\n", table
);
1380 XFREE(MTYPE_TMP
, table
);
1382 vty_out(vty
, "No YANG modules to display.\n\n");
1389 DEFPY (show_yang_module_detail
,
1390 show_yang_module_detail_cmd
,
1392 [module-translator WORD$translator_family]\
1393 WORD$module_name <summary|tree$tree|yang$yang|yin$yin>",
1395 "YANG information\n"
1396 "Show loaded modules\n"
1397 "YANG module translator\n"
1398 "YANG module translator\n"
1400 "Display summary information about the module\n"
1401 "Display module in the tree (RFC 8340) format\n"
1402 "Display module in the YANG format\n"
1403 "Display module in the YIN format\n")
1405 struct ly_ctx
*ly_ctx
;
1406 struct yang_translator
*translator
= NULL
;
1407 const struct lys_module
*module
;
1408 LYS_OUTFORMAT format
;
1411 if (translator_family
) {
1412 translator
= yang_translator_find(translator_family
);
1414 vty_out(vty
, "%% Module translator \"%s\" not found\n",
1418 ly_ctx
= translator
->ly_ctx
;
1420 ly_ctx
= ly_native_ctx
;
1422 module
= ly_ctx_get_module(ly_ctx
, module_name
, NULL
, 0);
1424 vty_out(vty
, "%% Module \"%s\" not found\n", module_name
);
1429 format
= LYS_OUT_YANG
;
1431 format
= LYS_OUT_YIN
;
1433 format
= LYS_OUT_TREE
;
1435 format
= LYS_OUT_INFO
;
1437 if (lys_print_mem(&strp
, module
, format
, NULL
, 0, 0) == 0) {
1438 vty_out(vty
, "%s\n", strp
);
1442 vty_out(vty
, "%% Error generating module information\n");
1449 DEFPY (show_yang_module_translator
,
1450 show_yang_module_translator_cmd
,
1451 "show yang module-translator",
1453 "YANG information\n"
1454 "Show loaded YANG module translators\n")
1456 struct yang_translator
*translator
;
1459 /* Prepare table. */
1460 tt
= ttable_new(&ttable_styles
[TTSTYLE_BLANK
]);
1461 ttable_add_row(tt
, "Family|Module|Deviations|Coverage (%%)");
1462 tt
->style
.cell
.rpad
= 2;
1463 tt
->style
.corner
= '+';
1465 ttable_rowseps(tt
, 0, BOTTOM
, true, '-');
1467 RB_FOREACH (translator
, yang_translators
, &yang_translators
) {
1468 struct yang_tmodule
*tmodule
;
1469 struct listnode
*ln
;
1471 for (ALL_LIST_ELEMENTS_RO(translator
->modules
, ln
, tmodule
)) {
1472 ttable_add_row(tt
, "%s|%s|%s|%.2f", translator
->family
,
1473 tmodule
->module
->name
,
1474 tmodule
->deviations
->name
,
1479 /* Dump the generated table. */
1480 if (tt
->nrows
> 1) {
1483 table
= ttable_dump(tt
, "\n");
1484 vty_out(vty
, "%s\n", table
);
1485 XFREE(MTYPE_TMP
, table
);
1487 vty_out(vty
, "No YANG module translators to display.\n\n");
1494 #ifdef HAVE_CONFIG_ROLLBACKS
1495 static int nb_cli_rollback_configuration(struct vty
*vty
,
1496 uint32_t transaction_id
)
1498 struct nb_config
*candidate
;
1502 candidate
= nb_db_transaction_load(transaction_id
);
1504 vty_out(vty
, "%% Transaction %u does not exist.\n\n",
1509 snprintf(comment
, sizeof(comment
), "Rollback to transaction %u",
1512 ret
= nb_candidate_commit(candidate
, NB_CLIENT_CLI
, true, comment
,
1514 nb_config_free(candidate
);
1518 "%% Configuration was successfully rolled back.\n\n");
1520 case NB_ERR_NO_CHANGES
:
1522 "%% Aborting - no configuration changes detected.\n\n");
1525 vty_out(vty
, "%% Rollback failed.\n\n");
1526 vty_out(vty
, "Please check the logs for more details.\n");
1530 #endif /* HAVE_CONFIG_ROLLBACKS */
1532 DEFPY (rollback_config
,
1533 rollback_config_cmd
,
1534 "rollback configuration (1-4294967296)$transaction_id",
1535 "Rollback to a previous state\n"
1536 "Running configuration\n"
1539 #ifdef HAVE_CONFIG_ROLLBACKS
1540 return nb_cli_rollback_configuration(vty
, transaction_id
);
1543 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1545 #endif /* HAVE_CONFIG_ROLLBACKS */
1548 /* Debug CLI commands. */
1549 static struct debug
*nb_debugs
[] = {
1550 &nb_dbg_cbs_config
, &nb_dbg_cbs_state
, &nb_dbg_cbs_rpc
,
1551 &nb_dbg_notif
, &nb_dbg_events
,
1554 static const char *const nb_debugs_conflines
[] = {
1555 "debug northbound callbacks configuration",
1556 "debug northbound callbacks state",
1557 "debug northbound callbacks rpc",
1558 "debug northbound notifications",
1559 "debug northbound events",
1562 DEFINE_HOOK(nb_client_debug_set_all
, (uint32_t flags
, bool set
), (flags
, set
));
1564 static void nb_debug_set_all(uint32_t flags
, bool set
)
1566 for (unsigned int i
= 0; i
< array_size(nb_debugs
); i
++) {
1567 DEBUG_FLAGS_SET(nb_debugs
[i
], flags
, set
);
1569 /* If all modes have been turned off, don't preserve options. */
1570 if (!DEBUG_MODE_CHECK(nb_debugs
[i
], DEBUG_MODE_ALL
))
1571 DEBUG_CLEAR(nb_debugs
[i
]);
1574 hook_call(nb_client_debug_set_all
, flags
, set
);
1579 "[no] debug northbound\
1581 callbacks$cbs [{configuration$cbs_cfg|state$cbs_state|rpc$cbs_rpc}]\
1582 |notifications$notifications\
1587 "Northbound debugging\n"
1595 uint32_t mode
= DEBUG_NODE2MODE(vty
->node
);
1598 bool none
= (!cbs_cfg
&& !cbs_state
&& !cbs_rpc
);
1600 if (none
|| cbs_cfg
)
1601 DEBUG_MODE_SET(&nb_dbg_cbs_config
, mode
, !no
);
1602 if (none
|| cbs_state
)
1603 DEBUG_MODE_SET(&nb_dbg_cbs_state
, mode
, !no
);
1604 if (none
|| cbs_rpc
)
1605 DEBUG_MODE_SET(&nb_dbg_cbs_rpc
, mode
, !no
);
1608 DEBUG_MODE_SET(&nb_dbg_notif
, mode
, !no
);
1610 DEBUG_MODE_SET(&nb_dbg_events
, mode
, !no
);
1612 /* no specific debug --> act on all of them */
1613 if (strmatch(argv
[argc
- 1]->text
, "northbound"))
1614 nb_debug_set_all(mode
, !no
);
1619 DEFINE_HOOK(nb_client_debug_config_write
, (struct vty
*vty
), (vty
));
1621 static int nb_debug_config_write(struct vty
*vty
)
1623 for (unsigned int i
= 0; i
< array_size(nb_debugs
); i
++)
1624 if (DEBUG_MODE_CHECK(nb_debugs
[i
], DEBUG_MODE_CONF
))
1625 vty_out(vty
, "%s\n", nb_debugs_conflines
[i
]);
1627 hook_call(nb_client_debug_config_write
, vty
);
1632 static struct debug_callbacks nb_dbg_cbs
= {.debug_set_all
= nb_debug_set_all
};
1633 static struct cmd_node nb_debug_node
= {NORTHBOUND_DEBUG_NODE
, "", 1};
1635 void nb_cli_install_default(int node
)
1637 install_element(node
, &show_config_candidate_section_cmd
);
1639 if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL
)
1642 install_element(node
, &config_commit_cmd
);
1643 install_element(node
, &config_commit_comment_cmd
);
1644 install_element(node
, &config_commit_check_cmd
);
1645 install_element(node
, &config_update_cmd
);
1646 install_element(node
, &config_discard_cmd
);
1647 install_element(node
, &show_config_running_cmd
);
1648 install_element(node
, &show_config_candidate_cmd
);
1649 install_element(node
, &show_config_compare_cmd
);
1650 install_element(node
, &show_config_transaction_cmd
);
1653 /* YANG module autocomplete. */
1654 static void yang_module_autocomplete(vector comps
, struct cmd_token
*token
)
1656 const struct lys_module
*module
;
1657 struct yang_translator
*module_tr
;
1661 while ((module
= ly_ctx_get_module_iter(ly_native_ctx
, &idx
)))
1662 vector_set(comps
, XSTRDUP(MTYPE_COMPLETION
, module
->name
));
1664 RB_FOREACH (module_tr
, yang_translators
, &yang_translators
) {
1666 while ((module
= ly_ctx_get_module_iter(module_tr
->ly_ctx
,
1669 XSTRDUP(MTYPE_COMPLETION
, module
->name
));
1673 /* YANG module translator autocomplete. */
1674 static void yang_translator_autocomplete(vector comps
, struct cmd_token
*token
)
1676 struct yang_translator
*module_tr
;
1678 RB_FOREACH (module_tr
, yang_translators
, &yang_translators
)
1679 vector_set(comps
, XSTRDUP(MTYPE_COMPLETION
, module_tr
->family
));
1682 static const struct cmd_variable_handler yang_var_handlers
[] = {
1683 {.varname
= "module_name", .completions
= yang_module_autocomplete
},
1684 {.varname
= "translator_family",
1685 .completions
= yang_translator_autocomplete
},
1686 {.completions
= NULL
}};
1688 void nb_cli_init(struct thread_master
*tm
)
1692 /* Initialize the shared candidate configuration. */
1693 vty_shared_candidate_config
= nb_config_new(NULL
);
1695 /* Install debug commands */
1696 debug_init(&nb_dbg_cbs
);
1697 install_node(&nb_debug_node
, nb_debug_config_write
);
1698 install_element(ENABLE_NODE
, &debug_nb_cmd
);
1699 install_element(CONFIG_NODE
, &debug_nb_cmd
);
1701 /* Install commands specific to the transaction-base mode. */
1702 if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL
) {
1703 install_element(ENABLE_NODE
, &config_exclusive_cmd
);
1704 install_element(ENABLE_NODE
, &config_private_cmd
);
1705 install_element(ENABLE_NODE
, &show_config_running_cmd
);
1706 install_element(ENABLE_NODE
,
1707 &show_config_compare_without_candidate_cmd
);
1708 install_element(ENABLE_NODE
, &show_config_transaction_cmd
);
1709 install_element(ENABLE_NODE
, &rollback_config_cmd
);
1710 install_element(ENABLE_NODE
, &clear_config_transactions_cmd
);
1712 install_element(CONFIG_NODE
, &config_load_cmd
);
1713 install_element(CONFIG_NODE
,
1714 &config_database_max_transactions_cmd
);
1717 /* Other commands. */
1718 install_element(CONFIG_NODE
, &yang_module_translator_load_cmd
);
1719 install_element(CONFIG_NODE
, &yang_module_translator_unload_cmd
);
1720 install_element(ENABLE_NODE
, &show_yang_operational_data_cmd
);
1721 install_element(ENABLE_NODE
, &show_yang_module_cmd
);
1722 install_element(ENABLE_NODE
, &show_yang_module_detail_cmd
);
1723 install_element(ENABLE_NODE
, &show_yang_module_translator_cmd
);
1724 cmd_variable_handler_register(yang_var_handlers
);
1727 void nb_cli_terminate(void)
1729 nb_config_free(vty_shared_candidate_config
);