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