blob: df4cfc68a8970affa2bca74d48b7d70cd3984909 [file] [log] [blame]
Radek Krejcic61f0b42017-06-07 13:21:41 +02001/**
2 * @file netconf.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @brief Python3 bindings for libnetconf2 (client-side)
5 *
6 * Copyright (c) 2017 CESNET, z.s.p.o.
7 *
8 * This source code is licensed under BSD 3-Clause License (the "License").
9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * https://opensource.org/licenses/BSD-3-Clause
13 */
14
15/* Python API header */
16#include <Python.h>
17
18/* standard headers */
Radek Krejcic61f0b42017-06-07 13:21:41 +020019#include <syslog.h>
20
21#include "netconf.h"
22
23PyObject *libnetconf2Error;
24PyObject *libnetconf2Warning;
Radek Krejci0f3499a2017-10-13 13:39:36 +020025PyObject *libnetconf2ReplyError;
Radek Krejcic61f0b42017-06-07 13:21:41 +020026
27/* syslog usage flag */
28static int syslogEnabled = 0;
29
Radek Krejci4bb7bbb2018-08-29 14:48:55 +020030/* libyang schema callback */
31static PyObject *schemaCallback = NULL;
32static void *schemaCallbackData = NULL;
33
Radek Krejcic61f0b42017-06-07 13:21:41 +020034static void
35clb_print(NC_VERB_LEVEL level, const char* msg)
36{
Radek Krejci0242d842017-10-13 16:12:16 +020037 switch (level) {
38 case NC_VERB_ERROR:
39 PyErr_SetString(libnetconf2Error, msg);
40 if (syslogEnabled) {
41 syslog(LOG_ERR, "%s", msg);
42 }
43 break;
44 case NC_VERB_WARNING:
45 if (syslogEnabled) {
46 syslog(LOG_WARNING, "%s", msg);
47 }
48 PyErr_WarnEx(libnetconf2Warning, msg, 1);
49 break;
50 case NC_VERB_VERBOSE:
51 if (syslogEnabled) {
52 syslog(LOG_INFO, "%s", msg);
53 }
54 break;
55 case NC_VERB_DEBUG:
56 if (syslogEnabled) {
57 syslog(LOG_DEBUG, "%s", msg);
58 }
59 break;
60 }
Radek Krejcic61f0b42017-06-07 13:21:41 +020061}
62
63static PyObject *
64setSyslog(PyObject *self, PyObject *args, PyObject *keywds)
65{
Radek Krejci0242d842017-10-13 16:12:16 +020066 char* name = NULL;
67 static char* logname = NULL;
68 static int option = LOG_PID;
69 static int facility = LOG_USER;
Radek Krejcic61f0b42017-06-07 13:21:41 +020070
Radek Krejci0242d842017-10-13 16:12:16 +020071 static char *kwlist[] = {"enabled", "name", "option", "facility", NULL};
Radek Krejcic61f0b42017-06-07 13:21:41 +020072
Radek Krejci0242d842017-10-13 16:12:16 +020073 if (!PyArg_ParseTupleAndKeywords(args, keywds, "p|sii", kwlist, &syslogEnabled, &name, &option, &facility)) {
74 return NULL;
75 }
Radek Krejcic61f0b42017-06-07 13:21:41 +020076
Radek Krejci0242d842017-10-13 16:12:16 +020077 if (name) {
78 free(logname);
79 logname = strdup(name);
80 } else {
81 free(logname);
82 logname = NULL;
83 }
84 closelog();
85 openlog(logname, option, facility);
Radek Krejcic61f0b42017-06-07 13:21:41 +020086
Radek Krejci0242d842017-10-13 16:12:16 +020087 Py_RETURN_NONE;
Radek Krejcic61f0b42017-06-07 13:21:41 +020088}
89
90static PyObject *
91setVerbosity(PyObject *self, PyObject *args, PyObject *keywds)
92{
Radek Krejci0242d842017-10-13 16:12:16 +020093 int level = NC_VERB_ERROR; /* 0 */
Radek Krejcic61f0b42017-06-07 13:21:41 +020094
Radek Krejci0242d842017-10-13 16:12:16 +020095 static char *kwlist[] = {"level", NULL};
Radek Krejcic61f0b42017-06-07 13:21:41 +020096
Radek Krejci0242d842017-10-13 16:12:16 +020097 if (!PyArg_ParseTupleAndKeywords(args, keywds, "i", kwlist, &level)) {
98 return NULL;
99 }
Radek Krejcic61f0b42017-06-07 13:21:41 +0200100
Radek Krejci0242d842017-10-13 16:12:16 +0200101 /* normalize level value if not from the enum */
102 if (level < NC_VERB_ERROR) {
103 nc_verbosity(NC_VERB_ERROR);
104 } else if (level > NC_VERB_DEBUG) {
105 nc_verbosity(NC_VERB_DEBUG);
106 } else {
107 nc_verbosity(level);
108 }
Radek Krejcic61f0b42017-06-07 13:21:41 +0200109
Radek Krejci0242d842017-10-13 16:12:16 +0200110 Py_RETURN_NONE;
Radek Krejcic61f0b42017-06-07 13:21:41 +0200111}
112
Radek Krejci74ca4812017-09-21 13:03:33 +0200113static PyObject *
114setSearchpath(PyObject *self, PyObject *args, PyObject *keywds)
115{
116 char *path;
117 static char *kwlist[] = {"path", NULL};
118
119 if (!PyArg_ParseTupleAndKeywords(args, keywds, "s", kwlist, &path)) {
120 return NULL;
121 }
122
123 if (nc_client_set_schema_searchpath(path)) {
124 return NULL;
125 }
126
127 Py_RETURN_NONE;
128}
129
Radek Krejci4bb7bbb2018-08-29 14:48:55 +0200130char *
131schemaCallbackWrapper(const char *mod_name, const char *mod_rev, const char *submod_name, const char *sub_rev,
132 void *user_data, LYS_INFORMAT *format, void (**free_module_data)(void *model_data))
133{
134 PyObject *arglist, *result, *data = NULL;
135 char *str = NULL;
136
137 arglist = Py_BuildValue("(ssssO)", mod_name, mod_rev, submod_name, sub_rev, schemaCallbackData ? schemaCallbackData : Py_None);
138 if (!arglist) {
139 PyErr_Print();
140 return NULL;
141 }
142 result = PyObject_CallObject(schemaCallback, arglist);
143 Py_DECREF(arglist);
144
145 if (result) {
146 if (!PyArg_ParseTuple(result, "iU", format, &data)) {
147 Py_DECREF(result);
148 return NULL;
149 }
150 Py_DECREF(result);
151 *free_module_data = free;
152 str = strdup(PyUnicode_AsUTF8(data));
153 Py_DECREF(data);
154 }
155
156 return str;
157}
158
159static PyObject *
160setSchemaCallback(PyObject *self, PyObject *args, PyObject *keywds)
161{
162 PyObject *clb = NULL, *data = NULL;
163 static char *kwlist[] = {"func", "priv", NULL};
164
165 if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|O:setSchemaCallback", kwlist, &clb, &data)) {
166 return NULL;
167 }
168
169 if (!clb || clb == Py_None) {
170 Py_XDECREF(schemaCallback);
171 Py_XDECREF(schemaCallbackData);
172 data = NULL;
173 } else if (!PyCallable_Check(clb)) {
174 PyErr_SetString(PyExc_TypeError, "The callback must be a function.");
175 return NULL;
176 } else {
177 Py_XDECREF(schemaCallback);
178 Py_XDECREF(schemaCallbackData);
179
180 Py_INCREF(clb);
181 if (data) {
182 Py_INCREF(data);
183 }
184 }
185 nc_client_set_schema_callback(schemaCallbackWrapper, NULL);
186 schemaCallback = clb;
187 schemaCallbackData = data;
188
189 Py_RETURN_NONE;
190}
191
Radek Krejcic61f0b42017-06-07 13:21:41 +0200192static PyMethodDef netconf2Methods[] = {
Radek Krejci0242d842017-10-13 16:12:16 +0200193 {"setVerbosity", (PyCFunction)setVerbosity, METH_VARARGS | METH_KEYWORDS,
194 "setVerbosity(level)\n--\n\n"
195 "Set verbose level\n\n"
196 ":param level: Verbosity level (0 - errors, 1 - warnings, 2 - verbose, 3 - debug)\n"
197 ":type level: int\n"
198 ":returns: None\n"},
199 {"setSyslog", (PyCFunction)setSyslog, METH_VARARGS | METH_KEYWORDS,
200 "setSyslog(enabled[, name=None][, option=LOG_PID][, facility=LOG_USER])\n--\n\n"
201 "Set application settings for syslog.\n\n"
202 ":param enabled: Flag to enable/disable logging into syslog.\n"
203 ":type enabled: bool\n"
204 ":param name: Identifier (program name is set by default).\n"
205 ":type name: string\n"
206 ":param option: ORed value of syslog options (LOG_PID by default).\n"
207 ":type option: int\n"
208 ":param facility: Type of the program logging the message (LOG_USER by default).\n"
209 ":type facility: int\n"
210 ":returns: None\n\n"
211 ".. seealso:: syslog.openlog\n"},
212 {"setSearchpath", (PyCFunction)setSearchpath, METH_VARARGS | METH_KEYWORDS,
213 "setSearchpath(path)\n--\n\n"
214 "Set location where YANG/YIN schemas are searched and where the schemas\n"
215 "retrieved via <get-schema> opration are stored.\n\n"
216 ":param path: Search directory.\n"
217 ":type path: string\n"
218 ":returns: None\n"},
Radek Krejci4bb7bbb2018-08-29 14:48:55 +0200219 {"setSchemaCallback", (PyCFunction)setSchemaCallback, METH_VARARGS | METH_KEYWORDS,
220 "Set schema search callaback.\n\n"
221 "setSchemaCallback(func, priv=None)\n"
222 "with func(str mod_name, str mod_rev, str submod_name, str submod_rev, priv)\n"
223 "callback returns tuple of format (e.g. LYS_IN_YANG) and string of the schema content.\n"},
Radek Krejci0242d842017-10-13 16:12:16 +0200224 {NULL, NULL, 0, NULL}
Radek Krejcic61f0b42017-06-07 13:21:41 +0200225};
226
Radek Krejcib03ebe42017-07-04 14:00:33 +0200227static char netconf2Docs[] =
228 "NETCONF Protocol client-side implementation using libnetconf2\n"
229 "\n"
230 "netconf2 is a wrapper around libnetconf2 functions designed for NETCONF\n"
231 "clients. it provides a higher level API than the original libnetconf2 to\n"
232 "better fit the usage in Python.\n";
233
Radek Krejcic61f0b42017-06-07 13:21:41 +0200234static struct PyModuleDef ncModule = {
Radek Krejci0242d842017-10-13 16:12:16 +0200235 PyModuleDef_HEAD_INIT,
236 "netconf2",
237 netconf2Docs,
238 -1,
239 netconf2Methods,
Radek Krejcic61f0b42017-06-07 13:21:41 +0200240};
241
242/* module initializer */
243PyMODINIT_FUNC
244PyInit_netconf2(void)
245{
Radek Krejcie205eec2018-02-15 16:44:03 +0100246 void* clb;
Radek Krejci0242d842017-10-13 16:12:16 +0200247 PyObject *nc;
Radek Krejcic61f0b42017-06-07 13:21:41 +0200248
Radek Krejci97211012017-10-13 16:05:45 +0200249 /* import libyang Python module to have it available */
Radek Krejcieed7f872018-02-13 16:57:42 +0100250 if (!PyImport_ImportModule("yang")) {
Radek Krejci97211012017-10-13 16:05:45 +0200251 return NULL;
252 }
253
Radek Krejci0242d842017-10-13 16:12:16 +0200254 /* initiate libnetconf2 client part */
255 nc_client_init();
Radek Krejcic61f0b42017-06-07 13:21:41 +0200256
Radek Krejci0242d842017-10-13 16:12:16 +0200257 /* set schema searchpath
258 * nc_client_set_schema_searchpath()
259 */
Radek Krejcic61f0b42017-06-07 13:21:41 +0200260
Radek Krejci0242d842017-10-13 16:12:16 +0200261 /* set print callback */
Radek Krejcie205eec2018-02-15 16:44:03 +0100262 clb = ly_get_log_clb();
Radek Krejci0242d842017-10-13 16:12:16 +0200263 nc_set_print_clb(clb_print);
Radek Krejcie205eec2018-02-15 16:44:03 +0100264 ly_set_log_clb(clb, 1);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200265
266 if (PyType_Ready(&ncSessionType) == -1) {
267 return NULL;
268 }
269
270 ncSSHType.tp_new = PyType_GenericNew;
271 if (PyType_Ready(&ncSSHType) == -1) {
272 return NULL;
273 }
Radek Krejcia2429d12017-10-13 13:37:24 +0200274/*
Radek Krejcic61f0b42017-06-07 13:21:41 +0200275 ncTLSType.tp_new = PyType_GenericNew;
276 if (PyType_Ready(&ncTLSType) == -1) {
277 return NULL;
278 }
Radek Krejcia2429d12017-10-13 13:37:24 +0200279*/
Radek Krejci0f3499a2017-10-13 13:39:36 +0200280 ncErrType.tp_new = PyType_GenericNew;
281 if (PyType_Ready(&ncErrType) == -1) {
282 return NULL;
283 }
Radek Krejcic61f0b42017-06-07 13:21:41 +0200284
Radek Krejci0242d842017-10-13 16:12:16 +0200285 /* create netconf as the Python module */
286 nc = PyModule_Create(&ncModule);
287 if (nc == NULL) {
288 return NULL;
289 }
Radek Krejcic61f0b42017-06-07 13:21:41 +0200290
291 Py_INCREF(&ncSSHType);
292 PyModule_AddObject(nc, "SSH", (PyObject *)&ncSSHType);
Radek Krejcia2429d12017-10-13 13:37:24 +0200293/*
Radek Krejcic61f0b42017-06-07 13:21:41 +0200294 Py_INCREF(&ncTLSType);
295 PyModule_AddObject(nc, "TLS", (PyObject *)&ncTLSType);
Radek Krejcia2429d12017-10-13 13:37:24 +0200296*/
Radek Krejci0f3499a2017-10-13 13:39:36 +0200297 Py_INCREF(&ncErrType);
298 PyModule_AddObject(nc, "ReplyErrorInfo", (PyObject *)&ncErrType);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200299
300 Py_INCREF(&ncSessionType);
301 PyModule_AddObject(nc, "Session", (PyObject *)&ncSessionType);
302
303/*
304 Py_INCREF(&ncTLSType);
305 PyModule_AddObject(nc, "TLS", (PyObject *)&ncTLSType);
306*/
307/*
308 PyModule_AddStringConstant(nc, "NETCONFv1_0", NETCONF_CAP_BASE10);
309 PyModule_AddStringConstant(nc, "NETCONFv1_1", NETCONF_CAP_BASE11);
310 PyModule_AddStringConstant(nc, "TRANSPORT_SSH", NETCONF_TRANSPORT_SSH);
311 PyModule_AddStringConstant(nc, "TRANSPORT_TLS", NETCONF_TRANSPORT_TLS);
312*/
Radek Krejcie0854c02017-10-10 21:11:29 +0200313 PyModule_AddIntConstant(nc, "DATASTORE_RUNNING", NC_DATASTORE_RUNNING);
314 PyModule_AddIntConstant(nc, "DATASTORE_STARTUP", NC_DATASTORE_STARTUP);
315 PyModule_AddIntConstant(nc, "DATASTORE_CANDIDATE", NC_DATASTORE_CANDIDATE);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200316
Radek Krejcib0bf24c2018-02-07 16:40:27 +0100317 PyModule_AddIntConstant(nc, "RPC_EDIT_ERROPT_STOP", NC_RPC_EDIT_ERROPT_STOP);
318 PyModule_AddIntConstant(nc, "RPC_EDIT_ERROPT_CONTINUE", NC_RPC_EDIT_ERROPT_CONTINUE);
319 PyModule_AddIntConstant(nc, "RPC_EDIT_ERROPT_ROLLBACK", NC_RPC_EDIT_ERROPT_ROLLBACK);
320
321 PyModule_AddIntConstant(nc, "RPC_EDIT_TESTOPT_TESTSET", NC_RPC_EDIT_TESTOPT_TESTSET);
322 PyModule_AddIntConstant(nc, "RPC_EDIT_TESTOPT_SET", NC_RPC_EDIT_TESTOPT_SET);
323 PyModule_AddIntConstant(nc, "RPC_EDIT_TESTOPT_TEST", NC_RPC_EDIT_TESTOPT_TEST);
324
325 PyModule_AddIntConstant(nc, "RPC_EDIT_DFLTOP_MERGE", NC_RPC_EDIT_DFLTOP_MERGE);
326 PyModule_AddIntConstant(nc, "RPC_EDIT_DFLTOP_REPLACE", NC_RPC_EDIT_DFLTOP_REPLACE);
327 PyModule_AddIntConstant(nc, "RPC_EDIT_DFLTOP_NONE", NC_RPC_EDIT_DFLTOP_NONE);
328
Radek Krejci0242d842017-10-13 16:12:16 +0200329 /* init libnetconf exceptions for use in clb_print() */
330 libnetconf2Error = PyErr_NewExceptionWithDoc("netconf2.Error",
331 "Error passed from the underlying libnetconf2 library.",
332 NULL, NULL);
333 Py_INCREF(libnetconf2Error);
334 PyModule_AddObject(nc, "Error", libnetconf2Error);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200335
Radek Krejci0242d842017-10-13 16:12:16 +0200336 libnetconf2Warning = PyErr_NewExceptionWithDoc("netconf2.Warning",
337 "Warning passed from the underlying libnetconf2 library.",
338 PyExc_Warning, NULL);
339 Py_INCREF(libnetconf2Warning);
340 PyModule_AddObject(nc, "Warning", libnetconf2Warning);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200341
Radek Krejci0f3499a2017-10-13 13:39:36 +0200342 libnetconf2ReplyError = PyErr_NewExceptionWithDoc("netconf2.ReplyError",
Radek Krejci0242d842017-10-13 16:12:16 +0200343 "NETCONF error returned from the server.",
344 NULL, NULL);
Radek Krejci0f3499a2017-10-13 13:39:36 +0200345 Py_INCREF(libnetconf2ReplyError);
346 PyModule_AddObject(nc, "ReplyError", libnetconf2ReplyError);
347
Radek Krejci0242d842017-10-13 16:12:16 +0200348 return nc;
Radek Krejcic61f0b42017-06-07 13:21:41 +0200349}