[lxc-devel] [pylxd/master] Files manager
rockstar on Github
lxc-bot at linuxcontainers.org
Tue May 31 03:29:35 UTC 2016
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 524 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160531/d5316358/attachment.bin>
-------------- next part --------------
From e87f17fff48b5e47da490f5e511fe306ade2471b Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul at eventuallyanyway.com>
Date: Mon, 30 May 2016 19:52:23 -0600
Subject: [PATCH 1/2] Update the mock service to be more strict
---
pylxd/tests/mock_lxd.py | 55 ++++++++++++++++++++++++-------------------
pylxd/tests/test_container.py | 6 ++---
pylxd/tests/test_profile.py | 4 ++--
3 files changed, 36 insertions(+), 29 deletions(-)
diff --git a/pylxd/tests/mock_lxd.py b/pylxd/tests/mock_lxd.py
index 30ddb0c..5e2aea4 100644
--- a/pylxd/tests/mock_lxd.py
+++ b/pylxd/tests/mock_lxd.py
@@ -21,19 +21,21 @@ def profiles_POST(request, context):
return json.dumps({'metadata': {}})
+def snapshot_DELETE(request, context):
+ context.status_code = 202
+ return json.dumps({'operation': 'operation-abc'})
+
+
def profile_GET(request, context):
name = request.path.split('/')[-1]
- if name in ('an-profile', 'an-new-profile'):
- return json.dumps({
- 'metadata': {
- 'name': name,
- 'description': 'An description',
- 'config': {},
- 'devices': {},
- },
- })
- else:
- context.status_code = 404
+ return json.dumps({
+ 'metadata': {
+ 'name': name,
+ 'description': 'An description',
+ 'config': {},
+ 'devices': {},
+ },
+ })
RULES = [
@@ -63,7 +65,7 @@ def profile_GET(request, context):
'ephemeral': True,
}}),
'method': 'GET',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container_name>.*)$',
+ 'url': r'^http://pylxd.test/1.0/containers/an-container$',
},
{
'text': json.dumps({'metadata': {
@@ -71,19 +73,19 @@ def profile_GET(request, context):
'status_code': 103,
}}),
'method': 'GET',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container_name>.*)/state$', # NOQA
+ 'url': r'^http://pylxd.test/1.0/containers/an-container/state$', # NOQA
},
{
'text': json.dumps({'metadata': [
'/1.0/containers/an_container/snapshots/an-snapshot',
]}),
'method': 'GET',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container_name>.*)/snapshots$', # NOQA
+ 'url': r'^http://pylxd.test/1.0/containers/an-container/snapshots$', # NOQA
},
{
'text': json.dumps({'operation': 'operation-abc'}),
'method': 'POST',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container_name>.*)/snapshots$', # NOQA
+ 'url': r'^http://pylxd.test/1.0/containers/an-container/snapshots$', # NOQA
},
{
'text': json.dumps({'metadata': {
@@ -91,32 +93,32 @@ def profile_GET(request, context):
'stateful': False,
}}),
'method': 'GET',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container>.*)/snapshots/(?P<snapshot>.*)$', # NOQA
+ 'url': r'^http://pylxd.test/1.0/containers/an-container/snapshots/an-snapshot$', # NOQA
},
{
'text': json.dumps({'operation': 'operation-abc'}),
'method': 'POST',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container>.*)/snapshots/(?P<snapshot>.*)$', # NOQA
+ 'url': r'^http://pylxd.test/1.0/containers/an-container/snapshots/an-snapshot$', # NOQA
},
{
- 'text': json.dumps({'operation': 'operation-abc'}),
+ 'text': snapshot_DELETE,
'method': 'DELETE',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container>.*)/snapshots/(?P<snapshot>.*)$', # NOQA
+ 'url': r'^http://pylxd.test/1.0/containers/an-container/snapshots/an-snapshot$', # NOQA
},
{
'text': json.dumps({'operation': 'operation-abc'}),
'method': 'POST',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container_name>.*)$',
+ 'url': r'^http://pylxd.test/1.0/containers/an-container$',
},
{
'text': json.dumps({'operation': 'operation-abc'}),
'method': 'PUT',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container_name>.*)$',
+ 'url': r'^http://pylxd.test/1.0/containers/an-container$',
},
{
'text': container_DELETE,
'method': 'DELETE',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container_name>.*)$',
+ 'url': r'^http://pylxd.test/1.0/containers/an-container$',
},
# Images
@@ -175,13 +177,18 @@ def profile_GET(request, context):
{
'text': profile_GET,
'method': 'GET',
- 'url': r'^http://pylxd.test/1.0/profiles/(?P<container_name>.*)$',
+ 'url': r'^http://pylxd.test/1.0/profiles/(an-profile|an-new-profile)$',
},
# Operations
{
'text': '{"metadata": {"id": "operation-abc"}}',
'method': 'GET',
- 'url': r'^http://pylxd.test/1.0/operations/(?P<operation_id>.*)$',
+ 'url': r'^http://pylxd.test/1.0/operations/operation-abc$',
+ },
+ {
+ 'text': '{"metadata": {}',
+ 'method': 'GET',
+ 'url': r'^http://pylxd.test/1.0/operations/operation-abc/wait$',
},
]
diff --git a/pylxd/tests/test_container.py b/pylxd/tests/test_container.py
index ca324ae..785b67b 100644
--- a/pylxd/tests/test_container.py
+++ b/pylxd/tests/test_container.py
@@ -32,7 +32,7 @@ def not_found(request, context):
self.add_rule({
'text': not_found,
'method': 'GET',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container_name>.*)$', # NOQA
+ 'url': r'^http://pylxd.test/1.0/containers/an-missing-container$', # NOQA
})
name = 'an-missing-container'
@@ -89,7 +89,7 @@ def not_found(request, context):
self.add_rule({
'text': not_found,
'method': 'GET',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container_name>.*)$', # NOQA
+ 'url': r'^http://pylxd.test/1.0/containers/an-missing-container$', # NOQA
})
an_container = container.Container(
@@ -224,7 +224,7 @@ def not_found(request, context):
self.add_rule({
'text': not_found,
'method': 'DELETE',
- 'url': r'^http://pylxd.test/1.0/containers/(?P<container>.*)/snapshots/(?P<snapshot>.*)$', # NOQA
+ 'url': r'^http://pylxd.test/1.0/containers/an-container/snapshots/an-snapshot$', # NOQA
})
snapshot = container.Snapshot(
diff --git a/pylxd/tests/test_profile.py b/pylxd/tests/test_profile.py
index 72e0692..9abc487 100644
--- a/pylxd/tests/test_profile.py
+++ b/pylxd/tests/test_profile.py
@@ -25,7 +25,7 @@ def not_found(request, context):
self.add_rule({
'text': not_found,
'method': 'GET',
- 'url': r'^http://pylxd.test/1.0/profiles/(?P<container_name>.*)$',
+ 'url': r'^http://pylxd.test/1.0/profiles/an-profile$',
})
self.assertRaises(
@@ -99,7 +99,7 @@ def not_found(request, context):
self.add_rule({
'text': not_found,
'method': 'GET',
- 'url': r'^http://pylxd.test/1.0/profiles/(?P<container_name>.*)$',
+ 'url': r'^http://pylxd.test/1.0/profiles/an-profile$',
})
an_profile = profile.Profile(name='an-profile', _client=self.client)
From 9d191119e46f31e2b68d8facc6f81c84f7bfdf61 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul at eventuallyanyway.com>
Date: Mon, 30 May 2016 21:14:59 -0600
Subject: [PATCH 2/2] Add a manager-esque FilesManager to namespace files.
---
pylxd/container.py | 46 +++++++++++++++++++++++++++++++------------
pylxd/exceptions.py | 8 ++++++--
pylxd/tests/mock_lxd.py | 40 ++++++++++++++++++++++++++-----------
pylxd/tests/test_container.py | 36 +++++++++++++++++++++++++++++++++
4 files changed, 104 insertions(+), 26 deletions(-)
diff --git a/pylxd/container.py b/pylxd/container.py
index 66a751b..96785f9 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -32,6 +32,31 @@ class Container(mixin.Waitable, mixin.Marshallable):
This class is not intended to be used directly, but rather to be used
via `Client.containers.create`.
"""
+ class FilesManager(object):
+ """A pseudo-manager for namespacing file operations."""
+
+ def __init__(self, client, container):
+ self._client = client
+ self._container = container
+
+ def put(self, filepath, data):
+ response = self._client.api.containers[
+ self._container.name].files.post(
+ params={'path': filepath}, data=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
+ )})
+ return response.content
class Snapshots(object):
def __init__(self, client, container):
@@ -89,6 +114,7 @@ def __init__(self, **kwargs):
setattr(self, key, value)
self.snapshots = self.Snapshots(self._client, self)
+ self.files = self.FilesManager(self._client, self)
def fetch(self):
"""Reload the container information."""
@@ -210,21 +236,15 @@ def delete_snapshot(self, name, wait=False): # pragma: no cover
snapshot = self.snapshots.get(name)
snapshot.delete()
- def get_file(self, filepath):
+ @deprecated('Container.get_file is deprecated. Please use Container.files.get') # NOQA
+ def get_file(self, filepath): # pragma: no cover
"""Get a file from the container."""
- response = self._client.api.containers[self.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 IOError('Error reading "{}"'.format(filepath))
- return response.content
-
- def put_file(self, filepath, data):
+ return self.files.get(filepath)
+
+ @deprecated('Container.put_file is deprecated. Please use Container.files.put') # NOQA
+ def put_file(self, filepath, data): # pragma: no cover
"""Put a file on the container."""
- response = self._client.api.containers[self.name].files.post(
- params={'path': filepath}, data=data)
- return response.status_code == 200
+ return self.files.put(filepath, data)
def execute(self, commands, environment={}):
"""Execute a command on the container."""
diff --git a/pylxd/exceptions.py b/pylxd/exceptions.py
index e910cbc..9e243dd 100644
--- a/pylxd/exceptions.py
+++ b/pylxd/exceptions.py
@@ -4,7 +4,9 @@ class ClientConnectionFailed(Exception):
class ClientAuthenticationFailed(Exception):
"""The LXD client's certificates are not trusted."""
- message = "LXD client certificates are not trusted."""
+
+ def __str__(self):
+ return "LXD client certificates are not trusted."""
class _LXDAPIException(Exception):
@@ -18,7 +20,9 @@ class _LXDAPIException(Exception):
def __init__(self, data):
self.data = data
- self.message = self.data.get('error')
+
+ def __str__(self):
+ return self.data.get('error')
class NotFound(_LXDAPIException):
diff --git a/pylxd/tests/mock_lxd.py b/pylxd/tests/mock_lxd.py
index 5e2aea4..5150503 100644
--- a/pylxd/tests/mock_lxd.py
+++ b/pylxd/tests/mock_lxd.py
@@ -46,6 +46,7 @@ def profile_GET(request, context):
'url': r'^http://pylxd.test/1.0$',
},
+
# Containers
{
'text': json.dumps({'metadata': [
@@ -76,6 +77,24 @@ def profile_GET(request, context):
'url': r'^http://pylxd.test/1.0/containers/an-container/state$', # NOQA
},
{
+ 'text': json.dumps({'operation': 'operation-abc'}),
+ 'method': 'POST',
+ 'url': r'^http://pylxd.test/1.0/containers/an-container$',
+ },
+ {
+ 'text': json.dumps({'operation': 'operation-abc'}),
+ 'method': 'PUT',
+ 'url': r'^http://pylxd.test/1.0/containers/an-container$',
+ },
+ {
+ 'text': container_DELETE,
+ 'method': 'DELETE',
+ 'url': r'^http://pylxd.test/1.0/containers/an-container$',
+ },
+
+
+ # Container Snapshots
+ {
'text': json.dumps({'metadata': [
'/1.0/containers/an_container/snapshots/an-snapshot',
]}),
@@ -105,22 +124,21 @@ def profile_GET(request, context):
'method': 'DELETE',
'url': r'^http://pylxd.test/1.0/containers/an-container/snapshots/an-snapshot$', # NOQA
},
+
+
+ # Container files
{
- 'text': json.dumps({'operation': 'operation-abc'}),
- 'method': 'POST',
- 'url': r'^http://pylxd.test/1.0/containers/an-container$',
- },
- {
- 'text': json.dumps({'operation': 'operation-abc'}),
- 'method': 'PUT',
- 'url': r'^http://pylxd.test/1.0/containers/an-container$',
+ 'text': 'This is a getted file',
+ 'method': 'GET',
+ 'url': r'^http://pylxd.test/1.0/containers/an-container/files\?path=%2Ftmp%2Fgetted$', # NOQA
},
{
- 'text': container_DELETE,
- 'method': 'DELETE',
- 'url': r'^http://pylxd.test/1.0/containers/an-container$',
+ 'method': 'POST',
+ 'url': r'^http://pylxd.test/1.0/containers/an-container/files\?path=%2Ftmp%2Fputted$', # NOQA
},
+
+
# Images
{
'text': json.dumps({'metadata': [
diff --git a/pylxd/tests/test_container.py b/pylxd/tests/test_container.py
index 785b67b..932a38e 100644
--- a/pylxd/tests/test_container.py
+++ b/pylxd/tests/test_container.py
@@ -232,3 +232,39 @@ def not_found(request, context):
name='an-snapshot')
self.assertRaises(RuntimeError, snapshot.delete)
+
+
+class TestFiles(testing.PyLXDTestCase):
+ """Tests for pylxd.container.Container.files."""
+
+ def setUp(self):
+ super(TestFiles, self).setUp()
+ self.container = container.Container.get(self.client, 'an-container')
+
+ def test_put(self):
+ """A file is put on the container."""
+ data = 'The quick brown fox'
+
+ self.container.files.put('/tmp/putted', data)
+
+ # TODO: Add an assertion here
+
+ def test_get(self):
+ """A file is retrieved from the container."""
+ data = self.container.files.get('/tmp/getted')
+
+ self.assertEqual(b'This is a getted file', data)
+
+ def test_get_not_found(self):
+ """NotFound is raised on bogus filenames."""
+ def not_found(request, context):
+ context.status_code = 500
+ rule = {
+ 'text': not_found,
+ 'method': 'GET',
+ 'url': r'^http://pylxd.test/1.0/containers/an-container/files\?path=%2Ftmp%2Fgetted$', # NOQA
+ }
+ self.add_rule(rule)
+
+ self.assertRaises(
+ exceptions.NotFound, self.container.files.get, '/tmp/getted')
More information about the lxc-devel
mailing list