From c69e4ebd84581f77546fbd63450d1ec53cf6aa81 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Wed, 1 May 2019 15:44:04 +0100 Subject: [PATCH 1/2] Add restore snapshot methods This patchset adds restore methods to the Container and Snapshot classes to make it possible to restore a snapshot without having to use the raw_api in pylxd. Closes: #353 Signed-off-by: Alex Kavanagh --- pylxd/models/container.py | 38 ++++++++++++++++++++++++++++ pylxd/tests/mock_lxd.py | 17 +++++++++++-- pylxd/tests/models/test_container.py | 13 ++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/pylxd/models/container.py b/pylxd/models/container.py index d6dc4694..9b6e5659 100644 --- a/pylxd/models/container.py +++ b/pylxd/models/container.py @@ -558,6 +558,28 @@ def publish(self, public=False, wait=False): return self.client.images.get(operation.metadata['fingerprint']) + def restore_snapshot(self, snapshot_name, wait=False): + """Restore a snapshot using its name. + + Attempts to restore a container using a snapshot previously made. The + container should be stopped, but the method does not enforce this + constraint, so an LXDAPIException may be raised if this method fails. + + :param snapshot_name: the name of the snapshot to restore from + :type snapshot_name: str + :param wait: wait until the operation is completed. + :type wait: boolean + :raises: LXDAPIException if the the operation fails. + :returns: the original response from the restore operation (not the + operation result) + :rtype: :class:`requests.Response` + """ + response = self.api.put(json={"restore": snapshot_name}) + if wait: + self.client.operations.wait_for_operation( + response.json()['operation']) + return response + class _CommandWebsocketClient(WebSocketBaseClient): # pragma: no cover """Handle a websocket for container.execute(...) and manage decoding of the @@ -710,3 +732,19 @@ def publish(self, public=False, wait=False): operation = self.client.operations.wait_for_operation( response.json()['operation']) return self.client.images.get(operation.metadata['fingerprint']) + + def restore(self, wait=False): + """Restore this snapshot. + + Attempts to restore a container using this snapshot. The container + should be stopped, but the method does not enforce this constraint, so + an LXDAPIException may be raised if this method fails. + + :param wait: wait until the operation is completed. + :type wait: boolean + :raises: LXDAPIException if the the operation fails. + :returns: the original response from the restore operation (not the + operation result) + :rtype: :class:`requests.Response` + """ + return self.container.restore_snapshot(self.name, wait) diff --git a/pylxd/tests/mock_lxd.py b/pylxd/tests/mock_lxd.py index 7bd2e991..5d44550e 100644 --- a/pylxd/tests/mock_lxd.py +++ b/pylxd/tests/mock_lxd.py @@ -28,6 +28,14 @@ def container_POST(request, context): } +def container_PUT(request, context): + context.status_code = 202 + return { + 'type': 'async', + 'operation': '/1.0/operations/operation-abc?project=default' + } + + def container_DELETE(request, context): context.status_code = 202 return json.dumps({ @@ -260,7 +268,7 @@ def snapshot_DELETE(request, context): 'method': 'GET', 'url': r'^http://pylxd.test/1.0/containers$', }, -{ + { 'text': json.dumps({ 'type': 'sync', 'metadata': [ @@ -332,7 +340,7 @@ def snapshot_DELETE(request, context): 'method': 'GET', 'url': r'^http://pylxd2.test/1.0/containers/an-container$', }, -{ + { 'json': { 'type': 'sync', 'metadata': { @@ -480,6 +488,11 @@ def snapshot_DELETE(request, context): 'method': 'POST', 'url': r'^http://pylxd.test/1.0/containers/an-container/exec$', # NOQA }, + { + 'json': container_PUT, + 'method': 'PUT', + 'url': r'^http://pylxd.test/1.0/containers/an-container$', + }, # Container Snapshots { diff --git a/pylxd/tests/models/test_container.py b/pylxd/tests/models/test_container.py index 795bebdc..1bcd527c 100644 --- a/pylxd/tests/models/test_container.py +++ b/pylxd/tests/models/test_container.py @@ -390,6 +390,12 @@ def test_publish(self): 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', image.fingerprint) + def test_restore_snapshot(self): + """Snapshots can be restored""" + an_container = models.Container( + self.client, name='an-container') + an_container.restore_snapshot('thing') + class TestContainerState(testing.PyLXDTestCase): """Tests for pylxd.models.ContainerState.""" @@ -540,6 +546,13 @@ def test_publish(self): 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', image.fingerprint) + def test_restore_snapshot(self): + """Snapshots can be restored from the snapshot object""" + snapshot = models.Snapshot( + self.client, container=self.container, + name='an-snapshot') + snapshot.restore(wait=True) + class TestFiles(testing.PyLXDTestCase): """Tests for pylxd.models.Container.files.""" From 40dc4dd41c8060dd3d89ea3965827390da4fd279 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Thu, 2 May 2019 14:59:01 +0100 Subject: [PATCH 2/2] Add methods to the docs Signed-off-by: Alex Kavanagh --- doc/source/containers.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/containers.rst b/doc/source/containers.rst index 48d5ff92..44b6e45a 100644 --- a/doc/source/containers.rst +++ b/doc/source/containers.rst @@ -71,6 +71,7 @@ Container methods `wait=True` is optional. The container on the new client is returned. - `publish` - Publish the container as an image. Note the container must be stopped in order to use this method. If `wait=True` is passed, then the image is returned. + - `restore_snapshot` - Restore a snapshot by name. Examples @@ -196,6 +197,7 @@ A container object (returned by `get` or `all`) has the following methods: image from the snapshot is bigger than the logical volume that is allocated by lxc. See https://github.com/lxc/lxd/issues/2201 for more details. The solution is to increase the `storage.lvm_volume_size` parameter in lxc. + - `restore` - restore the container to this snapshot. .. code-block:: python