mirror of
https://github.com/FRRouting/frr.git
synced 2025-04-30 13:37:17 +02:00
Merge pull request #15468 from idryzhov/mgmt-native-edit
mgmtd: add support for native 'edit' operation
This commit is contained in:
commit
7f7bcb1ffa
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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; \
|
||||
|
|
217
lib/northbound.c
217
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) {
|
||||
|
|
|
@ -998,6 +998,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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
40
lib/vty.c
40
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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
63
lib/yang.c
63
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
|
||||
*/
|
||||
|
|
13
lib/yang.h
13
lib/yang.h
|
@ -767,6 +767,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.
|
||||
|
@ -797,6 +805,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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -36,6 +36,7 @@ import time
|
|||
import os
|
||||
import pytest
|
||||
import platform
|
||||
import json
|
||||
|
||||
# Save the Current Working Directory to find configuration files.
|
||||
CWD = os.path.dirname(os.path.realpath(__file__))
|
||||
|
@ -45,7 +46,7 @@ sys.path.append(os.path.join(CWD, "../lib/"))
|
|||
# pylint: disable=C0413
|
||||
# Import topogen and topotest helpers
|
||||
from lib.topogen import Topogen, get_topogen
|
||||
from lib.topotest import version_cmp
|
||||
from lib.topotest import version_cmp, router_json_cmp
|
||||
|
||||
# Import topoJson from lib, to create topology and initial configuration
|
||||
from lib.common_config import (
|
||||
|
@ -401,6 +402,242 @@ def test_mgmt_delete_config(request):
|
|||
write_test_footer(tc_name)
|
||||
|
||||
|
||||
def test_mgmt_edit_config(request):
|
||||
"""
|
||||
Verify mgmt edit config.
|
||||
"""
|
||||
tc_name = request.node.name
|
||||
write_test_header(tc_name)
|
||||
tgen = get_topogen()
|
||||
# Don't run this test if we have any failure.
|
||||
if tgen.routers_have_failure():
|
||||
pytest.skip(tgen.errors)
|
||||
|
||||
reset_config_on_routers(tgen)
|
||||
|
||||
r1 = tgen.gears["r1"]
|
||||
|
||||
# check "create" operation
|
||||
data = {"frr-interface:interface": [{"name": "eth0", "description": "eth0-desc"}]}
|
||||
r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit create /frr-interface:lib lock commit {json.dumps(data, separators=(',', ':'))}"
|
||||
)
|
||||
data_out = data
|
||||
assert (
|
||||
router_json_cmp(
|
||||
r1,
|
||||
"show mgmt get-data /frr-interface:lib/interface[name='eth0'] only-config exact",
|
||||
data_out,
|
||||
exact=True,
|
||||
)
|
||||
== None
|
||||
)
|
||||
|
||||
# check error on "create" for an existing object
|
||||
data = {"frr-interface:interface": [{"name": "eth0", "description": "eth0-desc"}]}
|
||||
ret = r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit create /frr-interface:lib lock commit {json.dumps(data, separators=(',', ':'))}"
|
||||
)
|
||||
assert "Data already exists" in ret
|
||||
|
||||
# check adding a leaf to an existing object using "merge"
|
||||
data = {
|
||||
"frr-interface:interface": [
|
||||
{"name": "eth0", "frr-zebra:zebra": {"bandwidth": 100}}
|
||||
]
|
||||
}
|
||||
r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit merge /frr-interface:lib/interface[name='eth0'] lock commit {json.dumps(data, separators=(',', ':'))}"
|
||||
)
|
||||
data_out = {
|
||||
"frr-interface:interface": [
|
||||
{
|
||||
"name": "eth0",
|
||||
"description": "eth0-desc",
|
||||
"frr-zebra:zebra": {"bandwidth": 100},
|
||||
}
|
||||
]
|
||||
}
|
||||
assert (
|
||||
router_json_cmp(
|
||||
r1,
|
||||
"show mgmt get-data /frr-interface:lib/interface[name='eth0'] only-config exact",
|
||||
data_out,
|
||||
exact=True,
|
||||
)
|
||||
== None
|
||||
)
|
||||
|
||||
# check replacing an existing object using "replace"
|
||||
data = {
|
||||
"frr-interface:interface": [{"name": "eth0", "description": "eth0-desc-new"}]
|
||||
}
|
||||
r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit replace /frr-interface:lib/interface[name='eth0'] lock commit {json.dumps(data, separators=(',', ':'))}"
|
||||
)
|
||||
data_out = data
|
||||
assert (
|
||||
router_json_cmp(
|
||||
r1,
|
||||
"show mgmt get-data /frr-interface:lib/interface[name='eth0'] only-config exact",
|
||||
data_out,
|
||||
exact=True,
|
||||
)
|
||||
== None
|
||||
)
|
||||
|
||||
# check error on "replace" when keys in xpath and data are different
|
||||
data = {
|
||||
"frr-interface:interface": [{"name": "eth1", "description": "eth0-desc-new"}]
|
||||
}
|
||||
ret = r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit replace /frr-interface:lib/interface[name='eth0'] lock commit {json.dumps(data, separators=(',', ':'))}"
|
||||
)
|
||||
assert "List keys in xpath and data tree are different" in ret
|
||||
|
||||
# check deleting an existing object using "delete"
|
||||
r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit delete /frr-interface:lib/interface[name='eth0'] lock commit"
|
||||
)
|
||||
assert (
|
||||
router_json_cmp(
|
||||
r1,
|
||||
"show mgmt get-data /frr-interface:lib/interface[name='eth0'] only-config exact",
|
||||
{},
|
||||
exact=True,
|
||||
)
|
||||
== None
|
||||
)
|
||||
|
||||
# check error on "delete" for a non-existing object
|
||||
ret = r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit delete /frr-interface:lib/interface[name='eth0'] lock commit"
|
||||
)
|
||||
assert "Data missing" in ret
|
||||
|
||||
# check no error on "remove" for a non-existing object
|
||||
ret = r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit remove /frr-interface:lib/interface[name='eth0'] lock commit"
|
||||
)
|
||||
assert "Data missing" not in ret
|
||||
|
||||
# check "remove" for an existing object
|
||||
data = {"frr-interface:interface": [{"name": "eth0", "description": "eth0-desc"}]}
|
||||
r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit create /frr-interface:lib lock commit {json.dumps(data, separators=(',', ':'))}"
|
||||
)
|
||||
r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit remove /frr-interface:lib/interface[name='eth0'] lock commit"
|
||||
)
|
||||
assert (
|
||||
router_json_cmp(
|
||||
r1,
|
||||
"show mgmt get-data /frr-interface:lib/interface[name='eth0'] only-config exact",
|
||||
{},
|
||||
exact=True,
|
||||
)
|
||||
== None
|
||||
)
|
||||
|
||||
# check "create" of a top-level node
|
||||
data = {"frr-vrf:lib": {"vrf": [{"name": "vrf1"}]}}
|
||||
r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit create / lock commit {json.dumps(data, separators=(',', ':'))}"
|
||||
)
|
||||
data_out = data
|
||||
assert (
|
||||
router_json_cmp(
|
||||
r1,
|
||||
"show mgmt get-data /frr-vrf:lib only-config exact",
|
||||
data_out,
|
||||
exact=True,
|
||||
)
|
||||
== None
|
||||
)
|
||||
|
||||
# check "replace" of a top-level node
|
||||
data = {"frr-vrf:lib": {"vrf": [{"name": "vrf2"}]}}
|
||||
r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit replace /frr-vrf:lib lock commit {json.dumps(data, separators=(',', ':'))}"
|
||||
)
|
||||
data_out = data
|
||||
assert (
|
||||
router_json_cmp(
|
||||
r1,
|
||||
"show mgmt get-data /frr-vrf:lib only-config exact",
|
||||
data_out,
|
||||
exact=True,
|
||||
)
|
||||
== None
|
||||
)
|
||||
|
||||
# check "delete" of a top-level node
|
||||
r1.vtysh_cmd(f"conf\nmgmt edit delete /frr-vrf:lib lock commit")
|
||||
assert (
|
||||
router_json_cmp(
|
||||
r1, "show mgmt get-data /frr-vrf:lib only-config exact", {}, exact=True
|
||||
)
|
||||
== None
|
||||
)
|
||||
|
||||
# check replace of the whole config
|
||||
# this test won't work at the moment, because we don't allow to delete
|
||||
# interfaces from the config if they are present in the system
|
||||
# another problem is that we don't allow "/" as an xpath in get-data command
|
||||
# therefore, commenting it out for now
|
||||
# data = {
|
||||
# "frr-interface:lib": {
|
||||
# "interface": [{"name": "eth1", "description": "eth1-desc"}]
|
||||
# },
|
||||
# "frr-vrf:lib": {"vrf": [{"name": "vrf3"}]},
|
||||
# }
|
||||
# r1.vtysh_cmd(
|
||||
# f"conf\nmgmt edit replace / lock commit {json.dumps(data, separators=(',', ':'))}"
|
||||
# )
|
||||
# data_out = data
|
||||
# assert (
|
||||
# router_json_cmp(
|
||||
# r1,
|
||||
# "show mgmt get-data / only-config exact",
|
||||
# data_out,
|
||||
# exact=True,
|
||||
# )
|
||||
# == None
|
||||
# )
|
||||
|
||||
# check "merge" of the whole config
|
||||
data = {
|
||||
"frr-interface:lib": {
|
||||
"interface": [{"name": "eth2", "description": "eth2-desc"}]
|
||||
},
|
||||
"frr-vrf:lib": {"vrf": [{"name": "vrf4"}]},
|
||||
}
|
||||
r1.vtysh_cmd(
|
||||
f"conf\nmgmt edit merge / lock commit {json.dumps(data, separators=(',', ':'))}"
|
||||
)
|
||||
data_out = data
|
||||
assert (
|
||||
router_json_cmp(
|
||||
r1,
|
||||
"show mgmt get-data /frr-interface:lib only-config exact",
|
||||
{
|
||||
"frr-interface:lib": {
|
||||
"interface": [{"name": "eth2", "description": "eth2-desc"}]
|
||||
}
|
||||
},
|
||||
)
|
||||
== None
|
||||
)
|
||||
assert (
|
||||
router_json_cmp(
|
||||
r1,
|
||||
"show mgmt get-data /frr-vrf:lib only-config exact",
|
||||
{"frr-vrf:lib": {"vrf": [{"name": "vrf4"}]}},
|
||||
)
|
||||
== None
|
||||
)
|
||||
|
||||
|
||||
def test_mgmt_chaos_stop_start_frr(request):
|
||||
"""
|
||||
Kill mgmtd - verify that watch frr restarts.
|
||||
|
|
Loading…
Reference in a new issue