1#!/usr/bin/python
2
3# (c) 2018-2019, NetApp, Inc
4# GNU General Public License v3.0+
5# (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
10
11ANSIBLE_METADATA = {'metadata_version': '1.1',
12                    'status': ['preview'],
13                    'supported_by': 'community'}
14
15DOCUMENTATION = '''
16module: na_ontap_snapshot
17short_description: NetApp ONTAP manage Snapshots
18extends_documentation_fragment:
19    - netapp.na_ontap
20version_added: '2.6'
21author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
22description:
23- Create/Modify/Delete ONTAP snapshots
24options:
25  state:
26    description:
27    - If you want to create/modify a snapshot, or delete it.
28    choices: ['present', 'absent']
29    default: present
30  snapshot:
31    description:
32      Name of the snapshot to be managed.
33      The maximum string length is 256 characters.
34    required: true
35  from_name:
36    description:
37    - Name of the existing snapshot to be renamed to.
38    version_added: '2.8'
39  volume:
40    description:
41    - Name of the volume on which the snapshot is to be created.
42    required: true
43  async_bool:
44    description:
45    - If true, the snapshot is to be created asynchronously.
46    type: bool
47  comment:
48    description:
49      A human readable comment attached with the snapshot.
50      The size of the comment can be at most 255 characters.
51  snapmirror_label:
52    description:
53      A human readable SnapMirror Label attached with the snapshot.
54      Size of the label can be at most 31 characters.
55  ignore_owners:
56    description:
57    - if this field is true, snapshot will be deleted
58      even if some other processes are accessing it.
59    type: bool
60  snapshot_instance_uuid:
61    description:
62    - The 128 bit unique snapshot identifier expressed in the form of UUID.
63  vserver:
64    description:
65    - The Vserver name
66    required: true
67'''
68EXAMPLES = """
69    - name: create SnapShot
70      tags:
71        - create
72      na_ontap_snapshot:
73        state: present
74        snapshot: "{{ snapshot name }}"
75        volume: "{{ vol name }}"
76        comment: "i am a comment"
77        vserver: "{{ vserver name }}"
78        username: "{{ netapp username }}"
79        password: "{{ netapp password }}"
80        hostname: "{{ netapp hostname }}"
81    - name: delete SnapShot
82      tags:
83        - delete
84      na_ontap_snapshot:
85        state: absent
86        snapshot: "{{ snapshot name }}"
87        volume: "{{ vol name }}"
88        vserver: "{{ vserver name }}"
89        username: "{{ netapp username }}"
90        password: "{{ netapp password }}"
91        hostname: "{{ netapp hostname }}"
92    - name: modify SnapShot
93      tags:
94        - modify
95      na_ontap_snapshot:
96        state: present
97        snapshot: "{{ snapshot name }}"
98        comment: "New comments are great"
99        volume: "{{ vol name }}"
100        vserver: "{{ vserver name }}"
101        username: "{{ netapp username }}"
102        password: "{{ netapp password }}"
103        hostname: "{{ netapp hostname }}"
104"""
105
106RETURN = """
107"""
108import traceback
109
110from ansible.module_utils.basic import AnsibleModule
111from ansible.module_utils.netapp_module import NetAppModule
112from ansible.module_utils._text import to_native
113import ansible.module_utils.netapp as netapp_utils
114
115HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
116
117
118class NetAppOntapSnapshot(object):
119    """
120    Creates, modifies, and deletes a Snapshot
121    """
122
123    def __init__(self):
124        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
125        self.argument_spec.update(dict(
126            state=dict(required=False, choices=[
127                       'present', 'absent'], default='present'),
128            from_name=dict(required=False, type='str'),
129            snapshot=dict(required=True, type="str"),
130            volume=dict(required=True, type="str"),
131            async_bool=dict(required=False, type="bool", default=False),
132            comment=dict(required=False, type="str"),
133            snapmirror_label=dict(required=False, type="str"),
134            ignore_owners=dict(required=False, type="bool", default=False),
135            snapshot_instance_uuid=dict(required=False, type="str"),
136            vserver=dict(required=True, type="str"),
137
138        ))
139        self.module = AnsibleModule(
140            argument_spec=self.argument_spec,
141            supports_check_mode=True
142        )
143
144        self.na_helper = NetAppModule()
145        self.parameters = self.na_helper.set_parameters(self.module.params)
146
147        if HAS_NETAPP_LIB is False:
148            self.module.fail_json(
149                msg="the python NetApp-Lib module is required")
150        else:
151            self.server = netapp_utils.setup_na_ontap_zapi(
152                module=self.module, vserver=self.parameters['vserver'])
153        return
154
155    def get_snapshot(self, snapshot_name=None):
156        """
157        Checks to see if a snapshot exists or not
158        :return: Return True if a snapshot exists, False if it doesn't
159        """
160        if snapshot_name is None:
161            snapshot_name = self.parameters['snapshot']
162        snapshot_obj = netapp_utils.zapi.NaElement("snapshot-get-iter")
163        desired_attr = netapp_utils.zapi.NaElement("desired-attributes")
164        snapshot_info = netapp_utils.zapi.NaElement('snapshot-info')
165        comment = netapp_utils.zapi.NaElement('comment')
166        snapmirror_label = netapp_utils.zapi.NaElement('snapmirror-label')
167        # add more desired attributes that are allowed to be modified
168        snapshot_info.add_child_elem(comment)
169        snapshot_info.add_child_elem(snapmirror_label)
170        desired_attr.add_child_elem(snapshot_info)
171        snapshot_obj.add_child_elem(desired_attr)
172        # compose query
173        query = netapp_utils.zapi.NaElement("query")
174        snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info")
175        snapshot_info_obj.add_new_child("name", snapshot_name)
176        snapshot_info_obj.add_new_child("volume", self.parameters['volume'])
177        snapshot_info_obj.add_new_child("vserver", self.parameters['vserver'])
178        query.add_child_elem(snapshot_info_obj)
179        snapshot_obj.add_child_elem(query)
180        result = self.server.invoke_successfully(snapshot_obj, True)
181        return_value = None
182        if result.get_child_by_name('num-records') and \
183                int(result.get_child_content('num-records')) == 1:
184            attributes_list = result.get_child_by_name('attributes-list')
185            snap_info = attributes_list.get_child_by_name('snapshot-info')
186            return_value = {'comment': snap_info.get_child_content('comment')}
187            if snap_info.get_child_by_name('snapmirror-label'):
188                return_value['snapmirror_label'] = snap_info.get_child_content('snapmirror-label')
189            else:
190                return_value['snapmirror_label'] = None
191        return return_value
192
193    def create_snapshot(self):
194        """
195        Creates a new snapshot
196        """
197        snapshot_obj = netapp_utils.zapi.NaElement("snapshot-create")
198
199        # set up required variables to create a snapshot
200        snapshot_obj.add_new_child("snapshot", self.parameters['snapshot'])
201        snapshot_obj.add_new_child("volume", self.parameters['volume'])
202        # Set up optional variables to create a snapshot
203        if self.parameters.get('async_bool'):
204            snapshot_obj.add_new_child("async", str(self.parameters['async_bool']))
205        if self.parameters.get('comment'):
206            snapshot_obj.add_new_child("comment", self.parameters['comment'])
207        if self.parameters.get('snapmirror_label'):
208            snapshot_obj.add_new_child(
209                "snapmirror-label", self.parameters['snapmirror_label'])
210        try:
211            self.server.invoke_successfully(snapshot_obj, True)
212        except netapp_utils.zapi.NaApiError as error:
213            self.module.fail_json(msg='Error creating snapshot %s: %s' %
214                                  (self.parameters['snapshot'], to_native(error)),
215                                  exception=traceback.format_exc())
216
217    def delete_snapshot(self):
218        """
219        Deletes an existing snapshot
220        """
221        snapshot_obj = netapp_utils.zapi.NaElement("snapshot-delete")
222
223        # Set up required variables to delete a snapshot
224        snapshot_obj.add_new_child("snapshot", self.parameters['snapshot'])
225        snapshot_obj.add_new_child("volume", self.parameters['volume'])
226        # set up optional variables to delete a snapshot
227        if self.parameters.get('ignore_owners'):
228            snapshot_obj.add_new_child("ignore-owners", str(self.parameters['ignore_owners']))
229        if self.parameters.get('snapshot_instance_uuid'):
230            snapshot_obj.add_new_child("snapshot-instance-uuid", self.parameters['snapshot_instance_uuid'])
231        try:
232            self.server.invoke_successfully(snapshot_obj, True)
233        except netapp_utils.zapi.NaApiError as error:
234            self.module.fail_json(msg='Error deleting snapshot %s: %s' %
235                                  (self.parameters['snapshot'], to_native(error)),
236                                  exception=traceback.format_exc())
237
238    def modify_snapshot(self):
239        """
240        Modify an existing snapshot
241        :return:
242        """
243        snapshot_obj = netapp_utils.zapi.NaElement("snapshot-modify-iter")
244        # Create query object, this is the existing object
245        query = netapp_utils.zapi.NaElement("query")
246        snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info")
247        snapshot_info_obj.add_new_child("name", self.parameters['snapshot'])
248        snapshot_info_obj.add_new_child("vserver", self.parameters['vserver'])
249        query.add_child_elem(snapshot_info_obj)
250        snapshot_obj.add_child_elem(query)
251
252        # this is what we want to modify in the snapshot object
253        attributes = netapp_utils.zapi.NaElement("attributes")
254        snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info")
255        snapshot_info_obj.add_new_child("name", self.parameters['snapshot'])
256        if self.parameters.get('comment'):
257            snapshot_info_obj.add_new_child("comment", self.parameters['comment'])
258        if self.parameters.get('snapmirror_label'):
259            snapshot_info_obj.add_new_child("snapmirror-label", self.parameters['snapmirror_label'])
260        attributes.add_child_elem(snapshot_info_obj)
261        snapshot_obj.add_child_elem(attributes)
262        try:
263            self.server.invoke_successfully(snapshot_obj, True)
264        except netapp_utils.zapi.NaApiError as error:
265            self.module.fail_json(msg='Error modifying snapshot %s: %s' %
266                                  (self.parameters['snapshot'], to_native(error)),
267                                  exception=traceback.format_exc())
268
269    def rename_snapshot(self):
270        """
271        Rename the snapshot
272        """
273        snapshot_obj = netapp_utils.zapi.NaElement("snapshot-rename")
274
275        # set up required variables to rename a snapshot
276        snapshot_obj.add_new_child("current-name", self.parameters['from_name'])
277        snapshot_obj.add_new_child("new-name", self.parameters['snapshot'])
278        snapshot_obj.add_new_child("volume", self.parameters['volume'])
279        try:
280            self.server.invoke_successfully(snapshot_obj, True)
281        except netapp_utils.zapi.NaApiError as error:
282            self.module.fail_json(msg='Error renaming snapshot %s to %s: %s' %
283                                  (self.parameters['from_name'], self.parameters['snapshot'], to_native(error)),
284                                  exception=traceback.format_exc())
285
286    def apply(self):
287        """
288        Check to see which play we should run
289        """
290        current = self.get_snapshot()
291        netapp_utils.ems_log_event("na_ontap_snapshot", self.server)
292        rename, cd_action = None, None
293        modify = {}
294        if self.parameters.get('from_name'):
295            current_old_name = self.get_snapshot(self.parameters['from_name'])
296            rename = self.na_helper.is_rename_action(current_old_name, current)
297            modify = self.na_helper.get_modified_attributes(current_old_name, self.parameters)
298        else:
299            cd_action = self.na_helper.get_cd_action(current, self.parameters)
300            if cd_action is None:
301                modify = self.na_helper.get_modified_attributes(current, self.parameters)
302        if self.na_helper.changed:
303            if self.module.check_mode:
304                pass
305            else:
306                if rename:
307                    self.rename_snapshot()
308                if cd_action == 'create':
309                    self.create_snapshot()
310                elif cd_action == 'delete':
311                    self.delete_snapshot()
312                elif modify:
313                    self.modify_snapshot()
314        self.module.exit_json(changed=self.na_helper.changed)
315
316
317def main():
318    """
319    Creates, modifies, and deletes a Snapshot
320    """
321    obj = NetAppOntapSnapshot()
322    obj.apply()
323
324
325if __name__ == '__main__':
326    main()
327