From 00a022563689dc0ba6ac53bf04c65870d6a2e986 Mon Sep 17 00:00:00 2001 From: Thierry FOURNIER Date: Sun, 25 Feb 2018 20:59:57 +0100 Subject: [PATCH] MINOR: spoa-server: Add python This commit adds the Python support for the server. --- contrib/spoa_server/Makefile | 6 + contrib/spoa_server/ps_python.c | 644 ++++++++++++++++++++++++++++++++ 2 files changed, 650 insertions(+) create mode 100644 contrib/spoa_server/ps_python.c diff --git a/contrib/spoa_server/Makefile b/contrib/spoa_server/Makefile index 31e3de587..f0752829e 100644 --- a/contrib/spoa_server/Makefile +++ b/contrib/spoa_server/Makefile @@ -21,6 +21,12 @@ endif LDLIBS += -ldl -Wl,--export-dynamic -llua -lm -Wl,--no-export-dynamic endif +ifneq ($(USE_PYTHON),) +OBJS += ps_python.o +CFLAGS += -I/usr/include/python2.7 +LDLIBS += -lpython2.7 +endif + spoa: $(OBJS) $(LD) $(LDFLAGS) -o $@ $^ $(LDLIBS) diff --git a/contrib/spoa_server/ps_python.c b/contrib/spoa_server/ps_python.c new file mode 100644 index 000000000..0a9fbffc6 --- /dev/null +++ b/contrib/spoa_server/ps_python.c @@ -0,0 +1,644 @@ +/* spoa-server: processing Python + * + * Copyright 2018 OZON / Thierry Fournier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include + +#include + +#include +#include + +#include "spoa.h" + +/* Embedding python documentation: + * + * https://docs.python.org/2/extending/embedding.html + * https://docs.python.org/2/extending/extending.html#extending-python-with-c-or-c + * https://docs.python.org/2/extending/extending.html#calling-python-functions-from-c + */ + +static PyObject *module_ipaddress; +static PyObject *ipv4_address; +static PyObject *ipv6_address; +static PyObject *spoa_error; +static PyObject *empty_array; +static struct worker *worker; + +static int ps_python_start_worker(struct worker *w); +static int ps_python_load_file(struct worker *w, const char *file); +static int ps_python_exec_message(struct worker *w, void *ref, int nargs, struct spoe_kv *args); + +static struct ps ps_python_bindings = { + .init_worker = ps_python_start_worker, + .load_file = ps_python_load_file, + .exec_message = ps_python_exec_message, + .ext = ".py", +}; + +static PyObject *ps_python_register_message(PyObject *self, PyObject *args) +{ + const char *name; + PyObject *ref; + + if (!PyArg_ParseTuple(args, "sO!", &name, &PyFunction_Type, &ref)) + return NULL; + Py_XINCREF(ref); /* because the function is intenally refrenced */ + + ps_register_message(&ps_python_bindings, name, (void *)ref); + + return Py_None; +} + +static PyObject *ps_python_set_var_null(PyObject *self, PyObject *args) +{ + const char *name; + int name_len; + int scope; + + if (!PyArg_ParseTuple(args, "s#i", &name, &name_len, &scope)) + return NULL; + if (!set_var_null(worker, name, name_len, scope)) { + PyErr_SetString(spoa_error, "No space left available"); + return NULL; + } + return Py_None; +} + +static PyObject *ps_python_set_var_boolean(PyObject *self, PyObject *args) +{ + const char *name; + int name_len; + int scope; + int value; + + if (!PyArg_ParseTuple(args, "s#ii", &name, &name_len, &scope, &value)) + return NULL; + if (!set_var_bool(worker, name, name_len, scope, value)) { + PyErr_SetString(spoa_error, "No space left available"); + return NULL; + } + return Py_None; +} + +static PyObject *ps_python_set_var_int32(PyObject *self, PyObject *args) +{ + const char *name; + int name_len; + int scope; + int32_t value; + + if (!PyArg_ParseTuple(args, "s#ii", &name, &name_len, &scope, &value)) + return NULL; + if (!set_var_int32(worker, name, name_len, scope, value)) { + PyErr_SetString(spoa_error, "No space left available"); + return NULL; + } + return Py_None; +} + +static PyObject *ps_python_set_var_uint32(PyObject *self, PyObject *args) +{ + const char *name; + int name_len; + int scope; + uint32_t value; + + if (!PyArg_ParseTuple(args, "s#iI", &name, &name_len, &scope, &value)) + return NULL; + if (!set_var_uint32(worker, name, name_len, scope, value)) { + PyErr_SetString(spoa_error, "No space left available"); + return NULL; + } + return Py_None; +} + +static PyObject *ps_python_set_var_int64(PyObject *self, PyObject *args) +{ + const char *name; + int name_len; + int scope; + int64_t value; + + if (!PyArg_ParseTuple(args, "s#il", &name, &name_len, &scope, &value)) + return NULL; + if (!set_var_int64(worker, name, name_len, scope, value)) { + PyErr_SetString(spoa_error, "No space left available"); + return NULL; + } + return Py_None; +} + +static PyObject *ps_python_set_var_uint64(PyObject *self, PyObject *args) +{ + const char *name; + int name_len; + int scope; + uint64_t value; + + if (!PyArg_ParseTuple(args, "s#ik", &name, &name_len, &scope, &value)) + return NULL; + if (!set_var_uint64(worker, name, name_len, scope, value)) { + PyErr_SetString(spoa_error, "No space left available"); + return NULL; + } + return Py_None; +} + +static PyObject *ps_python_set_var_ipv4(PyObject *self, PyObject *args) +{ + const char *name; + int name_len; + int scope; + PyObject *ipv4; + PyObject *value; + struct in_addr ip; + + if (!PyArg_ParseTuple(args, "s#iO", &name, &name_len, &scope, &ipv4)) + return NULL; + if (!PyObject_IsInstance(ipv4, ipv4_address)) { + PyErr_Format(spoa_error, "must be 'IPv4Address', not '%s'", ipv4->ob_type->tp_name); + return NULL; + } + /* Execute packed ... I think .. */ + value = PyObject_GetAttrString(ipv4, "packed"); + if (value == NULL) + return NULL; + if (PyString_GET_SIZE(value) != sizeof(ip)) { + PyErr_Format(spoa_error, "UPv6 manipulation internal error"); + return NULL; + } + memcpy(&ip, PyString_AS_STRING(value), PyString_GET_SIZE(value)); + if (!set_var_ipv4(worker, name, name_len, scope, &ip)) { + PyErr_SetString(spoa_error, "No space left available"); + return NULL; + } + return Py_None; +} + +static PyObject *ps_python_set_var_ipv6(PyObject *self, PyObject *args) +{ + const char *name; + int name_len; + int scope; + PyObject *ipv6; + PyObject *value; + struct in6_addr ip; + + if (!PyArg_ParseTuple(args, "s#iO", &name, &name_len, &scope, &ipv6)) + return NULL; + if (!PyObject_IsInstance(ipv6, ipv6_address)) { + PyErr_Format(spoa_error, "must be 'IPv6Address', not '%s'", ipv6->ob_type->tp_name); + return NULL; + } + /* Execute packed ... I think .. */ + value = PyObject_GetAttrString(ipv6, "packed"); + if (value == NULL) + return NULL; + if (PyString_GET_SIZE(value) != sizeof(ip)) { + PyErr_Format(spoa_error, "UPv6 manipulation internal error"); + return NULL; + } + memcpy(&ip, PyString_AS_STRING(value), PyString_GET_SIZE(value)); + if (!set_var_ipv6(worker, name, name_len, scope, &ip)) { + PyErr_SetString(spoa_error, "No space left available"); + return NULL; + } + return Py_None; +} + +static PyObject *ps_python_set_var_str(PyObject *self, PyObject *args) +{ + const char *name; + int name_len; + int scope; + const char *value; + int value_len; + + if (!PyArg_ParseTuple(args, "s#is#", &name, &name_len, &scope, &value, &value_len)) + return NULL; + if (!set_var_string(worker, name, name_len, scope, value, value_len)) { + PyErr_SetString(spoa_error, "No space left available"); + return NULL; + } + return Py_None; +} + +static PyObject *ps_python_set_var_bin(PyObject *self, PyObject *args) +{ + const char *name; + int name_len; + int scope; + const char *value; + int value_len; + + if (!PyArg_ParseTuple(args, "s#is#", &name, &name_len, &scope, &value, &value_len)) + return NULL; + if (!set_var_bin(worker, name, name_len, scope, value, value_len)) { + PyErr_SetString(spoa_error, "No space left available"); + return NULL; + } + return Py_None; +} + + +static PyMethodDef spoa_methods[] = { + {"register_message", ps_python_register_message, METH_VARARGS, + "Register binding for SPOA message."}, + {"set_var_null", ps_python_set_var_null, METH_VARARGS, + "Set SPOA NULL variable"}, + {"set_var_boolean", ps_python_set_var_boolean, METH_VARARGS, + "Set SPOA boolean variable"}, + {"set_var_int32", ps_python_set_var_int32, METH_VARARGS, + "Set SPOA int32 variable"}, + {"set_var_uint32", ps_python_set_var_uint32, METH_VARARGS, + "Set SPOA uint32 variable"}, + {"set_var_int64", ps_python_set_var_int64, METH_VARARGS, + "Set SPOA int64 variable"}, + {"set_var_uint64", ps_python_set_var_uint64, METH_VARARGS, + "Set SPOA uint64 variable"}, + {"set_var_ipv4", ps_python_set_var_ipv4, METH_VARARGS, + "Set SPOA ipv4 variable"}, + {"set_var_ipv6", ps_python_set_var_ipv6, METH_VARARGS, + "Set SPOA ipv6 variable"}, + {"set_var_str", ps_python_set_var_str, METH_VARARGS, + "Set SPOA str variable"}, + {"set_var_bin", ps_python_set_var_bin, METH_VARARGS, + "Set SPOA bin variable"}, + { /* end */ } +}; + +static int ps_python_start_worker(struct worker *w) +{ + PyObject *m; + PyObject *module_name; + PyObject *value; + int ret; + + Py_SetProgramName("spoa-server"); + Py_Initialize(); + + module_name = PyString_FromString("ipaddress"); + if (module_name == NULL) { + PyErr_Print(); + return 0; + } + + module_ipaddress = PyImport_Import(module_name); + Py_DECREF(module_name); + if (module_ipaddress == NULL) { + PyErr_Print(); + return 0; + } + + ipv4_address = PyObject_GetAttrString(module_ipaddress, "IPv4Address"); + if (ipv4_address == NULL) { + PyErr_Print(); + return 0; + } + + ipv6_address = PyObject_GetAttrString(module_ipaddress, "IPv6Address"); + if (ipv4_address == NULL) { + PyErr_Print(); + return 0; + } + + m = Py_InitModule("spoa", spoa_methods); + if (m == NULL) { + PyErr_Print(); + return 0; + } + + spoa_error = PyErr_NewException("spoa.error", NULL, NULL); + Py_INCREF(spoa_error); + PyModule_AddObject(m, "error", spoa_error); + + + value = PyLong_FromLong(SPOE_SCOPE_PROC); + if (value == NULL) { + PyErr_Print(); + return 0; + } + + ret = PyModule_AddObject(m, "scope_proc", value); + if (ret == -1) { + PyErr_Print(); + return 0; + } + + value = PyLong_FromLong(SPOE_SCOPE_SESS); + if (value == NULL) { + PyErr_Print(); + return 0; + } + + ret = PyModule_AddObject(m, "scope_sess", value); + if (ret == -1) { + PyErr_Print(); + return 0; + } + + value = PyLong_FromLong(SPOE_SCOPE_TXN); + if (value == NULL) { + PyErr_Print(); + return 0; + } + + ret = PyModule_AddObject(m, "scope_txn", value); + if (ret == -1) { + PyErr_Print(); + return 0; + } + + value = PyLong_FromLong(SPOE_SCOPE_REQ); + if (value == NULL) { + PyErr_Print(); + return 0; + } + + ret = PyModule_AddObject(m, "scope_req", value); + if (ret == -1) { + PyErr_Print(); + return 0; + } + + value = PyLong_FromLong(SPOE_SCOPE_RES); + if (value == NULL) { + PyErr_Print(); + return 0; + } + + ret = PyModule_AddObject(m, "scope_res", value); + if (ret == -1) { + PyErr_Print(); + return 0; + } + + empty_array = PyDict_New(); + if (empty_array == NULL) { + PyErr_Print(); + return 0; + } + + worker = w; + return 1; +} + +static int ps_python_load_file(struct worker *w, const char *file) +{ + FILE *fp; + int ret; + + fp = fopen(file, "r"); + if (fp == NULL) { + LOG("python: Cannot read file \"%s\": %s", file, strerror(errno)); + return 0; + } + + ret = PyRun_SimpleFile(fp, file); + fclose(fp); + if (ret != 0) { + PyErr_Print(); + return 0; + } + + return 1; +} + +static int ps_python_exec_message(struct worker *w, void *ref, int nargs, struct spoe_kv *args) +{ + int i; + PyObject *python_ref = ref; + PyObject *fkw; + PyObject *kw_args; + PyObject *result; + PyObject *ent; + PyObject *key; + PyObject *value; + PyObject *func; + int ret; + char ipbuf[64]; + const char *p; + PyObject *ip_dict; + PyObject *ip_name; + PyObject *ip_value; + + /* Dict containing arguments */ + + kw_args = PyList_New(0); + if (kw_args == NULL) { + PyErr_Print(); + return 0; + } + + for (i = 0; i < nargs; i++) { + + /* New dict containing one argument */ + + ent = PyDict_New(); + if (ent == NULL) { + Py_DECREF(kw_args); + Py_DECREF(ent); + PyErr_Print(); + return 0; + } + + /* Create the name entry */ + + key = PyString_FromString("name"); + if (key == NULL) { + Py_DECREF(kw_args); + PyErr_Print(); + return 0; + } + + value = PyString_FromStringAndSize(args[i].name.str, args[i].name.len); + if (value == NULL) { + Py_DECREF(kw_args); + Py_DECREF(ent); + Py_DECREF(key); + PyErr_Print(); + return 0; + } + + ret = PyDict_SetItem(ent, key, value); + Py_DECREF(key); + Py_DECREF(value); + if (ret == -1) { + Py_DECREF(kw_args); + Py_DECREF(ent); + PyErr_Print(); + return 0; + } + + /* Create th value entry */ + + key = PyString_FromString("value"); + if (key == NULL) { + Py_DECREF(kw_args); + Py_DECREF(ent); + PyErr_Print(); + return 0; + } + + switch (args[i].value.type) { + case SPOE_DATA_T_NULL: + value = Py_None; + break; + case SPOE_DATA_T_BOOL: + value = PyBool_FromLong(args[i].value.u.boolean); + break; + case SPOE_DATA_T_INT32: + value = PyLong_FromLong(args[i].value.u.sint32); + break; + case SPOE_DATA_T_UINT32: + value = PyLong_FromLong(args[i].value.u.uint32); + break; + case SPOE_DATA_T_INT64: + value = PyLong_FromLong(args[i].value.u.sint64); + break; + case SPOE_DATA_T_UINT64: + value = PyLong_FromUnsignedLong(args[i].value.u.uint64); + break; + case SPOE_DATA_T_IPV4: + case SPOE_DATA_T_IPV6: + if (args[i].value.type == SPOE_DATA_T_IPV4) + p = inet_ntop(AF_INET, &args[i].value.u.ipv4, ipbuf, 64); + else + p = inet_ntop(AF_INET6, &args[i].value.u.ipv6, ipbuf, 64); + if (!p) + strcpy(ipbuf, "0.0.0.0"); + + func = PyObject_GetAttrString(module_ipaddress, "ip_address"); + if (func == NULL) { + Py_DECREF(kw_args); + Py_DECREF(ent); + PyErr_Print(); + return 0; + } + ip_dict = PyDict_New(); + if (ip_dict == NULL) { + Py_DECREF(kw_args); + Py_DECREF(ent); + Py_DECREF(func); + PyErr_Print(); + return 0; + } + ip_name = PyString_FromString("address"); + if (ip_name == NULL) { + Py_DECREF(kw_args); + Py_DECREF(ent); + Py_DECREF(func); + Py_DECREF(ip_dict); + PyErr_Print(); + return 0; + } + ip_value = PyUnicode_FromString(ipbuf); + if (ip_value == NULL) { + Py_DECREF(kw_args); + Py_DECREF(ent); + Py_DECREF(func); + Py_DECREF(ip_dict); + Py_DECREF(ip_name); + PyErr_Print(); + return 0; + } + ret = PyDict_SetItem(ip_dict, ip_name, ip_value); + Py_DECREF(ip_name); + Py_DECREF(ip_value); + if (ret == -1) { + Py_DECREF(ip_dict); + PyErr_Print(); + return 0; + } + value = PyObject_Call(func, empty_array, ip_dict); + Py_DECREF(func); + Py_DECREF(ip_dict); + break; + + case SPOE_DATA_T_STR: + value = PyString_FromStringAndSize(args[i].value.u.buffer.str, args[i].value.u.buffer.len); + break; + case SPOE_DATA_T_BIN: + value = PyString_FromStringAndSize(args[i].value.u.buffer.str, args[i].value.u.buffer.len); + break; + default: + value = Py_None; + break; + } + if (value == NULL) { + Py_DECREF(kw_args); + Py_DECREF(ent); + Py_DECREF(key); + PyErr_Print(); + return 0; + } + + ret = PyDict_SetItem(ent, key, value); + Py_DECREF(key); + Py_DECREF(value); + if (ret == -1) { + Py_DECREF(kw_args); + Py_DECREF(ent); + PyErr_Print(); + return 0; + } + + /* Add dict to the list */ + + ret = PyList_Append(kw_args, ent); + Py_DECREF(ent); + if (ret == -1) { + Py_DECREF(kw_args); + PyErr_Print(); + return 0; + } + } + + /* Dictionnary { args = } for the function */ + + fkw = PyDict_New(); + if (fkw == NULL) { + Py_DECREF(kw_args); + PyErr_Print(); + return 0; + } + + key = PyString_FromString("args"); + if (key == NULL) { + Py_DECREF(kw_args); + Py_DECREF(fkw); + PyErr_Print(); + return 0; + } + + ret = PyDict_SetItem(fkw, key, kw_args); + Py_DECREF(kw_args); + Py_DECREF(key); + if (ret == -1) { + Py_DECREF(fkw); + PyErr_Print(); + return 0; + } + + result = PyObject_Call(python_ref, empty_array, fkw); + if (result == NULL) { + PyErr_Print(); + return 0; + } + + return 1; +} + +__attribute__((constructor)) +static void __ps_python_init(void) +{ + ps_register(&ps_python_bindings); +}