pyapi FEATURE <edit-config> implementation
ncRPCEditConfig() method of the Session to send <edit-config> to the
NETCONF server.
diff --git a/python/examples/editconfig.py b/python/examples/editconfig.py
new file mode 100755
index 0000000..f405156
--- /dev/null
+++ b/python/examples/editconfig.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python3
+
+import sys
+import os
+import getpass
+import libyang as ly
+import netconf2 as nc
+
+def interactive_auth(name, instruct, prompt, data):
+ print(name)
+ return getpass.getpass(prompt)
+
+def password_auth(user, host, data):
+ return getpass.getpass((user if user else os.getlogin()) + '@' + host + ' password : ')
+
+def hostkey_check(hostname, state, keytype, hexa, priv):
+ return True
+
+#
+# get know where to connect
+#
+host = input("hostname: ")
+try:
+ port = int(input("port : "))
+except:
+ port = 0;
+user = input("username: ")
+
+#
+# set SSH settings
+#
+if user:
+ ssh = nc.SSH(username=user)
+else:
+ ssh = nc.SSH()
+ssh.setAuthInteractiveClb(interactive_auth)
+ssh.setAuthPasswordClb(password_auth)
+ssh.setAuthHostkeyCheckClb(hostkey_check)
+
+#
+# create NETCONF session to the server
+#
+try:
+ session = nc.Session(host, port, ssh)
+except Exception as e:
+ print(e)
+ sys.exit(1)
+
+# prepare config content as string or data tree
+tm = session.context.get_module("turing-machine")
+# config = "<turing-machine xmlns=\"http://example.net/turing-machine\"><transition-function><delta><label>left summand</label><input><state>0</state></input></delta></transition-function></turing-machine>"
+config = ly.Data_Node(session.context, "/turing-machine:turing-machine/transition-function/delta[label='left summand']/input/state", "5", 0, 0)
+
+# perform <edit-config> and print result
+try:
+ session.rpcEditConfig(nc.DATASTORE_RUNNING, config)
+except nc.ReplyError as e:
+ reply = {'success':False, 'error': []}
+ for err in e.args[0]:
+ reply['error'].append(json.loads(str(err)))
+ print(json.dumps(reply))
+ sys.exit(1)
+
+# print(data.print_mem(ly.LYD_XML, ly.LYP_FORMAT | ly.LYP_WITHSIBLINGS))
diff --git a/python/examples/get.py b/python/examples/get.py
index 5fa0bc4..9ccfb73 100755
--- a/python/examples/get.py
+++ b/python/examples/get.py
@@ -13,6 +13,9 @@
def password_auth(user, host, data):
return getpass.getpass((user if user else os.getlogin()) + '@' + host + ' password : ')
+def hostkey_check(hostname, state, keytype, hexa, priv):
+ return True
+
#
# get know where to connect
#
@@ -32,6 +35,7 @@
ssh = nc.SSH()
ssh.setAuthInteractiveClb(interactive_auth)
ssh.setAuthPasswordClb(password_auth)
+ssh.setAuthHostkeyCheckClb(hostkey_check)
#
# create NETCONF session to the server
diff --git a/python/netconf.c b/python/netconf.c
index e51e532..5b990fc 100644
--- a/python/netconf.c
+++ b/python/netconf.c
@@ -240,6 +240,18 @@
PyModule_AddIntConstant(nc, "DATASTORE_STARTUP", NC_DATASTORE_STARTUP);
PyModule_AddIntConstant(nc, "DATASTORE_CANDIDATE", NC_DATASTORE_CANDIDATE);
+ PyModule_AddIntConstant(nc, "RPC_EDIT_ERROPT_STOP", NC_RPC_EDIT_ERROPT_STOP);
+ PyModule_AddIntConstant(nc, "RPC_EDIT_ERROPT_CONTINUE", NC_RPC_EDIT_ERROPT_CONTINUE);
+ PyModule_AddIntConstant(nc, "RPC_EDIT_ERROPT_ROLLBACK", NC_RPC_EDIT_ERROPT_ROLLBACK);
+
+ PyModule_AddIntConstant(nc, "RPC_EDIT_TESTOPT_TESTSET", NC_RPC_EDIT_TESTOPT_TESTSET);
+ PyModule_AddIntConstant(nc, "RPC_EDIT_TESTOPT_SET", NC_RPC_EDIT_TESTOPT_SET);
+ PyModule_AddIntConstant(nc, "RPC_EDIT_TESTOPT_TEST", NC_RPC_EDIT_TESTOPT_TEST);
+
+ PyModule_AddIntConstant(nc, "RPC_EDIT_DFLTOP_MERGE", NC_RPC_EDIT_DFLTOP_MERGE);
+ PyModule_AddIntConstant(nc, "RPC_EDIT_DFLTOP_REPLACE", NC_RPC_EDIT_DFLTOP_REPLACE);
+ PyModule_AddIntConstant(nc, "RPC_EDIT_DFLTOP_NONE", NC_RPC_EDIT_DFLTOP_NONE);
+
/* init libnetconf exceptions for use in clb_print() */
libnetconf2Error = PyErr_NewExceptionWithDoc("netconf2.Error",
"Error passed from the underlying libnetconf2 library.",
diff --git a/python/rpc.c b/python/rpc.c
index ec7bd69..92109e7 100644
--- a/python/rpc.c
+++ b/python/rpc.c
@@ -31,6 +31,11 @@
extern PyObject *libnetconf2Error;
extern PyObject *libnetconf2ReplyError;
+static const char *ncds2str[] = {NULL, "config", "url", "running", "startup", "candidate"};
+const char *rpcedit_dfltop2str[] = {NULL, "merge", "replace", "none"};
+const char *rpcedit_testopt2str[] = {NULL, "test-then-set", "set", "test-only"};
+const char *rpcedit_erropt2str[] = {NULL, "stop-on-error", "continue-on-error", "rollback-on-error"};
+
static struct nc_reply *
rpc_send_recv(struct nc_session *session, struct nc_rpc *rpc)
{
@@ -190,3 +195,109 @@
return process_reply_data(reply);
}
+
+PyObject *
+ncRPCEditConfig(ncSessionObject *self, PyObject *args, PyObject *keywords)
+{
+ static char *kwlist[] = {"datastore", "data", "defop", "testopt", "erropt", NULL};
+ struct lyd_node *data = NULL, *node, *content_tree = NULL;
+ char *content_str = NULL;
+ const struct lys_module *ietfnc;
+ NC_DATASTORE datastore;
+ NC_RPC_EDIT_DFLTOP defop = 0;
+ NC_RPC_EDIT_TESTOPT testopt = 0;
+ NC_RPC_EDIT_ERROPT erropt = 0;
+ PyObject *content_o = NULL, *py_lyd_node;
+ struct nc_rpc *rpc;
+ struct nc_reply *reply;
+
+ ietfnc = ly_ctx_get_module(self->ctx, "ietf-netconf", NULL, 1);
+ if (!ietfnc) {
+ PyErr_SetString(libnetconf2Error, "Missing \"ietf-netconf\" schema in the context.");
+ return NULL;
+ }
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords, "iO|iii:ncRPCEditConfig", kwlist, &datastore, &content_o, &defop, &testopt, &erropt)) {
+ return NULL;
+ }
+
+ if (PyUnicode_Check(content_o)) {
+ content_str = PyUnicode_AsUTF8(content_o);
+ } else if (!strcmp(Py_TYPE(content_o)->tp_name, "Data_Node")) {
+ py_lyd_node = PyObject_CallMethod(content_o, "C_lyd_node", NULL);
+ if (!SWIG_IsOK(SWIG_Python_ConvertPtr(py_lyd_node, (void**)&content_tree, SWIG_Python_TypeQuery("lyd_node *"), SWIG_POINTER_DISOWN))) {
+ PyErr_SetString(PyExc_TypeError, "Invalid object representing <edit-config> content. Data_Node is accepted.");
+ goto error;
+ }
+ } else if (content_o != Py_None) {
+ PyErr_SetString(PyExc_TypeError, "Invalid object representing <edit-config> content. String or Data_Node is accepted.");
+ goto error;
+ }
+
+ data = lyd_new(NULL, ietfnc, "edit-config");
+ node = lyd_new(data, ietfnc, "target");
+ node = lyd_new_leaf(node, ietfnc, ncds2str[datastore], NULL);
+ if (!node) {
+ goto error;
+ }
+
+ if (defop) {
+ node = lyd_new_leaf(data, ietfnc, "default-operation", rpcedit_dfltop2str[defop]);
+ if (!node) {
+ goto error;
+ }
+ }
+
+ if (testopt) {
+ node = lyd_new_leaf(data, ietfnc, "test-option", rpcedit_testopt2str[testopt]);
+ if (!node) {
+ goto error;
+ }
+ }
+
+ if (erropt) {
+ node = lyd_new_leaf(data, ietfnc, "error-option", rpcedit_erropt2str[erropt]);
+ if (!node) {
+ goto error;
+ }
+ }
+
+ if (content_str) {
+ if (!content_str[0] || (content_str[0] == '<')) {
+ node = lyd_new_anydata(data, ietfnc, "config", content_str, LYD_ANYDATA_SXML);
+ } else {
+ node = lyd_new_leaf(data, ietfnc, "url", content_str);
+ }
+ } else if (content_tree) {
+ node = lyd_new_anydata(data, ietfnc, "config", content_tree, LYD_ANYDATA_DATATREE);
+ }
+ if (!node) {
+ goto error;
+ }
+
+ rpc = nc_rpc_act_generic(data, NC_PARAMTYPE_FREE);
+ data = NULL;
+ if (!rpc) {
+ goto error;
+ }
+
+ reply = rpc_send_recv(self->session, rpc);
+ nc_rpc_free(rpc);
+ if (!reply) {
+ goto error;
+ }
+ if (reply->type != NC_RPL_OK) {
+ if (reply->type == NC_RPL_ERROR) {
+ RAISE_REPLY_ERROR(reply);
+ } else {
+ PyErr_SetString(libnetconf2Error, "Unexpected reply received.");
+ }
+ goto error;
+ }
+
+ Py_RETURN_NONE;
+
+error:
+ lyd_free(data);
+ return NULL;
+}
diff --git a/python/rpc.h b/python/rpc.h
index 262325e..9eb95da 100644
--- a/python/rpc.h
+++ b/python/rpc.h
@@ -21,6 +21,7 @@
PyObject *ncRPCGet(ncSSHObject *self, PyObject *args, PyObject *keywords);
PyObject *ncRPCGetConfig(ncSSHObject *self, PyObject *args, PyObject *keywords);
+PyObject *ncRPCEditConfig(ncSSHObject *self, PyObject *args, PyObject *keywords);
#ifdef __cplusplus
}
diff --git a/python/session.c b/python/session.c
index 1e78b24..e11b033 100644
--- a/python/session.c
+++ b/python/session.c
@@ -487,10 +487,14 @@
"Send NETCONF <get> operation on the Session.\n\n"
"ncRPCGet(subtree=None, xpath=None)\n"
":returns: Reply from the server.\n"},
- {"rpcGetConfig", (PyCFunction)ncRPCGetConfig, METH_VARARGS | METH_KEYWORDS,
- "Send NETCONF <get-config> operation on the Session.\n\n"
- "ncRPCGetConfig(datastore, subtree=None, xpath=None)\n"
- ":returns: Reply from the server.\n"},
+ {"rpcGetConfig", (PyCFunction)ncRPCGetConfig, METH_VARARGS | METH_KEYWORDS,
+ "Send NETCONF <get-config> operation on the Session.\n\n"
+ "ncRPCGetConfig(datastore, subtree=None, xpath=None)\n"
+ ":returns: Reply from the server.\n"},
+ {"rpcEditConfig", (PyCFunction)ncRPCEditConfig, METH_VARARGS | METH_KEYWORDS,
+ "Send NETCONF <edit-config> operation on the Session.\n\n"
+ "ncRPCEditConfig(datastore, data, defop=None, testopt=None, erropt=None)\n"
+ ":returns: None\n"},
{NULL} /* Sentinel */
};