dhodovsk / source-git / pacemaker

Forked from source-git/pacemaker 3 years ago
Clone
Blob Blame History Raw
#!@PYTHON@
""" Regression tests for Pacemaker's pacemaker-execd
"""

# Pacemaker targets compatibility with Python 2.7 and 3.2+
from __future__ import print_function, unicode_literals, absolute_import, division

__copyright__ = "Copyright 2012-2019 the Pacemaker project contributors"
__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"

import io
import os
import stat
import sys
import subprocess
import shlex
import shutil
import time

# Where to find test binaries
# Prefer the source tree if available
BUILD_DIR = "@abs_top_builddir@"
TEST_DIR = sys.path[0]

SBIN_DIR = "@sbindir@"

# File permissions for executable scripts we create
EXECMODE = stat.S_IRUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH

# These values must be kept in sync with include/crm/crm.h
class CrmExit(object):
    OK                   =   0
    ERROR                =   1
    INVALID_PARAM        =   2
    UNIMPLEMENT_FEATURE  =   3
    INSUFFICIENT_PRIV    =   4
    NOT_INSTALLED        =   5
    NOT_CONFIGURED       =   6
    NOT_RUNNING          =   7
    USAGE                =  64
    DATAERR              =  65
    NOINPUT              =  66
    NOUSER               =  67
    NOHOST               =  68
    UNAVAILABLE          =  69
    SOFTWARE             =  70
    OSERR                =  71
    OSFILE               =  72
    CANTCREAT            =  73
    IOERR                =  74
    TEMPFAIL             =  75
    PROTOCOL             =  76
    NOPERM               =  77
    CONFIG               =  78
    FATAL                = 100
    PANIC                = 101
    DISCONNECT           = 102
    SOLO                 = 103
    DIGEST               = 104
    NOSUCH               = 105
    QUORUM               = 106
    UNSAFE               = 107
    EXISTS               = 108
    MULTIPLE             = 109
    OLD                  = 110
    TIMEOUT              = 124
    MAX                  = 255


def update_path():
    """ Set the PATH environment variable appropriately for the tests """

    new_path = os.environ['PATH']
    if os.path.exists("%s/cts-exec.in" % TEST_DIR):
        print("Running tests from the source tree: %s (%s)" % (BUILD_DIR, TEST_DIR))
        # For pacemaker-execd, cts-exec-helper, and pacemaker-remoted
        new_path = "%s/daemons/execd:%s" % (BUILD_DIR, new_path)
        new_path = "%s/tools:%s" % (BUILD_DIR, new_path)   # For crm_resource
        # For pacemaker-fenced
        new_path = "%s/daemons/fenced:%s" % (BUILD_DIR, new_path)
        # For cts-support
        new_path = "%s/cts:%s" % (BUILD_DIR, new_path)

    else:
        print("Running tests from the install tree: @CRM_DAEMON_DIR@ (not %s)" % TEST_DIR)
        # For cts-exec-helper, cts-support, pacemaker-execd, pacemaker-fenced,
        # and pacemaker-remoted
        new_path = "@CRM_DAEMON_DIR@:%s" % (new_path)

    print('Using PATH="{}"'.format(new_path))
    os.environ['PATH'] = new_path


def pipe_output(pipes, stdout=True, stderr=False):
    """ Wrapper to get text output from pipes regardless of Python version """

    output = ""
    pipe_outputs = pipes.communicate()
    if sys.version_info < (3,):
        if stdout:
            output = output + pipe_outputs[0]
        if stderr:
            output = output + pipe_outputs[1]
    else:
        if stdout:
            output = output + pipe_outputs[0].decode(sys.stdout.encoding)
        if stderr:
            output = output + pipe_outputs[1].decode(sys.stderr.encoding)
    return output


def output_from_command(command):
    """ Run a command, and return its standard output. """

    test = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
    test.wait()
    return pipe_output(test).split("\n")


class TestError(Exception):
    """ Base class for exceptions in this module """
    pass


class ExitCodeError(TestError):
    """ Exception raised when command exit status is unexpected """

    def __init__(self, exit_code):
        self.exit_code = exit_code

    def __str__(self):
        return repr(self.exit_code)


class OutputNotFoundError(TestError):
    """ Exception raised when command output does not contain wanted string """

    def __init__(self, output):
        self.output = output

    def __str__(self):
        return repr(self.output)


class OutputFoundError(TestError):
    """ Exception raised when command output contains unwanted string """

    def __init__(self, output):
        self.output = output

    def __str__(self):
        return repr(self.output)


class Test(object):
    """ Executor for a single pacemaker-execd regression test """

    def __init__(self, name, description, verbose=0, tls=0):
        self.name = name
        self.description = description
        self.cmds = []

        if tls:
            self.daemon_location = "pacemaker-remoted"
        else:
            self.daemon_location = "pacemaker-execd"

        self.test_tool_location = "cts-exec-helper"
        self.verbose = verbose
        self.tls = tls

        self.result_txt = ""
        self.cmd_tool_output = ""
        self.result_exitcode = CrmExit.OK

        self.execd_process = None
        self.stonith_process = None

        self.executed = 0

    def __new_cmd(self, cmd, args, exitcode, stdout_match="", no_wait=0, stdout_negative_match="", kill=None):
        """ Add a command to be executed as part of this test """

        if self.verbose and cmd == self.test_tool_location:
            args = args + " -V "

        if (cmd == self.test_tool_location) and self.tls:
            args = args + " -S "

        self.cmds.append(
            {
                "cmd" : cmd,
                "kill" : kill,
                "args" : args,
                "expected_exitcode" : exitcode,
                "stdout_match" : stdout_match,
                "stdout_negative_match" : stdout_negative_match,
                "no_wait" : no_wait,
                "cmd_output" : "",
            }
        )

    def start_environment(self):
        """ Prepare the host for running a test """

        ### make sure we are in full control here ###
        cmd = shlex.split("killall -q -9 pacemaker-fenced lt-pacemaker-fenced pacemaker-execd lt-pacemaker-execd cts-exec-helper lt-cts-exec-helper pacemaker-remoted")
        test = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        test.wait()

        additional_args = ""

        if self.tls == 0:
            self.stonith_process = subprocess.Popen(shlex.split("pacemaker-fenced -s"))

        if self.verbose:
            additional_args = additional_args + " -V"

        self.execd_process = subprocess.Popen(shlex.split("%s %s -l /tmp/pacemaker-execd-regression.log"
                                                         % (self.daemon_location, additional_args)))

        time.sleep(1)

    def clean_environment(self):
        """ Clean up the host after running a test """

        if self.execd_process:
            self.execd_process.terminate()
            self.execd_process.wait()

            if self.verbose:
                print("Daemon output")
                logfile = io.open('/tmp/pacemaker-execd-regression.log', 'rt', errors='replace')
                for line in logfile:
                    print(line.strip().encode('utf-8', 'replace'))
            os.remove('/tmp/pacemaker-execd-regression.log')

        if self.stonith_process:
            self.stonith_process.terminate()
            self.stonith_process.wait()

        self.execd_process = None
        self.stonith_process = None

    def add_sys_cmd(self, cmd, args):
        """ Add a simple command to be executed as part of this test """

        self.__new_cmd(cmd, args, CrmExit.OK, "")

    def add_cmd_check_stdout(self, args, match, no_match=""):
        """ Add a command with expected output to be executed as part of this test """

        self.__new_cmd(self.test_tool_location, args, CrmExit.OK, match, 0, no_match)

    def add_cmd(self, args):
        """ Add a cts-exec-helper command to be executed as part of this test """

        self.__new_cmd(self.test_tool_location, args, CrmExit.OK, "")

    def add_cmd_and_kill(self, kill_proc, args):
        """ Add a cts-exec-helper command and system command to be executed as part of this test """

        self.__new_cmd(self.test_tool_location, args, CrmExit.OK, "", kill=kill_proc)

    def add_expected_fail_cmd(self, args, exitcode=CrmExit.ERROR):
        """ Add a cts-exec-helper command to be executed as part of this test and expected to fail """

        self.__new_cmd(self.test_tool_location, args, exitcode, "")

    def get_exitcode(self):
        """ Return the exit status of the last test execution """

        return self.result_exitcode

    def print_result(self, filler):
        """ Print the result of the last test execution """

        print("%s%s" % (filler, self.result_txt))

    def run_cmd(self, args):
        """ Execute a command as part of this test """

        cmd = shlex.split(args['args'])
        cmd.insert(0, args['cmd'])
        if self.verbose:
            print("\n\nRunning: "+" ".join(cmd))
        test = subprocess.Popen(cmd, stdout=subprocess.PIPE)

        if args['kill']:
            if self.verbose:
                print("Also running: "+args['kill'])
            ### Typically, the kill argument is used to detect some sort of
            ### failure. Without yielding for a few seconds here, the process
            ### launched earlier that is listening for the failure may not have
            ### time to connect to pacemaker-execd.
            time.sleep(2)
            subprocess.Popen(shlex.split(args['kill']))

        if args['no_wait'] == 0:
            test.wait()
        else:
            return CrmExit.OK

        output = pipe_output(test)
        args['cmd_output'] = output

        if test.returncode != args['expected_exitcode']:
            raise ExitCodeError(test.returncode)

        if args['stdout_match'] != "" and output.count(args['stdout_match']) == 0:
            raise OutputNotFoundError(output)

        if args['stdout_negative_match'] != "" and output.count(args['stdout_negative_match']) != 0:
            raise OutputFoundError(output)

    def set_error(self, step, cmd):
        """ Record failure of this test """

        msg = "FAILURE - '%s' failed at step %d. Command: %s %s"
        self.result_txt = msg % (self.name, step, cmd['cmd'], cmd['args'])
        self.result_exitcode = CrmExit.ERROR

    def run(self):
        """ Execute this test. """

        res = 0
        i = 1

        if self.tls and self.name.count("stonith") != 0:
            self.result_txt = "SKIPPED - '%s' - disabled when testing pacemaker_remote" % (self.name)
            print(self.result_txt)
            return res

        self.start_environment()

        if self.verbose:
            print("\n--- START TEST - %s" % self.name)

        self.result_txt = "SUCCESS - '%s'" % (self.name)
        self.result_exitcode = CrmExit.OK
        for cmd in self.cmds:
            try:
                self.run_cmd(cmd)
            except ExitCodeError as e:
                print(cmd['cmd_output'])
                print("Step %d FAILED - command returned %s, expected %d" % (i, e, cmd['expected_exitcode']))
                self.set_error(i, cmd);
                break
            except OutputNotFoundError as e:
                print("Step %d FAILED - '%s' was not found in command output: %s" % (i, cmd['stdout_match'], e))
                self.set_error(i, cmd);
                break
            except OutputFoundError as e:
                print("Step %d FAILED - '%s' was found in command output: %s" % (i, cmd['stdout_negative_match'], e))
                self.set_error(i, cmd);
                break
            if self.verbose:
                print(cmd['cmd_output'].strip())
                print("Step %d SUCCESS" % (i))
            i = i + 1
        self.clean_environment()

        print(self.result_txt)
        if self.verbose:
            print("--- END TEST - %s\n" % self.name)

        self.executed = 1
        return res

class Tests(object):
    """ Collection of all pacemaker-execd regression tests """

    def __init__(self, verbose=0, tls=0):
        self.tests = []
        self.verbose = verbose
        self.tls = tls
        self.rsc_classes = output_from_command("crm_resource --list-standards")
        self.rsc_classes = self.rsc_classes[:-1] # Strip trailing empty line
        self.installed_files = []
        self.action_timeout = " -t 9000 "
        if self.tls:
            self.rsc_classes.remove("stonith")
        if "systemd" in self.rsc_classes:
            try:
                # This code doesn't need this import, but pacemaker-cts-dummyd
                # does, so ensure the dependency is available rather than cause
                # all systemd tests to fail.
                import systemd.daemon
            except ImportError:
                print("Python systemd bindings not found.")
                print("The tests for systemd class are not going to be run.")
                self.rsc_classes.remove("systemd")

        print("Testing resource classes", repr(self.rsc_classes))

        self.common_cmds = {
            "ocf_reg_line"      : "-c register_rsc -r ocf_test_rsc "+self.action_timeout+" -C ocf -P pacemaker -T Dummy",
            "ocf_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:ocf_test_rsc action:none rc:ok op_status:complete\"",
            "ocf_unreg_line"    : "-c unregister_rsc -r \"ocf_test_rsc\" "+self.action_timeout,
            "ocf_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:ocf_test_rsc action:none rc:ok op_status:complete\"",
            "ocf_start_line"    : "-c exec -r \"ocf_test_rsc\" -a \"start\" "+self.action_timeout,
            "ocf_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:ocf_test_rsc action:start rc:ok op_status:complete\" ",
            "ocf_stop_line"     : "-c exec -r \"ocf_test_rsc\" -a \"stop\" "+self.action_timeout,
            "ocf_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:ocf_test_rsc action:stop rc:ok op_status:complete\" ",
            "ocf_monitor_line"  : '-c exec -r ocf_test_rsc -a monitor -i 2s ' + self.action_timeout,
            "ocf_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:ocf_test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout,
            "ocf_cancel_line"   : '-c cancel -r ocf_test_rsc -a monitor -i 2s ' + self.action_timeout,
            "ocf_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:ocf_test_rsc action:monitor rc:ok op_status:Cancelled\" ",

            "systemd_reg_line"      : "-c register_rsc -r systemd_test_rsc " +
                                      self.action_timeout +
                                      " -C systemd -T pacemaker-cts-dummyd@3",
            "systemd_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:systemd_test_rsc action:none rc:ok op_status:complete\"",
            "systemd_unreg_line"    : "-c unregister_rsc -r \"systemd_test_rsc\" "+self.action_timeout,
            "systemd_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:systemd_test_rsc action:none rc:ok op_status:complete\"",
            "systemd_start_line"    : "-c exec -r \"systemd_test_rsc\" -a \"start\" "+self.action_timeout,
            "systemd_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:systemd_test_rsc action:start rc:ok op_status:complete\" ",
            "systemd_stop_line"     : "-c exec -r \"systemd_test_rsc\" -a \"stop\" "+self.action_timeout,
            "systemd_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:systemd_test_rsc action:stop rc:ok op_status:complete\" ",
            "systemd_monitor_line"  : '-c exec -r systemd_test_rsc -a monitor -i 2s ' + self.action_timeout,
            "systemd_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:systemd_test_rsc action:monitor rc:ok op_status:complete\" -t 15000 ",
            "systemd_cancel_line"   : '-c cancel -r systemd_test_rsc -a monitor -i 2s ' + self.action_timeout,
            "systemd_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:systemd_test_rsc action:monitor rc:ok op_status:Cancelled\" ",

            "upstart_reg_line"      : "-c register_rsc -r upstart_test_rsc "+self.action_timeout+" -C upstart -T pacemaker-cts-dummyd",
            "upstart_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:upstart_test_rsc action:none rc:ok op_status:complete\"",
            "upstart_unreg_line"    : "-c unregister_rsc -r \"upstart_test_rsc\" "+self.action_timeout,
            "upstart_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:upstart_test_rsc action:none rc:ok op_status:complete\"",
            "upstart_start_line"    : "-c exec -r \"upstart_test_rsc\" -a \"start\" "+self.action_timeout,
            "upstart_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:upstart_test_rsc action:start rc:ok op_status:complete\" ",
            "upstart_stop_line"     : "-c exec -r \"upstart_test_rsc\" -a \"stop\" "+self.action_timeout,
            "upstart_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:upstart_test_rsc action:stop rc:ok op_status:complete\" ",
            "upstart_monitor_line"  : '-c exec -r upstart_test_rsc -a monitor -i 2s ' + self.action_timeout,
            "upstart_monitor_event" : '-l "NEW_EVENT event_type:exec_complete rsc_id:upstart_test_rsc action:monitor rc:ok op_status:complete" -t 15000',
            "upstart_cancel_line"   : '-c cancel -r upstart_test_rsc -a monitor -i 2s ' + self.action_timeout,
            "upstart_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:upstart_test_rsc action:monitor rc:ok op_status:Cancelled\" ",

            "service_reg_line"      : "-c register_rsc -r service_test_rsc "+self.action_timeout+" -C service -T LSBDummy",
            "service_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:service_test_rsc action:none rc:ok op_status:complete\"",
            "service_unreg_line"    : "-c unregister_rsc -r \"service_test_rsc\" "+self.action_timeout,
            "service_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:service_test_rsc action:none rc:ok op_status:complete\"",
            "service_start_line"    : "-c exec -r \"service_test_rsc\" -a \"start\" "+self.action_timeout,
            "service_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:service_test_rsc action:start rc:ok op_status:complete\" ",
            "service_stop_line"     : "-c exec -r \"service_test_rsc\" -a \"stop\" "+self.action_timeout,
            "service_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:service_test_rsc action:stop rc:ok op_status:complete\" ",
            "service_monitor_line"  : '-c exec -r service_test_rsc -a monitor -i 2s ' + self.action_timeout,
            "service_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:service_test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout,
            "service_cancel_line"   : '-c cancel -r service_test_rsc -a monitor -i 2s ' + self.action_timeout,
            "service_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:service_test_rsc action:monitor rc:ok op_status:Cancelled\" ",

            "lsb_reg_line"      : "-c register_rsc -r lsb_test_rsc "+self.action_timeout+" -C lsb -T LSBDummy",
            "lsb_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:lsb_test_rsc action:none rc:ok op_status:complete\" ",
            "lsb_unreg_line"    : "-c unregister_rsc -r \"lsb_test_rsc\" "+self.action_timeout,
            "lsb_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:lsb_test_rsc action:none rc:ok op_status:complete\"",
            "lsb_start_line"    : "-c exec -r \"lsb_test_rsc\" -a \"start\" "+self.action_timeout,
            "lsb_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:lsb_test_rsc action:start rc:ok op_status:complete\" ",
            "lsb_stop_line"     : "-c exec -r \"lsb_test_rsc\" -a \"stop\" "+self.action_timeout,
            "lsb_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:lsb_test_rsc action:stop rc:ok op_status:complete\" ",
            "lsb_monitor_line"  : '-c exec -r lsb_test_rsc -a status -i 2s ' + self.action_timeout,
            "lsb_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:lsb_test_rsc action:status rc:ok op_status:complete\" "+self.action_timeout,
            "lsb_cancel_line"   : '-c cancel -r lsb_test_rsc -a status -i 2s ' + self.action_timeout,
            "lsb_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:lsb_test_rsc action:status rc:ok op_status:Cancelled\" ",

            "stonith_reg_line"      : "-c register_rsc -r stonith_test_rsc " + self.action_timeout +
				      " -C stonith -P pacemaker -T fence_dummy",
            "stonith_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:stonith_test_rsc action:none rc:ok op_status:complete\" ",
            "stonith_unreg_line"    : "-c unregister_rsc -r \"stonith_test_rsc\" "+self.action_timeout,
            "stonith_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:stonith_test_rsc action:none rc:ok op_status:complete\"",
            "stonith_start_line"    : '-c exec -r stonith_test_rsc -a start ' + self.action_timeout,
            "stonith_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:start rc:ok op_status:complete\" ",
            "stonith_stop_line"     : "-c exec -r \"stonith_test_rsc\" -a \"stop\" "+self.action_timeout,
            "stonith_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:stop rc:ok op_status:complete\" ",
            "stonith_monitor_line"  : '-c exec -r stonith_test_rsc -a monitor -i 2s ' + self.action_timeout,
            "stonith_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout,
            "stonith_cancel_line"   : '-c cancel -r stonith_test_rsc -a monitor -i 2s ' + self.action_timeout,
            "stonith_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:monitor rc:ok op_status:Cancelled\" ",
        }

    def new_test(self, name, description):
        """ Create a named test """

        test = Test(name, description, self.verbose, self.tls)
        self.tests.append(test)
        return test

    def setup_test_environment(self):
        """ Prepare the host before executing any tests """

        os.system("service pacemaker_remote stop")
        self.cleanup_test_environment()

        if self.tls and not os.path.isfile("/etc/pacemaker/authkey"):
            print("Installing /etc/pacemaker/authkey ...")
            os.system("mkdir -p /etc/pacemaker")
            os.system("dd if=/dev/urandom of=/etc/pacemaker/authkey bs=4096 count=1")
            self.installed_files.append("/etc/pacemaker/authkey")

        # If we're in build directory, install agents if not already installed
        if os.path.exists("%s/cts/cts-exec.in" % BUILD_DIR):

            if not os.path.exists("@OCF_RA_DIR@/pacemaker"):
                # @TODO remember which components were created and remove them
                os.makedirs("@OCF_RA_DIR@/pacemaker", 0o755)

            for agent in ["Dummy", "Stateful", "ping"]:
                agent_source = "%s/extra/resources/%s" % (BUILD_DIR, agent)
                agent_dest = "@OCF_RA_DIR@/pacemaker/%s" % (agent)
                if not os.path.exists(agent_dest):
                    print("Installing %s ..." % (agent_dest))
                    shutil.copyfile(agent_source, agent_dest)
                    os.chmod(agent_dest, EXECMODE)
                    self.installed_files.append(agent_dest)

        subprocess.call(["cts-support", "install"])

    def cleanup_test_environment(self):
        """ Clean up the host after executing desired tests """

        for installed_file in self.installed_files:
            print("Removing %s ..." % (installed_file))
            os.remove(installed_file)

        subprocess.call(["cts-support", "uninstall"])

    def build_generic_tests(self):
        """ Register tests that apply to all resource classes """

        common_cmds = self.common_cmds

        ### register/unregister tests ###
        for rsc in self.rsc_classes:
            test = self.new_test("generic_registration_%s" % (rsc),
                                 "Simple resource registration test for %s standard" % (rsc))
            test.add_cmd(common_cmds["%s_reg_line" % (rsc)] + " " + common_cmds["%s_reg_event" % (rsc)])
            test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])

        ### start/stop tests  ###
        for rsc in self.rsc_classes:
            test = self.new_test("generic_start_stop_%s" % (rsc), "Simple start and stop test for %s standard" % (rsc))
            test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
            test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
            test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
            test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])

        ### monitor cancel test ###
        for rsc in self.rsc_classes:
            test = self.new_test("generic_monitor_cancel_%s" % (rsc),
                                 "Simple monitor cancel test for %s standard" % (rsc))
            test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
            test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
            test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
            ### If this fails, that means the monitor may not be getting rescheduled ####
            test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
            ### If this fails, that means the monitor may not be getting rescheduled ####
            test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
            test.add_cmd(common_cmds["%s_cancel_line" % (rsc)] + " " + common_cmds["%s_cancel_event" % (rsc)])
            ### If this happens the monitor did not actually cancel correctly. ###
            test.add_expected_fail_cmd(common_cmds["%s_monitor_event" % (rsc)], CrmExit.TIMEOUT)
            ### If this happens the monitor did not actually cancel correctly. ###
            test.add_expected_fail_cmd(common_cmds["%s_monitor_event" % (rsc)], CrmExit.TIMEOUT)
            test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
            test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])

        ### monitor duplicate test ###
        for rsc in self.rsc_classes:
            test = self.new_test("generic_monitor_duplicate_%s" % (rsc),
                                 "Test creation and canceling of duplicate monitors for %s standard" % (rsc))
            test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
            test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
            test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
            ### If this fails, that means the monitor may not be getting rescheduled ####
            test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
            ### If this fails, that means the monitor may not be getting rescheduled ####
            test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])

            # Add the duplicate monitors
            test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
            test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
            test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
            test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
            # verify we still get update events
            ### If this fails, that means the monitor may not be getting rescheduled ####
            test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])

            # cancel the monitor, if the duplicate merged with the original, we should no longer see monitor updates
            test.add_cmd(common_cmds["%s_cancel_line" % (rsc)] + " " + common_cmds["%s_cancel_event" % (rsc)])
            ### If this happens the monitor did not actually cancel correctly. ###
            test.add_expected_fail_cmd(common_cmds["%s_monitor_event" % (rsc)], CrmExit.TIMEOUT)
            ### If this happens the monitor did not actually cancel correctly. ###
            test.add_expected_fail_cmd(common_cmds["%s_monitor_event" % (rsc)], CrmExit.TIMEOUT)
            test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
            test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])

        ### stop implies cancel test ###
        for rsc in self.rsc_classes:
            test = self.new_test("generic_stop_implies_cancel_%s" % (rsc),
                                 "Verify stopping a resource implies cancel of recurring ops for %s standard" % (rsc))
            test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
            test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
            test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
            ### If this fails, that means the monitor may not be getting rescheduled ####
            test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
            ### If this fails, that means the monitor may not be getting rescheduled ####
            test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
            test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
            ### If this happens the monitor did not actually cancel correctly. ###
            test.add_expected_fail_cmd(common_cmds["%s_monitor_event" % (rsc)], CrmExit.TIMEOUT)
            ### If this happens the monitor did not actually cancel correctly. ###
            test.add_expected_fail_cmd(common_cmds["%s_monitor_event" % (rsc)], CrmExit.TIMEOUT)
            test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])


    def build_multi_rsc_tests(self):
        """ Register complex tests that involve managing multiple resouces of different types """

        common_cmds = self.common_cmds
        # do not use service and systemd at the same time, it is the same resource.

        ### register start monitor stop unregister resources of each type at the same time. ###
        test = self.new_test("multi_rsc_start_stop_all",
                             "Start, monitor, and stop resources of multiple types and classes")
        for rsc in self.rsc_classes:
            test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
        for rsc in self.rsc_classes:
            test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
        for rsc in self.rsc_classes:
            test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
        for rsc in self.rsc_classes:
            ### If this fails, that means the monitor is not being rescheduled ####
            test.add_cmd(common_cmds["%s_monitor_event" % (rsc)])
        for rsc in self.rsc_classes:
            test.add_cmd(common_cmds["%s_cancel_line" % (rsc)] + " " + common_cmds["%s_cancel_event" % (rsc)])
        for rsc in self.rsc_classes:
            test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
        for rsc in self.rsc_classes:
            test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])

    def build_negative_tests(self):
        """ Register tests related to how pacemaker-execd handles failures """

        ### ocf start timeout test  ###
        test = self.new_test("ocf_start_timeout", "Force start timeout to occur, verify start failure.")
        test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "
                     + self.action_timeout +
                     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
        # -t must be less than self.action_timeout
        test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" -k \"op_sleep\" -v \"5\" -t 1000 -w")
        test.add_cmd('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:error op_status:Timed Out" '
                     + self.action_timeout)
        test.add_cmd("-c exec -r test_rsc -a stop " + self.action_timeout +
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
        test.add_cmd("-c unregister_rsc -r test_rsc " + self.action_timeout +
                     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### stonith start timeout test  ###
        test = self.new_test("stonith_start_timeout", "Force start timeout to occur, verify start failure.")
        test.add_cmd('-c register_rsc -r test_rsc ' +
                     '-C stonith -P pacemaker -T fence_dummy ' +
                     self.action_timeout +
                     '-l "NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete"')
        test.add_cmd('-c exec -r test_rsc -a start -k monitor_delay -v 30 ' +
                     '-t 1000 -w') # -t must be less than self.action_timeout
        test.add_cmd('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:OCF_TIMEOUT op_status:Timed Out" '
                     + self.action_timeout)
        test.add_cmd("-c exec -r test_rsc -a stop " + self.action_timeout +
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
        test.add_cmd("-c unregister_rsc -r test_rsc " + self.action_timeout +
                     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### stonith component fail ###
        common_cmds = self.common_cmds
        test = self.new_test("stonith_component_fail", "Kill stonith component after pacemaker-execd connects")
        test.add_cmd(common_cmds["stonith_reg_line"]   + " " + common_cmds["stonith_reg_event"])
        test.add_cmd(common_cmds["stonith_start_line"] + " " + common_cmds["stonith_start_event"])

        test.add_cmd('-c exec -r stonith_test_rsc -a monitor -i 600s '
                     '-l "NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:monitor rc:ok op_status:complete" '
                     + self.action_timeout)

        test.add_cmd_and_kill("killall -9 -q pacemaker-fenced lt-pacemaker-fenced",
                              '-l "NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:monitor rc:error op_status:error" -t 15000')
        test.add_cmd(common_cmds["stonith_unreg_line"] + " " + common_cmds["stonith_unreg_event"])


        ### monitor fail for ocf resources ###
        test = self.new_test("monitor_fail_ocf", "Force ocf monitor to fail, verify failure is reported.")
        test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "
                     + self.action_timeout +
                     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
        test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" " + self.action_timeout +
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
        test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" " + self.action_timeout +
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
        test.add_cmd('-c exec -r test_rsc -a monitor -i 1s '
                     + self.action_timeout +
                     '-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete"')
        test.add_cmd('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete"'
                     + self.action_timeout)
        test.add_cmd('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete"'
                     + self.action_timeout)
        test.add_cmd_and_kill("rm -f @localstatedir@/run/Dummy-test_rsc.state",
                              '-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete" ' + self.action_timeout)
        test.add_cmd('-c cancel -r test_rsc -a monitor -i 1s ' + self.action_timeout +
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
        test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" "
                                   + self.action_timeout, CrmExit.TIMEOUT)
        test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "
                                   + self.action_timeout, CrmExit.TIMEOUT)
        test.add_cmd("-c unregister_rsc -r \"test_rsc\" "
                     + self.action_timeout +
                     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### verify notify changes only for monitor operation.  ###
        test = self.new_test("monitor_changes_only", "Verify when flag is set, only monitor changes are notified.")
        test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
        test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+" -o "
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
        test.add_cmd('-c exec -r test_rsc -a monitor -i 1s '
                     + self.action_timeout +
                     ' -o -l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete" ')
        test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout, CrmExit.TIMEOUT)
        test.add_cmd_and_kill('rm -f @localstatedir@/run/Dummy-test_rsc.state', '-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete"' + self.action_timeout)
        test.add_cmd('-c cancel -r test_rsc -a monitor -i 1s'
                     + self.action_timeout +
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
        test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" "+self.action_timeout, CrmExit.TIMEOUT)
        test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout, CrmExit.TIMEOUT)
        test.add_cmd('-c unregister_rsc -r "test_rsc" ' + self.action_timeout +
                     '-l "NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete"')

        ### monitor fail for systemd resource ###
        if "systemd" in self.rsc_classes:
            test = self.new_test("monitor_fail_systemd", "Force systemd monitor to fail, verify failure is reported..")
            test.add_cmd("-c register_rsc -r \"test_rsc\" -C systemd -T pacemaker-cts-dummyd@3 " +
                         self.action_timeout +
                         "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
            test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
            test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
            test.add_cmd('-c exec -r test_rsc -a monitor -i 1s '
                         + self.action_timeout +
                         "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
            test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
            test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
            test.add_cmd_and_kill("killall -9 -q pacemaker-cts-dummyd",
                                  '-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete"' + self.action_timeout)
            test.add_cmd('-c cancel -r test_rsc -a monitor -i 1s' + self.action_timeout +
                         "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
            test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" "+self.action_timeout, CrmExit.TIMEOUT)
            test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout, CrmExit.TIMEOUT)
            test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### monitor fail for upstart resource ###
        if "upstart" in self.rsc_classes:
            test = self.new_test("monitor_fail_upstart", "Force upstart monitor to fail, verify failure is reported..")
            test.add_cmd("-c register_rsc -r \"test_rsc\" -C upstart -T pacemaker-cts-dummyd "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
            test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
            test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
            test.add_cmd('-c exec -r test_rsc -a monitor -i 1s ' + self.action_timeout +
                         "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
            test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
            test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
            test.add_cmd_and_kill('killall -9 -q dd', '-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete"' + self.action_timeout)
            test.add_cmd('-c cancel -r test_rsc -a monitor -i 1s'
                         + self.action_timeout +
                         "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
            test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" "+self.action_timeout, CrmExit.TIMEOUT)
            test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout, CrmExit.TIMEOUT)
            test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### Cancel non-existent operation on a resource ###
        test = self.new_test("cancel_non_existent_op", "Attempt to cancel the wrong monitor operation, verify expected failure")
        test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
        test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
        test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
        test.add_cmd('-c exec -r test_rsc -a monitor -i 1s '
                     + self.action_timeout +
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
        test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
        ### interval is wrong, should fail
        test.add_expected_fail_cmd('-c cancel -r test_rsc -a monitor -i 2s' + self.action_timeout +
                                   "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
        ### action name is wrong, should fail
        test.add_expected_fail_cmd('-c cancel -r test_rsc -a stop -i 1s' + self.action_timeout +
                                   "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
        test.add_cmd("-c unregister_rsc -r \"test_rsc\" " + self.action_timeout +
                     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### Attempt to invoke non-existent rsc id ###
        test = self.new_test("invoke_non_existent_rsc", "Attempt to perform operations on a non-existent rsc id.")
        test.add_expected_fail_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                                   "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:error op_status:complete\" ")
        test.add_expected_fail_cmd("-c exec -r test_rsc -a stop "+self.action_timeout+
                                   "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
        test.add_expected_fail_cmd('-c exec -r test_rsc -a monitor -i 6s '
                                   + self.action_timeout +
                                   "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
        test.add_expected_fail_cmd("-c cancel -r test_rsc -a start "+self.action_timeout+
                                   "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:Cancelled\" ")
        test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### Register and start a resource that doesn't exist, systemd  ###
        if "systemd" in self.rsc_classes:
            test = self.new_test("start_uninstalled_systemd", "Register uninstalled systemd agent, try to start, verify expected failure")
            test.add_cmd("-c register_rsc -r \"test_rsc\" -C systemd -T this_is_fake1234 "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
            test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:not installed op_status:Not installed\" ")
            test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        if "upstart" in self.rsc_classes:
            test = self.new_test("start_uninstalled_upstart", "Register uninstalled upstart agent, try to start, verify expected failure")
            test.add_cmd("-c register_rsc -r \"test_rsc\" -C upstart -T this_is_fake1234 "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
            test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:not installed op_status:Not installed\" ")
            test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
                         "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### Register and start a resource that doesn't exist, ocf ###
        test = self.new_test("start_uninstalled_ocf", "Register uninstalled ocf agent, try to start, verify expected failure.")
        test.add_cmd("-c register_rsc -r \"test_rsc\" -C ocf -P pacemaker -T this_is_fake1234 "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
        test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:not installed op_status:Not installed\" ")
        test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### Register ocf with non-existent provider  ###
        test = self.new_test("start_ocf_bad_provider", "Register ocf agent with a non-existent provider, verify expected failure.")
        test.add_cmd("-c register_rsc -r \"test_rsc\" -C ocf -P pancakes -T Dummy "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
        test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:not installed op_status:Not installed\" ")
        test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### Register ocf with empty provider field  ###
        test = self.new_test("start_ocf_no_provider", "Register ocf agent with a no provider, verify expected failure.")
        test.add_expected_fail_cmd("-c register_rsc -r \"test_rsc\" -C ocf -T Dummy "+self.action_timeout+
                                   "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
        test.add_expected_fail_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                                   "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:Error\" ")
        test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

    def build_stress_tests(self):
        """ Register stress tests """

        timeout = "-t 20000"

        iterations = 25
        test = self.new_test("ocf_stress", "Verify OCF agent handling works under load")
        for i in range(iterations):
            test.add_cmd("-c register_rsc -r rsc_%s %s -C ocf -P heartbeat -T Dummy -l \"NEW_EVENT event_type:register rsc_id:rsc_%s action:none rc:ok op_status:complete\"" % (i, timeout, i))
            test.add_cmd("-c exec -r rsc_%s -a start %s -l \"NEW_EVENT event_type:exec_complete rsc_id:rsc_%s action:start rc:ok op_status:complete\"" % (i, timeout, i))
            test.add_cmd('-c exec -r rsc_%s -a monitor %s -i 1s '
                         '-l "NEW_EVENT event_type:exec_complete rsc_id:rsc_%s action:monitor rc:ok op_status:complete"' % (i, timeout, i))
        for i in range(iterations):
            test.add_cmd("-c exec -r rsc_%s -a stop %s  -l \"NEW_EVENT event_type:exec_complete rsc_id:rsc_%s action:stop rc:ok op_status:complete\"" % (i, timeout, i))
            test.add_cmd("-c unregister_rsc -r rsc_%s %s -l \"NEW_EVENT event_type:unregister rsc_id:rsc_%s action:none rc:ok op_status:complete\"" % (i, timeout, i))


        if "systemd" in self.rsc_classes:
            test = self.new_test("systemd_stress", "Verify systemd dbus connection works under load")
            for i in range(iterations):
                test.add_cmd("-c register_rsc -r rsc_%s %s -C systemd -T pacemaker-cts-dummyd@3 -l \"NEW_EVENT event_type:register rsc_id:rsc_%s action:none rc:ok op_status:complete\"" % (i, timeout, i))
                test.add_cmd("-c exec -r rsc_%s -a start %s -l \"NEW_EVENT event_type:exec_complete rsc_id:rsc_%s action:start rc:ok op_status:complete\"" % (i, timeout, i))
                test.add_cmd('-c exec -r rsc_%s -a monitor %s -i 1s '
                             '-l "NEW_EVENT event_type:exec_complete rsc_id:rsc_%s action:monitor rc:ok op_status:complete"' % (i, timeout, i))

            for i in range(iterations):
                test.add_cmd("-c exec -r rsc_%s -a stop %s -l \"NEW_EVENT event_type:exec_complete rsc_id:rsc_%s action:stop rc:ok op_status:complete\"" % (i, timeout, i))
                test.add_cmd("-c unregister_rsc -r rsc_%s %s -l \"NEW_EVENT event_type:unregister rsc_id:rsc_%s action:none rc:ok op_status:complete\"" % (i, timeout, i))

        iterations = 9
        timeout = "-t 30000"
        ### Verify recurring op in-flight collision is handled in series properly
        test = self.new_test("rsc_inflight_collision", "Verify recurring ops do not collide with other operations for the same rsc.")
        test.add_cmd("-c register_rsc -r test_rsc -P pacemaker -C ocf -T Dummy "
                     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" "+self.action_timeout)
        test.add_cmd("-c exec -r test_rsc -a start %s -k op_sleep -v 1 -l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\"" % (timeout))
        for i in range(iterations):
            test.add_cmd('-c exec -r test_rsc -a monitor %s -i 100%dms '
                         '-k op_sleep -v 2 '
                         '-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete"' % (timeout, i))

        test.add_cmd("-c exec -r test_rsc -a stop %s -l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\"" % (timeout))
        test.add_cmd("-c unregister_rsc -r test_rsc %s -l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\"" % (timeout))

    def build_custom_tests(self):
        """ Register tests that target specific cases """

        ### verify resource temporary folder is created and used by OCF agents.  ###
        test = self.new_test("rsc_tmp_dir", "Verify creation and use of rsc temporary state directory")
        test.add_sys_cmd("ls", "-al @CRM_RSCTMP_DIR@")
        test.add_cmd("-c register_rsc -r test_rsc -P heartbeat -C ocf -T Dummy "
                     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" "+self.action_timeout)
        test.add_cmd("-c exec -r test_rsc -a start -t 4000")
        test.add_sys_cmd("ls", "-al @CRM_RSCTMP_DIR@")
        test.add_sys_cmd("ls", "@CRM_RSCTMP_DIR@/Dummy-test_rsc.state")
        test.add_cmd("-c exec -r test_rsc -a stop -t 4000")
        test.add_cmd("-c unregister_rsc -r test_rsc "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### start delay then stop test ###
        test = self.new_test("start_delay", "Verify start delay works as expected.")
        test.add_cmd("-c register_rsc -r test_rsc -P pacemaker -C ocf -T Dummy "
                     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" "+self.action_timeout)
        test.add_cmd("-c exec -r test_rsc -s 6000 -a start -w -t 6000")
        test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" -t 2000", CrmExit.TIMEOUT)
        test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" -t 6000")
        test.add_cmd("-c exec -r test_rsc -a stop " + self.action_timeout +
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
        test.add_cmd("-c unregister_rsc -r test_rsc " + self.action_timeout +
                     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### start delay, but cancel before it gets a chance to start.  ###
        test = self.new_test("start_delay_cancel", "Using start_delay, start a rsc, but cancel the start op before execution.")
        test.add_cmd("-c register_rsc -r test_rsc -P pacemaker -C ocf -T Dummy "
                     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" "+self.action_timeout)
        test.add_cmd("-c exec -r test_rsc -s 5000 -a start -w -t 4000")
        test.add_cmd("-c cancel -r test_rsc -a start " + self.action_timeout +
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:Cancelled\" ")
        test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" -t 5000", CrmExit.TIMEOUT)
        test.add_cmd("-c unregister_rsc -r test_rsc " + self.action_timeout +
                     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### Register a bunch of resources, verify we can get info on them ###
        test = self.new_test("verify_get_rsc_info", "Register multiple resources, verify retrieval of rsc info.")
        if "systemd" in self.rsc_classes:
            test.add_cmd("-c register_rsc -r rsc1 -C systemd -T pacemaker-cts-dummyd@3 "+self.action_timeout)
            test.add_cmd("-c get_rsc_info -r rsc1 ")
            test.add_cmd("-c unregister_rsc -r rsc1 "+self.action_timeout)
            test.add_expected_fail_cmd("-c get_rsc_info -r rsc1 ")

        if "upstart" in self.rsc_classes:
            test.add_cmd("-c register_rsc -r rsc1 -C upstart -T pacemaker-cts-dummyd "+self.action_timeout)
            test.add_cmd("-c get_rsc_info -r rsc1 ")
            test.add_cmd("-c unregister_rsc -r rsc1 "+self.action_timeout)
            test.add_expected_fail_cmd("-c get_rsc_info -r rsc1 ")

        test.add_cmd("-c register_rsc -r rsc2 -C ocf -T Dummy -P pacemaker "+self.action_timeout)
        test.add_cmd("-c get_rsc_info -r rsc2 ")
        test.add_cmd("-c unregister_rsc -r rsc2 "+self.action_timeout)
        test.add_expected_fail_cmd("-c get_rsc_info -r rsc2 ")

        ### Register duplicate, verify only one entry exists and can still be removed.
        test = self.new_test("duplicate_registration", "Register resource multiple times, verify only one entry exists and can be removed.")
        test.add_cmd("-c register_rsc -r rsc2 -C ocf -T Dummy -P pacemaker "+self.action_timeout)
        test.add_cmd_check_stdout("-c get_rsc_info -r rsc2 ", "id:rsc2 class:ocf provider:pacemaker type:Dummy")
        test.add_cmd("-c register_rsc -r rsc2 -C ocf -T Dummy -P pacemaker "+self.action_timeout)
        test.add_cmd_check_stdout("-c get_rsc_info -r rsc2 ", "id:rsc2 class:ocf provider:pacemaker type:Dummy")
        test.add_cmd("-c register_rsc -r rsc2 -C ocf -T Stateful -P pacemaker "+self.action_timeout)
        test.add_cmd_check_stdout("-c get_rsc_info -r rsc2 ", "id:rsc2 class:ocf provider:pacemaker type:Stateful")
        test.add_cmd("-c unregister_rsc -r rsc2 "+self.action_timeout)
        test.add_expected_fail_cmd("-c get_rsc_info -r rsc2 ")

        ### verify the option to only send notification to the original client. ###
        test = self.new_test("notify_orig_client_only", "Verify option to only send notifications to the client originating the action.")
        test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
        test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
        test.add_cmd('-c exec -r \"test_rsc\" -a \"monitor\" -i 1s '
                     + self.action_timeout + ' -n '
                     '-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete"')
        # this will fail because the monitor notifications should only go to the original caller, which no longer exists.
        test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout, CrmExit.TIMEOUT)
        test.add_cmd('-c cancel -r test_rsc -a monitor -i 1s -t 6000 ')
        test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
                     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")

        ### get metadata ###
        test = self.new_test("get_ocf_metadata", "Retrieve metadata for a resource")
        test.add_cmd_check_stdout("-c metadata -C \"ocf\" -P \"pacemaker\" -T \"Dummy\"",
                                  "resource-agent name=\"Dummy\"")
        test.add_cmd("-c metadata -C \"ocf\" -P \"pacemaker\" -T \"Stateful\"")
        test.add_expected_fail_cmd("-c metadata -P \"pacemaker\" -T \"Stateful\"")
        test.add_expected_fail_cmd("-c metadata -C \"ocf\" -P \"pacemaker\" -T \"fake_agent\"")

        ### get metadata ###
        test = self.new_test("get_lsb_metadata", "Retrieve metadata for a resource")
        test.add_cmd_check_stdout("-c metadata -C \"lsb\" -T \"LSBDummy\"",
                                  "resource-agent name='LSBDummy'")

        ### get stonith metadata ###
        test = self.new_test("get_stonith_metadata", "Retrieve stonith metadata for a resource")
        test.add_cmd_check_stdout("-c metadata -C \"stonith\" -P \"pacemaker\" -T \"fence_dummy\"",
                                  "resource-agent name=\"fence_dummy\"")

        ### get metadata ###
        if "systemd" in self.rsc_classes:
            test = self.new_test("get_systemd_metadata", "Retrieve metadata for a resource")
            test.add_cmd_check_stdout("-c metadata -C \"systemd\" -T \"pacemaker-cts-dummyd@\"",
                                      "resource-agent name=\"pacemaker-cts-dummyd@\"")

        ### get metadata ###
        if "upstart" in self.rsc_classes:
            test = self.new_test("get_upstart_metadata", "Retrieve metadata for a resource")
            test.add_cmd_check_stdout("-c metadata -C \"upstart\" -T \"pacemaker-cts-dummyd\"",
                                      "resource-agent name=\"pacemaker-cts-dummyd\"")

        ### get ocf providers  ###
        test = self.new_test("list_ocf_providers",
                             "Retrieve list of available resource providers, verifies pacemaker is a provider.")
        test.add_cmd_check_stdout("-c list_ocf_providers ", "pacemaker")
        test.add_cmd_check_stdout("-c list_ocf_providers -T ping", "pacemaker")

        ### Verify agents only exist in their lists ###
        test = self.new_test("verify_agent_lists", "Verify the agent lists contain the right data.")
        test.add_cmd_check_stdout("-c list_agents ", "Stateful")                                  ### ocf ###
        test.add_cmd_check_stdout("-c list_agents -C ocf", "Stateful")
        test.add_cmd_check_stdout("-c list_agents -C lsb", "", "Stateful")                        ### should not exist
        test.add_cmd_check_stdout("-c list_agents -C service", "", "Stateful")                    ### should not exist
        test.add_cmd_check_stdout("-c list_agents ", "LSBDummy")                                  ### init.d ###
        test.add_cmd_check_stdout("-c list_agents -C lsb", "LSBDummy")
        test.add_cmd_check_stdout("-c list_agents -C service", "LSBDummy")
        test.add_cmd_check_stdout("-c list_agents -C ocf", "", "pacemaker-cts-dummyd@")           ### should not exist

        test.add_cmd_check_stdout("-c list_agents -C ocf", "", "pacemaker-cts-dummyd@")           ### should not exist
        test.add_cmd_check_stdout("-c list_agents -C lsb", "", "fence_dummy")                     ### should not exist
        test.add_cmd_check_stdout("-c list_agents -C service", "", "fence_dummy")                 ### should not exist
        test.add_cmd_check_stdout("-c list_agents -C ocf", "", "fence_dummy")                     ### should not exist

        if "systemd" in self.rsc_classes:
            test.add_cmd_check_stdout("-c list_agents ", "pacemaker-cts-dummyd@")             ### systemd ###
            test.add_cmd_check_stdout("-c list_agents -C service", "LSBDummy")
            test.add_cmd_check_stdout("-c list_agents -C systemd", "", "Stateful")            ### should not exist
            test.add_cmd_check_stdout("-c list_agents -C systemd", "pacemaker-cts-dummyd@")
            test.add_cmd_check_stdout("-c list_agents -C systemd", "", "fence_dummy")         ### should not exist

        if "upstart" in self.rsc_classes:
            test.add_cmd_check_stdout("-c list_agents ", "pacemaker-cts-dummyd")              ### upstart ###
            test.add_cmd_check_stdout("-c list_agents -C service", "LSBDummy")
            test.add_cmd_check_stdout("-c list_agents -C upstart", "", "Stateful")            ### should not exist
            test.add_cmd_check_stdout("-c list_agents -C upstart", "pacemaker-cts-dummyd")
            test.add_cmd_check_stdout("-c list_agents -C upstart", "", "fence_dummy")         ### should not exist

        if "stonith" in self.rsc_classes:
            test.add_cmd_check_stdout("-c list_agents -C stonith", "fence_dummy")             ### stonith ###
            test.add_cmd_check_stdout("-c list_agents -C stonith", "", "pacemaker-cts-dummyd@") ### should not exist
            test.add_cmd_check_stdout("-c list_agents -C stonith", "", "Stateful")            ### should not exist
            test.add_cmd_check_stdout("-c list_agents ", "fence_dummy")

    def print_list(self):
        """ List all registered tests """

        print("\n==== %d TESTS FOUND ====" % (len(self.tests)))
        print("%35s - %s" % ("TEST NAME", "TEST DESCRIPTION"))
        print("%35s - %s" % ("--------------------", "--------------------"))
        for test in self.tests:
            print("%35s - %s" % (test.name, test.description))
        print("==== END OF LIST ====\n")

    def run_single(self, name):
        """ Run a single named test """

        for test in self.tests:
            if test.name == name:
                test.run()
                break

    def run_tests_matching(self, pattern):
        """ Run all tests whose name matches a pattern """

        for test in self.tests:
            if test.name.count(pattern) != 0:
                test.run()

    def run_tests(self):
        """ Run all tests """

        for test in self.tests:
            test.run()

    def exit(self):
        """ Exit (with error status code if any test failed) """

        for test in self.tests:
            if test.executed == 0:
                continue

            if test.get_exitcode() != CrmExit.OK:
                sys.exit(CrmExit.ERROR)

        sys.exit(CrmExit.OK)

    def print_results(self):
        """ Print summary of results of executed tests """

        failures = 0
        success = 0
        print("\n\n======= FINAL RESULTS ==========")
        print("\n--- FAILURE RESULTS:")
        for test in self.tests:
            if test.executed == 0:
                continue

            if test.get_exitcode() != CrmExit.OK:
                failures = failures + 1
                test.print_result("    ")
            else:
                success = success + 1

        if failures == 0:
            print("    None")

        print("\n--- TOTALS\n    Pass:%d\n    Fail:%d\n" % (success, failures))


class TestOptions(object):
    """ Option handler """

    def __init__(self):
        self.options = {}
        self.options['list-tests'] = 0
        self.options['run-all'] = 1
        self.options['run-only'] = ""
        self.options['run-only-pattern'] = ""
        self.options['verbose'] = 0
        self.options['invalid-arg'] = ""
        self.options['show-usage'] = 0
        self.options['pacemaker-remote'] = 0

    def build_options(self, argv):
        """ Set options based on command-line arguments """

        args = argv[1:]
        skip = 0
        for i in range(0, len(args)):
            if skip:
                skip = 0
                continue
            elif args[i] == "-h" or args[i] == "--help":
                self.options['show-usage'] = 1
            elif args[i] == "-l" or args[i] == "--list-tests":
                self.options['list-tests'] = 1
            elif args[i] == "-V" or args[i] == "--verbose":
                self.options['verbose'] = 1
            elif args[i] == "-R" or args[i] == "--pacemaker-remote":
                self.options['pacemaker-remote'] = 1
            elif args[i] == "-r" or args[i] == "--run-only":
                self.options['run-only'] = args[i+1]
                skip = 1
            elif args[i] == "-p" or args[i] == "--run-only-pattern":
                self.options['run-only-pattern'] = args[i+1]
                skip = 1

    def show_usage(self):
        """ Show command usage """

        print("usage: " + sys.argv[0] + " [options]")
        print("If no options are provided, all tests will run")
        print("Options:")
        print("\t [--help | -h]                        Show usage")
        print("\t [--list-tests | -l]                  Print out all registered tests.")
        print("\t [--run-only | -r 'testname']         Run a specific test")
        print("\t [--verbose | -V]                     Verbose output")
        print("\t [--pacemaker-remote | -R             Test pacemaker-remoted binary instead of pacemaker-execd")
        print("\t [--run-only-pattern | -p 'string']   Run only tests containing the string value")
        print("\n\tExample: Run only the test 'start_stop'")
        print("\t\t " + sys.argv[0] + " --run-only start_stop")
        print("\n\tExample: Run only the tests with the string 'systemd' present in them")
        print("\t\t " + sys.argv[0] + " --run-only-pattern systemd")


def main(argv):
    """ Run pacemaker-execd regression tests as specified by arguments """

    update_path()

    opts = TestOptions()
    opts.build_options(argv)

    tests = Tests(opts.options['verbose'], opts.options['pacemaker-remote'])

    tests.build_generic_tests()
    tests.build_multi_rsc_tests()
    tests.build_negative_tests()
    tests.build_custom_tests()
    tests.build_stress_tests()

    tests.setup_test_environment()

    print("Starting ...")

    if opts.options['list-tests']:
        tests.print_list()
    elif opts.options['show-usage']:
        opts.show_usage()
    elif opts.options['run-only-pattern'] != "":
        tests.run_tests_matching(opts.options['run-only-pattern'])
        tests.print_results()
    elif opts.options['run-only'] != "":
        tests.run_single(opts.options['run-only'])
        tests.print_results()
    else:
        tests.run_tests()
        tests.print_results()

    tests.cleanup_test_environment()
    tests.exit()


if __name__ == "__main__":
    main(sys.argv)