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_lun
18
19short_description: Manage  NetApp cDOT luns
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_lun) instead.
29
30description:
31- Create, destroy, resize luns on NetApp cDOT.
32
33options:
34
35  state:
36    description:
37    - Whether the specified lun should exist or not.
38    required: true
39    choices: ['present', 'absent']
40
41  name:
42    description:
43    - The name of the lun to manage.
44    required: true
45
46  flexvol_name:
47    description:
48    - The name of the FlexVol the lun should exist on.
49    - Required when C(state=present).
50
51  size:
52    description:
53    - The size of the lun in C(size_unit).
54    - Required when C(state=present).
55
56  size_unit:
57    description:
58    - The unit used to interpret the size parameter.
59    choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
60    default: 'gb'
61
62  force_resize:
63    description:
64    - Forcibly reduce the size. This is required for reducing the size of the LUN to avoid accidentally reducing the LUN size.
65    default: false
66
67  force_remove:
68    description:
69    - If "true", override checks that prevent a LUN from being destroyed if it is online and mapped.
70    - If "false", destroying an online and mapped LUN will fail.
71    default: false
72
73  force_remove_fenced:
74    description:
75    - If "true", override checks that prevent a LUN from being destroyed while it is fenced.
76    - If "false", attempting to destroy a fenced LUN will fail.
77    - The default if not specified is "false". This field is available in Data ONTAP 8.2 and later.
78    default: false
79
80  vserver:
81    required: true
82    description:
83    - The name of the vserver to use.
84
85'''
86
87EXAMPLES = """
88- name: Create LUN
89  na_cdot_lun:
90    state: present
91    name: ansibleLUN
92    flexvol_name: ansibleVolume
93    vserver: ansibleVServer
94    size: 5
95    size_unit: mb
96    hostname: "{{ netapp_hostname }}"
97    username: "{{ netapp_username }}"
98    password: "{{ netapp_password }}"
99
100- name: Resize Lun
101  na_cdot_lun:
102    state: present
103    name: ansibleLUN
104    force_resize: True
105    flexvol_name: ansibleVolume
106    vserver: ansibleVServer
107    size: 5
108    size_unit: gb
109    hostname: "{{ netapp_hostname }}"
110    username: "{{ netapp_username }}"
111    password: "{{ netapp_password }}"
112"""
113
114RETURN = """
115
116"""
117import traceback
118
119from ansible.module_utils.basic import AnsibleModule
120from ansible.module_utils._text import to_native
121import ansible.module_utils.netapp as netapp_utils
122
123HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
124
125
126class NetAppCDOTLUN(object):
127
128    def __init__(self):
129
130        self._size_unit_map = dict(
131            bytes=1,
132            b=1,
133            kb=1024,
134            mb=1024 ** 2,
135            gb=1024 ** 3,
136            tb=1024 ** 4,
137            pb=1024 ** 5,
138            eb=1024 ** 6,
139            zb=1024 ** 7,
140            yb=1024 ** 8
141        )
142
143        self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
144        self.argument_spec.update(dict(
145            state=dict(required=True, choices=['present', 'absent']),
146            name=dict(required=True, type='str'),
147            size=dict(type='int'),
148            size_unit=dict(default='gb',
149                           choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb',
150                                    'pb', 'eb', 'zb', 'yb'], type='str'),
151            force_resize=dict(default=False, type='bool'),
152            force_remove=dict(default=False, type='bool'),
153            force_remove_fenced=dict(default=False, type='bool'),
154            flexvol_name=dict(type='str'),
155            vserver=dict(required=True, type='str'),
156        ))
157
158        self.module = AnsibleModule(
159            argument_spec=self.argument_spec,
160            required_if=[
161                ('state', 'present', ['flexvol_name', 'size'])
162            ],
163            supports_check_mode=True
164        )
165
166        p = self.module.params
167
168        # set up state variables
169        self.state = p['state']
170        self.name = p['name']
171        self.size_unit = p['size_unit']
172        if p['size'] is not None:
173            self.size = p['size'] * self._size_unit_map[self.size_unit]
174        else:
175            self.size = None
176        self.force_resize = p['force_resize']
177        self.force_remove = p['force_remove']
178        self.force_remove_fenced = p['force_remove_fenced']
179        self.flexvol_name = p['flexvol_name']
180        self.vserver = p['vserver']
181
182        if HAS_NETAPP_LIB is False:
183            self.module.fail_json(msg="the python NetApp-Lib module is required")
184        else:
185            self.server = netapp_utils.setup_ontap_zapi(module=self.module, vserver=self.vserver)
186
187    def get_lun(self):
188        """
189        Return details about the LUN
190
191        :return: Details about the lun
192        :rtype: dict
193        """
194
195        luns = []
196        tag = None
197        while True:
198            lun_info = netapp_utils.zapi.NaElement('lun-get-iter')
199            if tag:
200                lun_info.add_new_child('tag', tag, True)
201
202            query_details = netapp_utils.zapi.NaElement('lun-info')
203            query_details.add_new_child('vserver', self.vserver)
204            query_details.add_new_child('volume', self.flexvol_name)
205
206            query = netapp_utils.zapi.NaElement('query')
207            query.add_child_elem(query_details)
208
209            lun_info.add_child_elem(query)
210
211            result = self.server.invoke_successfully(lun_info, True)
212            if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
213                attr_list = result.get_child_by_name('attributes-list')
214                luns.extend(attr_list.get_children())
215
216            tag = result.get_child_content('next-tag')
217
218            if tag is None:
219                break
220
221        # The LUNs have been extracted.
222        # Find the specified lun and extract details.
223        return_value = None
224        for lun in luns:
225            path = lun.get_child_content('path')
226            _rest, _splitter, found_name = path.rpartition('/')
227
228            if found_name == self.name:
229                size = lun.get_child_content('size')
230
231                # Find out if the lun is attached
232                attached_to = None
233                lun_id = None
234                if lun.get_child_content('mapped') == 'true':
235                    lun_map_list = netapp_utils.zapi.NaElement.create_node_with_children(
236                        'lun-map-list-info', **{'path': path})
237
238                    result = self.server.invoke_successfully(
239                        lun_map_list, enable_tunneling=True)
240
241                    igroups = result.get_child_by_name('initiator-groups')
242                    if igroups:
243                        for igroup_info in igroups.get_children():
244                            igroup = igroup_info.get_child_content(
245                                'initiator-group-name')
246                            attached_to = igroup
247                            lun_id = igroup_info.get_child_content('lun-id')
248
249                return_value = {
250                    'name': found_name,
251                    'size': size,
252                    'attached_to': attached_to,
253                    'lun_id': lun_id
254                }
255            else:
256                continue
257
258        return return_value
259
260    def create_lun(self):
261        """
262        Create LUN with requested name and size
263        """
264        path = '/vol/%s/%s' % (self.flexvol_name, self.name)
265        lun_create = netapp_utils.zapi.NaElement.create_node_with_children(
266            'lun-create-by-size', **{'path': path,
267                                     'size': str(self.size),
268                                     'ostype': 'linux'})
269
270        try:
271            self.server.invoke_successfully(lun_create, enable_tunneling=True)
272        except netapp_utils.zapi.NaApiError as e:
273            self.module.fail_json(msg="Error provisioning lun %s of size %s: %s" % (self.name, self.size, to_native(e)),
274                                  exception=traceback.format_exc())
275
276    def delete_lun(self):
277        """
278        Delete requested LUN
279        """
280        path = '/vol/%s/%s' % (self.flexvol_name, self.name)
281
282        lun_delete = netapp_utils.zapi.NaElement.create_node_with_children(
283            'lun-destroy', **{'path': path,
284                              'force': str(self.force_remove),
285                              'destroy-fenced-lun':
286                                  str(self.force_remove_fenced)})
287
288        try:
289            self.server.invoke_successfully(lun_delete, enable_tunneling=True)
290        except netapp_utils.zapi.NaApiError as e:
291            self.module.fail_json(msg="Error deleting lun %s: %s" % (path, to_native(e)),
292                                  exception=traceback.format_exc())
293
294    def resize_lun(self):
295        """
296        Resize requested LUN.
297
298        :return: True if LUN was actually re-sized, false otherwise.
299        :rtype: bool
300        """
301        path = '/vol/%s/%s' % (self.flexvol_name, self.name)
302
303        lun_resize = netapp_utils.zapi.NaElement.create_node_with_children(
304            'lun-resize', **{'path': path,
305                             'size': str(self.size),
306                             'force': str(self.force_resize)})
307        try:
308            self.server.invoke_successfully(lun_resize, enable_tunneling=True)
309        except netapp_utils.zapi.NaApiError as e:
310            if to_native(e.code) == "9042":
311                # Error 9042 denotes the new LUN size being the same as the
312                # old LUN size. This happens when there's barely any difference
313                # in the two sizes. For example, from 8388608 bytes to
314                # 8194304 bytes. This should go away if/when the default size
315                # requested/reported to/from the controller is changed to a
316                # larger unit (MB/GB/TB).
317                return False
318            else:
319                self.module.fail_json(msg="Error resizing lun %s: %s" % (path, to_native(e)),
320                                      exception=traceback.format_exc())
321
322        return True
323
324    def apply(self):
325        property_changed = False
326        multiple_properties_changed = False
327        size_changed = False
328        lun_exists = False
329        lun_detail = self.get_lun()
330
331        if lun_detail:
332            lun_exists = True
333            current_size = lun_detail['size']
334
335            if self.state == 'absent':
336                property_changed = True
337
338            elif self.state == 'present':
339                if not int(current_size) == self.size:
340                    size_changed = True
341                    property_changed = True
342
343        else:
344            if self.state == 'present':
345                property_changed = True
346
347        if property_changed:
348            if self.module.check_mode:
349                pass
350            else:
351                if self.state == 'present':
352                    if not lun_exists:
353                        self.create_lun()
354
355                    else:
356                        if size_changed:
357                            # Ensure that size was actually changed. Please
358                            # read notes in 'resize_lun' function for details.
359                            size_changed = self.resize_lun()
360                            if not size_changed and not \
361                                    multiple_properties_changed:
362                                property_changed = False
363
364                elif self.state == 'absent':
365                    self.delete_lun()
366
367        changed = property_changed or size_changed
368        # TODO: include other details about the lun (size, etc.)
369        self.module.exit_json(changed=changed)
370
371
372def main():
373    v = NetAppCDOTLUN()
374    v.apply()
375
376
377if __name__ == '__main__':
378    main()
379