Browse Source

service: detailed error messages for create and start

Fixes: #3355

Signed-off-by: Tomas Tomecek <[email protected]>
Tomas Tomecek 9 years ago
parent
commit
fea970dff3
5 changed files with 28 additions and 4 deletions
  1. 3 1
      compose/cli/main.py
  2. 7 0
      compose/errors.py
  3. 4 0
      compose/parallel.py
  4. 10 2
      compose/service.py
  5. 4 1
      tests/integration/service_test.py

+ 3 - 1
compose/cli/main.py

@@ -32,6 +32,7 @@ from ..service import BuildError
 from ..service import ConvergenceStrategy
 from ..service import ImageType
 from ..service import NeedsBuildError
+from ..service import OperationFailedError
 from .command import get_config_from_options
 from .command import project_from_options
 from .docopt_command import DocoptDispatcher
@@ -61,7 +62,8 @@ def main():
     except (KeyboardInterrupt, signals.ShutdownException):
         log.error("Aborting.")
         sys.exit(1)
-    except (UserError, NoSuchService, ConfigurationError, ProjectError) as e:
+    except (UserError, NoSuchService, ConfigurationError,
+            ProjectError, OperationFailedError) as e:
         log.error(e.msg)
         sys.exit(1)
     except BuildError as e:

+ 7 - 0
compose/errors.py

@@ -0,0 +1,7 @@
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+
+class OperationFailedError(Exception):
+    def __init__(self, reason):
+        self.msg = reason

+ 4 - 0
compose/parallel.py

@@ -12,6 +12,7 @@ from six.moves.queue import Empty
 from six.moves.queue import Queue
 
 from compose.cli.signals import ShutdownException
+from compose.errors import OperationFailedError
 from compose.utils import get_output_stream
 
 
@@ -47,6 +48,9 @@ def parallel_execute(objects, func, get_name, msg, get_deps=None):
         elif isinstance(exception, APIError):
             errors[get_name(obj)] = exception.explanation
             writer.write(get_name(obj), 'error')
+        elif isinstance(exception, OperationFailedError):
+            errors[get_name(obj)] = exception.msg
+            writer.write(get_name(obj), 'error')
         elif isinstance(exception, UpstreamError):
             writer.write(get_name(obj), 'error')
         else:

+ 10 - 2
compose/service.py

@@ -27,6 +27,7 @@ from .const import LABEL_PROJECT
 from .const import LABEL_SERVICE
 from .const import LABEL_VERSION
 from .container import Container
+from .errors import OperationFailedError
 from .parallel import parallel_execute
 from .parallel import parallel_start
 from .progress_stream import stream_output
@@ -278,7 +279,11 @@ class Service(object):
         if 'name' in container_options and not quiet:
             log.info("Creating %s" % container_options['name'])
 
-        return Container.create(self.client, **container_options)
+        try:
+            return Container.create(self.client, **container_options)
+        except APIError as ex:
+            raise OperationFailedError("Cannot create container for service %s: %s" %
+                                       (self.name, ex.explanation))
 
     def ensure_image_exists(self, do_build=BuildAction.none):
         if self.can_be_built() and do_build == BuildAction.force:
@@ -448,7 +453,10 @@ class Service(object):
 
     def start_container(self, container):
         self.connect_container_to_networks(container)
-        container.start()
+        try:
+            container.start()
+        except APIError as ex:
+            raise OperationFailedError("Cannot start service %s: %s" % (self.name, ex.explanation))
         return container
 
     def connect_container_to_networks(self, container):

+ 4 - 1
tests/integration/service_test.py

@@ -738,7 +738,10 @@ class ServiceTest(DockerClientTestCase):
 
         self.assertEqual(len(service.containers()), 1)
         self.assertTrue(service.containers()[0].is_running)
-        self.assertIn("ERROR: for composetest_web_2  Boom", mock_stderr.getvalue())
+        self.assertIn(
+            "ERROR: for composetest_web_2  Cannot create container for service web: Boom",
+            mock_stderr.getvalue()
+        )
 
     def test_scale_with_unexpected_exception(self):
         """Test that when scaling if the API returns an error, that is not of type