]> git.proxmox.com Git - mirror_frr.git/blob - lib/northbound.c
Merge pull request #3356 from opensourcerouting/router-id-loopbacks
[mirror_frr.git] / lib / northbound.c
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"
23 #include "log.h"
24 #include "lib_errors.h"
25 #include "command.h"
26 #include "db.h"
27 #include "northbound.h"
28 #include "northbound_cli.h"
29 #include "northbound_db.h"
30
31 DEFINE_MTYPE_STATIC(LIB, NB_NODE, "Northbound Node")
32 DEFINE_MTYPE_STATIC(LIB, NB_CONFIG, "Northbound Configuration")
33
34 /* Running configuration - shouldn't be modified directly. */
35 struct nb_config *running_config;
36
37 /*
38 * Global lock used to prevent multiple configuration transactions from
39 * happening concurrently.
40 */
41 static bool transaction_in_progress;
42
43 static int nb_configuration_callback(const enum nb_event event,
44 struct nb_config_change *change);
45 static struct nb_transaction *nb_transaction_new(struct nb_config *config,
46 struct nb_config_cbs *changes,
47 enum nb_client client,
48 const char *comment);
49 static void nb_transaction_free(struct nb_transaction *transaction);
50 static int nb_transaction_process(enum nb_event event,
51 struct nb_transaction *transaction);
52 static void nb_transaction_apply_finish(struct nb_transaction *transaction);
53
54 static void nb_node_new_cb(const struct lys_node *snode, void *arg1, void *arg2)
55 {
56 struct nb_node *nb_node;
57 struct lys_node *sparent, *sparent_list;
58
59 nb_node = XCALLOC(MTYPE_NB_NODE, sizeof(*nb_node));
60 yang_snode_get_path(snode, YANG_PATH_DATA, nb_node->xpath,
61 sizeof(nb_node->xpath));
62 nb_node->priority = NB_DFLT_PRIORITY;
63 sparent = yang_snode_real_parent(snode);
64 if (sparent)
65 nb_node->parent = sparent->priv;
66 sparent_list = yang_snode_parent_list(snode);
67 if (sparent_list)
68 nb_node->parent_list = sparent_list->priv;
69
70 /*
71 * Link the northbound node and the libyang schema node with one
72 * another.
73 */
74 nb_node->snode = snode;
75 lys_set_private(snode, nb_node);
76 }
77
78 static void nb_node_del_cb(const struct lys_node *snode, void *arg1, void *arg2)
79 {
80 struct nb_node *nb_node;
81
82 nb_node = snode->priv;
83 lys_set_private(snode, NULL);
84 XFREE(MTYPE_NB_NODE, nb_node);
85 }
86
87 struct nb_node *nb_node_find(const char *xpath)
88 {
89 const struct lys_node *snode;
90
91 /*
92 * Use libyang to find the schema node associated to the xpath and get
93 * the northbound node from there (snode private pointer).
94 */
95 snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
96 if (!snode)
97 return NULL;
98
99 return snode->priv;
100 }
101
102 static int nb_node_validate_cb(const struct nb_node *nb_node,
103 enum nb_operation operation,
104 int callback_implemented, bool optional)
105 {
106 bool valid;
107
108 valid = nb_operation_is_valid(operation, nb_node->snode);
109
110 if (!valid && callback_implemented)
111 flog_warn(EC_LIB_NB_CB_UNNEEDED,
112 "unneeded '%s' callback for '%s'",
113 nb_operation_name(operation), nb_node->xpath);
114
115 if (!optional && valid && !callback_implemented) {
116 flog_err(EC_LIB_NB_CB_MISSING, "missing '%s' callback for '%s'",
117 nb_operation_name(operation), nb_node->xpath);
118 return 1;
119 }
120
121 return 0;
122 }
123
124 /*
125 * Check if the required callbacks were implemented for the given northbound
126 * node.
127 */
128 static unsigned int nb_node_validate_cbs(const struct nb_node *nb_node)
129
130 {
131 unsigned int error = 0;
132
133 error += nb_node_validate_cb(nb_node, NB_OP_CREATE,
134 !!nb_node->cbs.create, false);
135 error += nb_node_validate_cb(nb_node, NB_OP_MODIFY,
136 !!nb_node->cbs.modify, false);
137 error += nb_node_validate_cb(nb_node, NB_OP_DELETE,
138 !!nb_node->cbs.delete, false);
139 error += nb_node_validate_cb(nb_node, NB_OP_MOVE, !!nb_node->cbs.move,
140 false);
141 error += nb_node_validate_cb(nb_node, NB_OP_APPLY_FINISH,
142 !!nb_node->cbs.apply_finish, true);
143 error += nb_node_validate_cb(nb_node, NB_OP_GET_ELEM,
144 !!nb_node->cbs.get_elem, false);
145 error += nb_node_validate_cb(nb_node, NB_OP_GET_NEXT,
146 !!nb_node->cbs.get_next, false);
147 error += nb_node_validate_cb(nb_node, NB_OP_GET_KEYS,
148 !!nb_node->cbs.get_keys, false);
149 error += nb_node_validate_cb(nb_node, NB_OP_LOOKUP_ENTRY,
150 !!nb_node->cbs.lookup_entry, false);
151 error += nb_node_validate_cb(nb_node, NB_OP_RPC, !!nb_node->cbs.rpc,
152 false);
153
154 return error;
155 }
156
157 static unsigned int nb_node_validate_priority(const struct nb_node *nb_node)
158 {
159 /* Top-level nodes can have any priority. */
160 if (!nb_node->parent)
161 return 0;
162
163 if (nb_node->priority < nb_node->parent->priority) {
164 flog_err(EC_LIB_NB_CB_INVALID_PRIO,
165 "node has higher priority than its parent [xpath %s]",
166 nb_node->xpath);
167 return 1;
168 }
169
170 return 0;
171 }
172
173 static void nb_node_validate(const struct lys_node *snode, void *arg1,
174 void *arg2)
175 {
176 struct nb_node *nb_node = snode->priv;
177 unsigned int *errors = arg1;
178
179 /* Validate callbacks and priority. */
180 *errors += nb_node_validate_cbs(nb_node);
181 *errors += nb_node_validate_priority(nb_node);
182 }
183
184 struct nb_config *nb_config_new(struct lyd_node *dnode)
185 {
186 struct nb_config *config;
187
188 config = XCALLOC(MTYPE_NB_CONFIG, sizeof(*config));
189 if (dnode)
190 config->dnode = dnode;
191 else
192 config->dnode = yang_dnode_new(ly_native_ctx);
193 config->version = 0;
194
195 return config;
196 }
197
198 void nb_config_free(struct nb_config *config)
199 {
200 if (config->dnode)
201 yang_dnode_free(config->dnode);
202 XFREE(MTYPE_NB_CONFIG, config);
203 }
204
205 struct nb_config *nb_config_dup(const struct nb_config *config)
206 {
207 struct nb_config *dup;
208
209 dup = XCALLOC(MTYPE_NB_CONFIG, sizeof(*dup));
210 dup->dnode = yang_dnode_dup(config->dnode);
211 dup->version = config->version;
212
213 return dup;
214 }
215
216 int nb_config_merge(struct nb_config *config_dst, struct nb_config *config_src,
217 bool preserve_source)
218 {
219 int ret;
220
221 ret = lyd_merge(config_dst->dnode, config_src->dnode, LYD_OPT_EXPLICIT);
222 if (ret != 0)
223 flog_warn(EC_LIB_LIBYANG, "%s: lyd_merge() failed", __func__);
224
225 if (!preserve_source)
226 nb_config_free(config_src);
227
228 return (ret == 0) ? NB_OK : NB_ERR;
229 }
230
231 void nb_config_replace(struct nb_config *config_dst,
232 struct nb_config *config_src, bool preserve_source)
233 {
234 /* Update version. */
235 if (config_src->version != 0)
236 config_dst->version = config_src->version;
237
238 /* Update dnode. */
239 yang_dnode_free(config_dst->dnode);
240 if (preserve_source) {
241 config_dst->dnode = yang_dnode_dup(config_src->dnode);
242 } else {
243 config_dst->dnode = config_src->dnode;
244 config_src->dnode = NULL;
245 nb_config_free(config_src);
246 }
247 }
248
249 /* Generate the nb_config_cbs tree. */
250 static inline int nb_config_cb_compare(const struct nb_config_cb *a,
251 const struct nb_config_cb *b)
252 {
253 /* Sort by priority first. */
254 if (a->nb_node->priority < b->nb_node->priority)
255 return -1;
256 if (a->nb_node->priority > b->nb_node->priority)
257 return 1;
258
259 /*
260 * Use XPath as a tie-breaker. This will naturally sort parent nodes
261 * before their children.
262 */
263 return strcmp(a->xpath, b->xpath);
264 }
265 RB_GENERATE(nb_config_cbs, nb_config_cb, entry, nb_config_cb_compare);
266
267 static void nb_config_diff_add_change(struct nb_config_cbs *changes,
268 enum nb_operation operation,
269 const struct lyd_node *dnode)
270 {
271 struct nb_config_change *change;
272
273 change = XCALLOC(MTYPE_TMP, sizeof(*change));
274 change->cb.operation = operation;
275 change->cb.nb_node = dnode->schema->priv;
276 yang_dnode_get_path(dnode, change->cb.xpath, sizeof(change->cb.xpath));
277 change->cb.dnode = dnode;
278
279 RB_INSERT(nb_config_cbs, changes, &change->cb);
280 }
281
282 static void nb_config_diff_del_changes(struct nb_config_cbs *changes)
283 {
284 while (!RB_EMPTY(nb_config_cbs, changes)) {
285 struct nb_config_change *change;
286
287 change = (struct nb_config_change *)RB_ROOT(nb_config_cbs,
288 changes);
289 RB_REMOVE(nb_config_cbs, changes, &change->cb);
290 XFREE(MTYPE_TMP, change);
291 }
292 }
293
294 /*
295 * Helper function used when calculating the delta between two different
296 * configurations. Given a new subtree, calculate all new YANG data nodes,
297 * excluding default leafs and leaf-lists. This is a recursive function.
298 */
299 static void nb_config_diff_new_subtree(const struct lyd_node *dnode,
300 struct nb_config_cbs *changes)
301 {
302 struct lyd_node *child;
303
304 LY_TREE_FOR (dnode->child, child) {
305 enum nb_operation operation;
306
307 switch (child->schema->nodetype) {
308 case LYS_LEAF:
309 case LYS_LEAFLIST:
310 if (lyd_wd_default((struct lyd_node_leaf_list *)child))
311 break;
312
313 if (nb_operation_is_valid(NB_OP_CREATE, child->schema))
314 operation = NB_OP_CREATE;
315 else if (nb_operation_is_valid(NB_OP_MODIFY,
316 child->schema))
317 operation = NB_OP_MODIFY;
318 else
319 continue;
320
321 nb_config_diff_add_change(changes, operation, child);
322 break;
323 case LYS_CONTAINER:
324 case LYS_LIST:
325 if (nb_operation_is_valid(NB_OP_CREATE, child->schema))
326 nb_config_diff_add_change(changes, NB_OP_CREATE,
327 child);
328 nb_config_diff_new_subtree(child, changes);
329 break;
330 default:
331 break;
332 }
333 }
334 }
335
336 /* Calculate the delta between two different configurations. */
337 static void nb_config_diff(const struct nb_config *config1,
338 const struct nb_config *config2,
339 struct nb_config_cbs *changes)
340 {
341 struct lyd_difflist *diff;
342
343 diff = lyd_diff(config1->dnode, config2->dnode,
344 LYD_DIFFOPT_WITHDEFAULTS);
345 assert(diff);
346
347 for (int i = 0; diff->type[i] != LYD_DIFF_END; i++) {
348 LYD_DIFFTYPE type;
349 struct lyd_node *dnode;
350 enum nb_operation operation;
351
352 type = diff->type[i];
353
354 switch (type) {
355 case LYD_DIFF_CREATED:
356 dnode = diff->second[i];
357
358 if (nb_operation_is_valid(NB_OP_CREATE, dnode->schema))
359 operation = NB_OP_CREATE;
360 else if (nb_operation_is_valid(NB_OP_MODIFY,
361 dnode->schema))
362 operation = NB_OP_MODIFY;
363 else
364 continue;
365 break;
366 case LYD_DIFF_DELETED:
367 dnode = diff->first[i];
368 operation = NB_OP_DELETE;
369 break;
370 case LYD_DIFF_CHANGED:
371 dnode = diff->second[i];
372 operation = NB_OP_MODIFY;
373 break;
374 case LYD_DIFF_MOVEDAFTER1:
375 case LYD_DIFF_MOVEDAFTER2:
376 default:
377 continue;
378 }
379
380 nb_config_diff_add_change(changes, operation, dnode);
381
382 if (type == LYD_DIFF_CREATED
383 && (dnode->schema->nodetype & (LYS_CONTAINER | LYS_LIST)))
384 nb_config_diff_new_subtree(dnode, changes);
385 }
386
387 lyd_free_diff(diff);
388 }
389
390 int nb_candidate_edit(struct nb_config *candidate,
391 const struct nb_node *nb_node,
392 enum nb_operation operation, const char *xpath,
393 const struct yang_data *previous,
394 const struct yang_data *data)
395 {
396 struct lyd_node *dnode;
397 char xpath_edit[XPATH_MAXLEN];
398
399 if (!nb_operation_is_valid(operation, nb_node->snode)) {
400 flog_warn(EC_LIB_NB_CANDIDATE_EDIT_ERROR,
401 "%s: %s operation not valid for %s", __func__,
402 nb_operation_name(operation), xpath);
403 return NB_ERR;
404 }
405
406 /* Use special notation for leaf-lists (RFC 6020, section 9.13.5). */
407 if (nb_node->snode->nodetype == LYS_LEAFLIST)
408 snprintf(xpath_edit, sizeof(xpath_edit), "%s[.='%s']", xpath,
409 data->value);
410 else
411 strlcpy(xpath_edit, xpath, sizeof(xpath_edit));
412
413 switch (operation) {
414 case NB_OP_CREATE:
415 case NB_OP_MODIFY:
416 ly_errno = 0;
417 dnode = lyd_new_path(candidate->dnode, ly_native_ctx,
418 xpath_edit, (void *)data->value, 0,
419 LYD_PATH_OPT_UPDATE);
420 if (!dnode && ly_errno) {
421 flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
422 __func__);
423 return NB_ERR;
424 }
425
426 /*
427 * If a new node was created, call lyd_validate() only to create
428 * default child nodes.
429 */
430 if (dnode) {
431 lyd_schema_sort(dnode, 0);
432 lyd_validate(&dnode, LYD_OPT_CONFIG, ly_native_ctx);
433 }
434 break;
435 case NB_OP_DELETE:
436 dnode = yang_dnode_get(candidate->dnode, xpath_edit);
437 if (!dnode)
438 /*
439 * Return a special error code so the caller can choose
440 * whether to ignore it or not.
441 */
442 return NB_ERR_NOT_FOUND;
443 lyd_free(dnode);
444 break;
445 case NB_OP_MOVE:
446 /* TODO: update configuration. */
447 break;
448 default:
449 flog_warn(EC_LIB_DEVELOPMENT,
450 "%s: unknown operation (%u) [xpath %s]", __func__,
451 operation, xpath_edit);
452 return NB_ERR;
453 }
454
455 return NB_OK;
456 }
457
458 bool nb_candidate_needs_update(const struct nb_config *candidate)
459 {
460 if (candidate->version < running_config->version)
461 return true;
462
463 return false;
464 }
465
466 int nb_candidate_update(struct nb_config *candidate)
467 {
468 struct nb_config *updated_config;
469
470 updated_config = nb_config_dup(running_config);
471 if (nb_config_merge(updated_config, candidate, true) != NB_OK)
472 return NB_ERR;
473
474 nb_config_replace(candidate, updated_config, false);
475
476 return NB_OK;
477 }
478
479 /*
480 * The northbound configuration callbacks use the 'priv' pointer present in the
481 * libyang lyd_node structure to store pointers to FRR internal variables
482 * associated to YANG lists and presence containers. Before commiting a
483 * candidate configuration, we must restore the 'priv' pointers stored in the
484 * running configuration since they might be lost while editing the candidate.
485 */
486 static void nb_candidate_restore_priv_pointers(struct nb_config *candidate)
487 {
488 struct lyd_node *root, *next, *dnode_iter;
489
490 LY_TREE_FOR (running_config->dnode, root) {
491 LY_TREE_DFS_BEGIN (root, next, dnode_iter) {
492 struct lyd_node *dnode_candidate;
493 char xpath[XPATH_MAXLEN];
494
495 if (!dnode_iter->priv)
496 goto next;
497
498 yang_dnode_get_path(dnode_iter, xpath, sizeof(xpath));
499 dnode_candidate =
500 yang_dnode_get(candidate->dnode, xpath);
501 if (dnode_candidate)
502 yang_dnode_set_entry(dnode_candidate,
503 dnode_iter->priv);
504
505 next:
506 LY_TREE_DFS_END(root, next, dnode_iter);
507 }
508 }
509 }
510
511 /*
512 * Perform YANG syntactic and semantic validation.
513 *
514 * WARNING: lyd_validate() can change the configuration as part of the
515 * validation process.
516 */
517 static int nb_candidate_validate_yang(struct nb_config *candidate)
518 {
519 if (lyd_validate(&candidate->dnode, LYD_OPT_STRICT | LYD_OPT_CONFIG,
520 ly_native_ctx)
521 != 0)
522 return NB_ERR_VALIDATION;
523
524 return NB_OK;
525 }
526
527 /* Perform code-level validation using the northbound callbacks. */
528 static int nb_candidate_validate_changes(struct nb_config *candidate,
529 struct nb_config_cbs *changes)
530 {
531 struct nb_config_cb *cb;
532
533 nb_candidate_restore_priv_pointers(candidate);
534 RB_FOREACH (cb, nb_config_cbs, changes) {
535 struct nb_config_change *change = (struct nb_config_change *)cb;
536 int ret;
537
538 ret = nb_configuration_callback(NB_EV_VALIDATE, change);
539 if (ret != NB_OK)
540 return NB_ERR_VALIDATION;
541 }
542
543 return NB_OK;
544 }
545
546 int nb_candidate_validate(struct nb_config *candidate)
547 {
548 struct nb_config_cbs changes;
549 int ret;
550
551 if (nb_candidate_validate_yang(candidate) != NB_OK)
552 return NB_ERR_VALIDATION;
553
554 RB_INIT(nb_config_cbs, &changes);
555 nb_config_diff(running_config, candidate, &changes);
556 ret = nb_candidate_validate_changes(candidate, &changes);
557 nb_config_diff_del_changes(&changes);
558
559 return ret;
560 }
561
562 int nb_candidate_commit_prepare(struct nb_config *candidate,
563 enum nb_client client, const char *comment,
564 struct nb_transaction **transaction)
565 {
566 struct nb_config_cbs changes;
567
568 if (nb_candidate_validate_yang(candidate) != NB_OK) {
569 flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
570 "%s: failed to validate candidate configuration",
571 __func__);
572 return NB_ERR_VALIDATION;
573 }
574
575 RB_INIT(nb_config_cbs, &changes);
576 nb_config_diff(running_config, candidate, &changes);
577 if (RB_EMPTY(nb_config_cbs, &changes))
578 return NB_ERR_NO_CHANGES;
579
580 if (nb_candidate_validate_changes(candidate, &changes) != NB_OK) {
581 flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
582 "%s: failed to validate candidate configuration",
583 __func__);
584 nb_config_diff_del_changes(&changes);
585 return NB_ERR_VALIDATION;
586 }
587
588 *transaction = nb_transaction_new(candidate, &changes, client, comment);
589 if (*transaction == NULL) {
590 flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED,
591 "%s: failed to create transaction", __func__);
592 nb_config_diff_del_changes(&changes);
593 return NB_ERR_LOCKED;
594 }
595
596 return nb_transaction_process(NB_EV_PREPARE, *transaction);
597 }
598
599 void nb_candidate_commit_abort(struct nb_transaction *transaction)
600 {
601 (void)nb_transaction_process(NB_EV_ABORT, transaction);
602 nb_transaction_free(transaction);
603 }
604
605 void nb_candidate_commit_apply(struct nb_transaction *transaction,
606 bool save_transaction, uint32_t *transaction_id)
607 {
608 (void)nb_transaction_process(NB_EV_APPLY, transaction);
609 nb_transaction_apply_finish(transaction);
610
611 /* Replace running by candidate. */
612 transaction->config->version++;
613 nb_config_replace(running_config, transaction->config, true);
614
615 /* Record transaction. */
616 if (save_transaction
617 && nb_db_transaction_save(transaction, transaction_id) != NB_OK)
618 flog_warn(EC_LIB_NB_TRANSACTION_RECORD_FAILED,
619 "%s: failed to record transaction", __func__);
620
621 nb_transaction_free(transaction);
622 }
623
624 int nb_candidate_commit(struct nb_config *candidate, enum nb_client client,
625 bool save_transaction, const char *comment,
626 uint32_t *transaction_id)
627 {
628 struct nb_transaction *transaction = NULL;
629 int ret;
630
631 ret = nb_candidate_commit_prepare(candidate, client, comment,
632 &transaction);
633 /*
634 * Apply the changes if the preparation phase succeeded. Otherwise abort
635 * the transaction.
636 */
637 if (ret == NB_OK)
638 nb_candidate_commit_apply(transaction, save_transaction,
639 transaction_id);
640 else if (transaction != NULL)
641 nb_candidate_commit_abort(transaction);
642
643 return ret;
644 }
645
646 static void nb_log_callback(const enum nb_event event,
647 enum nb_operation operation, const char *xpath,
648 const char *value)
649 {
650 zlog_debug(
651 "northbound callback: event [%s] op [%s] xpath [%s] value [%s]",
652 nb_event_name(event), nb_operation_name(operation), xpath,
653 value);
654 }
655
656 /*
657 * Call the northbound configuration callback associated to a given
658 * configuration change.
659 */
660 static int nb_configuration_callback(const enum nb_event event,
661 struct nb_config_change *change)
662 {
663 enum nb_operation operation = change->cb.operation;
664 const char *xpath = change->cb.xpath;
665 const struct nb_node *nb_node = change->cb.nb_node;
666 const struct lyd_node *dnode = change->cb.dnode;
667 union nb_resource *resource;
668 int ret = NB_ERR;
669
670 if (debug_northbound) {
671 const char *value = "(none)";
672
673 if (dnode && !yang_snode_is_typeless_data(dnode->schema))
674 value = yang_dnode_get_string(dnode, NULL);
675
676 nb_log_callback(event, operation, xpath, value);
677 }
678
679 if (event == NB_EV_VALIDATE)
680 resource = NULL;
681 else
682 resource = &change->resource;
683
684 switch (operation) {
685 case NB_OP_CREATE:
686 ret = (*nb_node->cbs.create)(event, dnode, resource);
687 break;
688 case NB_OP_MODIFY:
689 ret = (*nb_node->cbs.modify)(event, dnode, resource);
690 break;
691 case NB_OP_DELETE:
692 ret = (*nb_node->cbs.delete)(event, dnode);
693 break;
694 case NB_OP_MOVE:
695 ret = (*nb_node->cbs.move)(event, dnode);
696 break;
697 default:
698 break;
699 }
700
701 if (ret != NB_OK)
702 flog_warn(
703 EC_LIB_NB_CB_CONFIG,
704 "%s: error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]",
705 __func__, nb_err_name(ret), nb_event_name(event),
706 nb_operation_name(operation), xpath);
707
708 return ret;
709 }
710
711 static struct nb_transaction *nb_transaction_new(struct nb_config *config,
712 struct nb_config_cbs *changes,
713 enum nb_client client,
714 const char *comment)
715 {
716 struct nb_transaction *transaction;
717
718 if (transaction_in_progress) {
719 flog_warn(
720 EC_LIB_NB_TRANSACTION_CREATION_FAILED,
721 "%s: error - there's already another transaction in progress",
722 __func__);
723 return NULL;
724 }
725 transaction_in_progress = true;
726
727 transaction = XCALLOC(MTYPE_TMP, sizeof(*transaction));
728 transaction->client = client;
729 if (comment)
730 strlcpy(transaction->comment, comment,
731 sizeof(transaction->comment));
732 transaction->config = config;
733 transaction->changes = *changes;
734
735 return transaction;
736 }
737
738 static void nb_transaction_free(struct nb_transaction *transaction)
739 {
740 nb_config_diff_del_changes(&transaction->changes);
741 XFREE(MTYPE_TMP, transaction);
742 transaction_in_progress = false;
743 }
744
745 /* Process all configuration changes associated to a transaction. */
746 static int nb_transaction_process(enum nb_event event,
747 struct nb_transaction *transaction)
748 {
749 struct nb_config_cb *cb;
750
751 RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
752 struct nb_config_change *change = (struct nb_config_change *)cb;
753 int ret;
754
755 /*
756 * Only try to release resources that were allocated
757 * successfully.
758 */
759 if (event == NB_EV_ABORT && change->prepare_ok == false)
760 break;
761
762 /* Call the appropriate callback. */
763 ret = nb_configuration_callback(event, change);
764 switch (event) {
765 case NB_EV_PREPARE:
766 if (ret != NB_OK)
767 return ret;
768 change->prepare_ok = true;
769 break;
770 case NB_EV_ABORT:
771 case NB_EV_APPLY:
772 /*
773 * At this point it's not possible to reject the
774 * transaction anymore, so any failure here can lead to
775 * inconsistencies and should be treated as a bug.
776 * Operations prone to errors, like validations and
777 * resource allocations, should be performed during the
778 * 'prepare' phase.
779 */
780 break;
781 default:
782 break;
783 }
784 }
785
786 return NB_OK;
787 }
788
789 static struct nb_config_cb *
790 nb_apply_finish_cb_new(struct nb_config_cbs *cbs, const char *xpath,
791 const struct nb_node *nb_node,
792 const struct lyd_node *dnode)
793 {
794 struct nb_config_cb *cb;
795
796 cb = XCALLOC(MTYPE_TMP, sizeof(*cb));
797 strlcpy(cb->xpath, xpath, sizeof(cb->xpath));
798 cb->nb_node = nb_node;
799 cb->dnode = dnode;
800 RB_INSERT(nb_config_cbs, cbs, cb);
801
802 return cb;
803 }
804
805 static struct nb_config_cb *
806 nb_apply_finish_cb_find(struct nb_config_cbs *cbs, const char *xpath,
807 const struct nb_node *nb_node)
808 {
809 struct nb_config_cb s;
810
811 strlcpy(s.xpath, xpath, sizeof(s.xpath));
812 s.nb_node = nb_node;
813 return RB_FIND(nb_config_cbs, cbs, &s);
814 }
815
816 /* Call the 'apply_finish' callbacks. */
817 static void nb_transaction_apply_finish(struct nb_transaction *transaction)
818 {
819 struct nb_config_cbs cbs;
820 struct nb_config_cb *cb;
821
822 /* Initialize tree of 'apply_finish' callbacks. */
823 RB_INIT(nb_config_cbs, &cbs);
824
825 /* Identify the 'apply_finish' callbacks that need to be called. */
826 RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
827 struct nb_config_change *change = (struct nb_config_change *)cb;
828 const struct lyd_node *dnode = change->cb.dnode;
829
830 /*
831 * Iterate up to the root of the data tree. When a node is being
832 * deleted, skip its 'apply_finish' callback if one is defined
833 * (the 'apply_finish' callbacks from the node ancestors should
834 * be called though).
835 */
836 if (change->cb.operation == NB_OP_DELETE) {
837 char xpath[XPATH_MAXLEN];
838
839 dnode = dnode->parent;
840 if (!dnode)
841 break;
842
843 /*
844 * The dnode from 'delete' callbacks point to elements
845 * from the running configuration. Use yang_dnode_get()
846 * to get the corresponding dnode from the candidate
847 * configuration that is being committed.
848 */
849 yang_dnode_get_path(dnode, xpath, sizeof(xpath));
850 dnode = yang_dnode_get(transaction->config->dnode,
851 xpath);
852 }
853 while (dnode) {
854 char xpath[XPATH_MAXLEN];
855 struct nb_node *nb_node;
856
857 nb_node = dnode->schema->priv;
858 if (!nb_node->cbs.apply_finish)
859 goto next;
860
861 /*
862 * Don't call the callback more than once for the same
863 * data node.
864 */
865 yang_dnode_get_path(dnode, xpath, sizeof(xpath));
866 if (nb_apply_finish_cb_find(&cbs, xpath, nb_node))
867 goto next;
868
869 nb_apply_finish_cb_new(&cbs, xpath, nb_node, dnode);
870
871 next:
872 dnode = dnode->parent;
873 }
874 }
875
876 /* Call the 'apply_finish' callbacks, sorted by their priorities. */
877 RB_FOREACH (cb, nb_config_cbs, &cbs) {
878 if (debug_northbound)
879 nb_log_callback(NB_EV_APPLY, NB_OP_APPLY_FINISH,
880 cb->xpath, NULL);
881
882 (*cb->nb_node->cbs.apply_finish)(cb->dnode);
883 }
884
885 /* Release memory. */
886 while (!RB_EMPTY(nb_config_cbs, &cbs)) {
887 cb = RB_ROOT(nb_config_cbs, &cbs);
888 RB_REMOVE(nb_config_cbs, &cbs, cb);
889 XFREE(MTYPE_TMP, cb);
890 }
891 }
892
893 bool nb_operation_is_valid(enum nb_operation operation,
894 const struct lys_node *snode)
895 {
896 struct lys_node_container *scontainer;
897 struct lys_node_leaf *sleaf;
898
899 switch (operation) {
900 case NB_OP_CREATE:
901 if (!(snode->flags & LYS_CONFIG_W))
902 return false;
903
904 switch (snode->nodetype) {
905 case LYS_LEAF:
906 sleaf = (struct lys_node_leaf *)snode;
907 if (sleaf->type.base != LY_TYPE_EMPTY)
908 return false;
909 break;
910 case LYS_CONTAINER:
911 scontainer = (struct lys_node_container *)snode;
912 if (!scontainer->presence)
913 return false;
914 break;
915 case LYS_LIST:
916 case LYS_LEAFLIST:
917 break;
918 default:
919 return false;
920 }
921 return true;
922 case NB_OP_MODIFY:
923 if (!(snode->flags & LYS_CONFIG_W))
924 return false;
925
926 switch (snode->nodetype) {
927 case LYS_LEAF:
928 sleaf = (struct lys_node_leaf *)snode;
929 if (sleaf->type.base == LY_TYPE_EMPTY)
930 return false;
931
932 /* List keys can't be modified. */
933 if (lys_is_key(sleaf, NULL))
934 return false;
935 break;
936 default:
937 return false;
938 }
939 return true;
940 case NB_OP_DELETE:
941 if (!(snode->flags & LYS_CONFIG_W))
942 return false;
943
944 switch (snode->nodetype) {
945 case LYS_LEAF:
946 sleaf = (struct lys_node_leaf *)snode;
947
948 /* List keys can't be deleted. */
949 if (lys_is_key(sleaf, NULL))
950 return false;
951
952 /*
953 * Only optional leafs can be deleted, or leafs whose
954 * parent is a case statement.
955 */
956 if (snode->parent->nodetype == LYS_CASE)
957 return true;
958 if (sleaf->when)
959 return true;
960 if ((sleaf->flags & LYS_MAND_TRUE) || sleaf->dflt)
961 return false;
962 break;
963 case LYS_CONTAINER:
964 scontainer = (struct lys_node_container *)snode;
965 if (!scontainer->presence)
966 return false;
967 break;
968 case LYS_LIST:
969 case LYS_LEAFLIST:
970 break;
971 default:
972 return false;
973 }
974 return true;
975 case NB_OP_MOVE:
976 if (!(snode->flags & LYS_CONFIG_W))
977 return false;
978
979 switch (snode->nodetype) {
980 case LYS_LIST:
981 case LYS_LEAFLIST:
982 if (!(snode->flags & LYS_USERORDERED))
983 return false;
984 break;
985 default:
986 return false;
987 }
988 return true;
989 case NB_OP_APPLY_FINISH:
990 if (!(snode->flags & LYS_CONFIG_W))
991 return false;
992 return true;
993 case NB_OP_GET_ELEM:
994 if (!(snode->flags & LYS_CONFIG_R))
995 return false;
996
997 switch (snode->nodetype) {
998 case LYS_LEAF:
999 break;
1000 case LYS_CONTAINER:
1001 scontainer = (struct lys_node_container *)snode;
1002 if (!scontainer->presence)
1003 return false;
1004 break;
1005 default:
1006 return false;
1007 }
1008 return true;
1009 case NB_OP_GET_NEXT:
1010 case NB_OP_GET_KEYS:
1011 case NB_OP_LOOKUP_ENTRY:
1012 if (!(snode->flags & LYS_CONFIG_R))
1013 return false;
1014
1015 switch (snode->nodetype) {
1016 case LYS_LIST:
1017 break;
1018 default:
1019 return false;
1020 }
1021 return true;
1022 case NB_OP_RPC:
1023 if (snode->flags & (LYS_CONFIG_W | LYS_CONFIG_R))
1024 return false;
1025
1026 switch (snode->nodetype) {
1027 case LYS_RPC:
1028 case LYS_ACTION:
1029 break;
1030 default:
1031 return false;
1032 }
1033 return true;
1034 default:
1035 return false;
1036 }
1037 }
1038
1039 DEFINE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments),
1040 (xpath, arguments));
1041
1042 int nb_notification_send(const char *xpath, struct list *arguments)
1043 {
1044 int ret;
1045
1046 ret = hook_call(nb_notification_send, xpath, arguments);
1047 if (arguments)
1048 list_delete(&arguments);
1049
1050 return ret;
1051 }
1052
1053 const char *nb_event_name(enum nb_event event)
1054 {
1055 switch (event) {
1056 case NB_EV_VALIDATE:
1057 return "validate";
1058 case NB_EV_PREPARE:
1059 return "prepare";
1060 case NB_EV_ABORT:
1061 return "abort";
1062 case NB_EV_APPLY:
1063 return "apply";
1064 default:
1065 return "unknown";
1066 }
1067 }
1068
1069 const char *nb_operation_name(enum nb_operation operation)
1070 {
1071 switch (operation) {
1072 case NB_OP_CREATE:
1073 return "create";
1074 case NB_OP_MODIFY:
1075 return "modify";
1076 case NB_OP_DELETE:
1077 return "delete";
1078 case NB_OP_MOVE:
1079 return "move";
1080 case NB_OP_APPLY_FINISH:
1081 return "apply_finish";
1082 case NB_OP_GET_ELEM:
1083 return "get_elem";
1084 case NB_OP_GET_NEXT:
1085 return "get_next";
1086 case NB_OP_GET_KEYS:
1087 return "get_keys";
1088 case NB_OP_LOOKUP_ENTRY:
1089 return "lookup_entry";
1090 case NB_OP_RPC:
1091 return "rpc";
1092 default:
1093 return "unknown";
1094 }
1095 }
1096
1097 const char *nb_err_name(enum nb_error error)
1098 {
1099 switch (error) {
1100 case NB_OK:
1101 return "ok";
1102 case NB_ERR:
1103 return "generic error";
1104 case NB_ERR_NO_CHANGES:
1105 return "no changes";
1106 case NB_ERR_NOT_FOUND:
1107 return "element not found";
1108 case NB_ERR_LOCKED:
1109 return "resource is locked";
1110 case NB_ERR_VALIDATION:
1111 return "validation error";
1112 case NB_ERR_RESOURCE:
1113 return "failed to allocate resource";
1114 case NB_ERR_INCONSISTENCY:
1115 return "internal inconsistency";
1116 default:
1117 return "unknown";
1118 }
1119 }
1120
1121 const char *nb_client_name(enum nb_client client)
1122 {
1123 switch (client) {
1124 case NB_CLIENT_CLI:
1125 return "CLI";
1126 case NB_CLIENT_CONFD:
1127 return "ConfD";
1128 case NB_CLIENT_SYSREPO:
1129 return "Sysrepo";
1130 default:
1131 return "unknown";
1132 }
1133 }
1134
1135 static void nb_load_callbacks(const struct frr_yang_module_info *module)
1136 {
1137 for (size_t i = 0; module->nodes[i].xpath; i++) {
1138 struct nb_node *nb_node;
1139 uint32_t priority;
1140
1141 nb_node = nb_node_find(module->nodes[i].xpath);
1142 if (!nb_node) {
1143 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
1144 "%s: unknown data path: %s", __func__,
1145 module->nodes[i].xpath);
1146 continue;
1147 }
1148
1149 nb_node->cbs = module->nodes[i].cbs;
1150 priority = module->nodes[i].priority;
1151 if (priority != 0)
1152 nb_node->priority = priority;
1153 }
1154 }
1155
1156 void nb_init(const struct frr_yang_module_info *modules[], size_t nmodules)
1157 {
1158 unsigned int errors = 0;
1159
1160 /* Load YANG modules. */
1161 for (size_t i = 0; i < nmodules; i++)
1162 yang_module_load(modules[i]->name);
1163
1164 /* Create a nb_node for all YANG schema nodes. */
1165 yang_all_snodes_iterate(nb_node_new_cb, 0, NULL, NULL);
1166
1167 /* Load northbound callbacks. */
1168 for (size_t i = 0; i < nmodules; i++)
1169 nb_load_callbacks(modules[i]);
1170
1171 /* Validate northbound callbacks. */
1172 yang_all_snodes_iterate(nb_node_validate, 0, &errors, NULL);
1173 if (errors > 0) {
1174 flog_err(
1175 EC_LIB_NB_CBS_VALIDATION,
1176 "%s: failed to validate northbound callbacks: %u error(s)",
1177 __func__, errors);
1178 exit(1);
1179 }
1180
1181 /* Initialize the northbound database (used for the rollback log). */
1182 if (nb_db_init() != NB_OK)
1183 flog_warn(EC_LIB_NB_DATABASE,
1184 "%s: failed to initialize northbound database",
1185 __func__);
1186
1187 /* Create an empty running configuration. */
1188 running_config = nb_config_new(NULL);
1189
1190 /* Initialize the northbound CLI. */
1191 nb_cli_init();
1192 }
1193
1194 void nb_terminate(void)
1195 {
1196 /* Terminate the northbound CLI. */
1197 nb_cli_terminate();
1198
1199 /* Delete all nb_node's from all YANG modules. */
1200 yang_all_snodes_iterate(nb_node_del_cb, 0, NULL, NULL);
1201
1202 /* Delete the running configuration. */
1203 nb_config_free(running_config);
1204 }