class ElementOSVolume(object): """ Contains methods to parse arguments, derive details of ElementSW objects and send requests to ElementOS via the ElementSW SDK """ def __init__(self): """ Parse arguments, setup state variables, check parameters and ensure SDK is installed """ self._size_unit_map = netapp_utils.SF_BYTE_MAP self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update( dict( state=dict(required=True, choices=['present', 'absent']), name=dict(required=True, type='str'), account_id=dict(required=True), enable512e=dict(type='bool', aliases=['512emulation']), qos=dict(required=False, type='dict', default=None), attributes=dict(required=False, type='dict', default=None), size=dict(type='int'), size_unit=dict(default='gb', choices=[ 'bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb' ], type='str'), access=dict(required=False, type='str', default=None, choices=[ 'readOnly', 'readWrite', 'locked', 'replicationTarget' ]), )) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[('state', 'present', ['size', 'enable512e'])], supports_check_mode=True) param = self.module.params # set up state variables self.state = param['state'] self.name = param['name'] self.account_id = param['account_id'] self.enable512e = param['enable512e'] self.qos = param['qos'] self.attributes = param['attributes'] self.access = param['access'] self.size_unit = param['size_unit'] if param['size'] is not None: self.size = param['size'] * self._size_unit_map[self.size_unit] else: self.size = None if HAS_SF_SDK is False: self.module.fail_json( msg="Unable to import the ElementSW Python SDK") else: try: self.sfe = netapp_utils.create_sf_connection( module=self.module) except solidfire.common.ApiServerError: self.module.fail_json(msg="Unable to create the connection") self.elementsw_helper = NaElementSWModule(self.sfe) # add telemetry attributes if self.attributes is not None: self.attributes.update( self.elementsw_helper.set_element_attributes( source='na_elementsw_volume')) else: self.attributes = self.elementsw_helper.set_element_attributes( source='na_elementsw_volume') def get_account_id(self): """ Return account id if found """ try: # Update and return self.account_id self.account_id = self.elementsw_helper.account_exists( self.account_id) return self.account_id except Exception as err: self.module.fail_json(msg="Error: account_id %s does not exist" % self.account_id, exception=to_native(err)) def get_volume(self): """ Return volume details if found """ # Get volume details volume_id = self.elementsw_helper.volume_exists( self.name, self.account_id) if volume_id is not None: # Return volume_details volume_details = self.elementsw_helper.get_volume(volume_id) if volume_details is not None: return volume_details return None def create_volume(self): """ Create Volume :return: True if created, False if fails """ try: self.sfe.create_volume(name=self.name, account_id=self.account_id, total_size=self.size, enable512e=self.enable512e, qos=self.qos, attributes=self.attributes) except Exception as err: self.module.fail_json( msg="Error provisioning volume %s of size %s" % (self.name, self.size), exception=to_native(err)) def delete_volume(self, volume_id): """ Delete and purge the volume using volume id :return: Success : True , Failed : False """ try: self.sfe.delete_volume(volume_id=volume_id) self.sfe.purge_deleted_volume(volume_id=volume_id) # Delete method will delete and also purge the volume instead of moving the volume state to inactive. except Exception as err: # Throwing the exact error message instead of generic error message self.module.fail_json(msg=err.message, exception=to_native(err)) def update_volume(self, volume_id): """ Update the volume with the specified param :return: Success : True, Failed : False """ try: self.sfe.modify_volume(volume_id, account_id=self.account_id, access=self.access, qos=self.qos, total_size=self.size, attributes=self.attributes) except Exception as err: # Throwing the exact error message instead of generic error message self.module.fail_json(msg=err.message, exception=to_native(err)) def apply(self): # Perform pre-checks, call functions and exit changed = False volume_exists = False update_volume = False self.get_account_id() volume_detail = self.get_volume() if volume_detail: volume_exists = True volume_id = volume_detail.volume_id if self.state == 'absent': # Checking for state change(s) here, and applying it later in the code allows us to support # check_mode changed = True elif self.state == 'present': # Checking all the params for update operation if volume_detail.access is not None and self.access is not None and volume_detail.access != self.access: update_volume = True changed = True elif volume_detail.account_id is not None and self.account_id is not None \ and volume_detail.account_id != self.account_id: update_volume = True changed = True elif volume_detail.qos is not None and self.qos is not None: """ Actual volume_detail.qos has ['burst_iops', 'burst_time', 'curve', 'max_iops', 'min_iops'] keys. As only minOPS, maxOPS, burstOPS is important to consider, checking only these values. """ volume_qos = volume_detail.qos.__dict__ if volume_qos['min_iops'] != self.qos['minIOPS'] or volume_qos['max_iops'] != self.qos['maxIOPS'] \ or volume_qos['burst_iops'] != self.qos['burstIOPS']: update_volume = True changed = True else: # If check fails, do nothing pass if volume_detail.total_size is not None and volume_detail.total_size != self.size: size_difference = abs( float(volume_detail.total_size - self.size)) # Change size only if difference is bigger than 0.001 if size_difference / self.size > 0.001: update_volume = True changed = True else: # If check fails, do nothing pass if volume_detail.attributes is not None and self.attributes is not None and \ volume_detail.attributes != self.attributes: update_volume = True changed = True else: if self.state == 'present': changed = True result_message = "" if changed: if self.module.check_mode: result_message = "Check mode, skipping changes" else: if self.state == 'present': if not volume_exists: self.create_volume() result_message = "Volume created" elif update_volume: self.update_volume(volume_id) result_message = "Volume updated" elif self.state == 'absent': self.delete_volume(volume_id) result_message = "Volume deleted" self.module.exit_json(changed=changed, msg=result_message)
class ElementOSVolumeClone(object): """ Contains methods to parse arguments, derive details of Element Software objects and send requests to Element OS via the Solidfire SDK """ def __init__(self): """ Parse arguments, setup state variables, check parameters and ensure SDK is installed """ self._size_unit_map = netapp_utils.SF_BYTE_MAP self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update( dict( name=dict(required=True), src_volume_id=dict(required=True), src_snapshot_id=dict(), account_id=dict(required=True), attributes=dict(type='dict', default=None), size=dict(type='int'), size_unit=dict(default='gb', choices=[ 'bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb' ], type='str'), access=dict(type='str', default=None, choices=[ 'readOnly', 'readWrite', 'locked', 'replicationTarget' ]), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) parameters = self.module.params # set up state variables self.name = parameters['name'] self.src_volume_id = parameters['src_volume_id'] self.src_snapshot_id = parameters['src_snapshot_id'] self.account_id = parameters['account_id'] self.attributes = parameters['attributes'] self.size_unit = parameters['size_unit'] if parameters['size'] is not None: self.size = parameters['size'] * \ self._size_unit_map[self.size_unit] else: self.size = None self.access = parameters['access'] if HAS_SF_SDK is False: self.module.fail_json( msg="Unable to import the SolidFire Python SDK") else: self.sfe = netapp_utils.create_sf_connection(module=self.module) self.elementsw_helper = NaElementSWModule(self.sfe) # add telemetry attributes if self.attributes is not None: self.attributes.update( self.elementsw_helper.set_element_attributes( source='na_elementsw_volume_clone')) else: self.attributes = self.elementsw_helper.set_element_attributes( source='na_elementsw_volume_clone') def get_account_id(self): """ Return account id if found """ try: # Update and return self.account_id self.account_id = self.elementsw_helper.account_exists( self.account_id) return self.account_id except Exception as err: self.module.fail_json(msg="Error: account_id %s does not exist" % self.account_id, exception=to_native(err)) def get_snapshot_id(self): """ Return snapshot details if found """ src_snapshot = self.elementsw_helper.get_snapshot( self.src_snapshot_id, self.src_volume_id) # Update and return self.src_snapshot_id if src_snapshot is not None: self.src_snapshot_id = src_snapshot.snapshot_id # Return src_snapshot return self.src_snapshot_id return None def get_src_volume_id(self): """ Return volume id if found """ src_vol_id = self.elementsw_helper.volume_exists( self.src_volume_id, self.account_id) if src_vol_id is not None: # Update and return self.volume_id self.src_volume_id = src_vol_id # Return src_volume_id return self.src_volume_id return None def clone_volume(self): """Clone Volume from source""" try: self.sfe.clone_volume(volume_id=self.src_volume_id, name=self.name, new_account_id=self.account_id, new_size=self.size, access=self.access, snapshot_id=self.src_snapshot_id, attributes=self.attributes) except Exception as err: self.module.fail_json(msg="Error creating clone %s of size %s" % (self.name, self.size), exception=to_native(err)) def apply(self): """Perform pre-checks, call functions and exit""" changed = False result_message = "" if self.get_account_id() is None: self.module.fail_json(msg="Account id not found: %s" % (self.account_id)) # there is only one state. other operations # are part of the volume module # ensure that a volume with the clone name # isn't already present if self.elementsw_helper.volume_exists(self.name, self.account_id) is None: # check for the source volume if self.get_src_volume_id() is not None: # check for a valid snapshot if self.src_snapshot_id and not self.get_snapshot_id(): self.module.fail_json(msg="Snapshot id not found: %s" % (self.src_snapshot_id)) # change required changed = True else: self.module.fail_json(msg="Volume id not found %s" % (self.src_volume_id)) if changed: if self.module.check_mode: result_message = "Check mode, skipping changes" else: self.clone_volume() result_message = "Volume cloned" self.module.exit_json(changed=changed, msg=result_message)
class ElementSWSnapShotSchedule(object): """ Contains methods to parse arguments, derive details of ElementSW objects and send requests to ElementSW via the ElementSW SDK """ def __init__(self): """ Parse arguments, setup state variables, check parameters and ensure SDK is installed """ self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update( dict( state=dict(required=True, choices=['present', 'absent']), name=dict(required=True, type='str'), schedule_type=dict(required=False, choices=[ 'DaysOfWeekFrequency', 'DaysOfMonthFrequency', 'TimeIntervalFrequency' ]), time_interval_days=dict(required=False, type='int', default=1), time_interval_hours=dict(required=False, type='int', default=0), time_interval_minutes=dict(required=False, type='int', default=0), days_of_week_weekdays=dict(required=False, type='list'), days_of_week_hours=dict(required=False, type='int', default=0), days_of_week_minutes=dict(required=False, type='int', default=0), days_of_month_monthdays=dict(required=False, type='list'), days_of_month_hours=dict(required=False, type='int', default=0), days_of_month_minutes=dict(required=False, type='int', default=0), paused=dict(required=False, type='bool'), recurring=dict(required=False, type='bool'), starting_date=dict(required=False, type='str'), snapshot_name=dict(required=False, type='str'), volumes=dict(required=False, type='list'), account_id=dict(required=False, type='str'), retention=dict(required=False, type='str'), )) self.module = AnsibleModule( argument_spec=self.argument_spec, required_if=[('state', 'present', ['account_id', 'volumes', 'schedule_type']), ('schedule_type', 'DaysOfMonthFrequency', ['days_of_month_monthdays']), ('schedule_type', 'DaysOfWeekFrequency', ['days_of_week_weekdays'])], supports_check_mode=True) param = self.module.params # set up state variables self.state = param['state'] self.name = param['name'] self.schedule_type = param['schedule_type'] self.days_of_week_weekdays = param['days_of_week_weekdays'] self.days_of_week_hours = param['days_of_week_hours'] self.days_of_week_minutes = param['days_of_week_minutes'] self.days_of_month_monthdays = param['days_of_month_monthdays'] self.days_of_month_hours = param['days_of_month_hours'] self.days_of_month_minutes = param['days_of_month_minutes'] self.time_interval_days = param['time_interval_days'] self.time_interval_hours = param['time_interval_hours'] self.time_interval_minutes = param['time_interval_minutes'] self.paused = param['paused'] self.recurring = param['recurring'] if self.schedule_type == 'DaysOfWeekFrequency': # Create self.weekday list if self.schedule_type is days_of_week if self.days_of_week_weekdays is not None: # Create self.weekday list if self.schedule_type is days_of_week self.weekdays = [] for day in self.days_of_week_weekdays: if str(day).isdigit(): # If id specified, return appropriate day self.weekdays.append(Weekday.from_id(int(day))) else: # If name specified, return appropriate day self.weekdays.append( Weekday.from_name(day.capitalize())) if self.state == 'present' and self.schedule_type is None: # Mandate schedule_type for create operation self.module.fail_json( msg="Please provide required parameter: schedule_type") # Mandate schedule name for delete operation if self.state == 'absent' and self.name is None: self.module.fail_json( msg="Please provide required parameter: name") self.starting_date = param['starting_date'] self.snapshot_name = param['snapshot_name'] self.volumes = param['volumes'] self.account_id = param['account_id'] self.retention = param['retention'] self.create_schedule_result = None if HAS_SF_SDK is False: # Create ElementSW connection self.module.fail_json( msg="Unable to import the ElementSW Python SDK") else: self.sfe = netapp_utils.create_sf_connection(module=self.module) self.elementsw_helper = NaElementSWModule(self.sfe) def get_schedule(self): # Checking whether schedule id is exist or not # Return schedule details if found, None otherwise # If exist set variable self.name try: schedule_list = self.sfe.list_schedules() except ApiServerError: return None for schedule in schedule_list.schedules: if str(schedule.schedule_id) == self.name: self.name = schedule.name return schedule elif schedule.name == self.name: return schedule return None def get_account_id(self): # Validate account id # Return account_id if found, None otherwise try: account_id = self.elementsw_helper.account_exists(self.account_id) return account_id except ApiServerError: return None def get_volume_id(self): # Validate volume_ids # Return volume ids if found, fail if not found volume_ids = [] for volume in self.volumes: volume_id = self.elementsw_helper.volume_exists( volume.strip(), self.account_id) if volume_id: volume_ids.append(volume_id) else: self.module.fail_json( msg='Specified volume %s does not exist' % volume) return volume_ids def get_frequency(self): # Configuring frequency depends on self.schedule_type frequency = None if self.schedule_type is not None and self.schedule_type == 'DaysOfWeekFrequency': if self.weekdays is not None: frequency = DaysOfWeekFrequency( weekdays=self.weekdays, hours=self.days_of_week_hours, minutes=self.days_of_week_minutes) elif self.schedule_type is not None and self.schedule_type == 'DaysOfMonthFrequency': if self.days_of_month_monthdays is not None: frequency = DaysOfMonthFrequency( monthdays=self.days_of_month_monthdays, hours=self.days_of_month_hours, minutes=self.days_of_month_minutes) elif self.schedule_type is not None and self.schedule_type == 'TimeIntervalFrequency': frequency = netapp_utils.TimeIntervalFrequency( days=self.time_interval_days, hours=self.time_interval_hours, minutes=self.time_interval_minutes) return frequency def is_same_schedule_type(self, schedule_detail): # To check schedule type is same or not if str(schedule_detail.frequency).split('(')[0] == self.schedule_type: return True else: return False def create_schedule(self): # Create schedule try: frequency = self.get_frequency() if frequency is None: self.module.fail_json( msg= 'Failed to create schedule frequency object - type %s parameters' % self.schedule_type) # Create schedule name = self.name schedule_info = netapp_utils.ScheduleInfo( volume_ids=self.volumes, snapshot_name=self.snapshot_name, retention=self.retention) sched = netapp_utils.Schedule(schedule_info, name, frequency) sched.paused = self.paused sched.recurring = self.recurring sched.starting_date = self.starting_date self.create_schedule_result = self.sfe.create_schedule(sched) except Exception as e: self.module.fail_json(msg='Error creating schedule %s: %s' % (self.name, to_native(e.message)), exception=traceback.format_exc()) def delete_schedule(self, schedule_id): # delete schedule try: get_schedule_result = self.sfe.get_schedule( schedule_id=schedule_id) sched = get_schedule_result.schedule sched.to_be_deleted = True self.sfe.modify_schedule(schedule=sched) except Exception as e: self.module.fail_json(msg='Error deleting schedule %s: %s' % (self.name, to_native(e.message)), exception=traceback.format_exc()) def update_schedule(self, schedule_id): # Update schedule try: get_schedule_result = self.sfe.get_schedule( schedule_id=schedule_id) sched = get_schedule_result.schedule # Update schedule properties sched.frequency = self.get_frequency() if sched.frequency is None: self.module.fail_json( msg= 'Failed to create schedule frequency object - type %s parameters' % self.schedule_type) if self.volumes is not None and len(self.volumes) > 0: sched.schedule_info.volume_ids = self.volumes if self.retention is not None: sched.schedule_info.retention = self.retention if self.snapshot_name is not None: sched.schedule_info.snapshot_name = self.snapshot_name if self.paused is not None: sched.paused = self.paused if self.recurring is not None: sched.recurring = self.recurring if self.starting_date is not None: sched.starting_date = self.starting_date # Make API call self.sfe.modify_schedule(schedule=sched) except Exception as e: self.module.fail_json(msg='Error updating schedule %s: %s' % (self.name, to_native(e.message)), exception=traceback.format_exc()) def apply(self): # Perform pre-checks, call functions and exit changed = False update_schedule = False if self.account_id is not None: self.account_id = self.get_account_id() if self.state == 'present' and self.volumes is not None: if self.account_id: self.volumes = self.get_volume_id() else: self.module.fail_json( msg='Specified account id does not exist') # Getting the schedule details schedule_detail = self.get_schedule() if schedule_detail is None and self.state == 'present': if len(self.volumes) > 0: changed = True else: self.module.fail_json(msg='Specified volumes not on cluster') elif schedule_detail is not None: # Getting the schedule id if self.state == 'absent': changed = True else: # Check if we need to update the account if self.retention is not None and schedule_detail.schedule_info.retention != self.retention: update_schedule = True changed = True elif self.snapshot_name is not None and schedule_detail.schedule_info.snapshot_name != self.snapshot_name: update_schedule = True changed = True elif self.paused is not None and schedule_detail.paused != self.paused: update_schedule = True changed = True elif self.recurring is not None and schedule_detail.recurring != self.recurring: update_schedule = True changed = True elif self.starting_date is not None and schedule_detail.starting_date != self.starting_date: update_schedule = True changed = True elif self.volumes is not None and len(self.volumes) > 0: for volumeID in schedule_detail.schedule_info.volume_ids: if volumeID not in self.volumes: update_schedule = True changed = True temp_frequency = self.get_frequency() if temp_frequency is not None: # Checking schedule_type changes if self.is_same_schedule_type(schedule_detail): # If same schedule type if self.schedule_type == "TimeIntervalFrequency": # Check if there is any change in schedule.frequency, If schedule_type is time_interval if schedule_detail.frequency.days != temp_frequency.days or \ schedule_detail.frequency.hours != temp_frequency.hours or \ schedule_detail.frequency.minutes != temp_frequency.minutes: update_schedule = True changed = True elif self.schedule_type == "DaysOfMonthFrequency": # Check if there is any change in schedule.frequency, If schedule_type is days_of_month if len(schedule_detail.frequency.monthdays) != len(temp_frequency.monthdays) or \ schedule_detail.frequency.hours != temp_frequency.hours or \ schedule_detail.frequency.minutes != temp_frequency.minutes: update_schedule = True changed = True elif len(schedule_detail.frequency.monthdays ) == len(temp_frequency.monthdays): actual_frequency_monthday = schedule_detail.frequency.monthdays temp_frequency_monthday = temp_frequency.monthdays for monthday in actual_frequency_monthday: if monthday not in temp_frequency_monthday: update_schedule = True changed = True elif self.schedule_type == "DaysOfWeekFrequency": # Check if there is any change in schedule.frequency, If schedule_type is days_of_week if len(schedule_detail.frequency.weekdays) != len(temp_frequency.weekdays) or \ schedule_detail.frequency.hours != temp_frequency.hours or \ schedule_detail.frequency.minutes != temp_frequency.minutes: update_schedule = True changed = True elif len( schedule_detail.frequency.weekdays) == len( temp_frequency.weekdays): actual_frequency_weekdays = schedule_detail.frequency.weekdays temp_frequency_weekdays = temp_frequency.weekdays if len([ actual_weekday for actual_weekday, temp_weekday in zip(actual_frequency_weekdays, temp_frequency_weekdays) if actual_weekday != temp_weekday ]) != 0: update_schedule = True changed = True else: update_schedule = True changed = True else: self.module.fail_json( msg= 'Failed to create schedule frequency object - type %s parameters' % self.schedule_type) result_message = " " if changed: if self.module.check_mode: # Skip changes result_message = "Check mode, skipping changes" else: if self.state == 'present': if update_schedule: self.update_schedule(schedule_detail.schedule_id) result_message = "Snapshot Schedule modified" else: self.create_schedule() result_message = "Snapshot Schedule created" elif self.state == 'absent': self.delete_schedule(schedule_detail.schedule_id) result_message = "Snapshot Schedule deleted" self.module.exit_json(changed=changed, msg=result_message)
class ElementOSSnapshotRestore(object): """ Element OS Restore from snapshot """ def __init__(self): self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update(dict( account_id=dict(required=True, type='str'), src_volume_id=dict(required=True, type='str'), dest_volume_name=dict(required=True, type='str'), src_snapshot_id=dict(required=True, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) input_params = self.module.params self.account_id = input_params['account_id'] self.src_volume_id = input_params['src_volume_id'] self.dest_volume_name = input_params['dest_volume_name'] self.src_snapshot_id = input_params['src_snapshot_id'] if HAS_SF_SDK is False: self.module.fail_json( msg="Unable to import the SolidFire Python SDK") else: self.sfe = netapp_utils.create_sf_connection(module=self.module) self.elementsw_helper = NaElementSWModule(self.sfe) # add telemetry attributes self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_snapshot_restore') def get_account_id(self): """ Get account id if found """ try: # Update and return self.account_id self.account_id = self.elementsw_helper.account_exists(self.account_id) return self.account_id except Exception as err: self.module.fail_json(msg="Error: account_id %s does not exist" % self.account_id, exception=to_native(err)) def get_snapshot_id(self): """ Return snapshot details if found """ src_snapshot = self.elementsw_helper.get_snapshot(self.src_snapshot_id, self.src_volume_id) # Update and return self.src_snapshot_id if src_snapshot: self.src_snapshot_id = src_snapshot.snapshot_id # Return self.src_snapshot_id return self.src_snapshot_id return None def restore_snapshot(self): """ Restore Snapshot to Volume """ try: self.sfe.clone_volume(volume_id=self.src_volume_id, name=self.dest_volume_name, snapshot_id=self.src_snapshot_id, attributes=self.attributes) except Exception as exception_object: self.module.fail_json( msg='Error restore snapshot %s' % (to_native(exception_object)), exception=traceback.format_exc()) def apply(self): """ Check, process and initiate restore snapshot to volume operation """ changed = False result_message = None snapshot_detail = None self.get_account_id() src_vol_id = self.elementsw_helper.volume_exists(self.src_volume_id, self.account_id) if src_vol_id is not None: # Update self.src_volume_id self.src_volume_id = src_vol_id if self.get_snapshot_id() is not None: # Addressing idempotency by comparing volume does not exist with same volume name if self.elementsw_helper.volume_exists(self.dest_volume_name, self.account_id) is None: self.restore_snapshot() changed = True else: result_message = "No changes requested, Skipping changes" else: self.module.fail_json(msg="Snapshot id not found %s" % self.src_snapshot_id) else: self.module.fail_json(msg="Volume id not found %s" % self.src_volume_id) self.module.exit_json(changed=changed, msg=result_message)
class ElementOSSnapshot(object): """ Element OS Snapshot Manager """ def __init__(self): self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), account_id=dict(required=True, type='str'), name=dict(required=False, type='str'), src_volume_id=dict(required=True, type='str'), retention=dict(required=False, type='str'), src_snapshot_id=dict(required=False, type='str'), enable_remote_replication=dict(required=False, type='bool'), expiration_time=dict(required=False, type='str'), snap_mirror_label=dict(required=False, type='str'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) input_params = self.module.params self.state = input_params['state'] self.name = input_params['name'] self.account_id = input_params['account_id'] self.src_volume_id = input_params['src_volume_id'] self.src_snapshot_id = input_params['src_snapshot_id'] self.retention = input_params['retention'] self.properties_provided = False self.expiration_time = input_params['expiration_time'] if input_params['expiration_time'] is not None: self.properties_provided = True self.enable_remote_replication = input_params[ 'enable_remote_replication'] if input_params['enable_remote_replication'] is not None: self.properties_provided = True self.snap_mirror_label = input_params['snap_mirror_label'] if input_params['snap_mirror_label'] is not None: self.properties_provided = True if self.state == 'absent' and self.src_snapshot_id is None: self.module.fail_json( msg="Please provide required parameter : snapshot_id") if HAS_SF_SDK is False: self.module.fail_json( msg="Unable to import the SolidFire Python SDK") else: self.sfe = netapp_utils.create_sf_connection(module=self.module) self.elementsw_helper = NaElementSWModule(self.sfe) # add telemetry attributes self.attributes = self.elementsw_helper.set_element_attributes( source='na_elementsw_snapshot') def get_account_id(self): """ Return account id if found """ try: # Update and return self.account_id self.account_id = self.elementsw_helper.account_exists( self.account_id) return self.account_id except Exception as err: self.module.fail_json(msg="Error: account_id %s does not exist" % self.account_id, exception=to_native(err)) def get_src_volume_id(self): """ Return volume id if found """ src_vol_id = self.elementsw_helper.volume_exists( self.src_volume_id, self.account_id) if src_vol_id is not None: # Update and return self.volume_id self.src_volume_id = src_vol_id # Return src_volume_id return self.src_volume_id return None def get_snapshot(self, name=None): """ Return snapshot details if found """ src_snapshot = None if name is not None: src_snapshot = self.elementsw_helper.get_snapshot( name, self.src_volume_id) elif self.src_snapshot_id is not None: src_snapshot = self.elementsw_helper.get_snapshot( self.src_snapshot_id, self.src_volume_id) if src_snapshot is not None: # Update self.src_snapshot_id self.src_snapshot_id = src_snapshot.snapshot_id # Return src_snapshot return src_snapshot def create_snapshot(self): """ Create Snapshot """ try: self.sfe.create_snapshot( volume_id=self.src_volume_id, snapshot_id=self.src_snapshot_id, name=self.name, enable_remote_replication=self.enable_remote_replication, retention=self.retention, snap_mirror_label=self.snap_mirror_label, attributes=self.attributes) except Exception as exception_object: self.module.fail_json(msg='Error creating snapshot %s' % (to_native(exception_object)), exception=traceback.format_exc()) def modify_snapshot(self): """ Modify Snapshot Properties """ try: self.sfe.modify_snapshot( snapshot_id=self.src_snapshot_id, expiration_time=self.expiration_time, enable_remote_replication=self.enable_remote_replication, snap_mirror_label=self.snap_mirror_label) except Exception as exception_object: self.module.fail_json(msg='Error modify snapshot %s' % (to_native(exception_object)), exception=traceback.format_exc()) def delete_snapshot(self): """ Delete Snapshot """ try: self.sfe.delete_snapshot(snapshot_id=self.src_snapshot_id) except Exception as exception_object: self.module.fail_json(msg='Error delete snapshot %s' % (to_native(exception_object)), exception=traceback.format_exc()) def apply(self): """ Check, process and initiate snapshot operation """ changed = False snapshot_delete = False snapshot_create = False snapshot_modify = False result_message = None self.get_account_id() # Dont proceed if source volume is not found if self.get_src_volume_id() is None: self.module.fail_json(msg="Volume id not found %s" % self.src_volume_id) # Get snapshot details using source volume snapshot_detail = self.get_snapshot() if snapshot_detail: if self.properties_provided: if self.expiration_time != snapshot_detail.expiration_time: changed = True else: # To preserve value in case parameter expiration_time is not defined/provided. self.expiration_time = snapshot_detail.expiration_time if self.enable_remote_replication != snapshot_detail.enable_remote_replication: changed = True else: # To preserve value in case parameter enable_remote_Replication is not defined/provided. self.enable_remote_replication = snapshot_detail.enable_remote_replication if self.snap_mirror_label != snapshot_detail.snap_mirror_label: changed = True else: # To preserve value in case parameter snap_mirror_label is not defined/provided. self.snap_mirror_label = snapshot_detail.snap_mirror_label if self.account_id is None or self.src_volume_id is None or self.module.check_mode: changed = False result_message = "Check mode, skipping changes" elif self.state == 'absent' and snapshot_detail is not None: self.delete_snapshot() changed = True elif self.state == 'present' and snapshot_detail is not None: if changed: self.modify_snapshot() # Modify Snapshot properties elif not self.properties_provided: if self.name is not None: snapshot = self.get_snapshot(self.name) # If snapshot with name already exists return without performing any action if snapshot is None: self.create_snapshot( ) # Create Snapshot using parent src_snapshot_id changed = True else: self.create_snapshot() changed = True elif self.state == 'present': if self.name is not None: snapshot = self.get_snapshot(self.name) # If snapshot with name already exists return without performing any action if snapshot is None: self.create_snapshot( ) # Create Snapshot using parent src_snapshot_id changed = True else: self.create_snapshot() changed = True else: changed = False result_message = "No changes requested, skipping changes" self.module.exit_json(changed=changed, msg=result_message)
class ElementSWAccessGroup(object): """ Element Software Volume Access Group """ def __init__(self): self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update(dict( state=dict(required=True, choices=['present', 'absent']), from_name=dict(required=False, type='str'), name=dict(required=True, aliases=["src_access_group_id"], type='str'), initiators=dict(required=False, type='list'), volumes=dict(required=False, type='list'), account_id=dict(required=False, type='str'), virtual_network_id=dict(required=False, type='list'), virtual_network_tags=dict(required=False, type='list'), attributes=dict(required=False, type='dict'), )) self.module = AnsibleModule( argument_spec=self.argument_spec, required_if=[ ('state', 'present', ['account_id']) ], supports_check_mode=True ) input_params = self.module.params # Set up state variables self.state = input_params['state'] self.from_name = input_params['from_name'] self.access_group_name = input_params['name'] self.initiators = input_params['initiators'] self.volumes = input_params['volumes'] self.account_id = input_params['account_id'] self.virtual_network_id = input_params['virtual_network_id'] self.virtual_network_tags = input_params['virtual_network_tags'] self.attributes = input_params['attributes'] if HAS_SF_SDK is False: self.module.fail_json(msg="Unable to import the SolidFire Python SDK") else: self.sfe = netapp_utils.create_sf_connection(module=self.module) self.elementsw_helper = NaElementSWModule(self.sfe) # add telemetry attributes if self.attributes is not None: self.attributes.update(self.elementsw_helper.set_element_attributes(source='na_elementsw_access_group')) else: self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_access_group') def get_access_group(self, name): """ Get Access Group :description: Get Access Group object for a given name :return: object (Group object) :rtype: object (Group object) """ access_groups_list = self.sfe.list_volume_access_groups() group_obj = None for group in access_groups_list.volume_access_groups: # Check and get access_group object for a given name if str(group.volume_access_group_id) == name: group_obj = group elif group.name == name: group_obj = group return group_obj def get_account_id(self): # Validate account id # Return account_id if found, None otherwise try: account_id = self.elementsw_helper.account_exists(self.account_id) return account_id except solidfire.common.ApiServerError: return None def get_volume_id(self): # Validate volume_ids # Return volume ids if found, fail if not found volume_ids = [] for volume in self.volumes: volume_id = self.elementsw_helper.volume_exists(volume, self.account_id) if volume_id: volume_ids.append(volume_id) else: self.module.fail_json(msg='Specified volume %s does not exist' % volume) return volume_ids def create_access_group(self): """ Create the Access Group """ try: self.sfe.create_volume_access_group(name=self.access_group_name, initiators=self.initiators, volumes=self.volumes, virtual_network_id=self.virtual_network_id, virtual_network_tags=self.virtual_network_tags, attributes=self.attributes) except Exception as e: self.module.fail_json(msg="Error creating volume access group %s: %s" % (self.access_group_name, to_native(e)), exception=traceback.format_exc()) def delete_access_group(self): """ Delete the Access Group """ try: self.sfe.delete_volume_access_group(volume_access_group_id=self.group_id) except Exception as e: self.module.fail_json(msg="Error deleting volume access group %s: %s" % (self.access_group_name, to_native(e)), exception=traceback.format_exc()) def update_access_group(self): """ Update the Access Group if the access_group already exists """ try: self.sfe.modify_volume_access_group(volume_access_group_id=self.group_id, virtual_network_id=self.virtual_network_id, virtual_network_tags=self.virtual_network_tags, initiators=self.initiators, volumes=self.volumes, attributes=self.attributes) except Exception as e: self.module.fail_json(msg="Error updating volume access group %s: %s" % (self.access_group_name, to_native(e)), exception=traceback.format_exc()) def rename_access_group(self): """ Rename the Access Group to the new name """ try: self.sfe.modify_volume_access_group(volume_access_group_id=self.from_group_id, virtual_network_id=self.virtual_network_id, virtual_network_tags=self.virtual_network_tags, name=self.access_group_name, initiators=self.initiators, volumes=self.volumes, attributes=self.attributes) except Exception as e: self.module.fail_json(msg="Error updating volume access group %s: %s" % (self.from_name, to_native(e)), exception=traceback.format_exc()) def apply(self): """ Process the access group operation on the Element Software Cluster """ changed = False update_group = False input_account_id = self.account_id if self.account_id is not None: self.account_id = self.get_account_id() if self.state == 'present' and self.volumes is not None: if self.account_id: self.volumes = self.get_volume_id() else: self.module.fail_json(msg='Error: Specified account id "%s" does not exist.' % str(input_account_id)) group_detail = self.get_access_group(self.access_group_name) if group_detail is not None: # If access group found self.group_id = group_detail.volume_access_group_id if self.state == "absent": self.delete_access_group() changed = True else: # If state - present, check for any parameter of existing group needs modification. if self.volumes is not None and len(self.volumes) > 0: # Compare the volume list if not group_detail.volumes: # If access group does not have any volume attached update_group = True changed = True else: for volumeID in group_detail.volumes: if volumeID not in self.volumes: update_group = True changed = True break elif self.initiators is not None and group_detail.initiators != self.initiators: update_group = True changed = True elif self.virtual_network_id is not None or self.virtual_network_tags is not None: update_group = True changed = True if update_group: self.update_access_group() else: # access_group does not exist if self.state == "present" and self.from_name is not None: group_detail = self.get_access_group(self.from_name) if group_detail is not None: # If resource pointed by from_name exists, rename the access_group to name self.from_group_id = group_detail.volume_access_group_id self.rename_access_group() changed = True else: # If resource pointed by from_name does not exists, error out self.module.fail_json(msg="Resource does not exist : %s" % self.from_name) elif self.state == "present": # If from_name is not defined, Create from scratch. self.create_access_group() changed = True self.module.exit_json(changed=changed)
class ElementSWVolumePair(object): ''' class to handle volume pairing operations ''' def __init__(self): """ Setup Ansible parameters and SolidFire connection """ self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), src_volume=dict(required=True, type='str'), src_account=dict(required=True, type='str'), dest_volume=dict(required=True, type='str'), dest_account=dict(required=True, type='str'), mode=dict(required=False, type='str', choices=['async', 'sync', 'snapshotsonly'], default='async'), dest_mvip=dict(required=True, type='str'), dest_username=dict(required=False, type='str'), dest_password=dict(required=False, type='str', no_log=True))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) if HAS_SF_SDK is False: self.module.fail_json( msg="Unable to import the SolidFire Python SDK") else: self.elem = netapp_utils.create_sf_connection(module=self.module) self.elementsw_helper = NaElementSWModule(self.elem) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # get element_sw_connection for destination cluster # overwrite existing source host, user and password with destination credentials self.module.params['hostname'] = self.parameters['dest_mvip'] # username and password is same as source, # if dest_username and dest_password aren't specified if self.parameters.get('dest_username'): self.module.params['username'] = self.parameters['dest_username'] if self.parameters.get('dest_password'): self.module.params['password'] = self.parameters['dest_password'] self.dest_elem = netapp_utils.create_sf_connection(module=self.module) self.dest_elementsw_helper = NaElementSWModule(self.dest_elem) def check_if_already_paired(self, vol_id): """ Check for idempotency A volume can have only one pair Return paired-volume-id if volume is paired already None if volume is not paired """ paired_volumes = self.elem.list_volumes(volume_ids=[vol_id], is_paired=True) for vol in paired_volumes.volumes: for pair in vol.volume_pairs: if pair is not None: return pair.remote_volume_id return None def pair_volumes(self): """ Start volume pairing on source, and complete on target volume """ try: pair_key = self.elem.start_volume_pairing( volume_id=self.parameters['src_vol_id'], mode=self.parameters['mode']) self.dest_elem.complete_volume_pairing( volume_pairing_key=pair_key.volume_pairing_key, volume_id=self.parameters['dest_vol_id']) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error pairing volume id %s" % (self.parameters['src_vol_id']), exception=to_native(err)) def pairing_exists(self, src_id, dest_id): src_paired = self.check_if_already_paired( self.parameters['src_vol_id']) dest_paired = self.check_if_already_paired( self.parameters['dest_vol_id']) if src_paired is not None or dest_paired is not None: return True return None def unpair_volumes(self): """ Delete volume pair """ try: self.elem.remove_volume_pair( volume_id=self.parameters['src_vol_id']) self.dest_elem.remove_volume_pair( volume_id=self.parameters['dest_vol_id']) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error unpairing volume ids %s and %s" % (self.parameters['src_vol_id'], self.parameters['dest_vol_id']), exception=to_native(err)) def get_account_id(self, account, type): """ Get source and destination account IDs """ try: if type == 'src': self.parameters[ 'src_account_id'] = self.elementsw_helper.account_exists( account) elif type == 'dest': self.parameters[ 'dest_account_id'] = self.dest_elementsw_helper.account_exists( account) except solidfire.common.ApiServerError as err: self.module.fail_json( msg="Error: either account %s or %s does not exist" % (self.parameters['src_account'], self.parameters['dest_account']), exception=to_native(err)) def get_volume_id(self, volume, type): """ Get source and destination volume IDs """ if type == 'src': self.parameters[ 'src_vol_id'] = self.elementsw_helper.volume_exists( volume, self.parameters['src_account_id']) if self.parameters['src_vol_id'] is None: self.module.fail_json( msg="Error: source volume %s does not exist" % (self.parameters['src_volume'])) elif type == 'dest': self.parameters[ 'dest_vol_id'] = self.dest_elementsw_helper.volume_exists( volume, self.parameters['dest_account_id']) if self.parameters['dest_vol_id'] is None: self.module.fail_json( msg="Error: destination volume %s does not exist" % (self.parameters['dest_volume'])) def get_ids(self): """ Get IDs for volumes and accounts """ self.get_account_id(self.parameters['src_account'], 'src') self.get_account_id(self.parameters['dest_account'], 'dest') self.get_volume_id(self.parameters['src_volume'], 'src') self.get_volume_id(self.parameters['dest_volume'], 'dest') def apply(self): """ Call create / delete volume pair methods """ self.get_ids() paired = self.pairing_exists(self.parameters['src_vol_id'], self.parameters['dest_vol_id']) # calling helper to determine action cd_action = self.na_helper.get_cd_action(paired, self.parameters) if cd_action == "create": self.pair_volumes() elif cd_action == "delete": self.unpair_volumes() self.module.exit_json(changed=self.na_helper.changed)