diff --git a/cts/Makefile.am b/cts/Makefile.am
index 598ae32b9b..ba61984f17 100644
--- a/cts/Makefile.am
+++ b/cts/Makefile.am
@@ -1,76 +1,76 @@
#
-# Copyright 2001-2023 the Pacemaker project contributors
+# Copyright 2001-2024 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
# This source code is licensed under the GNU General Public License version 2
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
+include $(top_srcdir)/mk/python.mk
+
MAINTAINERCLEANFILES = Makefile.in
# Test commands and globally applicable test files should be in $(testdir),
# and command-specific test data should be in a command-specific subdirectory.
testdir = $(datadir)/$(PACKAGE)/tests
test_SCRIPTS = cts-attrd \
cts-cli \
cts-exec \
cts-fencing \
cts-lab \
cts-regression \
cts-scheduler
dist_test_DATA = README.md \
valgrind-pcmk.suppressions
clidir = $(testdir)/cli
dist_cli_DATA = $(wildcard cli/*.xml cli/*.exp)
ctsdir = $(datadir)/$(PACKAGE)/tests/cts
cts_SCRIPTS = cts
# Commands intended to be run only via other commands
halibdir = $(CRM_DAEMON_DIR)
dist_halib_SCRIPTS = cts-log-watcher
noinst_SCRIPTS = cluster_test
.PHONY: scheduler-list
scheduler-list:
@for T in "$(srcdir)"/scheduler/xml/*.xml; do \
echo $$(basename $$T .xml); \
done
CLEANFILES = $(builddir)/.regression.failed.diff
.PHONY: clean-local
clean-local:
rm -f scheduler/*/*.pe
SUBDIRS = benchmark \
scheduler \
support
.PHONY: cts-support-install
cts-support-install:
$(MAKE) $(AM_MAKEFLAGS) -C support cts-support
$(builddir)/support/cts-support install
.PHONY: cts-support-uninstall
cts-support-uninstall:
$(MAKE) $(AM_MAKEFLAGS) -C support cts-support
$(builddir)/support/cts-support uninstall
# Everything listed here is a python script, typically generated from a .in file
# (though that is not a requirement). We want to run pylint on all of these
# things after they've been built.
-python_files = cts-attrd \
- cts-exec \
- cts-fencing \
- cts-lab \
- cts-log-watcher \
- cts-regression \
+python_files = cts-attrd \
+ cts-exec \
+ cts-fencing \
+ cts-lab \
+ cts-log-watcher \
+ cts-regression \
cts-scheduler
-.PHONY: pylint
-pylint: $(python_files)
- PYTHONPATH=$(top_builddir)/python pylint --rcfile $(top_srcdir)/python/pylintrc $(python_files)
+PYCHECKFILES ?= $(python_files)
diff --git a/cts/cts-attrd.in b/cts/cts-attrd.in
index c9a219d326..29f577123f 100644
--- a/cts/cts-attrd.in
+++ b/cts/cts-attrd.in
@@ -1,367 +1,366 @@
#!@PYTHON@
-""" Regression tests for Pacemaker's attribute daemon
-"""
+"""Regression tests for Pacemaker's attribute daemon."""
# pylint doesn't like the module name "cts-attrd" which is an invalid complaint for this file
# but probably something we want to continue warning about elsewhere
# pylint: disable=invalid-name
# pacemaker imports need to come after we modify sys.path, which pylint will complain about.
# pylint: disable=wrong-import-position
__copyright__ = "Copyright 2023 the Pacemaker project contributors"
__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
import argparse
import os
import subprocess
import sys
import tempfile
# These imports allow running from a source checkout after running `make`.
# Note that while this doesn't necessarily mean it will successfully run tests,
# but being able to see --help output can be useful.
if os.path.exists("@abs_top_srcdir@/python"):
sys.path.insert(0, "@abs_top_srcdir@/python")
# pylint: disable=comparison-of-constants,comparison-with-itself,condition-evals-to-constant
if os.path.exists("@abs_top_builddir@/python") and "@abs_top_builddir@" != "@abs_top_srcdir@":
sys.path.insert(0, "@abs_top_builddir@/python")
from pacemaker.buildoptions import BuildOptions
from pacemaker.exitstatus import ExitStatus
from pacemaker._cts.corosync import Corosync
from pacemaker._cts.process import killall, exit_if_proc_running
from pacemaker._cts.test import Test, Tests
TEST_DIR = sys.path[0]
-def update_path():
- """ Set the PATH environment variable appropriately for the tests """
+def update_path():
+ """Set the PATH environment variable appropriately for the tests."""
new_path = os.environ['PATH']
if os.path.exists("%s/cts-attrd.in" % TEST_DIR):
# pylint: disable=protected-access
print("Running tests from the source tree: %s (%s)" % (BuildOptions._BUILD_DIR, TEST_DIR))
# For pacemaker-attrd
new_path = "%s/daemons/attrd:%s" % (BuildOptions._BUILD_DIR, new_path)
else:
print("Running tests from the install tree: %s (not %s)" % (BuildOptions.DAEMON_DIR, TEST_DIR))
# For pacemaker-attrd
new_path = "%s:%s" % (BuildOptions.DAEMON_DIR, new_path)
print('Using PATH="%s"' % new_path)
os.environ['PATH'] = new_path
class AttributeTest(Test):
- """ Executor for a single test """
+ """Executor for a single test."""
def __init__(self, name, description, **kwargs):
+ """
+ Create a new AttributeTest instance.
+
+ Arguments:
+ name -- A unique name for this test. This can be used on the
+ command line to specify that only a specific test should
+ be executed.
+ description -- A meaningful description for the test.
+ """
Test.__init__(self, name, description, **kwargs)
self._daemon_location = "pacemaker-attrd"
self._enable_corosync = True
def _kill_daemons(self):
killall([self._daemon_location])
def _start_daemons(self):
if self.verbose:
print("Starting %s" % self._daemon_location)
cmd = [self._daemon_location, "-s", "-l", self.logpath]
# pylint: disable=consider-using-with
self._daemon_process = subprocess.Popen(cmd)
class AttributeTests(Tests):
- """ Collection of all attribute regression tests """
+ """Collection of all attribute regression tests."""
def __init__(self, **kwargs):
+ """Create a new AttributeTests instance."""
Tests.__init__(self, **kwargs)
self._corosync = Corosync(self.verbose, self.logdir, "cts-attrd")
def new_test(self, name, description):
- """ Create a named test """
-
+ """Create a named test."""
test = AttributeTest(name, description, verbose=self.verbose, logdir=self.logdir)
self._tests.append(test)
return test
def setup_environment(self, use_corosync):
- """ Prepare the host before executing any tests """
-
+ """Prepare the host before executing any tests."""
if use_corosync:
self._corosync.start(kill_first=True)
def cleanup_environment(self, use_corosync):
- """ Clean up the host after executing desired tests """
-
+ """Clean up the host after executing desired tests."""
if use_corosync:
self._corosync.stop()
def build_basic_tests(self):
- """ Add basic tests - setting, querying, updating, and deleting attributes """
-
+ """Add basic tests - setting, querying, updating, and deleting attributes."""
test = self.new_test("set_attr_1",
"Set and query an attribute")
test.add_cmd("attrd_updater", "--name AAA -U 111 --output-as=xml")
test.add_cmd_check_stdout("attrd_updater", "--name AAA -Q --output-as=xml",
"name=\"AAA\" value=\"111\"")
test.add_log_pattern(r"Setting AAA\[.*\] in instance_attributes: \(unset\) -> 111",
regex=True)
# Setting the delay on an attribute that doesn't exist fails, but the failure is
# not passed back to attrd_updater.
test = self.new_test("set_attr_2",
"Set an attribute's delay")
test.add_cmd("attrd_updater", "--name AAA -Y -d 5 --output-as=xml")
test.add_log_pattern(r"Processed update-delay request from client .*: Error \(Attribute AAA does not exist\)",
regex=True)
test = self.new_test("set_attr_3",
"Set and query an attribute's delay and value")
test.add_cmd("attrd_updater", "--name AAA -B 111 -d 5 --output-as=xml")
test.add_cmd_check_stdout("attrd_updater", "--name AAA -Q --output-as=xml",
"name=\"AAA\" value=\"111\"")
test.add_log_pattern(r"Setting AAA\[.*\] in instance_attributes: \(unset\) -> 111 \| from .* with 5s write delay",
regex=True)
test = self.new_test("set_attr_4",
"Update an attribute that does not exist with a delay")
test.add_cmd("attrd_updater", "--name BBB -U 999 -d 10 --output-as=xml")
test.add_cmd_check_stdout("attrd_updater", "--name BBB -Q --output-as=xml",
"name=\"BBB\" value=\"999\"")
test.add_log_pattern(r"Setting BBB\[.*\] in instance_attributes: \(unset\) -> 999 \| from .* with 10s write delay",
regex=True)
test = self.new_test("update_attr_1",
"Update an attribute that already exists")
test.add_cmd("attrd_updater", "--name BBB -U 222 --output-as=xml")
test.add_cmd("attrd_updater", "--name BBB -U 333 --output-as=xml")
test.add_cmd_check_stdout("attrd_updater", "--name BBB -Q --output-as=xml",
"name=\"BBB\" value=\"333\"")
test.add_log_pattern(r"Setting BBB\[.*\] in instance_attributes: \(unset\) -> 222",
regex=True)
test.add_log_pattern(r"Setting BBB\[.*\] in instance_attributes: 222 -> 333",
regex=True)
test = self.new_test("update_attr_2",
"Update an attribute using a delay other than its default")
test.add_cmd("attrd_updater", "--name BBB -U 777 -d 10 --output-as=xml")
test.add_cmd("attrd_updater", "--name BBB -U 888 -d 7 --output-as=xml")
test.add_log_pattern(r"Setting BBB\[.*\] in instance_attributes: 777 -> 888 \| from .* with 10s write delay",
regex=True)
test = self.new_test("update_attr_delay_1",
"Update the delay of an attribute that already exists")
test.add_cmd("attrd_updater", "--name BBB -U 222 --output-as=xml")
test.add_cmd("attrd_updater", "--name BBB -Y -d 5 --output-as=xml")
test.add_log_pattern(r"Setting BBB\[.*\] in instance_attributes: \(unset\) -> 222",
regex=True)
test.add_log_pattern("Update attribute BBB delay to 5000ms (5)")
test = self.new_test("update_attr_delay_2",
"Update the delay and value of an attribute that already exists")
test.add_cmd("attrd_updater", "--name BBB -U 222 --output-as=xml")
test.add_cmd("attrd_updater", "--name BBB -B 333 -d 5 --output-as=xml")
test.add_log_pattern(r"Setting BBB\[.*\] in instance_attributes: \(unset\) -> 222",
regex=True)
test.add_log_pattern("Update attribute BBB delay to 5000ms (5)")
test.add_log_pattern(r"Setting BBB\[.*\] in instance_attributes: 222 -> 333",
regex=True)
test = self.new_test("missing_attr_1",
"Query an attribute that does not exist")
test.add_cmd_expected_fail("attrd_updater", "--name NOSUCH --output-as=xml",
ExitStatus.CONFIG)
test = self.new_test("delete_attr_1",
"Delete an existing attribute")
test.add_cmd("attrd_updater", "--name CCC -U 444 --output-as=xml")
test.add_cmd("attrd_updater", "--name CCC -D --output-as=xml")
test.add_log_pattern(r"Setting CCC\[.*\] in instance_attributes: \(unset\) -> 444",
regex=True)
test.add_log_pattern(r"Setting CCC\[.*\] in instance_attributes: 444 -> \(unset\)",
regex=True)
test = self.new_test("missing_attr_2",
"Delete an attribute that does not exist")
test.add_cmd("attrd_updater", "--name NOSUCH2 -D --output-as=xml")
test = self.new_test("attr_in_set_1",
"Set and query an attribute in a specific set")
test.add_cmd("attrd_updater", "--name DDD -U 555 --set=foo --output-as=xml")
test.add_cmd_check_stdout("attrd_updater", "--name DDD -Q --output-as=xml",
"name=\"DDD\" value=\"555\"")
test.add_log_pattern("Processed 1 private change for DDD (set foo)")
def build_multiple_query_tests(self):
- """ Add tests that set and query an attribute across multiple nodes """
-
+ """Add tests that set and query an attribute across multiple nodes."""
# NOTE: These tests make use of the fact that nothing in attrd actually
# cares about whether a node exists when you set or query an attribute.
# It just keeps creating new hash tables for each node you ask it about.
test = self.new_test("multi_query_1",
"Query an attribute set across multiple nodes")
test.add_cmd("attrd_updater", "--name AAA -U 111 --node cluster1 --output-as=xml")
test.add_cmd("attrd_updater", "--name AAA -U 222 --node cluster2 --output-as=xml")
test.add_cmd_check_stdout("attrd_updater", "--name AAA -QA --output-as=xml",
r"""\n.*""")
test.add_cmd_check_stdout("attrd_updater", "--name AAA -Q --node=cluster1 --output-as=xml",
"""""")
test.add_cmd_check_stdout("attrd_updater", "--name AAA -Q --node=cluster2 --output-as=xml",
"""""")
test.add_cmd_check_stdout("attrd_updater", "--name AAA -QA --output-as=xml",
r"""\n.*""",
env={"OCF_RESKEY_CRM_meta_on_node": "cluster1"})
test.add_cmd_check_stdout("attrd_updater", "--name AAA -Q --output-as=xml",
"""""",
env={"OCF_RESKEY_CRM_meta_on_node": "cluster1"})
test.add_cmd_check_stdout("attrd_updater", "--name AAA -Q --node=cluster2 --output-as=xml",
"""""",
env={"OCF_RESKEY_CRM_meta_on_node": "cluster1"})
def build_regex_tests(self):
- """ Add tests that use regexes """
-
+ """Add tests that use regexes."""
test = self.new_test("regex_update_1",
"Update attributes using a regex")
test.add_cmd("attrd_updater", "--name AAA -U 111 --output-as=xml")
test.add_cmd("attrd_updater", "--name ABB -U 222 --output-as=xml")
test.add_cmd("attrd_updater", "-P 'A.*' -U 333 --output-as=xml")
test.add_cmd_check_stdout("attrd_updater", "--name AAA -Q --output-as=xml",
"name=\"AAA\" value=\"333\"")
test.add_cmd_check_stdout("attrd_updater", "--name ABB -Q --output-as=xml",
"name=\"ABB\" value=\"333\"")
test.add_log_pattern(r"Setting AAA\[.*\] in instance_attributes: \(unset\) -> 111",
regex=True)
test.add_log_pattern(r"Setting ABB\[.*\] in instance_attributes: \(unset\) -> 222",
regex=True)
test.add_log_pattern(r"Setting ABB\[.*\] in instance_attributes: 222 -> 333",
regex=True)
test.add_log_pattern(r"Setting AAA\[.*\] in instance_attributes: 111 -> 333",
regex=True)
test = self.new_test("regex_delete_1",
"Delete attributes using a regex")
test.add_cmd("attrd_updater", "--name XAX -U 444 --output-as=xml")
test.add_cmd("attrd_updater", "--name XBX -U 555 --output-as=xml")
test.add_cmd("attrd_updater", "-P 'X[A|B]X' -D --output-as=xml")
test.add_log_pattern(r"Setting XAX\[.*\] in instance_attributes: \(unset\) -> 444",
regex=True)
test.add_log_pattern(r"Setting XBX\[.*\] in instance_attributes: \(unset\) -> 555",
regex=True)
test.add_log_pattern(r"Setting XBX\[.*\] in instance_attributes: 555 -> \(unset\)",
regex=True)
test.add_log_pattern(r"Setting XAX\[.*\] in instance_attributes: 444 -> \(unset\)",
regex=True)
def build_utilization_tests(self):
- """ Add tests that involve utilization attributes """
-
+ """Add tests that involve utilization attributes."""
test = self.new_test("utilization_1",
"Set and query a utilization attribute")
test.add_cmd("attrd_updater", "--name AAA -U ABC -z --output-as=xml")
test.add_cmd_check_stdout("attrd_updater", "--name AAA -Q --output-as=xml",
"name=\"AAA\" value=\"ABC\"")
test.add_log_pattern(r"Setting AAA\[.*\] in utilization: \(unset\) -> ABC",
regex=True)
def build_sync_point_tests(self):
- """ Add tests that involve sync points """
-
+ """Add tests that involve sync points."""
test = self.new_test("local_sync_point",
"Wait for a local sync point")
test.add_cmd("attrd_updater", "--name AAA -U 123 --wait=local --output-as=xml")
test.add_cmd_check_stdout("attrd_updater", "--name AAA -Q --output-as=xml",
"name=\"AAA\" value=\"123\"")
test.add_log_pattern(r"Alerting client .* for reached local sync point",
regex=True)
test = self.new_test("cluster_sync_point",
"Wait for a cluster-wide sync point")
test.add_cmd("attrd_updater", "--name BBB -U 456 --wait=cluster --output-as=xml")
test.add_cmd_check_stdout("attrd_updater", "--name BBB -Q --output-as=xml",
"name=\"BBB\" value=\"456\"")
test.add_log_pattern(r"Alerting client .* for reached cluster sync point",
regex=True)
def build_options():
- """ Handle command line arguments """
-
+ """Handle command line arguments."""
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
description="Run pacemaker-attrd regression tests",
epilog="Example: Run only the test 'start_stop'\n"
"\t " + sys.argv[0] + " --run-only start_stop\n\n"
"Example: Run only the tests with the string 'systemd' present in them\n"
"\t " + sys.argv[0] + " --run-only-pattern systemd")
parser.add_argument("-l", "--list-tests", action="store_true",
help="Print out all registered tests")
parser.add_argument("-p", "--run-only-pattern", metavar='PATTERN',
help="Run only tests matching the given pattern")
parser.add_argument("-r", "--run-only", metavar='TEST',
help="Run a specific test")
parser.add_argument("-V", "--verbose", action="store_true",
help="Verbose output")
args = parser.parse_args()
return args
def main():
- """ Run attrd regression tests as specified by arguments """
-
+ """Run attrd regression tests as specified by arguments."""
update_path()
# Ensure all command output is in portable locale for comparison
os.environ['LC_ALL'] = "C"
opts = build_options()
exit_if_proc_running("pacemaker-attrd")
# Create a temporary directory for log files (the directory and its
# contents will automatically be erased when done)
with tempfile.TemporaryDirectory(prefix="cts-attrd-") as logdir:
tests = AttributeTests(verbose=opts.verbose, logdir=logdir)
tests.build_basic_tests()
tests.build_multiple_query_tests()
tests.build_regex_tests()
tests.build_utilization_tests()
tests.build_sync_point_tests()
if opts.list_tests:
tests.print_list()
sys.exit(ExitStatus.OK)
print("Starting ...")
try:
tests.setup_environment(True)
except TimeoutError:
print("corosync did not start in time, exiting")
sys.exit(ExitStatus.TIMEOUT)
if opts.run_only_pattern:
tests.run_tests_matching(opts.run_only_pattern)
tests.print_results()
elif opts.run_only:
tests.run_single(opts.run_only)
tests.print_results()
else:
tests.run_tests()
tests.print_results()
tests.cleanup_environment(True)
tests.exit()
if __name__ == "__main__":
main()
diff --git a/python/Makefile.am b/mk/python.mk
similarity index 58%
copy from python/Makefile.am
copy to mk/python.mk
index d54c2ddb87..8b8fc408e3 100644
--- a/python/Makefile.am
+++ b/mk/python.mk
@@ -1,39 +1,27 @@
#
-# Copyright 2023-2024 the Pacemaker project contributors
+# Copyright 2024 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
# This source code is licensed under the GNU General Public License version 2
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
-MAINTAINERCLEANFILES = Makefile.in
-
-EXTRA_DIST = pylintrc
-
-SUBDIRS = pacemaker \
- tests
-
-.PHONY: check-local
-check-local:
- if [ "x$(top_srcdir)" != "x$(top_builddir)" ]; then \
- cp -r $(top_srcdir)/python/* $(abs_top_builddir)/python/; \
- fi
- PYTHONPATH=$(top_builddir)/python $(PYTHON) -m unittest discover -v -s $(top_builddir)/python/tests
-
.PHONY: pylint
-pylint:
- pylint $(SUBDIRS)
+pylint: $(PYCHECKFILES)
+ PYTHONPATH=$(abs_top_builddir)/python \
+ pylint --rcfile $(top_srcdir)/python/pylintrc $(PYCHECKFILES)
# Disabled warnings:
# W503 - Line break occurred before a binary operator
# (newer versions of pyflake and PEP8 want line breaks after binary
# operators, but older versions still suggest before)
# E501 - Line too long
#
# Disable unused imports on __init__.py files (we likely just have them
# there for re-exporting).
# Disable docstrings warnings on unit tests.
.PHONY: pyflake
-pyflake:
- flake8 --ignore=W503,E501 --per-file-ignores="__init__.py:F401 tests/*:D100,D101,D102,D104" $(SUBDIRS)
+pyflake: $(PYCHECKFILES)
+ PYTHONPATH=$(abs_top_builddir)/python \
+ flake8 --ignore=W503,E501 --per-file-ignores="__init__.py:F401 tests/*:D100,D101,D102,D104" $(PYCHECKFILES)
diff --git a/python/Makefile.am b/python/Makefile.am
index d54c2ddb87..558c2e91f9 100644
--- a/python/Makefile.am
+++ b/python/Makefile.am
@@ -1,39 +1,26 @@
#
# Copyright 2023-2024 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
# This source code is licensed under the GNU General Public License version 2
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
+include $(top_srcdir)/mk/python.mk
+
MAINTAINERCLEANFILES = Makefile.in
EXTRA_DIST = pylintrc
SUBDIRS = pacemaker \
tests
+PYCHECKFILES ?= $(SUBDIRS)
+
.PHONY: check-local
check-local:
if [ "x$(top_srcdir)" != "x$(top_builddir)" ]; then \
cp -r $(top_srcdir)/python/* $(abs_top_builddir)/python/; \
fi
PYTHONPATH=$(top_builddir)/python $(PYTHON) -m unittest discover -v -s $(top_builddir)/python/tests
-
-.PHONY: pylint
-pylint:
- pylint $(SUBDIRS)
-
-# Disabled warnings:
-# W503 - Line break occurred before a binary operator
-# (newer versions of pyflake and PEP8 want line breaks after binary
-# operators, but older versions still suggest before)
-# E501 - Line too long
-#
-# Disable unused imports on __init__.py files (we likely just have them
-# there for re-exporting).
-# Disable docstrings warnings on unit tests.
-.PHONY: pyflake
-pyflake:
- flake8 --ignore=W503,E501 --per-file-ignores="__init__.py:F401 tests/*:D100,D101,D102,D104" $(SUBDIRS)