[lxc-devel] [pylxd/master] Image export

rockstar on Github lxc-bot at linuxcontainers.org
Wed Sep 28 23:11:09 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 653 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160928/dc4e9cd2/attachment.bin>
-------------- next part --------------
From 47689a1dd77f1842f47d09ba7b10e6761fbdd031 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Wed, 28 Sep 2016 14:35:49 -0600
Subject: [PATCH 1/4] Update the apt cache in the integration test run before
 installing packages

---
 run_integration_tests | 1 +
 1 file changed, 1 insertion(+)

diff --git a/run_integration_tests b/run_integration_tests
index 54380e5..cdba3ee 100755
--- a/run_integration_tests
+++ b/run_integration_tests
@@ -8,6 +8,7 @@ CONTAINER_NAME=pylxd-`uuidgen | cut -d"-" -f1`
 # a bug in LXD).
 lxc launch $CONTAINER_IMAGE $CONTAINER_NAME -c security.nesting=true -c security.privileged=true
 sleep 5  # Wait for the network to come up
+lxc exec $CONTAINER_NAME -- apt-get update
 lxc exec $CONTAINER_NAME -- apt-get install -y tox python3-dev libssl-dev libffi-dev build-essential
 
 lxc exec $CONTAINER_NAME -- mkdir -p /opt/pylxd

From a66b49f114d6bf3c14b6f0392e8b56a176a6dc96 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Wed, 28 Sep 2016 14:45:10 -0600
Subject: [PATCH 2/4] Return a file-like object from Image.export

The buffered image is written to a temporary file on disk. It also
required a little bit of massaging on the response assertion code
to not try and read response content when the response is streamed.

Fixes #183
---
 integration/test_images.py |  4 ++--
 pylxd/client.py            |  6 +++++-
 pylxd/models/image.py      | 25 +++++++++++++++++--------
 3 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/integration/test_images.py b/integration/test_images.py
index 9882fac..f1a7473 100644
--- a/integration/test_images.py
+++ b/integration/test_images.py
@@ -85,8 +85,8 @@ def test_delete(self):
             self.client.images.get, self.image.fingerprint)
 
     def test_export(self):
-        """The imerage is successfully exported."""
-        data = self.image.export()
+        """The image is successfully exported."""
+        data = self.image.export().read()
         data_sha = hashlib.sha256(data).hexdigest()
 
         self.assertEqual(self.image.fingerprint, data_sha)
diff --git a/pylxd/client.py b/pylxd/client.py
index 4930f76..c25848e 100644
--- a/pylxd/client.py
+++ b/pylxd/client.py
@@ -91,7 +91,11 @@ def netloc(self):
     def get(self, *args, **kwargs):
         """Perform an HTTP GET."""
         response = self.session.get(self._api_endpoint, *args, **kwargs)
-        self._assert_response(response)
+
+        # In the case of streaming, we can't validate the json the way we
+        # would with normal HTTP responses, so just ignore that entirely.
+        if not kwargs.get('stream', False):
+            self._assert_response(response)
         return response
 
     def post(self, *args, **kwargs):
diff --git a/pylxd/models/image.py b/pylxd/models/image.py
index fbf2e43..2293dd9 100644
--- a/pylxd/models/image.py
+++ b/pylxd/models/image.py
@@ -11,7 +11,9 @@
 #    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 contextlib
 import hashlib
+import tempfile
 
 from pylxd.models import _model as model
 from pylxd.models.operation import Operation
@@ -97,8 +99,7 @@ def create(cls, client, image_data, public=False, wait=False):
     @classmethod
     def create_from_simplestreams(cls, client, server, alias,
                                   public=False, auto_update=False):
-        """ Copy an image from simplestreams.
-        """
+        """Copy an image from simplestreams."""
         config = {
             'public': public,
             'auto_update': auto_update,
@@ -119,8 +120,7 @@ def create_from_simplestreams(cls, client, server, alias,
     @classmethod
     def create_from_url(cls, client, url,
                         public=False, auto_update=False):
-        """ Copy an image from an url.
-        """
+        """Copy an image from an url."""
         config = {
             'public': public,
             'auto_update': auto_update,
@@ -137,9 +137,18 @@ def create_from_url(cls, client, url,
         return client.images.get(op.metadata['fingerprint'])
 
     def export(self):
-        """Export the image."""
-        response = self.api.export.get()
-        return response.content
+        """Export the image.
+
+        Because the image itself may be quite large, we stream the download
+        in 1kb chunks, and write it to a temporary file on disk. Once that
+        file is closed, it is deleted from the disk.
+        """
+        on_disk = tempfile.TemporaryFile()
+        with contextlib.closing(self.api.export.get(stream=True)) as response:
+            for chunk in response.iter_content(chunk_size=1024):
+                on_disk.write(chunk)
+        on_disk.seek(0)
+        return on_disk
 
     def add_alias(self, name, description):
         """Add an alias to the image."""
@@ -168,7 +177,7 @@ def delete_alias(self, name):
             pass
 
     def copy(self, new_client, public=None, auto_update=None, wait=False):
-        """ Copy an image to a another LXD.
+        """Copy an image to a another LXD.
 
         Destination host information is contained in the client
         connection passed in.

From 5c638fc10d57323a43473a982281eca550de8ba2 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Wed, 28 Sep 2016 17:06:21 -0600
Subject: [PATCH 3/4] Add documentation for the new Image.export API.

---
 doc/source/images.rst | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/doc/source/images.rst b/doc/source/images.rst
index 88f915e..982aa1d 100644
--- a/doc/source/images.rst
+++ b/doc/source/images.rst
@@ -53,8 +53,10 @@ the `LXD documentation`_.
 Image methods
 -------------
 
-  - `export` - Export the image. Returns binary data that is the
-    image itself.
+  - `export` - Export the image. Returns a file object with the contents
+    of the image. *Note: Prior to pylxd 2.1.1, this method returned a
+    bytestring with data; as it was not unbuffered, the API was severely
+    limited.*
 
   - `add_alias` - Add an alias to the image.
 

From e7a5721c023d080609458a592b0f4b70fb418f8b Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Wed, 28 Sep 2016 17:06:45 -0600
Subject: [PATCH 4/4] Add integration tests for container.publish

---
 integration/test_containers.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/integration/test_containers.py b/integration/test_containers.py
index 830ff7b..88589df 100644
--- a/integration/test_containers.py
+++ b/integration/test_containers.py
@@ -152,3 +152,11 @@ def test_execute(self):
         stdout, stderr = self.container.execute(['echo', 'test'])
 
         self.assertEqual('test\n', stdout)
+
+    def test_publish(self):
+        """A container is published."""
+        image = self.container.publish(wait=True)
+
+        self.assertIn(
+            image.fingerprint,
+            [i.fingerprint for i in self.client.images.all()])


More information about the lxc-devel mailing list