Repository: praekeltfoundation/seaworthy

Issue:
Use `rabbitmqctl wait` to wait for RabbitMQ containers to start
https://www.rabbitmq.com/rabbitmqctl.8.html#wait

Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index b4d4692..532ae77 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,10 +18,6 @@ after_success: if [[ $TOXENV =~ py..-(full|pytest3) ]]; then codecov; fi
 
 jobs:
   include:
-    - env: TOXENV=py34-core,py34-pytest,py34-testtools,py34-full
-      python: '3.4'
-      services: docker
-
     - env: TOXENV=py35-core,py35-pytest,py35-testtools,py35-full
       python: '3.5'
       services: docker
diff --git a/seaworthy/containers/rabbitmq.py b/seaworthy/containers/rabbitmq.py
index a4e3c4b..12b095a 100644
--- a/seaworthy/containers/rabbitmq.py
+++ b/seaworthy/containers/rabbitmq.py
@@ -20,9 +20,11 @@ class RabbitMQContainer(ContainerDefinition):
        Write more docs.
     """
 
-    # For some reason this container is slower to start through seaworthy than
-    # with a plain `docker run`, so give it a bit more time to get going. :-(
-    WAIT_TIMEOUT = 20.0
+    # There seems to be a weird interaction between the erlang runtime and
+    # something in docker which results in annoyingly long startup times in
+    # some environments. The best we can do to deal with that is to give it a
+    # bit more time to get going. :-(
+    WAIT_TIMEOUT = 30.0
 
     DEFAULT_NAME = 'rabbitmq'
     DEFAULT_IMAGE = 'rabbitmq:alpine'
@@ -51,6 +53,14 @@ class RabbitMQContainer(ContainerDefinition):
         self.user = user
         self.password = password
 
+    def wait_for_start(self):
+        """
+        Wait for the RabbitMQ process to be come up.
+        """
+        er = self.exec_rabbitmqctl(
+            'wait', ['--pid', '1', '--timeout', str(int(self.wait_timeout))])
+        output_lines(er, error_exc=TimeoutError)
+
     def base_kwargs(self):
         """
         Add a ``tmpfs`` entry for ``/var/lib/rabbitmq`` to avoid unnecessary
diff --git a/seaworthy/utils.py b/seaworthy/utils.py
index c2b1637..4eaa548 100644
--- a/seaworthy/utils.py
+++ b/seaworthy/utils.py
@@ -1,7 +1,7 @@
 from docker.models.containers import ExecResult
 
 
-def output_lines(output, encoding='utf-8'):
+def output_lines(output, encoding='utf-8', error_exc=None):
     """
     Convert bytestring container output or the result of a container exec
     command into a sequence of unicode lines.
@@ -9,12 +9,19 @@ def output_lines(output, encoding='utf-8'):
     :param output:
         Container output bytes or an
         :class:`docker.models.containers.ExecResult` instance.
-    :param encoding: The encoding to use when converting bytes to unicode
+    :param encoding:
+        The encoding to use when converting bytes to unicode
         (default ``utf-8``).
+    :param error_exc:
+        Optional exception to raise if ``output`` is an ``ExecResult`` with a
+        nonzero exit code.
 
     :returns: list[str]
+
     """
     if isinstance(output, ExecResult):
-        _, output = output
+        exit_code, output = output
+        if exit_code != 0 and error_exc is not None:
+            raise error_exc(output.decode(encoding))
 
     return output.decode(encoding).splitlines()


Test patch:
diff --git a/seaworthy/tests-core/test_utils.py b/seaworthy/tests-core/test_utils.py
index 0067627..66c5260 100644
--- a/seaworthy/tests-core/test_utils.py
+++ b/seaworthy/tests-core/test_utils.py
@@ -18,3 +18,10 @@ class TestOutputLinesFunc(unittest.TestCase):
     def test_custom_encoding(self):
         """String lines can be parsed using a custom encoding."""
         self.assertEqual(output_lines(b'\xe1', encoding='latin1'), ['á'])
+
+    def test_exec_result_error_exc(self):
+        """ExecResult with nonzero exit code can raise exception."""
+        with self.assertRaisesRegex(TimeoutError, 'x\r\ny'):
+            output_lines(ExecResult(128, b'x\r\ny'), error_exc=TimeoutError)
+        # No exception if the exit code is zero.
+        self.assertEqual(output_lines(ExecResult(0, b':-)')), [':-)'])