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>
This commit is contained in:
Igor Ryzhov 2024-03-03 21:40:16 +02:00
parent 73e0b7a198
commit 1196d947d3
16 changed files with 922 additions and 51 deletions

View file

@ -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;

View file

@ -114,6 +114,12 @@ struct mgmt_fe_client_cbs {
LYD_FORMAT result_type, void *result, size_t len,
int partial_error);
/* Called when edit result is returned */
int (*edit_notify)(struct mgmt_fe_client *client, uintptr_t user_data,
uint64_t client_id, uint64_t session_id,
uintptr_t session_ctx, uint64_t req_id,
const char *xpath);
/* Called with asynchronous notifications from backends */
int (*async_notification)(struct mgmt_fe_client *client,
uintptr_t user_data, uint64_t client_id,
@ -409,6 +415,45 @@ extern int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client,
uint8_t flags, uint8_t defaults,
const char *xpath);
/*
* Send EDIT to MGMTD daemon.
*
* client
* Client object.
*
* session_id
* Client session ID.
*
* req_id
* Client request ID.
*
* datastore
* Datastore for editing.
*
* request_type
* The LYD_FORMAT of the request.
*
* flags
* Flags to control the behavior of the request.
*
* operation
* NB_OP_* operation to perform.
*
* xpath
* the xpath to edit.
*
* data
* the data tree.
*
* Returns:
* 0 on success, otherwise msg_conn_send_msg() return values.
*/
extern int mgmt_fe_send_edit_req(struct mgmt_fe_client *client,
uint64_t session_id, uint64_t req_id,
uint8_t datastore, LYD_FORMAT request_type,
uint8_t flags, uint8_t operation,
const char *xpath, const char *data);
/*
* Destroy library and cleanup everything.
*/

View file

@ -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,

View file

@ -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; \

View file

@ -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) {

View file

@ -1004,6 +1004,44 @@ extern int nb_candidate_edit(struct nb_config *candidate,
const struct yang_data *previous,
const struct yang_data *data);
/*
* Edit a candidate configuration. Value is given as JSON/XML.
*
* candidate
* Candidate configuration to edit.
*
* operation
* Operation to apply.
*
* format
* LYD_FORMAT of the value.
*
* xpath
* XPath of the configuration node being edited.
* For create, it must be the parent.
*
* data
* New data tree for the node.
*
* xpath_created
* XPath of the created node if operation is "create".
*
* errmsg
* Buffer to store human-readable error message in case of error.
*
* errmsg_len
* Size of errmsg.
*
* Returns:
* - NB_OK on success.
* - NB_ERR for other errors.
*/
extern int nb_candidate_edit_tree(struct nb_config *candidate,
enum nb_operation operation,
LYD_FORMAT format, const char *xpath,
const char *data, char *xpath_created,
char *errmsg, size_t errmsg_len);
/*
* Create diff for configuration.
*

View file

@ -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;
}

View file

@ -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)
{

View file

@ -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);

View file

@ -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
*/

View file

@ -770,6 +770,14 @@ extern int yang_get_key_preds(char *s, const struct lysc_node *snode,
/* Get YANG keys from an existing dnode */
extern int yang_get_node_keys(struct lyd_node *node, struct yang_list_keys *keys);
/**
* yang_xpath_pop_node() - remove the last node from xpath string
* @xpath: an xpath string
*
* Return: NB_OK or NB_ERR_NOT_FOUND if nothing left to pop.
*/
extern int yang_xpath_pop_node(char *xpath);
/**
* yang_resolve_snodes() - Resolve an XPath to matching schema nodes.
* @ly_ctx: libyang context to operate on.
@ -800,6 +808,11 @@ extern LY_ERR yang_lyd_new_list(struct lyd_node_inner *parent,
const struct yang_list_keys *keys,
struct lyd_node **nodes);
extern LY_ERR yang_lyd_trim_xpath(struct lyd_node **rootp, const char *xpath);
extern LY_ERR yang_lyd_parse_data(const struct ly_ctx *ctx,
struct lyd_node *parent, struct ly_in *in,
LYD_FORMAT format, uint32_t parse_options,
uint32_t validate_options,
struct lyd_node **tree);
#ifdef __cplusplus
}

View file

@ -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.
*/

View file

@ -162,6 +162,26 @@ mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id,
uint32_t wd_options, const struct lyd_node *tree,
int partial_error, bool short_circuit_ok);
/**
* Send edit reply back to client. If error is not 0, a native error is sent.
*
* This also cleans up and frees the transaction.
*
* Args:
* session_id: the session.
* txn_id: the txn_id this data pertains to
* req_id: the req id for the edit message
* unlock: implicit-lock flag was set in the request
* commit: implicit-commit flag was set in the request
* xpath: the xpath of the data node that was created
* error: the error code, zero for successful request
* errstr: the error string, if error is non-zero
*/
extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id,
uint64_t req_id, bool unlock,
bool commit, const char *xpath,
int16_t error, const char *errstr);
/**
* Send an error back to the FE client using native messaging.
*

View file

@ -91,6 +91,11 @@ DECLARE_LIST(mgmt_txn_batches, struct mgmt_txn_be_cfg_batch, list_linkage);
#define FOREACH_TXN_CFG_BATCH_IN_LIST(list, batch) \
frr_each_safe (mgmt_txn_batches, list, batch)
struct mgmt_edit_req {
char xpath_created[XPATH_MAXLEN];
bool unlock;
};
struct mgmt_commit_cfg_req {
Mgmtd__DatastoreId src_ds_id;
struct mgmt_ds_ctx *src_ds_ctx;
@ -108,6 +113,12 @@ struct mgmt_commit_cfg_req {
enum mgmt_commit_phase be_phase[MGMTD_BE_CLIENT_ID_MAX];
/*
* Additional information when the commit is triggered by native edit
* request.
*/
struct mgmt_edit_req *edit;
/*
* Set of config changes to commit. This is used only
* when changes are NOT to be determined by comparing
@ -444,6 +455,8 @@ static void mgmt_txn_req_free(struct mgmt_txn_req **txn_req)
cleanup = (ccreq->phase >= MGMTD_COMMIT_PHASE_TXN_CREATE &&
ccreq->phase < MGMTD_COMMIT_PHASE_TXN_DELETE);
XFREE(MTYPE_MGMTD_TXN_REQ, ccreq->edit);
FOREACH_MGMTD_BE_CLIENT_ID (id) {
/*
* Send TXN_DELETE to cleanup state for this
@ -604,7 +617,8 @@ static void mgmt_txn_process_set_cfg(struct event *thread)
->dst_ds_id,
txn_req->req.set_cfg
->dst_ds_ctx,
false, false, true);
false, false, true,
NULL);
if (mm->perf_stats_en)
gettimeofday(&cmt_stats->last_start, NULL);
@ -655,7 +669,8 @@ static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn,
* b/c right now that is special cased.. that special casing should be
* removed; however...
*/
if (!txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id &&
if (!txn->commit_cfg_req->req.commit_cfg.edit &&
!txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id &&
!txn->commit_cfg_req->req.commit_cfg.rollback &&
mgmt_fe_send_commit_cfg_reply(txn->session_id, txn->txn_id,
txn->commit_cfg_req->req.commit_cfg
@ -671,7 +686,8 @@ static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn,
txn->txn_id, txn->session_id);
}
if (txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id &&
if (!txn->commit_cfg_req->req.commit_cfg.edit &&
txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id &&
!txn->commit_cfg_req->req.commit_cfg.rollback &&
mgmt_fe_send_set_cfg_reply(txn->session_id, txn->txn_id,
txn->commit_cfg_req->req.commit_cfg
@ -685,6 +701,21 @@ static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn,
txn->txn_id, txn->session_id);
}
if (txn->commit_cfg_req->req.commit_cfg.edit &&
mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id,
txn->commit_cfg_req->req_id,
txn->commit_cfg_req->req.commit_cfg
.edit->unlock,
true,
txn->commit_cfg_req->req.commit_cfg
.edit->xpath_created,
success ? 0 : -1,
error_if_any) != 0) {
__log_err("Failed to send EDIT-REPLY txn-id: %" PRIu64
" session-id: %" PRIu64,
txn->txn_id, txn->session_id);
}
if (success) {
/* Stop the commit-timeout timer */
/* XXX why only on success? */
@ -2011,7 +2042,7 @@ int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id,
Mgmtd__DatastoreId dst_ds_id,
struct mgmt_ds_ctx *dst_ds_ctx,
bool validate_only, bool abort,
bool implicit)
bool implicit, struct mgmt_edit_req *edit)
{
struct mgmt_txn_ctx *txn;
struct mgmt_txn_req *txn_req;
@ -2035,6 +2066,7 @@ int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id,
txn_req->req.commit_cfg.validate_only = validate_only;
txn_req->req.commit_cfg.abort = abort;
txn_req->req.commit_cfg.implicit = implicit;
txn_req->req.commit_cfg.edit = edit;
txn_req->req.commit_cfg.cmt_stats =
mgmt_fe_get_session_commit_stats(txn->session_id);
@ -2418,6 +2450,52 @@ 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.
*/

View file

@ -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.
*/

View file

@ -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);