Add python bindings via the --enable-python configure flag. Signed-off-by: Tom Hromatka <tom.hroma...@oracle.com> --- .github/actions/setup-libcgroup/action.yml | 6 +- .github/workflows/continuous-integration.yml | 9 + configure.ac | 16 ++ src/Makefile.am | 3 + src/python/.gitignore | 6 + src/python/Makefile.am | 56 ++++ src/python/cgroup.pxd | 64 +++++ src/python/libcgroup.pyx | 274 +++++++++++++++++++ src/python/setup.py | 47 ++++ 9 files changed, 479 insertions(+), 2 deletions(-) create mode 100644 src/python/.gitignore create mode 100644 src/python/Makefile.am create mode 100644 src/python/cgroup.pxd create mode 100644 src/python/libcgroup.pyx create mode 100755 src/python/setup.py
diff --git a/.github/actions/setup-libcgroup/action.yml b/.github/actions/setup-libcgroup/action.yml index fae416c8a410..01628b0a3886 100644 --- a/.github/actions/setup-libcgroup/action.yml +++ b/.github/actions/setup-libcgroup/action.yml @@ -26,13 +26,15 @@ runs: steps: - run: sudo apt-get update shell: bash - - run: sudo apt-get install libpam-dev lcov + - run: sudo apt-get install libpam-dev lcov python3-pip + shell: bash + - run: sudo pip install cython shell: bash - run: rm -fr tests/ shell: bash - run: ./bootstrap.sh shell: bash - - run: CFLAGS="$CFLAGS -g -O0 -Werror" ./configure --sysconfdir=/etc --localstatedir=/var --enable-code-coverage --enable-opaque-hierarchy="name=systemd" + - run: CFLAGS="$CFLAGS -g -O0 -Werror" ./configure --sysconfdir=/etc --localstatedir=/var --enable-code-coverage --enable-opaque-hierarchy="name=systemd" --enable-python shell: bash - run: make shell: bash diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 14fc04802f86..2ded6e9d3b2a 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -75,6 +75,9 @@ jobs: run: | # The cgroup v1 runner hosted by Github Actions doesn't allow # for exclusive cpusets. Thus, skip the cpuset automated test + pushd src/python/build/lib.* + export PYTHONPATH=$PYTHONPATH:$(pwd) + popd ./tests/ftests/ftests.py -l 10 -L ftests.log ./tests/ftests/ftests.py -l 10 -L ftests-nocontainer.log --skip 36 --no-container - name: Archive test logs @@ -125,6 +128,9 @@ jobs: uses: ./.github/actions/setup-libcgroup - name: Run functional tests run: | + pushd src/python/build/lib.* + export PYTHONPATH=$PYTHONPATH:$(pwd) + popd pushd tests/ftests make check popd @@ -169,6 +175,9 @@ jobs: uses: ./.github/actions/setup-libcgroup - name: Run functional tests run: | + pushd src/python/build/lib.* + export PYTHONPATH=$PYTHONPATH:$(pwd) + popd pushd tests/ftests make check popd diff --git a/configure.ac b/configure.ac index 2d10a527c1d4..84d7e688e4a7 100644 --- a/configure.ac +++ b/configure.ac @@ -75,6 +75,21 @@ AC_ARG_ENABLE([daemon], [with_daemon=true]) AM_CONDITIONAL([WITH_DAEMON], [test x$with_daemon = xtrue]) +AC_ARG_ENABLE([python], + [AS_HELP_STRING([--enable-python], + [build the python bindings, requires cython])]) +AS_IF([test "$enable_python" = yes], [ + # cython version check + AS_IF([test "$CYTHON_VER_MAJ" -eq 0 -a "$CYTHON_VER_MIN" -lt 29], [ + AC_MSG_ERROR([python bindings require cython 0.29 or higher]) + ]) + AM_PATH_PYTHON([3]) +]) +AM_CONDITIONAL([ENABLE_PYTHON], [test "$enable_python" = yes]) +AC_DEFINE_UNQUOTED([ENABLE_PYTHON], + [$(test "$enable_python" = yes && echo 1 || echo 0)], + [Python bindings build flag.]) + AC_ARG_ENABLE([initscript-install], [AS_HELP_STRING([--enable-initscript-install],[install init scripts [default=no]])], [ @@ -196,6 +211,7 @@ AC_CONFIG_FILES([Makefile src/daemon/Makefile src/tools/Makefile src/pam/Makefile + src/python/Makefile scripts/Makefile scripts/init.d/cgconfig scripts/init.d/cgred diff --git a/src/Makefile.am b/src/Makefile.am index 5227a0366072..176cf13c3333 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,6 +3,9 @@ BUILT_SOURCES = parse.c parse.h SUBDIRS = . daemon pam tools +if ENABLE_PYTHON +SUBDIRS += python +endif # generate parse.h from parse.y AM_YFLAGS = -d diff --git a/src/python/.gitignore b/src/python/.gitignore new file mode 100644 index 000000000000..8862a1395e13 --- /dev/null +++ b/src/python/.gitignore @@ -0,0 +1,6 @@ +build/ +Makefile +Makefile.in +__init__.py +*.c +*.swp diff --git a/src/python/Makefile.am b/src/python/Makefile.am new file mode 100644 index 000000000000..5c77add326bf --- /dev/null +++ b/src/python/Makefile.am @@ -0,0 +1,56 @@ +# +# Libcgroup Python Bindings +# +# Copyright (c) 2021 Oracle and/or its affiliates. +# Author: Tom Hromatka <tom.hroma...@oracle.com> +# + +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of version 2.1 of the GNU Lesser General Public License as +# published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, see <http://www.gnu.org/licenses>. +# +@CODE_COVERAGE_RULES@ + +PY_DISTUTILS = \ + VERSION_RELEASE="@PACKAGE_VERSION@" \ + CPPFLAGS="-I\${top_srcdir}/include ${AM_CPPFLAGS} ${CPPFLAGS}" \ + CFLAGS="$(CODE_COVERAGE_CFLAGS) ${AM_CFLAGS} ${CFLAGS}" \ + LDFLAGS="$(CODE_COVERAGE_LIBS) ${AM_LDFLAGS} ${LDFLAGS}" \ + ${PYTHON} ${srcdir}/setup.py + +# support silent builds +PY_BUILD_0 = @echo " PYTHON " $@; ${PY_DISTUTILS} -q build +PY_BUILD_1 = ${PY_DISTUTILS} build +PY_BUILD_ = ${PY_BUILD_0} +PY_BUILD = ${PY_BUILD_@AM_V@} + +PY_INSTALL = ${PY_DISTUTILS} install + +EXTRA_DIST = cgroup.pxd libcgroup.pyx setup.py + +all-local: build + +build: ../libcgroup.la cgroup.pxd libcgroup.pyx setup.py + [ ${srcdir} == ${builddir} ] || cp ${srcdir}/libcgroup.pyx ${builddir} + ${PY_BUILD} && touch build + +install-exec-local: build + ${PY_INSTALL} --install-lib=${DESTDIR}/${pyexecdir} \ + --record=${DESTDIR}/${pyexecdir}/install_files.txt + +uninstall-local: + cat ${DESTDIR}/${pyexecdir}/install_files.txt | xargs ${RM} -f + ${RM} -f ${DESTDIR}/${pyexecdir}/install_files.txt + +clean-local: + [ ${srcdir} == ${builddir} ] || ${RM} -f ${builddir}/libcgroup.pyx + ${RM} -rf libcgroup.c build diff --git a/src/python/cgroup.pxd b/src/python/cgroup.pxd new file mode 100644 index 000000000000..82fa2af91a2d --- /dev/null +++ b/src/python/cgroup.pxd @@ -0,0 +1,64 @@ +# +# Libcgroup Python Bindings +# +# Copyright (c) 2021 Oracle and/or its affiliates. +# Author: Tom Hromatka <tom.hroma...@oracle.com> +# + +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of version 2.1 of the GNU Lesser General Public License as +# published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, see <http://www.gnu.org/licenses>. +# + +# cython: language_level = 3str + +cdef extern from "libcgroup.h": + cdef struct cgroup: + pass + + cdef struct cgroup_controller: + pass + + cdef enum cg_version_t: + CGROUP_UNK + CGROUP_V1 + CGROUP_V2 + CGROUP_DISK + + cdef struct cgroup_library_version: + unsigned int major + unsigned int minor + unsigned int release + + int cgroup_init() + const cgroup_library_version * cgroup_version() + + cgroup *cgroup_new_cgroup(const char *name) + int cgroup_convert_cgroup(cgroup *out_cg, cg_version_t out_version, + cgroup *in_cg, cg_version_t in_version) + void cgroup_free(cgroup **cg) + + cgroup_controller *cgroup_add_controller(cgroup *cg, const char *name) + cgroup_controller *cgroup_get_controller(cgroup *cg, const char *name) + + int cgroup_add_value_string(cgroup_controller *cgc, const char *name, + const char *value) + int cgroup_get_value_string(cgroup_controller *cgc, const char *name, + char **value) + char *cgroup_get_value_name(cgroup_controller *cgc, int index) + int cgroup_get_value_name_count(cgroup_controller *cgc) + + int cgroup_cgxget(cgroup ** cg, cg_version_t version, + bint ignore_unmappable) + + int cgroup_cgxset(const cgroup * const cg, cg_version_t version, + bint ignore_unmappable) diff --git a/src/python/libcgroup.pyx b/src/python/libcgroup.pyx new file mode 100644 index 000000000000..cf43a5ad905d --- /dev/null +++ b/src/python/libcgroup.pyx @@ -0,0 +1,274 @@ +# +# Libcgroup Python Bindings +# +# Copyright (c) 2021 Oracle and/or its affiliates. +# Author: Tom Hromatka <tom.hroma...@oracle.com> +# + +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of version 2.1 of the GNU Lesser General Public License as +# published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, see <http://www.gnu.org/licenses>. +# + +# cython: language_level = 3str + +""" Python bindings for the libcgroup library +""" + +__author__ = 'Tom Hromatka <tom.hroma...@oracle.com>' +__date__ = "25 October 2021" + +cimport cgroup + +cdef class Version: + CGROUP_UNK = cgroup.CGROUP_UNK + CGROUP_V1 = cgroup.CGROUP_V1 + CGROUP_V2 = cgroup.CGROUP_V2 + CGROUP_DISK = cgroup.CGROUP_DISK + +def c_str(string): + return bytes(string, "ascii") + +def indent(in_str, cnt): + leading_indent = cnt * ' ' + return ''.join(leading_indent + line for line in in_str.splitlines(True)) + +class Controller: + def __init__(self, name): + self.name = name + # self.settings maps to + # struct control_value *values[CG_NV_MAX]; + self.settings = dict() + + def __str__(self): + out_str = "Controller {}\n".format(self.name) + + for setting_key in self.settings: + out_str += indent("{} = {}\n".format(setting_key, + self.settings[setting_key]), 4) + + return out_str + +cdef class Cgroup: + """ Python object representing a libcgroup cgroup """ + cdef cgroup.cgroup * _cgp + cdef public: + object name, controllers, version + + def __cinit__(self, name, version): + ret = cgroup.cgroup_init() + if ret != 0: + raise RuntimeError("Failed to initialize libcgroup: {}".format(ret)) + + self._cgp = cgroup.cgroup_new_cgroup(c_str(name)) + if self._cgp == NULL: + raise RuntimeError("Failed to create cgroup {}".format(name)) + + def __init__(self, name, version): + """Initialize this cgroup instance + + Arguments: + name - Name of this cgroup + version - Version of this cgroup + + Note: + Does not modify the cgroup sysfs. Does not read from the cgroup sysfs + """ + self.name = name + self.controllers = dict() + self.version = version + + def __str__(self): + out_str = "Cgroup {}\n".format(self.name) + for ctrl_key in self.controllers: + out_str += indent(str(self.controllers[ctrl_key]), 4) + + return out_str + + @staticmethod + def library_version(): + cdef const cgroup.cgroup_library_version * version + + version = cgroup.cgroup_version() + return [version.major, version.minor, version.release] + + def add_controller(self, ctrl_name): + """Add a controller to the Cgroup instance + + Arguments: + ctrl_name - name of the controller + + Description: + Adds a controller to the Cgroup instance + + Note: + Does not modify the cgroup sysfs + """ + cdef cgroup.cgroup_controller * cgcp + cdef cgroup.cgroup * cgp + + cgcp = cgroup.cgroup_add_controller(self._cgp, + c_str(ctrl_name)) + if cgcp == NULL: + raise RuntimeError("Failed to add controller {} to cgroup".format( + ctrl_name)) + + self.controllers[ctrl_name] = Controller(ctrl_name) + + def add_setting(self, setting_name, setting_value=None): + """Add a setting to the Cgroup/Controller instance + + Arguments: + setting_name - name of the cgroup setting, e.g. 'cpu.shares' + setting_value (optional) - value + + Description: + Adds a setting/value pair to the Cgroup/Controller instance + + Note: + Does not modify the cgroup sysfs + """ + cdef cgroup.cgroup_controller *cgcp + cdef char * value + + ctrl_name = setting_name.split('.')[0] + + cgcp = cgroup.cgroup_get_controller(self._cgp, + c_str(ctrl_name)) + if cgcp == NULL: + self.add_controller(ctrl_name) + + if setting_value == None: + ret = cgroup.cgroup_add_value_string(cgcp, + c_str(setting_name), NULL) + else: + ret = cgroup.cgroup_add_value_string(cgcp, + c_str(setting_name), c_str(setting_value)) + if ret != 0: + raise RuntimeError("Failed to add setting {}: {}".format( + setting_name, ret)) + + def _pythonize_cgroup(self): + """ + Given a populated self._cgp, populate the equivalent Python fields + """ + cdef char *setting_name + cdef char *setting_value + + for ctrlr_key in self.controllers: + cgcp = cgroup.cgroup_get_controller(self._cgp, + c_str(self.controllers[ctrlr_key].name)) + if cgcp == NULL: + raise RuntimeError("Failed to get controller {}".format( + ctrlr_key)) + + self.controllers[ctrlr_key] = Controller(ctrlr_key) + setting_cnt = cgroup.cgroup_get_value_name_count(cgcp) + + for i in range(0, setting_cnt): + setting_name = cgroup.cgroup_get_value_name(cgcp, i) + + ret = cgroup.cgroup_get_value_string(cgcp, + setting_name, &setting_value) + if ret != 0: + raise RuntimeError("Failed to get value {}: {}".format( + setting_name, ret)) + + name = setting_name.decode("ascii") + value = setting_value.decode("ascii").strip() + self.controllers[ctrlr_key].settings[name] = value + + def convert(self, out_version): + """Convert this cgroup to another cgroup version + + Arguments: + out_version - Version to convert to + + Return: + Returns the converted cgroup instance + + Description: + Convert this cgroup instance to a cgroup instance of a different + cgroup version + + Note: + Does not modify the cgroup sysfs. Does not read from the cgroup sysfs + """ + out_cgp = Cgroup(self.name, out_version) + ret = cgroup.cgroup_convert_cgroup(out_cgp._cgp, + out_version, self._cgp, self.version) + if ret != 0: + raise RuntimeError("Failed to convert cgroup: {}".format(ret)) + + for ctrlr_key in self.controllers: + out_cgp.controllers[ctrlr_key] = Controller(ctrlr_key) + + out_cgp._pythonize_cgroup() + + return out_cgp + + def cgxget(self, ignore_unmappable=False): + """Read the requested settings from the cgroup sysfs + + Arguments: + ignore_unmappable - Ignore cgroup settings that can't be converted + from one version to another + + Return: + Returns the cgroup instance that represents the settings read from + sysfs + + Description: + Given this cgroup instance, read the settings/values from the + cgroup sysfs. If the read was successful, the settings are + returned in the return value + + Note: + Reads from the cgroup sysfs + """ + cdef bint ignore + + if ignore_unmappable: + ignore = 1 + else: + ignore = 0 + + ret = cgroup.cgroup_cgxget(&self._cgp, self.version, ignore) + if ret != 0: + raise RuntimeError("cgxget failed: {}".format(ret)) + + self._pythonize_cgroup() + + def cgxset(self, ignore_unmappable=False): + """Write this cgroup to the cgroup sysfs + + Arguments: + ignore_unmappable - Ignore cgroup settings that can't be converted + from one version to another + + Description: + Write the settings/values in this cgroup instance to the cgroup sysfs + + Note: + Writes to the cgroup sysfs + """ + if ignore_unmappable: + ignore = 1 + else: + ignore = 0 + + ret = cgroup.cgroup_cgxset(self._cgp, self.version, ignore) + if ret != 0: + raise RuntimeError("cgxset failed: {}".format(ret)) + + def __dealloc__(self): + cgroup.cgroup_free(&self._cgp); diff --git a/src/python/setup.py b/src/python/setup.py new file mode 100755 index 000000000000..f4b95d4a1a80 --- /dev/null +++ b/src/python/setup.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +# +# Libcgroup Python Module Build Script +# + +# +# Copyright (c) 2021 Oracle and/or its affiliates. +# Author: Tom Hromatka <tom.hroma...@oracle.com> +# + +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of version 2.1 of the GNU Lesser General Public License as +# published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, see <http://www.gnu.org/licenses>. +# + +import os + +from distutils.core import setup +from distutils.extension import Extension +from Cython.Distutils import build_ext + +setup( + name = "libcgroup", + version = os.environ["VERSION_RELEASE"], + description = "Python bindings for libcgroup", + url = "https://github.com/libcgroup/libcgroup", + maintainer = "Tom Hromatka", + maintainer_email = "tom.hroma...@oracle.com", + license = "LGPLv2.1", + platforms = "Linux", + cmdclass = {'build_ext': build_ext}, + ext_modules = [ + Extension("libcgroup", ["libcgroup.pyx"], + # unable to handle libtool libraries directly + extra_objects=["../.libs/libcgroup.a"]) + ] +) -- 2.31.1 _______________________________________________ Libcg-devel mailing list Libcg-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/libcg-devel