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