internal netconf mods

This commit is contained in:
Olof hagsand 2017-03-25 18:24:52 +01:00
parent 2fcefda831
commit eec5896797
22 changed files with 665 additions and 1205 deletions

View file

@ -252,110 +252,19 @@ from_client_get_config(clicon_handle h,
return retval; return retval;
} }
#ifdef notused
/*! Internal message: Change entries as XML
* @param[in] h Clicon handle
* @param[in] s Socket where request arrived, and where replies are sent
* @param[in] pid Unix process id
* @param[in] msg Message
* @param[in] label Memory chunk
* @retval 0 OK
* @retval -1 Error. Send error message back to client.
*/
static int
from_client_xmlput(clicon_handle h,
int s,
int pid,
struct clicon_msg *msg,
const char *label)
{
int retval = -1;
char *db;
enum operation_type op;
cvec *cvv = NULL;
char *str = NULL;
char *api_path = NULL;
char *xml = NULL;
cxobj *xt = NULL;
int piddb;
cxobj *x;
if (clicon_msg_xmlput_decode(msg,
&db,
&op,
&api_path,
&xml,
label) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
clicon_err_reason);
goto done;
}
/* candidate is locked by other client */
if (strcmp(db, "candidate") == 0){
piddb = xmldb_islocked(h, db);
if (piddb && pid != piddb){
send_msg_err(s, OE_DB, 0,
"lock failed: locked by %d", piddb);
goto done;
}
}
if (clicon_xml_parse_string(&xml, &xt) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
clicon_err_reason);
goto done;
}
if (strlen(api_path)){
if (xt && xml_child_nr(xt)){
x = NULL;
while ((x = xml_child_each(xt, x, -1)) != NULL) {
if (xmldb_put(h, db, op, api_path, x) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
clicon_err_reason);
goto done;
}
}
}
else
if (xmldb_put(h, db, op, api_path, NULL) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
clicon_err_reason);
goto done;
}
}
else if (xmldb_put(h, db, op, NULL, xt) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
clicon_err_reason);
goto done;
}
if (send_msg_netconf_reply(s, "<rpc-reply><ok/></rpc-reply>") < 0)
goto done;
retval = 0;
done:
if (str)
free(str);
if (cvv)
cvec_free (cvv);
if (xt)
xml_free(xt);
return retval;
}
#endif
/*! Internal message: edit-config /*! Internal message: edit-config
* *
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] xe Netconf request xml tree * @param[in] xe Netconf request xml tree
* @param[in] mypid Process/session id of calling client
* @param[out] cbret Return xml value cligen buffer * @param[out] cbret Return xml value cligen buffer
* @see from_client_xmlput
* CLIXON addition: * CLIXON addition:
* <filter type="restconf" select="/data/profile=a" /> * <filter type="restconf" select="/data/profile=a" />
*/ */
static int static int
from_client_edit_config(clicon_handle h, from_client_edit_config(clicon_handle h,
cxobj *xn, cxobj *xn,
int mypid,
cbuf *cbret) cbuf *cbret)
{ {
int retval = -1; int retval = -1;
@ -367,11 +276,25 @@ from_client_edit_config(clicon_handle h,
cxobj *x; cxobj *x;
enum operation_type operation = OP_MERGE; enum operation_type operation = OP_MERGE;
char *api_path = NULL; char *api_path = NULL;
int piddb;
if ((target = netconf_db_find(xn, "target")) == NULL){ if ((target = netconf_db_find(xn, "target")) == NULL){
clicon_err(OE_XML, 0, "db not found"); clicon_err(OE_XML, 0, "db not found");
goto done; goto done;
} }
/* Check if target locked by other client */
piddb = xmldb_islocked(h, target);
if (piddb && mypid != piddb){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>lock-denied</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>Operation failed, lock is already held</error-message>"
"<error-info><session-id>%d</session-id></error-info>"
"</rpc-error></rpc-reply>",
piddb);
goto ok;
}
/* ie <filter type="restconf" select=<api-path> /> */ /* ie <filter type="restconf" select=<api-path> /> */
if ((xfilter = xpath_first(xn, "filter")) != NULL) if ((xfilter = xpath_first(xn, "filter")) != NULL)
api_path = xml_find_value(xfilter, "select"); api_path = xml_find_value(xfilter, "select");
@ -387,26 +310,6 @@ from_client_edit_config(clicon_handle h,
} }
if ((xc = xpath_first(xn, "config")) != NULL){ if ((xc = xpath_first(xn, "config")) != NULL){
/* XXX see from_client_xmlput() */ /* XXX see from_client_xmlput() */
if (api_path){
cbuf *cb;
cb=cbuf_new();
clicon_xml2cbuf(cb, xc, 0, 1);
fprintf(stderr, "%s: api_path:%s xml:%s\n",
__FUNCTION__, api_path, cbuf_get(cb));
cbuf_free(cb);
}
if (xml_body(xc)!= NULL){
if (xmldb_put_xkey(h, target, operation, api_path, xml_body(xc)) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>%s</error-message>"
"</rpc-error></rpc-reply>", clicon_err_reason);
goto ok;
}
}
else
if (xmldb_put(h, target, operation, api_path, xc) < 0){ if (xmldb_put(h, target, operation, api_path, xc) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>" cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>" "<error-tag>operation-failed</error-tag>"
@ -440,14 +343,14 @@ from_client_edit_config(clicon_handle h,
/*! Internal message: Lock database /*! Internal message: Lock database
* *
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] pid Unix process id
* @param[in] xe Netconf request xml tree * @param[in] xe Netconf request xml tree
* @param[in] pid Unix process id
* @param[out] cbret Return xml value cligen buffer * @param[out] cbret Return xml value cligen buffer
*/ */
static int static int
from_client_lock(clicon_handle h, from_client_lock(clicon_handle h,
int pid,
cxobj *xe, cxobj *xe,
int pid,
cbuf *cbret) cbuf *cbret)
{ {
int retval = -1; int retval = -1;
@ -494,14 +397,14 @@ from_client_lock(clicon_handle h,
/*! Internal message: Unlock database /*! Internal message: Unlock database
* *
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] pid Unix process id
* @param[in] xe Netconf request xml tree * @param[in] xe Netconf request xml tree
* @param[in] pid Unix process id
* @param[out] cbret Return xml value cligen buffer * @param[out] cbret Return xml value cligen buffer
*/ */
static int static int
from_client_unlock(clicon_handle h, from_client_unlock(clicon_handle h,
int pid,
cxobj *xe, cxobj *xe,
int pid,
cbuf *cbret) cbuf *cbret)
{ {
int retval = -1; int retval = -1;
@ -616,21 +519,23 @@ from_client_kill_session(clicon_handle h,
/*! Internal message: Copy database from db1 to db2 /*! Internal message: Copy database from db1 to db2
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] xe Netconf request xml tree * @param[in] xe Netconf request xml tree
* @param[in] mypid Process/session id of calling client
* @param[out] cbret Return xml value cligen buffer * @param[out] cbret Return xml value cligen buffer
* @retval 0 OK * @retval 0 OK
* @retval -1 Error. Send error message back to client. * @retval -1 Error. Send error message back to client.
*/ */
static int static int
from_client_copy_config(clicon_handle h, from_client_copy_config(clicon_handle h,
cxobj *xe, cxobj *xe,
int mypid,
cbuf *cbret) cbuf *cbret)
{ {
char *db1; char *source;
char *db2; char *target;
int retval = -1; int retval = -1;
int piddb;
if ((db1 = netconf_db_find(xe, "source")) == NULL){ if ((source = netconf_db_find(xe, "source")) == NULL){
cprintf(cbret, "<rpc-reply><rpc-error>" cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>" "<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>" "<error-type>protocol</error-type>"
@ -639,7 +544,7 @@ from_client_copy_config(clicon_handle h,
"</rpc-error></rpc-reply>"); "</rpc-error></rpc-reply>");
goto ok; goto ok;
} }
if ((db2 = netconf_db_find(xe, "target")) == NULL){ if ((target = netconf_db_find(xe, "target")) == NULL){
cprintf(cbret, "<rpc-reply><rpc-error>" cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>" "<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>" "<error-type>protocol</error-type>"
@ -648,7 +553,21 @@ from_client_copy_config(clicon_handle h,
"</rpc-error></rpc-reply>"); "</rpc-error></rpc-reply>");
goto ok; goto ok;
} }
if (xmldb_copy(h, db1, db2) < 0){ /* Check if target locked by other client */
piddb = xmldb_islocked(h, target);
if (piddb && mypid != piddb){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>lock-denied</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>Operation failed, lock is already held</error-message>"
"<error-info><session-id>%d</session-id></error-info>"
"</rpc-error></rpc-reply>",
piddb);
goto ok;
}
if (xmldb_copy(h, source, target) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>" cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>" "<error-tag>operation-failed</error-tag>"
"<error-type>application</error-type>" "<error-type>application</error-type>"
@ -667,18 +586,20 @@ from_client_copy_config(clicon_handle h,
/*! Internal message: Delete database /*! Internal message: Delete database
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] xe Netconf request xml tree * @param[in] xe Netconf request xml tree
* @param[in] mypid Process/session id of calling client
* @param[out] cbret Return xml value cligen buffer * @param[out] cbret Return xml value cligen buffer
* @retval 0 OK * @retval 0 OK
* @retval -1 Error. Send error message back to client. * @retval -1 Error. Send error message back to client.
*/ */
static int static int
from_client_delete_config(clicon_handle h, from_client_delete_config(clicon_handle h,
cxobj *xe, cxobj *xe,
int mypid,
cbuf *cbret) cbuf *cbret)
{ {
char *target;
int retval = -1; int retval = -1;
char *target;
int piddb;
if ((target = netconf_db_find(xe, "target")) == NULL|| if ((target = netconf_db_find(xe, "target")) == NULL||
strcmp(target, "running")==0){ strcmp(target, "running")==0){
@ -690,6 +611,20 @@ from_client_delete_config(clicon_handle h,
"</rpc-error></rpc-reply>"); "</rpc-error></rpc-reply>");
goto ok; goto ok;
} }
/* Check if target locked by other client */
piddb = xmldb_islocked(h, target);
if (piddb && mypid != piddb){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>lock-denied</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>Operation failed, lock is already held</error-message>"
"<error-info><session-id>%d</session-id></error-info>"
"</rpc-error></rpc-reply>",
piddb);
goto ok;
}
if (xmldb_delete(h, target) < 0){ if (xmldb_delete(h, target) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>" cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>" "<error-tag>operation-failed</error-tag>"
@ -807,15 +742,15 @@ from_client_debug(clicon_handle h,
return retval; return retval;
} }
/*! Internal clicon netconf message has arrived from a client. /*! An internal clicon message has arrived from a client. Receive and dispatch.
* @param[in] h Socket where message arrived. read from this. * @param[in] s Socket where message arrived. read from this.
* @param[in] ce Client session entry * @param[in] arg Client entry (from).
* @param[in] msg Clicon message. Contains internal netconf xml message. * @retval 0 OK
* @retval 0 OK. May be ok or error netconf reply * @retval -1 Error Terminates backend and is never called). Instead errors are
* @retval -1 Error * propagated back to client.
*/ */
static int static int
from_client_netconf(clicon_handle h, from_client_msg(clicon_handle h,
struct client_entry *ce, struct client_entry *ce,
struct clicon_msg *msg) struct clicon_msg *msg)
{ {
@ -825,12 +760,10 @@ from_client_netconf(clicon_handle h,
cxobj *xe; cxobj *xe;
char *name; char *name;
char *db; char *db;
cbuf *cbret; /* Return cligen buffer */ cbuf *cbret = NULL; /* return message */
int s;
int pid; int pid;
int ret; int ret;
s = ce->ce_s;
pid = ce->ce_pid; pid = ce->ce_pid;
/* Return netconf message. Should be filled in by the dispatch(sub) functions /* Return netconf message. Should be filled in by the dispatch(sub) functions
* as wither rpc-error or by positive response. * as wither rpc-error or by positive response.
@ -839,7 +772,7 @@ from_client_netconf(clicon_handle h,
clicon_err(OE_XML, errno, "cbuf_new"); clicon_err(OE_XML, errno, "cbuf_new");
goto done; goto done;
} }
if (clicon_msg_netconf_decode(msg, &xt) < 0){ if (clicon_msg_decode(msg, &xt) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>" cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>" "<error-tag>operation-failed</error-tag>"
"<error-type>rpc</error-type>" "<error-type>rpc</error-type>"
@ -847,7 +780,7 @@ from_client_netconf(clicon_handle h,
"<error-message>rpc expected</error-message>" "<error-message>rpc expected</error-message>"
"<error-info>Not recognized</error-info>" "<error-info>Not recognized</error-info>"
"</rpc-error></rpc-reply>"); "</rpc-error></rpc-reply>");
goto ok; goto reply;
} }
if ((x = xpath_first(xt, "/rpc")) == NULL){ if ((x = xpath_first(xt, "/rpc")) == NULL){
cprintf(cbret, "<rpc-reply><rpc-error>" cprintf(cbret, "<rpc-reply><rpc-error>"
@ -857,7 +790,7 @@ from_client_netconf(clicon_handle h,
"<error-message>rpc expected</error-message>" "<error-message>rpc expected</error-message>"
"<error-info>Not recognized</error-info>" "<error-info>Not recognized</error-info>"
"</rpc-error></rpc-reply>"); "</rpc-error></rpc-reply>");
goto ok; goto reply;
} }
xe = NULL; xe = NULL;
while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) { while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) {
@ -867,23 +800,23 @@ from_client_netconf(clicon_handle h,
goto done; goto done;
} }
else if (strcmp(name, "edit-config") == 0){ else if (strcmp(name, "edit-config") == 0){
if (from_client_edit_config(h, xe, cbret) <0) if (from_client_edit_config(h, xe, pid, cbret) <0)
goto done; goto done;
} }
else if (strcmp(name, "copy-config") == 0){ else if (strcmp(name, "copy-config") == 0){
if (from_client_copy_config(h, xe, cbret) <0) if (from_client_copy_config(h, xe, pid, cbret) <0)
goto done; goto done;
} }
else if (strcmp(name, "delete-config") == 0){ else if (strcmp(name, "delete-config") == 0){
if (from_client_delete_config(h, xe, cbret) <0) if (from_client_delete_config(h, xe, pid, cbret) <0)
goto done; goto done;
} }
else if (strcmp(name, "lock") == 0){ else if (strcmp(name, "lock") == 0){
if (from_client_lock(h, pid, xe, cbret) < 0) if (from_client_lock(h, xe, pid, cbret) < 0)
goto done; goto done;
} }
else if (strcmp(name, "unlock") == 0){ else if (strcmp(name, "unlock") == 0){
if (from_client_unlock(h, pid, xe, cbret) < 0) if (from_client_unlock(h, xe, pid, cbret) < 0)
goto done; goto done;
} }
else if (strcmp(name, "close-session") == 0){ else if (strcmp(name, "close-session") == 0){
@ -902,17 +835,17 @@ from_client_netconf(clicon_handle h,
"<error-severity>error</error-severity>" "<error-severity>error</error-severity>"
"<error-info><bad-element>source</bad-element></error-info>" "<error-info><bad-element>source</bad-element></error-info>"
"</rpc-error></rpc-reply>"); "</rpc-error></rpc-reply>");
goto ok; goto reply;
} }
if (from_client_validate(h, db, cbret) < 0) if (from_client_validate(h, db, cbret) < 0)
goto done; goto done;
} }
else if (strcmp(name, "commit") == 0){ else if (strcmp(name, "commit") == 0){
if (from_client_commit(h, cbret) < 0) if (from_client_commit(h, pid, cbret) < 0)
goto done; goto done;
} }
else if (strcmp(name, "discard-changes") == 0){ else if (strcmp(name, "discard-changes") == 0){
if (from_client_discard_changes(h, cbret) < 0) if (from_client_discard_changes(h, pid, cbret) < 0)
goto done; goto done;
} }
else if (strcmp(name, "create-subscription") == 0){ else if (strcmp(name, "create-subscription") == 0){
@ -937,82 +870,21 @@ from_client_netconf(clicon_handle h,
name); name);
} }
} }
ok: reply:
assert(cbuf_len(cbret)); assert(cbuf_len(cbret));
if (send_msg_reply(s, CLICON_MSG_NETCONF, if (send_msg_reply(ce->ce_s, cbuf_get(cbret), cbuf_len(cbret)+1) < 0){
cbuf_get(cbret), cbuf_len(cbret)+1) < 0){
if (errno == ECONNRESET) if (errno == ECONNRESET)
clicon_log(LOG_WARNING, "client rpc reset"); clicon_log(LOG_WARNING, "client rpc reset");
goto done; goto done;
} }
// ok:
retval = 0; retval = 0;
done: done:
if (xt) if (xt)
xml_free(xt); xml_free(xt);
if (cbret) if (cbret)
cbuf_free(cbret); cbuf_free(cbret);
return retval; return retval;// -1 here terminates backend
}
/*! Internal message: Change entry set/delete in database xmldb variant
* @param[in] h Clicon handle
* @param[in] s Socket where request arrived, and where replies are sent
* @param[in] pid Unix process id
* @param[in] msg Message
* @param[in] label Memory chunk
* @retval 0 OK
* @retval -1 Error. Send error message back to client.
*/
static int
from_client_change(clicon_handle h,
int s,
int pid,
struct clicon_msg *msg,
const char *label)
{
int retval = -1;
uint32_t len;
char *xk;
char *db;
enum operation_type op;
char *str = NULL;
char *val=NULL;
int piddb;
if (clicon_msg_change_decode(msg,
&db,
&op,
&xk,
&val,
&len,
label) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
clicon_err_reason);
goto done;
}
fprintf(stderr, "%s api_path:%s val:%s\n", __FUNCTION__, xk, val);
/* candidate is locked by other client */
if (strcmp(db, "candidate") == 0){
piddb = xmldb_islocked(h, db);
if (piddb && pid != piddb){
send_msg_err(s, OE_DB, 0,
"lock failed: locked by %d", piddb);
goto done;
}
}
/* Update database */
if (xmldb_put_xkey(h, db, op, xk, val) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
clicon_err_reason);
goto done;
}
if (send_msg_ok(s, NULL) < 0)
goto done;
retval = 0;
done:
if (str)
free(str);
return retval;
} }
/*! An internal clicon message has arrived from a client. Receive and dispatch. /*! An internal clicon message has arrived from a client. Receive and dispatch.
@ -1027,42 +899,22 @@ from_client(int s,
void* arg) void* arg)
{ {
int retval = -1; int retval = -1;
struct clicon_msg *msg = NULL;
struct client_entry *ce = (struct client_entry *)arg; struct client_entry *ce = (struct client_entry *)arg;
clicon_handle h = ce->ce_handle; clicon_handle h = ce->ce_handle;
struct clicon_msg *msg = NULL;
enum clicon_msg_type type;
int eof; int eof;
assert(s == ce->ce_s); // assert(s == ce->ce_s);
if (clicon_msg_rcv(ce->ce_s, &msg, &eof) < 0) if (clicon_msg_rcv(ce->ce_s, &msg, &eof) < 0)
goto done; goto done;
if (eof){ if (eof)
// xmldb_unlock_all(h, ce->ce_pid);
backend_client_rm(h, ce); backend_client_rm(h, ce);
goto ok; else
} if (from_client_msg(h, ce, msg) < 0)
type = ntohs(msg->op_type);
switch (type){
case CLICON_MSG_NETCONF:
if (from_client_netconf(h, ce, msg) < 0)
goto done; goto done;
break;
case CLICON_MSG_CHANGE:
if (from_client_change(h, ce->ce_s, ce->ce_pid, msg,
(char *)__FUNCTION__) < 0)
goto done;
break;
default:
send_msg_err(s, OE_PROTO, 0, "Unexpected message: %d", type);
goto done;
}
ok:
retval = 0; retval = 0;
done: done:
if (msg) if (msg)
free(msg); free(msg);
unchunk_group(__FUNCTION__); return retval;
if (0) return retval;
return 0; // -1 here terminates
} }

View file

@ -256,7 +256,7 @@ candidate_commit(clicon_handle h,
return retval; return retval;
} }
/*! Discard all changes in candidate / revert to running /*! Commit changes from candidate to running
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[out] cbret Return xml value cligen buffer * @param[out] cbret Return xml value cligen buffer
* @retval 0 OK. This may indicate both ok and err msg back to client * @retval 0 OK. This may indicate both ok and err msg back to client
@ -264,10 +264,25 @@ candidate_commit(clicon_handle h,
*/ */
int int
from_client_commit(clicon_handle h, from_client_commit(clicon_handle h,
int mypid,
cbuf *cbret) cbuf *cbret)
{ {
int retval = -1; int retval = -1;
int piddb;
/* Check if target locked by other client */
piddb = xmldb_islocked(h, "running");
if (piddb && mypid != piddb){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>lock-denied</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>Operation failed, lock is already held</error-message>"
"<error-info><session-id>%d</session-id></error-info>"
"</rpc-error></rpc-reply>",
piddb);
goto ok;
}
if (candidate_commit(h, "candidate") < 0){ if (candidate_commit(h, "candidate") < 0){
clicon_debug(1, "Commit candidate failed"); clicon_debug(1, "Commit candidate failed");
@ -290,17 +305,32 @@ from_client_commit(clicon_handle h,
/*! Discard all changes in candidate / revert to running /*! Discard all changes in candidate / revert to running
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] mypid Process/session id of calling client
* @param[out] cbret Return xml value cligen buffer * @param[out] cbret Return xml value cligen buffer
* @retval 0 OK. This may indicate both ok and err msg back to client * @retval 0 OK. This may indicate both ok and err msg back to client
* @retval -1 (Local) Error * @retval -1 (Local) Error
*/ */
int int
from_client_discard_changes(clicon_handle h, from_client_discard_changes(clicon_handle h,
int mypid,
cbuf *cbret) cbuf *cbret)
{ {
int retval = -1; int retval = -1;
int piddb;
/* Check if target locked by other client */
piddb = xmldb_islocked(h, "candidate");
if (piddb && mypid != piddb){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>lock-denied</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>Operation failed, lock is already held</error-message>"
"<error-info><session-id>%d</session-id></error-info>"
"</rpc-error></rpc-reply>",
piddb);
goto ok;
}
if (xmldb_copy(h, "running", "candidate") < 0){ if (xmldb_copy(h, "running", "candidate") < 0){
cprintf(cbret, "<rpc-reply><rpc-error>" cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>" "<error-tag>operation-failed</error-tag>"
@ -317,8 +347,6 @@ from_client_discard_changes(clicon_handle h,
return retval; /* may be zero if we ignoring errors from commit */ return retval; /* may be zero if we ignoring errors from commit */
} }
/*! Handle an incoming validate message from a client. /*! Handle an incoming validate message from a client.
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] db Database name * @param[in] db Database name

View file

@ -41,8 +41,8 @@
* Prototypes * Prototypes
*/ */
int from_client_validate(clicon_handle h, char *db, cbuf *cbret); int from_client_validate(clicon_handle h, char *db, cbuf *cbret);
int from_client_commit(clicon_handle h, cbuf *cbret); int from_client_commit(clicon_handle h, int pid, cbuf *cbret);
int from_client_discard_changes(clicon_handle h, cbuf *cbret); int from_client_discard_changes(clicon_handle h, int pid, cbuf *cbret);
int candidate_commit(clicon_handle h, char *db); int candidate_commit(clicon_handle h, char *db);
#endif /* _BACKEND_COMMIT_H_ */ #endif /* _BACKEND_COMMIT_H_ */

View file

@ -208,6 +208,7 @@ cli_dbxmlv(clicon_handle h,
cg_var *cval; cg_var *cval;
int len; int len;
cg_var *arg; cg_var *arg;
cbuf *cb = NULL;
if (cvec_len(argv) != 1){ if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be xml key format string", __FUNCTION__); clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be xml key format string", __FUNCTION__);
@ -225,7 +226,15 @@ cli_dbxmlv(clicon_handle h,
goto done; goto done;
} }
} }
if (clicon_rpc_change(h, "candidate", op, xk, str) < 0) if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (str)
cprintf(cb, "<config>%s</config>", str);
else
cprintf(cb, "<config/>");
if (clicon_rpc_edit_config(h, "candidate", op, xk, cbuf_get(cb)) < 0)
goto done; goto done;
if (clicon_autocommit(h)) { if (clicon_autocommit(h)) {
if (clicon_rpc_commit(h) < 0) if (clicon_rpc_commit(h) < 0)
@ -233,6 +242,8 @@ cli_dbxmlv(clicon_handle h,
} }
retval = 0; retval = 0;
done: done:
if (cb)
cbuf_free(cb);
if (str) if (str)
free(str); free(str);
if (xk) if (xk)
@ -771,7 +782,7 @@ discard_changesv(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv) cvec *argv)
{ {
return clicon_rpc_copy_config(h, "running", "candidate"); return clicon_rpc_discard_changes(h);
} }
/*! Copy from one database to another, eg running->startup /*! Copy from one database to another, eg running->startup
@ -805,7 +816,6 @@ cli_notification_cb(int s,
void *arg) void *arg)
{ {
struct clicon_msg *reply = NULL; struct clicon_msg *reply = NULL;
enum clicon_msg_type type;
int eof; int eof;
int retval = -1; int retval = -1;
char *eventstr = NULL; char *eventstr = NULL;
@ -826,10 +836,7 @@ cli_notification_cb(int s,
} }
if (format == NULL) if (format == NULL)
goto done; goto done;
type = ntohs(reply->op_type); if (clicon_msg_decode(reply, &xt) < 0)
switch (type){
case CLICON_MSG_NETCONF:
if (clicon_msg_netconf_decode(reply, &xt) < 0)
goto done; goto done;
if ((xe = xpath_first(xt, "//event")) != NULL) if ((xe = xpath_first(xt, "//event")) != NULL)
eventstr = xml_body(xe); eventstr = xml_body(xe);
@ -857,12 +864,7 @@ cli_notification_cb(int s,
goto done; goto done;
} }
} }
break;
default:
clicon_err(OE_PROTO, 0, "unexpected reply: %d", type);
goto done;
break;
}
retval = 0; retval = 0;
done: done:
if (xt) if (xt)

View file

@ -214,9 +214,17 @@ main(int argc, char **argv)
goto done; goto done;
} }
} }
if (addent) /* add entry */ if (addent){ /* add entry */
if (xmldb_put_xkey(h, db, OP_REPLACE, NULL, addstr) < 0) cxobj *xml = NULL;
if (clicon_xml_parse(&xml, "<config>%s</config>", addstr) < 0)
goto done; goto done;
if (xmldb_put(h, db, OP_REPLACE, NULL, xml) < 0)
goto done;
if (xml)
xml_free(xml);
}
if (rment) if (rment)
if (remove_entry(db, rmkey) < 0) if (remove_entry(db, rmkey) < 0)
goto done; goto done;

View file

@ -684,7 +684,6 @@ netconf_notification_cb(int s,
cbuf *cb; cbuf *cb;
cxobj *xe = NULL; /* event xml */ cxobj *xe = NULL; /* event xml */
cxobj *xt = NULL; /* top xml */ cxobj *xt = NULL; /* top xml */
enum clicon_msg_type type;
if (0){ if (0){
fprintf(stderr, "%s\n", __FUNCTION__); /* debug */ fprintf(stderr, "%s\n", __FUNCTION__); /* debug */
@ -703,11 +702,7 @@ netconf_notification_cb(int s,
xml_free(xfilter); xml_free(xfilter);
goto done; goto done;
} }
/* multiplex on message type: we only expect notify */ if (clicon_msg_decode(reply, &xt) < 0)
type = ntohs(reply->op_type);
switch (type){
case CLICON_MSG_NETCONF:
if (clicon_msg_netconf_decode(reply, &xt) < 0)
goto done; goto done;
if ((xe = xpath_first(xt, "//event")) != NULL) if ((xe = xpath_first(xt, "//event")) != NULL)
event = xml_body(xe); event = xml_body(xe);
@ -719,7 +714,6 @@ netconf_notification_cb(int s,
goto done; goto done;
if (xpath_first(xe, selector) == NULL) { if (xpath_first(xe, selector) == NULL) {
fprintf(stderr, "%s no match\n", __FUNCTION__); /* debug */ fprintf(stderr, "%s no match\n", __FUNCTION__); /* debug */
break;
} }
} }
/* create netconf message */ /* create netconf message */
@ -737,13 +731,6 @@ netconf_notification_cb(int s,
} }
fflush(stdout); fflush(stdout);
cbuf_free(cb); cbuf_free(cb);
break;
default:
clicon_err(OE_PROTO, 0, "%s: unexpected reply: %d",
__FUNCTION__, type);
goto done;
break;
}
retval = 0; retval = 0;
done: done:
if (xt != NULL) if (xt != NULL)

3
configure vendored
View file

@ -4315,7 +4315,7 @@ fi
ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile apps/dbctrl/Makefile apps/xmldb/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/docker/Makefile docker/Makefile docker/cli/Makefile docker/cli/Dockerfile docker/backend/Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile doc/Makefile" ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile apps/dbctrl/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/docker/Makefile docker/Makefile docker/cli/Makefile docker/cli/Dockerfile docker/backend/Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile doc/Makefile"
cat >confcache <<\_ACEOF cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure # This file is a shell script that caches the results of configure
@ -5018,7 +5018,6 @@ do
"apps/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/netconf/Makefile" ;; "apps/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/netconf/Makefile" ;;
"apps/restconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/restconf/Makefile" ;; "apps/restconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/restconf/Makefile" ;;
"apps/dbctrl/Makefile") CONFIG_FILES="$CONFIG_FILES apps/dbctrl/Makefile" ;; "apps/dbctrl/Makefile") CONFIG_FILES="$CONFIG_FILES apps/dbctrl/Makefile" ;;
"apps/xmldb/Makefile") CONFIG_FILES="$CONFIG_FILES apps/xmldb/Makefile" ;;
"include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;; "include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;;
"etc/Makefile") CONFIG_FILES="$CONFIG_FILES etc/Makefile" ;; "etc/Makefile") CONFIG_FILES="$CONFIG_FILES etc/Makefile" ;;
"etc/clixonrc") CONFIG_FILES="$CONFIG_FILES etc/clixonrc" ;; "etc/clixonrc") CONFIG_FILES="$CONFIG_FILES etc/clixonrc" ;;

View file

@ -110,7 +110,7 @@ downcall(clicon_handle h,
if ((cv = cvec_i(vars, 1)) != NULL) if ((cv = cvec_i(vars, 1)) != NULL)
str = cv_string_get(cv); str = cv_string_get(cv);
} }
if ((msg = clicon_msg_netconf_encode("<rpc><myrouting>%s</myrouting></rpc>", str)) == NULL) if ((msg = clicon_msg_encode("<rpc><myrouting>%s</myrouting></rpc>", str)) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;

View file

@ -46,31 +46,18 @@ enum format_enum{
MSG_NOTIFY_XML, /* means filter works on xml */ MSG_NOTIFY_XML, /* means filter works on xml */
}; };
/* See also map_type2str in clicon_proto.c */
enum clicon_msg_type{
CLICON_MSG_NETCONF = 1, /* Generic netconf message (lock/unlock/..) can all
msgs go to this?
1. string: netconf message
*/
CLICON_MSG_CHANGE, /* Change a (single) database entry:
1. uint32: operation: OP_MERGE/OP_REPLACE/OP_REMOVE
2. uint32: length of value string
3. string: name of database to change (eg "running")
4. string: key
5. string: value (can be NULL)
*/
};
/* Protocol message header */ /* Protocol message header */
struct clicon_msg { struct clicon_msg {
uint16_t op_len; /* length of message. */ uint16_t op_len; /* length of message. */
uint16_t op_type; /* message type, see enum clicon_msg_type */
char op_body[0]; /* rest of message, actual data */ char op_body[0]; /* rest of message, actual data */
}; };
/* /*
* Prototypes * Prototypes
*/ */
struct clicon_msg *clicon_msg_encode(char *format, ...);
int clicon_msg_decode(struct clicon_msg *msg, cxobj **xml);
int clicon_connect_unix(char *sockpath); int clicon_connect_unix(char *sockpath);
int clicon_rpc_connect_unix(struct clicon_msg *msg, int clicon_rpc_connect_unix(struct clicon_msg *msg,
@ -92,13 +79,6 @@ int clicon_msg_rcv(int s, struct clicon_msg **msg, int *eof);
int send_msg_notify(int s, int level, char *event); int send_msg_notify(int s, int level, char *event);
int send_msg_reply(int s, uint16_t type, char *data, uint16_t datalen); int send_msg_reply(int s, char *data, uint16_t datalen);
int send_msg_ok(int s, char *data);
int send_msg_err(int s, int err, int suberr, char *format, ...);
int send_msg_netconf_reply(int s, char *format, ...);
#endif /* _CLIXON_PROTO_H_ */ #endif /* _CLIXON_PROTO_H_ */

View file

@ -42,6 +42,7 @@
int clicon_rpc_msg(clicon_handle h, struct clicon_msg *msg, cxobj **xret0, int clicon_rpc_msg(clicon_handle h, struct clicon_msg *msg, cxobj **xret0,
int *sock0); int *sock0);
int clicon_rpc_netconf(clicon_handle h, char *xmlst, cxobj **xret, int *sp);
int clicon_rpc_netconf_xml(clicon_handle h, cxobj *xml, cxobj **xret, int *sp); int clicon_rpc_netconf_xml(clicon_handle h, cxobj *xml, cxobj **xret, int *sp);
int clicon_rpc_generate_error(cxobj *xerr); int clicon_rpc_generate_error(cxobj *xerr);
int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, cxobj **xret); int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, cxobj **xret);
@ -55,7 +56,7 @@ int clicon_rpc_close_session(clicon_handle h);
int clicon_rpc_kill_session(clicon_handle h, int session_id); int clicon_rpc_kill_session(clicon_handle h, int session_id);
int clicon_rpc_validate(clicon_handle h, char *db); int clicon_rpc_validate(clicon_handle h, char *db);
int clicon_rpc_commit(clicon_handle h); int clicon_rpc_commit(clicon_handle h);
// discard-changes int clicon_rpc_discard_changes(clicon_handle h);
int clicon_rpc_create_subscription(clicon_handle h, char *stream, char *filter, int clicon_rpc_create_subscription(clicon_handle h, char *stream, char *filter,
int *s); int *s);

View file

@ -1,70 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*
* Protocol to communicate between clients (eg clixon_cli, clixon_netconf)
* and server (clicon_backend)
*/
#ifndef _CLIXON_PROTO_ENCODE_H_
#define _CLIXON_PROTO_ENCODE_H_
/*
* Prototypes
*/
struct clicon_msg *clicon_msg_netconf_encode(char *format, ...);
struct clicon_msg *clicon_msg_netconf_encode_xml(cxobj *xml);
int clicon_msg_netconf_decode(struct clicon_msg *msg, cxobj **xml);
struct clicon_msg *
clicon_msg_change_encode(char *db, uint32_t op, char *key,
char *lvec, uint32_t lvec_len);
int
clicon_msg_change_decode(struct clicon_msg *msg,
char **db, uint32_t *op, char **key,
char **lvec, uint32_t *lvec_len,
const char *label);
struct clicon_msg *
clicon_msg_dbitems_get_reply_encode(cvec **cvecv,
int cveclen);
int
clicon_msg_dbitems_get_reply_decode(char *data,
uint16_t datalen,
cvec ***cvecv,
size_t *cveclen,
const char *label);
#endif /* _CLIXON_PROTO_ENCODE_H_ */

View file

@ -47,9 +47,6 @@ int xmldb_get(clicon_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen); cxobj **xtop, cxobj ***xvec, size_t *xlen);
int xmldb_put(clicon_handle h, char *db, enum operation_type op, int xmldb_put(clicon_handle h, char *db, enum operation_type op,
char *api_path, cxobj *xt); char *api_path, cxobj *xt);
int xmldb_put_xkey(clicon_handle h, char *db, enum operation_type op,
char *xkey, char *val);
int xmldb_dump(FILE *f, char *dbfilename, char *rxkey); int xmldb_dump(FILE *f, char *dbfilename, char *rxkey);
int xmldb_copy(clicon_handle h, char *from, char *to); int xmldb_copy(clicon_handle h, char *from, char *to);
int xmldb_lock(clicon_handle h, char *db, int pid); int xmldb_lock(clicon_handle h, char *db, int pid);

View file

@ -68,7 +68,7 @@ SRC = clixon_sig.c clixon_qdb.c clixon_log.c clixon_err.c clixon_event.c \
clixon_json.c \ clixon_json.c \
clixon_yang.c clixon_yang_type.c \ clixon_yang.c clixon_yang_type.c \
clixon_hash.c clixon_options.c clixon_plugin.c \ clixon_hash.c clixon_options.c clixon_plugin.c \
clixon_proto.c clixon_proto_encode.c clixon_proto_client.c \ clixon_proto.c clixon_proto_client.c \
clixon_xsl.c clixon_sha1.c clixon_xml_db.c clixon_xsl.c clixon_sha1.c clixon_xml_db.c
YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \ YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \

View file

@ -94,6 +94,7 @@ static struct errvec EV[] = {
{"UNIX error", OE_UNIX}, {"UNIX error", OE_UNIX},
{"Syslog error", OE_SYSLOG}, {"Syslog error", OE_SYSLOG},
{"Routing demon error", OE_ROUTING}, {"Routing demon error", OE_ROUTING},
{"XML error", OE_XML},
{"Plugins", OE_PLUGIN}, {"Plugins", OE_PLUGIN},
{"Yang error", OE_YANG}, {"Yang error", OE_YANG},
{"FATAL", OE_FATAL}, {"FATAL", OE_FATAL},

View file

@ -71,32 +71,60 @@
#include "clixon_xml.h" #include "clixon_xml.h"
#include "clixon_xsl.h" #include "clixon_xsl.h"
#include "clixon_proto.h" #include "clixon_proto.h"
#include "clixon_proto_encode.h"
static int _atomicio_sig = 0; static int _atomicio_sig = 0;
struct map_type2str{ /*! Encode a clicon netconf message
enum clicon_msg_type mt_type; * @param[in] param Variable agrument list format an XML netconf string
char *mt_str; /* string as in 4.2.4 in RFC 6020 */ * @retval msg Clicon message to send to eg clicon_msg_send()
}; */
struct clicon_msg *
/* Mapping between yang keyword string <--> clicon constants */ clicon_msg_encode(char *format, ...)
static const struct map_type2str msgmap[] = {
{CLICON_MSG_NETCONF, "netconf"},
{CLICON_MSG_CHANGE, "change"},
{-1, NULL},
};
static char *
msg_type2str(enum clicon_msg_type type)
{ {
const struct map_type2str *mt; va_list args;
int xmllen;
int len;
struct clicon_msg *msg = NULL;
int hdrlen = sizeof(*msg);
for (mt = &msgmap[0]; mt->mt_str; mt++) va_start(args, format);
if (mt->mt_type == type) xmllen = vsnprintf(NULL, 0, format, args) + 1;
return mt->mt_str; va_end(args);
len = hdrlen + xmllen;
if ((msg = (struct clicon_msg *)malloc(len)) == NULL){
clicon_err(OE_PROTO, errno, "malloc");
return NULL; return NULL;
} }
memset(msg, 0, len);
/* hdr */
msg->op_len = htons(len);
/* body */
va_start(args, format);
vsnprintf(msg->op_body, xmllen, format, args);
va_end(args);
return msg;
}
/*! Decode a clicon netconf message
*/
int
clicon_msg_decode(struct clicon_msg *msg,
cxobj **xml)
{
int retval = -1;
char *xmlstr;
/* body */
xmlstr = msg->op_body;
if (clicon_xml_parse_str(xmlstr, xml) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Open local connection using unix domain sockets /*! Open local connection using unix domain sockets
*/ */
@ -198,8 +226,8 @@ clicon_msg_send(int s,
{ {
int retval = -1; int retval = -1;
clicon_debug(2, "%s: send msg seq=%d len=%d", clicon_debug(2, "%s: send msg len=%d",
__FUNCTION__, ntohs(msg->op_type), ntohs(msg->op_len)); __FUNCTION__, ntohs(msg->op_len));
if (debug > 2) if (debug > 2)
msg_dump(msg); msg_dump(msg);
if (atomicio((ssize_t (*)(int, void *, size_t))write, if (atomicio((ssize_t (*)(int, void *, size_t))write,
@ -258,8 +286,8 @@ clicon_msg_rcv(int s,
goto done; goto done;
} }
mlen = ntohs(hdr.op_len); mlen = ntohs(hdr.op_len);
clicon_debug(2, "%s: rcv msg seq=%d, len=%d", clicon_debug(2, "%s: rcv msg len=%d",
__FUNCTION__, ntohs(hdr.op_type), mlen); __FUNCTION__, mlen);
if ((*msg = (struct clicon_msg *)malloc(mlen)) == NULL){ if ((*msg = (struct clicon_msg *)malloc(mlen)) == NULL){
clicon_err(OE_CFG, errno, "malloc"); clicon_err(OE_CFG, errno, "malloc");
goto done; goto done;
@ -303,8 +331,7 @@ clicon_rpc_connect_unix(struct clicon_msg *msg,
int s = -1; int s = -1;
struct stat sb; struct stat sb;
clicon_debug(1, "Send %s msg on %s", clicon_debug(1, "Send msg on %s", sockpath);
msg_type2str(ntohs(msg->op_type)), sockpath);
/* special error handling to get understandable messages (otherwise ENOENT) */ /* special error handling to get understandable messages (otherwise ENOENT) */
if (stat(sockpath, &sb) < 0){ if (stat(sockpath, &sb) < 0){
clicon_err(OE_PROTO, errno, "%s: config daemon not running?", sockpath); clicon_err(OE_PROTO, errno, "%s: config daemon not running?", sockpath);
@ -349,8 +376,7 @@ clicon_rpc_connect_inet(struct clicon_msg *msg,
int s = -1; int s = -1;
struct sockaddr_in addr; struct sockaddr_in addr;
clicon_debug(1, "Send %s msg to %s:%hu", clicon_debug(1, "Send msg to %s:%hu", dst, port);
msg_type2str(ntohs(msg->op_type)), dst, port);
memset(&addr, 0, sizeof(addr)); memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
@ -400,7 +426,6 @@ clicon_rpc(int s,
int retval = -1; int retval = -1;
struct clicon_msg *reply; struct clicon_msg *reply;
int eof; int eof;
enum clicon_msg_type type;
char *data = NULL; char *data = NULL;
cxobj *cx = NULL; cxobj *cx = NULL;
@ -414,17 +439,7 @@ clicon_rpc(int s,
errno = ESHUTDOWN; errno = ESHUTDOWN;
goto done; goto done;
} }
type = ntohs(reply->op_type);
switch (type){
case CLICON_MSG_NETCONF: /* ok or rpc-error expected */
data = reply->op_body; /* assume string */ data = reply->op_body; /* assume string */
break;
default:
clicon_err(OE_PROTO, 0, "%s: unexpected reply: %s",
__FUNCTION__, msg_type2str(type));
goto done;
break;
}
if (ret && data) if (ret && data)
if ((*ret = strdup(data)) == NULL){ if ((*ret = strdup(data)) == NULL){
clicon_err(OE_UNIX, errno, "strdup"); clicon_err(OE_UNIX, errno, "strdup");
@ -442,7 +457,6 @@ clicon_rpc(int s,
/*! Send a clicon_msg message as reply to a clicon rpc request /*! Send a clicon_msg message as reply to a clicon rpc request
* *
* @param[in] s Socket to communicate with client * @param[in] s Socket to communicate with client
* @param[in] type Clicon msg operation, see enum clicon_msg_type
* @param[in] data Returned data as byte-string. * @param[in] data Returned data as byte-string.
* @param[in] datalen Length of returned data XXX may be unecessary if always string? * @param[in] datalen Length of returned data XXX may be unecessary if always string?
* @retval 0 OK * @retval 0 OK
@ -450,7 +464,6 @@ clicon_rpc(int s,
*/ */
int int
send_msg_reply(int s, send_msg_reply(int s,
uint16_t type,
char *data, char *data,
uint16_t datalen) uint16_t datalen)
{ {
@ -462,7 +475,6 @@ send_msg_reply(int s,
if ((reply = (struct clicon_msg *)chunk(len, __FUNCTION__)) == NULL) if ((reply = (struct clicon_msg *)chunk(len, __FUNCTION__)) == NULL)
goto done; goto done;
memset(reply, 0, len); memset(reply, 0, len);
reply->op_type = htons(type);
reply->op_len = htons(len); reply->op_len = htons(len);
if (datalen > 0) if (datalen > 0)
memcpy(reply->op_body, data, datalen); memcpy(reply->op_body, data, datalen);
@ -490,7 +502,7 @@ send_msg_notify(int s,
int retval = -1; int retval = -1;
struct clicon_msg *msg = NULL; struct clicon_msg *msg = NULL;
if ((msg=clicon_msg_netconf_encode("<notification><event>%s</event></notification>", event)) == NULL) if ((msg=clicon_msg_encode("<notification><event>%s</event></notification>", event)) == NULL)
goto done; goto done;
if (clicon_msg_send(s, msg) < 0) if (clicon_msg_send(s, msg) < 0)
goto done; goto done;
@ -500,134 +512,3 @@ send_msg_notify(int s,
free(msg); free(msg);
return retval; return retval;
} }
/*! Send a clicon_msg OK message as reply to a clicon rpc request
*
* @param[in] s Socket to communicate with client
* @param[in] data Returned data as byte-string.
* @retval 0 OK
* @retval -1 Error
* @note send as netconf message XXX remove clicon header
*/
int
send_msg_ok(int s,
char *data)
{
int retval = -1;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL)
goto done;
if (data)
cprintf(cb, "<rpc-reply><ok>%s</ok></rpc-reply>", data);
else
cprintf(cb, "<rpc-reply><ok/></rpc-reply>");
if (send_msg_reply(s, CLICON_MSG_NETCONF, cbuf_get(cb), cbuf_len(cb)+1) < 0){
if (errno == ECONNRESET)
clicon_log(LOG_WARNING, "client rpc reset");
goto done;
}
retval=0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Send a clicon_msg Error message as reply to a clicon rpc request
*
* @param[in] s Socket to communicate with client
* @param[in] data Returned data as byte-string.
* @param[in] datalen Length of returned data
* @retval 0 OK
* @retval -1 Error
* @note send as netconf message XXX remove clicon header
*/
int
send_msg_err(int s,
int err,
int suberr,
char *format, ...)
{
va_list args;
char *info = NULL;
int len;
int retval = -1;
cbuf *cb = NULL;
va_start(args, format);
len = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
if ((info = malloc(len)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(info, 0, len);
va_start(args, format);
vsnprintf(info, len, format, args);
va_end(args);
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc-reply><rpc-error>"
"<error-type>clixon</error-type>"
"<error-tag>%d</error-tag>"
"<error-message>%d</error-message>"
"<error-info>%s</error-info>"
"</rpc-error></rpc-reply>",
err, suberr, info);
if (send_msg_reply(s, CLICON_MSG_NETCONF, cbuf_get(cb), cbuf_len(cb)+1) < 0){
if (errno == ECONNRESET)
clicon_log(LOG_WARNING, "client rpc reset");
goto done;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (info)
free(info);
return retval;
}
/*! Send a clicon_msg Error message as reply to a clicon rpc request
*
* @param[in] s Socket to communicate with client
* @param[in] data Returned data as byte-string.
* @param[in] datalen Length of returned data
* @retval 0 OK
* @retval -1 Error
* @note send as netconf message XXX remove clicon header
* XXX: see clicon_xml_parse
*/
int
send_msg_netconf_reply(int s,
char *format, ...)
{
va_list args;
char *str = NULL;
int len;
int retval = -1;
va_start(args, format);
len = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
if ((str = malloc(len)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(str, 0, len);
va_start(args, format);
len = vsnprintf(str, len, format, args);
va_end(args);
if (send_msg_reply(s, CLICON_MSG_NETCONF, str, len) < 0){
if (errno == ECONNRESET)
clicon_log(LOG_WARNING, "client rpc reset");
goto done;
}
retval = 0;
done:
if (str)
free(str);
return retval;
}

View file

@ -66,10 +66,9 @@
#include "clixon_xsl.h" #include "clixon_xsl.h"
#include "clixon_proto.h" #include "clixon_proto.h"
#include "clixon_err.h" #include "clixon_err.h"
#include "clixon_proto_encode.h"
#include "clixon_proto_client.h" #include "clixon_proto_client.h"
/*! Internal rpc function /*! Send internal netconf rpc from client to backend
* @param[in] h CLICON handle * @param[in] h CLICON handle
* @param[in] msg Encoded message. Deallocate woth free * @param[in] msg Encoded message. Deallocate woth free
* @param[out] xret Return value from backend as netconf xml tree. Free w xml_free * @param[out] xret Return value from backend as netconf xml tree. Free w xml_free
@ -88,11 +87,6 @@ clicon_rpc_msg(clicon_handle h,
int port; int port;
char *retdata = NULL; char *retdata = NULL;
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *x;
uint32_t err;
uint32_t suberr;
char *errmsg = NULL;
char *etype = NULL;
if ((sock = clicon_sock(h)) == NULL){ if ((sock = clicon_sock(h)) == NULL){
clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set"); clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set");
@ -124,38 +118,9 @@ clicon_rpc_msg(clicon_handle h,
goto done; goto done;
break; break;
} }
if (retdata &&
#if 1 /* Parse return data and intercept clixon errors */ clicon_xml_parse_str(retdata, &xret) < 0)
if (retdata) {
if (clicon_xml_parse_str(retdata, &xret) < 0)
goto done; goto done;
if (xpath_first(xret, "/rpc-reply/ok") != NULL ||
xpath_first(xret, "/rpc-reply/data") != NULL)
;
else if (xpath_first(xret, "/rpc-reply/rpc-error") != NULL){
if ((x=xpath_first(xret, "//error-type"))!=NULL)
etype = xml_body(x);
if (etype&&strcmp(etype, "clixon")==0){
if ((x=xpath_first(xret, "//error-tag"))!=NULL)
err = atoi(xml_body(x));
if ((x=xpath_first(xret, "//error-message"))!=NULL)
suberr = atoi(xml_body(x));
if ((x=xpath_first(xret, "//error-info"))!=NULL)
errmsg = xml_body(x);
if (debug)
clicon_err(err, suberr, "%s msgtype:%hu", errmsg, ntohs(msg->op_type));
else
clicon_err(err, suberr, "%s", errmsg);
goto done;
}
}
else{
clicon_err(OE_PROTO, 0, "%s: unexpected reply",
__FUNCTION__);
}
}
#endif
if (xret0){ if (xret0){
*xret0 = xret; *xret0 = xret;
xret = NULL; xret = NULL;
@ -172,20 +137,21 @@ clicon_rpc_msg(clicon_handle h,
/*! Generic xml netconf clicon rpc /*! Generic xml netconf clicon rpc
* Want to go over to use netconf directly between client and server,... * Want to go over to use netconf directly between client and server,...
* @param[in] h clicon handle * @param[in] h clicon handle
* @param[in] xi XML netconf tree * @param[in] xmlstr XML netconf tree as string
* @param[out] xret Return XML, error or OK * @param[out] xret Return XML netconf tree, error or OK
* @param[out] sp Socket pointer for notification, otherwise NULL * @param[out] sp Socket pointer for notification, otherwise NULL
* @see clicon_rpc_netconf_xml xml as tree instead of string
*/ */
int int
clicon_rpc_netconf_xml(clicon_handle h, clicon_rpc_netconf(clicon_handle h,
cxobj *xin, char *xmlstr,
cxobj **xret, cxobj **xret,
int *sp) int *sp)
{ {
int retval = -1; int retval = -1;
struct clicon_msg *msg = NULL; struct clicon_msg *msg = NULL;
if ((msg = clicon_msg_netconf_encode_xml(xin)) == NULL) if ((msg = clicon_msg_encode("%s", xmlstr)) < 0)
goto done; goto done;
if (clicon_rpc_msg(h, msg, xret, sp) < 0) if (clicon_rpc_msg(h, msg, xret, sp) < 0)
goto done; goto done;
@ -196,25 +162,64 @@ clicon_rpc_netconf_xml(clicon_handle h,
return retval; return retval;
} }
/*! Generic xml netconf clicon rpc
* Want to go over to use netconf directly between client and server,...
* @param[in] h clicon handle
* @param[in] xml XML netconf tree
* @param[out] xret Return XML netconf tree, error or OK
* @param[out] sp Socket pointer for notification, otherwise NULL
* @see clicon_rpc_netconf xml as string instead of tree
*/
int
clicon_rpc_netconf_xml(clicon_handle h,
cxobj *xml,
cxobj **xret,
int *sp)
{
int retval = -1;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (clicon_xml2cbuf(cb, xml, 0, 0) < 0)
goto done;
if (clicon_rpc_netconf(h, cbuf_get(cb), xret, sp) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Generate clicon error function call from Netconf error message
* @param[in] xerr Netconf error message on the level: <rpc-reply><rpc-error>
*/
int int
clicon_rpc_generate_error(cxobj *xerr) clicon_rpc_generate_error(cxobj *xerr)
{ {
int retval = -1;
cbuf *cb = NULL;
cxobj *x; cxobj *x;
char *etype="";
char *etag="";
char *emsg="";
char *einfo="";
if ((cb = cbuf_new()) ==NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if ((x=xpath_first(xerr, "error-type"))!=NULL) if ((x=xpath_first(xerr, "error-type"))!=NULL)
etype = xml_body(x); cprintf(cb, "%s ", xml_body(x));
if ((x=xpath_first(xerr, "error-tag"))!=NULL) if ((x=xpath_first(xerr, "error-tag"))!=NULL)
etag = xml_body(x); cprintf(cb, "%s ", xml_body(x));
if ((x=xpath_first(xerr, "error-message"))!=NULL) if ((x=xpath_first(xerr, "error-message"))!=NULL)
emsg = xml_body(x); cprintf(cb, "%s ", xml_body(x));
if ((x=xpath_first(xerr, "error-info"))!=NULL) if ((x=xpath_first(xerr, "error-info"))!=NULL)
einfo = xml_body(x); clicon_xml2cbuf(cb, xml_child_i(x,0), 0, 0);
clicon_err(OE_XML, 0, "%s %s %s %s", etype, etag, emsg, einfo); clicon_err_fn("Clixon", 0, OE_XML, 0, "%s", cbuf_get(cb));
return 0; retval = 0;
done:
return retval;
} }
/*! Get database configuration /*! Get database configuration
@ -252,7 +257,7 @@ clicon_rpc_get_config(clicon_handle h,
if (xpath && strlen(xpath)) if (xpath && strlen(xpath))
cprintf(cb, "<filter type=\"xpath\" select=\"%s\"/>", xpath); cprintf(cb, "<filter type=\"xpath\" select=\"%s\"/>", xpath);
cprintf(cb, "</get-config></rpc>"); cprintf(cb, "</get-config></rpc>");
if ((msg = clicon_msg_netconf_encode(cbuf_get(cb))) == NULL) if ((msg = clicon_msg_encode(cbuf_get(cb))) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;
@ -280,14 +285,14 @@ clicon_rpc_get_config(clicon_handle h,
} }
/*! Send database entries as XML to backend daemon /*! Send database entries as XML to backend daemon
* Same as clicon_proto_change just with a cvec instead of lvec
* @param[in] h CLICON handle * @param[in] h CLICON handle
* @param[in] db Name of database * @param[in] db Name of database
* @param[in] op Operation on database item: OP_MERGE, OP_REPLACE * @param[in] op Operation on database item: OP_MERGE, OP_REPLACE
* @param[in] api_path restconf API Path (or "") * @param[in] api_path restconf API Path (or "")
* @param[in] xml XML string. Ex: <a>..</a><b>...</b> * @param[in] xml XML string. Ex: <config><a>..</a><b>...</b></config>
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* @note xml arg need to have <config> as top element
* @code * @code
* if (clicon_rpc_edit_config(h, "running", OP_MERGE, "/", * if (clicon_rpc_edit_config(h, "running", OP_MERGE, "/",
* "<config><a>4</a></config>") < 0) * "<config><a>4</a></config>") < 0)
@ -299,7 +304,7 @@ clicon_rpc_edit_config(clicon_handle h,
char *db, char *db,
enum operation_type op, enum operation_type op,
char *api_path, char *api_path,
char *xml) char *xmlstr)
{ {
int retval = -1; int retval = -1;
struct clicon_msg *msg = NULL; struct clicon_msg *msg = NULL;
@ -314,10 +319,10 @@ clicon_rpc_edit_config(clicon_handle h,
xml_operation2str(op)); xml_operation2str(op));
if (api_path && strlen(api_path)) if (api_path && strlen(api_path))
cprintf(cb, "<filter type=\"restconf\" select=\"%s\"/>", api_path); cprintf(cb, "<filter type=\"restconf\" select=\"%s\"/>", api_path);
if (xml) if (xmlstr)
cprintf(cb, "%s", xml); cprintf(cb, "%s", xmlstr);
cprintf(cb, "</edit-config></rpc>"); cprintf(cb, "</edit-config></rpc>");
if ((msg = clicon_msg_netconf_encode(cbuf_get(cb))) == NULL) if ((msg = clicon_msg_encode(cbuf_get(cb))) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;
@ -357,7 +362,7 @@ clicon_rpc_copy_config(clicon_handle h,
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xerr; cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><copy-config><source><%s/></source><target><%s/></target></copy-config></rpc>", db1, db2)) == NULL) if ((msg = clicon_msg_encode("<rpc><copy-config><source><%s/></source><target><%s/></target></copy-config></rpc>", db1, db2)) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;
@ -391,7 +396,7 @@ clicon_rpc_delete_config(clicon_handle h,
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xerr; cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><delete-config><target><%s/></target></delete-config></rpc>", db)) == NULL) if ((msg = clicon_msg_encode("<rpc><delete-config><target><%s/></target></delete-config></rpc>", db)) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;
@ -408,6 +413,10 @@ clicon_rpc_delete_config(clicon_handle h,
return retval; return retval;
} }
/*! Lock a database
* @param[in] h CLICON handle
* @param[in] db database, eg "running"
*/
int int
clicon_rpc_lock(clicon_handle h, clicon_rpc_lock(clicon_handle h,
char *db) char *db)
@ -417,7 +426,7 @@ clicon_rpc_lock(clicon_handle h,
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xerr; cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><lock><target><%s/></target></lock></rpc>", db)) == NULL) if ((msg = clicon_msg_encode("<rpc><lock><target><%s/></target></lock></rpc>", db)) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;
@ -434,6 +443,10 @@ clicon_rpc_lock(clicon_handle h,
return retval; return retval;
} }
/*! Unlock a database
* @param[in] h CLICON handle
* @param[in] db database, eg "running"
*/
int int
clicon_rpc_unlock(clicon_handle h, clicon_rpc_unlock(clicon_handle h,
char *db) char *db)
@ -443,7 +456,7 @@ clicon_rpc_unlock(clicon_handle h,
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xerr; cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><unlock><target><%s/></target></unlock></rpc>", db)) == NULL) if ((msg = clicon_msg_encode("<rpc><unlock><target><%s/></target></unlock></rpc>", db)) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;
@ -460,6 +473,9 @@ clicon_rpc_unlock(clicon_handle h,
return retval; return retval;
} }
/*! Close a (user) session
* @param[in] h CLICON handle
*/
int int
clicon_rpc_close_session(clicon_handle h) clicon_rpc_close_session(clicon_handle h)
{ {
@ -468,7 +484,7 @@ clicon_rpc_close_session(clicon_handle h)
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xerr; cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><close-session/></rpc>")) == NULL) if ((msg = clicon_msg_encode("<rpc><close-session/></rpc>")) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;
@ -485,6 +501,10 @@ clicon_rpc_close_session(clicon_handle h)
return retval; return retval;
} }
/*! Kill other user sessions
* @param[in] h CLICON handle
* @param[in] session_id Session id of other user session
*/
int int
clicon_rpc_kill_session(clicon_handle h, clicon_rpc_kill_session(clicon_handle h,
int session_id) int session_id)
@ -494,7 +514,7 @@ clicon_rpc_kill_session(clicon_handle h,
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xerr; cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><kill-session><session-id>%d</session-id></kill-session></rpc>", session_id)) == NULL) if ((msg = clicon_msg_encode("<rpc><kill-session><session-id>%d</session-id></kill-session></rpc>", session_id)) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;
@ -511,7 +531,6 @@ clicon_rpc_kill_session(clicon_handle h,
return retval; return retval;
} }
/*! Send validate request to backend daemon /*! Send validate request to backend daemon
* @param[in] h CLICON handle * @param[in] h CLICON handle
* @param[in] db Name of database * @param[in] db Name of database
@ -527,7 +546,7 @@ clicon_rpc_validate(clicon_handle h,
cxobj *xerr; cxobj *xerr;
cxobj *x; cxobj *x;
if ((msg = clicon_msg_netconf_encode("<rpc><validate><source><%s/></source></validate></rpc>", db)) == NULL) if ((msg = clicon_msg_encode("<rpc><validate><source><%s/></source></validate></rpc>", db)) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;
@ -547,8 +566,6 @@ clicon_rpc_validate(clicon_handle h,
/*! Commit changes send a commit request to backend daemon /*! Commit changes send a commit request to backend daemon
* @param[in] h CLICON handle * @param[in] h CLICON handle
* @param[in] from name of 'from' database (eg "candidate")
* @param[in] db name of 'to' database (eg "running")
* @retval 0 OK * @retval 0 OK
*/ */
int int
@ -559,7 +576,36 @@ clicon_rpc_commit(clicon_handle h)
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xerr; cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><commit/></rpc>")) == NULL) if ((msg = clicon_msg_encode("<rpc><commit/></rpc>")) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0;
done:
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
/*! Discard all changes in candidate / revert to running
* @param[in] h CLICON handle
* @retval 0 OK
*/
int
clicon_rpc_discard_changes(clicon_handle h)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_encode("<rpc><discard_changes/></rpc>")) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;
@ -594,7 +640,7 @@ clicon_rpc_create_subscription(clicon_handle h,
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xerr; cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><create-subscription>" if ((msg = clicon_msg_encode("<rpc><create-subscription>"
"<stream>%s</stream>" "<stream>%s</stream>"
"<filter>%s</filter>" "<filter>%s</filter>"
"</create-subscription></rpc>", "</create-subscription></rpc>",
@ -615,43 +661,6 @@ clicon_rpc_create_subscription(clicon_handle h,
return retval; return retval;
} }
/*! Send database change request to backend daemon, variant for xmldb
* Same as clicon_proto_change just with a string
* @param[in] h CLICON handle
* @param[in] db Name of database
* @param[in] op Operation on database item: set, delete, (merge?)
* @param[in] key Database key
* @param[in] value value as string
* @retval 0 OK
* @retval -1 Error
* @note special case: remove all: key:"/" op:OP_REMOVE
*/
int
clicon_rpc_change(clicon_handle h,
char *db,
enum operation_type op,
char *key,
char *val)
{
int retval = -1;
struct clicon_msg *msg = NULL;
if ((msg = clicon_msg_change_encode(db,
op,
key,
val,
val?strlen(val)+1:0)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL) < 0)
goto done;
retval = 0;
done:
if (msg)
free(msg);
return retval;
}
/*! Send a debug request to backend server /*! Send a debug request to backend server
* @param[in] h CLICON handle * @param[in] h CLICON handle
* @param[in] level Debug level * @param[in] level Debug level
@ -665,7 +674,7 @@ clicon_rpc_debug(clicon_handle h,
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xerr; cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><debug><level>%d</level></debug></rpc>", level)) == NULL) if ((msg = clicon_msg_encode("<rpc><debug><level>%d</level></debug></rpc>", level)) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;

View file

@ -1,244 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*
* Protocol to communicate between clients (eg clicon_cli, clicon_netconf)
* and server (clicon_backend)
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <syslog.h>
#include <signal.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_queue.h"
#include "clixon_chunk.h"
#include "clixon_sig.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_xml.h"
#include "clixon_proto.h"
#include "clixon_proto_encode.h"
struct clicon_msg *
clicon_msg_netconf_encode(char *format, ...)
{
va_list args;
int xmllen;
int len;
struct clicon_msg *msg = NULL;
int hdrlen = sizeof(*msg);
va_start(args, format);
xmllen = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
len = hdrlen + xmllen;
if ((msg = (struct clicon_msg *)malloc(len)) == NULL){
clicon_err(OE_PROTO, errno, "malloc");
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_NETCONF);
msg->op_len = htons(len);
/* body */
va_start(args, format);
vsnprintf(msg->op_body, xmllen, format, args);
va_end(args);
return msg;
}
struct clicon_msg *
clicon_msg_netconf_encode_xml(cxobj *xml)
{
struct clicon_msg *msg = NULL;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (clicon_xml2cbuf(cb, xml, 0, 0) < 0)
goto done;
if ((msg = clicon_msg_netconf_encode("%s", cbuf_get(cb))) < 0)
goto done;
done:
if (cb)
cbuf_free(cb);
return msg;
}
int
clicon_msg_netconf_decode(struct clicon_msg *msg,
cxobj **xml)
{
int retval = -1;
char *xmlstr;
/* body */
xmlstr = msg->op_body;
if (clicon_xml_parse_str(xmlstr, xml) < 0)
goto done;
retval = 0;
done:
return retval;
}
struct clicon_msg *
clicon_msg_change_encode(char *db,
uint32_t op,
char *key,
char *str,
uint32_t str_len)
{
struct clicon_msg *msg;
uint16_t len;
int hdrlen = sizeof(*msg);
int p;
uint32_t tmp;
clicon_debug(2, "%s: op: %d str_len: %d db: %s key: '%s'",
__FUNCTION__,
op, str_len, db, key);
p = 0;
len = sizeof(*msg) + 2*sizeof(uint32_t) + strlen(db) + 1 +
strlen(key) + str_len;
if (str_len)
len++; /* if str not null add end of string */
if ((msg = (struct clicon_msg *)malloc(len)) == NULL){
clicon_err(OE_PROTO, errno, "malloc");
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_CHANGE);
msg->op_len = htons(len);
/* body */
tmp = htonl(op);
memcpy(msg->op_body+p, &tmp, sizeof(uint32_t));
p += sizeof(uint32_t);
tmp = htonl(str_len);
memcpy(msg->op_body+p, &tmp, sizeof(uint32_t));
p += sizeof(uint32_t);
strncpy(msg->op_body+p, db, len-p-hdrlen);
p += strlen(db)+1;
strncpy(msg->op_body+p, key, len-p-hdrlen);
p += strlen(key)+1;
if (str_len){
memcpy(msg->op_body+p, str, str_len);
p += str_len;
}
return msg;
}
int
clicon_msg_change_decode(struct clicon_msg *msg,
char **db,
uint32_t *op,
char **key,
char **str,
uint32_t *str_len,
const char *label)
{
int p;
uint32_t tmp;
p = 0;
/* body */
memcpy(&tmp, msg->op_body+p, sizeof(uint32_t));
*op = ntohl(tmp);
p += sizeof(uint32_t);
memcpy(&tmp, msg->op_body+p, sizeof(uint32_t));
*str_len = ntohl(tmp);
p += sizeof(uint32_t);
if ((*db = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*db)+1;
if ((*key = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*key)+1;
if (*str_len){
if ((*str = chunk(*str_len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk",
__FUNCTION__);
return -1;
}
memcpy(*str, msg->op_body+p, *str_len);
p += *str_len;
}
else
*str = NULL;
clicon_debug(2, "%s: op: %d str_len: %d db: %s key: '%s'",
__FUNCTION__,
*op, *str_len, *db, *key);
return 0;
}

View file

@ -1110,7 +1110,7 @@ clicon_xml_parse_str(char *str,
* *
* @code * @code
* cxobj *cx = NULL; * cxobj *cx = NULL;
* if (clicon_xml_parse(&cx, "<xml>%s</xml", 22) < 0) * if (clicon_xml_parse(&cx, "<xml>%d</xml>", 22) < 0)
* err; * err;
* xml_free(cx); * xml_free(cx);
* @endcode * @endcode

View file

@ -230,6 +230,7 @@ yang2xmlkeyfmt(yang_stmt *ys,
return retval; return retval;
} }
/*! Transform an xml key format and a vector of values to an XML key /*! Transform an xml key format and a vector of values to an XML key
* Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey() * Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey()
* Example: * Example:
@ -1183,6 +1184,225 @@ put(char *dbname,
return retval; return retval;
} }
/*! Modify database provided an XML database key and an operation
* @param[in] h CLICON handle
* @param[in] db Database name
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] xk XML Key, eg /aa/bb=17/name
* @param[in] val Key value, eg "17"
* @retval 0 OK
* @retval -1 Error
* @code
* if (xmldb_put_xkey(h, db, OP_MERGE, "/aa/bb=17/name", "17") < 0)
* err;
* @endcode
* @see xmldb_put with xml-tree, no path
*/
static int
xmldb_put_xkey(clicon_handle h,
char *db,
enum operation_type op,
char *xk,
char *val)
{
int retval = -1;
cxobj *x = NULL;
yang_stmt *y = NULL;
yang_stmt *ykey;
char **vec;
int nvec;
char **valvec;
int nvalvec;
int i;
int j;
char *name;
char *restval;
cg_var *cvi;
cvec *cvk = NULL; /* vector of index keys */
char *val2 = NULL;
cbuf *ckey=NULL; /* partial keys */
cbuf *csubkey=NULL; /* partial keys */
cbuf *crx=NULL; /* partial keys */
char *keyname;
int exists;
int npairs;
struct db_pair *pairs;
yang_spec *yspec;
char *filename = NULL;
yspec = clicon_dbspec_yang(h);
if (db2file(h, db, &filename) < 0)
goto done;
if (xk == NULL || *xk!='/'){
clicon_err(OE_DB, 0, "Invalid key: %s", xk);
goto done;
}
if ((ckey = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((csubkey = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((vec = clicon_strsplit(xk, "/", &nvec, __FUNCTION__)) == NULL)
goto done;
if (nvec < 2){
clicon_err(OE_XML, 0, "Malformed key: %s", xk);
goto done;
}
i = 1;
while (i<nvec){
name = vec[i]; /* E.g "x=1,2" -> name:x restval=1,2 */
if ((restval = index(name, '=')) != NULL){
*restval = '\0';
restval++;
}
if (i==1){
if (strlen(name)==0 && (op==OP_DELETE || op == OP_REMOVE)){
/* Special handling of "/" */
cprintf(ckey, "/");
break;
}
else
if ((y = yang_find_topnode(yspec, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", x?xml_name(x):"");
goto done;
}
}
else
if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
if ((op==OP_DELETE || op == OP_REMOVE) &&
y->ys_keyword == Y_LEAF &&
y->ys_parent->yn_keyword == Y_LIST &&
yang_key_match(y->ys_parent, y->ys_argument))
/* Special rule if key, dont write last key-name, rm whole*/;
else
cprintf(ckey, "/%s", name);
i++;
switch (y->ys_keyword){
case Y_LEAF_LIST:
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
cprintf(ckey, "=%s", restval);
break;
case Y_LIST:
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
if ((valvec = clicon_strsplit(restval, ",", &nvalvec, __FUNCTION__)) == NULL)
goto done;
if (cvec_len(cvk) != nvalvec){
clicon_err(OE_XML, errno, "List %s key length mismatch", name);
goto done;
}
cvi = NULL;
/* Iterate over individual yang keys */
j = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if (j)
cprintf(ckey, ",");
else
cprintf(ckey, "=");
val2 = valvec[j++];
cprintf(ckey, "%s", val2);
cbuf_reset(csubkey);
cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname);
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0)
goto done;
}
if (cvk){
cvec_free(cvk);
cvk = NULL;
}
break;
default:
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0)
goto done;
break;
}
}
xk = cbuf_get(ckey);
/* final key */
switch (op){
case OP_CREATE:
if ((exists = db_exists(filename, xk)) < 0)
goto done;
if (exists == 1){
clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk);
goto done;
}
case OP_MERGE:
case OP_REPLACE:
if (y->ys_keyword == Y_LEAF || y->ys_keyword == Y_LEAF_LIST){
if (db_set(filename, xk, val, val?strlen(val)+1:0) < 0)
goto done;
}
else
if (db_set(filename, xk, NULL, 0) < 0)
goto done;
break;
case OP_DELETE:
if ((exists = db_exists(filename, xk)) < 0)
goto done;
if (exists == 0){
clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", xk);
goto done;
}
case OP_REMOVE:
/* Read in complete database (this can be optimized) */
if ((crx = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(crx, "^%s.*$", xk);
if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0)
goto done;
for (i = 0; i < npairs; i++) {
if (db_del(filename, pairs[i].dp_key) < 0)
goto done;
}
break;
default:
break;
}
retval = 0;
done:
if (filename)
free(filename);
if (ckey)
cbuf_free(ckey);
if (csubkey)
cbuf_free(csubkey);
if (crx)
cbuf_free(crx);
if (cvk)
cvec_free(cvk);
unchunk_group(__FUNCTION__);
return retval;
}
/*! Modify database provided an xml tree, a restconf api_path and an operation /*! Modify database provided an xml tree, a restconf api_path and an operation
* *
* @param[in] h CLICON handle * @param[in] h CLICON handle
@ -1430,6 +1650,8 @@ xmldb_put(clicon_handle h,
yang_spec *yspec; yang_spec *yspec;
char *dbfilename = NULL; char *dbfilename = NULL;
if (xml_child_nr(xt)==0 || xml_body(xt)!= NULL)
return xmldb_put_xkey(h, db, op, api_path, xml_body(xt));
yspec = clicon_dbspec_yang(h); yspec = clicon_dbspec_yang(h);
if (db2file(h, db, &dbfilename) < 0) if (db2file(h, db, &dbfilename) < 0)
goto done; goto done;
@ -1464,224 +1686,6 @@ xmldb_put(clicon_handle h,
return retval; return retval;
} }
/*! Modify database provided an XML database key and an operation
* @param[in] h CLICON handle
* @param[in] db Database name
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] xk XML Key, eg /aa/bb=17/name
* @param[in] val Key value, eg "17"
* @retval 0 OK
* @retval -1 Error
* @code
* if (xmldb_put_xkey(h, db, OP_MERGE, "/aa/bb=17/name", "17") < 0)
* err;
* @endcode
* @see xmldb_put with xml-tree, no path
*/
int
xmldb_put_xkey(clicon_handle h,
char *db,
enum operation_type op,
char *xk,
char *val)
{
int retval = -1;
cxobj *x = NULL;
yang_stmt *y = NULL;
yang_stmt *ykey;
char **vec;
int nvec;
char **valvec;
int nvalvec;
int i;
int j;
char *name;
char *restval;
cg_var *cvi;
cvec *cvk = NULL; /* vector of index keys */
char *val2 = NULL;
cbuf *ckey=NULL; /* partial keys */
cbuf *csubkey=NULL; /* partial keys */
cbuf *crx=NULL; /* partial keys */
char *keyname;
int exists;
int npairs;
struct db_pair *pairs;
yang_spec *yspec;
char *filename = NULL;
yspec = clicon_dbspec_yang(h);
if (db2file(h, db, &filename) < 0)
goto done;
if (xk == NULL || *xk!='/'){
clicon_err(OE_DB, 0, "Invalid key: %s", xk);
goto done;
}
if ((ckey = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((csubkey = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((vec = clicon_strsplit(xk, "/", &nvec, __FUNCTION__)) == NULL)
goto done;
if (nvec < 2){
clicon_err(OE_XML, 0, "Malformed key: %s", xk);
goto done;
}
i = 1;
while (i<nvec){
name = vec[i]; /* E.g "x=1,2" -> name:x restval=1,2 */
if ((restval = index(name, '=')) != NULL){
*restval = '\0';
restval++;
}
if (i==1){
if (strlen(name)==0 && (op==OP_DELETE || op == OP_REMOVE)){
/* Special handling of "/" */
cprintf(ckey, "/");
break;
}
else
if ((y = yang_find_topnode(yspec, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", x?xml_name(x):"");
goto done;
}
}
else
if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
if ((op==OP_DELETE || op == OP_REMOVE) &&
y->ys_keyword == Y_LEAF &&
y->ys_parent->yn_keyword == Y_LIST &&
yang_key_match(y->ys_parent, y->ys_argument))
/* Special rule if key, dont write last key-name, rm whole*/;
else
cprintf(ckey, "/%s", name);
i++;
switch (y->ys_keyword){
case Y_LEAF_LIST:
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
cprintf(ckey, "=%s", restval);
break;
case Y_LIST:
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
if ((valvec = clicon_strsplit(restval, ",", &nvalvec, __FUNCTION__)) == NULL)
goto done;
if (cvec_len(cvk) != nvalvec){
clicon_err(OE_XML, errno, "List %s key length mismatch", name);
goto done;
}
cvi = NULL;
/* Iterate over individual yang keys */
j = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if (j)
cprintf(ckey, ",");
else
cprintf(ckey, "=");
val2 = valvec[j++];
cprintf(ckey, "%s", val2);
cbuf_reset(csubkey);
cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname);
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0)
goto done;
}
if (cvk){
cvec_free(cvk);
cvk = NULL;
}
break;
default:
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0)
goto done;
break;
}
}
xk = cbuf_get(ckey);
/* final key */
switch (op){
case OP_CREATE:
if ((exists = db_exists(filename, xk)) < 0)
goto done;
if (exists == 1){
clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk);
goto done;
}
case OP_MERGE:
case OP_REPLACE:
if (y->ys_keyword == Y_LEAF || y->ys_keyword == Y_LEAF_LIST){
if (db_set(filename, xk, val, val?strlen(val)+1:0) < 0)
goto done;
}
else
if (db_set(filename, xk, NULL, 0) < 0)
goto done;
break;
case OP_DELETE:
if ((exists = db_exists(filename, xk)) < 0)
goto done;
if (exists == 0){
clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", xk);
goto done;
}
case OP_REMOVE:
/* Read in complete database (this can be optimized) */
if ((crx = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(crx, "^%s.*$", xk);
if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0)
goto done;
for (i = 0; i < npairs; i++) {
if (db_del(filename, pairs[i].dp_key) < 0)
goto done;
}
break;
default:
break;
}
retval = 0;
done:
if (filename)
free(filename);
if (ckey)
cbuf_free(ckey);
if (csubkey)
cbuf_free(csubkey);
if (crx)
cbuf_free(crx);
if (cvk)
cvec_free(cvk);
unchunk_group(__FUNCTION__);
return retval;
}
/*! Raw dump of database, just keys and values, no xml interpretation /*! Raw dump of database, just keys and values, no xml interpretation
* @param[in] f File * @param[in] f File

View file

@ -57,7 +57,7 @@ EOF
fi fi
match=`echo "$ret" | grep -Eo "$expect"` match=`echo "$ret" | grep -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
err " err "\nExpected:\t\"$expect\"\nGot:\t\"$ret\""
fi fi
} }

View file

@ -22,6 +22,18 @@ sudo clixon_backend -If $clixon_cf
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err
fi fi
new "cli configure top"
expectfn "clixon_cli -1f $clixon_cf set interfaces" ""
new "cli show configuration top"
expectfn "clixon_cli -1f $clixon_cf show conf cli" "^interfaces$"
new "cli configure delete top"
expectfn "clixon_cli -1f $clixon_cf delete interfaces" ""
new "cli show configuration delete top"
expectfn "clixon_cli -1f $clixon_cf show conf cli" ""
new "cli configure" new "cli configure"
expectfn "clixon_cli -1f $clixon_cf set interfaces interface eth0" "" expectfn "clixon_cli -1f $clixon_cf set interfaces interface eth0" ""
@ -34,8 +46,21 @@ expectfn "clixon_cli -1f $clixon_cf -l o validate" "Missing mandatory variable"
new "cli configure more" new "cli configure more"
expectfn "clixon_cli -1f $clixon_cf set interfaces interface eth0 ipv4 address 1.2.3.4 prefix-length 24" "" expectfn "clixon_cli -1f $clixon_cf set interfaces interface eth0 ipv4 address 1.2.3.4 prefix-length 24" ""
expectfn "clixon_cli -1f $clixon_cf set interfaces interface eth0 description mydesc" ""
expectfn "clixon_cli -1f $clixon_cf set interfaces interface eth0 type bgp" "" expectfn "clixon_cli -1f $clixon_cf set interfaces interface eth0 type bgp" ""
new "cli show xpath description"
expectfn "clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" "<description>mydesc</description>"
new "cli delete description"
expectfn "clixon_cli -1f $clixon_cf -l o delete interfaces interface eth0 description mydesc"
new "cli show xpath no description"
expectfn "clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" ""
new "cli success validate"
expectfn "clixon_cli -1f $clixon_cf -l o validate" ""
new "cli commit" new "cli commit"
expectfn "clixon_cli -1f $clixon_cf -l o commit" "" expectfn "clixon_cli -1f $clixon_cf -l o commit" ""