]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: spoa-server: Add python
authorThierry FOURNIER <thierry.fournier@ozon.io>
Sun, 25 Feb 2018 19:59:57 +0000 (20:59 +0100)
committerWilly Tarreau <w@1wt.eu>
Mon, 13 May 2019 15:43:47 +0000 (17:43 +0200)
This commit adds the Python support for the server.

contrib/spoa_server/Makefile
contrib/spoa_server/ps_python.c [new file with mode: 0644]

index 31e3de58748d117ae61dd8f66f1f46d1f8878d99..f0752829e3aa7968636a4cf0f1141ced00aab174 100644 (file)
@@ -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 (file)
index 0000000..0a9fbff
--- /dev/null
@@ -0,0 +1,644 @@
+/* spoa-server: processing Python
+ *
+ * Copyright 2018 OZON / Thierry Fournier <thierry.fournier@ozon.io>
+ *
+ * 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 <Python.h>
+
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <string.h>
+
+#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 = <list-of-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);
+}