1#!/usr/bin/python
2
3# (c) 2017, NetApp, Inc
4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6from __future__ import absolute_import, division, print_function
7__metaclass__ = type
8
9
10ANSIBLE_METADATA = {'metadata_version': '1.1',
11                    'status': ['deprecated'],
12                    'supported_by': 'community'}
13
14
15DOCUMENTATION = '''
16
17module: na_cdot_volume
18
19short_description: Manage NetApp cDOT volumes
20extends_documentation_fragment:
21    - netapp.ontap
22version_added: '2.3'
23author: Sumit Kumar (@timuster) <sumit4@netapp.com>
24
25deprecated:
26  removed_in: '2.11'
27  why: Updated modules released with increased functionality
28  alternative: Use M(na_ontap_volume) instead.
29
30description:
31- Create or destroy volumes on NetApp cDOT
32
33options:
34
35  state:
36    description:
37    - Whether the specified volume should exist or not.
38    required: true
39    choices: ['present', 'absent']
40
41  name:
42    description:
43    - The name of the volume to manage.
44    required: true
45
46  infinite:
47    description:
48    - Set True if the volume is an Infinite Volume.
49    type: bool
50    default: 'no'
51
52  online:
53    description:
54    - Whether the specified volume is online, or not.
55    type: bool
56    default: 'yes'
57
58  aggregate_name:
59    description:
60    - The name of the aggregate the flexvol should exist on. Required when C(state=present).
61
62  size:
63    description:
64    - The size of the volume in (size_unit). Required when C(state=present).
65
66  size_unit:
67    description:
68    - The unit used to interpret the size parameter.
69    choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
70    default: 'gb'
71
72  vserver:
73    description:
74    - Name of the vserver to use.
75    required: true
76
77  junction_path:
78    description:
79    - Junction path where to mount the volume
80    required: false
81    version_added: '2.6'
82
83  export_policy:
84    description:
85    - Export policy to set for the specified junction path.
86    required: false
87    default: default
88    version_added: '2.6'
89
90  snapshot_policy:
91    description:
92    - Snapshot policy to set for the specified volume.
93    required: false
94    default: default
95    version_added: '2.6'
96
97'''
98
99EXAMPLES = """
100
101    - name: Create FlexVol
102      na_cdot_volume:
103        state: present
104        name: ansibleVolume
105        infinite: False
106        aggregate_name: aggr1
107        size: 20
108        size_unit: mb
109        vserver: ansibleVServer
110        hostname: "{{ netapp_hostname }}"
111        username: "{{ netapp_username }}"
112        password: "{{ netapp_password }}"
113        junction_path: /ansibleVolume
114        export_policy: all_nfs_networks
115        snapshot_policy: daily
116
117    - name: Make FlexVol offline
118      na_cdot_volume:
119        state: present
120        name: ansibleVolume
121        infinite: False
122        online: False
123        vserver: ansibleVServer
124        hostname: "{{ netapp_hostname }}"
125        username: "{{ netapp_username }}"
126        password: "{{ netapp_password }}"
127
128"""
129
130RETURN = """
131
132
133"""
134import traceback
135
136from ansible.module_utils.basic import AnsibleModule
137from ansible.module_utils._text import to_native
138import ansible.module_utils.netapp as netapp_utils
139
140
141HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
142
143
144class NetAppCDOTVolume(object):
145
146    def __init__(self):
147
148        self._size_unit_map = dict(
149            bytes=1,
150            b=1,
151            kb=1024,
152            mb=1024 ** 2,
153            gb=1024 ** 3,
154            tb=1024 ** 4,
155            pb=1024 ** 5,
156            eb=1024 ** 6,
157            zb=1024 ** 7,
158            yb=1024 ** 8
159        )
160
161        self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
162        self.argument_spec.update(dict(
163            state=dict(required=True, choices=['present', 'absent']),
164            name=dict(required=True, type='str'),
165            is_infinite=dict(required=False, type='bool', default=False, aliases=['infinite']),
166            is_online=dict(required=False, type='bool', default=True, aliases=['online']),
167            size=dict(type='int'),
168            size_unit=dict(default='gb',
169                           choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb',
170                                    'pb', 'eb', 'zb', 'yb'], type='str'),
171            aggregate_name=dict(type='str'),
172            vserver=dict(required=True, type='str', default=None),
173            junction_path=dict(required=False, type='str', default=None),
174            export_policy=dict(required=False, type='str', default='default'),
175            snapshot_policy=dict(required=False, type='str', default='default'),
176        ))
177
178        self.module = AnsibleModule(
179            argument_spec=self.argument_spec,
180            required_if=[
181                ('state', 'present', ['aggregate_name', 'size'])
182            ],
183            supports_check_mode=True
184        )
185
186        p = self.module.params
187
188        # set up state variables
189        self.state = p['state']
190        self.name = p['name']
191        self.is_infinite = p['is_infinite']
192        self.is_online = p['is_online']
193        self.size_unit = p['size_unit']
194        self.vserver = p['vserver']
195        self.junction_path = p['junction_path']
196        self.export_policy = p['export_policy']
197        self.snapshot_policy = p['snapshot_policy']
198
199        if p['size'] is not None:
200            self.size = p['size'] * self._size_unit_map[self.size_unit]
201        else:
202            self.size = None
203        self.aggregate_name = p['aggregate_name']
204
205        if HAS_NETAPP_LIB is False:
206            self.module.fail_json(msg="the python NetApp-Lib module is required")
207        else:
208            self.server = netapp_utils.setup_ontap_zapi(module=self.module, vserver=self.vserver)
209
210    def get_volume(self):
211        """
212        Return details about the volume
213        :param:
214            name : Name of the volume
215
216        :return: Details about the volume. None if not found.
217        :rtype: dict
218        """
219        volume_info = netapp_utils.zapi.NaElement('volume-get-iter')
220        volume_attributes = netapp_utils.zapi.NaElement('volume-attributes')
221        volume_id_attributes = netapp_utils.zapi.NaElement('volume-id-attributes')
222        volume_id_attributes.add_new_child('name', self.name)
223        volume_attributes.add_child_elem(volume_id_attributes)
224
225        query = netapp_utils.zapi.NaElement('query')
226        query.add_child_elem(volume_attributes)
227
228        volume_info.add_child_elem(query)
229
230        result = self.server.invoke_successfully(volume_info, True)
231
232        return_value = None
233
234        if result.get_child_by_name('num-records') and \
235                int(result.get_child_content('num-records')) >= 1:
236
237            volume_attributes = result.get_child_by_name(
238                'attributes-list').get_child_by_name(
239                'volume-attributes')
240            # Get volume's current size
241            volume_space_attributes = volume_attributes.get_child_by_name(
242                'volume-space-attributes')
243            current_size = volume_space_attributes.get_child_content('size')
244
245            # Get volume's state (online/offline)
246            volume_state_attributes = volume_attributes.get_child_by_name(
247                'volume-state-attributes')
248            current_state = volume_state_attributes.get_child_content('state')
249            is_online = None
250            if current_state == "online":
251                is_online = True
252            elif current_state == "offline":
253                is_online = False
254            return_value = {
255                'name': self.name,
256                'size': current_size,
257                'is_online': is_online,
258            }
259
260        return return_value
261
262    def create_volume(self):
263        create_parameters = {'volume': self.name,
264                             'containing-aggr-name': self.aggregate_name,
265                             'size': str(self.size),
266                             }
267        if self.junction_path:
268            create_parameters['junction-path'] = str(self.junction_path)
269        if self.export_policy != 'default':
270            create_parameters['export-policy'] = str(self.export_policy)
271        if self.snapshot_policy != 'default':
272            create_parameters['snapshot-policy'] = str(self.snapshot_policy)
273
274        volume_create = netapp_utils.zapi.NaElement.create_node_with_children(
275            'volume-create', **create_parameters)
276
277        try:
278            self.server.invoke_successfully(volume_create,
279                                            enable_tunneling=True)
280        except netapp_utils.zapi.NaApiError as e:
281            self.module.fail_json(msg='Error provisioning volume %s of size %s: %s' % (self.name, self.size, to_native(e)),
282                                  exception=traceback.format_exc())
283
284    def delete_volume(self):
285        if self.is_infinite:
286            volume_delete = netapp_utils.zapi.NaElement.create_node_with_children(
287                'volume-destroy-async', **{'volume-name': self.name})
288        else:
289            volume_delete = netapp_utils.zapi.NaElement.create_node_with_children(
290                'volume-destroy', **{'name': self.name, 'unmount-and-offline':
291                                     'true'})
292
293        try:
294            self.server.invoke_successfully(volume_delete,
295                                            enable_tunneling=True)
296        except netapp_utils.zapi.NaApiError as e:
297            self.module.fail_json(msg='Error deleting volume %s: %s' % (self.name, to_native(e)),
298                                  exception=traceback.format_exc())
299
300    def rename_volume(self):
301        """
302        Rename the volume.
303
304        Note: 'is_infinite' needs to be set to True in order to rename an
305        Infinite Volume.
306        """
307        if self.is_infinite:
308            volume_rename = netapp_utils.zapi.NaElement.create_node_with_children(
309                'volume-rename-async',
310                **{'volume-name': self.name, 'new-volume-name': str(
311                    self.name)})
312        else:
313            volume_rename = netapp_utils.zapi.NaElement.create_node_with_children(
314                'volume-rename', **{'volume': self.name, 'new-volume-name': str(
315                    self.name)})
316        try:
317            self.server.invoke_successfully(volume_rename,
318                                            enable_tunneling=True)
319        except netapp_utils.zapi.NaApiError as e:
320            self.module.fail_json(msg='Error renaming volume %s: %s' % (self.name, to_native(e)),
321                                  exception=traceback.format_exc())
322
323    def resize_volume(self):
324        """
325        Re-size the volume.
326
327        Note: 'is_infinite' needs to be set to True in order to rename an
328        Infinite Volume.
329        """
330        if self.is_infinite:
331            volume_resize = netapp_utils.zapi.NaElement.create_node_with_children(
332                'volume-size-async',
333                **{'volume-name': self.name, 'new-size': str(
334                    self.size)})
335        else:
336            volume_resize = netapp_utils.zapi.NaElement.create_node_with_children(
337                'volume-size', **{'volume': self.name, 'new-size': str(
338                    self.size)})
339        try:
340            self.server.invoke_successfully(volume_resize,
341                                            enable_tunneling=True)
342        except netapp_utils.zapi.NaApiError as e:
343            self.module.fail_json(msg='Error re-sizing volume %s: %s' % (self.name, to_native(e)),
344                                  exception=traceback.format_exc())
345
346    def change_volume_state(self):
347        """
348        Change volume's state (offline/online).
349
350        Note: 'is_infinite' needs to be set to True in order to change the
351        state of an Infinite Volume.
352        """
353        state_requested = None
354        if self.is_online:
355            # Requested state is 'online'.
356            state_requested = "online"
357            if self.is_infinite:
358                volume_change_state = netapp_utils.zapi.NaElement.create_node_with_children(
359                    'volume-online-async',
360                    **{'volume-name': self.name})
361            else:
362                volume_change_state = netapp_utils.zapi.NaElement.create_node_with_children(
363                    'volume-online',
364                    **{'name': self.name})
365        else:
366            # Requested state is 'offline'.
367            state_requested = "offline"
368            if self.is_infinite:
369                volume_change_state = netapp_utils.zapi.NaElement.create_node_with_children(
370                    'volume-offline-async',
371                    **{'volume-name': self.name})
372            else:
373                volume_change_state = netapp_utils.zapi.NaElement.create_node_with_children(
374                    'volume-offline',
375                    **{'name': self.name})
376        try:
377            self.server.invoke_successfully(volume_change_state,
378                                            enable_tunneling=True)
379        except netapp_utils.zapi.NaApiError as e:
380            self.module.fail_json(msg='Error changing the state of volume %s to %s: %s' %
381                                  (self.name, state_requested, to_native(e)),
382                                  exception=traceback.format_exc())
383
384    def apply(self):
385        changed = False
386        volume_exists = False
387        rename_volume = False
388        resize_volume = False
389        volume_detail = self.get_volume()
390
391        if volume_detail:
392            volume_exists = True
393
394            if self.state == 'absent':
395                changed = True
396
397            elif self.state == 'present':
398                if str(volume_detail['size']) != str(self.size):
399                    resize_volume = True
400                    changed = True
401                if (volume_detail['is_online'] is not None) and (volume_detail['is_online'] != self.is_online):
402                    changed = True
403                    if self.is_online is False:
404                        # Volume is online, but requested state is offline
405                        pass
406                    else:
407                        # Volume is offline but requested state is online
408                        pass
409
410        else:
411            if self.state == 'present':
412                changed = True
413
414        if changed:
415            if self.module.check_mode:
416                pass
417            else:
418                if self.state == 'present':
419                    if not volume_exists:
420                        self.create_volume()
421
422                    else:
423                        if resize_volume:
424                            self.resize_volume()
425                        if volume_detail['is_online'] is not \
426                                None and volume_detail['is_online'] != \
427                                self.is_online:
428                            self.change_volume_state()
429                        # Ensure re-naming is the last change made.
430                        if rename_volume:
431                            self.rename_volume()
432
433                elif self.state == 'absent':
434                    self.delete_volume()
435
436        self.module.exit_json(changed=changed)
437
438
439def main():
440    v = NetAppCDOTVolume()
441    v.apply()
442
443
444if __name__ == '__main__':
445    main()
446