1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# (c) 2017, Simon Dodsley (simon@purestorage.com) 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10ANSIBLE_METADATA = {'metadata_version': '1.1', 11 'status': ['preview'], 12 'supported_by': 'community'} 13 14DOCUMENTATION = r''' 15--- 16module: purefa_pgsnap 17version_added: '2.6' 18short_description: Manage protection group snapshots on Pure Storage FlashArrays 19description: 20- Create or delete protection group snapshots on Pure Storage FlashArray. 21- Recovery of replicated snapshots on the replica target array is enabled. 22author: 23- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> 24options: 25 name: 26 description: 27 - The name of the source protection group. 28 type: str 29 required: true 30 suffix: 31 description: 32 - Suffix of snapshot name. 33 state: 34 description: 35 - Define whether the protection group snapshot should exist or not. 36 Copy (added in 2.7) will create a full read/write clone of the 37 snapshot. 38 type: str 39 choices: [ absent, present, copy ] 40 default: present 41 eradicate: 42 description: 43 - Define whether to eradicate the snapshot on delete or leave in trash. 44 type: bool 45 default: 'no' 46 restore: 47 description: 48 - Restore a specific volume from a protection group snapshot. 49 type: str 50 version_added: 2.7 51 overwrite: 52 description: 53 - Define whether to overwrite the target volume if it already exists. 54 type: bool 55 default: 'no' 56 version_added: 2.8 57 target: 58 description: 59 - Volume to restore a specified volume to. 60 - If not supplied this will default to the volume defined in I(restore) 61 type: str 62 version_added: 2.8 63 now: 64 description: Whether to initiate a snapshot of the protection group immediately 65 type: bool 66 default: False 67 version_added: 2.9 68 apply_retention: 69 description: Apply retention schedule settings to the snapshot 70 type: bool 71 default: False 72 version_added: 2.9 73 remote: 74 description: Force immeadiate snapshot to remote targets 75 type: bool 76 default: False 77 version_added: 2.9 78extends_documentation_fragment: 79- purestorage.fa 80''' 81 82EXAMPLES = r''' 83- name: Create protection group snapshot foo.ansible 84 purefa_pgsnap: 85 name: foo 86 suffix: ansible 87 fa_url: 10.10.10.2 88 api_token: e31060a7-21fc-e277-6240-25983c6c4592 89 state: present 90 91- name: Delete and eradicate protection group snapshot named foo.snap 92 purefa_pgsnap: 93 name: foo 94 suffix: snap 95 eradicate: true 96 fa_url: 10.10.10.2 97 api_token: e31060a7-21fc-e277-6240-25983c6c4592 98 state: absent 99 100- name: Restore volume data from local protection group snapshot named foo.snap to volume data2 101 purefa_pgsnap: 102 name: foo 103 suffix: snap 104 restore: data 105 target: data2 106 overwrite: true 107 fa_url: 10.10.10.2 108 api_token: e31060a7-21fc-e277-6240-25983c6c4592 109 state: copy 110 111- name: Restore remote protection group snapshot arrayA:pgname.snap.data to local copy 112 purefa_pgsnap: 113 name: arrayA:pgname 114 suffix: snap 115 restore: data 116 fa_url: 10.10.10.2 117 api_token: e31060a7-21fc-e277-6240-25983c6c4592 118 state: copy 119 120- name: Create snapshot of existing pgroup foo with suffix and force immeadiate copy to remote targets 121 purefa_pgsnap: 122 name: pgname 123 suffix: force 124 now: True 125 apply_retention: True 126 remote: True 127 fa_url: 10.10.10.2 128 api_token: e31060a7-21fc-e277-6240-25983c6c4592 129 state: copy 130''' 131 132RETURN = r''' 133''' 134 135from ansible.module_utils.basic import AnsibleModule 136from ansible.module_utils.pure import get_system, purefa_argument_spec 137 138from datetime import datetime 139 140 141def get_pgroup(module, array): 142 """Return Protection Group or None""" 143 try: 144 return array.get_pgroup(module.params['name']) 145 except Exception: 146 return None 147 148 149def get_pgroupvolume(module, array): 150 """Return Protection Group Volume or None""" 151 try: 152 pgroup = array.get_pgroup(module.params['name']) 153 for volume in pgroup['volumes']: 154 if volume == module.params['restore']: 155 return volume 156 except Exception: 157 return None 158 159 160def get_rpgsnapshot(module, array): 161 """Return iReplicated Snapshot or None""" 162 try: 163 snapname = module.params['name'] + "." + module.params['suffix'] + "." + module.params['restore'] 164 for snap in array.list_volumes(snap=True): 165 if snap['name'] == snapname: 166 return snapname 167 except Exception: 168 return None 169 170 171def get_pgsnapshot(module, array): 172 """Return Snapshot (active or deleted) or None""" 173 try: 174 snapname = module.params['name'] + "." + module.params['suffix'] 175 for snap in array.get_pgroup(module.params['name'], snap=True, pending=True): 176 if snap['name'] == snapname: 177 return snapname 178 except Exception: 179 return None 180 181 182def create_pgsnapshot(module, array): 183 """Create Protection Group Snapshot""" 184 changed = True 185 if not module.check_mode: 186 try: 187 if module.params['now'] and array.get_pgroup(module.params['name'])['targets'] is not None: 188 array.create_pgroup_snapshot(source=module.params['name'], 189 suffix=module.params['suffix'], 190 snap=True, 191 apply_retention=module.params['apply_retention'], 192 replicate_now=module.params['remote']) 193 else: 194 array.create_pgroup_snapshot(source=module.params['name'], 195 suffix=module.params['suffix'], 196 snap=True, 197 apply_retention=module.params['apply_retention']) 198 except Exception: 199 module.fail_json(msg="Snapshot of pgroup {0} failed.".format(module.params['name'])) 200 module.exit_json(changed=changed) 201 202 203def restore_pgsnapvolume(module, array): 204 """Restore a Protection Group Snapshot Volume""" 205 changed = True 206 if not module.check_mode: 207 if ":" in module.params['name']: 208 if get_rpgsnapshot(module, array)is None: 209 module.fail_json(msg="Selected restore snapshot {0} does not exist in the Protection Group".format(module.params['restore'])) 210 else: 211 if get_pgroupvolume(module, array) is None: 212 module.fail_json(msg="Selected restore volume {0} does not exist in the Protection Group".format(module.params['restore'])) 213 volume = module.params['name'] + "." + module.params['suffix'] + "." + module.params['restore'] 214 try: 215 array.copy_volume(volume, module.params['target'], overwrite=module.params['overwrite']) 216 except Exception: 217 module.fail_json(msg="Failed to restore {0} from pgroup {1}".format(volume, module.params['name'])) 218 module.exit_json(changed=changed) 219 220 221def update_pgsnapshot(module, array): 222 """Update Protection Group Snapshot""" 223 changed = True 224 module.exit_json(changed=changed) 225 226 227def delete_pgsnapshot(module, array): 228 """ Delete Protection Group Snapshot""" 229 changed = True 230 if not module.check_mode: 231 snapname = module.params['name'] + "." + module.params['suffix'] 232 try: 233 array.destroy_pgroup(snapname) 234 if module.params['eradicate']: 235 try: 236 array.eradicate_pgroup(snapname) 237 except Exception: 238 module.fail_json(msg="Failed to eradicate pgroup {0}".format(snapname)) 239 except Exception: 240 module.fail_json(msg="Failed to delete pgroup {0}".format(snapname)) 241 module.exit_json(changed=changed) 242 243 244def main(): 245 argument_spec = purefa_argument_spec() 246 argument_spec.update(dict( 247 name=dict(type='str', required=True), 248 suffix=dict(type='str'), 249 restore=dict(type='str'), 250 overwrite=dict(type='bool', default=False), 251 target=dict(type='str'), 252 eradicate=dict(type='bool', default=False), 253 now=dict(type='bool', default=False), 254 apply_retention=dict(type='bool', default=False), 255 remote=dict(type='bool', default=False), 256 state=dict(type='str', default='present', choices=['absent', 'present', 'copy']), 257 )) 258 259 required_if = [('state', 'copy', ['suffix', 'restore'])] 260 261 module = AnsibleModule(argument_spec, 262 required_if=required_if, 263 supports_check_mode=True) 264 265 if module.params['suffix'] is None: 266 suffix = "snap-" + str((datetime.utcnow() - datetime(1970, 1, 1, 0, 0, 0, 0)).total_seconds()) 267 module.params['suffix'] = suffix.replace(".", "") 268 269 if not module.params['target'] and module.params['restore']: 270 module.params['target'] = module.params['restore'] 271 272 state = module.params['state'] 273 array = get_system(module) 274 pgroup = get_pgroup(module, array) 275 if pgroup is None: 276 module.fail_json(msg="Protection Group {0} does not exist.".format(module.params['name'])) 277 pgsnap = get_pgsnapshot(module, array) 278 279 if state == 'copy': 280 restore_pgsnapvolume(module, array) 281 elif state == 'present' and not pgsnap: 282 create_pgsnapshot(module, array) 283 elif state == 'present' and pgsnap: 284 update_pgsnapshot(module, array) 285 elif state == 'absent' and pgsnap: 286 delete_pgsnapshot(module, array) 287 elif state == 'absent' and not pgsnap: 288 module.exit_json(changed=False) 289 290 module.exit_json(changed=False) 291 292 293if __name__ == '__main__': 294 main() 295