Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/python/pacemaker/_cts/scenarios.py b/python/pacemaker/_cts/scenarios.py
index ee860a88de..3a6586c951 100644
--- a/python/pacemaker/_cts/scenarios.py
+++ b/python/pacemaker/_cts/scenarios.py
@@ -1,361 +1,401 @@
-""" Test scenario classes for Pacemaker's Cluster Test Suite (CTS)
-"""
+""" Test scenario classes for Pacemaker's Cluster Test Suite (CTS) """
__all__ = [ "AllOnce", "Boot", "BootCluster", "LeaveBooted", "RandomTests", "Sequence" ]
__copyright__ = "Copyright 2000-2023 the Pacemaker project contributors"
__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
import re
import time
from pacemaker._cts.audits import ClusterAudit
from pacemaker._cts.input import should_continue
from pacemaker._cts.tests.ctstest import CTSTest
from pacemaker._cts.watcher import LogWatcher
class ScenarioComponent:
+ """ The base class for all scenario components. A scenario component is
+ one single step in a scenario. Each component is basically just a setup
+ and teardown method.
+ """
def __init__(self, cm, env):
+ """ Create a new ScenarioComponent instance
+
+ Arguments:
+
+ cm -- A ClusterManager instance
+ env -- An Environment instance
+ """
+
# pylint: disable=invalid-name
self._cm = cm
self._env = env
def is_applicable(self):
- '''Return True if the current ScenarioComponent is applicable
- in the given LabEnvironment given to the constructor.
- '''
+ """ Return True if this component is applicable in the given Environment.
+ This method must be provided by all subclasses.
+ """
raise NotImplementedError
def setup(self):
- '''Set up the given ScenarioComponent'''
+ """ Set up the component, returning True on success. This method must be
+ provided by all subclasses.
+ """
raise NotImplementedError
def teardown(self):
- '''Tear down (undo) the given ScenarioComponent'''
+ """ Tear down the given component. This method must be provided by all
+ subclasses.
+ """
raise NotImplementedError
class Scenario:
- (
-'''The basic idea of a scenario is that of an ordered list of
-ScenarioComponent objects. Each ScenarioComponent is setup() in turn,
-and then after the tests have been run, they are torn down using teardown()
-(in reverse order).
+ """ The base class for scenario. A scenario is an ordered list of
+ ScenarioComponent objects. A scenario proceeds by setting up all its
+ components in sequence, running a list of tests and audits, and then
+ tearing down its components in reverse.
+ """
-A Scenario is applicable to a particular cluster manager iff each
-ScenarioComponent is applicable.
+ def __init__(self, cm, components, audits, tests):
+ """ Create a new Scenario instance
-A partially set up scenario is torn down if it fails during setup.
-''')
+ Arguments:
- def __init__(self, cm, components, audits, tests):
- # pylint: disable=invalid-name
+ cm -- A ClusterManager instance
+ components -- A list of ScenarioComponents comprising this Scenario
+ audits -- A list of ClusterAudits that will be performed as
+ part of this Scenario
+ tests -- A list of CTSTests that will be run
+ """
- "Initialize the Scenario from the list of ScenarioComponents"
+ # pylint: disable=invalid-name
self.stats = { "success": 0, "failure": 0, "BadNews": 0, "skipped": 0 }
self.tests = tests
self._audits = audits
self._bad_news = None
self._cm = cm
self._components = components
for comp in components:
if not issubclass(comp.__class__, ScenarioComponent):
raise ValueError("Init value must be subclass of ScenarioComponent")
for audit in audits:
if not issubclass(audit.__class__, ClusterAudit):
raise ValueError("Init value must be subclass of ClusterAudit")
for test in tests:
if not issubclass(test.__class__, CTSTest):
raise ValueError("Init value must be a subclass of CTSTest")
def is_applicable(self):
- (
-'''A Scenario is_applicable() iff each of its ScenarioComponents is_applicable()
-'''
- )
+ """ Return True if all ScenarioComponents are applicable """
for comp in self._components:
if not comp.is_applicable():
return False
return True
def setup(self):
- '''Set up the Scenario. Return TRUE on success.'''
+ """ Set up the scenario, returning True on success. If setup fails at
+ some point, tear down those components that did successfully set up.
+ """
self._cm.prepare()
self.audit() # Also detects remote/local log config
self._cm.ns.wait_for_all_nodes(self._cm.Env["nodes"])
self.audit()
self._cm.install_support()
self._bad_news = LogWatcher(self._cm.Env["LogFileName"],
self._cm.templates.get_patterns("BadNews"),
self._cm.Env["nodes"],
self._cm.Env["LogWatcher"],
"BadNews", 0)
self._bad_news.set_watch() # Call after we've figured out what type of log watching to do in LogAudit
j = 0
while j < len(self._components):
if not self._components[j].setup():
# OOPS! We failed. Tear partial setups down.
self.audit()
self._cm.log("Tearing down partial setup")
self.teardown(j)
return False
j += 1
self.audit()
return True
def teardown(self, n_components=None):
-
- '''Tear Down the Scenario - in reverse order.'''
+ """ Tear down the scenario in the reverse order it was set up. If
+ n_components is not None, only tear down that many components.
+ """
if not n_components:
n_components = len(self._components)-1
j = n_components
while j >= 0:
self._components[j].teardown()
j -= 1
self.audit()
self._cm.install_support("uninstall")
def incr(self, name):
- '''Increment (or initialize) the value associated with the given name'''
+ """ Increment the given stats key """
+
if not name in self.stats:
self.stats[name] = 0
self.stats[name] += 1
def run(self, iterations):
+ """ Run all tests in the scenario the given number of times """
+
self._cm.oprofileStart()
try:
self.run_loop(iterations)
self._cm.oprofileStop()
except:
self._cm.oprofileStop()
raise
def run_loop(self, iterations):
+ """ Do the hard part of the run method - actually run all the tests the
+ given number of times.
+ """
+
raise NotImplementedError
def run_test(self, test, testcount):
+ """ Run the given test. testcount is the number of tests (including
+ this one) that have been run across all iterations.
+ """
+
nodechoice = self._cm.Env.random_node()
ret = True
did_run = False
self._cm.instance_errorstoignore_clear()
choice = "(%s)" % nodechoice
self._cm.log("Running test {:<22} {:<15} [{:>3}]".format(test.name, choice, testcount))
starttime = test.set_timer()
if not test.setup(nodechoice):
self._cm.log("Setup failed")
ret = False
elif not test.can_run_now(nodechoice):
self._cm.log("Skipped")
test.skipped()
else:
did_run = True
ret = test(nodechoice)
if not test.teardown(nodechoice):
self._cm.log("Teardown failed")
if not should_continue(self._cm.Env):
raise ValueError("Teardown of %s on %s failed" % (test.name, nodechoice))
ret = False
stoptime = time.time()
self._cm.oprofileSave(testcount)
elapsed_time = stoptime - starttime
test_time = stoptime - test.get_timer()
if "min_time" not in test.stats:
test.stats["elapsed_time"] = elapsed_time
test.stats["min_time"] = test_time
test.stats["max_time"] = test_time
else:
test.stats["elapsed_time"] += elapsed_time
if test_time < test.stats["min_time"]:
test.stats["min_time"] = test_time
if test_time > test.stats["max_time"]:
test.stats["max_time"] = test_time
if ret:
self.incr("success")
test.log_timer()
else:
self.incr("failure")
self._cm.statall()
did_run = True # Force the test count to be incremented anyway so test extraction works
self.audit(test.errors_to_ignore)
return did_run
def summarize(self):
+ """ Output scenario results """
+
self._cm.log("****************")
self._cm.log("Overall Results:%r" % self.stats)
self._cm.log("****************")
stat_filter = {
"calls":0,
"failure":0,
"skipped":0,
"auditfail":0,
}
self._cm.log("Test Summary")
for test in self.tests:
for key in list(stat_filter.keys()):
stat_filter[key] = test.stats[key]
name = "Test %s:" % test.name
self._cm.log("{:<25} {!r}".format(name, stat_filter))
self._cm.debug("Detailed Results")
for test in self.tests:
name = "Test %s:" % test.name
self._cm.debug("{:<25} {!r}".format(name, stat_filter))
self._cm.log("<<<<<<<<<<<<<<<< TESTS COMPLETED")
def audit(self, local_ignore=None):
+ """ Perform all scenario audits and log results. If there are too many
+ failures, prompt the user to confirm that the scenario should continue
+ running.
+ """
+
errcount = 0
ignorelist = ["CTS:"]
if local_ignore:
ignorelist.extend(local_ignore)
ignorelist.extend(self._cm.errorstoignore())
ignorelist.extend(self._cm.instance_errorstoignore())
# This makes sure everything is stabilized before starting...
failed = 0
for audit in self._audits:
if not audit():
self._cm.log("Audit %s FAILED." % audit.name)
failed += 1
else:
self._cm.debug("Audit %s passed." % audit.name)
while errcount < 1000:
match = None
if self._bad_news:
match = self._bad_news.look(0)
if match:
add_err = True
for ignore in ignorelist:
if add_err and re.search(ignore, match):
add_err = False
if add_err:
self._cm.log("BadNews: %s" % match)
self.incr("BadNews")
errcount += 1
else:
break
else:
print("Big problems")
if not should_continue(self._cm.Env):
self._cm.log("Shutting down.")
self.summarize()
self.teardown()
raise ValueError("Looks like we hit a BadNews jackpot!")
if self._bad_news:
self._bad_news.end()
return failed
class AllOnce(Scenario):
- '''Every Test Once''' # Accessable as __doc__
+ """ Every Test Once """
+
def run_loop(self, iterations):
testcount = 1
for test in self.tests:
self.run_test(test, testcount)
testcount += 1
class RandomTests(Scenario):
- '''Random Test Execution'''
+ """ Random Test Execution """
+
def run_loop(self, iterations):
testcount = 1
while testcount <= iterations:
test = self._cm.Env.random_gen.choice(self.tests)
self.run_test(test, testcount)
testcount += 1
class Sequence(Scenario):
- '''Named Tests in Sequence'''
+ """ Named Tests in Sequence """
+
def run_loop(self, iterations):
testcount = 1
while testcount <= iterations:
for test in self.tests:
self.run_test(test, testcount)
testcount += 1
class Boot(Scenario):
- '''Start the Cluster'''
+ """ Start the Cluster """
+
def run_loop(self, iterations):
return
class BootCluster(ScenarioComponent):
- (
-'''BootCluster is the most basic of ScenarioComponents.
-This ScenarioComponent simply starts the cluster manager on all the nodes.
-It is fairly robust as it waits for all nodes to come up before starting
-as they might have been rebooted or crashed for some reason beforehand.
-''')
+ """ The BootCluster component simply starts the cluster manager on all
+ nodes, waiting for each to come up before starting given that a node
+ might have been rebooted or crashed beforehand.
+ """
+
def is_applicable(self):
- '''BootCluster is so generic it is always Applicable'''
+ """ BootCluster is always applicable """
+
return True
def setup(self):
- '''Basic Cluster Manager startup. Start everything'''
+ """ Set up the component, returning True on success """
self._cm.prepare()
# Clear out the cobwebs ;-)
self._cm.stopall(verbose=True, force=True)
# Now start the Cluster Manager on all the nodes.
self._cm.log("Starting Cluster Manager on all nodes.")
return self._cm.startall(verbose=True, quick=True)
def teardown(self):
- '''Set up the given ScenarioComponent'''
-
- # Stop the cluster manager everywhere
+ """ Tear down the component """
self._cm.log("Stopping Cluster Manager on all nodes")
self._cm.stopall(verbose=True, force=False)
class LeaveBooted(BootCluster):
- def teardown(self):
- '''Set up the given ScenarioComponent'''
+ """ The LeaveBooted component leaves all nodes up when the scenario
+ is complete.
+ """
- # Stop the cluster manager everywhere
+ def teardown(self):
+ """ Tear down the component """
self._cm.log("Leaving Cluster running on all nodes")

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jun 26, 7:04 PM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1959469
Default Alt Text
(14 KB)

Event Timeline