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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, 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_pgsched
short_description: Manage protection groups replication schedules on Pure Storage FlashArrays
version_added: '1.0.0'
description:
- Modify or delete protection groups replication schedules on Pure Storage FlashArrays.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
name:
description:
- The name of the protection group.
type: str
required: true
state:
description:
- Define whether to set or delete the protection group schedule.
type: str
default: present
choices: [ absent, present ]
schedule:
description:
- Which schedule to change.
type: str
choices: ['replication', 'snapshot']
required: true
enabled:
description:
- Enable the schedule being configured.
type: bool
default: true
replicate_at:
description:
- Provide a time in 12-hour AM/PM format, eg. 11AM
- Only valid if I(replicate_frequency) is an exact multiple of 86400, ie 1 day.
type: str
blackout_start:
description:
- Specifies the time at which to suspend replication.
- Provide a time in 12-hour AM/PM format, eg. 11AM
type: str
blackout_end:
description:
- Specifies the time at which to restart replication.
- Provide a time in 12-hour AM/PM format, eg. 5PM
type: str
replicate_frequency:
description:
- Specifies the replication frequency in seconds.
- Range 900 - 34560000 (FA-405, //M10, //X10i and Cloud Block Store).
- Range 300 - 34560000 (all other arrays).
type: int
snap_at:
description:
- Provide a time in 12-hour AM/PM format, eg. 11AM
- Only valid if I(snap_frequency) is an exact multiple of 86400, ie 1 day.
type: str
snap_frequency:
description:
- Specifies the snapshot frequency in seconds.
- Range available 300 - 34560000.
type: int
days:
description:
- Specifies the number of days to keep the I(per_day) snapshots beyond the
I(all_for) period before they are eradicated
- Max retention period is 4000 days
type: int
all_for:
description:
- Specifies the length of time, in seconds, to keep the snapshots on the
source array before they are eradicated.
- Range available 1 - 34560000.
type: int
per_day:
description:
- Specifies the number of I(per_day) snapshots to keep beyond the I(all_for) period.
- Maximum number is 1440
type: int
target_all_for:
description:
- Specifies the length of time, in seconds, to keep the replicated snapshots on the targets.
- Range is 1 - 34560000 seconds.
type: int
target_per_day:
description:
- Specifies the number of I(per_day) replicated snapshots to keep beyond the I(target_all_for) period.
- Maximum number is 1440
type: int
target_days:
description:
- Specifies the number of days to keep the I(target_per_day) replicated snapshots
beyond the I(target_all_for) period before they are eradicated.
- Max retention period is 4000 days
type: int
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
EXAMPLES = r"""
- name: Update protection group snapshot schedule
purestorage.flasharray.purefa_pgsched:
name: foo
schedule: snapshot
enabled: true
snap_frequency: 86400
snap_at: 3PM
per_day: 5
all_for: 5
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Update protection group replication schedule
purestorage.flasharray.purefa_pgsched:
name: foo
schedule: replication
enabled: true
replicate_frequency: 86400
replicate_at: 3PM
target_per_day: 5
target_all_for: 5
blackout_start: 2AM
blackout_end: 5AM
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete protection group snapshot schedule
purestorage.flasharray.purefa_pgsched:
name: foo
schedule: snapshot
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete protection group replication schedule
purestorage.flasharray.purefa_pgsched:
name: foo
schedule: replication
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
"""
RETURN = r"""
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
get_system,
purefa_argument_spec,
)
def get_pending_pgroup(module, array):
"""Get Protection Group"""
pgroup = None
if ":" in module.params["name"]:
for pgrp in array.list_pgroups(pending=True, on="*"):
if pgrp["name"] == module.params["name"] and pgrp["time_remaining"]:
pgroup = pgrp
break
else:
for pgrp in array.list_pgroups(pending=True):
if pgrp["name"] == module.params["name"] and pgrp["time_remaining"]:
pgroup = pgrp
break
return pgroup
def get_pgroup(module, array):
"""Get Protection Group"""
pgroup = None
if ":" in module.params["name"]:
if "::" not in module.params["name"]:
for pgrp in array.list_pgroups(on="*"):
if pgrp["name"] == module.params["name"]:
pgroup = pgrp
break
else:
for pgrp in array.list_pgroups():
if pgrp["name"] == module.params["name"]:
pgroup = pgrp
break
else:
for pgrp in array.list_pgroups():
if pgrp["name"] == module.params["name"]:
pgroup = pgrp
break
return pgroup
def _convert_to_minutes(hour):
if hour[-2:] == "AM" and hour[:2] == "12":
return 0
elif hour[-2:] == "AM":
return int(hour[:-2]) * 3600
elif hour[-2:] == "PM" and hour[:2] == "12":
return 43200
return (int(hour[:-2]) + 12) * 3600
def update_schedule(module, array, snap_time, repl_time):
"""Update Protection Group Schedule"""
changed = False
try:
schedule = array.get_pgroup(module.params["name"], schedule=True)
retention = array.get_pgroup(module.params["name"], retention=True)
if not schedule["replicate_blackout"]:
schedule["replicate_blackout"] = [{"start": 0, "end": 0}]
except Exception:
module.fail_json(
msg="Failed to get current schedule for pgroup {0}.".format(
module.params["name"]
)
)
current_repl = {
"replicate_frequency": schedule["replicate_frequency"],
"replicate_enabled": schedule["replicate_enabled"],
"target_days": retention["target_days"],
"replicate_at": schedule["replicate_at"],
"target_per_day": retention["target_per_day"],
"target_all_for": retention["target_all_for"],
"blackout_start": schedule["replicate_blackout"][0]["start"],
"blackout_end": schedule["replicate_blackout"][0]["end"],
}
current_snap = {
"days": retention["days"],
"snap_frequency": schedule["snap_frequency"],
"snap_enabled": schedule["snap_enabled"],
"snap_at": schedule["snap_at"],
"per_day": retention["per_day"],
"all_for": retention["all_for"],
}
if module.params["schedule"] == "snapshot":
if not module.params["snap_frequency"]:
snap_frequency = current_snap["snap_frequency"]
else:
if not 300 <= module.params["snap_frequency"] <= 34560000:
module.fail_json(
msg="Snap Frequency support is out of range (300 to 34560000)"
)
else:
snap_frequency = module.params["snap_frequency"]
if module.params["enabled"] is None:
snap_enabled = current_snap["snap_enabled"]
else:
snap_enabled = module.params["enabled"]
if not module.params["snap_at"]:
snap_at = current_snap["snap_at"]
else:
snap_at = _convert_to_minutes(module.params["snap_at"].upper())
if not module.params["days"]:
if isinstance(module.params["days"], int):
days = module.params["days"]
else:
days = current_snap["days"]
else:
if module.params["days"] > 4000:
module.fail_json(msg="Maximum value for days is 4000")
else:
days = module.params["days"]
if module.params["per_day"] is None:
per_day = current_snap["per_day"]
else:
if module.params["per_day"] > 1440:
module.fail_json(msg="Maximum value for per_day is 1440")
else:
per_day = module.params["per_day"]
if not module.params["all_for"]:
all_for = current_snap["all_for"]
else:
if module.params["all_for"] > 34560000:
module.fail_json(msg="Maximum all_for value is 34560000")
else:
all_for = module.params["all_for"]
new_snap = {
"days": days,
"snap_frequency": snap_frequency,
"snap_enabled": snap_enabled,
"snap_at": snap_at,
"per_day": per_day,
"all_for": all_for,
}
module.warn("current {0}; new: {1}".format(current_snap, new_snap))
if current_snap != new_snap:
changed = True
if not module.check_mode:
try:
array.set_pgroup(
module.params["name"], snap_enabled=module.params["enabled"]
)
if snap_time:
array.set_pgroup(
module.params["name"],
snap_frequency=snap_frequency,
snap_at=snap_at,
)
else:
array.set_pgroup(
module.params["name"],
snap_frequency=snap_frequency,
)
array.set_pgroup(
module.params["name"],
days=days,
per_day=per_day,
all_for=all_for,
)
except Exception:
module.fail_json(
msg="Failed to change snapshot schedule for pgroup {0}.".format(
module.params["name"]
)
)
else:
if not module.params["replicate_frequency"]:
replicate_frequency = current_repl["replicate_frequency"]
else:
model = array.get(controllers=True)[0]["model"]
if "405" in model or "10" in model or "CBS" in model:
if not 900 <= module.params["replicate_frequency"] <= 34560000:
module.fail_json(
msg="Replication Frequency support is out of range (900 to 34560000)"
)
else:
replicate_frequency = module.params["replicate_frequency"]
else:
if not 300 <= module.params["replicate_frequency"] <= 34560000:
module.fail_json(
msg="Replication Frequency support is out of range (300 to 34560000)"
)
else:
replicate_frequency = module.params["replicate_frequency"]
if module.params["enabled"] is None:
replicate_enabled = current_repl["replicate_enabled"]
else:
replicate_enabled = module.params["enabled"]
if not module.params["replicate_at"]:
replicate_at = current_repl["replicate_at"]
else:
replicate_at = _convert_to_minutes(module.params["replicate_at"].upper())
if not module.params["target_days"]:
if isinstance(module.params["target_days"], int):
target_days = module.params["target_days"]
else:
target_days = current_repl["target_days"]
else:
if module.params["target_days"] > 4000:
module.fail_json(msg="Maximum value for target_days is 4000")
else:
target_days = module.params["target_days"]
if not module.params["target_per_day"]:
if isinstance(module.params["target_per_day"], int):
target_per_day = module.params["target_per_day"]
else:
target_per_day = current_repl["target_per_day"]
else:
if module.params["target_per_day"] > 1440:
module.fail_json(msg="Maximum value for target_per_day is 1440")
else:
target_per_day = module.params["target_per_day"]
if not module.params["target_all_for"]:
target_all_for = current_repl["target_all_for"]
else:
if module.params["target_all_for"] > 34560000:
module.fail_json(msg="Maximum target_all_for value is 34560000")
else:
target_all_for = module.params["target_all_for"]
if not module.params["blackout_end"]:
blackout_end = current_repl["blackout_start"]
else:
blackout_end = _convert_to_minutes(module.params["blackout_end"].upper())
if not module.params["blackout_start"]:
blackout_start = current_repl["blackout_start"]
else:
blackout_start = _convert_to_minutes(
module.params["blackout_start"].upper()
)
new_repl = {
"replicate_frequency": replicate_frequency,
"replicate_enabled": replicate_enabled,
"target_days": target_days,
"replicate_at": replicate_at,
"target_per_day": target_per_day,
"target_all_for": target_all_for,
"blackout_start": blackout_start,
"blackout_end": blackout_end,
}
if current_repl != new_repl:
changed = True
if not module.check_mode:
blackout = {"start": blackout_start, "end": blackout_end}
try:
array.set_pgroup(
module.params["name"],
replicate_enabled=module.params["enabled"],
)
if repl_time:
array.set_pgroup(
module.params["name"],
replicate_frequency=replicate_frequency,
replicate_at=replicate_at,
)
else:
array.set_pgroup(
module.params["name"],
replicate_frequency=replicate_frequency,
)
if blackout_start == 0:
array.set_pgroup(module.params["name"], replicate_blackout=None)
else:
array.set_pgroup(
module.params["name"], replicate_blackout=blackout
)
array.set_pgroup(
module.params["name"],
target_days=target_days,
target_per_day=target_per_day,
target_all_for=target_all_for,
)
except Exception:
module.fail_json(
msg="Failed to change replication schedule for pgroup {0}.".format(
module.params["name"]
)
)
module.exit_json(changed=changed)
def delete_schedule(module, array):
"""Delete, ie. disable, Protection Group Schedules"""
changed = False
try:
current_state = array.get_pgroup(module.params["name"], schedule=True)
if module.params["schedule"] == "replication":
if current_state["replicate_enabled"]:
changed = True
if not module.check_mode:
array.set_pgroup(module.params["name"], replicate_enabled=False)
array.set_pgroup(
module.params["name"],
target_days=0,
target_per_day=0,
target_all_for=1,
)
array.set_pgroup(
module.params["name"],
replicate_frequency=14400,
replicate_blackout=None,
)
else:
if current_state["snap_enabled"]:
changed = True
if not module.check_mode:
array.set_pgroup(module.params["name"], snap_enabled=False)
array.set_pgroup(
module.params["name"], days=0, per_day=0, all_for=1
)
array.set_pgroup(module.params["name"], snap_frequency=300)
except Exception:
module.fail_json(
msg="Deleting pgroup {0} {1} schedule failed.".format(
module.params["name"], module.params["schedule"]
)
)
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(
dict(
name=dict(type="str", required=True),
state=dict(type="str", default="present", choices=["absent", "present"]),
schedule=dict(
type="str", required=True, choices=["replication", "snapshot"]
),
blackout_start=dict(type="str"),
blackout_end=dict(type="str"),
snap_at=dict(type="str"),
replicate_at=dict(type="str"),
replicate_frequency=dict(type="int"),
snap_frequency=dict(type="int"),
all_for=dict(type="int"),
days=dict(type="int"),
per_day=dict(type="int"),
target_all_for=dict(type="int"),
target_per_day=dict(type="int"),
target_days=dict(type="int"),
enabled=dict(type="bool", default=True),
)
)
required_together = [["blackout_start", "blackout_end"]]
module = AnsibleModule(
argument_spec, required_together=required_together, supports_check_mode=True
)
state = module.params["state"]
array = get_system(module)
pgroup = get_pgroup(module, array)
repl_time = False
if module.params["replicate_at"] and module.params["replicate_frequency"]:
if not module.params["replicate_frequency"] % 86400 == 0:
module.fail_json(
msg="replicate_at not valid unless replicate frequency is measured in days, ie. a multiple of 86400"
)
repl_time = True
snap_time = False
if module.params["snap_at"] and module.params["snap_frequency"]:
if not module.params["snap_frequency"] % 86400 == 0:
module.fail_json(
msg="snap_at not valid unless snapshot frequency is measured in days, ie. a multiple of 86400"
)
snap_time = True
if pgroup and state == "present":
update_schedule(module, array, snap_time, repl_time)
elif pgroup and state == "absent":
delete_schedule(module, array)
elif pgroup is None:
module.fail_json(
msg="Specified protection group {0} does not exist.".format(
module.params["name"]
)
)
if __name__ == "__main__":
main()
|