diff --git a/ci/roles/volume/tasks/main.yml b/ci/roles/volume/tasks/main.yml index fb62cad0..cae067a4 100644 --- a/ci/roles/volume/tasks/main.yml +++ b/ci/roles/volume/tasks/main.yml @@ -8,8 +8,24 @@ display_description: Test volume register: vol +- name: Create volume backup + openstack.cloud.volume_backup: + cloud: "{{ cloud }}" + state: present + display_name: ansible_volume_backup + volume: ansible_volume + register: vol_backup + - debug: var=vol +- debug: var=vol_backup + +- name: Delete volume backup + openstack.cloud.volume_backup: + cloud: "{{ cloud }}" + display_name: ansible_volume_backup + state: absent + - name: Delete volume openstack.cloud.volume: cloud: "{{ cloud }}" diff --git a/meta/runtime.yml b/meta/runtime.yml index 69584b2f..2ebe1a01 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -88,6 +88,7 @@ action_groups: - subnet - subnets_info - volume + - volume_backup - volume_info - volume_snapshot os: diff --git a/plugins/modules/volume_backup.py b/plugins/modules/volume_backup.py new file mode 100644 index 00000000..eb93a945 --- /dev/null +++ b/plugins/modules/volume_backup.py @@ -0,0 +1,219 @@ +#!/usr/bin/python +# coding: utf-8 -*- +# +# Copyright (c) 2020 by Open Telekom Cloud, operated by T-Systems International GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +DOCUMENTATION = ''' +--- +module: volume_backup +short_description: Add/Delete Volume backup +extends_documentation_fragment: openstack.cloud.openstack +author: OpenStack Ansible SIG +description: + - Add or Remove Volume Backup in OTC. +options: + display_name: + description: + - Name that has to be given to the backup + required: true + type: str + aliases: ['name'] + display_description: + description: + - String describing the backup + required: false + type: str + aliases: ['description'] + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + type: str + volume: + description: + - Name or ID of the volume. Required when state is True. + type: str + required: False + snapshot: + description: Name or ID of the Snapshot to take backup of + type: str + force: + description: + - Indicates whether to backup, even if the volume is attached. + type: bool + default: False + metadata: + description: Metadata for the backup + type: dict + incremental: + description: The backup mode + type: bool + default: False +requirements: ["openstacksdk"] +''' + +RETURN = ''' +id: + description: The Volume backup ID. + returned: On success when C(state=present) + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" +backup: + description: Dictionary describing the Cluster. + returned: On success when C(state=present) + type: complex + contains: + id: + description: Unique UUID. + type: str + sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" + name: + description: Name given to the load balancer. + type: str + sample: "elb_test" +''' + +EXAMPLES = ''' +- name: Create backup + openstack.cloud.volume_backup: + display_name: test_volume_backup + volume: "test_volume" + +- name: Create backup from snapshot + openstack.cloud.volume_backup: + display_name: test_volume_backup + volume: "test_volume" + snapshot: "test_snapshot" + +- name: Delete volume backup + openstack.cloud.volume_backup: + display_name: test_volume_backup + state: absent +''' + +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule + + +class VolumeBackupModule(OpenStackModule): + argument_spec = dict( + display_name=dict(required=True, aliases=['name'], type='str'), + display_description=dict(required=False, aliases=['description'], + type='str'), + volume=dict(required=False, type='str'), + snapshot=dict(required=False, type='str'), + state=dict(default='present', type='str', choices=['absent', 'present']), + force=dict(default=False, type='bool'), + metadata=dict(required=False, type='dict'), + incremental=dict(required=False, default=False, type='bool') + ) + module_kwargs = dict( + required_if=[ + ('state', 'present', ['volume']) + ], + supports_check_mode=True + ) + + def _create_backup(self): + if self.ansible.check_mode: + self.exit_json(changed=True) + + name = self.params['display_name'] + description = self.params['display_description'] + volume = self.params['volume'] + snapshot = self.params['snapshot'] + force = self.params['force'] + is_incremental = self.params['incremental'] + metadata = self.params['metadata'] + + changed = False + + cloud_volume = self.conn.block_storage.find_volume(volume) + cloud_snapshot_id = None + + attrs = { + 'name': name, + 'volume_id': cloud_volume.id, + 'force': force, + 'is_incremental': is_incremental + } + + if snapshot: + cloud_snapshot_id = self.conn.block_storage.find_snapshot( + snapshot, ignore_missing=False).id + attrs['snapshot_id'] = cloud_snapshot_id + + if metadata: + attrs['metadata'] = metadata + + if description: + attrs['description'] = description + + backup = self.conn.block_storage.create_backup(**attrs) + changed = True + + if self.params['wait']: + try: + backup = self.conn.block_storage.wait_for_status( + backup, + status='available', + wait=self.params['timeout']) + self.exit_json( + changed=True, volume_backup=backup.to_dict(), id=backup.id + ) + except self.sdk.exceptions.ResourceTimeout: + self.fail_json( + msg='Timeout failure waiting for backup ' + 'to complete' + ) + + self.exit_json( + changed=changed, volume_backup=backup.to_dict(), id=backup.id + ) + + def _delete_backup(self, backup): + if self.ansible.check_mode: + self.exit_json(changed=True) + + if backup: + self.conn.block_storage.delete_backup(backup) + if self.params['wait']: + try: + self.conn.block_storage.wait_for_delete( + backup, + interval=2, + wait=self.params['timeout']) + except self.sdk.exceptions.ResourceTimeout: + self.fail_json( + msg='Timeout failure waiting for backup ' + 'to be deleted' + ) + + self.exit_json(changed=True) + + def run(self): + name = self.params['display_name'] + + backup = self.conn.block_storage.find_backup(name) + + if self.params['state'] == 'present': + if not backup: + self._create_backup() + else: + # For the moment we do not support backup update, since SDK + # doesn't support it either => do nothing + self.exit_json(changed=False) + + elif self.params['state'] == 'absent': + self._delete_backup(backup) + + +def main(): + module = VolumeBackupModule() + module() + + +if __name__ == '__main__': + main()