1#!/usr/bin/python 2# (c) 2017, NetApp, Inc 3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 4 5"""Element SW Software Snapshot Schedule""" 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': 'certified'} 14 15 16DOCUMENTATION = ''' 17 18module: na_elementsw_snapshot_schedule 19 20short_description: NetApp Element Software Snapshot Schedules 21extends_documentation_fragment: 22 - netapp.solidfire 23version_added: '2.7' 24author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com> 25description: 26- Create, destroy, or update accounts on ElementSW 27 28options: 29 30 state: 31 description: 32 - Whether the specified schedule should exist or not. 33 required: true 34 choices: ['present', 'absent'] 35 36 paused: 37 description: 38 - Pause / Resume a schedule. 39 type: bool 40 41 recurring: 42 description: 43 - Should the schedule recur? 44 type: bool 45 46 schedule_type: 47 description: 48 - Schedule type for creating schedule. 49 choices: ['DaysOfWeekFrequency','DaysOfMonthFrequency','TimeIntervalFrequency'] 50 51 time_interval_days: 52 description: Time interval in days. 53 default: 1 54 55 time_interval_hours: 56 description: Time interval in hours. 57 default: 0 58 59 time_interval_minutes: 60 description: Time interval in minutes. 61 default: 0 62 63 days_of_week_weekdays: 64 description: List of days of the week (Sunday to Saturday) 65 66 days_of_week_hours: 67 description: Time specified in hours 68 default: 0 69 70 days_of_week_minutes: 71 description: Time specified in minutes. 72 default: 0 73 74 days_of_month_monthdays: 75 description: List of days of the month (1-31) 76 77 days_of_month_hours: 78 description: Time specified in hours 79 default: 0 80 81 days_of_month_minutes: 82 description: Time specified in minutes. 83 default: 0 84 85 name: 86 description: 87 - Name for the snapshot schedule. 88 - It accepts either schedule_id or schedule_name 89 - if name is digit, it will consider as schedule_id 90 - If name is string, it will consider as schedule_name 91 92 snapshot_name: 93 description: 94 - Name for the created snapshots. 95 96 volumes: 97 description: 98 - Volume IDs that you want to set the snapshot schedule for. 99 - It accepts both volume_name and volume_id 100 101 account_id: 102 description: 103 - Account ID for the owner of this volume. 104 - It accepts either account_name or account_id 105 - if account_id is digit, it will consider as account_id 106 - If account_id is string, it will consider as account_name 107 108 retention: 109 description: 110 - Retention period for the snapshot. 111 - Format is 'HH:mm:ss'. 112 113 starting_date: 114 description: 115 - Starting date for the schedule. 116 - Required when C(state=present). 117 - "Format: C(2016-12-01T00:00:00Z)" 118 119 120 password: 121 description: 122 - Element SW access account password 123 aliases: 124 - pass 125 126 username: 127 description: 128 - Element SW access account user-name 129 aliases: 130 - user 131''' 132 133EXAMPLES = """ 134 - name: Create Snapshot schedule 135 na_elementsw_snapshot_schedule: 136 hostname: "{{ elementsw_hostname }}" 137 username: "{{ elementsw_username }}" 138 password: "{{ elementsw_password }}" 139 state: present 140 name: Schedule_A 141 schedule_type: TimeIntervalFrequency 142 time_interval_days: 1 143 starting_date: '2016-12-01T00:00:00Z' 144 volumes: 145 - 7 146 - test 147 account_id: 1 148 149 - name: Update Snapshot schedule 150 na_elementsw_snapshot_schedule: 151 hostname: "{{ elementsw_hostname }}" 152 username: "{{ elementsw_username }}" 153 password: "{{ elementsw_password }}" 154 state: present 155 name: Schedule_A 156 schedule_type: TimeIntervalFrequency 157 time_interval_days: 1 158 starting_date: '2016-12-01T00:00:00Z' 159 volumes: 160 - 8 161 - test1 162 account_id: 1 163 164 - name: Delete Snapshot schedule 165 na_elementsw_snapshot_schedule: 166 hostname: "{{ elementsw_hostname }}" 167 username: "{{ elementsw_username }}" 168 password: "{{ elementsw_password }}" 169 state: absent 170 name: 6 171""" 172 173RETURN = """ 174 175schedule_id: 176 description: Schedule ID of the newly created schedule 177 returned: success 178 type: str 179""" 180import traceback 181from ansible.module_utils.basic import AnsibleModule 182from ansible.module_utils._text import to_native 183import ansible.module_utils.netapp as netapp_utils 184from ansible.module_utils.netapp_elementsw_module import NaElementSWModule 185 186HAS_SF_SDK = netapp_utils.has_sf_sdk() 187try: 188 from solidfire.custom.models import DaysOfWeekFrequency, Weekday, DaysOfMonthFrequency 189 from solidfire.common import ApiServerError 190except Exception: 191 HAS_SF_SDK = False 192 193 194class ElementSWSnapShotSchedule(object): 195 """ 196 Contains methods to parse arguments, 197 derive details of ElementSW objects 198 and send requests to ElementSW via 199 the ElementSW SDK 200 """ 201 202 def __init__(self): 203 """ 204 Parse arguments, setup state variables, 205 check parameters and ensure SDK is installed 206 """ 207 self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() 208 self.argument_spec.update(dict( 209 state=dict(required=True, choices=['present', 'absent']), 210 name=dict(required=True, type='str'), 211 schedule_type=dict(required=False, choices=['DaysOfWeekFrequency', 'DaysOfMonthFrequency', 'TimeIntervalFrequency']), 212 213 time_interval_days=dict(required=False, type='int', default=1), 214 time_interval_hours=dict(required=False, type='int', default=0), 215 time_interval_minutes=dict(required=False, type='int', default=0), 216 217 days_of_week_weekdays=dict(required=False, type='list'), 218 days_of_week_hours=dict(required=False, type='int', default=0), 219 days_of_week_minutes=dict(required=False, type='int', default=0), 220 221 days_of_month_monthdays=dict(required=False, type='list'), 222 days_of_month_hours=dict(required=False, type='int', default=0), 223 days_of_month_minutes=dict(required=False, type='int', default=0), 224 225 paused=dict(required=False, type='bool'), 226 recurring=dict(required=False, type='bool'), 227 228 starting_date=dict(required=False, type='str'), 229 230 snapshot_name=dict(required=False, type='str'), 231 volumes=dict(required=False, type='list'), 232 account_id=dict(required=False, type='str'), 233 retention=dict(required=False, type='str'), 234 )) 235 236 self.module = AnsibleModule( 237 argument_spec=self.argument_spec, 238 required_if=[ 239 ('state', 'present', ['account_id', 'volumes', 'schedule_type']), 240 ('schedule_type', 'DaysOfMonthFrequency', ['days_of_month_monthdays']), 241 ('schedule_type', 'DaysOfWeekFrequency', ['days_of_week_weekdays']) 242 243 ], 244 supports_check_mode=True 245 ) 246 247 param = self.module.params 248 249 # set up state variables 250 self.state = param['state'] 251 self.name = param['name'] 252 self.schedule_type = param['schedule_type'] 253 self.days_of_week_weekdays = param['days_of_week_weekdays'] 254 self.days_of_week_hours = param['days_of_week_hours'] 255 self.days_of_week_minutes = param['days_of_week_minutes'] 256 self.days_of_month_monthdays = param['days_of_month_monthdays'] 257 self.days_of_month_hours = param['days_of_month_hours'] 258 self.days_of_month_minutes = param['days_of_month_minutes'] 259 self.time_interval_days = param['time_interval_days'] 260 self.time_interval_hours = param['time_interval_hours'] 261 self.time_interval_minutes = param['time_interval_minutes'] 262 self.paused = param['paused'] 263 self.recurring = param['recurring'] 264 if self.schedule_type == 'DaysOfWeekFrequency': 265 # Create self.weekday list if self.schedule_type is days_of_week 266 if self.days_of_week_weekdays is not None: 267 # Create self.weekday list if self.schedule_type is days_of_week 268 self.weekdays = [] 269 for day in self.days_of_week_weekdays: 270 if str(day).isdigit(): 271 # If id specified, return appropriate day 272 self.weekdays.append(Weekday.from_id(int(day))) 273 else: 274 # If name specified, return appropriate day 275 self.weekdays.append(Weekday.from_name(day.capitalize())) 276 277 if self.state == 'present' and self.schedule_type is None: 278 # Mandate schedule_type for create operation 279 self.module.fail_json( 280 msg="Please provide required parameter: schedule_type") 281 282 # Mandate schedule name for delete operation 283 if self.state == 'absent' and self.name is None: 284 self.module.fail_json( 285 msg="Please provide required parameter: name") 286 287 self.starting_date = param['starting_date'] 288 self.snapshot_name = param['snapshot_name'] 289 self.volumes = param['volumes'] 290 self.account_id = param['account_id'] 291 self.retention = param['retention'] 292 self.create_schedule_result = None 293 294 if HAS_SF_SDK is False: 295 # Create ElementSW connection 296 self.module.fail_json(msg="Unable to import the ElementSW Python SDK") 297 else: 298 self.sfe = netapp_utils.create_sf_connection(module=self.module) 299 self.elementsw_helper = NaElementSWModule(self.sfe) 300 301 def get_schedule(self): 302 # Checking whether schedule id is exist or not 303 # Return schedule details if found, None otherwise 304 # If exist set variable self.name 305 try: 306 schedule_list = self.sfe.list_schedules() 307 except ApiServerError: 308 return None 309 310 for schedule in schedule_list.schedules: 311 if str(schedule.schedule_id) == self.name: 312 self.name = schedule.name 313 return schedule 314 elif schedule.name == self.name: 315 return schedule 316 return None 317 318 def get_account_id(self): 319 # Validate account id 320 # Return account_id if found, None otherwise 321 try: 322 account_id = self.elementsw_helper.account_exists(self.account_id) 323 return account_id 324 except ApiServerError: 325 return None 326 327 def get_volume_id(self): 328 # Validate volume_ids 329 # Return volume ids if found, fail if not found 330 volume_ids = [] 331 for volume in self.volumes: 332 volume_id = self.elementsw_helper.volume_exists(volume.strip(), self.account_id) 333 if volume_id: 334 volume_ids.append(volume_id) 335 else: 336 self.module.fail_json(msg='Specified volume %s does not exist' % volume) 337 return volume_ids 338 339 def get_frequency(self): 340 # Configuring frequency depends on self.schedule_type 341 frequency = None 342 if self.schedule_type is not None and self.schedule_type == 'DaysOfWeekFrequency': 343 if self.weekdays is not None: 344 frequency = DaysOfWeekFrequency(weekdays=self.weekdays, 345 hours=self.days_of_week_hours, 346 minutes=self.days_of_week_minutes) 347 elif self.schedule_type is not None and self.schedule_type == 'DaysOfMonthFrequency': 348 if self.days_of_month_monthdays is not None: 349 frequency = DaysOfMonthFrequency(monthdays=self.days_of_month_monthdays, 350 hours=self.days_of_month_hours, 351 minutes=self.days_of_month_minutes) 352 elif self.schedule_type is not None and self.schedule_type == 'TimeIntervalFrequency': 353 frequency = netapp_utils.TimeIntervalFrequency(days=self.time_interval_days, 354 hours=self.time_interval_hours, 355 minutes=self.time_interval_minutes) 356 return frequency 357 358 def is_same_schedule_type(self, schedule_detail): 359 # To check schedule type is same or not 360 if str(schedule_detail.frequency).split('(')[0] == self.schedule_type: 361 return True 362 else: 363 return False 364 365 def create_schedule(self): 366 # Create schedule 367 try: 368 frequency = self.get_frequency() 369 if frequency is None: 370 self.module.fail_json(msg='Failed to create schedule frequency object - type %s parameters' % self.schedule_type) 371 372 # Create schedule 373 name = self.name 374 schedule_info = netapp_utils.ScheduleInfo( 375 volume_ids=self.volumes, 376 snapshot_name=self.snapshot_name, 377 retention=self.retention 378 ) 379 380 sched = netapp_utils.Schedule(schedule_info, name, frequency) 381 sched.paused = self.paused 382 sched.recurring = self.recurring 383 sched.starting_date = self.starting_date 384 385 self.create_schedule_result = self.sfe.create_schedule(sched) 386 387 except Exception as e: 388 self.module.fail_json(msg='Error creating schedule %s: %s' % (self.name, to_native(e.message)), 389 exception=traceback.format_exc()) 390 391 def delete_schedule(self, schedule_id): 392 # delete schedule 393 try: 394 get_schedule_result = self.sfe.get_schedule(schedule_id=schedule_id) 395 sched = get_schedule_result.schedule 396 sched.to_be_deleted = True 397 self.sfe.modify_schedule(schedule=sched) 398 399 except Exception as e: 400 self.module.fail_json(msg='Error deleting schedule %s: %s' % (self.name, to_native(e.message)), 401 exception=traceback.format_exc()) 402 403 def update_schedule(self, schedule_id): 404 # Update schedule 405 try: 406 get_schedule_result = self.sfe.get_schedule(schedule_id=schedule_id) 407 sched = get_schedule_result.schedule 408 # Update schedule properties 409 sched.frequency = self.get_frequency() 410 if sched.frequency is None: 411 self.module.fail_json(msg='Failed to create schedule frequency object - type %s parameters' % self.schedule_type) 412 413 if self.volumes is not None and len(self.volumes) > 0: 414 sched.schedule_info.volume_ids = self.volumes 415 if self.retention is not None: 416 sched.schedule_info.retention = self.retention 417 if self.snapshot_name is not None: 418 sched.schedule_info.snapshot_name = self.snapshot_name 419 if self.paused is not None: 420 sched.paused = self.paused 421 if self.recurring is not None: 422 sched.recurring = self.recurring 423 if self.starting_date is not None: 424 sched.starting_date = self.starting_date 425 426 # Make API call 427 self.sfe.modify_schedule(schedule=sched) 428 429 except Exception as e: 430 self.module.fail_json(msg='Error updating schedule %s: %s' % (self.name, to_native(e.message)), 431 exception=traceback.format_exc()) 432 433 def apply(self): 434 # Perform pre-checks, call functions and exit 435 436 changed = False 437 update_schedule = False 438 439 if self.account_id is not None: 440 self.account_id = self.get_account_id() 441 442 if self.state == 'present' and self.volumes is not None: 443 if self.account_id: 444 self.volumes = self.get_volume_id() 445 else: 446 self.module.fail_json(msg='Specified account id does not exist') 447 448 # Getting the schedule details 449 schedule_detail = self.get_schedule() 450 451 if schedule_detail is None and self.state == 'present': 452 if len(self.volumes) > 0: 453 changed = True 454 else: 455 self.module.fail_json(msg='Specified volumes not on cluster') 456 elif schedule_detail is not None: 457 # Getting the schedule id 458 if self.state == 'absent': 459 changed = True 460 else: 461 # Check if we need to update the account 462 if self.retention is not None and schedule_detail.schedule_info.retention != self.retention: 463 update_schedule = True 464 changed = True 465 elif self.snapshot_name is not None and schedule_detail.schedule_info.snapshot_name != self.snapshot_name: 466 update_schedule = True 467 changed = True 468 elif self.paused is not None and schedule_detail.paused != self.paused: 469 update_schedule = True 470 changed = True 471 elif self.recurring is not None and schedule_detail.recurring != self.recurring: 472 update_schedule = True 473 changed = True 474 elif self.starting_date is not None and schedule_detail.starting_date != self.starting_date: 475 update_schedule = True 476 changed = True 477 elif self.volumes is not None and len(self.volumes) > 0: 478 for volumeID in schedule_detail.schedule_info.volume_ids: 479 if volumeID not in self.volumes: 480 update_schedule = True 481 changed = True 482 483 temp_frequency = self.get_frequency() 484 if temp_frequency is not None: 485 # Checking schedule_type changes 486 if self.is_same_schedule_type(schedule_detail): 487 # If same schedule type 488 if self.schedule_type == "TimeIntervalFrequency": 489 # Check if there is any change in schedule.frequency, If schedule_type is time_interval 490 if schedule_detail.frequency.days != temp_frequency.days or \ 491 schedule_detail.frequency.hours != temp_frequency.hours or \ 492 schedule_detail.frequency.minutes != temp_frequency.minutes: 493 update_schedule = True 494 changed = True 495 elif self.schedule_type == "DaysOfMonthFrequency": 496 # Check if there is any change in schedule.frequency, If schedule_type is days_of_month 497 if len(schedule_detail.frequency.monthdays) != len(temp_frequency.monthdays) or \ 498 schedule_detail.frequency.hours != temp_frequency.hours or \ 499 schedule_detail.frequency.minutes != temp_frequency.minutes: 500 update_schedule = True 501 changed = True 502 elif len(schedule_detail.frequency.monthdays) == len(temp_frequency.monthdays): 503 actual_frequency_monthday = schedule_detail.frequency.monthdays 504 temp_frequency_monthday = temp_frequency.monthdays 505 for monthday in actual_frequency_monthday: 506 if monthday not in temp_frequency_monthday: 507 update_schedule = True 508 changed = True 509 elif self.schedule_type == "DaysOfWeekFrequency": 510 # Check if there is any change in schedule.frequency, If schedule_type is days_of_week 511 if len(schedule_detail.frequency.weekdays) != len(temp_frequency.weekdays) or \ 512 schedule_detail.frequency.hours != temp_frequency.hours or \ 513 schedule_detail.frequency.minutes != temp_frequency.minutes: 514 update_schedule = True 515 changed = True 516 elif len(schedule_detail.frequency.weekdays) == len(temp_frequency.weekdays): 517 actual_frequency_weekdays = schedule_detail.frequency.weekdays 518 temp_frequency_weekdays = temp_frequency.weekdays 519 if len([actual_weekday for actual_weekday, temp_weekday in 520 zip(actual_frequency_weekdays, temp_frequency_weekdays) if actual_weekday != temp_weekday]) != 0: 521 update_schedule = True 522 changed = True 523 else: 524 update_schedule = True 525 changed = True 526 else: 527 self.module.fail_json(msg='Failed to create schedule frequency object - type %s parameters' % self.schedule_type) 528 529 result_message = " " 530 if changed: 531 if self.module.check_mode: 532 # Skip changes 533 result_message = "Check mode, skipping changes" 534 else: 535 if self.state == 'present': 536 if update_schedule: 537 self.update_schedule(schedule_detail.schedule_id) 538 result_message = "Snapshot Schedule modified" 539 else: 540 self.create_schedule() 541 result_message = "Snapshot Schedule created" 542 elif self.state == 'absent': 543 self.delete_schedule(schedule_detail.schedule_id) 544 result_message = "Snapshot Schedule deleted" 545 546 self.module.exit_json(changed=changed, msg=result_message) 547 548 549def main(): 550 v = ElementSWSnapShotSchedule() 551 v.apply() 552 553 554if __name__ == '__main__': 555 main() 556