[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