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