-
Notifications
You must be signed in to change notification settings - Fork 345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Start adding a more useful python module around libpacemaker #3813
base: main
Are you sure you want to change the base?
Changes from all commits
b312ae2
3d8a15e
dc06f40
4daf00b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
"""A module providing exceptions that can be raised by the Pacemaker module.""" | ||
|
||
__all__ = ["PacemakerError"] | ||
__copyright__ = "Copyright 2025 the Pacemaker project contributors" | ||
__license__ = "GNU Lesser General Public License version 2.1 or later (LGPLv2.1+)" | ||
|
||
|
||
class PacemakerError(Exception): | ||
"""Base exception class for all Pacemaker errors.""" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
* Copyright 2025 the Pacemaker project contributors | ||
* | ||
* The version control history for this file may have further details. | ||
* | ||
* This source code is licensed under the GNU Lesser General Public License | ||
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. | ||
*/ | ||
|
||
#include <Python.h> | ||
#include <libxml/tree.h> | ||
|
||
#include <pacemaker.h> | ||
|
||
/* This file defines a c-based low level module that wraps libpacemaker | ||
* functions and returns python objects. This is necessary because most | ||
* libpacemaker functions return an xmlNode **, which needs to be coerced | ||
* through the PyCapsule type into something that libxml2's python | ||
* bindings can work with. | ||
*/ | ||
|
||
/* Base exception class for any errors in the _pcmksupport module */ | ||
static PyObject *PacemakerError; | ||
|
||
PyMODINIT_FUNC PyInit__pcmksupport(void); | ||
|
||
static PyObject * | ||
py_list_standards(PyObject *self, PyObject *args) | ||
{ | ||
int rc; | ||
xmlNodePtr xml = NULL; | ||
|
||
if (!PyArg_ParseTuple(args, "")) { | ||
return NULL; | ||
} | ||
|
||
rc = pcmk_list_standards(&xml); | ||
if (rc != pcmk_rc_ok) { | ||
PyErr_SetString(PacemakerError, pcmk_rc_str(rc)); | ||
return NULL; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am unsure what to do here - on the one hand, |
||
} | ||
|
||
return PyCapsule_New(xml, "xmlNodePtr", NULL); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I mentioned in the commit message, I think these functions could almost certainly be auto-generated by a quick script based on a single line description of their name, argument types, and return value. |
||
static PyMethodDef pcmksupportMethods[] = { | ||
{ "list_standards", py_list_standards, METH_VARARGS, NULL }, | ||
{ NULL, NULL, 0, NULL } | ||
}; | ||
|
||
static struct PyModuleDef moduledef = { | ||
PyModuleDef_HEAD_INIT, | ||
"_pcmksupport", | ||
NULL, | ||
-1, | ||
pcmksupportMethods, | ||
NULL, | ||
NULL, | ||
NULL, | ||
NULL | ||
}; | ||
|
||
PyMODINIT_FUNC | ||
PyInit__pcmksupport(void) | ||
{ | ||
PyObject *module = PyModule_Create(&moduledef); | ||
|
||
if (module == NULL) { | ||
return NULL; | ||
} | ||
|
||
/* Add the base exception to the module */ | ||
PacemakerError = PyErr_NewException("_pcmksupport.PacemakerError", NULL, NULL); | ||
|
||
/* FIXME: When we can support Python >= 3.10, we can use PyModule_AddObjectRef */ | ||
if (PyModule_AddObject(module, "PacemakerError", PacemakerError) < 0) { | ||
Py_XDECREF(PacemakerError); | ||
return NULL; | ||
} | ||
|
||
return module; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
"""A module for managing cluster resources.""" | ||
|
||
__all__ = ["list_standards"] | ||
__copyright__ = "Copyright 2025 the Pacemaker project contributors" | ||
__license__ = "GNU Lesser General Public License version 2.1 or later (LGPLv2.1+)" | ||
|
||
import libxml2 | ||
|
||
import _pcmksupport | ||
from pacemaker.exceptions import PacemakerError | ||
|
||
|
||
def list_standards(): | ||
"""Return a list of supported resource standards.""" | ||
try: | ||
xml = _pcmksupport.list_standards() | ||
except _pcmksupport.PacemakerError as e: | ||
raise PacemakerError(*e.args) from None | ||
|
||
doc = libxml2.xmlDoc(xml) | ||
|
||
return [item.getContent() for item in doc.xpathEval("/pacemaker-result/standards/item")] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The intention here (and in any further functions) would be to convert the returned XML into a python native type, like a list of strings here. That's what I'm going through so much trouble everywhere to deal with the XML types. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the advantage (or necessity) of using PyCapsule, etc., rather than
ctypes
orcffi
for our use case?I have not created Python bindings myself. Before I do any more research, I'm wondering what you've found on this topic and what led you to PyCapsule.
Note from the Python documentation (first URL):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So far, using PyCapsule is the only way I've been able to make anything that doesn't just segfault.
Here's what I want:
xmlNodePtr *
.Basically, I want to take something that was allocated and generated by libxml2 in C (the
xmlNodePtr *
) and pass it through our own python code and libxml2's python bindings, back into libxml2 in C during the process of converting the types. There's something about this process that just segfaults unless I use the PyCapsule type. Almost everything else in this PR is just boilerplate necessary to be able to use that type.My first approach was to use ctypes just like we are doing for ExitStatus, but I couldn't get any result besides segfaults there. I think what we'd need to do is define a structure class that mirrors an xmlNode, which seems like way too much work. That's why I turned to this approach. I haven't tried the ffi module yet.
Honestly, I would love to nuke the entire C module portion of this and do everything in python with ctypes/ffi. I just can't figure it out yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For instance:
It feels really close - in particular, I don't think the void pointer type is correct here. But I'm not sure how to hand it an xmlNodePtr. ctypes really only knows about basic types. Beyond that, you're supposed to define classes that derive from Structure. But, an xmlNode comes from libxml2 so I don't want to define a class for that and I don't see anywhere that they define one.