diff options
| author | Igor Ryzhov <iryzhov@nfware.com> | 2024-03-03 21:40:16 +0200 | 
|---|---|---|
| committer | Igor Ryzhov <iryzhov@nfware.com> | 2024-03-26 17:00:15 +0200 | 
| commit | 1196d947d3f2241897ec5037d7db0519ad27a6ea (patch) | |
| tree | 43b23854208035d43190ca33c451edfe3f2ff8cd | |
| parent | 73e0b7a198c8a2f483af2140e116e53df1e5cb5d (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.c | 54 | ||||
| -rw-r--r-- | lib/mgmt_fe_client.h | 45 | ||||
| -rw-r--r-- | lib/mgmt_msg_native.c | 2 | ||||
| -rw-r--r-- | lib/mgmt_msg_native.h | 63 | ||||
| -rw-r--r-- | lib/northbound.c | 217 | ||||
| -rw-r--r-- | lib/northbound.h | 38 | ||||
| -rw-r--r-- | lib/northbound_oper.c | 34 | ||||
| -rw-r--r-- | lib/vty.c | 40 | ||||
| -rw-r--r-- | lib/vty.h | 4 | ||||
| -rw-r--r-- | lib/yang.c | 63 | ||||
| -rw-r--r-- | lib/yang.h | 13 | ||||
| -rw-r--r-- | mgmtd/mgmt_fe_adapter.c | 194 | ||||
| -rw-r--r-- | mgmtd/mgmt_fe_adapter.h | 20 | ||||
| -rw-r--r-- | mgmtd/mgmt_txn.c | 86 | ||||
| -rw-r--r-- | mgmtd/mgmt_txn.h | 41 | ||||
| -rw-r--r-- | mgmtd/mgmt_vty.c | 59 | 
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;  	} @@ -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)  { @@ -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);  | 
