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_policy 17short_description: NetApp ONTAP manage Snapshot Policy 18extends_documentation_fragment: 19 - netapp.na_ontap 20version_added: '2.8' 21author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com> 22description: 23- Create/Modify/Delete ONTAP snapshot policies 24options: 25 state: 26 description: 27 - If you want to create, modify or delete a snapshot policy. 28 choices: ['present', 'absent'] 29 default: present 30 name: 31 description: 32 Name of the snapshot policy to be managed. 33 The maximum string length is 256 characters. 34 required: true 35 enabled: 36 description: 37 - Status of the snapshot policy indicating whether the policy will be enabled or disabled. 38 type: bool 39 comment: 40 description: 41 A human readable comment attached with the snapshot. 42 The size of the comment can be at most 255 characters. 43 count: 44 description: 45 Retention count for the snapshots created by the schedule. 46 type: list 47 schedule: 48 description: 49 - Schedule to be added inside the policy. 50 type: list 51 snapmirror_label: 52 description: 53 - SnapMirror label assigned to each schedule inside the policy. Use an empty 54 string ('') for no label. 55 type: list 56 required: false 57 version_added: '2.9' 58 vserver: 59 description: 60 - The name of the vserver to use. In a multi-tenanted environment, assigning a 61 Snapshot Policy to a vserver will restrict its use to that vserver. 62 required: false 63 version_added: '2.9' 64''' 65EXAMPLES = """ 66 - name: Create Snapshot policy 67 na_ontap_snapshot_policy: 68 state: present 69 name: ansible2 70 schedule: hourly 71 count: 150 72 enabled: True 73 username: "{{ netapp_username }}" 74 password: "{{ netapp_password }}" 75 hostname: "{{ netapp_hostname }}" 76 https: False 77 78 - name: Create Snapshot policy with multiple schedules 79 na_ontap_snapshot_policy: 80 state: present 81 name: ansible2 82 schedule: ['hourly', 'daily', 'weekly', 'monthly', '5min'] 83 count: [1, 2, 3, 4, 5] 84 enabled: True 85 username: "{{ netapp_username }}" 86 password: "{{ netapp_password }}" 87 hostname: "{{ netapp_hostname }}" 88 https: False 89 90 - name: Create Snapshot policy owned by a vserver 91 na_ontap_snapshot_policy: 92 state: present 93 name: ansible3 94 vserver: ansible 95 schedule: ['hourly', 'daily', 'weekly', 'monthly', '5min'] 96 count: [1, 2, 3, 4, 5] 97 snapmirror_label: ['hourly', 'daily', 'weekly', 'monthly', ''] 98 enabled: True 99 username: "{{ netapp_username }}" 100 password: "{{ netapp_password }}" 101 hostname: "{{ netapp_hostname }}" 102 https: False 103 104 - name: Modify Snapshot policy with multiple schedules 105 na_ontap_snapshot_policy: 106 state: present 107 name: ansible2 108 schedule: ['daily', 'weekly'] 109 count: [20, 30] 110 snapmirror_label: ['daily', 'weekly'] 111 enabled: True 112 username: "{{ netapp_username }}" 113 password: "{{ netapp_password }}" 114 hostname: "{{ netapp_hostname }}" 115 https: False 116 117 - name: Delete Snapshot policy 118 na_ontap_snapshot_policy: 119 state: absent 120 name: ansible2 121 username: "{{ netapp_username }}" 122 password: "{{ netapp_password }}" 123 hostname: "{{ netapp_hostname }}" 124 https: False 125""" 126 127RETURN = """ 128""" 129import traceback 130 131from ansible.module_utils.basic import AnsibleModule 132from ansible.module_utils.netapp_module import NetAppModule 133from ansible.module_utils._text import to_native 134import ansible.module_utils.netapp as netapp_utils 135 136HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() 137 138 139class NetAppOntapSnapshotPolicy(object): 140 """ 141 Creates and deletes a Snapshot Policy 142 """ 143 144 def __init__(self): 145 self.argument_spec = netapp_utils.na_ontap_host_argument_spec() 146 self.argument_spec.update(dict( 147 state=dict(required=False, choices=[ 148 'present', 'absent'], default='present'), 149 name=dict(required=True, type="str"), 150 enabled=dict(required=False, type="bool"), 151 # count is a list of integers 152 count=dict(required=False, type="list", elements="int"), 153 comment=dict(required=False, type="str"), 154 schedule=dict(required=False, type="list", elements="str"), 155 snapmirror_label=dict(required=False, type="list", elements="str"), 156 vserver=dict(required=False, type="str") 157 )) 158 self.module = AnsibleModule( 159 argument_spec=self.argument_spec, 160 required_if=[ 161 ('state', 'present', ['enabled', 'count', 'schedule']), 162 ], 163 supports_check_mode=True 164 ) 165 166 self.na_helper = NetAppModule() 167 self.parameters = self.na_helper.set_parameters(self.module.params) 168 169 if HAS_NETAPP_LIB is False: 170 self.module.fail_json( 171 msg="the python NetApp-Lib module is required") 172 else: 173 if 'vserver' in self.parameters: 174 self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) 175 else: 176 self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) 177 return 178 179 def get_snapshot_policy(self): 180 """ 181 Checks to see if a snapshot policy exists or not 182 :return: Return policy details if a snapshot policy exists, None if it doesn't 183 """ 184 snapshot_obj = netapp_utils.zapi.NaElement("snapshot-policy-get-iter") 185 # compose query 186 query = netapp_utils.zapi.NaElement("query") 187 snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-policy-info") 188 snapshot_info_obj.add_new_child("policy", self.parameters['name']) 189 if 'vserver' in self.parameters: 190 snapshot_info_obj.add_new_child("vserver-name", self.parameters['vserver']) 191 query.add_child_elem(snapshot_info_obj) 192 snapshot_obj.add_child_elem(query) 193 try: 194 result = self.server.invoke_successfully(snapshot_obj, True) 195 if result.get_child_by_name('num-records') and \ 196 int(result.get_child_content('num-records')) == 1: 197 snapshot_policy = result.get_child_by_name('attributes-list').get_child_by_name('snapshot-policy-info') 198 current = {} 199 current['name'] = snapshot_policy.get_child_content('policy') 200 current['vserver'] = snapshot_policy.get_child_content('vserver-name') 201 current['enabled'] = False if snapshot_policy.get_child_content('enabled').lower() == 'false' else True 202 current['comment'] = snapshot_policy.get_child_content('comment') or '' 203 current['schedule'], current['count'], current['snapmirror_label'] = [], [], [] 204 if snapshot_policy.get_child_by_name('snapshot-policy-schedules'): 205 for schedule in snapshot_policy['snapshot-policy-schedules'].get_children(): 206 current['schedule'].append(schedule.get_child_content('schedule')) 207 current['count'].append(int(schedule.get_child_content('count'))) 208 snapmirror_label = schedule.get_child_content('snapmirror-label') 209 if snapmirror_label is None or snapmirror_label == '-': 210 snapmirror_label = '' 211 current['snapmirror_label'].append(snapmirror_label) 212 return current 213 except netapp_utils.zapi.NaApiError as error: 214 self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) 215 return None 216 217 def validate_parameters(self): 218 """ 219 Validate if each schedule has a count associated 220 :return: None 221 """ 222 if 'count' not in self.parameters or 'schedule' not in self.parameters or \ 223 len(self.parameters['count']) > 5 or len(self.parameters['schedule']) > 5 or \ 224 len(self.parameters['count']) < 1 or len(self.parameters['schedule']) < 1 or \ 225 len(self.parameters['count']) != len(self.parameters['schedule']): 226 self.module.fail_json(msg="Error: A Snapshot policy must have at least 1 " 227 "schedule and can have up to a maximum of 5 schedules, with a count " 228 "representing the maximum number of Snapshot copies for each schedule") 229 230 if 'snapmirror_label' in self.parameters: 231 if len(self.parameters['snapmirror_label']) != len(self.parameters['schedule']): 232 self.module.fail_json(msg="Error: Each Snapshot Policy schedule must have an " 233 "accompanying SnapMirror Label") 234 235 def modify_snapshot_policy(self, current): 236 """ 237 Modifies an existing snapshot policy 238 """ 239 # Set up required variables to modify snapshot policy 240 options = {'policy': self.parameters['name']} 241 modify = False 242 243 # Set up optional variables to modify snapshot policy 244 if 'enabled' in self.parameters and self.parameters['enabled'] != current['enabled']: 245 options['enabled'] = str(self.parameters['enabled']) 246 modify = True 247 if 'comment' in self.parameters and self.parameters['comment'] != current['comment']: 248 options['comment'] = self.parameters['comment'] 249 modify = True 250 251 if modify: 252 snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children('snapshot-policy-modify', **options) 253 try: 254 self.server.invoke_successfully(snapshot_obj, True) 255 except netapp_utils.zapi.NaApiError as error: 256 self.module.fail_json(msg='Error modifying snapshot policy %s: %s' % 257 (self.parameters['name'], to_native(error)), 258 exception=traceback.format_exc()) 259 260 def modify_snapshot_policy_schedules(self, current): 261 """ 262 Modify existing schedules in snapshot policy 263 :return: None 264 """ 265 self.validate_parameters() 266 267 delete_schedules, modify_schedules, add_schedules = [], [], [] 268 269 if 'snapmirror_label' in self.parameters: 270 snapmirror_labels = self.parameters['snapmirror_label'] 271 else: 272 # User hasn't supplied any snapmirror labels. 273 snapmirror_labels = [None] * len(self.parameters['schedule']) 274 275 # Identify schedules for deletion 276 for schedule in current['schedule']: 277 schedule = schedule.strip() 278 if schedule not in [item.strip() for item in self.parameters['schedule']]: 279 options = {'policy': current['name'], 280 'schedule': schedule} 281 delete_schedules.append(options) 282 283 # Identify schedules to be modified or added 284 for schedule, count, snapmirror_label in zip(self.parameters['schedule'], self.parameters['count'], snapmirror_labels): 285 schedule = schedule.strip() 286 if snapmirror_label is not None: 287 snapmirror_label = snapmirror_label.strip() 288 289 options = {'policy': current['name'], 290 'schedule': schedule} 291 292 if schedule in current['schedule']: 293 # Schedule exists. Only modify if it has changed. 294 modify = False 295 schedule_index = current['schedule'].index(schedule) 296 297 if count != current['count'][schedule_index]: 298 options['new-count'] = str(count) 299 modify = True 300 301 if snapmirror_label is not None: 302 if snapmirror_label != current['snapmirror_label'][schedule_index]: 303 options['new-snapmirror-label'] = snapmirror_label 304 modify = True 305 306 if modify: 307 modify_schedules.append(options) 308 else: 309 # New schedule 310 options['count'] = str(count) 311 if snapmirror_label is not None and snapmirror_label != '': 312 options['snapmirror-label'] = snapmirror_label 313 add_schedules.append(options) 314 315 # Delete N-1 schedules no longer required. Must leave 1 schedule in policy 316 # at any one time. Delete last one afterwards. 317 while len(delete_schedules) > 1: 318 options = delete_schedules.pop() 319 self.modify_snapshot_policy_schedule(options, 'snapshot-policy-remove-schedule') 320 321 # Modify schedules. 322 while len(modify_schedules) > 0: 323 options = modify_schedules.pop() 324 self.modify_snapshot_policy_schedule(options, 'snapshot-policy-modify-schedule') 325 326 # Add N-1 new schedules. Add last one after last schedule has been deleted. 327 while len(add_schedules) > 1: 328 options = add_schedules.pop() 329 self.modify_snapshot_policy_schedule(options, 'snapshot-policy-add-schedule') 330 331 # Delete last schedule no longer required. 332 while len(delete_schedules) > 0: 333 options = delete_schedules.pop() 334 self.modify_snapshot_policy_schedule(options, 'snapshot-policy-remove-schedule') 335 336 # Add last new schedule. 337 while len(add_schedules) > 0: 338 options = add_schedules.pop() 339 self.modify_snapshot_policy_schedule(options, 'snapshot-policy-add-schedule') 340 341 def modify_snapshot_policy_schedule(self, options, zapi): 342 """ 343 Add, modify or remove a schedule to/from a snapshot policy 344 """ 345 snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options) 346 try: 347 self.server.invoke_successfully(snapshot_obj, enable_tunneling=True) 348 except netapp_utils.zapi.NaApiError as error: 349 self.module.fail_json(msg='Error modifying snapshot policy schedule %s: %s' % 350 (self.parameters['name'], to_native(error)), 351 exception=traceback.format_exc()) 352 353 def create_snapshot_policy(self): 354 """ 355 Creates a new snapshot policy 356 """ 357 # set up required variables to create a snapshot policy 358 self.validate_parameters() 359 options = {'policy': self.parameters['name'], 360 'enabled': str(self.parameters['enabled']), 361 } 362 363 if 'snapmirror_label' in self.parameters: 364 snapmirror_labels = self.parameters['snapmirror_label'] 365 else: 366 # User hasn't supplied any snapmirror labels. 367 snapmirror_labels = [None] * len(self.parameters['schedule']) 368 369 # zapi attribute for first schedule is schedule1, second is schedule2 and so on 370 positions = [str(i) for i in range(1, len(self.parameters['schedule']) + 1)] 371 for schedule, count, snapmirror_label, position in zip(self.parameters['schedule'], self.parameters['count'], snapmirror_labels, positions): 372 schedule = schedule.strip() 373 options['count' + position] = str(count) 374 options['schedule' + position] = schedule 375 if snapmirror_label is not None: 376 snapmirror_label = snapmirror_label.strip() 377 if snapmirror_label != '': 378 options['snapmirror-label' + position] = snapmirror_label 379 snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children('snapshot-policy-create', **options) 380 381 # Set up optional variables to create a snapshot policy 382 if self.parameters.get('comment'): 383 snapshot_obj.add_new_child("comment", self.parameters['comment']) 384 try: 385 self.server.invoke_successfully(snapshot_obj, True) 386 except netapp_utils.zapi.NaApiError as error: 387 self.module.fail_json(msg='Error creating snapshot policy %s: %s' % 388 (self.parameters['name'], to_native(error)), 389 exception=traceback.format_exc()) 390 391 def delete_snapshot_policy(self): 392 """ 393 Deletes an existing snapshot policy 394 """ 395 snapshot_obj = netapp_utils.zapi.NaElement("snapshot-policy-delete") 396 397 # Set up required variables to delete a snapshot policy 398 snapshot_obj.add_new_child("policy", self.parameters['name']) 399 try: 400 self.server.invoke_successfully(snapshot_obj, True) 401 except netapp_utils.zapi.NaApiError as error: 402 self.module.fail_json(msg='Error deleting snapshot policy %s: %s' % 403 (self.parameters['name'], to_native(error)), 404 exception=traceback.format_exc()) 405 406 def asup_log_for_cserver(self, event_name): 407 """ 408 Fetch admin vserver for the given cluster 409 Create and Autosupport log event with the given module name 410 :param event_name: Name of the event log 411 :return: None 412 """ 413 results = netapp_utils.get_cserver(self.server) 414 cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) 415 netapp_utils.ems_log_event(event_name, cserver) 416 417 def apply(self): 418 """ 419 Check to see which play we should run 420 """ 421 self.asup_log_for_cserver("na_ontap_snapshot_policy") 422 current = self.get_snapshot_policy() 423 modify = None 424 cd_action = self.na_helper.get_cd_action(current, self.parameters) 425 if cd_action is None and self.parameters['state'] == 'present': 426 # Don't sort schedule/count/snapmirror_label lists as it can 427 # mess up the intended parameter order. 428 modify = self.na_helper.get_modified_attributes(current, self.parameters) 429 430 if self.na_helper.changed: 431 if self.module.check_mode: 432 pass 433 else: 434 if cd_action == 'create': 435 self.create_snapshot_policy() 436 elif cd_action == 'delete': 437 self.delete_snapshot_policy() 438 if modify: 439 self.modify_snapshot_policy(current) 440 self.modify_snapshot_policy_schedules(current) 441 self.module.exit_json(changed=self.na_helper.changed) 442 443 444def main(): 445 """ 446 Creates and deletes a Snapshot Policy 447 """ 448 obj = NetAppOntapSnapshotPolicy() 449 obj.apply() 450 451 452if __name__ == '__main__': 453 main() 454