summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Ryzhov <iryzhov@nfware.com>2024-03-03 21:40:16 +0200
committerIgor Ryzhov <iryzhov@nfware.com>2024-03-26 17:00:15 +0200
commit1196d947d3f2241897ec5037d7db0519ad27a6ea (patch)
tree43b23854208035d43190ca33c451edfe3f2ff8cd
parent73e0b7a198c8a2f483af2140e116e53df1e5cb5d (diff)
mgmtd: add support for native 'edit' operation
This operation basically implements support for RESTCONF operations. It receives an xpath and a data tree in JSON/XML format, instead of a list of (xpath, value) tuples as required by the current protobuf interface. Signed-off-by: Igor Ryzhov <iryzhov@nfware.com>
-rw-r--r--lib/mgmt_fe_client.c54
-rw-r--r--lib/mgmt_fe_client.h45
-rw-r--r--lib/mgmt_msg_native.c2
-rw-r--r--lib/mgmt_msg_native.h63
-rw-r--r--lib/northbound.c217
-rw-r--r--lib/northbound.h38
-rw-r--r--lib/northbound_oper.c34
-rw-r--r--lib/vty.c40
-rw-r--r--lib/vty.h4
-rw-r--r--lib/yang.c63
-rw-r--r--lib/yang.h13
-rw-r--r--mgmtd/mgmt_fe_adapter.c194
-rw-r--r--mgmtd/mgmt_fe_adapter.h20
-rw-r--r--mgmtd/mgmt_txn.c86
-rw-r--r--mgmtd/mgmt_txn.h41
-rw-r--r--mgmtd/mgmt_vty.c59
16 files changed, 922 insertions, 51 deletions
diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c
index a107582bea..3345505213 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 eee4594e17..9d569348ae 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,
@@ -410,6 +416,45 @@ extern int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client,
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.
*/
extern void mgmt_fe_client_destroy(struct mgmt_fe_client *client);
diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c
index 98b7da45ce..09ea43ece0 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 53bb81be28..b7c29862aa 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 <stdalign.h>
@@ -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 487f225913..37b6552118 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 5be111cf0a..15a4999943 100644
--- a/lib/northbound.h
+++ b/lib/northbound.h
@@ -1005,6 +1005,44 @@ extern int nb_candidate_edit(struct nb_config *candidate,
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.
*
* dnode
diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c
index 2394b5e865..7e7190f5a4 100644
--- a/lib/northbound_oper.c
+++ b/lib/northbound_oper.c
@@ -342,38 +342,6 @@ static void nb_op_resume_data_tree(struct nb_op_yield_state *ys)
/* ======================= */
/**
- * __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.
* @dnode: resulting tree (trunk)
@@ -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 912c893556..d69b5eebd2 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 a59ac7a652..0e0c92c336 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 d71cb2f498..013a762842 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 65f6a73e0b..a831c9bfdf 100644
--- a/lib/yang.h
+++ b/lib/yang.h
@@ -771,6 +771,14 @@ extern int yang_get_key_preds(char *s, const struct lysc_node *snode,
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.
* @xpath: the path or XPath to resolve.
@@ -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 62d1a0109a..ab0da64d8f 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 @@ done:
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 @@ done:
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 e768a3cca0..8d61ffe910 100644
--- a/mgmtd/mgmt_fe_adapter.h
+++ b/mgmtd/mgmt_fe_adapter.h
@@ -163,6 +163,26 @@ mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id,
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.
*
* This also cleans up and frees the transaction.
diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c
index 3a052c0e35..901163c6e6 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;
@@ -109,6 +114,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
* candidate and running DSs. This is typically used
@@ -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 @@ state:
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 b7198326da..aeb74469f1 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 2cd24719bc..61d0760e05 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);