Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE]: Add refresh support for snapshot, filesystem_snapshot, volu… #36

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/doc_fragments/unity.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class ModuleDocFragment(object):
- A Dell Unity Storage device version 5.1 or later.
- Ansible-core 2.12 or later.
- Python 3.9, 3.10 or 3.11.
- Storops Python SDK 1.2.11.
- Storops Python SDK > 1.2.11.
notes:
- The modules present in this collection named as 'dellemc.unity'
are built to support the Dell Unity storage platform.
Expand Down
123 changes: 120 additions & 3 deletions plugins/modules/consistencygroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
consistency group, unmapping hosts from consistency group,
renaming consistency group, modifying attributes of consistency group,
enabling replication in consistency group, disabling replication in
consistency group and deleting consistency group.
consistency group, deleting consistency group
and refreshing thin clone consistency group.

extends_documentation_fragment:
- dellemc.unity.unity
Expand Down Expand Up @@ -197,9 +198,34 @@
state:
description:
- Define whether the consistency group should exist or not.
choices: [absent, present]
- The C(refreshed) state is not idempotent. It always executes the
refresh operation.
choices: [absent, present, refreshed]
required: true
type: str
retention_duration:
description:
- This option is for specifying the retention duration for the backup copy
of the snapshot created during the refresh operation.
- The retention duration is set in seconds. See the examples how to
calculate it.
- If set to C(0), the backup copy is deleted immediately
- If not set, the storage defaults are used
type: int
copy_name:
description:
- The backup copy name of the snapshot created by the refresh operation.
type: str
force_refresh:
description:
- When set to C(true), the refresh operation will proceed even if host
access is configured on the storage resource.
type: bool
default: false
snapshot_name:
description:
- The source snapshot which is used to refresh the thin clone
type: str
notes:
- The I(check_mode) is not supported.
"""
Expand Down Expand Up @@ -337,6 +363,48 @@
cg_name: "dis_repl_ans_source"
replication_state: "disable"
state: "present"

- name: Force Refresh a thin clone, keep backup snapshot for 2 days
dellemc.unity.consistencygroup:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
cg_name: "{{cg_name}}"
retention_duration: "{{ '2days' | community.general.to_seconds | int }}"
force_refresh: True
state: "refreshed"

- name: Refresh a Snapshot, delete backup snapshot
dellemc.unity.consistencygroup:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
cg_name: "{{cg_name}}"
retention_duration: 0
state: "refreshed"

- name: Refresh a thin clone and set backup snapshot name
dellemc.unity.consistencygroup:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
cg_name: "{{cg_name}}"
copy_name: "{{snapshot_name}}_before_refresh"
state: "refreshed"

- name: Refresh a thin clone from a snapshot, keep backup snapshot for 2 days
dellemc.unity.consistencygroup:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
cg_name: "{{cg_name}}"
snapshot_name: "{{new_snapshot_name}}"
retention_duration: "{{ '2days' | community.general.to_seconds | int }}"
state: "refreshed"
"""

RETURN = r'''
Expand Down Expand Up @@ -1062,6 +1130,35 @@ def delete_cg(self, cg_name):
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)

def refresh_cg(self, cg_name, snapshot=None, copy_name=None, force=False,
retention_duration=None):
"""Refresh thin clone

:param copy_name: name of the backup snapshot
:param force: proceeed refresh even if host access is configured
:param retention_duration: Backup copy retention duration in seconds
"""
cg_obj = self.return_cg_instance(cg_name)
try:
if snapshot is not None:
resp = snapshot\
.refresh_thin_clone(sr=cg_obj,
copy_name=copy_name,
force=force,
retention_duration=retention_duration)
else:
resp = cg_obj.refresh(copy_name=copy_name,
force=force,
retention_duration=retention_duration)
resp.raise_if_err()
cg_obj.update()
except Exception as e:
error_msg = "Failed to refresh thin clone" \
" [name: %s, id: %s] with error %s"\
% (cg_obj.name, cg_obj.id, str(e))
LOG.error(error_msg)
self.module.fail_json(msg=error_msg)

def refine_volumes(self, volumes):
"""Refine volumes.
:param volumes: Volumes that is to be added/removed
Expand Down Expand Up @@ -1286,6 +1383,10 @@ def perform_module_operation(self):
mapping_state = self.module.params['mapping_state']
replication = self.module.params['replication_params']
replication_state = self.module.params['replication_state']
retention_duration = self.module.params['retention_duration']
copy_name = self.module.params['copy_name']
force_refresh = self.module.params['force_refresh']
snapshot_name = self.module.params['snapshot_name']
state = self.module.params['state']

# result is a dictionary that contains changed status and consistency
Expand Down Expand Up @@ -1399,6 +1500,16 @@ def perform_module_operation(self):
else:
result['changed'] = self.disable_cg_replication(cg_name)

if state == 'refreshed' and cg_details:
snapshot = None
if snapshot_name is not None:
snapshot = self.unity_conn.get_snap(name=snapshot_name)

self.refresh_cg(cg_name=cg_name, snapshot=snapshot,
copy_name=copy_name, force=force_refresh,
retention_duration=retention_duration)
result['changed'] = True

if result['create_cg'] or result['modify_cg'] or result[
'add_vols_to_cg'] or result['remove_vols_from_cg'] or result[
'delete_cg'] or result['rename_cg'] or result[
Expand Down Expand Up @@ -1501,7 +1612,13 @@ def get_consistencygroup_parameters():
destination_pool_id=dict(type='str')
)),
replication_state=dict(type='str', choices=['enable', 'disable']),
state=dict(required=True, type='str', choices=['present', 'absent'])
retention_duration=dict(required=False, type='int'),
copy_name=dict(required=False, type='str'),
force_refresh=dict(required=False, type='bool',
default=False),
snapshot_name=dict(required=False, type='str'),
state=dict(required=True, type='str',
choices=['present', 'absent', 'refreshed'])
)


Expand Down
77 changes: 74 additions & 3 deletions plugins/modules/filesystem_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
description:
- Managing Filesystem Snapshot on the Unity storage system includes
create filesystem snapshot, get filesystem snapshot, modify filesystem
snapshot and delete filesystem snapshot.
snapshot, delete filesystem snapshot and refresh filesystem snapshot.
version_added: '1.1.0'
extends_documentation_fragment:
- dellemc.unity.unity
Expand Down Expand Up @@ -99,13 +99,26 @@
- If not given, snapshot's access type will be C(Checkpoint).
type: str
choices: ['Checkpoint' , 'Protocol']
retention_duration:
description:
- This option is for specifying the retention duration for the backup copy
of the snapshot created during the refresh operation.
- The retention duration is set in seconds. See the examples how to
calculate it.
- If set to C(0), the backup copy is deleted immediately
- If not set, the storage defaults are used
type: int
copy_name:
description:
- The backup copy name of the snapshot created by the refresh operation.
type: str
state:
description:
- The state option is used to mention the existence of the filesystem
snapshot.
type: str
required: true
choices: ['absent', 'present']
choices: ['absent', 'present', 'refreshed']
notes:
- Filesystem snapshot cannot be deleted, if it has nfs or smb share.
- The I(check_mode) is not supported.
Expand Down Expand Up @@ -197,6 +210,37 @@
validate_certs: "{{validate_certs}}"
snapshot_id: "10008000403"
state: "absent"

- name: Refresh a Snapshot, keep backup snapshot for 3 days
dellemc.unity.snapshot:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
snapshot_name: "ansible_test_FS_snap"
retention_duration: "{{ '3days' | community.general.to_seconds | int }}"
state: "refreshed"

- name: Refresh a Snapshot, delete backup snapshot
dellemc.unity.snapshot:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
snapshot_name: "ansible_test_FS_snap"
retention_duration: 0
state: "refreshed"

- name: Refresh a Snapshot and set backup snapshot name
dellemc.unity.snapshot:
unispherehost: "{{unispherehost}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
snapshot_name: "ansible_test_FS_snap"
copy_name: "{{snapshot_name}}_before_refresh"
state: "refreshed"

'''

RETURN = r'''
Expand Down Expand Up @@ -471,6 +515,20 @@ def delete_fs_snapshot(self, fs_snapshot):
LOG.error(error_msg)
self.module.fail_json(msg=error_msg)

def refresh_fs_snapshot(self, fs_snapshot, copy_name=None,
retention_duration=None):
try:
resp = fs_snapshot.refresh(copy_name=copy_name,
retention_duration=retention_duration)
resp.raise_if_err()
fs_snapshot.update()
except Exception as e:
error_msg = "Failed to refresh snapshot" \
" [name: %s , id: %s] with error %s"\
% (fs_snapshot.name, fs_snapshot.id, str(e))
LOG.error(error_msg)
self.module.fail_json(msg=error_msg)

def get_fs_snapshot_obj(self, name=None, id=None):
fs_snapshot = id if id else name
msg = "Failed to get details of filesystem snapshot %s with error %s."
Expand Down Expand Up @@ -585,6 +643,8 @@ def perform_module_operation(self):
description = self.module.params['description']
fs_access_type = self.module.params['fs_access_type']
state = self.module.params['state']
retention_duration = self.module.params['retention_duration']
copy_name = self.module.params['copy_name']
nas_server_resource = None
filesystem_resource = None
changed = False
Expand Down Expand Up @@ -707,6 +767,14 @@ def perform_module_operation(self):
fs_snapshot = self.delete_fs_snapshot(fs_snapshot)
changed = True

# Refresh the snapshot:
if fs_snapshot and state == "refreshed":
fs_snapshot = self\
.refresh_fs_snapshot(fs_snapshot=fs_snapshot,
copy_name=copy_name,
retention_duration=retention_duration)
changed = True

# Add filesystem snapshot details to the result.
if fs_snapshot:
fs_snapshot.update()
Expand Down Expand Up @@ -754,7 +822,10 @@ def get_snapshot_parameters():
description=dict(required=False, type='str'),
fs_access_type=dict(required=False, type='str',
choices=['Checkpoint', 'Protocol']),
state=dict(required=True, type='str', choices=['present', 'absent'])
retention_duration=dict(required=False, type='int'),
copy_name=dict(required=False, type='str'),
state=dict(required=True, type='str',
choices=['present', 'absent', 'refreshed'])
)


Expand Down
Loading