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