From 5d0e7931d1d6bb24edd475caaff9602d74559768 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Mon, 13 May 2024 15:22:03 -0400 Subject: [PATCH] Add support for rolling back snapshots We supported creating snapshots, but not doing anything with them. This removes the manual task of restoring a snapshot and replace it with a PVC abstraction of rolling back to a snapshot. While Ceph recommends cloning a snapshot instead of rolling back, due to the time taken, in our usecase I don't think that is an optimal strategy, as it will leave dangling clones that we'd then have to manage. Closes #183 --- api-daemon/pvcapid/flaskapi.py | 53 ++++++++++++++++++++++++++++++++++ api-daemon/pvcapid/helper.py | 16 ++++++++++ client-cli/pvc/cli/cli.py | 27 +++++++++++++++++ client-cli/pvc/lib/storage.py | 24 +++++++++++++++ daemon-common/ceph.py | 30 +++++++++++++++++++ 5 files changed, 150 insertions(+) diff --git a/api-daemon/pvcapid/flaskapi.py b/api-daemon/pvcapid/flaskapi.py index 0d01f59c..0b1e1665 100755 --- a/api-daemon/pvcapid/flaskapi.py +++ b/api-daemon/pvcapid/flaskapi.py @@ -6362,6 +6362,59 @@ api.add_resource( ) +# /storage/ceph/snapshot////rollback +class API_Storage_Ceph_Snapshot_Rollback_Element(Resource): + @Authenticator + def post(self, pool, volume, snapshot): + """ + Roll back an RBD volume {volume} in pool {pool} to snapshot {snapshot} + + WARNING: This action cannot be done on an active RBD volume. All IO MUST be stopped first. + --- + tags: + - storage / ceph + parameters: + - in: query + name: snapshot + type: string + required: true + description: The name of the snapshot + - in: query + name: volume + type: string + required: true + description: The name of the volume + - in: query + name: pool + type: integer + required: true + description: The name of the pool + responses: + 200: + description: OK + schema: + type: object + id: Message + 404: + description: Not found + schema: + type: object + id: Message + 400: + description: Bad request + schema: + type: object + id: Message + """ + return api_helper.ceph_volume_snapshot_rollback(pool, volume, snapshot) + + +api.add_resource( + API_Storage_Ceph_Snapshot_Rollback_Element, + "/storage/ceph/snapshot////rollback", +) + + ########################################################## # Provisioner API ########################################################## diff --git a/api-daemon/pvcapid/helper.py b/api-daemon/pvcapid/helper.py index 97bd1829..aac500e6 100755 --- a/api-daemon/pvcapid/helper.py +++ b/api-daemon/pvcapid/helper.py @@ -2183,6 +2183,22 @@ def ceph_volume_snapshot_rename(zkhandler, pool, volume, name, new_name): return output, retcode +@ZKConnection(config) +def ceph_volume_snapshot_rollback(zkhandler, pool, volume, name): + """ + Roll back a Ceph RBD volume to a given snapshot in the PVC Ceph storage cluster. + """ + retflag, retdata = pvc_ceph.rollback_snapshot(zkhandler, pool, volume, name) + + if retflag: + retcode = 200 + else: + retcode = 400 + + output = {"message": retdata.replace('"', "'")} + return output, retcode + + @ZKConnection(config) def ceph_volume_snapshot_remove(zkhandler, pool, volume, name): """ diff --git a/client-cli/pvc/cli/cli.py b/client-cli/pvc/cli/cli.py index d26e044c..4eac7587 100644 --- a/client-cli/pvc/cli/cli.py +++ b/client-cli/pvc/cli/cli.py @@ -4372,6 +4372,32 @@ def cli_storage_volume_snapshot_remove(pool, volume, name): finish(retcode, retmsg) +############################################################################### +# > pvc storage volume snapshot rollback +############################################################################### +@click.command(name="rollback", short_help="Roll back RBD volume to snapshot.") +@connection_req +@click.argument("pool") +@click.argument("volume") +@click.argument("name") +@confirm_opt("Roll back to snapshot {name} for volume {pool}/{volume}") +def cli_storage_volume_snapshot_rollback(pool, volume, name): + """ + Roll back the Ceph RBD volume VOLUME in pool POOL to the snapshot NAME. + + DANGER: All data written to the volume since the given snapshot will be permanently lost. + + WARNING: A rollback cannot be performed on an RBD volume with active I/O. Doing so will cause + undefined behaviour and possible corruption. Ensure that any VM(s) using this RBD volume are + stopped or disabled before attempting a snapshot rollback. + """ + + retcode, retmsg = pvc.lib.storage.ceph_snapshot_rollback( + CLI_CONFIG, pool, volume, name + ) + finish(retcode, retmsg) + + ############################################################################### # > pvc storage volume snapshot list ############################################################################### @@ -6349,6 +6375,7 @@ cli_storage_volume.add_command(cli_storage_volume_list) cli_storage_volume_snapshot.add_command(cli_storage_volume_snapshot_add) cli_storage_volume_snapshot.add_command(cli_storage_volume_snapshot_rename) cli_storage_volume_snapshot.add_command(cli_storage_volume_snapshot_remove) +cli_storage_volume_snapshot.add_command(cli_storage_volume_snapshot_rollback) cli_storage_volume_snapshot.add_command(cli_storage_volume_snapshot_list) cli_storage_volume.add_command(cli_storage_volume_snapshot) cli_storage.add_command(cli_storage_volume) diff --git a/client-cli/pvc/lib/storage.py b/client-cli/pvc/lib/storage.py index bd50f4ee..7f3bcb95 100644 --- a/client-cli/pvc/lib/storage.py +++ b/client-cli/pvc/lib/storage.py @@ -1544,6 +1544,30 @@ def ceph_snapshot_add(config, pool, volume, snapshot): return retstatus, response.json().get("message", "") +def ceph_snapshot_rollback(config, pool, volume, snapshot): + """ + Roll back Ceph volume to snapshot + + API endpoint: POST /api/v1/storage/ceph/snapshot/{pool}/{volume}/{snapshot}/rollback + API arguments: + API schema: {"message":"{data}"} + """ + response = call_api( + config, + "post", + "/storage/ceph/snapshot/{pool}/{volume}/{snapshot}/rollback".format( + snapshot=snapshot, volume=volume, pool=pool + ), + ) + + if response.status_code == 200: + retstatus = True + else: + retstatus = False + + return retstatus, response.json().get("message", "") + + def ceph_snapshot_remove(config, pool, volume, snapshot): """ Remove Ceph snapshot diff --git a/daemon-common/ceph.py b/daemon-common/ceph.py index ab7e8db6..b6289d68 100644 --- a/daemon-common/ceph.py +++ b/daemon-common/ceph.py @@ -1082,6 +1082,36 @@ def rename_snapshot(zkhandler, pool, volume, name, new_name): ) +def rollback_snapshot(zkhandler, pool, volume, name): + if not verifyVolume(zkhandler, pool, volume): + return False, 'ERROR: No volume with name "{}" is present in pool "{}".'.format( + volume, pool + ) + if not verifySnapshot(zkhandler, pool, volume, name): + return ( + False, + 'ERROR: No snapshot with name "{}" is present for volume "{}" in pool "{}".'.format( + name, volume, pool + ), + ) + + # 1. Roll back the snapshot + retcode, stdout, stderr = common.run_os_command( + "rbd snap rollback {}/{}@{}".format(pool, volume, name) + ) + if retcode: + return ( + False, + 'ERROR: Failed to roll back RBD volume "{}" in pool "{}" to snapshot "{}": {}'.format( + volume, pool, name, stderr + ), + ) + + return True, 'Rolled back RBD volume "{}" in pool "{}" to snapshot "{}".'.format( + volume, pool, name + ) + + def remove_snapshot(zkhandler, pool, volume, name): if not verifyVolume(zkhandler, pool, volume): return False, 'ERROR: No volume with name "{}" is present in pool "{}".'.format(