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: purefb_snap
17version_added: '2.6'
18short_description: Manage filesystem snapshots on Pure Storage FlashBlades
19description:
20- Create or delete volumes and filesystem snapshots on Pure Storage FlashBlades.
21author:
22- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
23options:
24  name:
25    description:
26    - The name of the source filesystem.
27    required: true
28    type: str
29  suffix:
30    description:
31    - Suffix of snapshot name.
32    type: str
33  state:
34    description:
35    - Define whether the filesystem snapshot should exist or not.
36    choices: [ absent, present ]
37    default: present
38    type: str
39  eradicate:
40    description:
41    - Define whether to eradicate the snapshot on delete or leave in trash.
42    type: bool
43    default: 'no'
44extends_documentation_fragment:
45- purestorage.fb
46'''
47
48EXAMPLES = r'''
49- name: Create snapshot foo.ansible
50  purefb_snap:
51    name: foo
52    suffix: ansible
53    fb_url: 10.10.10.2
54    fb_api_token: e31060a7-21fc-e277-6240-25983c6c4592
55    state: present
56
57- name: Delete snapshot named foo.snap
58  purefb_snap:
59    name: foo
60    suffix: snap
61    fb_url: 10.10.10.2
62    fb_api_token: e31060a7-21fc-e277-6240-25983c6c4592
63    state: absent
64
65- name: Recover deleted snapshot foo.ansible
66  purefb_snap:
67    name: foo
68    suffix: ansible
69    fb_url: 10.10.10.2
70    fb_api_token: e31060a7-21fc-e277-6240-25983c6c4592
71    state: present
72
73- name: Eradicate snapshot named foo.snap
74  purefb_snap:
75    name: foo
76    suffix: snap
77    eradicate: true
78    fb_url: 10.10.10.2
79    fb_api_token: e31060a7-21fc-e277-6240-25983c6c4592
80    state: absent
81'''
82
83RETURN = r'''
84'''
85
86from ansible.module_utils.basic import AnsibleModule
87from ansible.module_utils.pure import get_blade, purefb_argument_spec
88
89from datetime import datetime
90
91HAS_PURITY_FB = True
92try:
93    from purity_fb import FileSystemSnapshot, SnapshotSuffix
94except ImportError:
95    HAS_PURITY_FB = False
96
97
98def get_fs(module, blade):
99    """Return Filesystem or None"""
100    fs = []
101    fs.append(module.params['name'])
102    try:
103        res = blade.file_systems.list_file_systems(names=fs)
104        return res.items[0]
105    except Exception:
106        return None
107
108
109def get_fssnapshot(module, blade):
110    """Return Snapshot or None"""
111    try:
112        filt = 'source=\'' + module.params['name'] + '\' and suffix=\'' + module.params['suffix'] + '\''
113        res = blade.file_system_snapshots.list_file_system_snapshots(filter=filt)
114        return res.items[0]
115    except Exception:
116        return None
117
118
119def create_snapshot(module, blade):
120    """Create Snapshot"""
121    if not module.check_mode:
122        source = []
123        source.append(module.params['name'])
124        try:
125            blade.file_system_snapshots.create_file_system_snapshots(sources=source, suffix=SnapshotSuffix(module.params['suffix']))
126            changed = True
127        except Exception:
128            changed = False
129    module.exit_json(changed=changed)
130
131
132def recover_snapshot(module, blade):
133    """Recover deleted Snapshot"""
134    if not module.check_mode:
135        snapname = module.params['name'] + "." + module.params['suffix']
136        new_attr = FileSystemSnapshot(destroyed=False)
137        try:
138            blade.file_system_snapshots.update_file_system_snapshots(name=snapname, attributes=new_attr)
139            changed = True
140        except Exception:
141            changed = False
142    module.exit_json(changed=changed)
143
144
145def update_snapshot(module, blade):
146    """Update Snapshot"""
147    changed = False
148    module.exit_json(changed=changed)
149
150
151def delete_snapshot(module, blade):
152    """ Delete Snapshot"""
153    if not module.check_mode:
154        snapname = module.params['name'] + "." + module.params['suffix']
155        new_attr = FileSystemSnapshot(destroyed=True)
156        try:
157            blade.file_system_snapshots.update_file_system_snapshots(name=snapname, attributes=new_attr)
158            changed = True
159            if module.params['eradicate']:
160                try:
161                    blade.file_system_snapshots.delete_file_system_snapshots(name=snapname)
162                    changed = True
163                except Exception:
164                    changed = False
165        except Exception:
166            changed = False
167    module.exit_json(changed=changed)
168
169
170def eradicate_snapshot(module, blade):
171    """ Eradicate Snapshot"""
172    if not module.check_mode:
173        snapname = module.params['name'] + "." + module.params['suffix']
174        try:
175            blade.file_system_snapshots.delete_file_system_snapshots(name=snapname)
176            changed = True
177        except Exception:
178            changed = False
179    module.exit_json(changed=changed)
180
181
182def main():
183    argument_spec = purefb_argument_spec()
184    argument_spec.update(
185        dict(
186            name=dict(required=True),
187            suffix=dict(type='str'),
188            eradicate=dict(default='false', type='bool'),
189            state=dict(default='present', choices=['present', 'absent'])
190        )
191    )
192
193    module = AnsibleModule(argument_spec,
194                           supports_check_mode=True)
195
196    if not HAS_PURITY_FB:
197        module.fail_json(msg='purity_fb sdk is required for this module')
198
199    if module.params['suffix'] is None:
200        suffix = "snap-" + str((datetime.utcnow() - datetime(1970, 1, 1, 0, 0, 0, 0)).total_seconds())
201        module.params['suffix'] = suffix.replace(".", "")
202
203    state = module.params['state']
204    blade = get_blade(module)
205    fs = get_fs(module, blade)
206    snap = get_fssnapshot(module, blade)
207
208    if state == 'present' and fs and not fs.destroyed and not snap:
209        create_snapshot(module, blade)
210    elif state == 'present' and fs and not fs.destroyed and snap and not snap.destroyed:
211        update_snapshot(module, blade)
212    elif state == 'present' and fs and not fs.destroyed and snap and snap.destroyed:
213        recover_snapshot(module, blade)
214    elif state == 'present' and fs and fs.destroyed:
215        update_snapshot(module, blade)
216    elif state == 'present' and not fs:
217        update_snapshot(module, blade)
218    elif state == 'absent' and snap and not snap.destroyed:
219        delete_snapshot(module, blade)
220    elif state == 'absent' and snap and snap.destroyed:
221        eradicate_snapshot(module, blade)
222    elif state == 'absent' and not snap:
223        module.exit_json(changed=False)
224
225
226if __name__ == '__main__':
227    main()
228