[lxc-devel] [pylxd/master] Better http assertions
rockstar on Github
lxc-bot at linuxcontainers.org
Fri Jun 3 20:36:52 UTC 2016
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 667 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160603/44199bef/attachment.bin>
-------------- next part --------------
From 28daa60c1176519c819016f1d7af91e678984a6e Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Fri, 3 Jun 2016 13:32:05 -0600
Subject: [PATCH 1/3] Add response assertions for lxd
This exposed a bug where deleting a running container doesn't
report that the deletion didn't occur.
---
integration/test_containers.py | 2 +-
integration/test_images.py | 10 ++++++--
integration/testing.py | 29 +++++++++++++++++++----
pylxd/client.py | 54 +++++++++++++++++++++++++++++++++++++-----
pylxd/container.py | 42 ++++++++++++++++----------------
pylxd/exceptions.py | 33 ++++++++++++++++++++++++--
pylxd/image.py | 25 +++++++++++--------
pylxd/profile.py | 19 +++++++++------
8 files changed, 162 insertions(+), 52 deletions(-)
diff --git a/integration/test_containers.py b/integration/test_containers.py
index 609f19b..156f50e 100644
--- a/integration/test_containers.py
+++ b/integration/test_containers.py
@@ -132,7 +132,7 @@ def test_snapshot(self):
def test_put_get_file(self):
"""A file is written to the container and then read."""
filepath = '/tmp/an_file'
- data = 'abcdef'
+ data = b'abcdef'
retval = self.container.put_file(filepath, data)
diff --git a/integration/test_images.py b/integration/test_images.py
index a16932e..4e2d94f 100644
--- a/integration/test_images.py
+++ b/integration/test_images.py
@@ -11,6 +11,8 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+import time
+
from pylxd import exceptions
from integration.testing import create_busybox_image, IntegrationTestCase
@@ -33,6 +35,9 @@ def test_all(self):
fingerprint, _ = self.create_image()
self.addCleanup(self.delete_image, fingerprint)
+ # XXX: rockstar (02 Jun 2016) - This seems to have a failure
+ # of some sort. This is a hack.
+ time.sleep(5)
images = self.client.images.all()
self.assertIn(fingerprint, [image.fingerprint for image in images])
@@ -42,8 +47,9 @@ def test_create(self):
path, fingerprint = create_busybox_image()
self.addCleanup(self.delete_image, fingerprint)
- with open(path) as f:
- image = self.client.images.create(f.read(), wait=True)
+ with open(path, 'rb') as f:
+ data = f.read()
+ image = self.client.images.create(data, wait=True)
self.assertEqual(fingerprint, image.fingerprint)
diff --git a/integration/testing.py b/integration/testing.py
index c8ee7be..a6ea93b 100644
--- a/integration/testing.py
+++ b/integration/testing.py
@@ -14,6 +14,7 @@
import unittest
import uuid
+from pylxd import exceptions
from pylxd.client import Client
from integration.busybox import create_busybox_image
@@ -59,9 +60,19 @@ def delete_container(self, name, enforce=False):
# enforce is a hack. There's a race somewhere in the delete.
# To ensure we don't get an infinite loop, let's count.
count = 0
- result = self.lxd['containers'][name].delete()
- while enforce and result.status_code == 404 and count < 10:
+ try:
result = self.lxd['containers'][name].delete()
+ except exceptions.LXDAPIException as e:
+ if e.response.status_code in (400, 404):
+ return
+ raise
+ while enforce and result.status_code == 404 and count < 10:
+ try:
+ result = self.lxd['containers'][name].delete()
+ except exceptions.LXDAPIException as e:
+ if e.response.status_code in (400, 404):
+ return
+ raise
count += 1
try:
operation_uuid = result.json()['operation'].split('/')[-1]
@@ -92,7 +103,12 @@ def create_image(self):
def delete_image(self, fingerprint):
"""Delete an image in lxd."""
- self.lxd.images[fingerprint].delete()
+ try:
+ self.lxd.images[fingerprint].delete()
+ except exceptions.LXDAPIException as e:
+ if e.response.status_code == 404:
+ return
+ raise
def create_profile(self):
"""Create a profile."""
@@ -106,7 +122,12 @@ def create_profile(self):
def delete_profile(self, name):
"""Delete a profile."""
- self.lxd.profiles[name].delete()
+ try:
+ self.lxd.profiles[name].delete()
+ except exceptions.LXDAPIException as e:
+ if e.response.status_code == 404:
+ return
+ raise
def assertCommon(self, response):
"""Assert common LXD responses.
diff --git a/pylxd/client.py b/pylxd/client.py
index cfec79a..b8ea444 100644
--- a/pylxd/client.py
+++ b/pylxd/client.py
@@ -27,8 +27,7 @@
class _APINode(object):
- """An api node object.
- """
+ """An api node object."""
def __init__(self, api_endpoint, cert=None, verify=True):
self._api_endpoint = api_endpoint
@@ -50,21 +49,64 @@ def __getitem__(self, item):
'{}/{}'.format(self._api_endpoint, item),
cert=self.session.cert, verify=self.session.verify)
+ def _assert_response(self, response, allowed_status_codes=(200,)):
+ """Assert properties of the response.
+
+ LXD's API clearly defines specific responses. If the API
+ response is something unexpected (i.e. an error), then
+ we need to raise an exception and have the call points
+ handle the errors or just let the issue be raised to the
+ user.
+ """
+ if response.status_code not in allowed_status_codes:
+ raise exceptions.LXDAPIException(response)
+
+ try:
+ data = response.json()
+ except ValueError:
+ # Not a JSON response
+ return
+
+ if response.status_code == 200:
+ # Synchronous request
+ try:
+ if data['type'] != 'sync':
+ raise exceptions.LXDAPIException(response)
+ except KeyError:
+ # Missing 'type' in response
+ raise exceptions.LXDAPIException(response)
+
+ if response.status_code == 202:
+ try:
+ if data['type'] != 'async':
+ raise exceptions.LXDAPIException(response)
+ except KeyError:
+ # Missing 'type' in response
+ raise exceptions.LXDAPIException(response)
+
def get(self, *args, **kwargs):
"""Perform an HTTP GET."""
- return self.session.get(self._api_endpoint, *args, **kwargs)
+ response = self.session.get(self._api_endpoint, *args, **kwargs)
+ self._assert_response(response)
+ return response
def post(self, *args, **kwargs):
"""Perform an HTTP POST."""
- return self.session.post(self._api_endpoint, *args, **kwargs)
+ response = self.session.post(self._api_endpoint, *args, **kwargs)
+ self._assert_response(response, allowed_status_codes=(200, 202))
+ return response
def put(self, *args, **kwargs):
"""Perform an HTTP PUT."""
- return self.session.put(self._api_endpoint, *args, **kwargs)
+ response = self.session.put(self._api_endpoint, *args, **kwargs)
+ self._assert_response(response, allowed_status_codes=(200, 202))
+ return response
def delete(self, *args, **kwargs):
"""Perform an HTTP delete."""
- return self.session.delete(self._api_endpoint, *args, **kwargs)
+ response = self.session.delete(self._api_endpoint, *args, **kwargs)
+ self._assert_response(response, allowed_status_codes=(200, 202))
+ return response
class Client(object):
diff --git a/pylxd/container.py b/pylxd/container.py
index 4620f58..21a5c69 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -44,16 +44,15 @@ def put(self, filepath, data):
return response.status_code == 200
def get(self, filepath):
- response = self._client.api.containers[
- self._container.name].files.get(
- params={'path': filepath})
- if response.status_code == 500:
- # XXX: rockstar (15 Feb 2016) - This should really
- # return a 404. I blame LXD.
- raise exceptions.NotFound({
- 'error': '{} not found in container {}'.format(
- filepath, self._container.name
- )})
+ try:
+ response = self._client.api.containers[
+ self._container.name].files.get(
+ params={'path': filepath})
+ except exceptions.LXDAPIException as e:
+ # LXD 2.0.3+ return 404, not 500,
+ if e.response.status_code in (500, 404):
+ raise exceptions.NotFound()
+ raise
return response.content
__slots__ = [
@@ -65,10 +64,13 @@ def get(self, filepath):
@classmethod
def get(cls, client, name):
"""Get a container by name."""
- response = client.api.containers[name].get()
+ try:
+ response = client.api.containers[name].get()
+ except exceptions.LXDAPIException as e:
+ if e.response.status_code == 404:
+ raise exceptions.NotFound()
+ raise
- if response.status_code == 404:
- raise exceptions.NotFound(response.json())
container = cls(_client=client, **response.json()['metadata'])
return container
@@ -95,7 +97,7 @@ def create(cls, client, config, wait=False):
response = client.api.containers.post(json=config)
if response.status_code != 202:
- raise exceptions.CreateFailed(response.json())
+ raise exceptions.CreateFailed(response)
if wait:
Operation.wait_for_operation(client, response.json()['operation'])
return cls(name=config['name'], _client=client)
@@ -151,8 +153,6 @@ def delete(self, wait=False):
"""Delete the container."""
response = self._client.api.containers[self.name].delete()
- if response.status_code != 202:
- raise RuntimeError('Error deleting instance {}'.format(self.name))
if wait:
self.wait_for_operation(response.json()['operation'])
@@ -260,10 +260,12 @@ class Snapshot(mixin.Waitable, mixin.Marshallable):
@classmethod
def get(cls, client, container, name):
- response = client.api.containers[container.name].snapshots[name].get()
-
- if response.status_code == 404:
- raise exceptions.NotFound(response.json())
+ try:
+ response = client.api.containers[container.name].snapshots[name].get()
+ except exceptions.LXDAPIException as e:
+ if e.response.status_code == 404:
+ raise exceptions.NotFound()
+ raise
snapshot = cls(
_client=client, _container=container,
diff --git a/pylxd/exceptions.py b/pylxd/exceptions.py
index 9e243dd..8fe70a1 100644
--- a/pylxd/exceptions.py
+++ b/pylxd/exceptions.py
@@ -9,12 +9,38 @@ def __str__(self):
return "LXD client certificates are not trusted."""
+class LXDAPIException(Exception):
+ """A generic exception for representing unexpected LXD API responses.
+
+ LXD API responses are clearly documented, and are either a standard
+ return value, and background operation, or an error. This exception
+ is raised on an error case, or when the response status code is
+ not expected for the response.
+ """
+
+ def __init__(self, response):
+ super(LXDAPIException, self).__init__()
+ self.response = response
+
+ def __str__(self):
+ try:
+ data = self.response.json()
+ return data['error']
+ except (ValueError, KeyError):
+ pass
+ return self.response.content.decode('utf-8')
+
+
class _LXDAPIException(Exception):
"""A LXD API Exception.
An exception representing an issue in the LXD API. It
contains the error information returned from LXD.
+ This exception type should be phased out, with the exception being
+ raised at a lower level (i.e. where we actually talk to the LXD
+ API, not in our pylxd logic).
+
DO NOT CATCH THIS EXCEPTION DIRECTLY.
"""
@@ -25,8 +51,11 @@ def __str__(self):
return self.data.get('error')
-class NotFound(_LXDAPIException):
- """Generic get failure exception."""
+class NotFound(Exception):
+ """Generic NotFound exception."""
+
+ def __str__(self):
+ return 'Object not found'
class CreateFailed(_LXDAPIException):
diff --git a/pylxd/image.py b/pylxd/image.py
index 535c083..104d91f 100644
--- a/pylxd/image.py
+++ b/pylxd/image.py
@@ -30,11 +30,14 @@ class Image(mixin.Waitable, mixin.Marshallable):
@classmethod
def get(cls, client, fingerprint):
"""Get an image."""
- response = client.api.images[fingerprint].get()
+ try:
+ response = client.api.images[fingerprint].get()
+ except exceptions.LXDAPIException as e:
+ if e.response.status_code == 404:
+ raise exceptions.NotFound()
+ raise
- if response.status_code == 404:
- raise exceptions.NotFound(response.json())
- image = Image(_client=client, **response.json()['metadata'])
+ image = cls(_client=client, **response.json()['metadata'])
return image
@classmethod
@@ -45,7 +48,7 @@ def all(cls, client):
images = []
for url in response.json()['metadata']:
fingerprint = url.split('/')[-1]
- images.append(Image(_client=client, fingerprint=fingerprint))
+ images.append(cls(_client=client, fingerprint=fingerprint))
return images
@classmethod
@@ -64,7 +67,7 @@ def create(cls, client, image_data, public=False, wait=False):
if wait:
Operation.wait_for_operation(client, response.json()['operation'])
- return cls.get(client, fingerprint)
+ return cls(_client=client, fingerprint=fingerprint)
def __init__(self, **kwargs):
super(Image, self).__init__()
@@ -89,10 +92,12 @@ def delete(self, wait=False):
def fetch(self):
"""Fetch the object from LXD, populating attributes."""
- response = self._client.api.images[self.fingerprint].get()
-
- if response.status_code == 404:
- raise exceptions.NotFound(response.json())
+ try:
+ response = self._client.api.images[self.fingerprint].get()
+ except exceptions.LXDAPIException as e:
+ if e.response.status_code == 404:
+ raise exceptions.NotFound()
+ raise
for key, val in six.iteritems(response.json()['metadata']):
setattr(self, key, val)
diff --git a/pylxd/profile.py b/pylxd/profile.py
index 749aa0d..7cf1024 100644
--- a/pylxd/profile.py
+++ b/pylxd/profile.py
@@ -26,10 +26,13 @@ class Profile(mixin.Marshallable):
@classmethod
def get(cls, client, name):
"""Get a profile."""
- response = client.api.profiles[name].get()
+ try:
+ response = client.api.profiles[name].get()
+ except exceptions.LXDAPIException as e:
+ if e.response.status_code == 404:
+ raise exceptions.NotFound()
+ raise
- if response.status_code == 404:
- raise exceptions.NotFound(response.json())
return cls(_client=client, **response.json()['metadata'])
@classmethod
@@ -85,10 +88,12 @@ def delete(self):
def fetch(self):
"""Fetch the object from LXD, populating attributes."""
- response = self._client.api.profiles[self.name].get()
-
- if response.status_code == 404:
- raise exceptions.NotFound(response.json())
+ try:
+ response = self._client.api.profiles[self.name].get()
+ except exceptions.LXDAPIException as e:
+ if e.response.status_code == 404:
+ raise exceptions.NotFound()
+ raise
for key, val in six.iteritems(response.json()['metadata']):
setattr(self, key, val)
From 0b4a0a323701cafd690e20a50d26e64e0e91515e Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Fri, 3 Jun 2016 14:27:45 -0600
Subject: [PATCH 2/3] Fix the unit tests
---
pylxd/container.py | 17 ++++---
pylxd/image.py | 10 ++--
pylxd/profile.py | 8 +--
pylxd/tests/mock_lxd.py | 115 ++++++++++++++++++++++++++++--------------
pylxd/tests/test_client.py | 24 +++++++--
pylxd/tests/test_container.py | 8 +--
6 files changed, 121 insertions(+), 61 deletions(-)
diff --git a/pylxd/container.py b/pylxd/container.py
index 21a5c69..39524f7 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -94,10 +94,11 @@ def all(cls, client):
@classmethod
def create(cls, client, config, wait=False):
"""Create a new container config."""
- response = client.api.containers.post(json=config)
+ try:
+ response = client.api.containers.post(json=config)
+ except exceptions.LXDAPIException as e:
+ raise exceptions.CreateFailed(e.response)
- if response.status_code != 202:
- raise exceptions.CreateFailed(response)
if wait:
Operation.wait_for_operation(client, response.json()['operation'])
return cls(name=config['name'], _client=client)
@@ -112,10 +113,12 @@ def __init__(self, **kwargs):
def fetch(self):
"""Reload the container information."""
- response = self._client.api.containers[self.name].get()
- if response.status_code == 404:
- raise NameError(
- 'Container named "{}" has gone away'.format(self.name))
+ try:
+ response = self._client.api.containers[self.name].get()
+ except exceptions.LXDAPIException as e:
+ if e.response.status_code == 404:
+ raise exceptions.NotFound()
+ raise
for key, value in six.iteritems(response.json()['metadata']):
setattr(self, key, value)
# XXX: rockstar (28 Mar 2016) - This method was named improperly
diff --git a/pylxd/image.py b/pylxd/image.py
index 104d91f..82b965d 100644
--- a/pylxd/image.py
+++ b/pylxd/image.py
@@ -59,11 +59,11 @@ def create(cls, client, image_data, public=False, wait=False):
headers = {}
if public:
headers['X-LXD-Public'] = '1'
- response = client.api.images.post(
- data=image_data, headers=headers)
-
- if response.status_code != 202:
- raise exceptions.CreateFailed(response.json())
+ try:
+ response = client.api.images.post(
+ data=image_data, headers=headers)
+ except exceptions.LXDAPIException as e:
+ raise exceptions.CreateFailed(e.response.json())
if wait:
Operation.wait_for_operation(client, response.json()['operation'])
diff --git a/pylxd/profile.py b/pylxd/profile.py
index 7cf1024..1bcc0e0 100644
--- a/pylxd/profile.py
+++ b/pylxd/profile.py
@@ -54,10 +54,10 @@ def create(cls, client, name, config=None, devices=None):
profile['config'] = config
if devices is not None:
profile['devices'] = devices
- response = client.api.profiles.post(json=profile)
-
- if response.status_code is not 200:
- raise exceptions.CreateFailed(response.json())
+ try:
+ client.api.profiles.post(json=profile)
+ except exceptions.LXDAPIException as e:
+ raise exceptions.CreateFailed(e.response.json())
return cls.get(client, name)
diff --git a/pylxd/tests/mock_lxd.py b/pylxd/tests/mock_lxd.py
index 07c0f39..6df21c9 100644
--- a/pylxd/tests/mock_lxd.py
+++ b/pylxd/tests/mock_lxd.py
@@ -3,32 +3,43 @@
def containers_POST(request, context):
context.status_code = 202
- return json.dumps({'operation': 'operation-abc'})
+ return json.dumps({
+ 'type': 'async',
+ 'operation': 'operation-abc'})
def container_DELETE(request, context):
context.status_code = 202
- return json.dumps({'operation': 'operation-abc'})
+ return json.dumps({
+ 'type': 'async',
+ 'operation': 'operation-abc'})
def images_POST(request, context):
context.status_code = 202
- return json.dumps({'metadata': {}})
+ return json.dumps({
+ 'type': 'async',
+ 'metadata': {}})
def profiles_POST(request, context):
context.status_code = 200
- return json.dumps({'metadata': {}})
+ return json.dumps({
+ 'type': 'sync',
+ 'metadata': {}})
def snapshot_DELETE(request, context):
context.status_code = 202
- return json.dumps({'operation': 'operation-abc'})
+ return json.dumps({
+ 'type': 'async',
+ 'operation': 'operation-abc'})
def profile_GET(request, context):
name = request.path.split('/')[-1]
return json.dumps({
+ 'type': 'sync',
'metadata': {
'name': name,
'description': 'An description',
@@ -41,8 +52,10 @@ def profile_GET(request, context):
RULES = [
# General service endpoints
{
- 'text': json.dumps({'metadata': {'auth': 'trusted',
- 'environment': {}}}),
+ 'text': json.dumps({
+ 'type': 'sync',
+ 'metadata': {'auth': 'trusted',
+ 'environment': {}}}),
'method': 'GET',
'url': r'^http://pylxd.test/1.0$',
},
@@ -50,9 +63,11 @@ def profile_GET(request, context):
# Containers
{
- 'text': json.dumps({'metadata': [
- 'http://pylxd.test/1.0/containers/an-container',
- ]}),
+ 'text': json.dumps({
+ 'type': 'sync',
+ 'metadata': [
+ 'http://pylxd.test/1.0/containers/an-container',
+ ]}),
'method': 'GET',
'url': r'^http://pylxd.test/1.0/containers$',
},
@@ -62,28 +77,36 @@ def profile_GET(request, context):
'url': r'^http://pylxd.test/1.0/containers$',
},
{
- 'text': json.dumps({'metadata': {
- 'name': 'an-container',
- 'ephemeral': True,
- }}),
+ 'text': json.dumps({
+ 'type': 'sync',
+ 'metadata': {
+ 'name': 'an-container',
+ 'ephemeral': True,
+ }}),
'method': 'GET',
'url': r'^http://pylxd.test/1.0/containers/an-container$',
},
{
- 'text': json.dumps({'metadata': {
- 'status': 'Running',
- 'status_code': 103,
- }}),
+ 'text': json.dumps({
+ 'type': 'sync',
+ 'metadata': {
+ 'status': 'Running',
+ 'status_code': 103,
+ }}),
'method': 'GET',
'url': r'^http://pylxd.test/1.0/containers/an-container/state$', # NOQA
},
{
- 'text': json.dumps({'operation': 'operation-abc'}),
+ 'text': json.dumps({
+ 'type': 'sync', # This should be async
+ 'operation': 'operation-abc'}),
'method': 'POST',
'url': r'^http://pylxd.test/1.0/containers/an-container$',
},
{
- 'text': json.dumps({'operation': 'operation-abc'}),
+ 'text': json.dumps({
+ 'type': 'sync', # This should be async
+ 'operation': 'operation-abc'}),
'method': 'PUT',
'url': r'^http://pylxd.test/1.0/containers/an-container$',
},
@@ -96,27 +119,35 @@ def profile_GET(request, context):
# Container Snapshots
{
- 'text': json.dumps({'metadata': [
- '/1.0/containers/an_container/snapshots/an-snapshot',
- ]}),
+ 'text': json.dumps({
+ 'type': 'sync',
+ 'metadata': [
+ '/1.0/containers/an_container/snapshots/an-snapshot',
+ ]}),
'method': 'GET',
'url': r'^http://pylxd.test/1.0/containers/an-container/snapshots$', # NOQA
},
{
- 'text': json.dumps({'operation': 'operation-abc'}),
+ 'text': json.dumps({
+ 'type': 'sync', # This should be async
+ 'operation': 'operation-abc'}),
'method': 'POST',
'url': r'^http://pylxd.test/1.0/containers/an-container/snapshots$', # NOQA
},
{
- 'text': json.dumps({'metadata': {
- 'name': 'an_container/an-snapshot',
- 'stateful': False,
- }}),
+ 'text': json.dumps({
+ 'type': 'sync',
+ 'metadata': {
+ 'name': 'an_container/an-snapshot',
+ 'stateful': False,
+ }}),
'method': 'GET',
'url': r'^http://pylxd.test/1.0/containers/an-container/snapshots/an-snapshot$', # NOQA
},
{
- 'text': json.dumps({'operation': 'operation-abc'}),
+ 'text': json.dumps({
+ 'type': 'sync', # This should be async
+ 'operation': 'operation-abc'}),
'method': 'POST',
'url': r'^http://pylxd.test/1.0/containers/an-container/snapshots/an-snapshot$', # NOQA
},
@@ -142,9 +173,11 @@ def profile_GET(request, context):
# Images
{
- 'text': json.dumps({'metadata': [
- 'http://pylxd.test/1.0/images/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', # NOQA
- ]}),
+ 'text': json.dumps({
+ 'type': 'sync',
+ 'metadata': [
+ 'http://pylxd.test/1.0/images/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', # NOQA
+ ]}),
'method': 'GET',
'url': r'^http://pylxd.test/1.0/images$',
},
@@ -155,6 +188,7 @@ def profile_GET(request, context):
},
{
'text': json.dumps({
+ 'type': 'sync',
'metadata': {
'aliases': [
{
@@ -182,9 +216,11 @@ def profile_GET(request, context):
# Profiles
{
- 'text': json.dumps({'metadata': [
- 'http://pylxd.test/1.0/profiles/an-profile',
- ]}),
+ 'text': json.dumps({
+ 'type': 'sync',
+ 'metadata': [
+ 'http://pylxd.test/1.0/profiles/an-profile',
+ ]}),
'method': 'GET',
'url': r'^http://pylxd.test/1.0/profiles$',
},
@@ -201,12 +237,17 @@ def profile_GET(request, context):
# Operations
{
- 'text': '{"metadata": {"id": "operation-abc"}}',
+ 'text': json.dumps({
+ 'type': 'sync',
+ 'metadata': {'id': 'operation-abc'},
+ }),
'method': 'GET',
'url': r'^http://pylxd.test/1.0/operations/operation-abc$',
},
{
- 'text': '{"metadata": {}',
+ 'text': json.dumps({
+ 'type': 'sync',
+ }),
'method': 'GET',
'url': r'^http://pylxd.test/1.0/operations/operation-abc/wait$',
},
diff --git a/pylxd/tests/test_client.py b/pylxd/tests/test_client.py
index 3f69d31..7871531 100644
--- a/pylxd/tests/test_client.py
+++ b/pylxd/tests/test_client.py
@@ -117,7 +117,11 @@ def test_session_unix_socket(self):
@mock.patch('pylxd.client.requests.Session')
def test_get(self, Session):
"""Perform a session get."""
- session = mock.Mock()
+ response = mock.Mock(**{
+ 'status_code': 200,
+ 'json.return_value': {'type': 'sync'},
+ })
+ session = mock.Mock(**{'get.return_value': response})
Session.return_value = session
node = client._APINode('http://test.com')
@@ -129,7 +133,11 @@ def test_get(self, Session):
@mock.patch('pylxd.client.requests.Session')
def test_post(self, Session):
"""Perform a session post."""
- session = mock.Mock()
+ response = mock.Mock(**{
+ 'status_code': 200,
+ 'json.return_value': {'type': 'sync'},
+ })
+ session = mock.Mock(**{'post.return_value': response})
Session.return_value = session
node = client._APINode('http://test.com')
@@ -141,7 +149,11 @@ def test_post(self, Session):
@mock.patch('pylxd.client.requests.Session')
def test_put(self, Session):
"""Perform a session put."""
- session = mock.Mock()
+ response = mock.Mock(**{
+ 'status_code': 200,
+ 'json.return_value': {'type': 'sync'},
+ })
+ session = mock.Mock(**{'put.return_value': response})
Session.return_value = session
node = client._APINode('http://test.com')
@@ -153,7 +165,11 @@ def test_put(self, Session):
@mock.patch('pylxd.client.requests.Session')
def test_delete(self, Session):
"""Perform a session delete."""
- session = mock.Mock()
+ response = mock.Mock(**{
+ 'status_code': 200,
+ 'json.return_value': {'type': 'sync'},
+ })
+ session = mock.Mock(**{'delete.return_value': response})
Session.return_value = session
node = client._APINode('http://test.com')
diff --git a/pylxd/tests/test_container.py b/pylxd/tests/test_container.py
index 932a38e..8a4d6b3 100644
--- a/pylxd/tests/test_container.py
+++ b/pylxd/tests/test_container.py
@@ -79,7 +79,7 @@ def test_fetch(self):
self.assertTrue(an_container.ephemeral)
def test_fetch_not_found(self):
- """NameError is raised on a 404 for updating container."""
+ """NotFound is raised on a 404 for updating container."""
def not_found(request, context):
context.status_code = 404
return json.dumps({
@@ -95,7 +95,7 @@ def not_found(request, context):
an_container = container.Container(
name='an-missing-container', _client=self.client)
- self.assertRaises(NameError, an_container.fetch)
+ self.assertRaises(exceptions.NotFound, an_container.fetch)
def test_update(self):
"""A container is updated."""
@@ -214,7 +214,7 @@ def test_delete(self):
# TODO: add an assertion here
def test_delete_failure(self):
- """If the response indicates delete failure, raise RuntimeError."""
+ """If the response indicates delete failure, raise an exception."""
def not_found(request, context):
context.status_code = 404
return json.dumps({
@@ -231,7 +231,7 @@ def not_found(request, context):
_client=self.client, _container=self.container,
name='an-snapshot')
- self.assertRaises(RuntimeError, snapshot.delete)
+ self.assertRaises(exceptions.LXDAPIException, snapshot.delete)
class TestFiles(testing.PyLXDTestCase):
From 6ead81fe0246b2e6b12e2649c2df2781bf921f37 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Fri, 3 Jun 2016 14:30:38 -0600
Subject: [PATCH 3/3] Fix lint
---
pylxd/container.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/pylxd/container.py b/pylxd/container.py
index 39524f7..6df712c 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -264,7 +264,8 @@ class Snapshot(mixin.Waitable, mixin.Marshallable):
@classmethod
def get(cls, client, container, name):
try:
- response = client.api.containers[container.name].snapshots[name].get()
+ response = client.api.containers[
+ container.name].snapshots[name].get()
except exceptions.LXDAPIException as e:
if e.response.status_code == 404:
raise exceptions.NotFound()
More information about the lxc-devel
mailing list