You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by gm...@apache.org on 2018/11/05 14:30:27 UTC

qpid-dispatch git commit: DISPATCH-1170 - Fix for system tests execution on python3 only machines. This closes #414

Repository: qpid-dispatch
Updated Branches:
  refs/heads/master 855011938 -> bdd2aa389


DISPATCH-1170 - Fix for system tests execution on python3 only machines. This closes #414


Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/bdd2aa38
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/bdd2aa38
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/bdd2aa38

Branch: refs/heads/master
Commit: bdd2aa389dc8d5fb035e203cf6270142daeb2604
Parents: 8550119
Author: Fernando Giorgetti <fg...@redhat.com>
Authored: Mon Nov 5 11:16:50 2018 -0200
Committer: Ganesh Murthy <gm...@redhat.com>
Committed: Mon Nov 5 09:29:57 2018 -0500

----------------------------------------------------------------------
 CMakeLists.txt                                |   4 +-
 tests/CMakeLists.txt                          |   8 +-
 tests/system_tests_authz_service_plugin.py.in | 208 ++++++++++++++
 tests/system_tests_handle_failover.py.in      | 309 +++++++++++++++++++++
 4 files changed, 523 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/bdd2aa38/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9dc7180..bfe2c4a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,10 +48,8 @@ include(FindPythonLibs)
 
 if (PYTHON_VERSION_MAJOR STREQUAL 3)
     set(PY_STRING "python3")
-    set(PY_UNIT2_STRING "unit2-3")
 elseif(PYTHON_VERSION_MAJOR STREQUAL 2)
     set(PY_STRING "python")
-    set(PY_UNIT2_STRING "unit2")
 endif()
 
 # Find python-unittest2
@@ -244,6 +242,8 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/run.py.in ${CMAKE_CURRENT_BINARY_DIR}
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/run.py.in ${CMAKE_CURRENT_BINARY_DIR}/tests/run.py)
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/authservice.py.in ${CMAKE_CURRENT_SOURCE_DIR}/tests/authservice.py)
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/failoverserver.py.in ${CMAKE_CURRENT_SOURCE_DIR}/tests/failoverserver.py)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/system_tests_authz_service_plugin.py.in ${CMAKE_CURRENT_SOURCE_DIR}/tests/system_tests_authz_service_plugin.py)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/system_tests_handle_failover.py.in ${CMAKE_CURRENT_SOURCE_DIR}/tests/system_tests_handle_failover.py)
 execute_process(COMMAND ${RUN} --sh OUTPUT_FILE config.sh)
 
 if (NOT UNITTEST2_MISSING)

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/bdd2aa38/tests/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index fe99dec..1075c76 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -67,9 +67,9 @@ add_test(unit_tests_size_1     ${TEST_WRAP} -x unit_tests_size 1)
 add_test(unit_tests            ${TEST_WRAP} -x unit_tests ${CMAKE_CURRENT_SOURCE_DIR}/threads4.conf)
 
 # Unit test python modules
-add_test(router_engine_test    ${TEST_WRAP} -x ${PY_UNIT2_STRING} -v router_engine_test)
-add_test(management_test       ${TEST_WRAP} -x ${PY_UNIT2_STRING} -v management)
-add_test(router_policy_test    ${TEST_WRAP} -x ${PY_UNIT2_STRING} -v router_policy_test)
+add_test(router_engine_test    ${TEST_WRAP} -x unit2 -v router_engine_test)
+add_test(management_test       ${TEST_WRAP} -x unit2 -v management)
+add_test(router_policy_test    ${TEST_WRAP} -x unit2 -v router_policy_test)
 
 if(USE_LIBWEBSOCKETS)
   set(SYSTEM_TESTS_HTTP system_tests_http)
@@ -125,7 +125,7 @@ foreach(py_test_module
     system_tests_core_client
     )
 
-  add_test(${py_test_module} ${TEST_WRAP} -x ${PY_UNIT2_STRING} -v ${py_test_module})
+  add_test(${py_test_module} ${TEST_WRAP} -x unit2 -v ${py_test_module})
   list(APPEND SYSTEM_TEST_FILES ${CMAKE_CURRENT_SOURCE_DIR}/${py_test_module}.py)
 endforeach()
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/bdd2aa38/tests/system_tests_authz_service_plugin.py.in
----------------------------------------------------------------------
diff --git a/tests/system_tests_authz_service_plugin.py.in b/tests/system_tests_authz_service_plugin.py.in
new file mode 100644
index 0000000..a214df9
--- /dev/null
+++ b/tests/system_tests_authz_service_plugin.py.in
@@ -0,0 +1,208 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from __future__ import unicode_literals
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import print_function
+
+import unittest2 as unittest
+import os, json
+from subprocess import PIPE, Popen, STDOUT
+from system_test import TestCase, Qdrouterd, main_module, DIR, TIMEOUT, Process, SkipIfNeeded
+from proton import Array, Data, Message, SASL, symbol, UNDESCRIBED
+from proton.handlers import MessagingHandler
+from proton.reactor import Container
+
+
+class AuthServicePluginAuthzTest(TestCase):
+    @classmethod
+    def addUser(cls, user, password):
+        # Create a sasl database.
+        p = Popen(['saslpasswd2', '-c', '-p', '-f', 'users.sasldb', user],
+                  stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True)
+        result = p.communicate(password)
+        assert p.returncode == 0, "saslpasswd2 exit status %s, output:\n%s" % (p.returncode, result)
+
+    @classmethod
+    def createSaslFiles(cls):
+        cls.addUser('guest', 'guest')
+        cls.addUser('admin', 'admin')
+        # Create a SASL configuration file.
+        with open('tests-mech-SCRAM.conf', 'w') as sasl_conf:
+            sasl_conf.write("""
+mech_list: SCRAM-SHA-1 PLAIN
+""")
+        with open('proton-server.conf', 'w') as sasl_conf:
+            sasl_conf.write("""
+pwcheck_method: auxprop
+auxprop_plugin: sasldb
+sasldb_path: users.sasldb
+mech_list: SCRAM-SHA-1 PLAIN
+""")
+
+
+    @classmethod
+    def setUpClass(cls):
+        """
+        Tests the delegation of sasl auth to an external auth service.
+        """
+        super(AuthServicePluginAuthzTest, cls).setUpClass()
+
+        if not SASL.extended():
+            return
+
+        cls.createSaslFiles()
+
+        cls.auth_service_port = cls.tester.get_port()
+        cls.tester.popen(['/usr/bin/env', '${PY_STRING}', os.path.join(os.path.dirname(os.path.abspath(__file__)), 'authservice.py'), '-a', 'amqps://127.0.0.1:%d' % cls.auth_service_port, '-c', os.getcwd()], expect=Process.RUNNING)
+
+        policy_config_path = os.path.join(DIR, 'policy-authz')
+
+        cls.router_port = cls.tester.get_port()
+        cls.tester.qdrouterd('router', Qdrouterd.Config([
+                     ('sslProfile', {'name':'myssl'}),
+                     ('policy', {'maxConnections': 2, 'policyDir': policy_config_path, 'enableVhostPolicy': 'true'}),
+                     # authService attribute has been deprecated. We are using it here to make sure that we are
+                     # still backward compatible.
+                     ('authServicePlugin', {'name':'myauth', 'sslProfile':'myssl', 'port': cls.auth_service_port, 'host': '127.0.0.1'}),
+                     ('listener', {'host': '0.0.0.0', 'port': cls.router_port, 'role': 'normal', 'saslPlugin':'myauth', 'saslMechanisms':'SCRAM-SHA-1 PLAIN'}),
+                     ('router', {'mode': 'standalone', 'id': 'router',
+                                 'saslConfigName': 'tests-mech-SCRAM',
+                                 'saslConfigPath': os.getcwd()})
+        ])).wait_ready()
+
+    @SkipIfNeeded(not SASL.extended(), "Cyrus library not available. skipping test")
+    def test_authorized(self):
+        container = Container()
+        client = ConnectionHandler('foo', 1)
+        container.connect("guest:guest@127.0.0.1:%d" % self.router_port, handler=client)
+        container.run()
+        self.assertEqual(1, client.sent)
+        self.assertEqual(1, client.received)
+        self.assertEqual(0, len(client.errors))
+
+    @SkipIfNeeded(not SASL.extended(), "Cyrus library not available. skipping test")
+    def test_unauthorized(self):
+        container = Container()
+        client = ConnectionHandler('bar', 1)
+        container.connect("guest:guest@127.0.0.1:%d" % self.router_port, handler=client)
+        container.run()
+        self.assertEqual(0, client.sent)
+        self.assertEqual(0, client.received)
+        self.assertEqual(2, len(client.errors))
+        self.assertEqual('amqp:unauthorized-access', client.errors[0])
+        self.assertEqual('amqp:unauthorized-access', client.errors[1])
+
+    @SkipIfNeeded(not SASL.extended(), "Cyrus library not available. skipping test")
+    def test_wildcard(self):
+        container = Container()
+        client = ConnectionHandler('whatever', 1)
+        container.connect("admin:admin@127.0.0.1:%d" % self.router_port, handler=client)
+        container.run()
+        self.assertEqual(1, client.sent)
+        self.assertEqual(1, client.received)
+        self.assertEqual(0, len(client.errors))
+
+    @SkipIfNeeded(not SASL.extended(), "Cyrus library not available. skipping test")
+    def test_dynamic_source_anonymous_sender(self):
+        container = Container()
+        client = DynamicSourceAnonymousSender()
+        container.connect("admin:admin@127.0.0.1:%d" % self.router_port, handler=client)
+        container.run()
+        self.assertEqual('hello', client.message)
+        self.assertEqual(0, len(client.errors))
+
+
+class AuthServicePluginAuthzDeprecatedTest(AuthServicePluginAuthzTest):
+    @classmethod
+    def setUpClass(cls):
+        """
+        Tests the delegation of sasl auth to an external auth service.
+        """
+        super(AuthServicePluginAuthzTest, cls).setUpClass()
+
+        if not SASL.extended():
+            return
+
+        cls.createSaslFiles()
+
+        cls.auth_service_port = cls.tester.get_port()
+        cls.tester.popen(['/usr/bin/env', '${PY_STRING}', os.path.join(os.path.dirname(os.path.abspath(__file__)), 'authservice.py'), '-a', 'amqps://127.0.0.1:%d' % cls.auth_service_port, '-c', os.getcwd()], expect=Process.RUNNING)
+
+        cls.router_port = cls.tester.get_port()
+        cls.tester.qdrouterd('router', Qdrouterd.Config([
+                     ('sslProfile', {'name':'myssl'}),
+                     # authService and authSslProfile attributea have been deprecated.
+                     # We are using it here to make sure that we are backward compatible.
+                     ('authServicePlugin', {'name':'myauth', 'authSslProfile':'myssl', 'authService': '127.0.0.1:%d' % cls.auth_service_port}),
+                     ('listener', {'host': '0.0.0.0', 'port': cls.router_port, 'role': 'normal', 'saslPlugin':'myauth', 'saslMechanisms':'SCRAM-SHA-1 PLAIN'}),
+                     ('router', {'mode': 'standalone', 'id': 'router',
+                                 'saslConfigName': 'tests-mech-SCRAM',
+                                 'saslConfigPath': os.getcwd()})
+        ])).wait_ready()
+class ConnectionHandler(MessagingHandler):
+    def __init__(self, address, count):
+        super(ConnectionHandler, self).__init__()
+        self.address = address
+        self.count = count
+        self.received = 0
+        self.sent = 0
+        self.errors = []
+
+    def on_message(self, event):
+        self.received += 1
+        if self.received == self.count:
+            event.connection.close()
+
+    def on_sendable(self, event):
+        if self.sent < self.count:
+            self.sent += 1
+            event.sender.send(Message(body='msg-%s' %self.sent))
+
+    def on_link_error(self, event):
+        self.errors.append(event.link.remote_condition.name)
+        event.connection.close()
+
+    def on_connection_opened(self, event):
+        event.container.create_receiver(event.connection, self.address)
+        event.container.create_sender(event.connection, self.address)
+
+class DynamicSourceAnonymousSender(MessagingHandler):
+    def __init__(self):
+        super(DynamicSourceAnonymousSender, self).__init__()
+        self.sender = None
+        self.message = None
+        self.errors = []
+
+    def on_message(self, event):
+        self.message = event.message.body;
+        event.connection.close()
+
+    def on_link_opened(self, event):
+        if event.receiver:
+            self.sender.send(Message(address=event.receiver.remote_source.address, body='hello'))
+
+    def on_connection_opened(self, event):
+        event.container.create_receiver(event.connection, None, dynamic=True)
+        self.sender = event.container.create_sender(event.connection, None)
+
+if __name__ == '__main__':
+    unittest.main(main_module())
+

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/bdd2aa38/tests/system_tests_handle_failover.py.in
----------------------------------------------------------------------
diff --git a/tests/system_tests_handle_failover.py.in b/tests/system_tests_handle_failover.py.in
new file mode 100644
index 0000000..83ac620
--- /dev/null
+++ b/tests/system_tests_handle_failover.py.in
@@ -0,0 +1,309 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from __future__ import unicode_literals
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import print_function
+
+import os
+from threading import Timer
+import unittest2 as unittest
+import json, re
+from system_test import main_module, TIMEOUT
+from system_test import TestCase, Qdrouterd, Process, TIMEOUT
+from subprocess import PIPE, STDOUT
+
+
+class FailoverTest(TestCase):
+    inter_router_port = None
+
+    @classmethod
+    def router(cls, name, config):
+        config = Qdrouterd.Config(config)
+
+        cls.routers.append(cls.tester.qdrouterd(name, config, wait=True))
+
+    @classmethod
+    def setUpClass(cls):
+        super(FailoverTest, cls).setUpClass()
+
+        cls.routers = []
+
+        cls.inter_router_port = cls.tester.get_port()
+        cls.inter_router_port_1 = cls.tester.get_port()
+        cls.backup_port = cls.tester.get_port()
+        cls.backup_url = 'amqp://0.0.0.0:' + str(cls.backup_port)
+        cls.my_server_port = cls.tester.get_port()
+
+        cls.failover_list = 'amqp://third-host:5671, ' + cls.backup_url
+
+        #
+        # Router A tries to connect to Router B via its connectorToB. Router B responds with an open frame which will
+        # have the failover-server-list as one of its connection properties like the following -
+        # [0x13024d0]:0 <- @open(16) [container-id="Router.A", max-frame-size=16384, channel-max=32767,
+        # idle-time-out=8000, offered-capabilities=:"ANONYMOUS-RELAY",
+        # properties={:product="qpid-dispatch-router", :version="1.0.0",
+        #  :"failover-server-list"=[{:"network-host"="some-host", :port="35000"},
+        #  {:"network-host"="0.0.0.0", :port="25000"}]}]
+        #
+        # The suite of tests determine if the router receiving this open frame stores it properly and if the
+        # original connection goes down, check that the router is trying to make connections to the failover urls.
+        #
+        FailoverTest.router('B', [
+                        ('router', {'mode': 'interior', 'id': 'B'}),
+                        ('listener', {'host': '0.0.0.0', 'role': 'inter-router', 'port': cls.inter_router_port,
+                                      'failoverUrls': cls.failover_list}),
+                        ('listener', {'host': '0.0.0.0', 'role': 'normal', 'port': cls.tester.get_port()}),
+                        ]
+              )
+
+        FailoverTest.router('A',
+                    [
+                        ('router', {'mode': 'interior', 'id': 'A'}),
+                        ('listener', {'host': '0.0.0.0', 'role': 'normal', 'port': cls.tester.get_port()}),
+                        ('connector', {'name': 'connectorToB', 'role': 'inter-router',
+                                       'port': cls.inter_router_port, 'verifyHostname': 'no'}),
+                    ]
+               )
+
+        FailoverTest.router('C', [
+                            ('router', {'mode': 'interior', 'id': 'C'}),
+                            ('listener', {'host': '0.0.0.0', 'role': 'inter-router', 'port': cls.backup_port}),
+                            ('listener', {'host': '0.0.0.0', 'role': 'normal', 'port': cls.tester.get_port()}),
+                        ]
+              )
+
+        cls.routers[1].wait_router_connected('B')
+
+    def __init__(self, test_method):
+        TestCase.__init__(self, test_method)
+        self.success = False
+        self.timer_delay = 2
+        self.max_attempts = 10
+        self.attempts = 0
+
+    def address(self):
+        return self.routers[1].addresses[0]
+
+    def run_qdmanage(self, cmd, input=None, expect=Process.EXIT_OK, address=None):
+        p = self.popen(
+            ['qdmanage'] + cmd.split(' ') + ['--bus', address or self.address(), '--indent=-1', '--timeout', str(TIMEOUT)],
+            stdin=PIPE, stdout=PIPE, stderr=STDOUT, expect=expect,
+            universal_newlines=True)
+        out = p.communicate(input)[0]
+        try:
+            p.teardown()
+        except Exception as e:
+            raise Exception("%s\n%s" % (e, out))
+        return out
+
+    def run_qdstat(self, args, regexp=None, address=None):
+        p = self.popen(
+            ['qdstat', '--bus', str(address or self.router.addresses[0]), '--timeout', str(TIMEOUT) ] + args,
+            name='qdstat-'+self.id(), stdout=PIPE, expect=None,
+            universal_newlines=True)
+
+        out = p.communicate()[0]
+        assert p.returncode == 0, \
+            "qdstat exit status %s, output:\n%s" % (p.returncode, out)
+        if regexp: assert re.search(regexp, out, re.I), "Can't find '%s' in '%s'" % (regexp, out)
+        return out
+
+    def test_1_connector_has_failover_list(self):
+        """
+        This is the most simple and straightforward case. Router A connects to Router B. Router B sends
+        failover information to Router A.
+        We make a qdmanage connector query to Router A which checks if Router A is storing the failover information
+        received from  Router B.The failover list must consist of the original connection info (from the connector)
+        followed by the two items sent by the Router B (stored in cls.failover_list)
+        The 'failoverUrls' is comma separated.
+        """
+        long_type = 'org.apache.qpid.dispatch.connector'
+        query_command = 'QUERY --type=' + long_type
+        output = json.loads(self.run_qdmanage(query_command))
+        expected = "amqp://127.0.0.1:" + str(FailoverTest.inter_router_port) + ", " + FailoverTest.failover_list
+
+        self.assertEqual(expected, output[0]['failoverUrls'])
+
+    def schedule_B_to_C_failover_test(self):
+        if self.attempts < self.max_attempts:
+            if not self.success:
+                Timer(self.timer_delay, self.check_C_connector).start()
+                self.attempts += 1
+
+    def check_C_connector(self):
+        long_type = 'org.apache.qpid.dispatch.connector'
+        query_command = 'QUERY --type=' + long_type
+        output = json.loads(self.run_qdmanage(query_command, address=self.routers[1].addresses[0]))
+
+        expected = FailoverTest.backup_url  + ", " + "amqp://127.0.0.1:" + str(FailoverTest.inter_router_port) \
+                   + ", " + "amqp://third-host:5671"
+
+        if output[0].get('failoverUrls') == expected:
+            self.success = True
+        else:
+            self.schedule_B_to_C_failover_test()
+
+    def can_terminate(self):
+        if self.attempts == self.max_attempts:
+            return True
+
+        if self.success:
+            return True
+
+        return False
+
+    def test_2_remove_router_B(self):
+        """
+        In this test, we are killing Router B. As a result, Router A should try to connect to Router C.
+        Router C does NOT have a failover list, so the open frame that Router C sends to Router A will not contain
+        the failover-server-list property..Hence the failoverUrls list will remain unchanged except that the order of
+        the URLs would be different.
+        """
+
+        # First make sure there are no inter-router connections on router C
+        outs = self.run_qdstat(['--connections'], address=self.routers[2].addresses[1])
+
+        inter_router = 'inter-router' in outs
+        self.assertFalse(inter_router)
+
+        # Kill the router B
+        FailoverTest.routers[0].teardown()
+
+        # Schedule a test to make sure that the failover url is available
+        # and Router C has an inter-router connection
+        self.schedule_B_to_C_failover_test()
+
+        while not self.can_terminate():
+            pass
+
+        self.assertTrue(self.success)
+
+
+    def schedule_C_to_B_failover_test(self):
+        if self.attempts < self.max_attempts:
+            if not self.success:
+                Timer(self.timer_delay, self.check_B_connector).start()
+                self.attempts += 1
+
+    def check_B_connector(self):
+        # Router A should now try to connect to Router B again since we killed Router C.
+        long_type = 'org.apache.qpid.dispatch.connector'
+        query_command = 'QUERY --type=' + long_type
+        output = json.loads(self.run_qdmanage(query_command, address=self.routers[1].addresses[0]))
+
+        # The order that the URLs appear in the failoverUrls is important. This is the order in which the router
+        # will attempt to make connections in case the existing connection goes down.
+
+        expected = "amqp://127.0.0.1:" + str(FailoverTest.inter_router_port) + ", " + \
+                   FailoverTest.failover_list + \
+                   ', amqp://127.0.0.1:%d' % FailoverTest.my_server_port
+
+        if output[0].get('failoverUrls') == expected:
+            self.success = True
+        else:
+            self.schedule_C_to_B_failover_test()
+
+    def test_3_reinstate_router_B(self):
+        """
+        In this test, we are restarting Router B and killing Router C. Router A should now try to connect back to
+        Router B since it maintains the original connection info to Router B from the connector config information.
+        Before starting Router B back again, we
+        have a small config change to Router B  wherein we are adding a new failover url to the original list.
+        This new failover url
+        points to our own server which will accept connections. This server will actually be used in the next test
+        but this test maskes sure that the new server url also shows up in the failoverUrls list.
+        """
+        FailoverTest.router('B', [
+                        ('router', {'mode': 'interior', 'id': 'B'}),
+                        ('listener', {'host': '0.0.0.0', 'role': 'inter-router', 'port': FailoverTest.inter_router_port,
+                                      'failoverUrls': FailoverTest.failover_list +  ', amqp://127.0.0.1:%d' % FailoverTest.my_server_port}),
+                        ('listener', {'host': '0.0.0.0', 'role': 'normal', 'port': FailoverTest.tester.get_port()}),
+                        ])
+
+        FailoverTest.routers[3].wait_ready()
+
+        # Kill the router C.
+        # Now since Router B is up and running, router A should try to re-connect to Router B.
+        # This will prove that the router A is preserving the original connector information specified in its config.
+        FailoverTest.routers[2].teardown()
+
+        self.success = False
+        self.attempts = 0
+
+        # Schedule a test to make sure that the failover url is available
+        self.schedule_C_to_B_failover_test()
+
+        while not self.can_terminate():
+            pass
+
+        self.assertTrue(self.success)
+
+    def check_A_connector(self):
+        # Router A should now try to connect to Router B again since we killed Router C.
+        long_type = 'org.apache.qpid.dispatch.connector'
+        query_command = 'QUERY --type=' + long_type
+        output = json.loads(self.run_qdmanage(query_command, address=self.routers[1].addresses[0]))
+
+        # The order that the URLs appear in the failoverUrls is important. This is the order in which the router
+        # will attempt to make connections in case the existing connection goes down.
+        expected = 'amqp://127.0.0.1:%d' % FailoverTest.my_server_port + ", " + "amqp://127.0.0.1:" + str(FailoverTest.inter_router_port)
+
+        if output[0].get('failoverUrls') == expected:
+            self.success = True
+        else:
+            self.schedule_B_to_my_server_failover_test()
+
+    def schedule_B_to_my_server_failover_test(self):
+        if self.attempts < self.max_attempts:
+            if not self.success:
+                Timer(self.timer_delay, self.check_A_connector).start()
+                self.attempts += 1
+
+    def test_4_remove_router_B_connect_to_my_server(self):
+        """
+        This test kills Router B again and makes sure that Router A now connects to our custom server that
+        accepts connections. This custom server intentionally sends an empty list for failover-server-list
+        Router A must look at this empty list and wipe out all failover information except the original connector information
+        and the current connection info.
+        """
+
+
+        # Start MyServer
+        proc = FailoverTest.tester.popen(
+            ['/usr/bin/env', '${PY_STRING}', os.path.join(os.path.dirname(os.path.abspath(__file__)), 'failoverserver.py'), '-a',
+             'amqp://127.0.0.1:%d' % FailoverTest.my_server_port], expect=Process.RUNNING)
+
+        # Kill the router B again
+        FailoverTest.routers[3].teardown()
+
+        self.success = False
+        self.attempts = 0
+
+        self.schedule_B_to_my_server_failover_test()
+
+        while not self.can_terminate():
+            pass
+
+        self.assertTrue(self.success)
+
+
+if __name__ == '__main__':
+    unittest.main(main_module())


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org