Refactored volume_backup{,_info} modules

Change-Id: I523fd25a11f8f39a346afc17ae1e3a4dfcb8bae2
This commit is contained in:
Jakob Meng 2022-11-03 10:17:39 +01:00
parent e9cd9bff2a
commit 764a8bff64
7 changed files with 365 additions and 197 deletions

View File

@ -111,6 +111,7 @@
user_group
user_role
volume
volume_backup
# failing tags
# neutron_rbac

View File

@ -46,35 +46,6 @@
description: Test volume
register: vol
- name: Create volume backup
openstack.cloud.volume_backup:
cloud: "{{ cloud }}"
state: present
name: ansible_volume_backup
volume: ansible_volume
register: vol_backup
- name: Get backup info
openstack.cloud.volume_backup_info:
cloud: "{{ cloud }}"
name: ansible_volume_backup
register: backup_info
- debug: var=vol
- debug: var=vol_backup
- debug: var=backup_info
- debug: var=snap_info
- name: Delete volume backup
openstack.cloud.volume_backup:
cloud: "{{ cloud }}"
name: ansible_volume_backup
wait: false
state: absent
- name: Delete volume snapshot
openstack.cloud.volume_snapshot:
cloud: "{{ cloud }}"

View File

@ -0,0 +1,22 @@
expected_fields:
- availability_zone
- container
- created_at
- data_timestamp
- description
- fail_reason
- force
- has_dependent_backups
- id
- is_incremental
- links
- metadata
- name
- object_count
- project_id
- size
- snapshot_id
- status
- updated_at
- user_id
- volume_id

View File

@ -0,0 +1,89 @@
---
- name: Get existing backups
openstack.cloud.volume_backup_info:
cloud: "{{ cloud }}"
register: info
- name: Assert volume_backup_info
assert:
that:
- info.volume_backups|length == 0
- name: Get non-existing backup
openstack.cloud.volume_backup_info:
cloud: "{{ cloud }}"
name: non-existing-backup
register: info
- name: Assert volume_backup_info
assert:
that:
- info.volume_backups|length == 0
- name: Create volume
openstack.cloud.volume:
cloud: "{{ cloud }}"
state: present
size: 1
name: ansible_volume
register: volume
- name: Create volume backup
openstack.cloud.volume_backup:
cloud: "{{ cloud }}"
state: present
name: ansible_volume_backup
volume: ansible_volume
# TODO: Uncomment code when https://storyboard.openstack.org/#!/story/2010395 has been solved.
#metadata:
# key1: value1
# key2: value2
register: backup
- name: Assert volume_backup
assert:
that:
- backup.volume_backup.name == "ansible_volume_backup"
- backup.volume_backup.volume_id == volume.volume.id
# TODO: Uncomment code when https://storyboard.openstack.org/#!/story/2010395 has been solved.
#- backup.volume_backup.metadata.keys()|sort == ['key1', 'key2']
#- backup.volume_backup.metadata['key1'] == 'value1'
#- backup.volume_backup.metadata['key2'] == 'value2'
- name: Assert return values of volume_backup module
assert:
that:
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(backup.volume_backup.keys())|length == 0
- name: Get backup info
openstack.cloud.volume_backup_info:
cloud: "{{ cloud }}"
name: ansible_volume_backup
register: info
- name: Assert volume_backup_info
assert:
that:
- info.volume_backups|length == 1
- info.volume_backups[0].id == backup.backup.id
- info.volume_backups[0].volume_id == volume.volume.id
- name: Assert return values of volume_info module
assert:
that:
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(info.volume_backups[0].keys())|length == 0
- name: Delete volume backup
openstack.cloud.volume_backup:
cloud: "{{ cloud }}"
name: ansible_volume_backup
wait: false
state: absent
- name: Delete volume
openstack.cloud.volume:
cloud: "{{ cloud }}"
state: absent
name: ansible_volume

View File

@ -65,6 +65,7 @@
- { role: user_group, tags: user_group }
- { role: user_role, tags: user_role }
- { role: volume, tags: volume }
- { role: volume_backup, tags: volume_backup }
- role: loadbalancer
tags: loadbalancer
- { role: quota, tags: quota }

View File

@ -4,28 +4,41 @@
# 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 = '''
DOCUMENTATION = r'''
---
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.
- Add or Remove Volume Backup in OpenStack.
options:
display_name:
description:
description:
- String describing the backup
type: str
aliases: ['display_description']
force:
description:
- Indicates whether to backup, even if the volume is attached.
type: bool
default: False
is_incremental:
description: The backup mode
type: bool
default: False
aliases: ['incremental']
metadata:
description: Metadata for the backup
type: dict
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
aliases: ['display_name']
snapshot:
description: Name or ID of the Snapshot to take backup of.
type: str
aliases: ['description']
state:
description:
- Should the resource be present or absent.
@ -34,63 +47,117 @@ options:
type: str
volume:
description:
- Name or ID of the volume. Required when state is True.
- Name or ID of the volume.
- Required when I(state) is C(present).
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"]
notes:
- This module does not support updates to existing backups.
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = '''
id:
description: The Volume backup ID.
returned: On success when C(state=present)
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
RETURN = r'''
backup:
description: Dictionary describing the Cluster.
description: Same as C(volume_backup), kept for backward compatibility.
returned: On success when C(state=present)
type: complex
type: dict
volume_backup:
description: Dictionary describing the volume backup.
returned: On success when C(state=present)
type: dict
contains:
availability_zone:
description: Backup availability zone.
type: str
container:
description: The container name.
type: str
created_at:
description: Backup creation time.
type: str
data_timestamp:
description: The time when the data on the volume was first saved.
If it is a backup from volume, it will be the same as
C(created_at) for a backup. If it is a backup from a
snapshot, it will be the same as created_at for the
snapshot.
type: str
description:
description: Backup desciption.
type: str
fail_reason:
description: Backup fail reason.
type: str
force:
description: Force backup.
type: bool
has_dependent_backups:
description: If this value is true, there are other backups
depending on this backup.
type: bool
id:
description: Unique UUID.
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
is_incremental:
description: Backup incremental property.
type: bool
links:
description: A list of links associated with this volume.
type: list
metadata:
description: Backup metadata.
type: dict
name:
description: Name given to the load balancer.
description: Backup Name.
type: str
object_count:
description: backup object count.
type: int
project_id:
description: The UUID of the owning project.
type: str
size:
description: The size of the volume, in gibibytes (GiB).
type: int
snapshot_id:
description: Snapshot ID.
type: str
status:
description: Backup status.
type: str
updated_at:
description: Backup update time.
type: str
user_id:
description: The UUID of the project owner.
type: str
volume_id:
description: Volume ID.
type: str
sample: "elb_test"
'''
EXAMPLES = '''
EXAMPLES = r'''
- name: Create backup
openstack.cloud.volume_backup:
display_name: test_volume_backup
name: test_volume_backup
volume: "test_volume"
- name: Create backup from snapshot
openstack.cloud.volume_backup:
display_name: test_volume_backup
volume: "test_volume"
name: test_volume_backup
snapshot: "test_snapshot"
volume: "test_volume"
- name: Delete volume backup
openstack.cloud.volume_backup:
display_name: test_volume_backup
name: test_volume_backup
state: absent
'''
@ -98,18 +165,20 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
class VolumeBackupModule(OpenStackModule):
module_min_sdk_version = '0.49.0'
argument_spec = dict(
display_name=dict(required=True, aliases=['name']),
display_description=dict(aliases=['description']),
volume=dict(),
description=dict(aliases=['display_description']),
force=dict(default=False, type='bool'),
is_incremental=dict(default=False,
type='bool',
aliases=['incremental']),
metadata=dict(type='dict'),
name=dict(required=True, aliases=['display_name']),
snapshot=dict(),
state=dict(default='present', choices=['absent', 'present']),
force=dict(default=False, type='bool'),
metadata=dict(type='dict'),
incremental=dict(default=False, type='bool')
volume=dict(),
)
module_kwargs = dict(
required_if=[
('state', 'present', ['volume'])
@ -117,98 +186,79 @@ class VolumeBackupModule(OpenStackModule):
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']
name = self.params['name']
state = self.params['state']
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)
if self.ansible.check_mode:
self.exit_json(changed=self._will_change(state, backup))
elif self.params['state'] == 'absent':
self._delete_backup(backup)
if state == 'present' and not backup:
backup = self._create()
self.exit_json(changed=True,
backup=backup.to_dict(computed=False),
volume_backup=backup.to_dict(computed=False))
elif state == 'present' and backup:
# We do not support backup updates, because
# openstacksdk does not support it either
self.exit_json(changed=False,
backup=backup.to_dict(computed=False),
volume_backup=backup.to_dict(computed=False))
elif state == 'absent' and backup:
self._delete(backup)
self.exit_json(changed=True)
else: # state == 'absent' and not backup
self.exit_json(changed=False)
def _create(self):
args = dict()
for k in ['description', 'is_incremental', 'force', 'metadata',
'name']:
if self.params[k] is not None:
args[k] = self.params[k]
volume_name_or_id = self.params['volume']
volume = self.conn.block_storage.find_volume(volume_name_or_id,
ignore_missing=False)
args['volume_id'] = volume.id
snapshot_name_or_id = self.params['snapshot']
if snapshot_name_or_id:
snapshot = self.conn.block_storage.find_snapshot(
snapshot_name_or_id, ignore_missing=False)
args['snapshot_id'] = snapshot.id
backup = self.conn.block_storage.create_backup(**args)
if self.params['wait']:
backup = self.conn.block_storage.wait_for_status(
backup, status='available', wait=self.params['timeout'])
return backup
def _delete(self, backup):
self.conn.block_storage.delete_backup(backup)
if self.params['wait']:
self.conn.block_storage.wait_for_delete(
backup, wait=self.params['timeout'])
def _will_change(self, state, backup):
if state == 'present' and not backup:
return True
elif state == 'present' and backup:
# We do not support backup updates, because
# openstacksdk does not support it either
return False
elif state == 'absent' and backup:
return False
else:
# state == 'absent' and not backup:
return True
def main():

View File

@ -4,8 +4,7 @@
# 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 = '''
DOCUMENTATION = r'''
---
module: volume_backup_info
short_description: Get Backups
@ -19,14 +18,18 @@ options:
type: str
volume:
description:
- Name of the volume.
- Name or ID of the volume.
type: str
requirements: ["openstacksdk"]
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = '''
RETURN = r'''
volume_backups:
description: List of dictionaries describing volume backups.
type: list
@ -36,12 +39,32 @@ volume_backups:
availability_zone:
description: Backup availability zone.
type: str
container:
description: The container name.
type: str
created_at:
description: Backup creation time.
type: str
data_timestamp:
description: The time when the data on the volume was first saved.
If it is a backup from volume, it will be the same as
C(created_at) for a backup. If it is a backup from a
snapshot, it will be the same as created_at for the
snapshot.
type: str
description:
description: Backup desciption.
type: str
fail_reason:
description: Backup fail reason.
type: str
force:
description: Force backup.
type: bool
has_dependent_backups:
description: If this value is true, there are other backups
depending on this backup.
type: bool
id:
description: Unique UUID.
type: str
@ -49,12 +72,24 @@ volume_backups:
is_incremental:
description: Backup incremental property.
type: bool
links:
description: A list of links associated with this volume.
type: list
metadata:
description: Backup metadata.
type: dict
name:
description: Backup Name.
type: str
object_count:
description: backup object count.
type: int
project_id:
description: The UUID of the owning project.
type: str
size:
description: The size of the volume, in gibibytes (GiB).
type: int
snapshot_id:
description: Snapshot ID.
type: str
@ -64,57 +99,56 @@ volume_backups:
updated_at:
description: Backup update time.
type: str
user_id:
description: The UUID of the project owner.
type: str
volume_id:
description: Volume ID.
type: str
'''
EXAMPLES = '''
# Get backups.
- openstack.cloud.volume_backup_info:
register: backup
EXAMPLES = r'''
- name: Get all backups
openstack.cloud.volume_backup_info:
- openstack.cloud.volume_backup_info:
- name: Get backup 'my_fake_backup'
openstack.cloud.volume_backup_info:
name: my_fake_backup
register: backup
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class VolumeBackupInfoModule(OpenStackModule):
module_min_sdk_version = '0.49.0'
argument_spec = dict(
name=dict(),
volume=dict()
)
module_kwargs = dict(
supports_check_mode=True
)
def run(self):
name_filter = self.params['name']
volume = self.params['volume']
kwargs = dict((k, self.params[k])
for k in ['name']
if self.params[k] is not None)
data = []
attrs = {}
volume_name_or_id = self.params['volume']
volume = None
if volume_name_or_id:
volume = self.conn.block_storage.find_volume(volume_name_or_id)
if volume:
kwargs['volume_id'] = volume.id
if name_filter:
attrs['name'] = name_filter
if volume:
attrs['volume_id'] = self.conn.block_storage.find_volume(volume)
if volume_name_or_id and not volume:
backups = []
else:
backups = [b.to_dict(computed=False)
for b in self.conn.block_storage.backups(**kwargs)]
for raw in self.conn.block_storage.backups(**attrs):
dt = raw.to_dict()
dt.pop('location')
data.append(dt)
self.exit_json(
changed=False,
volume_backups=data
)
self.exit_json(changed=False, volume_backups=backups)
def main():