The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/pylxd/pull/157
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === 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.
From 592646263f05d1a2bfe70ced05eae08d4e74930e Mon Sep 17 00:00:00 2001 From: Paul Hummer <paul.hum...@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."""
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel