Raise errors in a centralized fashion.
authorPete Vander Giessen <petevg@gmail.com>
Tue, 7 Mar 2017 16:30:00 +0000 (10:30 -0600)
committerPete Vander Giessen <petevg@gmail.com>
Tue, 7 Mar 2017 17:11:49 +0000 (11:11 -0600)
Fixes error with annotations that's exposed when we actually run tests
with the "raise errors" fix.

Added tests to verify that errors get raised.

juju/application.py
juju/client/connection.py
juju/model.py
tests/integration/test_errors.py [new file with mode: 0644]

index 9863d88..6b1f8ab 100644 (file)
@@ -253,7 +253,7 @@ class Application(model.ModelEntity):
         self.ann_facade.connect(self.connection)
 
         ann = client.EntityAnnotations(
-            entity=self.name,
+            entity=self.tag,
             annotations=annotations,
         )
         return await self.ann_facade.Set([ann])
index 9e8cb8f..f408135 100644 (file)
@@ -14,7 +14,7 @@ from http.client import HTTPSConnection
 import yaml
 
 from juju import tag
-from juju.errors import JujuAPIError, JujuConnectionError
+from juju.errors import JujuAPIError, JujuConnectionError, JujuError
 
 log = logging.getLogger("websocket")
 
@@ -91,8 +91,30 @@ class Connection:
         outgoing = json.dumps(msg, indent=2, cls=encoder)
         await self.ws.send(outgoing)
         result = await self.recv()
-        if result and 'error' in result:
+
+        if not result:
+            return result
+
+        if 'error' in result:
+            # API Error Response
             raise JujuAPIError(result)
+
+        if not 'response' in result:
+            # This may never happen
+            return result
+
+        if 'results' in result['response']:
+            # Check for errors in a result list.
+            errors = []
+            for res in result['response']['results']:
+                if res.get('error', {}).get('message'):
+                    errors.append(res['error']['message'])
+            if errors:
+                raise JujuError(errors)
+
+        elif result['response'].get('error', {}).get('message'):
+            raise JujuError(result['response']['error']['message'])
+
         return result
 
     def http_headers(self):
index 1106a16..548fc03 100644 (file)
@@ -1042,10 +1042,7 @@ class Model(object):
             )
             app.placement = placement
 
-            result = await app_facade.Deploy([app])
-            errors = [r.error.message for r in result.results if r.error]
-            if errors:
-                raise JujuError('\n'.join(errors))
+            await app_facade.Deploy([app])
             return await self._wait_for_new('application', application_name)
 
     def destroy(self):
@@ -1512,9 +1509,6 @@ class BundleHandler(object):
         self.plan = await self.client_facade.GetBundleChanges(
             yaml.dump(self.bundle))
 
-        if self.plan.errors:
-            raise JujuError('\n'.join(self.plan.errors))
-
     async def execute_plan(self):
         for step in self.plan.changes:
             method = getattr(self, step.method)
diff --git a/tests/integration/test_errors.py b/tests/integration/test_errors.py
new file mode 100644 (file)
index 0000000..4a95a27
--- /dev/null
@@ -0,0 +1,65 @@
+import pytest
+
+from .. import base
+
+MB = 1
+GB = 1024
+
+
+@base.bootstrapped
+@pytest.mark.asyncio
+async def test_juju_api_error(event_loop):
+    '''
+    Verify that we raise a JujuAPIError for responses with an error in
+    a top level key (for completely invalid requests).
+
+    '''
+    from juju.errors import JujuAPIError
+
+    async with base.CleanModel() as model:
+        with pytest.raises(JujuAPIError):
+            await model.add_machine(constraints={'mem': 'foo'})
+
+
+@base.bootstrapped
+@pytest.mark.asyncio
+async def test_juju_error_in_results_list(event_loop):
+    '''
+    Replicate the code that caused
+    https://github.com/juju/python-libjuju/issues/67, and verify that
+    we get a JujuError instead of passing silently by the failure.
+
+    (We don't raise a JujuAPIError, because the request isn't
+    completely invalid -- it's just passing a tag that doesn't exist.)
+
+    This also verifies that we will raise a JujuError any time there
+    is an error in one of a list of results.
+
+    '''
+    from juju.errors import JujuError
+    from juju.client import client
+
+    async with base.CleanModel() as model:
+        # Replicate
+        ann_facade = client.AnnotationsFacade()
+        ann_facade.connect(model.connection)
+
+        ann = client.EntityAnnotations(
+            entity='badtag',
+            annotations={'gui-x': '1', 'gui-y': '1'},
+        )
+        with pytest.raises(JujuError):
+            return await ann_facade.Set([ann])
+
+
+@base.bootstrapped
+@pytest.mark.asyncio
+async def test_juju_error_in_result(event_loop):
+    '''
+    Verify that we raise a JujuError when appropraite when we are
+    looking at a single result coming back.
+
+    # TODO: write this!
+
+    '''
+    pass