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