]> git.proxmox.com Git - mirror_frr.git/blob - lib/northbound_cli.c
Merge pull request #12760 from opensourcerouting/fix/switch_to_pr_for_commitlint
[mirror_frr.git] / lib / northbound_cli.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2018 NetDEF, Inc.
4 * Renato Westphal
5 */
6
7 #include <zebra.h>
8
9 #include "libfrr.h"
10 #include "lib/version.h"
11 #include "defaults.h"
12 #include "log.h"
13 #include "lib_errors.h"
14 #include "command.h"
15 #include "termtable.h"
16 #include "db.h"
17 #include "debug.h"
18 #include "yang_translator.h"
19 #include "northbound.h"
20 #include "northbound_cli.h"
21 #include "northbound_db.h"
22 #include "lib/northbound_cli_clippy.c"
23
24 struct debug nb_dbg_cbs_config = {0, "Northbound callbacks: configuration"};
25 struct debug nb_dbg_cbs_state = {0, "Northbound callbacks: state"};
26 struct debug nb_dbg_cbs_rpc = {0, "Northbound callbacks: RPCs"};
27 struct debug nb_dbg_notif = {0, "Northbound notifications"};
28 struct debug nb_dbg_events = {0, "Northbound events"};
29 struct debug nb_dbg_libyang = {0, "libyang debugging"};
30
31 struct nb_config *vty_shared_candidate_config;
32 static struct thread_master *master;
33
34 static void vty_show_nb_errors(struct vty *vty, int error, const char *errmsg)
35 {
36 vty_out(vty, "Error type: %s\n", nb_err_name(error));
37 if (strlen(errmsg) > 0)
38 vty_out(vty, "Error description: %s\n", errmsg);
39 }
40
41 static int nb_cli_classic_commit(struct vty *vty)
42 {
43 struct nb_context context = {};
44 char errmsg[BUFSIZ] = {0};
45 int ret;
46
47 context.client = NB_CLIENT_CLI;
48 context.user = vty;
49 ret = nb_candidate_commit(context, vty->candidate_config, true, NULL,
50 NULL, errmsg, sizeof(errmsg));
51 switch (ret) {
52 case NB_OK:
53 /* Successful commit. Print warnings (if any). */
54 if (strlen(errmsg) > 0)
55 vty_out(vty, "%s\n", errmsg);
56 break;
57 case NB_ERR_NO_CHANGES:
58 break;
59 default:
60 vty_out(vty, "%% Configuration failed.\n\n");
61 vty_show_nb_errors(vty, ret, errmsg);
62 if (vty->pending_commit)
63 vty_out(vty,
64 "The following commands were dynamically grouped into the same transaction and rejected:\n%s",
65 vty->pending_cmds_buf);
66
67 /* Regenerate candidate for consistency. */
68 nb_config_replace(vty->candidate_config, running_config, true);
69 return CMD_WARNING_CONFIG_FAILED;
70 }
71
72 return CMD_SUCCESS;
73 }
74
75 static void nb_cli_pending_commit_clear(struct vty *vty)
76 {
77 vty->pending_commit = 0;
78 XFREE(MTYPE_TMP, vty->pending_cmds_buf);
79 vty->pending_cmds_buflen = 0;
80 vty->pending_cmds_bufpos = 0;
81 }
82
83 int nb_cli_pending_commit_check(struct vty *vty)
84 {
85 int ret = CMD_SUCCESS;
86
87 if (vty->pending_commit) {
88 ret = nb_cli_classic_commit(vty);
89 nb_cli_pending_commit_clear(vty);
90 }
91
92 return ret;
93 }
94
95 static int nb_cli_schedule_command(struct vty *vty)
96 {
97 /* Append command to dynamically sized buffer of scheduled commands. */
98 if (!vty->pending_cmds_buf) {
99 vty->pending_cmds_buflen = 4096;
100 vty->pending_cmds_buf =
101 XCALLOC(MTYPE_TMP, vty->pending_cmds_buflen);
102 }
103 if ((strlen(vty->buf) + 3)
104 > (vty->pending_cmds_buflen - vty->pending_cmds_bufpos)) {
105 vty->pending_cmds_buflen *= 2;
106 vty->pending_cmds_buf =
107 XREALLOC(MTYPE_TMP, vty->pending_cmds_buf,
108 vty->pending_cmds_buflen);
109 }
110 strlcat(vty->pending_cmds_buf, "- ", vty->pending_cmds_buflen);
111 vty->pending_cmds_bufpos = strlcat(vty->pending_cmds_buf, vty->buf,
112 vty->pending_cmds_buflen);
113
114 /* Schedule the commit operation. */
115 vty->pending_commit = 1;
116
117 return CMD_SUCCESS;
118 }
119
120 void nb_cli_enqueue_change(struct vty *vty, const char *xpath,
121 enum nb_operation operation, const char *value)
122 {
123 struct vty_cfg_change *change;
124
125 if (vty->num_cfg_changes == VTY_MAXCFGCHANGES) {
126 /* Not expected to happen. */
127 vty_out(vty,
128 "%% Exceeded the maximum number of changes (%u) for a single command\n\n",
129 VTY_MAXCFGCHANGES);
130 return;
131 }
132
133 change = &vty->cfg_changes[vty->num_cfg_changes++];
134 strlcpy(change->xpath, xpath, sizeof(change->xpath));
135 change->operation = operation;
136 change->value = value;
137 }
138
139 static int nb_cli_apply_changes_internal(struct vty *vty,
140 const char *xpath_base,
141 bool clear_pending)
142 {
143 bool error = false;
144
145 if (xpath_base == NULL)
146 xpath_base = "";
147
148 VTY_CHECK_XPATH;
149
150 /* Edit candidate configuration. */
151 for (size_t i = 0; i < vty->num_cfg_changes; i++) {
152 struct vty_cfg_change *change = &vty->cfg_changes[i];
153 struct nb_node *nb_node;
154 char xpath[XPATH_MAXLEN];
155 struct yang_data *data;
156 int ret;
157
158 /* Handle relative XPaths. */
159 memset(xpath, 0, sizeof(xpath));
160 if (vty->xpath_index > 0
161 && (xpath_base[0] == '.' || change->xpath[0] == '.'))
162 strlcpy(xpath, VTY_CURR_XPATH, sizeof(xpath));
163 if (xpath_base[0]) {
164 if (xpath_base[0] == '.')
165 strlcat(xpath, xpath_base + 1, sizeof(xpath));
166 else
167 strlcat(xpath, xpath_base, sizeof(xpath));
168 }
169 if (change->xpath[0] == '.')
170 strlcat(xpath, change->xpath + 1, sizeof(xpath));
171 else
172 strlcpy(xpath, change->xpath, sizeof(xpath));
173
174 /* Find the northbound node associated to the data path. */
175 nb_node = nb_node_find(xpath);
176 if (!nb_node) {
177 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
178 "%s: unknown data path: %s", __func__, xpath);
179 error = true;
180 continue;
181 }
182
183 /* If the value is not set, get the default if it exists. */
184 if (change->value == NULL)
185 change->value = yang_snode_get_default(nb_node->snode);
186 data = yang_data_new(xpath, change->value);
187
188 /*
189 * Ignore "not found" errors when editing the candidate
190 * configuration.
191 */
192 ret = nb_candidate_edit(vty->candidate_config, nb_node,
193 change->operation, xpath, NULL, data);
194 yang_data_free(data);
195 if (ret != NB_OK && ret != NB_ERR_NOT_FOUND) {
196 flog_warn(
197 EC_LIB_NB_CANDIDATE_EDIT_ERROR,
198 "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
199 __func__, nb_operation_name(change->operation),
200 xpath);
201 error = true;
202 continue;
203 }
204 }
205
206 if (error) {
207 char buf[BUFSIZ];
208
209 /*
210 * Failure to edit the candidate configuration should never
211 * happen in practice, unless there's a bug in the code. When
212 * that happens, log the error but otherwise ignore it.
213 */
214 vty_out(vty, "%% Failed to edit configuration.\n\n");
215 vty_out(vty, "%s",
216 yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
217 }
218
219 /*
220 * Maybe do an implicit commit when using the classic CLI mode.
221 *
222 * NOTE: the implicit commit might be scheduled to run later when
223 * too many commands are being sent at the same time. This is a
224 * protection mechanism where multiple commands are grouped into the
225 * same configuration transaction, allowing them to be processed much
226 * faster.
227 */
228 if (frr_get_cli_mode() == FRR_CLI_CLASSIC) {
229 if (clear_pending) {
230 if (vty->pending_commit)
231 return nb_cli_pending_commit_check(vty);
232 } else if (vty->pending_allowed)
233 return nb_cli_schedule_command(vty);
234 assert(!vty->pending_commit);
235 return nb_cli_classic_commit(vty);
236 }
237
238 return CMD_SUCCESS;
239 }
240
241 int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, ...)
242 {
243 char xpath_base[XPATH_MAXLEN] = {};
244
245 /* Parse the base XPath format string. */
246 if (xpath_base_fmt) {
247 va_list ap;
248
249 va_start(ap, xpath_base_fmt);
250 vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap);
251 va_end(ap);
252 }
253 return nb_cli_apply_changes_internal(vty, xpath_base, false);
254 }
255
256 int nb_cli_apply_changes_clear_pending(struct vty *vty,
257 const char *xpath_base_fmt, ...)
258 {
259 char xpath_base[XPATH_MAXLEN] = {};
260
261 /* Parse the base XPath format string. */
262 if (xpath_base_fmt) {
263 va_list ap;
264
265 va_start(ap, xpath_base_fmt);
266 vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap);
267 va_end(ap);
268 }
269 return nb_cli_apply_changes_internal(vty, xpath_base, true);
270 }
271
272 int nb_cli_rpc(struct vty *vty, const char *xpath, struct list *input,
273 struct list *output)
274 {
275 struct nb_node *nb_node;
276 int ret;
277 char errmsg[BUFSIZ] = {0};
278
279 nb_node = nb_node_find(xpath);
280 if (!nb_node) {
281 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
282 "%s: unknown data path: %s", __func__, xpath);
283 return CMD_WARNING;
284 }
285
286 ret = nb_callback_rpc(nb_node, xpath, input, output, errmsg,
287 sizeof(errmsg));
288 switch (ret) {
289 case NB_OK:
290 return CMD_SUCCESS;
291 default:
292 if (strlen(errmsg))
293 vty_show_nb_errors(vty, ret, errmsg);
294 return CMD_WARNING;
295 }
296 }
297
298 void nb_cli_confirmed_commit_clean(struct vty *vty)
299 {
300 thread_cancel(&vty->t_confirmed_commit_timeout);
301 nb_config_free(vty->confirmed_commit_rollback);
302 vty->confirmed_commit_rollback = NULL;
303 }
304
305 int nb_cli_confirmed_commit_rollback(struct vty *vty)
306 {
307 struct nb_context context = {};
308 uint32_t transaction_id;
309 char errmsg[BUFSIZ] = {0};
310 int ret;
311
312 /* Perform the rollback. */
313 context.client = NB_CLIENT_CLI;
314 context.user = vty;
315 ret = nb_candidate_commit(
316 context, vty->confirmed_commit_rollback, true,
317 "Rollback to previous configuration - confirmed commit has timed out",
318 &transaction_id, errmsg, sizeof(errmsg));
319 if (ret == NB_OK) {
320 vty_out(vty,
321 "Rollback performed successfully (Transaction ID #%u).\n",
322 transaction_id);
323 /* Print warnings (if any). */
324 if (strlen(errmsg) > 0)
325 vty_out(vty, "%s\n", errmsg);
326 } else {
327 vty_out(vty,
328 "Failed to rollback to previous configuration.\n\n");
329 vty_show_nb_errors(vty, ret, errmsg);
330 }
331
332 return ret;
333 }
334
335 static void nb_cli_confirmed_commit_timeout(struct thread *thread)
336 {
337 struct vty *vty = THREAD_ARG(thread);
338
339 /* XXX: broadcast this message to all logged-in users? */
340 vty_out(vty,
341 "\nConfirmed commit has timed out, rolling back to previous configuration.\n\n");
342
343 nb_cli_confirmed_commit_rollback(vty);
344 nb_cli_confirmed_commit_clean(vty);
345 }
346
347 static int nb_cli_commit(struct vty *vty, bool force,
348 unsigned int confirmed_timeout, char *comment)
349 {
350 struct nb_context context = {};
351 uint32_t transaction_id = 0;
352 char errmsg[BUFSIZ] = {0};
353 int ret;
354
355 /* Check if there's a pending confirmed commit. */
356 if (vty->t_confirmed_commit_timeout) {
357 if (confirmed_timeout) {
358 /* Reset timeout if "commit confirmed" is used again. */
359 vty_out(vty,
360 "%% Resetting confirmed-commit timeout to %u minute(s)\n\n",
361 confirmed_timeout);
362
363 thread_cancel(&vty->t_confirmed_commit_timeout);
364 thread_add_timer(master,
365 nb_cli_confirmed_commit_timeout, vty,
366 confirmed_timeout * 60,
367 &vty->t_confirmed_commit_timeout);
368 } else {
369 /* Accept commit confirmation. */
370 vty_out(vty, "%% Commit complete.\n\n");
371 nb_cli_confirmed_commit_clean(vty);
372 }
373 return CMD_SUCCESS;
374 }
375
376 /* "force" parameter. */
377 if (!force && nb_candidate_needs_update(vty->candidate_config)) {
378 vty_out(vty,
379 "%% Candidate configuration needs to be updated before commit.\n\n");
380 vty_out(vty,
381 "Use the \"update\" command or \"commit force\".\n");
382 return CMD_WARNING;
383 }
384
385 /* "confirm" parameter. */
386 if (confirmed_timeout) {
387 vty->confirmed_commit_rollback = nb_config_dup(running_config);
388
389 vty->t_confirmed_commit_timeout = NULL;
390 thread_add_timer(master, nb_cli_confirmed_commit_timeout, vty,
391 confirmed_timeout * 60,
392 &vty->t_confirmed_commit_timeout);
393 }
394
395 context.client = NB_CLIENT_CLI;
396 context.user = vty;
397 ret = nb_candidate_commit(context, vty->candidate_config, true, comment,
398 &transaction_id, errmsg, sizeof(errmsg));
399
400 /* Map northbound return code to CLI return code. */
401 switch (ret) {
402 case NB_OK:
403 nb_config_replace(vty->candidate_config_base, running_config,
404 true);
405 vty_out(vty,
406 "%% Configuration committed successfully (Transaction ID #%u).\n\n",
407 transaction_id);
408 /* Print warnings (if any). */
409 if (strlen(errmsg) > 0)
410 vty_out(vty, "%s\n", errmsg);
411 return CMD_SUCCESS;
412 case NB_ERR_NO_CHANGES:
413 vty_out(vty, "%% No configuration changes to commit.\n\n");
414 return CMD_SUCCESS;
415 default:
416 vty_out(vty,
417 "%% Failed to commit candidate configuration.\n\n");
418 vty_show_nb_errors(vty, ret, errmsg);
419 return CMD_WARNING;
420 }
421 }
422
423 static int nb_cli_candidate_load_file(struct vty *vty,
424 enum nb_cfg_format format,
425 struct yang_translator *translator,
426 const char *path, bool replace)
427 {
428 struct nb_config *loaded_config = NULL;
429 struct lyd_node *dnode;
430 struct ly_ctx *ly_ctx;
431 int ly_format;
432 char buf[BUFSIZ];
433 LY_ERR err;
434
435 switch (format) {
436 case NB_CFG_FMT_CMDS:
437 loaded_config = nb_config_new(NULL);
438 if (!vty_read_config(loaded_config, path, config_default)) {
439 vty_out(vty, "%% Failed to load configuration.\n\n");
440 vty_out(vty,
441 "Please check the logs for more details.\n");
442 nb_config_free(loaded_config);
443 return CMD_WARNING;
444 }
445 break;
446 case NB_CFG_FMT_JSON:
447 case NB_CFG_FMT_XML:
448 ly_format = (format == NB_CFG_FMT_JSON) ? LYD_JSON : LYD_XML;
449
450 ly_ctx = translator ? translator->ly_ctx : ly_native_ctx;
451 err = lyd_parse_data_path(ly_ctx, path, ly_format,
452 LYD_PARSE_ONLY | LYD_PARSE_NO_STATE,
453 0, &dnode);
454 if (err || !dnode) {
455 flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_path() failed",
456 __func__);
457 vty_out(vty, "%% Failed to load configuration:\n\n");
458 vty_out(vty, "%s",
459 yang_print_errors(ly_native_ctx, buf,
460 sizeof(buf)));
461 return CMD_WARNING;
462 }
463 if (translator
464 && yang_translate_dnode(translator,
465 YANG_TRANSLATE_TO_NATIVE, &dnode)
466 != YANG_TRANSLATE_SUCCESS) {
467 vty_out(vty, "%% Failed to translate configuration\n");
468 yang_dnode_free(dnode);
469 return CMD_WARNING;
470 }
471 loaded_config = nb_config_new(dnode);
472 break;
473 }
474
475 if (replace)
476 nb_config_replace(vty->candidate_config, loaded_config, false);
477 else if (nb_config_merge(vty->candidate_config, loaded_config, false)
478 != NB_OK) {
479 vty_out(vty,
480 "%% Failed to merge the loaded configuration:\n\n");
481 vty_out(vty, "%s",
482 yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
483 return CMD_WARNING;
484 }
485
486 return CMD_SUCCESS;
487 }
488
489 static int nb_cli_candidate_load_transaction(struct vty *vty,
490 uint32_t transaction_id,
491 bool replace)
492 {
493 struct nb_config *loaded_config;
494 char buf[BUFSIZ];
495
496 loaded_config = nb_db_transaction_load(transaction_id);
497 if (!loaded_config) {
498 vty_out(vty, "%% Transaction %u does not exist.\n\n",
499 transaction_id);
500 return CMD_WARNING;
501 }
502
503 if (replace)
504 nb_config_replace(vty->candidate_config, loaded_config, false);
505 else if (nb_config_merge(vty->candidate_config, loaded_config, false)
506 != NB_OK) {
507 vty_out(vty,
508 "%% Failed to merge the loaded configuration:\n\n");
509 vty_out(vty, "%s",
510 yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
511 return CMD_WARNING;
512 }
513
514 return CMD_SUCCESS;
515 }
516
517 /* Prepare the configuration for display. */
518 void nb_cli_show_config_prepare(struct nb_config *config, bool with_defaults)
519 {
520 /* Nothing to do for daemons that don't implement any YANG module. */
521 if (config->dnode == NULL)
522 return;
523
524 /*
525 * Call lyd_validate() only to create default child nodes, ignoring
526 * any possible validation error. This doesn't need to be done when
527 * displaying the running configuration since it's always fully
528 * validated.
529 */
530 if (config != running_config)
531 (void)lyd_validate_all(&config->dnode, ly_native_ctx,
532 LYD_VALIDATE_NO_STATE, NULL);
533 }
534
535 static int lyd_node_cmp(const struct lyd_node **dnode1,
536 const struct lyd_node **dnode2)
537 {
538 struct nb_node *nb_node = (*dnode1)->schema->priv;
539
540 return nb_node->cbs.cli_cmp(*dnode1, *dnode2);
541 }
542
543 static void show_dnode_children_cmds(struct vty *vty,
544 const struct lyd_node *root,
545 bool with_defaults)
546 {
547 struct nb_node *nb_node, *sort_node = NULL;
548 struct listnode *listnode;
549 struct lyd_node *child;
550 struct list *sort_list;
551 void *data;
552
553 LY_LIST_FOR (lyd_child(root), child) {
554 nb_node = child->schema->priv;
555
556 /*
557 * We finished processing current list,
558 * it's time to print the config.
559 */
560 if (sort_node && nb_node != sort_node) {
561 list_sort(sort_list,
562 (int (*)(const void **,
563 const void **))lyd_node_cmp);
564
565 for (ALL_LIST_ELEMENTS_RO(sort_list, listnode, data))
566 nb_cli_show_dnode_cmds(vty, data,
567 with_defaults);
568
569 list_delete(&sort_list);
570 sort_node = NULL;
571 }
572
573 /*
574 * If the config needs to be sorted,
575 * then add the dnode to the sorting
576 * list for later processing.
577 */
578 if (nb_node && nb_node->cbs.cli_cmp) {
579 if (!sort_node) {
580 sort_node = nb_node;
581 sort_list = list_new();
582 }
583
584 listnode_add(sort_list, child);
585 continue;
586 }
587
588 nb_cli_show_dnode_cmds(vty, child, with_defaults);
589 }
590
591 if (sort_node) {
592 list_sort(sort_list,
593 (int (*)(const void **, const void **))lyd_node_cmp);
594
595 for (ALL_LIST_ELEMENTS_RO(sort_list, listnode, data))
596 nb_cli_show_dnode_cmds(vty, data, with_defaults);
597
598 list_delete(&sort_list);
599 sort_node = NULL;
600 }
601 }
602
603 void nb_cli_show_dnode_cmds(struct vty *vty, const struct lyd_node *root,
604 bool with_defaults)
605 {
606 struct nb_node *nb_node;
607
608 if (!with_defaults && yang_dnode_is_default_recursive(root))
609 return;
610
611 nb_node = root->schema->priv;
612
613 if (nb_node && nb_node->cbs.cli_show)
614 (*nb_node->cbs.cli_show)(vty, root, with_defaults);
615
616 if (!(root->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)))
617 show_dnode_children_cmds(vty, root, with_defaults);
618
619 if (nb_node && nb_node->cbs.cli_show_end)
620 (*nb_node->cbs.cli_show_end)(vty, root);
621 }
622
623 static void nb_cli_show_config_cmds(struct vty *vty, struct nb_config *config,
624 bool with_defaults)
625 {
626 struct lyd_node *root;
627
628 vty_out(vty, "Configuration:\n");
629 vty_out(vty, "!\n");
630 vty_out(vty, "frr version %s\n", FRR_VER_SHORT);
631 vty_out(vty, "frr defaults %s\n", frr_defaults_profile());
632
633 LY_LIST_FOR (config->dnode, root) {
634 nb_cli_show_dnode_cmds(vty, root, with_defaults);
635 }
636
637 vty_out(vty, "!\n");
638 vty_out(vty, "end\n");
639 }
640
641 static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format,
642 struct nb_config *config,
643 struct yang_translator *translator,
644 bool with_defaults)
645 {
646 struct lyd_node *dnode;
647 char *strp;
648 int options = 0;
649
650 dnode = yang_dnode_dup(config->dnode);
651 if (translator
652 && yang_translate_dnode(translator, YANG_TRANSLATE_FROM_NATIVE,
653 &dnode)
654 != YANG_TRANSLATE_SUCCESS) {
655 vty_out(vty, "%% Failed to translate configuration\n");
656 yang_dnode_free(dnode);
657 return CMD_WARNING;
658 }
659
660 SET_FLAG(options, LYD_PRINT_WITHSIBLINGS);
661 if (with_defaults)
662 SET_FLAG(options, LYD_PRINT_WD_ALL);
663 else
664 SET_FLAG(options, LYD_PRINT_WD_TRIM);
665
666 if (lyd_print_mem(&strp, dnode, format, options) == 0 && strp) {
667 vty_out(vty, "%s", strp);
668 free(strp);
669 }
670
671 yang_dnode_free(dnode);
672
673 return CMD_SUCCESS;
674 }
675
676 static int nb_cli_show_config(struct vty *vty, struct nb_config *config,
677 enum nb_cfg_format format,
678 struct yang_translator *translator,
679 bool with_defaults)
680 {
681 nb_cli_show_config_prepare(config, with_defaults);
682
683 switch (format) {
684 case NB_CFG_FMT_CMDS:
685 nb_cli_show_config_cmds(vty, config, with_defaults);
686 break;
687 case NB_CFG_FMT_JSON:
688 return nb_cli_show_config_libyang(vty, LYD_JSON, config,
689 translator, with_defaults);
690 case NB_CFG_FMT_XML:
691 return nb_cli_show_config_libyang(vty, LYD_XML, config,
692 translator, with_defaults);
693 }
694
695 return CMD_SUCCESS;
696 }
697
698 static int nb_write_config(struct nb_config *config, enum nb_cfg_format format,
699 struct yang_translator *translator, char *path,
700 size_t pathlen)
701 {
702 int fd;
703 struct vty *file_vty;
704 int ret = 0;
705
706 snprintf(path, pathlen, "/tmp/frr.tmp.XXXXXXXX");
707 fd = mkstemp(path);
708 if (fd < 0) {
709 flog_warn(EC_LIB_SYSTEM_CALL, "%s: mkstemp() failed: %s",
710 __func__, safe_strerror(errno));
711 return -1;
712 }
713 if (fchmod(fd, CONFIGFILE_MASK) != 0) {
714 flog_warn(EC_LIB_SYSTEM_CALL,
715 "%s: fchmod() failed: %s(%d):", __func__,
716 safe_strerror(errno), errno);
717 return -1;
718 }
719
720 /* Make vty for configuration file. */
721 file_vty = vty_new();
722 file_vty->wfd = fd;
723 file_vty->type = VTY_FILE;
724 if (config)
725 ret = nb_cli_show_config(file_vty, config, format, translator,
726 false);
727 vty_close(file_vty);
728
729 return ret;
730 }
731
732 static int nb_cli_show_config_compare(struct vty *vty,
733 struct nb_config *config1,
734 struct nb_config *config2,
735 enum nb_cfg_format format,
736 struct yang_translator *translator)
737 {
738 char config1_path[256];
739 char config2_path[256];
740 char command[BUFSIZ];
741 FILE *fp;
742 char line[1024];
743 int lineno = 0;
744
745 if (nb_write_config(config1, format, translator, config1_path,
746 sizeof(config1_path))
747 != 0) {
748 vty_out(vty, "%% Failed to process configurations.\n\n");
749 return CMD_WARNING;
750 }
751 if (nb_write_config(config2, format, translator, config2_path,
752 sizeof(config2_path))
753 != 0) {
754 vty_out(vty, "%% Failed to process configurations.\n\n");
755 unlink(config1_path);
756 return CMD_WARNING;
757 }
758
759 snprintf(command, sizeof(command), "diff -u %s %s", config1_path,
760 config2_path);
761 fp = popen(command, "r");
762 if (!fp) {
763 vty_out(vty, "%% Failed to generate configuration diff.\n\n");
764 unlink(config1_path);
765 unlink(config2_path);
766 return CMD_WARNING;
767 }
768 /* Print diff line by line. */
769 while (fgets(line, sizeof(line), fp) != NULL) {
770 if (lineno++ < 2)
771 continue;
772 vty_out(vty, "%s", line);
773 }
774 pclose(fp);
775
776 unlink(config1_path);
777 unlink(config2_path);
778
779 return CMD_SUCCESS;
780 }
781
782 /* Configure exclusively from this terminal. */
783 DEFUN (config_exclusive,
784 config_exclusive_cmd,
785 "configure exclusive",
786 "Configuration from vty interface\n"
787 "Configure exclusively from this terminal\n")
788 {
789 return vty_config_enter(vty, true, true);
790 }
791
792 /* Configure using a private candidate configuration. */
793 DEFUN (config_private,
794 config_private_cmd,
795 "configure private",
796 "Configuration from vty interface\n"
797 "Configure using a private candidate configuration\n")
798 {
799 return vty_config_enter(vty, true, false);
800 }
801
802 DEFPY (config_commit,
803 config_commit_cmd,
804 "commit [{force$force|confirmed (1-60)}]",
805 "Commit changes into the running configuration\n"
806 "Force commit even if the candidate is outdated\n"
807 "Rollback this commit unless there is a confirming commit\n"
808 "Timeout in minutes for the commit to be confirmed\n")
809 {
810 return nb_cli_commit(vty, !!force, confirmed, NULL);
811 }
812
813 DEFPY (config_commit_comment,
814 config_commit_comment_cmd,
815 "commit [{force$force|confirmed (1-60)}] comment LINE...",
816 "Commit changes into the running configuration\n"
817 "Force commit even if the candidate is outdated\n"
818 "Rollback this commit unless there is a confirming commit\n"
819 "Timeout in minutes for the commit to be confirmed\n"
820 "Assign a comment to this commit\n"
821 "Comment for this commit (Max 80 characters)\n")
822 {
823 char *comment;
824 int idx = 0;
825 int ret;
826
827 argv_find(argv, argc, "LINE", &idx);
828 comment = argv_concat(argv, argc, idx);
829 ret = nb_cli_commit(vty, !!force, confirmed, comment);
830 XFREE(MTYPE_TMP, comment);
831
832 return ret;
833 }
834
835 DEFPY (config_commit_check,
836 config_commit_check_cmd,
837 "commit check",
838 "Commit changes into the running configuration\n"
839 "Check if the configuration changes are valid\n")
840 {
841 struct nb_context context = {};
842 char errmsg[BUFSIZ] = {0};
843 int ret;
844
845 context.client = NB_CLIENT_CLI;
846 context.user = vty;
847 ret = nb_candidate_validate(&context, vty->candidate_config, errmsg,
848 sizeof(errmsg));
849 if (ret != NB_OK) {
850 vty_out(vty,
851 "%% Failed to validate candidate configuration.\n\n");
852 vty_show_nb_errors(vty, ret, errmsg);
853 return CMD_WARNING;
854 }
855
856 vty_out(vty, "%% Candidate configuration validated successfully.\n\n");
857
858 return CMD_SUCCESS;
859 }
860
861 DEFPY (config_update,
862 config_update_cmd,
863 "update",
864 "Update candidate configuration\n")
865 {
866 if (!nb_candidate_needs_update(vty->candidate_config)) {
867 vty_out(vty, "%% Update is not necessary.\n\n");
868 return CMD_SUCCESS;
869 }
870
871 if (nb_candidate_update(vty->candidate_config) != NB_OK) {
872 vty_out(vty,
873 "%% Failed to update the candidate configuration.\n\n");
874 vty_out(vty, "Please check the logs for more details.\n");
875 return CMD_WARNING;
876 }
877
878 nb_config_replace(vty->candidate_config_base, running_config, true);
879
880 vty_out(vty, "%% Candidate configuration updated successfully.\n\n");
881
882 return CMD_SUCCESS;
883 }
884
885 DEFPY (config_discard,
886 config_discard_cmd,
887 "discard",
888 "Discard changes in the candidate configuration\n")
889 {
890 nb_config_replace(vty->candidate_config, vty->candidate_config_base,
891 true);
892
893 return CMD_SUCCESS;
894 }
895
896 DEFPY (config_load,
897 config_load_cmd,
898 "configuration load\
899 <\
900 file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
901 |transaction (1-4294967295)$tid\
902 >\
903 [replace$replace]",
904 "Configuration related settings\n"
905 "Load configuration into candidate\n"
906 "Load configuration file into candidate\n"
907 "Load configuration file in JSON format\n"
908 "Load configuration file in XML format\n"
909 "Translate configuration file\n"
910 "YANG module translator\n"
911 "Configuration file name (full path)\n"
912 "Load configuration from transaction into candidate\n"
913 "Transaction ID\n"
914 "Replace instead of merge\n")
915 {
916 if (filename) {
917 enum nb_cfg_format format;
918 struct yang_translator *translator = NULL;
919
920 if (json)
921 format = NB_CFG_FMT_JSON;
922 else if (xml)
923 format = NB_CFG_FMT_XML;
924 else
925 format = NB_CFG_FMT_CMDS;
926
927 if (translator_family) {
928 translator = yang_translator_find(translator_family);
929 if (!translator) {
930 vty_out(vty,
931 "%% Module translator \"%s\" not found\n",
932 translator_family);
933 return CMD_WARNING;
934 }
935 }
936
937 return nb_cli_candidate_load_file(vty, format, translator,
938 filename, !!replace);
939 }
940
941 return nb_cli_candidate_load_transaction(vty, tid, !!replace);
942 }
943
944 DEFPY (show_config_running,
945 show_config_running_cmd,
946 "show configuration running\
947 [<json$json|xml$xml> [translate WORD$translator_family]]\
948 [with-defaults$with_defaults]",
949 SHOW_STR
950 "Configuration information\n"
951 "Running configuration\n"
952 "Change output format to JSON\n"
953 "Change output format to XML\n"
954 "Translate output\n"
955 "YANG module translator\n"
956 "Show default values\n")
957
958 {
959 enum nb_cfg_format format;
960 struct yang_translator *translator = NULL;
961
962 if (json)
963 format = NB_CFG_FMT_JSON;
964 else if (xml)
965 format = NB_CFG_FMT_XML;
966 else
967 format = NB_CFG_FMT_CMDS;
968
969 if (translator_family) {
970 translator = yang_translator_find(translator_family);
971 if (!translator) {
972 vty_out(vty, "%% Module translator \"%s\" not found\n",
973 translator_family);
974 return CMD_WARNING;
975 }
976 }
977
978 nb_cli_show_config(vty, running_config, format, translator,
979 !!with_defaults);
980
981 return CMD_SUCCESS;
982 }
983
984 DEFPY (show_config_candidate,
985 show_config_candidate_cmd,
986 "show configuration candidate\
987 [<json$json|xml$xml> [translate WORD$translator_family]]\
988 [<\
989 with-defaults$with_defaults\
990 |changes$changes\
991 >]",
992 SHOW_STR
993 "Configuration information\n"
994 "Candidate configuration\n"
995 "Change output format to JSON\n"
996 "Change output format to XML\n"
997 "Translate output\n"
998 "YANG module translator\n"
999 "Show default values\n"
1000 "Show changes applied in the candidate configuration\n")
1001
1002 {
1003 enum nb_cfg_format format;
1004 struct yang_translator *translator = NULL;
1005
1006 if (json)
1007 format = NB_CFG_FMT_JSON;
1008 else if (xml)
1009 format = NB_CFG_FMT_XML;
1010 else
1011 format = NB_CFG_FMT_CMDS;
1012
1013 if (translator_family) {
1014 translator = yang_translator_find(translator_family);
1015 if (!translator) {
1016 vty_out(vty, "%% Module translator \"%s\" not found\n",
1017 translator_family);
1018 return CMD_WARNING;
1019 }
1020 }
1021
1022 if (changes)
1023 return nb_cli_show_config_compare(
1024 vty, vty->candidate_config_base, vty->candidate_config,
1025 format, translator);
1026
1027 nb_cli_show_config(vty, vty->candidate_config, format, translator,
1028 !!with_defaults);
1029
1030 return CMD_SUCCESS;
1031 }
1032
1033 DEFPY (show_config_candidate_section,
1034 show_config_candidate_section_cmd,
1035 "show",
1036 SHOW_STR)
1037 {
1038 struct lyd_node *dnode;
1039
1040 /* Top-level configuration node, display everything. */
1041 if (vty->xpath_index == 0)
1042 return nb_cli_show_config(vty, vty->candidate_config,
1043 NB_CFG_FMT_CMDS, NULL, false);
1044
1045 /* Display only the current section of the candidate configuration. */
1046 dnode = yang_dnode_get(vty->candidate_config->dnode, VTY_CURR_XPATH);
1047 if (!dnode)
1048 /* Shouldn't happen. */
1049 return CMD_WARNING;
1050
1051 nb_cli_show_dnode_cmds(vty, dnode, 0);
1052 vty_out(vty, "!\n");
1053
1054 return CMD_SUCCESS;
1055 }
1056
1057 DEFPY (show_config_compare,
1058 show_config_compare_cmd,
1059 "show configuration compare\
1060 <\
1061 candidate$c1_candidate\
1062 |running$c1_running\
1063 |transaction (1-4294967295)$c1_tid\
1064 >\
1065 <\
1066 candidate$c2_candidate\
1067 |running$c2_running\
1068 |transaction (1-4294967295)$c2_tid\
1069 >\
1070 [<json$json|xml$xml> [translate WORD$translator_family]]",
1071 SHOW_STR
1072 "Configuration information\n"
1073 "Compare two different configurations\n"
1074 "Candidate configuration\n"
1075 "Running configuration\n"
1076 "Configuration transaction\n"
1077 "Transaction ID\n"
1078 "Candidate configuration\n"
1079 "Running configuration\n"
1080 "Configuration transaction\n"
1081 "Transaction ID\n"
1082 "Change output format to JSON\n"
1083 "Change output format to XML\n"
1084 "Translate output\n"
1085 "YANG module translator\n")
1086 {
1087 enum nb_cfg_format format;
1088 struct yang_translator *translator = NULL;
1089 struct nb_config *config1, *config_transaction1 = NULL;
1090 struct nb_config *config2, *config_transaction2 = NULL;
1091 int ret = CMD_WARNING;
1092
1093 if (c1_candidate)
1094 config1 = vty->candidate_config;
1095 else if (c1_running)
1096 config1 = running_config;
1097 else {
1098 config_transaction1 = nb_db_transaction_load(c1_tid);
1099 if (!config_transaction1) {
1100 vty_out(vty, "%% Transaction %u does not exist\n\n",
1101 (unsigned int)c1_tid);
1102 goto exit;
1103 }
1104 config1 = config_transaction1;
1105 }
1106
1107 if (c2_candidate)
1108 config2 = vty->candidate_config;
1109 else if (c2_running)
1110 config2 = running_config;
1111 else {
1112 config_transaction2 = nb_db_transaction_load(c2_tid);
1113 if (!config_transaction2) {
1114 vty_out(vty, "%% Transaction %u does not exist\n\n",
1115 (unsigned int)c2_tid);
1116 goto exit;
1117 }
1118 config2 = config_transaction2;
1119 }
1120
1121 if (json)
1122 format = NB_CFG_FMT_JSON;
1123 else if (xml)
1124 format = NB_CFG_FMT_XML;
1125 else
1126 format = NB_CFG_FMT_CMDS;
1127
1128 if (translator_family) {
1129 translator = yang_translator_find(translator_family);
1130 if (!translator) {
1131 vty_out(vty, "%% Module translator \"%s\" not found\n",
1132 translator_family);
1133 goto exit;
1134 }
1135 }
1136
1137 ret = nb_cli_show_config_compare(vty, config1, config2, format,
1138 translator);
1139 exit:
1140 if (config_transaction1)
1141 nb_config_free(config_transaction1);
1142 if (config_transaction2)
1143 nb_config_free(config_transaction2);
1144
1145 return ret;
1146 }
1147
1148 /*
1149 * Stripped down version of the "show configuration compare" command.
1150 * The "candidate" option is not present so the command can be installed in
1151 * the enable node.
1152 */
1153 ALIAS (show_config_compare,
1154 show_config_compare_without_candidate_cmd,
1155 "show configuration compare\
1156 <\
1157 running$c1_running\
1158 |transaction (1-4294967295)$c1_tid\
1159 >\
1160 <\
1161 running$c2_running\
1162 |transaction (1-4294967295)$c2_tid\
1163 >\
1164 [<json$json|xml$xml> [translate WORD$translator_family]]",
1165 SHOW_STR
1166 "Configuration information\n"
1167 "Compare two different configurations\n"
1168 "Running configuration\n"
1169 "Configuration transaction\n"
1170 "Transaction ID\n"
1171 "Running configuration\n"
1172 "Configuration transaction\n"
1173 "Transaction ID\n"
1174 "Change output format to JSON\n"
1175 "Change output format to XML\n"
1176 "Translate output\n"
1177 "YANG module translator\n")
1178
1179 DEFPY (clear_config_transactions,
1180 clear_config_transactions_cmd,
1181 "clear configuration transactions oldest (1-100)$n",
1182 CLEAR_STR
1183 "Configuration activity\n"
1184 "Delete transactions from the transactions log\n"
1185 "Delete oldest <n> transactions\n"
1186 "Number of transactions to delete\n")
1187 {
1188 #ifdef HAVE_CONFIG_ROLLBACKS
1189 if (nb_db_clear_transactions(n) != NB_OK) {
1190 vty_out(vty, "%% Failed to delete transactions.\n\n");
1191 return CMD_WARNING;
1192 }
1193 #else
1194 vty_out(vty,
1195 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1196 #endif /* HAVE_CONFIG_ROLLBACKS */
1197
1198 return CMD_SUCCESS;
1199 }
1200
1201 DEFPY (config_database_max_transactions,
1202 config_database_max_transactions_cmd,
1203 "configuration database max-transactions (1-100)$max",
1204 "Configuration related settings\n"
1205 "Configuration database\n"
1206 "Set the maximum number of transactions to store\n"
1207 "Number of transactions\n")
1208 {
1209 #ifdef HAVE_CONFIG_ROLLBACKS
1210 if (nb_db_set_max_transactions(max) != NB_OK) {
1211 vty_out(vty,
1212 "%% Failed to update the maximum number of transactions.\n\n");
1213 return CMD_WARNING;
1214 }
1215 vty_out(vty,
1216 "%% Maximum number of transactions updated successfully.\n\n");
1217 #else
1218 vty_out(vty,
1219 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1220 #endif /* HAVE_CONFIG_ROLLBACKS */
1221
1222 return CMD_SUCCESS;
1223 }
1224
1225 DEFPY (yang_module_translator_load,
1226 yang_module_translator_load_cmd,
1227 "yang module-translator load FILENAME$filename",
1228 "YANG related settings\n"
1229 "YANG module translator\n"
1230 "Load YANG module translator\n"
1231 "File name (full path)\n")
1232 {
1233 struct yang_translator *translator;
1234
1235 translator = yang_translator_load(filename);
1236 if (!translator) {
1237 vty_out(vty, "%% Failed to load \"%s\"\n\n", filename);
1238 vty_out(vty, "Please check the logs for more details.\n");
1239 return CMD_WARNING;
1240 }
1241
1242 vty_out(vty, "%% Module translator \"%s\" loaded successfully.\n\n",
1243 translator->family);
1244
1245 return CMD_SUCCESS;
1246 }
1247
1248 DEFPY (yang_module_translator_unload_family,
1249 yang_module_translator_unload_cmd,
1250 "yang module-translator unload WORD$translator_family",
1251 "YANG related settings\n"
1252 "YANG module translator\n"
1253 "Unload YANG module translator\n"
1254 "Name of the module translator\n")
1255 {
1256 struct yang_translator *translator;
1257
1258 translator = yang_translator_find(translator_family);
1259 if (!translator) {
1260 vty_out(vty, "%% Module translator \"%s\" not found\n",
1261 translator_family);
1262 return CMD_WARNING;
1263 }
1264
1265 yang_translator_unload(translator);
1266
1267 return CMD_SUCCESS;
1268 }
1269
1270 #ifdef HAVE_CONFIG_ROLLBACKS
1271 static void nb_cli_show_transactions_cb(void *arg, int transaction_id,
1272 const char *client_name,
1273 const char *date, const char *comment)
1274 {
1275 struct ttable *tt = arg;
1276
1277 ttable_add_row(tt, "%d|%s|%s|%s", transaction_id, client_name, date,
1278 comment);
1279 }
1280
1281 static int nb_cli_show_transactions(struct vty *vty)
1282 {
1283 struct ttable *tt;
1284
1285 /* Prepare table. */
1286 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1287 ttable_add_row(tt, "Transaction ID|Client|Date|Comment");
1288 tt->style.cell.rpad = 2;
1289 tt->style.corner = '+';
1290 ttable_restyle(tt);
1291 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1292
1293 /* Fetch transactions from the northbound database. */
1294 if (nb_db_transactions_iterate(nb_cli_show_transactions_cb, tt)
1295 != NB_OK) {
1296 vty_out(vty,
1297 "%% Failed to fetch configuration transactions.\n");
1298 return CMD_WARNING;
1299 }
1300
1301 /* Dump the generated table. */
1302 if (tt->nrows > 1) {
1303 char *table;
1304
1305 table = ttable_dump(tt, "\n");
1306 vty_out(vty, "%s\n", table);
1307 XFREE(MTYPE_TMP, table);
1308 } else
1309 vty_out(vty, "No configuration transactions to display.\n\n");
1310
1311 ttable_del(tt);
1312
1313 return CMD_SUCCESS;
1314 }
1315 #endif /* HAVE_CONFIG_ROLLBACKS */
1316
1317 DEFPY (show_config_transaction,
1318 show_config_transaction_cmd,
1319 "show configuration transaction\
1320 [\
1321 (1-4294967295)$transaction_id\
1322 [<json$json|xml$xml> [translate WORD$translator_family]]\
1323 [<\
1324 with-defaults$with_defaults\
1325 |changes$changes\
1326 >]\
1327 ]",
1328 SHOW_STR
1329 "Configuration information\n"
1330 "Configuration transaction\n"
1331 "Transaction ID\n"
1332 "Change output format to JSON\n"
1333 "Change output format to XML\n"
1334 "Translate output\n"
1335 "YANG module translator\n"
1336 "Show default values\n"
1337 "Show changes compared to the previous transaction\n")
1338 {
1339 #ifdef HAVE_CONFIG_ROLLBACKS
1340 if (transaction_id) {
1341 struct nb_config *config;
1342 enum nb_cfg_format format;
1343 struct yang_translator *translator = NULL;
1344
1345 if (json)
1346 format = NB_CFG_FMT_JSON;
1347 else if (xml)
1348 format = NB_CFG_FMT_XML;
1349 else
1350 format = NB_CFG_FMT_CMDS;
1351
1352 if (translator_family) {
1353 translator = yang_translator_find(translator_family);
1354 if (!translator) {
1355 vty_out(vty,
1356 "%% Module translator \"%s\" not found\n",
1357 translator_family);
1358 return CMD_WARNING;
1359 }
1360 }
1361
1362 config = nb_db_transaction_load(transaction_id);
1363 if (!config) {
1364 vty_out(vty, "%% Transaction %u does not exist.\n\n",
1365 (unsigned int)transaction_id);
1366 return CMD_WARNING;
1367 }
1368
1369 if (changes) {
1370 struct nb_config *prev_config;
1371 int ret;
1372
1373 /* NOTE: this can be NULL. */
1374 prev_config =
1375 nb_db_transaction_load(transaction_id - 1);
1376
1377 ret = nb_cli_show_config_compare(
1378 vty, prev_config, config, format, translator);
1379 if (prev_config)
1380 nb_config_free(prev_config);
1381 nb_config_free(config);
1382
1383 return ret;
1384 }
1385
1386 nb_cli_show_config(vty, config, format, translator,
1387 !!with_defaults);
1388 nb_config_free(config);
1389
1390 return CMD_SUCCESS;
1391 }
1392
1393 return nb_cli_show_transactions(vty);
1394 #else
1395 vty_out(vty,
1396 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1397 return CMD_WARNING;
1398 #endif /* HAVE_CONFIG_ROLLBACKS */
1399 }
1400
1401 static int nb_cli_oper_data_cb(const struct lysc_node *snode,
1402 struct yang_translator *translator,
1403 struct yang_data *data, void *arg)
1404 {
1405 struct lyd_node *dnode = arg;
1406 struct ly_ctx *ly_ctx;
1407
1408 if (translator) {
1409 int ret;
1410
1411 ret = yang_translate_xpath(translator,
1412 YANG_TRANSLATE_FROM_NATIVE,
1413 data->xpath, sizeof(data->xpath));
1414 switch (ret) {
1415 case YANG_TRANSLATE_SUCCESS:
1416 break;
1417 case YANG_TRANSLATE_NOTFOUND:
1418 goto exit;
1419 case YANG_TRANSLATE_FAILURE:
1420 goto error;
1421 }
1422
1423 ly_ctx = translator->ly_ctx;
1424 } else
1425 ly_ctx = ly_native_ctx;
1426
1427 LY_ERR err =
1428 lyd_new_path(dnode, ly_ctx, data->xpath, (void *)data->value,
1429 LYD_NEW_PATH_UPDATE, &dnode);
1430 if (err) {
1431 flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path(%s) failed: %s",
1432 __func__, data->xpath, ly_errmsg(ly_native_ctx));
1433 goto error;
1434 }
1435
1436 exit:
1437 yang_data_free(data);
1438 return NB_OK;
1439
1440 error:
1441 yang_data_free(data);
1442 return NB_ERR;
1443 }
1444
1445 DEFPY (show_yang_operational_data,
1446 show_yang_operational_data_cmd,
1447 "show yang operational-data XPATH$xpath\
1448 [{\
1449 format <json$json|xml$xml>\
1450 |translate WORD$translator_family\
1451 |with-config$with_config\
1452 }]",
1453 SHOW_STR
1454 "YANG information\n"
1455 "Show YANG operational data\n"
1456 "XPath expression specifying the YANG data path\n"
1457 "Set the output format\n"
1458 "JavaScript Object Notation\n"
1459 "Extensible Markup Language\n"
1460 "Translate operational data\n"
1461 "YANG module translator\n"
1462 "Merge configuration data\n")
1463 {
1464 LYD_FORMAT format;
1465 struct yang_translator *translator = NULL;
1466 struct ly_ctx *ly_ctx;
1467 struct lyd_node *dnode;
1468 char *strp;
1469 uint32_t print_options = LYD_PRINT_WITHSIBLINGS;
1470
1471 if (xml)
1472 format = LYD_XML;
1473 else
1474 format = LYD_JSON;
1475
1476 if (translator_family) {
1477 translator = yang_translator_find(translator_family);
1478 if (!translator) {
1479 vty_out(vty, "%% Module translator \"%s\" not found\n",
1480 translator_family);
1481 return CMD_WARNING;
1482 }
1483
1484 ly_ctx = translator->ly_ctx;
1485 } else
1486 ly_ctx = ly_native_ctx;
1487
1488 /* Obtain data. */
1489 dnode = yang_dnode_new(ly_ctx, false);
1490 if (nb_oper_data_iterate(xpath, translator, 0, nb_cli_oper_data_cb,
1491 dnode)
1492 != NB_OK) {
1493 vty_out(vty, "%% Failed to fetch operational data.\n");
1494 yang_dnode_free(dnode);
1495 return CMD_WARNING;
1496 }
1497
1498 if (with_config && yang_dnode_exists(running_config->dnode, xpath)) {
1499 struct lyd_node *config_dnode =
1500 yang_dnode_get(running_config->dnode, xpath);
1501 if (config_dnode != NULL) {
1502 lyd_merge_tree(&dnode, yang_dnode_dup(config_dnode),
1503 LYD_MERGE_DESTRUCT);
1504 print_options |= LYD_PRINT_WD_ALL;
1505 }
1506 }
1507
1508 (void)lyd_validate_all(&dnode, ly_ctx, 0, NULL);
1509
1510 /* Display the data. */
1511 if (lyd_print_mem(&strp, dnode, format, print_options) != 0 || !strp) {
1512 vty_out(vty, "%% Failed to display operational data.\n");
1513 yang_dnode_free(dnode);
1514 return CMD_WARNING;
1515 }
1516 vty_out(vty, "%s", strp);
1517 free(strp);
1518 yang_dnode_free(dnode);
1519
1520 return CMD_SUCCESS;
1521 }
1522
1523 DEFPY (show_yang_module,
1524 show_yang_module_cmd,
1525 "show yang module [module-translator WORD$translator_family]",
1526 SHOW_STR
1527 "YANG information\n"
1528 "Show loaded modules\n"
1529 "YANG module translator\n"
1530 "YANG module translator\n")
1531 {
1532 struct ly_ctx *ly_ctx;
1533 struct yang_translator *translator = NULL;
1534 const struct lys_module *module;
1535 struct ttable *tt;
1536 uint32_t idx = 0;
1537
1538 if (translator_family) {
1539 translator = yang_translator_find(translator_family);
1540 if (!translator) {
1541 vty_out(vty, "%% Module translator \"%s\" not found\n",
1542 translator_family);
1543 return CMD_WARNING;
1544 }
1545 ly_ctx = translator->ly_ctx;
1546 } else
1547 ly_ctx = ly_native_ctx;
1548
1549 /* Prepare table. */
1550 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1551 ttable_add_row(tt, "Module|Version|Revision|Flags|Namespace");
1552 tt->style.cell.rpad = 2;
1553 tt->style.corner = '+';
1554 ttable_restyle(tt);
1555 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1556
1557 while ((module = ly_ctx_get_module_iter(ly_ctx, &idx))) {
1558 char flags[8];
1559
1560 snprintf(flags, sizeof(flags), "%c%c",
1561 module->implemented ? 'I' : ' ',
1562 LY_ARRAY_COUNT(module->deviated_by) ? 'D' : ' ');
1563
1564 ttable_add_row(tt, "%s|%s|%s|%s|%s", module->name,
1565 (module->parsed->version == 2) ? "1.1" : "1.0",
1566 module->revision ? module->revision : "-", flags,
1567 module->ns);
1568 }
1569
1570 /* Dump the generated table. */
1571 if (tt->nrows > 1) {
1572 char *table;
1573
1574 vty_out(vty, " Flags: I - Implemented, D - Deviated\n\n");
1575
1576 table = ttable_dump(tt, "\n");
1577 vty_out(vty, "%s\n", table);
1578 XFREE(MTYPE_TMP, table);
1579 } else
1580 vty_out(vty, "No YANG modules to display.\n\n");
1581
1582 ttable_del(tt);
1583
1584 return CMD_SUCCESS;
1585 }
1586
1587 DEFPY(show_yang_module_detail, show_yang_module_detail_cmd,
1588 "show yang module\
1589 [module-translator WORD$translator_family]\
1590 WORD$module_name <compiled$compiled|summary|tree$tree|yang$yang|yin$yin>",
1591 SHOW_STR
1592 "YANG information\n"
1593 "Show loaded modules\n"
1594 "YANG module translator\n"
1595 "YANG module translator\n"
1596 "Module name\n"
1597 "Display compiled module in YANG format\n"
1598 "Display summary information about the module\n"
1599 "Display module in the tree (RFC 8340) format\n"
1600 "Display module in the YANG format\n"
1601 "Display module in the YIN format\n")
1602 {
1603 struct ly_ctx *ly_ctx;
1604 struct yang_translator *translator = NULL;
1605 const struct lys_module *module;
1606 LYS_OUTFORMAT format;
1607 char *strp;
1608
1609 if (translator_family) {
1610 translator = yang_translator_find(translator_family);
1611 if (!translator) {
1612 vty_out(vty, "%% Module translator \"%s\" not found\n",
1613 translator_family);
1614 return CMD_WARNING;
1615 }
1616 ly_ctx = translator->ly_ctx;
1617 } else
1618 ly_ctx = ly_native_ctx;
1619
1620 module = ly_ctx_get_module_latest(ly_ctx, module_name);
1621 if (!module) {
1622 vty_out(vty, "%% Module \"%s\" not found\n", module_name);
1623 return CMD_WARNING;
1624 }
1625
1626 if (yang)
1627 format = LYS_OUT_YANG;
1628 else if (yin)
1629 format = LYS_OUT_YIN;
1630 else if (compiled)
1631 format = LYS_OUT_YANG_COMPILED;
1632 else if (tree)
1633 format = LYS_OUT_TREE;
1634 else {
1635 vty_out(vty,
1636 "%% libyang v2 does not currently support summary\n");
1637 return CMD_WARNING;
1638 }
1639
1640 if (lys_print_mem(&strp, module, format, 0) == 0) {
1641 vty_out(vty, "%s\n", strp);
1642 free(strp);
1643 } else {
1644 /* Unexpected. */
1645 vty_out(vty, "%% Error generating module information\n");
1646 return CMD_WARNING;
1647 }
1648
1649 return CMD_SUCCESS;
1650 }
1651
1652 DEFPY (show_yang_module_translator,
1653 show_yang_module_translator_cmd,
1654 "show yang module-translator",
1655 SHOW_STR
1656 "YANG information\n"
1657 "Show loaded YANG module translators\n")
1658 {
1659 struct yang_translator *translator;
1660 struct ttable *tt;
1661
1662 /* Prepare table. */
1663 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
1664 ttable_add_row(tt, "Family|Module|Deviations|Coverage (%%)");
1665 tt->style.cell.rpad = 2;
1666 tt->style.corner = '+';
1667 ttable_restyle(tt);
1668 ttable_rowseps(tt, 0, BOTTOM, true, '-');
1669
1670 RB_FOREACH (translator, yang_translators, &yang_translators) {
1671 struct yang_tmodule *tmodule;
1672 struct listnode *ln;
1673
1674 for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
1675 ttable_add_row(tt, "%s|%s|%s|%.2f", translator->family,
1676 tmodule->module->name,
1677 tmodule->deviations->name,
1678 tmodule->coverage);
1679 }
1680 }
1681
1682 /* Dump the generated table. */
1683 if (tt->nrows > 1) {
1684 char *table;
1685
1686 table = ttable_dump(tt, "\n");
1687 vty_out(vty, "%s\n", table);
1688 XFREE(MTYPE_TMP, table);
1689 } else
1690 vty_out(vty, "No YANG module translators to display.\n\n");
1691
1692 ttable_del(tt);
1693
1694 return CMD_SUCCESS;
1695 }
1696
1697 #ifdef HAVE_CONFIG_ROLLBACKS
1698 static int nb_cli_rollback_configuration(struct vty *vty,
1699 uint32_t transaction_id)
1700 {
1701 struct nb_context context = {};
1702 struct nb_config *candidate;
1703 char comment[80];
1704 char errmsg[BUFSIZ] = {0};
1705 int ret;
1706
1707 candidate = nb_db_transaction_load(transaction_id);
1708 if (!candidate) {
1709 vty_out(vty, "%% Transaction %u does not exist.\n\n",
1710 transaction_id);
1711 return CMD_WARNING;
1712 }
1713
1714 snprintf(comment, sizeof(comment), "Rollback to transaction %u",
1715 transaction_id);
1716
1717 context.client = NB_CLIENT_CLI;
1718 context.user = vty;
1719 ret = nb_candidate_commit(context, candidate, true, comment, NULL,
1720 errmsg, sizeof(errmsg));
1721 nb_config_free(candidate);
1722 switch (ret) {
1723 case NB_OK:
1724 vty_out(vty,
1725 "%% Configuration was successfully rolled back.\n\n");
1726 /* Print warnings (if any). */
1727 if (strlen(errmsg) > 0)
1728 vty_out(vty, "%s\n", errmsg);
1729 return CMD_SUCCESS;
1730 case NB_ERR_NO_CHANGES:
1731 vty_out(vty,
1732 "%% Aborting - no configuration changes detected.\n\n");
1733 return CMD_WARNING;
1734 default:
1735 vty_out(vty, "%% Rollback failed.\n\n");
1736 vty_show_nb_errors(vty, ret, errmsg);
1737 return CMD_WARNING;
1738 }
1739 }
1740 #endif /* HAVE_CONFIG_ROLLBACKS */
1741
1742 DEFPY (rollback_config,
1743 rollback_config_cmd,
1744 "rollback configuration (1-4294967295)$transaction_id",
1745 "Rollback to a previous state\n"
1746 "Running configuration\n"
1747 "Transaction ID\n")
1748 {
1749 #ifdef HAVE_CONFIG_ROLLBACKS
1750 return nb_cli_rollback_configuration(vty, transaction_id);
1751 #else
1752 vty_out(vty,
1753 "%% FRR was compiled without --enable-config-rollbacks.\n\n");
1754 return CMD_SUCCESS;
1755 #endif /* HAVE_CONFIG_ROLLBACKS */
1756 }
1757
1758 /* Debug CLI commands. */
1759 static struct debug *nb_debugs[] = {
1760 &nb_dbg_cbs_config, &nb_dbg_cbs_state, &nb_dbg_cbs_rpc,
1761 &nb_dbg_notif, &nb_dbg_events, &nb_dbg_libyang,
1762 };
1763
1764 static const char *const nb_debugs_conflines[] = {
1765 "debug northbound callbacks configuration",
1766 "debug northbound callbacks state",
1767 "debug northbound callbacks rpc",
1768 "debug northbound notifications",
1769 "debug northbound events",
1770 "debug northbound libyang",
1771 };
1772
1773 DEFINE_HOOK(nb_client_debug_set_all, (uint32_t flags, bool set), (flags, set));
1774
1775 static void nb_debug_set_all(uint32_t flags, bool set)
1776 {
1777 for (unsigned int i = 0; i < array_size(nb_debugs); i++) {
1778 DEBUG_FLAGS_SET(nb_debugs[i], flags, set);
1779
1780 /* If all modes have been turned off, don't preserve options. */
1781 if (!DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_ALL))
1782 DEBUG_CLEAR(nb_debugs[i]);
1783 }
1784
1785 hook_call(nb_client_debug_set_all, flags, set);
1786 }
1787
1788 DEFPY (debug_nb,
1789 debug_nb_cmd,
1790 "[no] debug northbound\
1791 [<\
1792 callbacks$cbs [{configuration$cbs_cfg|state$cbs_state|rpc$cbs_rpc}]\
1793 |notifications$notifications\
1794 |events$events\
1795 |libyang$libyang\
1796 >]",
1797 NO_STR
1798 DEBUG_STR
1799 "Northbound debugging\n"
1800 "Callbacks\n"
1801 "Configuration\n"
1802 "State\n"
1803 "RPC\n"
1804 "Notifications\n"
1805 "Events\n"
1806 "libyang debugging\n")
1807 {
1808 uint32_t mode = DEBUG_NODE2MODE(vty->node);
1809
1810 if (cbs) {
1811 bool none = (!cbs_cfg && !cbs_state && !cbs_rpc);
1812
1813 if (none || cbs_cfg)
1814 DEBUG_MODE_SET(&nb_dbg_cbs_config, mode, !no);
1815 if (none || cbs_state)
1816 DEBUG_MODE_SET(&nb_dbg_cbs_state, mode, !no);
1817 if (none || cbs_rpc)
1818 DEBUG_MODE_SET(&nb_dbg_cbs_rpc, mode, !no);
1819 }
1820 if (notifications)
1821 DEBUG_MODE_SET(&nb_dbg_notif, mode, !no);
1822 if (events)
1823 DEBUG_MODE_SET(&nb_dbg_events, mode, !no);
1824 if (libyang) {
1825 DEBUG_MODE_SET(&nb_dbg_libyang, mode, !no);
1826 yang_debugging_set(!no);
1827 }
1828
1829 /* no specific debug --> act on all of them */
1830 if (strmatch(argv[argc - 1]->text, "northbound")) {
1831 nb_debug_set_all(mode, !no);
1832 yang_debugging_set(!no);
1833 }
1834
1835 return CMD_SUCCESS;
1836 }
1837
1838 DEFINE_HOOK(nb_client_debug_config_write, (struct vty *vty), (vty));
1839
1840 static int nb_debug_config_write(struct vty *vty)
1841 {
1842 for (unsigned int i = 0; i < array_size(nb_debugs); i++)
1843 if (DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_CONF))
1844 vty_out(vty, "%s\n", nb_debugs_conflines[i]);
1845
1846 hook_call(nb_client_debug_config_write, vty);
1847
1848 return 1;
1849 }
1850
1851 static struct debug_callbacks nb_dbg_cbs = {.debug_set_all = nb_debug_set_all};
1852 static struct cmd_node nb_debug_node = {
1853 .name = "northbound debug",
1854 .node = NORTHBOUND_DEBUG_NODE,
1855 .prompt = "",
1856 .config_write = nb_debug_config_write,
1857 };
1858
1859 void nb_cli_install_default(int node)
1860 {
1861 _install_element(node, &show_config_candidate_section_cmd);
1862
1863 if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL)
1864 return;
1865
1866 _install_element(node, &config_commit_cmd);
1867 _install_element(node, &config_commit_comment_cmd);
1868 _install_element(node, &config_commit_check_cmd);
1869 _install_element(node, &config_update_cmd);
1870 _install_element(node, &config_discard_cmd);
1871 _install_element(node, &show_config_running_cmd);
1872 _install_element(node, &show_config_candidate_cmd);
1873 _install_element(node, &show_config_compare_cmd);
1874 _install_element(node, &show_config_transaction_cmd);
1875 }
1876
1877 /* YANG module autocomplete. */
1878 static void yang_module_autocomplete(vector comps, struct cmd_token *token)
1879 {
1880 const struct lys_module *module;
1881 struct yang_translator *module_tr;
1882 uint32_t idx;
1883
1884 idx = 0;
1885 while ((module = ly_ctx_get_module_iter(ly_native_ctx, &idx)))
1886 vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module->name));
1887
1888 RB_FOREACH (module_tr, yang_translators, &yang_translators) {
1889 idx = 0;
1890 while ((module = ly_ctx_get_module_iter(module_tr->ly_ctx,
1891 &idx)))
1892 vector_set(comps,
1893 XSTRDUP(MTYPE_COMPLETION, module->name));
1894 }
1895 }
1896
1897 /* YANG module translator autocomplete. */
1898 static void yang_translator_autocomplete(vector comps, struct cmd_token *token)
1899 {
1900 struct yang_translator *module_tr;
1901
1902 RB_FOREACH (module_tr, yang_translators, &yang_translators)
1903 vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module_tr->family));
1904 }
1905
1906 static const struct cmd_variable_handler yang_var_handlers[] = {
1907 {.varname = "module_name", .completions = yang_module_autocomplete},
1908 {.varname = "translator_family",
1909 .completions = yang_translator_autocomplete},
1910 {.completions = NULL}};
1911
1912 void nb_cli_init(struct thread_master *tm)
1913 {
1914 master = tm;
1915
1916 /* Initialize the shared candidate configuration. */
1917 vty_shared_candidate_config = nb_config_new(NULL);
1918
1919 debug_init(&nb_dbg_cbs);
1920
1921 install_node(&nb_debug_node);
1922 install_element(ENABLE_NODE, &debug_nb_cmd);
1923 install_element(CONFIG_NODE, &debug_nb_cmd);
1924
1925 /* Install commands specific to the transaction-base mode. */
1926 if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
1927 install_element(ENABLE_NODE, &config_exclusive_cmd);
1928 install_element(ENABLE_NODE, &config_private_cmd);
1929 install_element(ENABLE_NODE,
1930 &show_config_compare_without_candidate_cmd);
1931 install_element(ENABLE_NODE, &show_config_transaction_cmd);
1932 install_element(ENABLE_NODE, &rollback_config_cmd);
1933 install_element(ENABLE_NODE, &clear_config_transactions_cmd);
1934
1935 install_element(CONFIG_NODE, &config_load_cmd);
1936 install_element(CONFIG_NODE,
1937 &config_database_max_transactions_cmd);
1938 }
1939
1940 /* Other commands. */
1941 install_element(ENABLE_NODE, &show_config_running_cmd);
1942 install_element(CONFIG_NODE, &yang_module_translator_load_cmd);
1943 install_element(CONFIG_NODE, &yang_module_translator_unload_cmd);
1944 install_element(ENABLE_NODE, &show_yang_operational_data_cmd);
1945 install_element(ENABLE_NODE, &show_yang_module_cmd);
1946 install_element(ENABLE_NODE, &show_yang_module_detail_cmd);
1947 install_element(ENABLE_NODE, &show_yang_module_translator_cmd);
1948 cmd_variable_handler_register(yang_var_handlers);
1949 }
1950
1951 void nb_cli_terminate(void)
1952 {
1953 nb_config_free(vty_shared_candidate_config);
1954 }