From 868a6303be5cc40bbfe164edd00a545a452489cd Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 06:54:22 +0200 Subject: [PATCH] Allow Proxmox Snapshot Restoring (#4377) (#4571) * Allow restoring of snapshots * Fix formatting * Add documentation for new feature * Revert unrelated reformatting * Add documentation for snapshot change * Remove redundant multiple call to status API * Remove unneccesary indent * Add documentation for timeout fix * Update changelog fragment to reflect real changes Co-authored-by: Felix Fontein * Update changelog fragment to reflect real changes Co-authored-by: Felix Fontein * Add Tests for Snapshot rollback * Update tests/unit/plugins/modules/cloud/misc/test_proxmox_snap.py Co-authored-by: Felix Fontein * Update changelogs/fragments/4377-allow-proxmox-snapshot-restoring.yml Co-authored-by: Felix Fontein * Update plugins/modules/cloud/misc/proxmox_snap.py Co-authored-by: Felix Fontein Co-authored-by: Felix Fontein (cherry picked from commit dbad1e0f1123cbb8694fa6571770176c394fe711) Co-authored-by: Timon Michel --- .../4377-allow-proxmox-snapshot-restoring.yml | 3 + plugins/modules/cloud/misc/proxmox_snap.py | 66 ++++++++++++++++--- .../modules/cloud/misc/test_proxmox_snap.py | 23 +++++++ 3 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 changelogs/fragments/4377-allow-proxmox-snapshot-restoring.yml diff --git a/changelogs/fragments/4377-allow-proxmox-snapshot-restoring.yml b/changelogs/fragments/4377-allow-proxmox-snapshot-restoring.yml new file mode 100644 index 0000000000..8067cc18df --- /dev/null +++ b/changelogs/fragments/4377-allow-proxmox-snapshot-restoring.yml @@ -0,0 +1,3 @@ +minor_changes: + - proxmox_snap - add restore snapshot option (https://github.com/ansible-collections/community.general/pull/4377). + - proxmox_snap - fixed timeout value to correctly reflect time in seconds. The timeout was off by one second (https://github.com/ansible-collections/community.general/pull/4377). \ No newline at end of file diff --git a/plugins/modules/cloud/misc/proxmox_snap.py b/plugins/modules/cloud/misc/proxmox_snap.py index cf570bd151..6802ec78e7 100644 --- a/plugins/modules/cloud/misc/proxmox_snap.py +++ b/plugins/modules/cloud/misc/proxmox_snap.py @@ -13,7 +13,7 @@ module: proxmox_snap short_description: Snapshot management of instances in Proxmox VE cluster version_added: 2.0.0 description: - - Allows you to create/delete snapshots from instances in Proxmox VE cluster. + - Allows you to create/delete/restore snapshots from instances in Proxmox VE cluster. - Supports both KVM and LXC, OpenVZ has not been tested, as it is no longer supported on Proxmox VE. options: hostname: @@ -28,7 +28,8 @@ options: state: description: - Indicate desired state of the instance snapshot. - choices: ['present', 'absent'] + - The C(rollback) value was added in community.general 4.8.0. + choices: ['present', 'absent', 'rollback'] default: present type: str force: @@ -53,7 +54,7 @@ options: type: int snapname: description: - - Name of the snapshot that has to be created. + - Name of the snapshot that has to be created/deleted/restored. default: 'ansible_snap' type: str @@ -84,6 +85,15 @@ EXAMPLES = r''' vmid: 100 state: absent snapname: pre-updates + +- name: Rollback container snapshot + community.general.proxmox_snap: + api_user: root@pam + api_password: 1q2w3e + api_host: node1 + vmid: 100 + state: rollback + snapname: pre-updates ''' RETURN = r'''#''' @@ -109,15 +119,15 @@ class ProxmoxSnapAnsible(ProxmoxAnsible): else: taskid = self.snapshot(vm, vmid).post(snapname=snapname, description=description, vmstate=int(vmstate)) while timeout: - if (self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['status'] == 'stopped' and - self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): + status_data = self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get() + if status_data['status'] == 'stopped' and status_data['exitstatus'] == 'OK': return True - timeout -= 1 if timeout == 0: self.module.fail_json(msg='Reached timeout while waiting for creating VM snapshot. Last line in task before timeout: %s' % self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1]) time.sleep(1) + timeout -= 1 return False def snapshot_remove(self, vm, vmid, timeout, snapname, force): @@ -126,15 +136,32 @@ class ProxmoxSnapAnsible(ProxmoxAnsible): taskid = self.snapshot(vm, vmid).delete(snapname, force=int(force)) while timeout: - if (self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['status'] == 'stopped' and - self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): + status_data = self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get() + if status_data['status'] == 'stopped' and status_data['exitstatus'] == 'OK': return True - timeout -= 1 if timeout == 0: self.module.fail_json(msg='Reached timeout while waiting for removing VM snapshot. Last line in task before timeout: %s' % self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1]) time.sleep(1) + timeout -= 1 + return False + + def snapshot_rollback(self, vm, vmid, timeout, snapname): + if self.module.check_mode: + return True + + taskid = self.snapshot(vm, vmid)(snapname).post("rollback") + while timeout: + status_data = self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get() + if status_data['status'] == 'stopped' and status_data['exitstatus'] == 'OK': + return True + if timeout == 0: + self.module.fail_json(msg='Reached timeout while waiting for rolling back VM snapshot. Last line in task before timeout: %s' % + self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1]) + + time.sleep(1) + timeout -= 1 return False @@ -144,7 +171,7 @@ def main(): vmid=dict(required=False), hostname=dict(), timeout=dict(type='int', default=30), - state=dict(default='present', choices=['present', 'absent']), + state=dict(default='present', choices=['present', 'absent', 'rollback']), description=dict(type='str'), snapname=dict(type='str', default='ansible_snap'), force=dict(type='bool', default='no'), @@ -211,6 +238,25 @@ def main(): except Exception as e: module.fail_json(msg="Removing snapshot %s of VM %s failed with exception: %s" % (snapname, vmid, to_native(e))) + elif state == 'rollback': + try: + snap_exist = False + + for i in proxmox.snapshot(vm, vmid).get(): + if i['name'] == snapname: + snap_exist = True + continue + + if not snap_exist: + module.exit_json(changed=False, msg="Snapshot %s does not exist" % snapname) + if proxmox.snapshot_rollback(vm, vmid, timeout, snapname): + if module.check_mode: + module.exit_json(changed=True, msg="Snapshot %s would be rolled back" % snapname) + else: + module.exit_json(changed=True, msg="Snapshot %s rolled back" % snapname) + + except Exception as e: + module.fail_json(msg="Rollback of snapshot %s of VM %s failed with exception: %s" % (snapname, vmid, to_native(e))) if __name__ == '__main__': diff --git a/tests/unit/plugins/modules/cloud/misc/test_proxmox_snap.py b/tests/unit/plugins/modules/cloud/misc/test_proxmox_snap.py index efd6e4ecb6..1ee14d81ef 100644 --- a/tests/unit/plugins/modules/cloud/misc/test_proxmox_snap.py +++ b/tests/unit/plugins/modules/cloud/misc/test_proxmox_snap.py @@ -91,3 +91,26 @@ def test_remove_snapshot_check_mode(connect_mock, capfd, mocker): out, err = capfd.readouterr() assert not err assert not json.loads(out)['changed'] + + +@patch('ansible_collections.community.general.plugins.module_utils.proxmox.ProxmoxAnsible._connect') +def test_rollback_snapshot_check_mode(connect_mock, capfd, mocker): + set_module_args({"hostname": "test-lxc", + "api_user": "root@pam", + "api_password": "secret", + "api_host": "127.0.0.1", + "state": "rollback", + "snapname": "test", + "timeout": "1", + "force": True, + "_ansible_check_mode": True}) + proxmox_utils.HAS_PROXMOXER = True + connect_mock.side_effect = lambda: fake_api(mocker) + with pytest.raises(SystemExit) as results: + proxmox_snap.main() + + out, err = capfd.readouterr() + assert not err + output = json.loads(out) + assert not output['changed'] + assert output['msg'] == "Snapshot test does not exist"