diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c index a107582bea07..3345505213ff 100644 --- a/lib/mgmt_fe_client.c +++ b/lib/mgmt_fe_client.c @@ -329,6 +329,36 @@ int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, return ret; } +int mgmt_fe_send_edit_req(struct mgmt_fe_client *client, uint64_t session_id, + uint64_t req_id, uint8_t datastore, + LYD_FORMAT request_type, uint8_t flags, + uint8_t operation, const char *xpath, const char *data) +{ + struct mgmt_msg_edit *msg; + int ret; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit, 0, + MTYPE_MSG_NATIVE_EDIT); + msg->refer_id = session_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_EDIT; + msg->request_type = request_type; + msg->flags = flags; + msg->datastore = datastore; + msg->operation = operation; + + mgmt_msg_native_xpath_encode(msg, xpath); + if (data) + mgmt_msg_native_append(msg, data, strlen(data) + 1); + + debug_fe_client("Sending EDIT_REQ session-id %" PRIu64 + " req-id %" PRIu64 " xpath: %s", + session_id, req_id, xpath); + + ret = mgmt_msg_native_send_msg(&client->client.conn, msg, false); + mgmt_msg_native_free_msg(msg); + return ret; +} static int mgmt_fe_client_handle_msg(struct mgmt_fe_client *client, Mgmtd__FeMessage *fe_msg) @@ -503,7 +533,9 @@ static void fe_client_handle_native_msg(struct mgmt_fe_client *client, struct mgmt_fe_client_session *session = NULL; struct mgmt_msg_notify_data *notify_msg; struct mgmt_msg_tree_data *tree_msg; + struct mgmt_msg_edit_reply *edit_msg; struct mgmt_msg_error *err_msg; + const char *xpath = NULL; const char *data = NULL; size_t dlen; @@ -554,6 +586,28 @@ static void fe_client_handle_native_msg(struct mgmt_fe_client *client, msg_len - sizeof(*tree_msg), tree_msg->partial_error); break; + case MGMT_MSG_CODE_EDIT_REPLY: + if (!session->client->cbs.edit_notify) + return; + + edit_msg = (typeof(edit_msg))msg; + if (msg_len < sizeof(*edit_msg)) { + log_err_fe_client("Corrupt edit-reply msg recv"); + return; + } + + xpath = mgmt_msg_native_xpath_decode(edit_msg, msg_len); + if (!xpath) { + log_err_fe_client("Corrupt edit-reply msg recv"); + return; + } + + session->client->cbs.edit_notify(client, client->user_data, + session->client_id, + msg->refer_id, + session->user_ctx, msg->req_id, + xpath); + break; case MGMT_MSG_CODE_NOTIFY: if (!session->client->cbs.async_notification) return; diff --git a/lib/mgmt_fe_client.h b/lib/mgmt_fe_client.h index eee4594e17dc..9d569348ae58 100644 --- a/lib/mgmt_fe_client.h +++ b/lib/mgmt_fe_client.h @@ -114,6 +114,12 @@ struct mgmt_fe_client_cbs { LYD_FORMAT result_type, void *result, size_t len, int partial_error); + /* Called when edit result is returned */ + int (*edit_notify)(struct mgmt_fe_client *client, uintptr_t user_data, + uint64_t client_id, uint64_t session_id, + uintptr_t session_ctx, uint64_t req_id, + const char *xpath); + /* Called with asynchronous notifications from backends */ int (*async_notification)(struct mgmt_fe_client *client, uintptr_t user_data, uint64_t client_id, @@ -409,6 +415,45 @@ extern int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, uint8_t flags, uint8_t defaults, const char *xpath); +/* + * Send EDIT to MGMTD daemon. + * + * client + * Client object. + * + * session_id + * Client session ID. + * + * req_id + * Client request ID. + * + * datastore + * Datastore for editing. + * + * request_type + * The LYD_FORMAT of the request. + * + * flags + * Flags to control the behavior of the request. + * + * operation + * NB_OP_* operation to perform. + * + * xpath + * the xpath to edit. + * + * data + * the data tree. + * + * Returns: + * 0 on success, otherwise msg_conn_send_msg() return values. + */ +extern int mgmt_fe_send_edit_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + uint8_t datastore, LYD_FORMAT request_type, + uint8_t flags, uint8_t operation, + const char *xpath, const char *data); + /* * Destroy library and cleanup everything. */ diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c index 98b7da45ce8d..09ea43ece0bf 100644 --- a/lib/mgmt_msg_native.c +++ b/lib/mgmt_msg_native.c @@ -14,6 +14,8 @@ DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_TREE, "native get tree msg"); DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_TREE_DATA, "native tree data msg"); DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_DATA, "native get data msg"); DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_NOTIFY, "native get data msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_EDIT, "native edit msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_EDIT_REPLY, "native edit reply msg"); int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id, uint64_t req_id, bool short_circuit_ok, diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h index 53bb81be2832..b7c29862aadd 100644 --- a/lib/mgmt_msg_native.h +++ b/lib/mgmt_msg_native.h @@ -21,6 +21,7 @@ extern "C" { #include "memory.h" #include "mgmt_msg.h" #include "mgmt_defines.h" +#include "northbound.h" #include @@ -149,6 +150,8 @@ DECLARE_MTYPE(MSG_NATIVE_GET_TREE); DECLARE_MTYPE(MSG_NATIVE_TREE_DATA); DECLARE_MTYPE(MSG_NATIVE_GET_DATA); DECLARE_MTYPE(MSG_NATIVE_NOTIFY); +DECLARE_MTYPE(MSG_NATIVE_EDIT); +DECLARE_MTYPE(MSG_NATIVE_EDIT_REPLY); /* * Native message codes @@ -158,6 +161,8 @@ DECLARE_MTYPE(MSG_NATIVE_NOTIFY); #define MGMT_MSG_CODE_TREE_DATA 2 #define MGMT_MSG_CODE_GET_DATA 3 #define MGMT_MSG_CODE_NOTIFY 4 +#define MGMT_MSG_CODE_EDIT 5 +#define MGMT_MSG_CODE_EDIT_REPLY 6 /* * Datastores @@ -318,6 +323,60 @@ _Static_assert(sizeof(struct mgmt_msg_notify_data) == offsetof(struct mgmt_msg_notify_data, data), "Size mismatch"); +#define EDIT_FLAG_IMPLICIT_LOCK 0x01 +#define EDIT_FLAG_IMPLICIT_COMMIT 0x02 + +#define EDIT_OP_CREATE 0 +#define EDIT_OP_DELETE 4 +#define EDIT_OP_MERGE 2 +#define EDIT_OP_REPLACE 5 +#define EDIT_OP_REMOVE 3 + +_Static_assert(EDIT_OP_CREATE == NB_OP_CREATE_EXCL, "Operation mismatch"); +_Static_assert(EDIT_OP_DELETE == NB_OP_DELETE, "Operation mismatch"); +_Static_assert(EDIT_OP_MERGE == NB_OP_MODIFY, "Operation mismatch"); +_Static_assert(EDIT_OP_REPLACE == NB_OP_REPLACE, "Operation mismatch"); +_Static_assert(EDIT_OP_REMOVE == NB_OP_DESTROY, "Operation mismatch"); + +/** + * struct mgmt_msg_edit - frontend edit request. + * + * @request_type: ``LYD_FORMAT`` for the @data. + * @flags: combination of ``EDIT_FLAG_*`` flags. + * @datastore: the datastore to edit. + * @operation: one of ``EDIT_OP_*`` operations. + * @data: the xpath followed by the tree data for the operation. + * for CREATE, xpath points to the parent node. + */ +struct mgmt_msg_edit { + struct mgmt_msg_header; + uint8_t request_type; + uint8_t flags; + uint8_t datastore; + uint8_t operation; + uint8_t resv2[4]; + + alignas(8) char data[]; +}; +_Static_assert(sizeof(struct mgmt_msg_edit) == + offsetof(struct mgmt_msg_edit, data), + "Size mismatch"); + +/** + * struct mgmt_msg_edit_reply - frontend edit reply. + * + * @data: the xpath of the data node that was created. + */ +struct mgmt_msg_edit_reply { + struct mgmt_msg_header; + uint8_t resv2[8]; + + alignas(8) char data[]; +}; +_Static_assert(sizeof(struct mgmt_msg_edit_reply) == + offsetof(struct mgmt_msg_edit_reply, data), + "Size mismatch"); + /* * Validate that the message ends in a NUL terminating byte */ @@ -504,13 +563,13 @@ extern int vmgmt_msg_native_send_error(struct msg_conn *conn, * The xpath string or NULL if there was an error decoding (i.e., the * message is corrupt). */ -#define mgmt_msg_native_xpath_data_decode(msg, msglen, data) \ +#define mgmt_msg_native_xpath_data_decode(msg, msglen, __data) \ ({ \ size_t __len = (msglen) - sizeof(*msg); \ const char *__s = NULL; \ if (msg->vsplit && msg->vsplit <= __len && \ msg->data[msg->vsplit - 1] == 0) { \ - (data) = msg->data + msg->vsplit; \ + (__data) = msg->data + msg->vsplit; \ __s = msg->data; \ } \ __s; \ diff --git a/lib/northbound.c b/lib/northbound.c index 487f225913cf..37b655211879 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -813,6 +813,223 @@ int nb_candidate_edit(struct nb_config *candidate, const struct nb_node *nb_node return NB_OK; } +static int nb_candidate_edit_tree_add(struct nb_config *candidate, + enum nb_operation operation, + LYD_FORMAT format, const char *xpath, + const char *data, char *xpath_created, + char *errmsg, size_t errmsg_len) +{ + struct lyd_node *tree = NULL; + struct lyd_node *parent = NULL; + struct lyd_node *dnode = NULL; + struct lyd_node *existing = NULL; + struct lyd_node *ex_parent = NULL; + char *parent_xpath = NULL; + struct ly_in *in; + LY_ERR err; + bool root; + int ret; + + ly_in_new_memory(data, &in); + + root = xpath[0] == 0 || (xpath[0] == '/' && xpath[1] == 0); + + /* get parent xpath if xpath is not root */ + if (!root) { + /* NB_OP_CREATE_EXCT already expects parent xpath */ + parent_xpath = XSTRDUP(MTYPE_TMP, xpath); + + /* for other operations - pop one level */ + if (operation != NB_OP_CREATE_EXCL) { + ret = yang_xpath_pop_node(parent_xpath); + if (ret) { + snprintf(errmsg, errmsg_len, "Invalid xpath"); + goto done; + } + + /* root is not actually a parent */ + if (parent_xpath[0] == 0) + XFREE(MTYPE_TMP, parent_xpath); + } + } + + /* + * Create parent if it's not root. We're creating a new tree here to be + * merged later with candidate. + */ + if (parent_xpath) { + err = lyd_new_path2(NULL, ly_native_ctx, parent_xpath, NULL, 0, + 0, 0, &tree, &parent); + if (err) { + yang_print_errors(ly_native_ctx, errmsg, errmsg_len); + ret = NB_ERR; + goto done; + } + assert(parent); + } + + /* parse data */ + err = yang_lyd_parse_data(ly_native_ctx, parent, in, format, + LYD_PARSE_ONLY | LYD_PARSE_STRICT | + LYD_PARSE_NO_STATE, + 0, &dnode); + if (err) { + yang_print_errors(ly_native_ctx, errmsg, errmsg_len); + ret = NB_ERR; + goto done; + } + + /* set the tree if we created a top-level node */ + if (!parent) + tree = dnode; + + /* save xpath of the created node */ + lyd_path(dnode, LYD_PATH_STD, xpath_created, XPATH_MAXLEN); + + /* verify that list keys are the same in the xpath and the data tree */ + if (!root && (operation == NB_OP_REPLACE || operation == NB_OP_MODIFY)) { + if (lyd_find_path(tree, xpath, 0, NULL)) { + snprintf(errmsg, errmsg_len, + "List keys in xpath and data tree are different"); + ret = NB_ERR; + goto done; + } + } + + /* check if the node already exists in candidate */ + if (operation == NB_OP_CREATE_EXCL || operation == NB_OP_REPLACE) { + existing = yang_dnode_get(candidate->dnode, xpath_created); + + /* if the existing node is implicit default, ignore */ + if (existing && (existing->flags & LYD_DEFAULT)) + existing = NULL; + + if (existing) { + if (operation == NB_OP_CREATE_EXCL) { + snprintf(errmsg, errmsg_len, + "Data already exists"); + ret = NB_ERR; + goto done; + } + + if (root) { + candidate->dnode = NULL; + } else { + /* if it's the first top-level node, update candidate */ + if (candidate->dnode == existing) + candidate->dnode = + candidate->dnode->next; + + ex_parent = lyd_parent(existing); + lyd_unlink_tree(existing); + } + } + } + + err = lyd_merge_siblings(&candidate->dnode, tree, + LYD_MERGE_DESTRUCT | LYD_MERGE_WITH_FLAGS); + if (err) { + /* if replace failed, restore the original node */ + if (existing) { + if (root) { + candidate->dnode = existing; + } else { + if (ex_parent) + lyd_insert_child(ex_parent, existing); + else + lyd_insert_sibling(candidate->dnode, + existing, + &candidate->dnode); + } + } + yang_print_errors(ly_native_ctx, errmsg, errmsg_len); + ret = NB_ERR; + goto done; + } else { + /* + * Free existing node after replace. + * We're using `lyd_free_siblings` here to free the whole + * tree if we replaced the root node. It won't affect other + * siblings if it wasn't root, because the existing node + * was unlinked from the tree. + */ + if (existing) + lyd_free_siblings(existing); + + tree = NULL; /* LYD_MERGE_DESTRUCT deleted the tree */ + } + + ret = NB_OK; +done: + if (tree) + lyd_free_all(tree); + XFREE(MTYPE_TMP, parent_xpath); + ly_in_free(in, 0); + + return ret; +} + +static int nb_candidate_edit_tree_del(struct nb_config *candidate, + enum nb_operation operation, + const char *xpath, char *errmsg, + size_t errmsg_len) +{ + struct lyd_node *dnode; + + /* deleting root - remove the whole config */ + if (xpath[0] == 0 || (xpath[0] == '/' && xpath[1] == 0)) { + lyd_free_all(candidate->dnode); + candidate->dnode = NULL; + return NB_OK; + } + + dnode = yang_dnode_get(candidate->dnode, xpath); + if (!dnode || (dnode->flags & LYD_DEFAULT)) { + if (operation == NB_OP_DELETE) { + snprintf(errmsg, errmsg_len, "Data missing"); + return NB_ERR; + } else + return NB_OK; + } + + /* if it's the first top-level node, update candidate */ + if (candidate->dnode == dnode) + candidate->dnode = candidate->dnode->next; + + lyd_free_tree(dnode); + + return NB_OK; +} + +int nb_candidate_edit_tree(struct nb_config *candidate, + enum nb_operation operation, LYD_FORMAT format, + const char *xpath, const char *data, + char *xpath_created, char *errmsg, size_t errmsg_len) +{ + int ret = NB_ERR; + + switch (operation) { + case NB_OP_CREATE_EXCL: + case NB_OP_CREATE: + case NB_OP_MODIFY: + case NB_OP_REPLACE: + ret = nb_candidate_edit_tree_add(candidate, operation, format, + xpath, data, xpath_created, + errmsg, errmsg_len); + break; + case NB_OP_DESTROY: + case NB_OP_DELETE: + ret = nb_candidate_edit_tree_del(candidate, operation, xpath, + errmsg, errmsg_len); + break; + case NB_OP_MOVE: + /* not supported yet */ + break; + } + + return ret; +} + const char *nb_operation_name(enum nb_operation operation) { switch (operation) { diff --git a/lib/northbound.h b/lib/northbound.h index 5be111cf0a12..15a49999437e 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -1004,6 +1004,44 @@ extern int nb_candidate_edit(struct nb_config *candidate, const struct yang_data *previous, const struct yang_data *data); +/* + * Edit a candidate configuration. Value is given as JSON/XML. + * + * candidate + * Candidate configuration to edit. + * + * operation + * Operation to apply. + * + * format + * LYD_FORMAT of the value. + * + * xpath + * XPath of the configuration node being edited. + * For create, it must be the parent. + * + * data + * New data tree for the node. + * + * xpath_created + * XPath of the created node if operation is "create". + * + * errmsg + * Buffer to store human-readable error message in case of error. + * + * errmsg_len + * Size of errmsg. + * + * Returns: + * - NB_OK on success. + * - NB_ERR for other errors. + */ +extern int nb_candidate_edit_tree(struct nb_config *candidate, + enum nb_operation operation, + LYD_FORMAT format, const char *xpath, + const char *data, char *xpath_created, + char *errmsg, size_t errmsg_len); + /* * Create diff for configuration. * diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c index 2394b5e86581..7e7190f5a49e 100644 --- a/lib/northbound_oper.c +++ b/lib/northbound_oper.c @@ -341,38 +341,6 @@ static void nb_op_resume_data_tree(struct nb_op_yield_state *ys) /* Start of walk init code */ /* ======================= */ -/** - * __xpath_pop_node() - remove the last node from xpath string - * @xpath: an xpath string - * - * Return: NB_OK or NB_ERR_NOT_FOUND if nothing left to pop. - */ -static int __xpath_pop_node(char *xpath) -{ - int len = strlen(xpath); - bool abs = xpath[0] == '/'; - char *slash; - - /* "//" or "/" => NULL */ - if (abs && (len == 1 || (len == 2 && xpath[1] == '/'))) - return NB_ERR_NOT_FOUND; - - slash = (char *)frrstr_back_to_char(xpath, '/'); - /* "/foo/bar/" or "/foo/bar//" => "/foo " */ - if (slash && slash == &xpath[len - 1]) { - xpath[--len] = 0; - slash = (char *)frrstr_back_to_char(xpath, '/'); - if (slash && slash == &xpath[len - 1]) { - xpath[--len] = 0; - slash = (char *)frrstr_back_to_char(xpath, '/'); - } - } - if (!slash) - return NB_ERR_NOT_FOUND; - *slash = 0; - return NB_OK; -} - /** * nb_op_xpath_to_trunk() - generate a lyd_node tree (trunk) using an xpath. * @xpath_in: xpath query string to build trunk from. @@ -398,7 +366,7 @@ static enum nb_error nb_op_xpath_to_trunk(const char *xpath_in, if (err == LY_SUCCESS) break; - ret = __xpath_pop_node(xpath); + ret = yang_xpath_pop_node(xpath); if (ret != NB_OK) break; } diff --git a/lib/vty.c b/lib/vty.c index 912c89355610..d69b5eebd270 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -3826,6 +3826,23 @@ static int vty_mgmt_get_tree_result_notified( return 0; } +static int vty_mgmt_edit_result_notified(struct mgmt_fe_client *client, + uintptr_t user_data, + uint64_t client_id, uint64_t session_id, + uintptr_t session_ctx, uint64_t req_id, + const char *xpath) +{ + struct vty *vty = (struct vty *)session_ctx; + + debug_fe_client("EDIT request for client 0x%" PRIx64 " req-id %" PRIu64 + " was successful, xpath: %s", + client_id, req_id, xpath); + + vty_mgmt_resume_response(vty, CMD_SUCCESS); + + return 0; +} + static int vty_mgmt_error_notified(struct mgmt_fe_client *client, uintptr_t user_data, uint64_t client_id, uint64_t session_id, uintptr_t session_ctx, @@ -3867,6 +3884,7 @@ static struct mgmt_fe_client_cbs mgmt_cbs = { .commit_config_notify = vty_mgmt_commit_config_result_notified, .get_data_notify = vty_mgmt_get_data_result_notified, .get_tree_notify = vty_mgmt_get_tree_result_notified, + .edit_notify = vty_mgmt_edit_result_notified, .error_notify = vty_mgmt_error_notified, }; @@ -4122,6 +4140,28 @@ int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, return 0; } +int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, + LYD_FORMAT request_type, uint8_t flags, + uint8_t operation, const char *xpath, + const char *data) +{ + vty->mgmt_req_id++; + + if (mgmt_fe_send_edit_req(mgmt_fe_client, vty->mgmt_session_id, + vty->mgmt_req_id, datastore, request_type, + flags, operation, xpath, data)) { + zlog_err("Failed to send EDIT to MGMTD session-id: %" PRIu64 + " req-id %" PRIu64 ".", + vty->mgmt_session_id, vty->mgmt_req_id); + vty_out(vty, "Failed to send EDIT to MGMTD!\n"); + return -1; + } + + vty->mgmt_req_pending_cmd = "MESSAGE_EDIT_REQ"; + + return 0; +} + /* Install vty's own commands like `who' command. */ void vty_init(struct event_loop *master_thread, bool do_command_logging) { diff --git a/lib/vty.h b/lib/vty.h index a59ac7a652ee..0e0c92c33650 100644 --- a/lib/vty.h +++ b/lib/vty.h @@ -419,6 +419,10 @@ extern int vty_mgmt_send_get_req(struct vty *vty, bool is_config, extern int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, LYD_FORMAT result_type, uint8_t flags, uint8_t defaults, const char *xpath); +extern int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, + LYD_FORMAT request_type, uint8_t flags, + uint8_t operation, const char *xpath, + const char *data); extern int vty_mgmt_send_lockds_req(struct vty *vty, Mgmtd__DatastoreId ds_id, bool lock, bool scok); extern void vty_mgmt_resume_response(struct vty *vty, int ret); diff --git a/lib/yang.c b/lib/yang.c index d71cb2f498c7..013a7628424a 100644 --- a/lib/yang.c +++ b/lib/yang.c @@ -12,6 +12,7 @@ #include "yang.h" #include "yang_translator.h" #include "northbound.h" +#include "frrstr.h" #include "lib/config_paths.h" @@ -1122,6 +1123,32 @@ int yang_get_node_keys(struct lyd_node *node, struct yang_list_keys *keys) return NB_OK; } +int yang_xpath_pop_node(char *xpath) +{ + int len = strlen(xpath); + bool abs = xpath[0] == '/'; + char *slash; + + /* "//" or "/" => NULL */ + if (abs && (len == 1 || (len == 2 && xpath[1] == '/'))) + return NB_ERR_NOT_FOUND; + + slash = (char *)frrstr_back_to_char(xpath, '/'); + /* "/foo/bar/" or "/foo/bar//" => "/foo " */ + if (slash && slash == &xpath[len - 1]) { + xpath[--len] = 0; + slash = (char *)frrstr_back_to_char(xpath, '/'); + if (slash && slash == &xpath[len - 1]) { + xpath[--len] = 0; + slash = (char *)frrstr_back_to_char(xpath, '/'); + } + } + if (!slash) + return NB_ERR_NOT_FOUND; + *slash = 0; + return NB_OK; +} + /* * ------------------------ * Libyang Future Functions @@ -1275,6 +1302,42 @@ LY_ERR yang_lyd_trim_xpath(struct lyd_node **root, const char *xpath) #endif } +/* Can be replaced by `lyd_parse_data` with libyang >= 2.1.156 */ +LY_ERR yang_lyd_parse_data(const struct ly_ctx *ctx, struct lyd_node *parent, + struct ly_in *in, LYD_FORMAT format, + uint32_t parse_options, uint32_t validate_options, + struct lyd_node **tree) +{ + struct lyd_node *child; + LY_ERR err; + + err = lyd_parse_data(ctx, parent, in, format, parse_options, + validate_options, tree); + if (err) + return err; + + if (!parent || !(parse_options & LYD_PARSE_ONLY)) + return LY_SUCCESS; + + /* + * Versions prior to 2.1.156 don't return `tree` if `parent` is not NULL + * and validation is disabled (`LYD_PARSE_ONLY`). To work around this, + * go through the children and find the one with `LYD_NEW` flag set. + */ + *tree = NULL; + + LY_LIST_FOR (lyd_child_no_keys(parent), child) { + if (child->flags & LYD_NEW) { + *tree = child; + break; + } + } + + assert(tree); + + return LY_SUCCESS; +} + /* * Safe to remove after libyang v2.1.128 is required */ diff --git a/lib/yang.h b/lib/yang.h index 65f6a73e0b50..a831c9bfdfda 100644 --- a/lib/yang.h +++ b/lib/yang.h @@ -770,6 +770,14 @@ extern int yang_get_key_preds(char *s, const struct lysc_node *snode, /* Get YANG keys from an existing dnode */ extern int yang_get_node_keys(struct lyd_node *node, struct yang_list_keys *keys); +/** + * yang_xpath_pop_node() - remove the last node from xpath string + * @xpath: an xpath string + * + * Return: NB_OK or NB_ERR_NOT_FOUND if nothing left to pop. + */ +extern int yang_xpath_pop_node(char *xpath); + /** * yang_resolve_snodes() - Resolve an XPath to matching schema nodes. * @ly_ctx: libyang context to operate on. @@ -800,6 +808,11 @@ extern LY_ERR yang_lyd_new_list(struct lyd_node_inner *parent, const struct yang_list_keys *keys, struct lyd_node **nodes); extern LY_ERR yang_lyd_trim_xpath(struct lyd_node **rootp, const char *xpath); +extern LY_ERR yang_lyd_parse_data(const struct ly_ctx *ctx, + struct lyd_node *parent, struct ly_in *in, + LYD_FORMAT format, uint32_t parse_options, + uint32_t validate_options, + struct lyd_node **tree); #ifdef __cplusplus } diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c index 62d1a0109afc..ab0da64d8f88 100644 --- a/mgmtd/mgmt_fe_adapter.c +++ b/mgmtd/mgmt_fe_adapter.c @@ -898,11 +898,13 @@ static int mgmt_fe_session_handle_commit_config_req_msg( /* * Create COMMITConfig request under the transaction */ - if (mgmt_txn_send_commit_config_req( - session->cfg_txn_id, commcfg_req->req_id, - commcfg_req->src_ds_id, src_ds_ctx, commcfg_req->dst_ds_id, - dst_ds_ctx, commcfg_req->validate_only, commcfg_req->abort, - false) != 0) { + if (mgmt_txn_send_commit_config_req(session->cfg_txn_id, + commcfg_req->req_id, + commcfg_req->src_ds_id, src_ds_ctx, + commcfg_req->dst_ds_id, dst_ds_ctx, + commcfg_req->validate_only, + commcfg_req->abort, false, + NULL) != 0) { fe_adapter_send_commit_cfg_reply( session, commcfg_req->src_ds_id, commcfg_req->dst_ds_id, commcfg_req->req_id, MGMTD_INTERNAL_ERROR, @@ -1099,6 +1101,33 @@ static int fe_adapter_send_tree_data(struct mgmt_fe_session_ctx *session, return ret; } +static int fe_adapter_send_edit_reply(struct mgmt_fe_session_ctx *session, + uint64_t req_id, const char *xpath) +{ + struct mgmt_msg_edit_reply *msg; + int ret; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit_reply, 0, + MTYPE_MSG_NATIVE_EDIT_REPLY); + msg->refer_id = session->session_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_EDIT_REPLY; + + mgmt_msg_native_xpath_encode(msg, xpath); + + __dbg("Sending edit-reply from adapter %s to session-id %" PRIu64 + " req-id %" PRIu64 " len %u", + session->adapter->name, session->session_id, req_id, + mgmt_msg_native_get_msg_len(msg)); + + ret = fe_adapter_send_native_msg(session->adapter, msg, + mgmt_msg_native_get_msg_len(msg), + false); + mgmt_msg_native_free_msg(msg); + + return ret; +} + /** * fe_adapter_handle_get_data() - Handle a get-tree message from a FE client. * @session: the client session. @@ -1224,6 +1253,112 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, darr_free(xpath_resolved); } +static void fe_adapter_handle_edit(struct mgmt_fe_session_ctx *session, + void *__msg, size_t msg_len) +{ + struct mgmt_msg_edit *msg = __msg; + Mgmtd__DatastoreId ds_id, rds_id; + struct mgmt_ds_ctx *ds_ctx, *rds_ctx; + const char *xpath, *data; + bool lock, commit; + int ret; + + if (msg->datastore != MGMT_MSG_DATASTORE_CANDIDATE) { + fe_adapter_send_error(session, msg->req_id, false, -EINVAL, + "Unsupported datastore"); + return; + } + + xpath = mgmt_msg_native_xpath_data_decode(msg, msg_len, data); + if (!xpath || !data) { + fe_adapter_send_error(session, msg->req_id, false, -EINVAL, + "Invalid message"); + return; + } + + ds_id = MGMTD_DS_CANDIDATE; + ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); + assert(ds_ctx); + + rds_id = MGMTD_DS_RUNNING; + rds_ctx = mgmt_ds_get_ctx_by_id(mm, rds_id); + assert(rds_ctx); + + lock = CHECK_FLAG(msg->flags, EDIT_FLAG_IMPLICIT_LOCK); + commit = CHECK_FLAG(msg->flags, EDIT_FLAG_IMPLICIT_COMMIT); + + if (lock) { + if (mgmt_fe_session_write_lock_ds(ds_id, ds_ctx, session)) { + fe_adapter_send_error(session, msg->req_id, false, + -EBUSY, + "Candidate DS is locked by another session"); + return; + } + + if (commit) { + if (mgmt_fe_session_write_lock_ds(rds_id, rds_ctx, + session)) { + mgmt_fe_session_unlock_ds(ds_id, ds_ctx, + session); + fe_adapter_send_error( + session, msg->req_id, false, -EBUSY, + "Running DS is locked by another session"); + return; + } + } + } else { + if (!session->ds_locked[ds_id]) { + fe_adapter_send_error(session, msg->req_id, false, + -EBUSY, + "Candidate DS is not locked"); + return; + } + + if (commit) { + if (!session->ds_locked[rds_id]) { + fe_adapter_send_error(session, msg->req_id, + false, -EBUSY, + "Running DS is not locked"); + return; + } + } + } + + session->cfg_txn_id = mgmt_create_txn(session->session_id, + MGMTD_TXN_TYPE_CONFIG); + if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) { + if (lock) { + mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); + if (commit) + mgmt_fe_session_unlock_ds(rds_id, rds_ctx, + session); + } + fe_adapter_send_error(session, msg->req_id, false, -EBUSY, + "Failed to create a configuration transaction"); + return; + } + + __dbg("Created new config txn-id: %" PRIu64 " for session-id: %" PRIu64, + session->cfg_txn_id, session->session_id); + + ret = mgmt_txn_send_edit(session->cfg_txn_id, msg->req_id, ds_id, + ds_ctx, rds_id, rds_ctx, lock, commit, + msg->request_type, msg->flags, msg->operation, + xpath, data); + if (ret) { + /* destroy the just created txn */ + mgmt_destroy_txn(&session->cfg_txn_id); + if (lock) { + mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); + if (commit) + mgmt_fe_session_unlock_ds(rds_id, rds_ctx, + session); + } + fe_adapter_send_error(session, msg->req_id, false, -EBUSY, + "Failed to create a configuration transaction"); + } +} + /** * Handle a native encoded message from the FE client. */ @@ -1245,6 +1380,9 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, case MGMT_MSG_CODE_GET_DATA: fe_adapter_handle_get_data(session, msg, msg_len); break; + case MGMT_MSG_CODE_EDIT: + fe_adapter_handle_edit(session, msg, msg_len); + break; default: __log_err("unknown native message session-id %" PRIu64 " req-id %" PRIu64 " code %u to FE adapter %s", @@ -1484,6 +1622,52 @@ int mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, return ret; } +int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, + uint64_t req_id, bool unlock, bool commit, + const char *xpath, int16_t error, + const char *errstr) +{ + struct mgmt_fe_session_ctx *session; + Mgmtd__DatastoreId ds_id, rds_id; + struct mgmt_ds_ctx *ds_ctx, *rds_ctx; + int ret; + + session = mgmt_session_id2ctx(session_id); + if (!session || session->cfg_txn_id != txn_id) + return -1; + + if (session->cfg_txn_id != MGMTD_TXN_ID_NONE && commit) + mgmt_fe_session_register_event(session, + MGMTD_FE_SESSION_CFG_TXN_CLNUP); + + if (unlock) { + ds_id = MGMTD_DS_CANDIDATE; + ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); + assert(ds_ctx); + + mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); + + if (commit) { + rds_id = MGMTD_DS_RUNNING; + rds_ctx = mgmt_ds_get_ctx_by_id(mm, rds_id); + assert(rds_ctx); + + mgmt_fe_session_unlock_ds(rds_id, rds_ctx, session); + } + } + + if (error) + ret = fe_adapter_send_error(session, req_id, false, error, "%s", + errstr); + else + ret = fe_adapter_send_edit_reply(session, req_id, xpath); + + if (session->cfg_txn_id != MGMTD_TXN_ID_NONE && !commit) + mgmt_destroy_txn(&session->cfg_txn_id); + + return ret; +} + /** * Send an error back to the FE client and cleanup any in-progress txn. */ diff --git a/mgmtd/mgmt_fe_adapter.h b/mgmtd/mgmt_fe_adapter.h index e768a3cca0b3..8d61ffe9106b 100644 --- a/mgmtd/mgmt_fe_adapter.h +++ b/mgmtd/mgmt_fe_adapter.h @@ -162,6 +162,26 @@ mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, uint32_t wd_options, const struct lyd_node *tree, int partial_error, bool short_circuit_ok); +/** + * Send edit reply back to client. If error is not 0, a native error is sent. + * + * This also cleans up and frees the transaction. + * + * Args: + * session_id: the session. + * txn_id: the txn_id this data pertains to + * req_id: the req id for the edit message + * unlock: implicit-lock flag was set in the request + * commit: implicit-commit flag was set in the request + * xpath: the xpath of the data node that was created + * error: the error code, zero for successful request + * errstr: the error string, if error is non-zero + */ +extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, + uint64_t req_id, bool unlock, + bool commit, const char *xpath, + int16_t error, const char *errstr); + /** * Send an error back to the FE client using native messaging. * diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c index 3a052c0e35fc..901163c6e69a 100644 --- a/mgmtd/mgmt_txn.c +++ b/mgmtd/mgmt_txn.c @@ -91,6 +91,11 @@ DECLARE_LIST(mgmt_txn_batches, struct mgmt_txn_be_cfg_batch, list_linkage); #define FOREACH_TXN_CFG_BATCH_IN_LIST(list, batch) \ frr_each_safe (mgmt_txn_batches, list, batch) +struct mgmt_edit_req { + char xpath_created[XPATH_MAXLEN]; + bool unlock; +}; + struct mgmt_commit_cfg_req { Mgmtd__DatastoreId src_ds_id; struct mgmt_ds_ctx *src_ds_ctx; @@ -108,6 +113,12 @@ struct mgmt_commit_cfg_req { enum mgmt_commit_phase be_phase[MGMTD_BE_CLIENT_ID_MAX]; + /* + * Additional information when the commit is triggered by native edit + * request. + */ + struct mgmt_edit_req *edit; + /* * Set of config changes to commit. This is used only * when changes are NOT to be determined by comparing @@ -444,6 +455,8 @@ static void mgmt_txn_req_free(struct mgmt_txn_req **txn_req) cleanup = (ccreq->phase >= MGMTD_COMMIT_PHASE_TXN_CREATE && ccreq->phase < MGMTD_COMMIT_PHASE_TXN_DELETE); + XFREE(MTYPE_MGMTD_TXN_REQ, ccreq->edit); + FOREACH_MGMTD_BE_CLIENT_ID (id) { /* * Send TXN_DELETE to cleanup state for this @@ -604,7 +617,8 @@ static void mgmt_txn_process_set_cfg(struct event *thread) ->dst_ds_id, txn_req->req.set_cfg ->dst_ds_ctx, - false, false, true); + false, false, true, + NULL); if (mm->perf_stats_en) gettimeofday(&cmt_stats->last_start, NULL); @@ -655,7 +669,8 @@ static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn, * b/c right now that is special cased.. that special casing should be * removed; however... */ - if (!txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id && + if (!txn->commit_cfg_req->req.commit_cfg.edit && + !txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id && !txn->commit_cfg_req->req.commit_cfg.rollback && mgmt_fe_send_commit_cfg_reply(txn->session_id, txn->txn_id, txn->commit_cfg_req->req.commit_cfg @@ -671,7 +686,8 @@ static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn, txn->txn_id, txn->session_id); } - if (txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id && + if (!txn->commit_cfg_req->req.commit_cfg.edit && + txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id && !txn->commit_cfg_req->req.commit_cfg.rollback && mgmt_fe_send_set_cfg_reply(txn->session_id, txn->txn_id, txn->commit_cfg_req->req.commit_cfg @@ -685,6 +701,21 @@ static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn, txn->txn_id, txn->session_id); } + if (txn->commit_cfg_req->req.commit_cfg.edit && + mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id, + txn->commit_cfg_req->req_id, + txn->commit_cfg_req->req.commit_cfg + .edit->unlock, + true, + txn->commit_cfg_req->req.commit_cfg + .edit->xpath_created, + success ? 0 : -1, + error_if_any) != 0) { + __log_err("Failed to send EDIT-REPLY txn-id: %" PRIu64 + " session-id: %" PRIu64, + txn->txn_id, txn->session_id); + } + if (success) { /* Stop the commit-timeout timer */ /* XXX why only on success? */ @@ -2011,7 +2042,7 @@ int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, Mgmtd__DatastoreId dst_ds_id, struct mgmt_ds_ctx *dst_ds_ctx, bool validate_only, bool abort, - bool implicit) + bool implicit, struct mgmt_edit_req *edit) { struct mgmt_txn_ctx *txn; struct mgmt_txn_req *txn_req; @@ -2035,6 +2066,7 @@ int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, txn_req->req.commit_cfg.validate_only = validate_only; txn_req->req.commit_cfg.abort = abort; txn_req->req.commit_cfg.implicit = implicit; + txn_req->req.commit_cfg.edit = edit; txn_req->req.commit_cfg.cmt_stats = mgmt_fe_get_session_commit_stats(txn->session_id); @@ -2418,6 +2450,52 @@ int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, return 0; } +int mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, + Mgmtd__DatastoreId ds_id, struct mgmt_ds_ctx *ds_ctx, + Mgmtd__DatastoreId commit_ds_id, + struct mgmt_ds_ctx *commit_ds_ctx, bool unlock, + bool commit, LYD_FORMAT request_type, uint8_t flags, + uint8_t operation, const char *xpath, const char *data) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_edit_req *edit; + struct nb_config *nb_config; + char errstr[BUFSIZ]; + int ret; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn) + return -1; + + edit = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_edit_req)); + + nb_config = mgmt_ds_get_nb_config(ds_ctx); + assert(nb_config); + + ret = nb_candidate_edit_tree(nb_config, operation, request_type, xpath, + data, edit->xpath_created, errstr, + sizeof(errstr)); + if (ret) + goto reply; + + if (commit) { + edit->unlock = unlock; + + mgmt_txn_send_commit_config_req(txn_id, req_id, ds_id, ds_ctx, + commit_ds_id, commit_ds_ctx, + false, false, true, edit); + return 0; + } +reply: + mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id, req_id, + unlock, commit, edit->xpath_created, + ret ? -1 : 0, errstr); + + XFREE(MTYPE_MGMTD_TXN_REQ, edit); + + return 0; +} + /* * Error reply from the backend client. */ diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h index b7198326da7e..aeb74469f141 100644 --- a/mgmtd/mgmt_txn.h +++ b/mgmtd/mgmt_txn.h @@ -43,6 +43,7 @@ PREDECL_LIST(mgmt_txns); struct mgmt_master; +struct mgmt_edit_req; enum mgmt_txn_type { MGMTD_TXN_TYPE_NONE = 0, @@ -171,16 +172,17 @@ extern int mgmt_txn_send_set_config_req(uint64_t txn_id, uint64_t req_id, * implicit * TRUE if the commit is implicit, FALSE otherwise. * + * edit + * Additional info when triggered from native edit request. + * * Returns: * 0 on success, -1 on failures. */ -extern int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, - Mgmtd__DatastoreId src_ds_id, - struct mgmt_ds_ctx *dst_ds_ctx, - Mgmtd__DatastoreId dst_ds_id, - struct mgmt_ds_ctx *src_ds_ctx, - bool validate_only, bool abort, - bool implicit); +extern int mgmt_txn_send_commit_config_req( + uint64_t txn_id, uint64_t req_id, Mgmtd__DatastoreId src_ds_id, + struct mgmt_ds_ctx *dst_ds_ctx, Mgmtd__DatastoreId dst_ds_id, + struct mgmt_ds_ctx *src_ds_ctx, bool validate_only, bool abort, + bool implicit, struct mgmt_edit_req *edit); /* * Send get-{cfg,data} request to be processed later in transaction. @@ -219,6 +221,31 @@ extern int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, uint32_t wd_options, bool simple_xpath, const char *xpath); +/** + * Send edit request. + * + * Args: + * txn_id: Transaction identifier. + * req_id: FE client request identifier. + * ds_id: Datastore ID. + * ds_ctx: Datastore context. + * commit_ds_id: Commit datastore ID. + * commit_ds_ctx: Commit datastore context. + * unlock: Unlock datastores after the edit. + * commit: Commit the candidate datastore after the edit. + * request_type: LYD_FORMAT request type. + * flags: option flags for the request. + * operation: The operation to perform. + * xpath: The xpath of data node to edit. + * data: The data tree. + */ +extern int +mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, Mgmtd__DatastoreId ds_id, + struct mgmt_ds_ctx *ds_ctx, Mgmtd__DatastoreId commit_ds_id, + struct mgmt_ds_ctx *commit_ds_ctx, bool unlock, bool commit, + LYD_FORMAT request_type, uint8_t flags, uint8_t operation, + const char *xpath, const char *data); + /* * Notifiy backend adapter on connection. */ diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c index 2cd24719bc04..61d0760e05d6 100644 --- a/mgmtd/mgmt_vty.c +++ b/mgmtd/mgmt_vty.c @@ -238,6 +238,64 @@ DEFPY(mgmt_replace_config_data, mgmt_replace_config_data_cmd, return CMD_SUCCESS; } +DEFPY(mgmt_edit, mgmt_edit_cmd, + "mgmt edit {create|delete|merge|replace|remove}$op XPATH [json|xml]$fmt [lock$lock] [commit$commit] [DATA]", + MGMTD_STR + "Edit configuration data\n" + "Create data\n" + "Delete data\n" + "Merge data\n" + "Replace data\n" + "Remove data\n" + "XPath expression specifying the YANG data path\n" + "JSON input format (default)\n" + "XML input format\n" + "Lock the datastores automatically\n" + "Commit the changes automatically\n" + "Data tree\n") +{ + LYD_FORMAT format = (fmt && fmt[0] == 'x') ? LYD_XML : LYD_JSON; + uint8_t operation; + uint8_t flags = 0; + + switch (op[2]) { + case 'e': + operation = NB_OP_CREATE_EXCL; + break; + case 'l': + operation = NB_OP_DELETE; + break; + case 'r': + operation = NB_OP_MODIFY; + break; + case 'p': + operation = NB_OP_REPLACE; + break; + case 'm': + operation = NB_OP_DESTROY; + break; + default: + vty_out(vty, "Invalid operation!\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!data && (operation == NB_OP_CREATE_EXCL || + operation == NB_OP_MODIFY || operation == NB_OP_REPLACE)) { + vty_out(vty, "Data tree is missing!\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (lock) + flags |= EDIT_FLAG_IMPLICIT_LOCK; + + if (commit) + flags |= EDIT_FLAG_IMPLICIT_COMMIT; + + vty_mgmt_send_edit_req(vty, MGMT_MSG_DATASTORE_CANDIDATE, format, flags, + operation, xpath, data); + return CMD_SUCCESS; +} + DEFPY(show_mgmt_get_config, show_mgmt_get_config_cmd, "show mgmt get-config [candidate|operational|running]$dsname WORD$path", SHOW_STR MGMTD_STR @@ -643,6 +701,7 @@ void mgmt_vty_init(void) install_element(CONFIG_NODE, &mgmt_delete_config_data_cmd); install_element(CONFIG_NODE, &mgmt_remove_config_data_cmd); install_element(CONFIG_NODE, &mgmt_replace_config_data_cmd); + install_element(CONFIG_NODE, &mgmt_edit_cmd); install_element(CONFIG_NODE, &mgmt_load_config_cmd); install_element(CONFIG_NODE, &mgmt_save_config_cmd); install_element(CONFIG_NODE, &mgmt_rollback_cmd);