Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4525451
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
14 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Jun 26, 7:04 PM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1959469
Default Alt Text
(14 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment