diff --git a/centos_iso_image.inc b/centos_iso_image.inc index dd87294..35634e2 100644 --- a/centos_iso_image.inc +++ b/centos_iso_image.inc @@ -10,3 +10,6 @@ monitor-tools # vm-topology vm-topology + +# kube-cpusets +kube-cpusets diff --git a/centos_pkg_dirs b/centos_pkg_dirs index 3003956..d678a8f 100644 --- a/centos_pkg_dirs +++ b/centos_pkg_dirs @@ -2,3 +2,4 @@ collectd-extensions influxdb-extensions monitor-tools vm-topology +kube-cpusets diff --git a/kube-cpusets/LICENSE b/kube-cpusets/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/kube-cpusets/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/kube-cpusets/PKG-INFO b/kube-cpusets/PKG-INFO new file mode 100644 index 0000000..fa058a3 --- /dev/null +++ b/kube-cpusets/PKG-INFO @@ -0,0 +1,12 @@ +Metadata-Version: 1.1 +Name: kube-cpusets +Version: 1.0 +Summary: Display kubernetes containers cpusets per numa-node +Home-page: +Author: Windriver +Author-email: info@windriver.com +License: Apache-2.0 + +Description: Display kubernetes containers cpusets per numa-node + +Platform: UNKNOWN diff --git a/kube-cpusets/centos/build_srpm.data b/kube-cpusets/centos/build_srpm.data new file mode 100644 index 0000000..8d7bf17 --- /dev/null +++ b/kube-cpusets/centos/build_srpm.data @@ -0,0 +1,4 @@ +PACKAGE_NAME=kube-cpusets +VERSION=1.0 +SRC_DIR=$PKG_BASE/$PACKAGE_NAME +TIS_PATCH_VER=PKG_GITREVCOUNT diff --git a/kube-cpusets/centos/kube-cpusets.spec b/kube-cpusets/centos/kube-cpusets.spec new file mode 100644 index 0000000..f9dc613 --- /dev/null +++ b/kube-cpusets/centos/kube-cpusets.spec @@ -0,0 +1,67 @@ +%global pypi_name kube-cpusets + +Summary: Display kubernetes containers cpusets per numa node +Name: kube-cpusets +Version: 1.0 +Release: %{tis_patch_ver}%{?_tis_dist} +License: Apache-2.0 +Group: base +Packager: Wind River + +URL: unknown +Source0: %{pypi_name}-%{version}.tar.gz + +BuildArch: noarch + +BuildRequires: python +BuildRequires: python-setuptools +BuildRequires: python2-pip +BuildRequires: python2-wheel + +Requires: python +Requires: python-devel +Requires: /usr/bin/env +Requires: /bin/bash + +%define debug_package %{nil} + +%description +Display kubernetes containers cpusets per numa node + +%define pythonroot /usr/lib64/python2.7/site-packages + +%prep +%autosetup -p 1 -n %{pypi_name}-%{version} +# Remove bundled egg-info +rm -rf %{pypi_name}.egg-info +# Let RPM handle the dependencies +rm -f requirements.txt +rm -f test-requirements.txt + +%build +%{__python2} setup.py build +%py2_build_wheel + +%install +%{__python2} setup.py install --skip-build --root %{buildroot} +mkdir -p $RPM_BUILD_ROOT/wheels +install -m 644 dist/*.whl $RPM_BUILD_ROOT/wheels/ + +%files +%defattr(-,root,root,-) +%doc LICENSE +%{_bindir}/kube-cpusets +%{python2_sitelib}/kube_cpusets +%{python2_sitelib}/*.egg-info + +%package wheels +Summary: %{name} wheels + +%description wheels +Contains python wheels for %{name} + +%files wheels +/wheels/* + +%clean +rm -rf $RPM_BUILD_ROOT diff --git a/kube-cpusets/kube-cpusets/.coveragerc b/kube-cpusets/kube-cpusets/.coveragerc new file mode 100644 index 0000000..25a3155 --- /dev/null +++ b/kube-cpusets/kube-cpusets/.coveragerc @@ -0,0 +1,7 @@ +[run] +branch = True +source = kube_cpusets +omit = kube_cpusets/tests/* + +[report] +ignore_errors = True diff --git a/kube-cpusets/kube-cpusets/.stestr.conf b/kube-cpusets/kube-cpusets/.stestr.conf new file mode 100644 index 0000000..6bf8740 --- /dev/null +++ b/kube-cpusets/kube-cpusets/.stestr.conf @@ -0,0 +1,2 @@ +[DEFAULT] +test_path=kube_cpusets/tests diff --git a/kube-cpusets/kube-cpusets/LICENSE b/kube-cpusets/kube-cpusets/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/kube-cpusets/kube-cpusets/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/kube-cpusets/kube-cpusets/kube_cpusets/__init__.py b/kube-cpusets/kube-cpusets/kube_cpusets/__init__.py new file mode 100644 index 0000000..865d450 --- /dev/null +++ b/kube-cpusets/kube-cpusets/kube_cpusets/__init__.py @@ -0,0 +1,6 @@ +""" +Copyright (c) 2020 Wind River Systems, Inc. + +SPDX-License-Identifier: Apache-2.0 + +""" diff --git a/kube-cpusets/kube-cpusets/kube_cpusets/kube_cpusets.py b/kube-cpusets/kube-cpusets/kube_cpusets/kube_cpusets.py new file mode 100755 index 0000000..4e55912 --- /dev/null +++ b/kube-cpusets/kube-cpusets/kube_cpusets/kube_cpusets.py @@ -0,0 +1,474 @@ +# +# Copyright (c) 2020 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +""" +This tool gathers cpuset usage information for all kubernetes containers +that are running on the current host. + +With kubernetes CPUManager policy 'none', the k8s-infra cpuset is used for +all pods. For policy 'static', pods get an exclusive cpuset in the case of +QoS Guaranteed or using isolcpus, otherwise pods inherit DefaultCPUSet. + +This displays the cpusets per container and the mapping to numa nodes. +This displays the aggregate cpuset usage per system-level groupings +(i.e., platform, isolated, guaranteed, default), per numa-node. + +Usage: kube-cpusets [-h] [--debug] +""" + +import argparse +import itertools as it +import json +import logging +import logging.handlers +import os +import re +import socket +import subprocess +import sys + +import prettytable + +# Constants +statefile = '/var/lib/kubelet/cpu_manager_state' +platformconf = '/etc/platform/platform.conf' +reservedfile = '/etc/platform/worker_reserved.conf' + +LOG = logging.getLogger(__name__) + + +def format_range_set(items): + """Generate a pretty-printed value of ranges from a set of integers. + + e.g., given a set or list of integers, format as '3-6,8-9,12-17' + """ + ranges = [] + for _, iterable in it.groupby(enumerate(sorted(items)), + lambda x: x[1] - x[0]): + rng = list(iterable) + if len(rng) == 1: + s = str(rng[0][1]) + else: + s = "%s-%s" % (rng[0][1], rng[-1][1]) + ranges.append(s) + return ','.join(ranges) + + +def range_to_list(csv_range=None): + """Convert a string of comma separate ranges into an expanded list. + + e.g., '1-3,8-9,15' is converted to [1,2,3,8,9,15] + """ + if not csv_range: + return [] + ranges = [(lambda L: range(L[0], L[-1] + 1))(map(int, r.split('-'))) + for r in csv_range.split(',')] + return [y for x in ranges for y in x] + + +def cpuset_from_cpulist_file(filename): + """Read cpulist file and convert to set of integers. + + File containing comma separated ranges is converted to an expanded set + of integers. e.g., '1-3,8-9,15' is converted to set([1,2,3,8,9,15]) + """ + cpuset_str = None + try: + with open(filename, 'r') as f: + cpuset_str = f.readline().strip() + except Exception as e: + LOG.error('Cannot parse file:%s, error=%s', filename, e) + cpuset = set(range_to_list(csv_range=cpuset_str)) + return cpuset + + +def get_isolated_cpuset(): + """Get isolated cpuset from sys devices. + + Reads sys devices isolated cpu file containing comma separated ranges + and convert to an expanded set of integers. + """ + filename = '/sys/devices/system/cpu/isolated' + cpuset = cpuset_from_cpulist_file(filename) + return cpuset + + +def get_online_cpuset(): + """Get online cpuset from sys devices. + + Reads sys devices online cpu file containing comma separated ranges + and convert to an expanded set of integers. + """ + filename = '/sys/devices/system/cpu/online' + cpuset = cpuset_from_cpulist_file(filename) + return cpuset + + +def get_k8sinfra_cpuset(): + """Get cgroup k8s-infra cpuset from sys fs cgroup. + + Reads sys fs cgroup k8s-infra cpuset.cpus file containing comma + separated ranges and convert to an expanded set of integers. + """ + filename = '/sys/fs/cgroup/cpuset/k8s-infra/cpuset.cpus' + cpuset = cpuset_from_cpulist_file(filename) + return cpuset + + +def get_node_cpusets(): + """Get cpusets per numa node from sys devices. + + Returns a list of nodes with the set of integers per numa node. + """ + nodepath = '/sys/devices/system/node' + re_node = re.compile(r'^node(\d+)$') + nodes = {} + if os.path.isdir(nodepath): + for d in os.listdir(nodepath): + match = re_node.search(d) + if match: + node = int(match.group(1)) + filename = nodepath + '/node' + str(node) + '/cpulist' + cpuset = set() + if os.path.isfile(filename): + cpuset = cpuset_from_cpulist_file(filename) + nodes[node] = cpuset + return nodes + + +def gather_info_and_display(): + """Gather cpuset information for all kubernetes containers. + + Display the cpuset per container and the mapping to numa nodes. + Display the aggregate cpuset usage per system-level groupings, + per-numa node. + """ + hostname = socket.gethostname() + + # Read current host cpusets from sysfs + node_cpusets = get_node_cpusets() + isolated_cpuset = get_isolated_cpuset() + online_cpuset = get_online_cpuset() + k8sinfra_cpuset = get_k8sinfra_cpuset() + LOG.debug('node_cpusets = %r', node_cpusets) + LOG.debug('isolated_cpuset = %r', isolated_cpuset) + LOG.debug('online_cpuset = %r', online_cpuset) + LOG.debug('k8sinfra_cpuset = %r', k8sinfra_cpuset) + + # Obtain platform node configuration + re_keyval = re.compile(r'^(\S+)\s*=\s*(\S+)') + platconf = {} + try: + with open(platformconf, 'r') as f: + for line in f: + m = re.search(re_keyval, line) + if m: + key = m.group(1) + value = m.group(2) + platconf[key] = value + except Exception as e: + LOG.error('Could not parse: %s, error: %s.', platformconf, e) + return 1 + nodetype = platconf.get('nodetype') + subfunction = platconf.get('subfunction') + system_type = platconf.get('system_type') + + # Obtain platform cpuset for worker node, as configured by sysinv/puppet. + re_platform = re.compile(r'^PLATFORM_CPU_LIST\s*=\s*\"(\S+)\"') + if 'worker' in subfunction: + cpulist_str = None + try: + with open(reservedfile, 'r') as f: + for line in f: + m = re.search(re_platform, line) + if m: + cpulist_str = m.group(1) + except Exception as e: + LOG.error('Could not parse: %s, error: %s.', reservedfile, e) + platform_cpuset = set(range_to_list(csv_range=cpulist_str)) + else: + platform_cpuset = online_cpuset - isolated_cpuset + LOG.debug('platform_cpuset = %r', platform_cpuset) + + # Read cpusets from kubelet cpumanager JSON state file dictionary + state = {} + try: + with open(statefile, 'r') as f: + state = json.load(f) + except Exception as e: + LOG.error('Could not load: %s, error: %s.', statefile, e) + return 1 + LOG.debug('cpu-manager state = %r', state) + + # Obtain cpu-manager policy + policy = str(state['policyName']) + + # Print tool header line + LOG.info('host:%s, system_type=%s, nodetype=%s, subfunction=%s, ' + 'cpumanager_policy=%s', + hostname, system_type, nodetype, subfunction, policy) + + # Determine default cpu-manager cpuset + if 'defaultCpuSet' not in state: + LOG.error('Missing defaultCpuSet in %s', statefile) + return 1 + default_cpuranges = str(state['defaultCpuSet']) + default_cpuset = set(range_to_list(csv_range=default_cpuranges)) + LOG.debug('default_cpuset = %r', default_cpuset) + + # Determine aggregate of cpumanager static allocations, + # i.e., this contains: platform, guaranteed, isolated . + static_cpuset = set() + if 'entries' in state: + for _, dcpus in state['entries'].items(): + for cpus in [str(i) for i in dcpus.values()]: + cpulist = set(range_to_list(csv_range=cpus)) + static_cpuset.update(cpulist) + + # Determine guaranteed cpuset + guaranteed_cpuset = static_cpuset - platform_cpuset - isolated_cpuset + LOG.debug('guaranteed_cpuset = %r', guaranteed_cpuset) + + # Determine isolated cpuset + isolated_used_cpuset = static_cpuset.intersection(isolated_cpuset) + isolated_free_cpuset = isolated_cpuset - isolated_used_cpuset + + # Get list of containers on this host + cmd = ['crictl', 'ps', '--output=json'] + try: + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + LOG.debug('command: %s\n%s', ' '.join(cmd), output) + except subprocess.CalledProcessError as e: + LOG.error('Could not list containers, error=%s', e) + return 1 + + pt = prettytable.PrettyTable( + ['namespace', + 'pod.name', + 'container.name', + 'container.id', + 'state', + 'QoS', + 'shares', + 'group', + 'cpus', + ], caching=False) + pt.align = 'l' + pt.align['shares'] = 'r' + + # Gather data for each container + J = json.loads(output) + + containers = {} + for cont in J['containers']: + containers[cont['id']] = { + 'name': cont['metadata']['name'], + 'pod.name': cont['labels']['io.kubernetes.pod.name'], + 'cont.name': cont['labels']['io.kubernetes.container.name'], + 'namespace': cont['labels']['io.kubernetes.pod.namespace'], + 'state': cont['state'], + } + for cid, C in sorted(containers.items(), + key=lambda kv: (kv[1]['namespace'], + kv[1]['name'], + kv[1]['cont.name'])): + cid_short = cid[0:13] + pname = C['pod.name'] + cname = C['cont.name'] + namespace = C['namespace'] + cstate = C['state'] + + # Now that we have the container ids, get more detailed resource info + cmd = ['crictl', 'inspect', '--output=json', cid] + try: + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + LOG.debug('command: %s\n%s', ' '.join(cmd), output) + except subprocess.CalledProcessError as e: + LOG.error('Could not get container %s, error=%s', cid, e) + return 1 + + inspect = json.loads(output) + linux = inspect['info']['runtimeSpec']['linux'] + cgroupsPath = linux['cgroupsPath'] + resources = linux['resources'] + cpu = resources['cpu'] + shares = cpu.get('shares', '-') + cpus = cpu.get('cpus') + cpuset = set(range_to_list(csv_range=cpus)) + if not cpuset: + cpuset = k8sinfra_cpuset + + # Determine pod QoS + if 'besteffort' in cgroupsPath: + QoS = 'besteffort' + elif 'burstable' in cgroupsPath: + QoS = 'burstable' + else: + QoS = 'guaranteed' + + # Determine cpuset group mapping + if cpus is None: + group = 'k8s-infra' + else: + if cpuset.issubset(platform_cpuset): + group = 'platform' + elif cpuset.issubset(isolated_cpuset): + group = 'isolated' + elif cpuset.issubset(guaranteed_cpuset): + group = 'guaranteed' + elif cpuset.issubset(default_cpuset): + group = 'default' + else: + group = 'unknown' + + # Determine per-numa node mapping of the container cpuset + per_node = [] + for node, node_cpuset in sorted(node_cpusets.items(), + key=lambda kv: kv[0]): + # calculate subset of cpus for the given numa node + n_cpuset = cpuset.intersection(node_cpuset) + if n_cpuset: + cpuranges = format_range_set(n_cpuset) + per_node.append('node {} {}'.format(node, cpuranges)) + per_node_cpus = '; '.join(per_node) + + pt.add_row( + [namespace, + pname, + cname, + cid_short, + cstate, + QoS, + shares, + group, + per_node_cpus or '-', + ]) + + # Display overall cpusets summary per numa node + pt0 = prettytable.PrettyTable( + ['Node', + 'Total', + 'Platform', + 'Isolated_used', + 'Isolated_free', + 'Guaranteed', + 'Default', + ], caching=False) + pt0.align = 'r' + + # Display total cpu resources per numa node + pt1 = prettytable.PrettyTable( + ['Node', + 'Total', + 'Platform', + 'Isolated_used', + 'Isolated_free', + 'Guaranteed', + 'Default', + ], caching=False) + pt1.align = 'r' + + for node, node_cpuset in sorted(node_cpusets.items(), key=lambda kv: kv[0]): + # calculate subset of cpus for the given numa node + node_platform = platform_cpuset.intersection(node_cpuset) + node_default = default_cpuset.intersection(node_cpuset) + node_guaranteed = guaranteed_cpuset.intersection(node_cpuset) + node_isolated_used = isolated_used_cpuset.intersection(node_cpuset) + node_isolated_free = isolated_free_cpuset.intersection(node_cpuset) + + # format cpusets as strings + node_cpuranges = format_range_set(node_cpuset) or '-' + platform_cpuranges = format_range_set(node_platform) or '-' + default_cpuranges = format_range_set(node_default) or '-' + guaranteed_cpuranges = format_range_set(node_guaranteed) or '-' + isolated_used_cpuranges = format_range_set(node_isolated_used) or '-' + isolated_free_cpuranges = format_range_set(node_isolated_free) or '-' + + # calculate overall usage and free + node_used = len(node_cpuset) + platform_used = len(node_platform) + default_used = len(node_default) + guaranteed_used = len(node_guaranteed) + isolated_used = len(node_isolated_used) + isolated_free = len(node_isolated_free) + + pt0.add_row( + [node, + node_cpuranges, + platform_cpuranges, + isolated_used_cpuranges, + isolated_free_cpuranges, + guaranteed_cpuranges, + default_cpuranges, + ]) + + pt1.add_row( + [node, + node_used, + platform_used, + isolated_used, + isolated_free, + guaranteed_used, + default_used, + ]) + + # Dump the tables out + print('\nPer-container cpusets:') + print(pt) + + print('\nLogical cpusets usage per numa node:') + print(pt0) + + print('\nLogical cpus usage per numa node:') + print(pt1) + + return 0 + + +def main(): + """Main program.""" + + # Parse command line arguments + parser = argparse.ArgumentParser( + description='Display kubernetes containers cpuset ' + 'usage per numa-node.') + parser.add_argument('--debug', + action='store_true', + help='display debug info') + args = parser.parse_args() + + # Configure logging + if args.debug: + level = logging.DEBUG + else: + level = logging.INFO + out_hdlr = logging.StreamHandler(sys.stdout) + formatter = logging.Formatter( + '%(asctime)s %(process)s %(levelname)s %(module)s: %(message)s') + out_hdlr.setFormatter(formatter) + out_hdlr.setLevel(level) + LOG.addHandler(out_hdlr) + LOG.setLevel(level) + + # Limit access of this tool since some sysfs data requires root. + if os.geteuid() != 0: + LOG.error('Require sudo/root.') + sys.exit(1) + + try: + ret = gather_info_and_display() + sys.exit(ret) + + except KeyboardInterrupt as e: + LOG.info('caught: %r, shutting down', e) + sys.exit(0) + + except IOError: + sys.exit(0) + + except Exception as e: + LOG.error('exception: %r', e, exc_info=1) + sys.exit(-4) diff --git a/kube-cpusets/kube-cpusets/kube_cpusets/tests/__init__.py b/kube-cpusets/kube-cpusets/kube_cpusets/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kube-cpusets/kube-cpusets/kube_cpusets/tests/test_kube_cpusets.py b/kube-cpusets/kube-cpusets/kube_cpusets/tests/test_kube_cpusets.py new file mode 100644 index 0000000..36626e7 --- /dev/null +++ b/kube-cpusets/kube-cpusets/kube_cpusets/tests/test_kube_cpusets.py @@ -0,0 +1,16 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (c) 2020 Wind River Systems, Inc. +# + +import testtools + + +class KubeCpusetsTestCase(testtools.TestCase): + + # NOTE(jgauld): Suggest add basic cpuset parsing tests. + # /sys/devices/system/cpu/isolated + def test_isolated_cpusets_parse(self): + pass + # kube_cpusets.kube_cpusets.get_isolated_cpuset() diff --git a/kube-cpusets/kube-cpusets/pylint.rc b/kube-cpusets/kube-cpusets/pylint.rc new file mode 100644 index 0000000..41f74c2 --- /dev/null +++ b/kube-cpusets/kube-cpusets/pylint.rc @@ -0,0 +1,346 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# DEPRECATED +include-ids=no + +# DEPRECATED +symbols=no + + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# Disable specific warnings: +# W0703: Catching too general exception +# Disable convention warnings: +# C0103: Invalid %s name "%s" : this is anal +# C0111: Missing %s docstring +# C0330: Wrong hanging indentation before block : inconsistent with flake8 +# C0325: Unnecessary parens after %r keyword : inconsistent with python 3 +# Disable refactor for bad code smell: +# R0912: Too many branches +# R0914: Too many local variables +# R0915: Too many statements +disable=C0103, C0111, C0325, C0330, R0912, R0914, R0915, W0703 + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input,file + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis +ignored-modules= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=rpm,PKCS1_PSS + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/kube-cpusets/kube-cpusets/requirements.txt b/kube-cpusets/kube-cpusets/requirements.txt new file mode 100644 index 0000000..2683dd4 --- /dev/null +++ b/kube-cpusets/kube-cpusets/requirements.txt @@ -0,0 +1,4 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + diff --git a/kube-cpusets/kube-cpusets/setup.cfg b/kube-cpusets/kube-cpusets/setup.cfg new file mode 100644 index 0000000..e4786e0 --- /dev/null +++ b/kube-cpusets/kube-cpusets/setup.cfg @@ -0,0 +1,22 @@ +[metadata] +license_files = LICENSE +name = kube_cpusets +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + +[files] +packages = + kube_cpusets + +[wheel] +universal = 1 diff --git a/kube-cpusets/kube-cpusets/setup.py b/kube-cpusets/kube-cpusets/setup.py new file mode 100644 index 0000000..fe18c35 --- /dev/null +++ b/kube-cpusets/kube-cpusets/setup.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2020 Wind River Systems, Inc. + +SPDX-License-Identifier: Apache-2.0 + +""" + +import setuptools + +setuptools.setup( + name='kube_cpusets', + version='1.0.0', + description='display kubernetes containers cpusets per numa node', + license='Apache-2.0', + packages=['kube_cpusets'], + entry_points={ + 'console_scripts': [ + 'kube-cpusets = kube_cpusets.kube_cpusets:main', + ]} +) diff --git a/kube-cpusets/kube-cpusets/test-requirements.txt b/kube-cpusets/kube-cpusets/test-requirements.txt new file mode 100644 index 0000000..dadd6c5 --- /dev/null +++ b/kube-cpusets/kube-cpusets/test-requirements.txt @@ -0,0 +1,11 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +hacking>=1.1.0,<=2.0.0 # Apache-2.0 + +coverage!=4.4,>=4.0 # Apache-2.0 +mock>=2.0.0 # BSD +stestr>=1.0.0 # Apache-2.0 +testtools>=2.2.0 # MIT + diff --git a/kube-cpusets/kube-cpusets/tox.ini b/kube-cpusets/kube-cpusets/tox.ini new file mode 100644 index 0000000..d08bd57 --- /dev/null +++ b/kube-cpusets/kube-cpusets/tox.ini @@ -0,0 +1,107 @@ +# +# Copyright (c) 2020 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +[tox] +envlist = flake8,py27,py36,pylint,cover +minversion = 2.3.2 +skipsdist = True + +stxdir = {toxinidir}/../../.. + +[testenv] +setenv = VIRTUAL_ENV={envdir} + LANG=en_US.UTF-8 + LANGUAGE=en_US:en + LC_ALL=C + OS_STDERR_CAPTURE=1 + OS_STDOUT_CAPTURE=1 + OS_TEST_PATH=./kube_cpusets/tests + OS_TEST_TIMEOUT=60 + PYTHONDONTWRITEBYTECODE=1 + PYTHONHASHSEED=0 + PYTHONWARNINGS=default::DeprecationWarning + PIP_DISABLE_PIP_VERSION_CHECK=1 + +passenv = + XDG_CACHE_HOME + +sitepackages = False +install_command = pip install \ + -v -v -v \ + -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/stable/stein/upper-constraints.txt} \ + {opts} {packages} + +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + -e{[tox]stxdir}/config/tsconfig/tsconfig + +whitelist_externals = find + sh + +[testenv:stestr] +commands = + find . -name "*.pyc" -delete + stestr run {posargs} + stestr slowest + +[testenv:py27] +basepython = python2.7 +commands = {[testenv:stestr]commands} + +[testenv:py36] +basepython = python3.6 +commands = {[testenv:stestr]commands} + +[bandit] +exclude = tests + +[testenv:bandit] +basepython = python3 +deps = -r{toxinidir}/test-requirements.txt + bandit +commands = bandit --ini tox.ini -n 5 -r kube_cpusets + +[flake8] +show-source = True +ignore = +exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,release-tag-* +# H106: Don't put vim configuration in source files (off by default). +# H203: Use assertIs(Not)None to check for None (off by default). +# enable: H904 Delay string interpolations at logging calls (off by default). +enable-extensions = H106 H203 H904 +max-line-length = 120 + + +[testenv:flake8] +basepython = python3 +deps = {[testenv]deps} + flake8-bugbear +usedevelop = False +#skip_install = True +commands = + flake8 {posargs} . + +[testenv:pylint] +deps = {[testenv]deps} + pylint + +basepython = python2.7 +sitepackages = False + +commands = pylint kube_cpusets --rcfile=./pylint.rc + +[testenv:cover] +setenv = + PYTHON=coverage run --parallel-mode + PYTHONDONTWRITEBYTECODE=True + +commands = coverage erase + find . -name "*.pyc" -delete + stestr run {posargs} + coverage combine + coverage html -d cover + coverage xml -o cover/coverage.xml + coverage report