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