[lxc-devel] [pylxd/master] Add support for container and snapshot publish.
rockstar on Github
lxc-bot at linuxcontainers.org
Wed Jul 13 03:30:33 UTC 2016
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 434 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160713/9afc1932/attachment.bin>
-------------- next part --------------
From 592646263f05d1a2bfe70ced05eae08d4e74930e Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Tue, 12 Jul 2016 21:29:37 -0600
Subject: [PATCH] Add support for container and snapshot publish.
Snapshot publish is broken in LXD, but this is a guess for how
it is supposed to work, based on how it works in container
publish.
---
pylxd/container.py | 49 +++++++++++++++++++++++++++++++++++++++++
pylxd/exceptions.py | 3 +++
pylxd/operation.py | 15 +++++++++----
pylxd/tests/test_container.py | 51 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 114 insertions(+), 4 deletions(-)
diff --git a/pylxd/container.py b/pylxd/container.py
index 3f49b32..7495572 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -289,6 +289,30 @@ def migrate(self, new_client, wait=False):
}
return new_client.containers.create(config, wait=wait)
+ def publish(self, public=False, wait=False):
+ """Publish a container as an image.
+
+ The container must be stopped in order publish it as an image. This
+ method does not enforce that constraint, so a LXDAPIException may be
+ raised if this method is called on a running container.
+
+ If wait=True, an Image is returned.
+ """
+ data = {
+ 'public': public,
+ 'source': {
+ 'type': 'container',
+ 'name': self.name,
+ }
+ }
+
+ response = self.client.api.images.post(json=data)
+ if wait:
+ operation = Operation.wait_for_operation(
+ self.client, response.json()['operation'])
+
+ return self.client.images.get(operation.metadata['fingerprint'])
+
class _CommandWebsocketClient(WebSocketBaseClient): # pragma: no cover
def __init__(self, manager, *args, **kwargs):
@@ -374,3 +398,28 @@ def rename(self, new_name, wait=False):
Operation.wait_for_operation(
self.client, response.json()['operation'])
self.name = new_name
+
+ def publish(self, public=False, wait=False):
+ """Publish a snapshot as an image.
+
+ If wait=True, an Image is returned.
+
+ This functionality is currently broken in LXD. Please see
+ https://github.com/lxc/lxd/issues/2201 - The implementation
+ here is mostly a guess. Once that bug is fixed, we can verify
+ that this works, or file a bug to fix it appropriately.
+ """
+ data = {
+ 'public': public,
+ 'source': {
+ 'type': 'snapshot',
+ 'name': '{}/{}'.format(self.container.name, self.name),
+ 'alias': 'lololol',
+ }
+ }
+
+ response = self.client.api.images.post(json=data)
+ if wait:
+ operation = Operation.wait_for_operation(
+ self.client, response.json()['operation'])
+ return self.client.images.get(operation.metadata['fingerprint'])
diff --git a/pylxd/exceptions.py b/pylxd/exceptions.py
index 36d8086..d5b6b5e 100644
--- a/pylxd/exceptions.py
+++ b/pylxd/exceptions.py
@@ -15,6 +15,9 @@ def __init__(self, response):
self.response = response
def __str__(self):
+ if self.response.status_code == 200: # Operation failure
+ return self.response.json()['metadata']['err']
+
try:
data = self.response.json()
return data['error']
diff --git a/pylxd/operation.py b/pylxd/operation.py
index 09c9d24..1594680 100644
--- a/pylxd/operation.py
+++ b/pylxd/operation.py
@@ -11,7 +11,7 @@
# 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 six
+from pylxd import exceptions
class Operation(object):
@@ -27,7 +27,7 @@ def wait_for_operation(cls, client, operation_id):
"""Get an operation and wait for it to complete."""
operation = cls.get(client, operation_id)
operation.wait()
- return operation
+ return cls.get(client, operation.id)
@classmethod
def get(cls, client, operation_id):
@@ -39,9 +39,16 @@ def get(cls, client, operation_id):
def __init__(self, **kwargs):
super(Operation, self).__init__()
- for key, value in six.iteritems(kwargs):
+ for key, value in kwargs.items():
setattr(self, key, value)
def wait(self):
"""Wait for the operation to complete and return."""
- self._client.api.operations[self.id].wait.get()
+ response = self._client.api.operations[self.id].wait.get()
+
+ try:
+ if response.json()['metadata']['status'] == 'Failure':
+ raise exceptions.LXDAPIException(response)
+ except KeyError:
+ # Support for legacy LXD
+ pass
diff --git a/pylxd/tests/test_container.py b/pylxd/tests/test_container.py
index d483cba..0178cb7 100644
--- a/pylxd/tests/test_container.py
+++ b/pylxd/tests/test_container.py
@@ -191,6 +191,31 @@ def test_migrate(self):
self.assertEqual('an-container', an_migrated_container.name)
self.assertEqual(client2, an_migrated_container.client)
+ def test_publish(self):
+ """Containers can be published."""
+ self.add_rule({
+ 'text': json.dumps({
+ 'type': 'sync',
+ 'metadata': {
+ 'id': 'operation-abc',
+ 'metadata': {
+ 'fingerprint': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' # NOQA
+ }
+ }
+ }),
+ 'method': 'GET',
+ 'url': r'^http://pylxd.test/1.0/operations/operation-abc$',
+ })
+
+ an_container = container.Container(
+ self.client, name='an-container')
+
+ image = an_container.publish(wait=True)
+
+ self.assertEqual(
+ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
+ image.fingerprint)
+
class TestContainerState(testing.PyLXDTestCase):
"""Tests for pylxd.container.ContainerState."""
@@ -313,6 +338,32 @@ def not_found(request, context):
self.assertRaises(exceptions.LXDAPIException, snapshot.delete)
+ def test_publish(self):
+ """Snapshots can be published."""
+ self.add_rule({
+ 'text': json.dumps({
+ 'type': 'sync',
+ 'metadata': {
+ 'id': 'operation-abc',
+ 'metadata': {
+ 'fingerprint': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' # NOQA
+ }
+ }
+ }),
+ 'method': 'GET',
+ 'url': r'^http://pylxd.test/1.0/operations/operation-abc$',
+ })
+
+ snapshot = container.Snapshot(
+ self.client, container=self.container,
+ name='an-snapshot')
+
+ image = snapshot.publish(wait=True)
+
+ self.assertEqual(
+ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
+ image.fingerprint)
+
class TestFiles(testing.PyLXDTestCase):
"""Tests for pylxd.container.Container.files."""
More information about the lxc-devel
mailing list