[asterisk-commits] Add the ability to execute the Test Suite against a remote i... (testsuite[master])

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri Oct 30 11:49:24 CDT 2015


Anonymous Coward #1000019 has submitted this change and it was merged.

Change subject: Add the ability to execute the Test Suite against a remote instance of Asterisk
......................................................................


Add the ability to execute the Test Suite against a remote instance of Asterisk

The normal operation of the Test Suite is for it to manage and control local
instances of Asterisk, sand-boxing as many instances as a test needs. This
works well when what is tested are ideal, logical blocks of functionality.
There are, however, a few drawbacks to this approach:
(1) The Test Suite runs on the same machine as the n instances of Asterisk that
    it ochestrates. While for some smaller tests this is fine, for some
    scenarios, this has drawbacks as it may affect the system under test.
(2) The Test Suite has to 'repeat itself' for large categories of tests. If
    what is being tested is a small piece of functionality, this repetition is
    merely a nuisance. For larger, complex systems, this is more problematic.

Fundamentally altering the nature of the Test Suite is clearly not a good idea:
we have hundreds of tests that assume they (a) control the configuration of the
system under test, and (b) that the system under test is on the same machine.
At the same time, the Test Suite has a lot of libraries and functionality that
make it suitable for testing more complex systems in which Asterisk is merely
a piece.

This patch makes that possible.

The Test Suite can now be placed into another 'mode' of operation. This mode
fundamentally changes how the management of Asterisk processes works, such that
the 'normal' tests in the Test Suite tree are non-operational. Instead, it
switches into a 'remote' mode, where it assumes that the Asterisk instances
under test exist on a remote system. The 'remote' mode is triggered by a
specific configuration in the global test-config.yaml file.

In remote mode, several changes occur:
(1) All CLI interaction with the remote Asterisk instances occurs over SSH.
    SSH credentials are provided for each remote Asterisk instance in the
    global test-config.yaml file.
(2) No configuration of the remote system is explicitly handled by the
    Asterisk class. Instead, it is assumed that another pluggable module
    would provide that work, or that the system has a static configuration
    that the test is aware of.
(3) We no longer attempt to start or stop Asterisk. Instead, we merely check
    that Asterisk is indeed fully booted. Tests will still receive callbacks
    when Asterisk has been 'started' and 'stopped', which merely reflect
    the beginning/ending of a test.
(4) AMI/AGI factories can now be pointed at a remote system. ARI always had
    this ability, given proper module configuration.
(5) Dependency testing is much more limited. Since the Asterisk system(s) are
    remote, and we may have many systems we are testing against, the following
    are not checked:
    - Asterisk versions
    - Asterisk build options
    - Asterisk module dependencies

Sample YAML demonstrating how to switch the Test Suite into remote mode has
been provided as well.

Change-Id: I59a7632e5a1cb38c46484c5035f36993139b7f3f
---
M lib/python/asterisk/asterisk.py
M lib/python/asterisk/test_case.py
M lib/python/asterisk/test_config.py
M sample-yaml/test-config.yaml.sample
M test-config.yaml
5 files changed, 354 insertions(+), 87 deletions(-)

Approvals:
  Mark Michelson: Looks good to me, but someone else must approve
  Anonymous Coward #1000019: Verified
  Matt Jordan: Looks good to me, approved
  Joshua Colp: Looks good to me, but someone else must approve



diff --git a/lib/python/asterisk/asterisk.py b/lib/python/asterisk/asterisk.py
index a287643..855c621 100755
--- a/lib/python/asterisk/asterisk.py
+++ b/lib/python/asterisk/asterisk.py
@@ -23,8 +23,124 @@
 from version import AsteriskVersion
 
 from twisted.internet import reactor, protocol, defer, utils, error
+from twisted.python.failure import Failure
+
+REMOTE_ERROR = None
+try:
+    from twisted.python.filepath import FilePath
+    from twisted.internet.endpoints import UNIXClientEndpoint
+    from twisted.conch.ssh.keys import Key
+    from twisted.conch.client.knownhosts import KnownHostsFile
+    from twisted.conch.endpoints import SSHCommandClientEndpoint
+    from twisted.internet.error import ConnectionDone, ProcessTerminated
+except ImportError as ie:
+    # We cache any import errors here, as there's no point in bothering
+    # things if we don't need the SSH connection to a remote Asterisk instance
+    REMOTE_ERROR = ie
+
 
 LOGGER = logging.getLogger(__name__)
+
+
+class AsteriskRemoteProtocol(protocol.Protocol):
+    """Class that acts as a remote protocol to Asterisk"""
+
+    def connectionMade(self):
+        """Called on connect"""
+        LOGGER.debug('Connection made to remote Asterisk instance')
+        self.output = ''
+        self.finished = defer.Deferred()
+
+    def dataReceived(self, data):
+        """Called when we receive data back"""
+        LOGGER.debug(data)
+        self.output += data
+
+    def connectionLost(self, reason):
+        """Called when the connect is lost"""
+        if reason.type == ProcessTerminated:
+            self.output += reason.getErrorMessage()
+            self.finished.errback(self)
+        else:
+            self.finished.callback(self)
+
+
+class AsteriskRemoteCliCommand(object):
+    """Class that attempts to manipulate a remote Asterisk CLI"""
+
+    def __init__(self, remote_config, cmd):
+        """Create a new remote Astrisk CLI Protocol instance
+
+        Keyword Arguments:
+        remote_config The parameters configuring this remote CLI command
+        cmd           The CLI command to run
+        """
+        if REMOTE_ERROR:
+            raise REMOTE_ERROR
+
+        self.exitcode = -1
+        self.output = ""
+        self.err = ""
+
+        self.config = remote_config
+        self.cmd = cmd
+        self.keys = []
+
+        identity = self.config.get('identity')
+        if identity:
+            key_path = os.path.expanduser(identity)
+            if os.path.exists(key_path):
+                passphrase = self.config.get('passphrase')
+                self.keys.append(Key.fromFile(key_path, passphrase=passphrase))
+
+        known_hosts_file = self.config.get('known_hosts', '~/.ssh/known_hosts')
+        known_hosts_path = FilePath(os.path.expanduser(known_hosts_file))
+        if known_hosts_path.exists():
+            self.known_hosts = KnownHostsFile.fromPath(known_hosts_path)
+        else:
+            self.known_hosts = None
+
+        no_agent = self.config.get('no-agent')
+        if no_agent or 'SSH_AUTH_SOCK' not in os.environ:
+            self.agent_endpoint = None
+        else:
+            self.agent_endpoint = UNIXClientEndpoint(reactor,
+                                                     os.environ['SSH_AUTH_SOCK'])
+
+    def execute(self):
+        """Execute the CLI command"""
+
+        cmd = " ".join(self.cmd)
+        LOGGER.debug('Executing {0}'.format(cmd))
+        endpoint = SSHCommandClientEndpoint.newConnection(reactor,
+            b"{0}".format(cmd), self.config.get('username'),
+            self.config.get('host'),
+            port=self.config.get('port', 22),
+            password=self.config.get('passsword'),
+            agentEndpoint=self.agent_endpoint,
+            keys=self.keys,
+            knownHosts=self.known_hosts)
+        factory = protocol.Factory()
+        factory.protocol = AsteriskRemoteProtocol
+
+        def _nominal(param):
+            self.exitcode = 0
+            self.output = param.output
+            self.err = self.output
+            LOGGER.debug('Remote Asterisk process completed successfully')
+            return self
+
+        def _error(param):
+            self.exitcode = -1
+            self.output = param.value.output
+            self.err = self.output
+            LOGGER.warning('Remote Asterisk process failed: {0}'.format(self.err))
+            return Failure(self)
+
+        deferred = endpoint.connect(factory)
+        deferred.addCallback(lambda proto: proto.finished)
+        deferred.addCallbacks(_nominal, _error)
+        return deferred
 
 
 class AsteriskCliCommand(object):
@@ -151,9 +267,10 @@
 class Asterisk(object):
     """An instance of Asterisk.
 
-    This class assumes that Asterisk has been installed on the system.  The
-    installed version of Asterisk will be mirrored for this dynamically created
-    instance.
+    This class has two modes of operation, depending on how it is instantiated.
+    In the first, the class assumes that Asterisk has been installed on the system.
+    The installed version of Asterisk will be mirrored for this dynamically created
+    instance. The following applies to instances of Asterisk controlled locally.
 
     Instantiate an Asterisk object to create an instance of Asterisk from
     within python code.  Multiple instances of Asterisk are allowed.  However,
@@ -166,6 +283,15 @@
     dictionary to the ast_conf_options parameter.  The key should be the option
     name and the value is the option's value to be written out into
     asterisk.conf.
+
+    In the second mode of operation, this class provides a facade over some
+    remote running instance of Asterisk. In this case, the configuration of
+    that instance of Asterisk is assumed to be set, and any properties passed
+    to this class that may attempt to configure the remote instance of Asterisk
+    are ignored. Instead, a block of configuration is provided to this class
+    that defines how it should connect (over SSH) to the remote instance.
+    Users of this class can manipulate the remote instance using CLI commands
+    in the same fashion as if the instance were local.
 
     If AST_TEST_ROOT is unset (the default):
 
@@ -211,13 +337,22 @@
         # The default etc directory for Asterisk
         default_etc_directory = "/etc/asterisk"
 
-    def __init__(self, base=None, ast_conf_options=None, host="127.0.0.1"):
+    def __init__(self, base=None, ast_conf_options=None, host="127.0.0.1",
+                 remote_config=None):
         """Construct an Asterisk instance.
 
         Keyword Arguments:
         base -- This is the root of the files associated with this instance of
-        Asterisk.  By default, the base is "/tmp/asterisk-testsuite" directory.
-        Given a base, it will be appended to the default base directory.
+                Asterisk. By default, the base is "/tmp/asterisk-testsuite"
+                directory. Given a base, it will be appended to the default base
+                directory.
+        ast_conf_options -- Configuration overrides for asterisk.conf.
+        host -- The IP address the Asterisk instance runs on
+        remote_config -- Configuration section that defines this as a remote
+                         Asterisk instance. If provided, base and
+                         ast_conf_options are generally ignored, and the
+                         Asterisk instance's configuration is treated as
+                         immutable on some remote machine defined by 'host'
 
         Example Usage:
         self.asterisk = Asterisk(base="manager/login")
@@ -226,11 +361,16 @@
         self._stop_deferred = None
         self._stop_cancel_tokens = []
         self.directories = {}
-        self.ast_version = AsteriskVersion()
-        self.process_protocol = None
+        self.protocol = None
         self.process = None
         self.astetcdir = ""
         self.original_astmoddir = ""
+        self.ast_version = None
+        self.remote_config = remote_config
+
+        # If the process is remote, don't bother
+        if not self.remote_config:
+            self.ast_version = AsteriskVersion()
 
         valgrind_env = os.getenv("VALGRIND_ENABLE") or ""
         self.valgrind_enabled = True if "true" in valgrind_env else False
@@ -247,46 +387,56 @@
         self.host = host
 
         self._ast_conf_options = ast_conf_options
-        self._directory_structure_made = False
-        self._configs_installed = False
-        self._configs_set_up = False
 
-        # Find the system installed asterisk.conf
-        ast_confs = [
-            os.path.join(self.default_etc_directory, "asterisk.conf"),
-            "/usr/local/etc/asterisk/asterisk.conf",
-        ]
-        self._ast_conf = None
-        for config in ast_confs:
-            if os.path.exists(config):
-                self._ast_conf = ConfigFile(config)
-                break
-        if self._ast_conf is None:
-            msg = "Unable to locate asterisk.conf in any known location"
-            LOGGER.error(msg)
-            raise Exception(msg)
+        if self.remote_config:
+            # Pretend as if we made the structure
+            self._directory_structure_made = True
+            self._configs_installed = True
+            self._configs_set_up = True
 
-        # Set which astxxx this instance will be
-        i = 1
-        while True:
-            if not os.path.isdir("%s/ast%d" % (self.base, i)):
-                self.base = "%s/ast%d" % (self.base, i)
-                break
-            i += 1
+            # And assume we've got /etc/asterisk/ where we expect it to be
+            self.astetcdir = "/etc/asterisk"
+        else:
+            self._directory_structure_made = False
+            self._configs_installed = False
+            self._configs_set_up = False
 
-        # Get the Asterisk directories from the Asterisk config file
-        for cat in self._ast_conf.categories:
-            if cat.name == "directories":
-                for (var, val) in cat.options:
-                    self.directories[var] = val
+            # Find the system installed asterisk.conf
+            ast_confs = [
+                os.path.join(self.default_etc_directory, "asterisk.conf"),
+                "/usr/local/etc/asterisk/asterisk.conf",
+            ]
+            self._ast_conf = None
+            for config in ast_confs:
+                if os.path.exists(config):
+                    self._ast_conf = ConfigFile(config)
+                    break
+            if self._ast_conf is None:
+                msg = "Unable to locate asterisk.conf in any known location"
+                LOGGER.error(msg)
+                raise Exception(msg)
 
-        # self.original_astmoddir is for dependency checking only
-        if "astmoddir" in self.directories:
-            if self.localtest_root:
-                self.original_astmoddir = "%s%s" % (
-                    self.localtest_root, self.directories["astmoddir"])
-            else:
-                self.original_astmoddir = self.directories["astmoddir"]
+            # Set which astxxx this instance will be
+            i = 1
+            while True:
+                if not os.path.isdir("%s/ast%d" % (self.base, i)):
+                    self.base = "%s/ast%d" % (self.base, i)
+                    break
+                i += 1
+
+            # Get the Asterisk directories from the Asterisk config file
+            for cat in self._ast_conf.categories:
+                if cat.name == "directories":
+                    for (var, val) in cat.options:
+                        self.directories[var] = val
+
+            # self.original_astmoddir is for dependency checking only
+            if "astmoddir" in self.directories:
+                if self.localtest_root:
+                    self.original_astmoddir = "%s%s" % (
+                        self.localtest_root, self.directories["astmoddir"])
+                else:
+                    self.original_astmoddir = self.directories["astmoddir"]
 
     def start(self, deps=None):
         """Start this instance of Asterisk.
@@ -304,13 +454,17 @@
         def __start_asterisk_callback(cmd):
             """Begin the Asterisk startup cycle"""
 
-            self.process_protocol = AsteriskProtocol(self.host,
-                                                     self._stop_deferred)
-            self.process = reactor.spawnProcess(self.process_protocol,
+            self.__start_asterisk_time = time.time()
+
+            if self.remote_config:
+                reactor.callLater(0, __execute_wait_fully_booted)
+                return
+
+            self.protocol = AsteriskProtocol(self.host, self._stop_deferred)
+            self.process = reactor.spawnProcess(self.protocol,
                                                 cmd[0],
                                                 cmd, env=os.environ)
             # Begin the wait fully booted cycle
-            self.__start_asterisk_time = time.time()
             reactor.callLater(1, __execute_wait_fully_booted)
 
         def __execute_wait_fully_booted():
@@ -416,7 +570,7 @@
         def __send_stop_gracefully():
             """Send a core stop gracefully CLI command"""
             LOGGER.debug('sending stop gracefully')
-            if self.ast_version < AsteriskVersion("1.6.0"):
+            if self.ast_version and self.ast_version < AsteriskVersion("1.6.0"):
                 cli_deferred = self.cli_exec("stop gracefully")
             else:
                 cli_deferred = self.cli_exec("core stop gracefully")
@@ -436,7 +590,7 @@
         def __send_kill():
             """Check to see if the process is running and kill it with fire"""
             try:
-                if not self.process_protocol.exited:
+                if self.process and not self.protocol.exited:
                     LOGGER.warning("Sending KILL to Asterisk %s" % self.host)
                     self.process.signalProcess("KILL")
             except error.ProcessExitedAlready:
@@ -463,7 +617,11 @@
             self._stop_deferred.callback(reason)
             return reason
 
-        if self.process_protocol.exited:
+        if not self.process:
+            reactor.callLater(0, __process_stopped, None)
+            return self._stop_deferred
+
+        if self.protocol.exited:
             try:
                 if not self._stop_deferred.called:
                     self._stop_deferred.callback(
@@ -588,7 +746,7 @@
             return
 
         tmp = "%s/%s/%s" % (os.path.dirname(cfg_path),
-                            self.ast_version.branch,
+                            self.ast_version.branch if self.ast_version else '',
                             os.path.basename(cfg_path))
         if os.path.exists(tmp):
             cfg_path = tmp
@@ -675,7 +833,7 @@
                 "<tech/data> application <appname> appdata\n"
                 "<tech/data> extension <exten>@<context>")
 
-        if self.ast_version < AsteriskVersion("1.6.2"):
+        if self.ast_version and self.ast_version < AsteriskVersion("1.6.2"):
             return self.cli_exec("originate %s" % argstr)
         else:
             return self.cli_exec("channel originate %s" % argstr)
@@ -692,14 +850,21 @@
         Example Usage:
         asterisk.cli_exec("core set verbose 10")
         """
+        # If this is going to a remote system, make sure we enclose
+        # the command in quotes
+        if self.remote_config:
+            cli_cmd = '"{0}"'.format(cli_cmd)
+
         cmd = [
             self.ast_binary,
             "-C", "%s" % os.path.join(self.astetcdir, "asterisk.conf"),
             "-rx", "%s" % cli_cmd
         ]
         LOGGER.debug("Executing %s ..." % cmd)
-
-        cli_protocol = AsteriskCliCommand(self.host, cmd)
+        if not self.remote_config:
+            cli_protocol = AsteriskCliCommand(self.host, cmd)
+        else:
+            cli_protocol = AsteriskRemoteCliCommand(self.remote_config, cmd)
         return cli_protocol.execute()
 
     def _make_directory_structure(self):
diff --git a/lib/python/asterisk/test_case.py b/lib/python/asterisk/test_case.py
index 6b76aaf..ce06b9e 100644
--- a/lib/python/asterisk/test_case.py
+++ b/lib/python/asterisk/test_case.py
@@ -215,36 +215,66 @@
         self.condition_controller.register_observer(
             self.handle_condition_failure, 'Failed')
 
+    def get_asterisk_hosts(self, count):
+        """Return a list of host dictionaries for Asterisk instances
+
+        Keyword Arguments:
+        count  The number of Asterisk instances to create, if no remote
+               Asterisk instances have been specified
+        """
+        if self.global_config.config:
+            asterisks = self.global_config.config.get('asterisk-instances')
+        else:
+            asterisks = [{'num': i + 1, 'host': '127.0.0.%d' % (i + 1)}
+                         for i in range(count)]
+        return asterisks
+
     def create_asterisk(self, count=1, base_configs_path=None):
         """Create n instances of Asterisk
+
+        Note: if the instances of Asterisk being created are remote, the
+        keyword arguments to this function are ignored.
 
         Keyword arguments:
         count             The number of Asterisk instances to create.  Each
                           Asterisk instance will be hosted on 127.0.0.x, where x
-                          is the 1-based index of the instance created
+                          is the 1-based index of the instance created.
         base_configs_path Provides common configuration for Asterisk instances
                           to use. This is useful for certain test types that use
                           the same configuration all the time. This
                           configuration can be overwritten by individual tests,
                           however.
         """
-        for i in range(count):
-            num = i + 1
-            LOGGER.info("Creating Asterisk instance %d" % num)
-            host = "127.0.0.%d" % num
-            self.ast.append(Asterisk(base=self.testlogdir, host=host,
-                                     ast_conf_options=self.ast_conf_options))
+        for i, ast_config in enumerate(self.get_asterisk_hosts(count)):
+            local_num = ast_config.get('num')
+            host = ast_config.get('host')
+
+            if not host:
+                msg = "Cannot manage Asterisk instance without 'host'"
+                raise Exception(msg)
+
+            if local_num:
+                LOGGER.info("Creating Asterisk instance %d" % local_num)
+                ast_instance = Asterisk(base=self.testlogdir, host=host,
+                                        ast_conf_options=self.ast_conf_options)
+            else:
+                LOGGER.info("Managing Asterisk instance at %s" % host)
+                ast_instance = Asterisk(base=self.testlogdir, host=host,
+                                        remote_config=ast_config)
+            self.ast.append(ast_instance)
             self.condition_controller.register_asterisk_instance(self.ast[i])
-            # If a base configuration for this Asterisk instance has been
-            # provided, install it first
-            if base_configs_path:
-                ast_dir = "%s/ast%d" % (base_configs_path, num)
-                self.ast[i].install_configs(ast_dir,
+
+            if local_num:
+                # If a base configuration for this Asterisk instance has been
+                # provided, install it first
+                if base_configs_path:
+                    ast_dir = "%s/ast%d" % (base_configs_path, local_num)
+                    self.ast[i].install_configs(ast_dir,
+                                                self.test_config.get_deps())
+                # Copy test specific config files
+                self.ast[i].install_configs("%s/configs/ast%d" %
+                                            (self.test_name, local_num),
                                             self.test_config.get_deps())
-            # Copy test specific config files
-            self.ast[i].install_configs("%s/configs/ast%d" %
-                                        (self.test_name, num),
-                                        self.test_config.get_deps())
 
     def create_ami_factory(self, count=1, username="user", secret="mysecret",
                            port=5038):
@@ -263,16 +293,18 @@
             """Called if the connection is lost and re-made"""
             login_deferred.addCallbacks(self._ami_connect, self.ami_login_error)
 
-        for i in range(count):
-            host = "127.0.0.%d" % (i + 1)
+        for i, ast_config in enumerate(self.get_asterisk_hosts(count)):
+            host = ast_config.get('host')
+            ami_config = ast_config.get('ami', {})
+            actual_user = ami_config.get('username', username)
+            actual_secret = ami_config.get('secret', secret)
+            actual_port = ami_config.get('port', port)
+
             self.ami.append(None)
-            LOGGER.info("Creating AMIFactory %d" % (i + 1))
-            try:
-                ami_factory = manager.AMIFactory(username, secret, i,
-                                                 on_reconnect=on_reconnect)
-            except:
-                ami_factory = manager.AMIFactory(username, secret, i)
-            deferred = ami_factory.login(ip=host, port=port)
+            LOGGER.info("Creating AMIFactory %d to %s" % ((i + 1), host))
+            ami_factory = manager.AMIFactory(actual_user, actual_secret, i,
+                                             on_reconnect=on_reconnect)
+            deferred = ami_factory.login(ip=host, port=actual_port)
             deferred.addCallbacks(self._ami_connect, self.ami_login_error)
 
     def create_fastagi_factory(self, count=1):
@@ -284,8 +316,9 @@
         count The number of instances of AGI to create
         """
 
-        for i in range(count):
-            host = "127.0.0.%d" % (i + 1)
+        for i, ast_config in enumerate(self.get_asterisk_hosts(count)):
+            host = ast_config.get('host')
+
             self.fastagi.append(None)
             LOGGER.info("Creating FastAGI Factory %d" % (i + 1))
             fastagi_factory = fastagi.FastAGIFactory(self.fastagi_connect)
@@ -904,6 +937,12 @@
         super(TestCaseModule, self).run()
 
         if self.connect_ami:
-            self.create_ami_factory(count=self.asterisk_instances)
+            if not isinstance(self.connect_ami, dict):
+                self.create_ami_factory(count=self.asterisk_instances)
+            else:
+                self.create_ami_factory(**self.connect_ami)
         if self.connect_agi:
-            self.create_fastagi_factory(count=self.asterisk_instances)
+            if not isinstance(self.connect_agi, dict):
+                self.create_fastagi_factory(count=self.asterisk_instances)
+            else:
+                self.create_fastagi_factory(**self.connect_agi)
diff --git a/lib/python/asterisk/test_config.py b/lib/python/asterisk/test_config.py
index b773726..c0abe48 100644
--- a/lib/python/asterisk/test_config.py
+++ b/lib/python/asterisk/test_config.py
@@ -84,9 +84,15 @@
 class Dependency(object):
     """Class that checks and stores the dependencies for a particular Test."""
 
-    asterisk_build_options = AsteriskBuildOptions()
+    try:
+        asterisk_build_options = AsteriskBuildOptions()
+    except:
+        asterisk_build_options = None
 
-    ast = Asterisk()
+    try:
+        ast = Asterisk()
+    except:
+        ast = None
 
     def __init__(self, dep):
         """Construct a new dependency
@@ -148,6 +154,15 @@
             print "Unknown dependency type specified:"
             for key in dep.keys():
                 print key
+
+    def depend_remote(self):
+        """Check to see if we run against a remote instance of Asterisk"""
+        test_config = TestConfig(os.getcwd())
+        if not test_config.config:
+            return False
+        if test_config.config.get('asterisk-instances'):
+            return True
+        return False
 
     def depend_soundcard(self):
         """Check the soundcard custom dependency"""
@@ -222,10 +237,18 @@
 
     def _find_build_flag(self, name):
         """Determine if the specified build option exists"""
-        return (self.asterisk_build_options.check_option(name))
+        if self.asterisk_build_options:
+            return (self.asterisk_build_options.check_option(name))
+        else:
+            print "Unable to evaluate build options: no build options found"
+            return False
 
     def _find_asterisk_module(self, name):
         """Determine if an Asterisk module exists"""
+        if not Dependency.ast:
+            print "Unable to evaluate Asterisk modules: Asterisk not found"
+            return False
+
         if Dependency.ast.original_astmoddir == "":
             return False
 
diff --git a/sample-yaml/test-config.yaml.sample b/sample-yaml/test-config.yaml.sample
index 2e920a7..6c1acf6 100644
--- a/sample-yaml/test-config.yaml.sample
+++ b/sample-yaml/test-config.yaml.sample
@@ -77,6 +77,15 @@
         # depend_ipv6() method is called to determine if the dependency is met.
         - custom: 'ipv6'
         - custom: 'fax'
+
+        # A 'remote' custom dependency specifies that this test does not run against
+        # a local instance of Asterisk, but against some remote instance. This will
+        # cause the Test Suite to not create new instances of Asterisk, modify the
+        # Asterisk configuration, etc. Instead, it will look to its global configuration
+        # settings for an 'asterisk-instances' section. This section defines the
+        # remote Asterisk instances that this test will run against.
+        - custom: 'remote'
+
     # A sequence of tags that categorize the test into groups of similar functionality
     tags: # OPTIONAL
         #
diff --git a/test-config.yaml b/test-config.yaml
index 34c7e6f..eb34483 100644
--- a/test-config.yaml
+++ b/test-config.yaml
@@ -91,6 +91,37 @@
 # It is included merely for completeness.
 config-standard:
 
+# This section defines how the Test Suite will behave if running against
+# a remote instance of Asterisk. In this particular case, the Test Suite
+# will not spawn or control any local instances of Asterisk; instead, it
+# will use an SSH connection to run CLI commands against remote Asterisk
+# instances and point its AMI/AGI/ARI interfaces at the configured instance(s).
+#
+# Note that the remote system should be in your known_hosts file before
+# running a test.
+#
+# This is useful when you want to use the Test Suite to test Asterisk, but
+# where Asterisk has a particular complex configuration and is part of a
+# larger integrated system.
+config-remote:
+    asterisk-instances:
+        -
+            # The host to connect to. This will be used for the SSH
+            # connection, as well as for the various API connections
+            host: '192.168.0.102'
+            # Path to the SSH private key
+            identity: '~/.ssh/id_psa'
+            # Passphrase used for encrypted private keys
+            passphrase: 'imalittleteapot'
+            # SSH username
+            username: 'user'
+            # SSH password
+            passsword: 'supersecret'
+            # AMI credentials.
+            ami:
+                username: 'asterisk'
+                secret: 'asterisk'
+
 # This test enables the pre- and post-test condition checking on all tests
 # that support it.  Individual tests can override the behavior of a pre-
 # and post-test in their test-config.yaml files.

-- 
To view, visit https://gerrit.asterisk.org/1521
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I59a7632e5a1cb38c46484c5035f36993139b7f3f
Gerrit-PatchSet: 2
Gerrit-Project: testsuite
Gerrit-Branch: master
Gerrit-Owner: Matt Jordan <mjordan at digium.com>
Gerrit-Reviewer: Anonymous Coward #1000019
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Mark Michelson <mmichelson at digium.com>
Gerrit-Reviewer: Matt Jordan <mjordan at digium.com>



More information about the asterisk-commits mailing list