]> git.proxmox.com Git - mirror_frr.git/blobdiff - lib/northbound_confd.c
zebra, lib: fix the ZEBRA_INTERFACE_VRF_UPDATE zapi message
[mirror_frr.git] / lib / northbound_confd.c
index 3579d1da00151c02bdaa8171d8e6eaf18f9076cb..53149d0fd22b956d4341e78919e0d45bb7c86eb9 100644 (file)
@@ -41,6 +41,7 @@ static struct confd_daemon_ctx *dctx;
 static struct confd_notification_ctx *live_ctx;
 static bool confd_connected;
 static struct list *confd_spoints;
+static struct nb_transaction *transaction;
 
 static void frr_confd_finish_cdb(void);
 static void frr_confd_finish_dp(void);
@@ -137,10 +138,19 @@ static int frr_confd_hkeypath_get_list_entry(const confd_hkeypath_t *kp,
                        nb_node_list = nb_node_list->parent_list;
 
                /* Obtain list entry. */
-               *list_entry =
-                       nb_node_list->cbs.lookup_entry(*list_entry, &keys);
-               if (*list_entry == NULL)
-                       return -1;
+               if (!CHECK_FLAG(nb_node_list->flags, F_NB_NODE_KEYLESS_LIST)) {
+                       *list_entry = nb_node_list->cbs.lookup_entry(
+                               *list_entry, &keys);
+                       if (*list_entry == NULL)
+                               return -1;
+               } else {
+                       unsigned long ptr_ulong;
+
+                       /* Retrieve list entry from pseudo-key (string). */
+                       if (sscanf(keys.key[0], "%lu", &ptr_ulong) != 1)
+                               return -1;
+                       *list_entry = (const void *)ptr_ulong;
+               }
 
                curr_list++;
        }
@@ -270,41 +280,12 @@ frr_confd_cdb_diff_iter(confd_hkeypath_t *kp, enum cdb_iter_op cdb_op,
        return ITER_RECURSE;
 }
 
-static int frr_confd_cdb_read_cb(struct thread *thread)
+static int frr_confd_cdb_read_cb_prepare(int fd, int *subp, int reslen)
 {
-       int fd = THREAD_FD(thread);
-       int *subp = NULL;
-       enum cdb_sub_notification cdb_ev;
-       int flags;
-       int reslen = 0;
        struct nb_config *candidate;
        struct cdb_iter_args iter_args;
        int ret;
 
-       thread = NULL;
-       thread_add_read(master, frr_confd_cdb_read_cb, NULL, fd, &thread);
-
-       if (cdb_read_subscription_socket2(fd, &cdb_ev, &flags, &subp, &reslen)
-           != CONFD_OK) {
-               flog_err_confd("cdb_read_subscription_socket2");
-               return -1;
-       }
-
-       /*
-        * Ignore CDB_SUB_ABORT and CDB_SUB_COMMIT. We'll leverage the
-        * northbound layer itself to abort or apply the configuration changes
-        * when a transaction is created.
-        */
-       if (cdb_ev != CDB_SUB_PREPARE) {
-               free(subp);
-               if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY)
-                   != CONFD_OK) {
-                       flog_err_confd("cdb_sync_subscription_socket");
-                       return -1;
-               }
-               return 0;
-       }
-
        candidate = nb_config_dup(running_config);
 
        /* Iterate over all configuration changes. */
@@ -332,8 +313,13 @@ static int frr_confd_cdb_read_cb(struct thread *thread)
                return 0;
        }
 
-       ret = nb_candidate_commit(candidate, NB_CLIENT_CONFD, true, NULL, NULL);
-       nb_config_free(candidate);
+       /*
+        * Validate the configuration changes and allocate all resources
+        * required to apply them.
+        */
+       transaction = NULL;
+       ret = nb_candidate_commit_prepare(candidate, NB_CLIENT_CONFD, NULL,
+                                         &transaction);
        if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) {
                enum confd_errcode errcode;
                const char *errmsg;
@@ -353,6 +339,7 @@ static int frr_confd_cdb_read_cb(struct thread *thread)
                        break;
                }
 
+               /* Reject the configuration changes. */
                if (cdb_sub_abort_trans(cdb_sub_sock, errcode, 0, 0, "%s",
                                        errmsg)
                    != CONFD_OK) {
@@ -360,16 +347,101 @@ static int frr_confd_cdb_read_cb(struct thread *thread)
                        return -1;
                }
        } else {
+               /* Acknowledge the notification. */
                if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY)
                    != CONFD_OK) {
                        flog_err_confd("cdb_sync_subscription_socket");
                        return -1;
                }
+
+               /* No configuration changes. */
+               if (!transaction)
+                       nb_config_free(candidate);
+       }
+
+       return 0;
+}
+
+static int frr_confd_cdb_read_cb_commit(int fd, int *subp, int reslen)
+{
+       /*
+        * No need to process the configuration changes again as we're already
+        * keeping track of them in the "transaction" variable.
+        */
+       free(subp);
+
+       /* Apply the transaction. */
+       if (transaction) {
+               struct nb_config *candidate = transaction->config;
+
+               nb_candidate_commit_apply(transaction, true, NULL);
+               nb_config_free(candidate);
+       }
+
+       /* Acknowledge the notification. */
+       if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) {
+               flog_err_confd("cdb_sync_subscription_socket");
+               return -1;
        }
 
        return 0;
 }
 
+static int frr_confd_cdb_read_cb_abort(int fd, int *subp, int reslen)
+{
+       /*
+        * No need to process the configuration changes again as we're already
+        * keeping track of them in the "transaction" variable.
+        */
+       free(subp);
+
+       /* Abort the transaction. */
+       if (transaction) {
+               struct nb_config *candidate = transaction->config;
+
+               nb_candidate_commit_abort(transaction);
+               nb_config_free(candidate);
+       }
+
+       /* Acknowledge the notification. */
+       if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) {
+               flog_err_confd("cdb_sync_subscription_socket");
+               return -1;
+       }
+
+       return 0;
+}
+
+static int frr_confd_cdb_read_cb(struct thread *thread)
+{
+       int fd = THREAD_FD(thread);
+       enum cdb_sub_notification cdb_ev;
+       int flags;
+       int *subp = NULL;
+       int reslen = 0;
+
+       thread = NULL;
+       thread_add_read(master, frr_confd_cdb_read_cb, NULL, fd, &thread);
+
+       if (cdb_read_subscription_socket2(fd, &cdb_ev, &flags, &subp, &reslen)
+           != CONFD_OK) {
+               flog_err_confd("cdb_read_subscription_socket2");
+               return -1;
+       }
+
+       switch (cdb_ev) {
+       case CDB_SUB_PREPARE:
+               return frr_confd_cdb_read_cb_prepare(fd, subp, reslen);
+       case CDB_SUB_COMMIT:
+               return frr_confd_cdb_read_cb_commit(fd, subp, reslen);
+       case CDB_SUB_ABORT:
+               return frr_confd_cdb_read_cb_abort(fd, subp, reslen);
+       default:
+               flog_err_confd("unknown CDB event");
+               return -1;
+       }
+}
+
 /* Trigger CDB subscriptions to read the startup configuration. */
 static void *thread_cdb_trigger_subscriptions(void *data)
 {
@@ -577,7 +649,6 @@ static int frr_confd_data_get_next(struct confd_trans_ctx *tctx,
 {
        struct nb_node *nb_node;
        char xpath[BUFSIZ];
-       struct yang_list_keys keys;
        struct yang_data *data;
        const void *parent_list_entry, *nb_next;
        confd_value_t v[LIST_MAXKEYS];
@@ -609,18 +680,53 @@ static int frr_confd_data_get_next(struct confd_trans_ctx *tctx,
 
        switch (nb_node->snode->nodetype) {
        case LYS_LIST:
-               memset(&keys, 0, sizeof(keys));
-               if (nb_node->cbs.get_keys(nb_next, &keys) != NB_OK) {
-                       flog_warn(EC_LIB_NB_CB_STATE,
-                                 "%s: failed to get list keys", __func__);
-                       confd_data_reply_next_key(tctx, NULL, -1, -1);
-                       return CONFD_OK;
-               }
+               if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) {
+                       struct yang_list_keys keys;
+
+                       memset(&keys, 0, sizeof(keys));
+                       if (nb_node->cbs.get_keys(nb_next, &keys) != NB_OK) {
+                               flog_warn(EC_LIB_NB_CB_STATE,
+                                         "%s: failed to get list keys",
+                                         __func__);
+                               confd_data_reply_next_key(tctx, NULL, -1, -1);
+                               return CONFD_OK;
+                       }
 
-               /* Feed keys to ConfD. */
-               for (size_t i = 0; i < keys.num; i++)
-                       CONFD_SET_STR(&v[i], keys.key[i]);
-               confd_data_reply_next_key(tctx, v, keys.num, (long)nb_next);
+                       /* Feed keys to ConfD. */
+                       for (size_t i = 0; i < keys.num; i++)
+                               CONFD_SET_STR(&v[i], keys.key[i]);
+                       confd_data_reply_next_key(tctx, v, keys.num,
+                                                 (long)nb_next);
+               } else {
+                       char pointer_str[16];
+
+                       /*
+                        * ConfD 6.6 user guide, chapter 6.11 (Operational data
+                        * lists without keys):
+                        * "To support this without having completely separate
+                        * APIs, we use a "pseudo" key in the ConfD APIs for
+                        * this type of list. This key is not part of the data
+                        * model, and completely hidden in the northbound agent
+                        * interfaces, but is used with e.g. the get_next() and
+                        * get_elem() callbacks as if it were a normal key. This
+                        * "pseudo" key is always a single signed 64-bit
+                        * integer, i.e. the confd_value_t type is C_INT64. The
+                        * values can be chosen arbitrarily by the application,
+                        * as long as a key value returned by get_next() can be
+                        * used to get the data for the corresponding list entry
+                        * with get_elem() or get_object() as usual. It could
+                        * e.g. be an index into an array that holds the data,
+                        * or even a memory address in integer form".
+                        *
+                        * Since we're using the CONFD_DAEMON_FLAG_STRINGSONLY
+                        * option, we must convert our pseudo-key (a void
+                        * pointer) to a string before sending it to confd.
+                        */
+                       snprintf(pointer_str, sizeof(pointer_str), "%lu",
+                                (unsigned long)nb_next);
+                       CONFD_SET_STR(&v[0], pointer_str);
+                       confd_data_reply_next_key(tctx, v, 1, (long)nb_next);
+               }
                break;
        case LYS_LEAFLIST:
                data = nb_node->cbs.get_elem(xpath, nb_next);
@@ -729,6 +835,7 @@ static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx,
        const void *nb_next;
 #define CONFD_OBJECTS_PER_TIME 100
        struct confd_next_object objects[CONFD_OBJECTS_PER_TIME + 1];
+       char pseudo_keys[CONFD_OBJECTS_PER_TIME][16];
        int nobjects = 0;
 
        frr_confd_get_xpath(kp, xpath, sizeof(xpath));
@@ -781,6 +888,26 @@ static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx,
                        XMALLOC(MTYPE_CONFD,
                                CONFD_MAX_CHILD_NODES * sizeof(confd_value_t));
 
+               /*
+                * ConfD 6.6 user guide, chapter 6.11 (Operational data lists
+                * without keys):
+                * "In the response to the get_next_object() callback, the data
+                * provider is expected to provide the key values along with the
+                * other leafs in an array that is populated according to the
+                * data model. This must be done also for this type of list,
+                * even though the key isn't actually in the data model. The
+                * "pseudo" key must always be the first element in the array".
+                */
+               if (CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) {
+                       confd_value_t *v;
+
+                       snprintf(pseudo_keys[j], sizeof(pseudo_keys[j]), "%lu",
+                                (unsigned long)nb_next);
+
+                       v = &object->v[nvalues++];
+                       CONFD_SET_STR(v, pseudo_keys[j]);
+               }
+
                /* Loop through list child nodes. */
                LY_TREE_FOR (nb_node->snode->child, child) {
                        struct nb_node *nb_node_child = child->priv;