From 1869663d03dcf7dc7ab7e2436bd97b47370f7abc Mon Sep 17 00:00:00 2001 From: Igor Ryzhov Date: Tue, 6 Feb 2024 02:22:12 +0200 Subject: [PATCH] mgmtd: add support for native 'edit' operation This operation basically implements support for RESTCONF operations. It receives an xpath and data in JSON/XML format, instead of (xpath, value) tuples as required by the current protobuf interface. Signed-off-by: Igor Ryzhov --- lib/mgmt_fe_client.c | 33 +++++++ lib/mgmt_fe_client.h | 39 ++++++++ lib/mgmt_msg_native.c | 1 + lib/mgmt_msg_native.h | 47 +++++++++ lib/northbound.c | 211 ++++++++++++++++++++++++++++++++++++++++ lib/northbound.h | 31 ++++++ lib/northbound_oper.c | 34 +------ lib/vty.c | 22 +++++ lib/vty.h | 4 + lib/yang.c | 61 ++++++++++++ lib/yang.h | 13 +++ mgmtd/mgmt_fe_adapter.c | 131 ++++++++++++++++++++++++- mgmtd/mgmt_main.c | 7 ++ mgmtd/mgmt_txn.c | 88 ++++++++++++++++- mgmtd/mgmt_txn.h | 43 ++++++-- mgmtd/mgmt_vty.c | 59 +++++++++++ mgmtd/subdir.am | 1 + 17 files changed, 776 insertions(+), 49 deletions(-) diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c index bf9514db1974..40c922e71d93 100644 --- a/lib/mgmt_fe_client.c +++ b/lib/mgmt_fe_client.c @@ -329,6 +329,39 @@ 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 *value) +{ + struct mgmt_msg_edit *msg; + size_t xplen = strlen(xpath) + 1; + size_t vlen = value ? strlen(value) + 1 : 0; + int ret; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit, xplen + vlen, + 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; + msg->xpath_len = xplen; + strlcpy(msg->data, xpath, xplen); + if (value) + strlcpy(msg->data + xplen, value, vlen); + + 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) diff --git a/lib/mgmt_fe_client.h b/lib/mgmt_fe_client.h index ec7c68cc0a9f..989f059d87e0 100644 --- a/lib/mgmt_fe_client.h +++ b/lib/mgmt_fe_client.h @@ -409,6 +409,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. + * + * value + * the value of data node. + * + * 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 *value); + /* * Destroy library and cleanup everything. */ diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c index 8d90aff0fa08..48b9c7798f3b 100644 --- a/lib/mgmt_msg_native.c +++ b/lib/mgmt_msg_native.c @@ -15,6 +15,7 @@ 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"); 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 c7c972393dbd..24df35bf4ef7 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 @@ -144,6 +145,7 @@ 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); /* * Native message codes @@ -153,6 +155,7 @@ 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 /* * Datastores @@ -317,6 +320,50 @@ _Static_assert(sizeof(struct mgmt_msg_notify_data) == offsetof(struct mgmt_msg_notify_data, result), "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. + * @xpath_len: the length of the xpath. + * @data: the xpath and value to edit, both NULL terminated. + * for CREATE operation, xpath should point 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; + + uint32_t xpath_len; + + alignas(8) char data[]; +}; +_Static_assert(sizeof(struct mgmt_msg_edit) == + offsetof(struct mgmt_msg_edit, data), + "Size mismatch"); + +#define mgmt_msg_edit_get_xpath(msg) ((msg)->data) +#define mgmt_msg_edit_get_value(msg) ((msg)->data + (msg)->xpath_len) + /* * Validate that the message ends in a NUL terminating byte */ diff --git a/lib/northbound.c b/lib/northbound.c index a0b1bd18c54f..80c253dc8e81 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -797,6 +797,217 @@ 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 *value, + struct lyd_node **errors) +{ + char parent_xpath[XPATH_MAXLEN]; + 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; + struct ly_in *in; + bool root; + int ret; + LY_ERR err = LY_SUCCESS; + + ly_in_new_memory(value, &in); + + strlcpy(parent_xpath, xpath, sizeof(parent_xpath)); + root = parent_xpath[0] == 0 || + (parent_xpath[0] == '/' && parent_xpath[1] == 0); + + /* + * NB_OP_CREATE_EXCL already expects parent xpath. + * For others - pop one level if it's not root. + */ + if (operation != NB_OP_CREATE_EXCL && !root) { + ret = yang_xpath_pop_node(parent_xpath); + if (ret) { + yang_create_error(errors, "application", "invalid-value", + NULL, NULL, "Invalid xpath"); + goto done; + } + root = parent_xpath[0] == 0 || + (parent_xpath[0] == '/' && parent_xpath[1] == 0); + } + + /* + * Create parent if it's not root. We're creating a new tree here to be + * merged later with candidate. + */ + if (!root) { + err = lyd_new_path2(NULL, ly_native_ctx, parent_xpath, NULL, 0, + 0, 0, &tree, &parent); + if (err) { + yang_create_error(errors, "application", + "operation-failed", NULL, NULL, + "Cannot create new data tree"); + 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_create_error(errors, "application", "invalid-value", NULL, + NULL, "Invalid data"); + goto done; + } + + /* set the tree if we created a top-level node */ + if (root) + tree = dnode; + + /* check if the node already exists in candidate */ + if ((operation == NB_OP_CREATE_EXCL || operation == NB_OP_REPLACE) && + dnode != NULL) { + char path[XPATH_MAXLEN]; + + lyd_path(dnode, LYD_PATH_STD, path, sizeof(path)); + + if (root) + existing = candidate->dnode; + else + existing = yang_dnode_get(candidate->dnode, path); + + if (existing) { + if (operation == NB_OP_CREATE_EXCL) { + yang_create_error(errors, "application", + "data-exists", NULL, NULL, + "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_create_error(errors, "application", "operation-failed", + NULL, NULL, "Cannot update candidate"); + 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 (err) + ret = NB_ERR; + + if (tree) + lyd_free_all(tree); + 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, + struct lyd_node **errors) +{ + 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) { + yang_create_error(errors, "application", "data-missing", + NULL, NULL, "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 *value, + struct lyd_node **errors) +{ + 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, value, errors); + break; + case NB_OP_DESTROY: + case NB_OP_DELETE: + ret = nb_candidate_edit_tree_del(candidate, operation, xpath, + errors); + 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 9279122deb8a..9b4792ea521e 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -975,6 +975,37 @@ 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. + * + * value + * New value of the configuration node. + * + * errors + * Pointer to store the error tree in case of error. + * + * 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 *value, struct lyd_node **errors); + /* * 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 c2890ce9fcfb..6b3505655139 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -4125,6 +4125,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 *value) +{ + 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, value)) { + 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 06973da9161f..27ea0cfa27a8 100644 --- a/lib/vty.h +++ b/lib/vty.h @@ -423,6 +423,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 *value); 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 f2491c18da4c..30a9e3471445 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" @@ -1082,6 +1083,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; +} + LY_ERR yang_create_error(struct lyd_node **errors, const char *type, const char *tag, const char *app_tag, const char *path, const char *message) @@ -1295,6 +1322,40 @@ 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; + } + } + + return LY_SUCCESS; +} + /* * Safe to remove after libyang v2.1.128 is required */ diff --git a/lib/yang.h b/lib/yang.h index 17744a68031d..498de681389c 100644 --- a/lib/yang.h +++ b/lib/yang.h @@ -759,6 +759,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_create_error() - create a yang-error data node * @errors: pointer to the errors container to add the error to @@ -803,6 +811,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 a36d17aaefe8..3b6d764312a1 100644 --- a/mgmtd/mgmt_fe_adapter.c +++ b/mgmtd/mgmt_fe_adapter.c @@ -919,11 +919,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, false, + false) != 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, @@ -1245,6 +1247,122 @@ 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; + struct lyd_node *errors = NULL; + bool lock, commit; + int ret; + + if (msg->datastore != MGMT_MSG_DATASTORE_CANDIDATE) { + yang_create_error(&errors, "protocol", "invalid-value", NULL, + NULL, "Unsupported datastore requested"); + fe_adapter_send_errors(session, msg->req_id, false, + msg->request_type, -EINVAL, errors); + 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_ds_lock(ds_ctx, msg->refer_id)) { + yang_create_error(&errors, "protocol", "lock-denied", + NULL, NULL, + "Candidate DS is locked by another session"); + fe_adapter_send_errors(session, msg->req_id, false, + msg->request_type, -EBUSY, + errors); + return; + } + + if (commit) { + if (mgmt_ds_lock(rds_ctx, msg->refer_id)) { + mgmt_ds_unlock(ds_ctx); + yang_create_error(&errors, "protocol", + "lock-denied", NULL, NULL, + "Running DS is locked by another session"); + fe_adapter_send_errors(session, msg->req_id, + false, msg->request_type, + -EBUSY, errors); + return; + } + } + } else { + if (!session->ds_locked[ds_id]) { + yang_create_error(&errors, "protocol", + "operation-failed", NULL, NULL, + "Candidate DS is not locked"); + fe_adapter_send_errors(session, msg->req_id, false, + msg->request_type, -EBUSY, + errors); + return; + } + + if (commit) { + if (!session->ds_locked[rds_id]) { + yang_create_error(&errors, "protocol", + "operation-failed", NULL, NULL, + "Running DS is not locked"); + fe_adapter_send_errors(session, msg->req_id, + false, msg->request_type, + -EBUSY, errors); + return; + } + } + } + + session->txn_id = mgmt_create_txn(session->session_id, + MGMTD_TXN_TYPE_CONFIG); + if (session->txn_id == MGMTD_SESSION_ID_NONE) { + if (lock) { + mgmt_ds_unlock(ds_ctx); + if (commit) + mgmt_ds_unlock(rds_ctx); + } + yang_create_error(&errors, "protocol", "resource-denied", NULL, + NULL, + "Failed to create a configuration transaction"); + fe_adapter_send_errors(session, msg->req_id, false, + msg->request_type, -EBUSY, errors); + return; + } + + __dbg("Created new config txn-id: %" PRIu64 " for session-id: %" PRIu64, + session->txn_id, session->session_id); + + ret = mgmt_txn_send_edit(session->txn_id, msg->req_id, ds_id, ds_ctx, + rds_id, rds_ctx, lock, commit, + msg->request_type, msg->flags, msg->operation, + mgmt_msg_edit_get_xpath(msg), + mgmt_msg_edit_get_value(msg)); + if (ret) { + /* destroy the just created txn */ + mgmt_destroy_txn(&session->txn_id); + if (lock) { + mgmt_ds_unlock(ds_ctx); + if (commit) + mgmt_ds_unlock(rds_ctx); + } + yang_create_error(&errors, "protocol", "resource-denied", NULL, + NULL, + "Failed to create a configuration transaction"); + fe_adapter_send_errors(session, msg->req_id, false, + msg->request_type, -EBUSY, errors); + } +} + /** * Handle a native encoded message from the FE client. */ @@ -1266,6 +1384,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", diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c index 5be849b63c41..3de315179ffc 100644 --- a/mgmtd/mgmt_main.c +++ b/mgmtd/mgmt_main.c @@ -154,6 +154,12 @@ const struct frr_yang_module_info ietf_netconf_with_defaults_info = { .nodes = { { .xpath = NULL } }, }; +const struct frr_yang_module_info ietf_restconf = { + .name = "ietf-restconf", + .ignore_cfg_cbs = true, + .nodes = { { .xpath = NULL } }, +}; + /* * These are stub info structs that are used to load the modules used by backend * clients into mgmtd. The modules are used by libyang in order to support @@ -179,6 +185,7 @@ static const struct frr_yang_module_info *const mgmt_yang_modules[] = { /* mgmtd-only modules */ &ietf_netconf_with_defaults_info, + &ietf_restconf, /* * YANG module info used by backend clients get added here. diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c index df2a1d852d09..8a530270e2a2 100644 --- a/mgmtd/mgmt_txn.c +++ b/mgmtd/mgmt_txn.c @@ -105,6 +105,8 @@ struct mgmt_commit_cfg_req { uint8_t abort : 1; uint8_t implicit : 1; uint8_t rollback : 1; + uint8_t unlock : 1; + uint8_t native : 1; /* Track commit phases */ enum mgmt_commit_phase phase; @@ -609,7 +611,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, + false, false); if (mm->perf_stats_en) gettimeofday(&cmt_stats->last_start, NULL); @@ -650,6 +653,7 @@ static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn, const char *error_if_any) { bool success, create_cmt_info_rec; + int ret; if (!txn->commit_cfg_req) return -1; @@ -660,7 +664,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.native && + !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 @@ -676,7 +681,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.native && + 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 @@ -690,6 +696,24 @@ 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.native) { + if (success) + ret = mgmt_fe_adapter_txn_success(txn->txn_id, + txn->commit_cfg_req + ->req_id, + false); + else + ret = mgmt_fe_adapter_txn_error(txn->txn_id, + txn->commit_cfg_req + ->req_id, + false, -1, error_if_any); + + if (ret) + __log_err("Failed to send NATIVE 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? */ @@ -750,6 +774,11 @@ static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn, mgmt_history_rollback_complete(success); } + if (txn->commit_cfg_req->req.commit_cfg.unlock) { + mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.src_ds_ctx); + mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx); + } + txn->commit_cfg_req->req.commit_cfg.cmt_stats = NULL; mgmt_txn_req_free(&txn->commit_cfg_req); @@ -2039,7 +2068,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, bool unlock, bool native) { struct mgmt_txn_ctx *txn; struct mgmt_txn_req *txn_req; @@ -2063,6 +2092,8 @@ 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.unlock = unlock; + txn_req->req.commit_cfg.native = native; txn_req->req.commit_cfg.cmt_stats = mgmt_fe_get_session_commit_stats(txn->session_id); @@ -2433,6 +2464,55 @@ 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 *value) +{ + struct mgmt_txn_ctx *txn; + struct nb_config *nb_config; + struct lyd_node *errors = NULL; + int ret; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn) + return -1; + + nb_config = mgmt_ds_get_nb_config(ds_ctx); + assert(nb_config); + + ret = nb_candidate_edit_tree(nb_config, operation, request_type, xpath, + value, &errors); + if (ret) + goto reply; + + if (commit) { + ret = mgmt_txn_send_commit_config_req(txn_id, req_id, ds_id, + ds_ctx, commit_ds_id, + commit_ds_ctx, false, + false, true, unlock, true); + if (ret) { + yang_create_error(&errors, "protocol", + "resource-denied", NULL, NULL, + "Failed to create a commit request"); + goto reply; + } + return 0; + } +reply: + if (unlock) { + mgmt_ds_unlock(ds_ctx); + if (commit) + mgmt_ds_unlock(commit_ds_ctx); + } + + mgmt_fe_adapter_txn_errors(txn->txn_id, req_id, false, request_type, + errors); + return 0; +} + /* * Error reply from the backend client. */ diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h index b7198326da7e..5959bfdd6916 100644 --- a/mgmtd/mgmt_txn.h +++ b/mgmtd/mgmt_txn.h @@ -171,16 +171,20 @@ 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. * + * unlock + * if true, unlock candidate and running datastores after the commit. + * + * native + * if true, send a native message reply. + * * 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, bool unlock, bool native); /* * Send get-{cfg,data} request to be processed later in transaction. @@ -219,6 +223,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. + * value: The value of data node. + */ +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 *value); + /* * Notifiy backend adapter on connection. */ diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c index 12ea62eceff5..c4d489852983 100644 --- a/mgmtd/mgmt_vty.c +++ b/mgmtd/mgmt_vty.c @@ -237,6 +237,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] [VALUE]", + 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" + "Value\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 (!value && (operation == NB_OP_CREATE_EXCL || + operation == NB_OP_MODIFY || operation == NB_OP_REPLACE)) { + vty_out(vty, "Value 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, value); + 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 @@ -641,6 +699,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); diff --git a/mgmtd/subdir.am b/mgmtd/subdir.am index 0af64dc0be52..8c8be9ebc5ce 100644 --- a/mgmtd/subdir.am +++ b/mgmtd/subdir.am @@ -64,6 +64,7 @@ nodist_mgmtd_mgmtd_SOURCES = \ yang/ietf/ietf-netconf-acm.yang.c \ yang/ietf/ietf-netconf.yang.c \ yang/ietf/ietf-netconf-with-defaults.yang.c \ + yang/ietf/ietf-restconf.yang.c \ # nothing mgmtd_mgmtd_CFLAGS = $(AM_CFLAGS) -I ./ mgmtd_mgmtd_LDADD = mgmtd/libmgmtd.a lib/libfrr.la $(LIBCAP) $(LIBM) $(LIBYANG_LIBS) $(UST_LIBS)