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 2020/10/13 13:49:04 UTC

[qpid-dispatch] branch dev-protocol-adaptors updated: DISPATCH-1743: Added http2 system tests. Uses Quart to launch a server and curl as the client

This is an automated email from the ASF dual-hosted git repository.

gmurthy pushed a commit to branch dev-protocol-adaptors
in repository https://gitbox.apache.org/repos/asf/qpid-dispatch.git


The following commit(s) were added to refs/heads/dev-protocol-adaptors by this push:
     new 3c726ba  DISPATCH-1743: Added http2 system tests. Uses Quart to launch a server and curl as the client
3c726ba is described below

commit 3c726bad51eed116bb70a04fe1910ab96040064b
Author: Ganesh Murthy <gm...@apache.org>
AuthorDate: Mon Oct 12 11:55:28 2020 -0400

    DISPATCH-1743: Added http2 system tests. Uses Quart to launch a server and curl as the client
---
 .travis.yml                       |   4 +-
 LICENSE                           |   4 +
 README                            |  11 ++
 dockerfiles/Dockerfile-fedora     |   9 +-
 dockerfiles/Dockerfile-ubuntu     |   7 +-
 tests/http2_server.py             |  68 ++++++++++-
 tests/images/apache.jpg           | Bin 0 -> 5147 bytes
 tests/images/balanced-routing.png | Bin 0 -> 45011 bytes
 tests/quart/LICENSE               |  22 ++++
 tests/system_test.py              |   9 +-
 tests/system_tests_http2.py       | 247 +++++++++++++++++++++++++++++++++++---
 11 files changed, 353 insertions(+), 28 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index ab84fe7..61b28ce 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -67,6 +67,8 @@ jobs:
     # https://github.com/pypa/virtualenv/issues/1873
     - python -m pip install --user --upgrade pip
     - python -m pip install --user --upgrade tox virtualenv==20.0.23
+    # Install quart to run the http2 tests.
+    - python -m pip install --user quart
     env:
     - CC=clang-10
     - CXX=clang++-10
@@ -101,6 +103,7 @@ addons:
     - python2.7-dev
     - sasl2-bin
     - swig
+    - libnghttp2-dev
     # documentation
     - asciidoc
     - asciidoctor
@@ -110,7 +113,6 @@ addons:
     - tox
     # code coverage
     - lcov
-    - libnghttp2-dev
 
 install:
 - NPROC=2
diff --git a/LICENSE b/LICENSE
index 2569aa5..6ab2e63 100644
--- a/LICENSE
+++ b/LICENSE
@@ -209,3 +209,7 @@ This product bundles a copy of DOCTEST within its test code (found at
 tests/c_unittests/doctest.h), which is distributed under the MIT licence.
 
 A copy of its licence can be found at: tests/c_unittests/LICENSE.txt
+
+The tests run a http2 server which uses a Python ASGI web microframework
+called Quart which is distributed under the MIT licence.
+A copy of Quart's licence can be found at: tests/quart/LICENSE
diff --git a/README b/README
index 453694b..635a8ff 100644
--- a/README
+++ b/README
@@ -68,6 +68,17 @@ $ ./run.py -m unittest system_tests_qdstat
 Run it without arguments to get a summary of how it can be used:
 $ ./run.py
 
+The HTTP2 system tests (tests/system_tests_http2.py) use the Python Quart framework to start a HTTP2 server. 
+The HTTP2 system tests will run only if
+   1. Python version >= 3.7
+   2. Python Web Microframework Quart version >= 0.13
+   3. curl is available
+
+To install pip and Quart
+ - curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
+ - python3 get-pip.py
+ - pip3 install --user quart
+
 
 Test Suite Code Coverage (GNU tools only)
 =========================================
diff --git a/dockerfiles/Dockerfile-fedora b/dockerfiles/Dockerfile-fedora
index 685e4c9..72b7f18 100644
--- a/dockerfiles/Dockerfile-fedora
+++ b/dockerfiles/Dockerfile-fedora
@@ -30,11 +30,16 @@ FROM fedora:latest
 MAINTAINER "dev@qpid.apache.org"
 
 # Install required packages. Some in this list are from proton's INSTALL.md (https://github.com/apache/qpid-proton/blob/master/INSTALL.md) and the rest are from dispatch (https://github.com/apache/qpid-dispatch/blob/master/README)
-RUN dnf -y install gcc gcc-c++ cmake openssl-devel cyrus-sasl-devel cyrus-sasl-plain cyrus-sasl-gssapi cyrus-sasl-md5 swig java-1.8.0-openjdk-devel git make valgrind emacs libwebsockets-devel python-devel
+RUN dnf -y install gcc gcc-c++ cmake openssl-devel cyrus-sasl-devel cyrus-sasl-plain cyrus-sasl-gssapi cyrus-sasl-md5 swig java-1.8.0-openjdk-devel git make valgrind emacs libwebsockets-devel python-devel libnghttp2-devel curl
+
+RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
+RUN python3 get-pip.py
+RUN pip3 install --user quart
 
 # Create a main directory and clone the qpid-proton repo from github
 RUN mkdir /main && cd /main && git clone https://gitbox.apache.org/repos/asf/qpid-proton.git  && cd /main/qpid-proton && mkdir /main/qpid-proton/build
 
+WORKDIR /main
 WORKDIR /main/qpid-proton/build
 
 # make and install proton
@@ -51,4 +56,4 @@ RUN cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make install
 
 # Start the dispatch router
 ENTRYPOINT ["qdrouterd"]
-# CMD ["/bin/bash"]
+#CMD ["/bin/bash"]
diff --git a/dockerfiles/Dockerfile-ubuntu b/dockerfiles/Dockerfile-ubuntu
index be6b995..dde2c8b 100644
--- a/dockerfiles/Dockerfile-ubuntu
+++ b/dockerfiles/Dockerfile-ubuntu
@@ -26,9 +26,14 @@ MAINTAINER "dev@qpid.apache.org"
 ARG DEBIAN_FRONTEND=noninteractive
 # Install all the required packages. Some in this list were picked off from proton's INSTALL.md (https://github.com/apache/qpid-proton/blob/master/INSTALL.md) and the rest are from dispatch (https://github.com/apache/qpid-dispatch/blob/master/README)
 RUN apt-get update && \
-    apt-get install -y gcc g++ automake libwebsockets-dev libtool zlib1g-dev cmake libsasl2-dev libssl-dev python python-dev libuv1-dev sasl2-bin swig maven git && \
+    apt-get install -y curl gcc g++ automake libwebsockets-dev libtool zlib1g-dev cmake libsasl2-dev libssl-dev libnghttp2-dev python3-dev libuv1-dev sasl2-bin swig maven git && \
     apt-get -y clean
 
+RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
+RUN python3 get-pip.py
+RUN pip3 install --user quart
+
+
 RUN git clone https://gitbox.apache.org/repos/asf/qpid-dispatch.git && cd /qpid-dispatch && git submodule add https://gitbox.apache.org/repos/asf/qpid-proton.git && git submodule update --init
 
 WORKDIR /qpid-dispatch
diff --git a/tests/http2_server.py b/tests/http2_server.py
index f28b572..c56dd2a 100644
--- a/tests/http2_server.py
+++ b/tests/http2_server.py
@@ -16,15 +16,75 @@
 # specific language governing permissions and limitations
 # under the License.
 #
+import os
+from quart import Quart, request
+from quart.static import send_file
+from quart.exceptions import HTTPStatusException
+import json
+app = Quart(__name__)
+import system_test
+class MyInfo(object):
+    def __init__(self, fname, lname, id=None):
+        self.fname = fname
+        self.lname = lname
+        self.id = id
+        #self.hobby = None
+        #self.style = None
 
+my_info = MyInfo(fname="John", lname="Doe")
 
-from quart import Quart
+def image_file(name):
+    return os.path.join(system_test.DIR, 'images', name)
 
-app = Quart(__name__)
+@app.route("/myinfo/delete/<id>", methods=["DELETE"])
+async def delete_myinfo(id):
+    my_info.id = id
+    jsonStr = json.dumps(my_info.__dict__)
+    return jsonStr
+    
+@app.route('/myinfo', methods=['GET', 'POST', 'PUT'])
+async def create_myinfo():
+    form = await request.form
+    name = form['fname']
+    age = form['lname']
+    message = "Success! Your first name is %s, last name is %s" % (name, age)
+    return message
+
+def large_string(length):
+    i = 0
+    ret_string = ""
+    while (i < length):
+        ret_string += str(i) + ","
+        i += 1
+    return ret_string
 
 @app.route('/')
 async def index():
-    return 'Hello World'
+    return large_string(1000)
+
+@app.route('/largeget', methods=['GET'])
+async def largeget():
+    return large_string(50000)
+
+@app.route('/patch', methods=['PATCH'])
+async def patch():
+    data = await request.data
+    return data
+
+# Return a 500 error, "Service Unavailable"
+@app.route('/test/500')
+async def service_unavailable():
+    raise HTTPStatusException()
+
+@app.route('/images/balanced-routing.png', methods=['GET'])
+async def get_png_images():
+    img_file = image_file("balanced-routing.png")
+    return await send_file(img_file, mimetype='image/png')
+
+@app.route('/images/apache.jpg', methods=['GET'])
+async def get_jpg_images():
+    img_file = image_file("apache.jpg")
+    return await send_file(img_file, mimetype='image/jpg')
 
 #app.run(port=5000, certfile='cert.pem', keyfile='key.pem')
-app.run(port=5000)
+app.run(port=os.getenv('SERVER_LISTEN_PORT'))
diff --git a/tests/images/apache.jpg b/tests/images/apache.jpg
new file mode 100644
index 0000000..2475669
Binary files /dev/null and b/tests/images/apache.jpg differ
diff --git a/tests/images/balanced-routing.png b/tests/images/balanced-routing.png
new file mode 100644
index 0000000..3c2e018
Binary files /dev/null and b/tests/images/balanced-routing.png differ
diff --git a/tests/quart/LICENSE b/tests/quart/LICENSE
new file mode 100644
index 0000000..6417781
--- /dev/null
+++ b/tests/quart/LICENSE
@@ -0,0 +1,22 @@
+Copyright P G Jones 2017.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/tests/system_test.py b/tests/system_test.py
index c633b56..521ce9f 100755
--- a/tests/system_test.py
+++ b/tests/system_test.py
@@ -607,6 +607,12 @@ class Qdrouterd(Process):
         raise Exception("Unknown protocol family: %s" % protocol_family)
 
     @property
+    def http_addresses(self):
+        """Return http://host:port addresses for all http listeners"""
+        cfg = self.config.sections('httpListener')
+        return ["http://%s" % self._cfg_2_host_port(l) for l in cfg]
+
+    @property
     def addresses(self):
         """Return amqp://host:port addresses for all listeners"""
         cfg = self.config.sections('listener')
@@ -770,7 +776,7 @@ class Tester(object):
         """Return a Qdrouterd that will be cleaned up on teardown"""
         return self.cleanup(Qdrouterd(*args, **kwargs))
 
-    def httpserver(self, *args, **kwargs):
+    def http2server(self, *args, **kwargs):
         return self.cleanup(Http2Server(*args, **kwargs))
 
     port_range = (20000, 30000)
@@ -1113,7 +1119,6 @@ class AsyncTestSender(MessagingHandler):
             self._conn.close()
             self._conn = None
 
-
 class QdManager(object):
     """
     A means to invoke qdmanage during a testcase
diff --git a/tests/system_tests_http2.py b/tests/system_tests_http2.py
index b1ca195..3dd0664 100644
--- a/tests/system_tests_http2.py
+++ b/tests/system_tests_http2.py
@@ -17,36 +17,247 @@
 # under the License.
 #
 
-import os
+import os, sys
 from time import sleep
+import system_test
+from system_test import TestCase, Qdrouterd, Process, SkipIfNeeded
+from subprocess import PIPE
 
-from system_test import TestCase, Qdrouterd, Process
 
+def python_37_available():
+    if sys.version_info >= (3, 7):
+        return True
 
-class Http2Test(TestCase):
+def curl_available():
+    popen_args = ['curl', '--version']
+    try:
+        process = Process(popen_args,
+                          name='curl_check',
+                          stdout=PIPE,
+                          expect=None,
+                          universal_newlines=True)
+        out = process.communicate()[0]
+        return True
+    except:
+        return False
+
+def quart_available():
+    popen_args = ['quart', '--version']
+    try:
+        process = Process(popen_args,
+                          name='curl_check',
+                          stdout=PIPE,
+                          expect=None,
+                          universal_newlines=True)
+        out = process.communicate()[0]
+        parts = out.split(".")
+        if int(parts[1]) >= 13:
+            return True
+        return False
+    except Exception as e:
+        print (e)
+        print("quart_not_available")
+        return False
+
+def skip_test():
+    if python_37_available() and quart_available() and curl_available():
+        return False
+    return True
+
+class Http2TestBase(TestCase):
+    def run_curl(self, args=None, regexp=None, address=None):
+        # Tell with -m / --max-time the maximum time, in seconds, that you
+        # allow the command line to spend before curl exits with a
+        # timeout error code (28).
+        local_args = ["--http2-prior-knowledge"]
+        if args:
+            local_args =  args + ["--http2-prior-knowledge"]
+
+        popen_args = ['curl',
+                      str(address),
+                      '--max-time', str(system_test.TIMEOUT)] + local_args
+        p = self.popen(popen_args,
+                       name='curl-' + self.id(), stdout=PIPE, expect=None,
+                       universal_newlines=True)
+
+        out = p.communicate()[0]
+        assert (p.returncode == 0)
+        return out
+
+
+class CommonHttp2Tests():
+    """
+    The tests in this class are run by both Http2TestOneRouter and
+    Http2TestTwoRouter
+    """
+    @SkipIfNeeded(skip_test(), "Python 3.7 or greater, Quart 0.13.0 or greater and curl needed to run http2 tests")
+    # Tests the HTTP2 head request
+    def test_head_request(self):
+        # Run curl 127.0.0.1:port --http2-prior-knowledge --head
+        address = self.router_qdra.http_addresses[0]
+        out = self.run_curl(args=["--head"], address=address)
+        self.assertIn('HTTP/2 200', out)
+        self.assertIn('server: hypercorn-h2', out)
+        self.assertIn('content-type: text/html; charset=utf-8', out)
+
+    @SkipIfNeeded(skip_test(), "Python 3.7 or greater, Quart 0.13.0 or greater and curl needed to run http2 tests")
+    def test_get_request(self):
+        # Run curl 127.0.0.1:port --http2-prior-knowledge
+        address = self.router_qdra.http_addresses[0]
+        out = self.run_curl(address=address)
+        i = 0
+        ret_string = ""
+        while (i < 1000):
+            ret_string += str(i) + ","
+            i += 1
+        self.assertIn(ret_string, out)
+
+    @SkipIfNeeded(skip_test(), "Python 3.7 or greater, Quart 0.13.0 or greater and curl needed to run http2 tests")
+    def test_large_get_request(self):
+        # Tests a large get request. Response is more than 50k which means it
+        # will span many qd_http2_buffer_t objects.
+        # Run curl 127.0.0.1:port/largeget --http2-prior-knowledge
+        address = self.router_qdra.http_addresses[0] + "/largeget"
+        out = self.run_curl(address=address)
+        self.assertIn("49996,49997,49998,49999", out)
+
+    @SkipIfNeeded(skip_test(), "Python 3.7 or greater, Quart 0.13.0 or greater and curl needed to run http2 tests")
+    def test_post_request(self):
+        # curl -d "fname=John&lname=Doe" -X POST 127.0.0.1:9000/myinfo --http2-prior-knowledge
+        address = self.router_qdra.http_addresses[0] + "/myinfo"
+        out = self.run_curl(args=['-d', 'fname=John&lname=Doe', '-X', 'POST'], address=address)
+        self.assertIn('Success! Your first name is John, last name is Doe', out)
+
+    @SkipIfNeeded(skip_test(), "Python 3.7 or greater, Quart 0.13.0 or greater and curl needed to run http2 tests")
+    def test_delete_request(self):
+        # curl -X DELETE "http://127.0.0.1:9000/myinfo/delete/22122" -H  "accept: application/json" --http2-prior-knowledge
+        address = self.router_qdra.http_addresses[0] + "/myinfo/delete/22122"
+        out = self.run_curl(args=['-X', 'DELETE'], address=address)
+        self.assertIn('{"fname": "John", "lname": "Doe", "id": "22122"}', out)
+
+
+    @SkipIfNeeded(skip_test(), "Python 3.7 or greater, Quart 0.13.0 or greater and curl needed to run http2 tests")
+    def test_put_request(self):
+        # curl -d "fname=John&lname=Doe" -X PUT 127.0.0.1:9000/myinfo --http2-prior-knowledge
+        address = self.router_qdra.http_addresses[0] + "/myinfo"
+        out = self.run_curl(args=['-d', 'fname=John&lname=Doe', '-X', 'PUT'], address=address)
+        self.assertIn('Success! Your first name is John, last name is Doe', out)
+
+    @SkipIfNeeded(skip_test(), "Python 3.7 or greater, Quart 0.13.0 or greater and curl needed to run http2 tests")
+    def test_patch_request(self):
+        # curl -d "fname=John&lname=Doe" -X PATCH 127.0.0.1:9000/myinfo --http2-prior-knowledge
+        address = self.router_qdra.http_addresses[0] + "/patch"
+        out = self.run_curl(args=['--data', '{\"op\":\"add\",\"path\":\"/user\",\"value\":\"jane\"}', '-X', 'PATCH'], address=address)
+        self.assertIn('"op":"add"', out)
+
+    @SkipIfNeeded(skip_test(), "Python 3.7 or greater, Quart 0.13.0 or greater and curl needed to run http2 tests")
+    def test_404(self):
+        # Run curl 127.0.0.1:port/unavilable --http2-prior-knowledge
+        address = self.router_qdra.http_addresses[0] + "/unavilable"
+        out = self.run_curl(address=address)
+        self.assertIn('404 Not Found', out)
+
+    @SkipIfNeeded(skip_test(), "Python 3.7 or greater, Quart 0.13.0 or greater and curl needed to run http2 tests")
+    def test_500(self):
+        # Run curl 127.0.0.1:port/unavilable --http2-prior-knowledge
+        address = self.router_qdra.http_addresses[0] + "/test/500"
+        out = self.run_curl(address=address)
+        self.assertIn('500 Internal Server Error', out)
+
+    @SkipIfNeeded(skip_test(), "Python 3.7 or greater, Quart 0.13.0 or greater and curl needed to run http2 tests")
+    def test_get_image_png(self):
+        # Run curl 127.0.0.1:port --http2-prior-knowledge
+        passed = False
+        try:
+            address = self.router_qdra.http_addresses[0] + "/images/balanced-routing.png"
+            self.run_curl(address=address)
+        except UnicodeDecodeError as u:
+            if "codec can't decode byte 0x89" in str(u):
+                passed = True
+        self.assertTrue(passed)
+
+    @SkipIfNeeded(skip_test(), "Python 3.7 or greater, Quart 0.13.0 or greater and curl needed to run http2 tests")
+    def test_get_image_jpg(self):
+        # Run curl 127.0.0.1:port --http2-prior-knowledge
+        passed = False
+        try:
+            address = self.router_qdra.http_addresses[0] + "/images/apache.jpg"
+            self.run_curl(address=address)
+        except UnicodeDecodeError as u:
+            print (u)
+            if "codec can't decode byte 0xff" in str(u):
+                passed = True
+        self.assertTrue(passed)
+
+
+class Http2TestOneRouter(Http2TestBase, CommonHttp2Tests):
     @classmethod
     def setUpClass(cls):
-        super(Http2Test, cls).setUpClass()
+        super(Http2TestOneRouter, cls).setUpClass()
+        if skip_test():
+            return
+        cls.http2_server_name = "http2_server"
+        os.environ["QUART_APP"] = "http2server:app"
+        os.environ['SERVER_LISTEN_PORT'] = str(cls.tester.get_port())
+        cls.http2_server = cls.tester.http2server(name=cls.http2_server_name,
+                                                  listen_port=int(os.getenv('SERVER_LISTEN_PORT')),
+                                                  py_string='python3',
+                                                  server_file="http2_server.py")
         name = "http2-test-router"
-        cls.router_http2_listen_port = cls.tester.get_port()
         config = Qdrouterd.Config([
             ('router', {'mode': 'standalone', 'id': 'QDR'}),
-            ('listener', {'port': cls.router_http2_listen_port, 'role': 'normal', 'host': '0.0.0.0'}),
-            ('httpListener', {'port': cls.tester.get_port(), 'address': 'examples',
-                              'host': '0.0.0.0'})
+            ('listener', {'port': cls.tester.get_port(), 'role': 'normal', 'host': '0.0.0.0'}),
 
+            ('httpListener', {'port': cls.tester.get_port(), 'address': 'examples',
+                              'host': '127.0.0.1', 'protocolVersion': 'HTTP2'}),
+            ('httpConnector',
+             {'port': os.getenv('SERVER_LISTEN_PORT'), 'address': 'examples',
+              'host': '127.0.0.1', 'protocolVersion': 'HTTP2'})
         ])
-        cls.router = cls.tester.qdrouterd(name, config, wait=True)
+        cls.router_qdra = cls.tester.qdrouterd(name, config, wait=True)
+
+
+
+class Http2TestTwoRouter(Http2TestBase, CommonHttp2Tests):
+    @classmethod
+    def setUpClass(cls):
+        super(Http2TestTwoRouter, cls).setUpClass()
+        if skip_test():
+            return
         cls.http2_server_name = "http2_server"
-        #cls.server_http2_listen_port = cls.tester.get_port()
-        cls.server_http2_listen_port = 5000
         os.environ["QUART_APP"] = "http2server:app"
-        cls.http2_server = cls.tester.httpserver(name=cls.http2_server_name,
-                                                 listen_port=cls.server_http2_listen_port,
-                                                 py_string='python3',
-                                                 server_file="http2_server.py")
-        sleep(5)
+        os.environ['SERVER_LISTEN_PORT'] = str(cls.tester.get_port())
+        cls.http2_server = cls.tester.http2server(name=cls.http2_server_name,
+                                                  listen_port=int(os.getenv('SERVER_LISTEN_PORT')),
+                                                  py_string='python3',
+                                                  server_file="http2_server.py")
+        name = "http2-test-router"
+        inter_router_port = cls.tester.get_port()
 
-    def test_head_request(self):
-        pass
+        config_qdra = Qdrouterd.Config([
+            ('router', {'mode': 'interior', 'id': 'QDR.A'}),
+            ('listener', {'port': cls.tester.get_port(), 'role': 'normal', 'host': '0.0.0.0'}),
+            ('httpListener', {'port': cls.tester.get_port(), 'address': 'examples',
+                              'host': '127.0.0.1', 'protocolVersion': 'HTTP2'}),
+            ('listener', {'role': 'inter-router', 'port': inter_router_port})
+        ])
+
+        config_qdrb = Qdrouterd.Config([
+            ('router', {'mode': 'interior', 'id': 'QDR.B'}),
+            ('listener', {'port': cls.tester.get_port(), 'role': 'normal', 'host': '0.0.0.0'}),
+            ('httpConnector',
+             {'port': os.getenv('SERVER_LISTEN_PORT'), 'address': 'examples',
+              'host': '127.0.0.1', 'protocolVersion': 'HTTP2'}),
+            ('connector', {'name': 'connectorToA', 'role': 'inter-router',
+                           'port': inter_router_port,
+                           'verifyHostname': 'no'})
+
+        ])
+
+        cls.router_qdra = cls.tester.qdrouterd(name, config_qdra, wait=True)
+        cls.router_qdrb = cls.tester.qdrouterd(name, config_qdrb, wait=True)
+
+        cls.router_qdra.wait_router_connected('QDR.B')
+        cls.router_qdrb.wait_router_connected('QDR.A')
 
+        sleep(2)
\ No newline at end of file


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