[lxc-devel] [pylxd/master] Add more exceptions to the API

rockstar on Github lxc-bot at linuxcontainers.org
Wed May 25 03:00:22 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 865 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160525/557a1540/attachment.bin>
-------------- next part --------------
From 51d99be3f98736ae39ba041445d8e1b64636e2ff Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul at eventuallyanyway.com>
Date: Tue, 24 May 2016 14:50:35 -0600
Subject: [PATCH 1/3] Raise NotFound when the container isn't found, rather
 than NameError.

---
 pylxd/container.py            | 5 +++--
 pylxd/exceptions.py           | 4 ++++
 pylxd/tests/test_container.py | 5 +++--
 3 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/pylxd/container.py b/pylxd/container.py
index 4701b41..b47a515 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -14,7 +14,7 @@
 
 import six
 
-from pylxd import mixin
+from pylxd import exceptions, mixin
 from pylxd.containerState import ContainerState
 from pylxd.operation import Operation
 
@@ -38,7 +38,8 @@ def get(cls, client, name):
         response = client.api.containers[name].get()
 
         if response.status_code == 404:
-            raise NameError('No container named "{}"'.format(name))
+            raise exceptions.NotFound(
+                'No container named "{}"'.format(name))
         container = cls(_client=client, **response.json()['metadata'])
         return container
 
diff --git a/pylxd/exceptions.py b/pylxd/exceptions.py
index 41b5400..6d89650 100644
--- a/pylxd/exceptions.py
+++ b/pylxd/exceptions.py
@@ -1,2 +1,6 @@
 class ClientConnectionFailed(Exception):
     """An exception raised when the Client connection fails."""
+
+
+class NotFound(Exception):
+    """Generic get failure exception."""
diff --git a/pylxd/tests/test_container.py b/pylxd/tests/test_container.py
index a7a0c99..7610793 100644
--- a/pylxd/tests/test_container.py
+++ b/pylxd/tests/test_container.py
@@ -1,4 +1,4 @@
-from pylxd import container
+from pylxd import container, exceptions
 from pylxd.tests import testing
 
 
@@ -24,7 +24,8 @@ def test_get_not_found(self):
         name = 'an-missing-container'
 
         self.assertRaises(
-            NameError, container.Container.get, self.client, name)
+            exceptions.NotFound,
+            container.Container.get, self.client, name)
 
     def test_create(self):
         """A new container is created."""

From 7603b2d7e35546dba813658b06441c9db111e02e Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul at eventuallyanyway.com>
Date: Tue, 24 May 2016 20:55:54 -0600
Subject: [PATCH 2/3] Add CreateFailed exception to Container.

---
 pylxd/container.py            |  2 +-
 pylxd/exceptions.py           |  4 ++++
 pylxd/tests/test_container.py | 15 +++++++++++++++
 pylxd/tests/testing.py        |  4 ++++
 4 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/pylxd/container.py b/pylxd/container.py
index b47a515..6013fb8 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -66,7 +66,7 @@ def create(cls, client, config, wait=False):
         response = client.api.containers.post(json=config)
 
         if response.status_code != 202:
-            raise RuntimeError('Error creating instance')
+            raise exceptions.CreateFailed()
         if wait:
             Operation.wait_for_operation(client, response.json()['operation'])
         return cls(name=config['name'], _client=client)
diff --git a/pylxd/exceptions.py b/pylxd/exceptions.py
index 6d89650..579392d 100644
--- a/pylxd/exceptions.py
+++ b/pylxd/exceptions.py
@@ -4,3 +4,7 @@ class ClientConnectionFailed(Exception):
 
 class NotFound(Exception):
     """Generic get failure exception."""
+
+
+class CreateFailed(Exception):
+    """Generic create failure exception."""
diff --git a/pylxd/tests/test_container.py b/pylxd/tests/test_container.py
index 7610793..fcbe49a 100644
--- a/pylxd/tests/test_container.py
+++ b/pylxd/tests/test_container.py
@@ -36,6 +36,21 @@ def test_create(self):
 
         self.assertEqual(config['name'], an_new_container.name)
 
+    def test_create_failed(self):
+        """If the container creation fails, CreateFailed is raised."""
+        def create_fail(request, context):
+            context.status_code = 500
+        self.add_rule({
+            'text': create_fail,
+            'method': 'POST',
+            'url': r'^http://pylxd.test/1.0/containers$',
+        })
+        config = {'name': 'an-new-container'}
+
+        self.assertRaises(
+            exceptions.CreateFailed,
+            container.Container.create, self.client, config)
+
     def test_reload(self):
         """A reload updates the properties of a container."""
         an_container = container.Container(
diff --git a/pylxd/tests/testing.py b/pylxd/tests/testing.py
index 88cde8c..9adfb37 100644
--- a/pylxd/tests/testing.py
+++ b/pylxd/tests/testing.py
@@ -17,3 +17,7 @@ def setUp(self):
 
     def tearDown(self):
         mock_services.stop_http_mock()
+
+    def add_rule(self, rule):
+        """Add a rule to the mock LXD service."""
+        mock_services.update_http_rules([rule])

From 120e165a2e628ca94c59f83bc9165e54236307a2 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul at eventuallyanyway.com>
Date: Tue, 24 May 2016 20:56:06 -0600
Subject: [PATCH 3/3] Add new exceptions to Profile and Image.

---
 pylxd/image.py              |  8 +++++---
 pylxd/profile.py            |  9 ++++++---
 pylxd/tests/mock_lxd.py     | 13 ++++++++++++-
 pylxd/tests/test_image.py   | 39 ++++++++++++++++++++++++++++++++++++++-
 pylxd/tests/test_profile.py | 38 +++++++++++++++++++++++++++++++++++++-
 5 files changed, 98 insertions(+), 9 deletions(-)

diff --git a/pylxd/image.py b/pylxd/image.py
index 87c7d1d..074bd7d 100644
--- a/pylxd/image.py
+++ b/pylxd/image.py
@@ -14,7 +14,7 @@
 import hashlib
 import six
 
-from pylxd import mixin
+from pylxd import exceptions, mixin
 from pylxd.operation import Operation
 
 
@@ -33,8 +33,7 @@ def get(cls, client, fingerprint):
         response = client.api.images[fingerprint].get()
 
         if response.status_code == 404:
-            raise NameError(
-                'No image with fingerprint "{}"'.format(fingerprint))
+            raise exceptions.NotFound()
         image = Image(_client=client, **response.json()['metadata'])
         return image
 
@@ -60,6 +59,9 @@ def create(cls, client, image_data, public=False, wait=False):
         response = client.api.images.post(
             data=image_data, headers=headers)
 
+        if response.status_code != 202:
+            raise exceptions.CreateFailed()
+
         if wait:
             Operation.wait_for_operation(client, response.json()['operation'])
         return cls.get(client, fingerprint)
diff --git a/pylxd/profile.py b/pylxd/profile.py
index 1dab086..87f2cf6 100644
--- a/pylxd/profile.py
+++ b/pylxd/profile.py
@@ -12,7 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 import six
-from pylxd import mixin
+from pylxd import exceptions, mixin
 
 
 class Profile(mixin.Marshallable):
@@ -29,7 +29,7 @@ def get(cls, client, name):
         response = client.api.profiles[name].get()
 
         if response.status_code == 404:
-            raise NameError('No profile with name "{}"'.format(name))
+            raise exceptions.NotFound()
         return cls(_client=client, **response.json()['metadata'])
 
     @classmethod
@@ -46,11 +46,14 @@ def all(cls, client):
     @classmethod
     def create(cls, client, name, config):
         """Create a profile."""
-        client.api.profiles.post(json={
+        request = client.api.profiles.post(json={
             'name': name,
             'config': config
             })
 
+        if request.status_code is not 202:
+            raise exceptions.CreateFailed()
+
         return cls.get(client, name)
 
     def __init__(self, **kwargs):
diff --git a/pylxd/tests/mock_lxd.py b/pylxd/tests/mock_lxd.py
index 9f144db..e693b2b 100644
--- a/pylxd/tests/mock_lxd.py
+++ b/pylxd/tests/mock_lxd.py
@@ -23,6 +23,16 @@ def container_DELETE(request, context):
     return json.dumps({'operation': 'operation-abc'})
 
 
+def images_POST(request, context):
+    context.status_code = 202
+    return json.dumps({'metadata': {}})
+
+
+def profiles_POST(request, context):
+    context.status_code = 202
+    return json.dumps({'metadata': {}})
+
+
 def profile_GET(request, context):
     name = request.path.split('/')[-1]
     if name in ('an-profile', 'an-new-profile'):
@@ -86,7 +96,7 @@ def profile_GET(request, context):
         'url': r'^http://pylxd.test/1.0/images$',
     },
     {
-        'text': json.dumps({'metadata': {}}),
+        'text': images_POST,
         'method': 'POST',
         'url': r'^http://pylxd.test/1.0/images$',
     },
@@ -109,6 +119,7 @@ def profile_GET(request, context):
         'url': r'^http://pylxd.test/1.0/profiles$',
     },
     {
+        'text': profiles_POST,
         'method': 'POST',
         'url': r'^http://pylxd.test/1.0/profiles$',
     },
diff --git a/pylxd/tests/test_image.py b/pylxd/tests/test_image.py
index 7e97997..27980b4 100644
--- a/pylxd/tests/test_image.py
+++ b/pylxd/tests/test_image.py
@@ -1,12 +1,35 @@
 import hashlib
 
-from pylxd import image
+from pylxd import exceptions, image
 from pylxd.tests import testing
 
 
 class TestImage(testing.PyLXDTestCase):
     """Tests for pylxd.image.Image."""
 
+    def test_get(self):
+        """An image is fetched."""
+        fingerprint = hashlib.sha256(b'').hexdigest()
+        a_image = image.Image.get(self.client, fingerprint)
+
+        self.assertEqual(fingerprint, a_image.fingerprint)
+
+    def test_get_not_found(self):
+        """NotFound is raised when the image isn't found."""
+        def not_found(request, context):
+            context.status_code = 404
+        self.add_rule({
+            'text': not_found,
+            'method': 'GET',
+            'url': r'^http://pylxd.test/1.0/images/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855$',  # NOQA
+        })
+
+        fingerprint = hashlib.sha256(b'').hexdigest()
+
+        self.assertRaises(
+            exceptions.NotFound,
+            image.Image.get, self.client, fingerprint)
+
     def test_all(self):
         """A list of all images is returned."""
         images = image.Image.all(self.client)
@@ -20,3 +43,17 @@ def test_create(self):
 
         self.assertIsInstance(a_image, image.Image)
         self.assertEqual(fingerprint, a_image.fingerprint)
+
+    def test_create_failed(self):
+        """If image creation fails, CreateFailed is raised."""
+        def create_fail(request, context):
+            context.status_code = 500
+        self.add_rule({
+            'text': create_fail,
+            'method': 'POST',
+            'url': r'^http://pylxd.test/1.0/images$',
+        })
+
+        self.assertRaises(
+            exceptions.CreateFailed,
+            image.Image.create, self.client, b'')
diff --git a/pylxd/tests/test_profile.py b/pylxd/tests/test_profile.py
index f8ea7e1..cd31e08 100644
--- a/pylxd/tests/test_profile.py
+++ b/pylxd/tests/test_profile.py
@@ -1,10 +1,31 @@
-from pylxd import profile
+from pylxd import exceptions, profile
 from pylxd.tests import testing
 
 
 class TestProfile(testing.PyLXDTestCase):
     """Tests for pylxd.profile.Profile."""
 
+    def test_get(self):
+        """A profile is fetched."""
+        name = 'an-profile'
+        an_profile = profile.Profile.get(self.client, name)
+
+        self.assertEqual(name, an_profile.name)
+
+    def test_get_not_found(self):
+        """NotFound is raised on unknown profiles."""
+        def not_found(request, context):
+            context.status_code = 404
+        self.add_rule({
+            'text': not_found,
+            'method': 'GET',
+            'url': r'^http://pylxd.test/1.0/profiles/(?P<container_name>.*)$',
+        })
+
+        self.assertRaises(
+            exceptions.NotFound,
+            profile.Profile.get, self.client, 'an-profile')
+
     def test_all(self):
         """A list of all profiles is returned."""
         profiles = profile.Profile.all(self.client)
@@ -18,3 +39,18 @@ def test_create(self):
 
         self.assertIsInstance(an_profile, profile.Profile)
         self.assertEqual('an-new-profile', an_profile.name)
+
+    def test_create_failed(self):
+        """CreateFailed is raised when errors occur."""
+        def error(request, context):
+            context.status_code = 503
+        self.add_rule({
+            'text': error,
+            'method': 'POST',
+            'url': r'^http://pylxd.test/1.0/profiles$',
+        })
+
+        self.assertRaises(
+            exceptions.CreateFailed,
+            profile.Profile.create, self.client,
+            name='an-new-profile', config={})


More information about the lxc-devel mailing list