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)
class NetAppOntapQosPolicyGroup(object): """ Create, delete, modify and rename a policy group. """ def __init__(self): """ Initialize the Ontap qos policy group class. """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), vserver=dict(required=True, type='str'), max_throughput=dict(required=False, type='str'), min_throughput=dict(required=False, type='str'), force=dict(required=False, type='bool', default=False))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def get_policy_group(self, policy_group_name=None): """ Return details of a policy group. :param policy_group_name: policy group name :return: policy group details. :rtype: dict. """ if policy_group_name is None: policy_group_name = self.parameters['name'] policy_group_get_iter = netapp_utils.zapi.NaElement( 'qos-policy-group-get-iter') policy_group_info = netapp_utils.zapi.NaElement( 'qos-policy-group-info') policy_group_info.add_new_child('policy-group', policy_group_name) policy_group_info.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(policy_group_info) policy_group_get_iter.add_child_elem(query) result = self.server.invoke_successfully(policy_group_get_iter, True) policy_group_detail = None if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) == 1: policy_info = result.get_child_by_name( 'attributes-list').get_child_by_name('qos-policy-group-info') policy_group_detail = { 'name': policy_info.get_child_content('policy-group'), 'vserver': policy_info.get_child_content('vserver'), 'max_throughput': policy_info.get_child_content('max-throughput'), 'min_throughput': policy_info.get_child_content('min-throughput') } return policy_group_detail def create_policy_group(self): """ create a policy group name. """ policy_group = netapp_utils.zapi.NaElement('qos-policy-group-create') policy_group.add_new_child('policy-group', self.parameters['name']) policy_group.add_new_child('vserver', self.parameters['vserver']) if self.parameters.get('max_throughput'): policy_group.add_new_child('max-throughput', self.parameters['max_throughput']) if self.parameters.get('min_throughput'): policy_group.add_new_child('min-throughput', self.parameters['min_throughput']) try: self.server.invoke_successfully(policy_group, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating qos policy group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_policy_group(self, policy_group=None): """ delete an existing policy group. :param policy_group: policy group name. """ if policy_group is None: policy_group = self.parameters['name'] policy_group_obj = netapp_utils.zapi.NaElement( 'qos-policy-group-delete') policy_group_obj.add_new_child('policy-group', policy_group) if self.parameters.get('force'): policy_group_obj.add_new_child('force', str(self.parameters['force'])) try: self.server.invoke_successfully(policy_group_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting qos policy group %s: %s' % (policy_group, to_native(error)), exception=traceback.format_exc()) def modify_policy_group(self): """ Modify policy group. """ policy_group_obj = netapp_utils.zapi.NaElement( 'qos-policy-group-modify') policy_group_obj.add_new_child('policy-group', self.parameters['name']) if self.parameters.get('max_throughput'): policy_group_obj.add_new_child('max-throughput', self.parameters['max_throughput']) if self.parameters.get('min_throughput'): policy_group_obj.add_new_child('min-throughput', self.parameters['min_throughput']) try: self.server.invoke_successfully(policy_group_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying qos policy group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_policy_group(self): """ Rename policy group name. """ rename_obj = netapp_utils.zapi.NaElement('qos-policy-group-rename') rename_obj.add_new_child('new-name', self.parameters['name']) rename_obj.add_new_child('policy-group-name', self.parameters['from_name']) try: self.server.invoke_successfully(rename_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error renaming qos policy group %s: %s' % (self.parameters['from_name'], to_native(error)), exception=traceback.format_exc()) def modify_helper(self, modify): """ helper method to modify policy group. :param modify: modified attributes. """ for attribute in modify.keys(): if attribute in ['max_throughput', 'min_throughput']: self.modify_policy_group() def apply(self): """ Run module based on playbook """ self.asup_log_for_cserver("na_ontap_qos_policy_group") current = self.get_policy_group() rename, cd_action = None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_policy_group(self.parameters['from_name']), current) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_policy_group() if cd_action == 'create': self.create_policy_group() elif cd_action == 'delete': self.delete_policy_group() elif modify: self.modify_helper(modify) self.module.exit_json(changed=self.na_helper.changed) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver)
class NetAppOntapSnapshot(object): """ Creates, modifies, and deletes a Snapshot """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), from_name=dict(required=False, type='str'), snapshot=dict(required=True, type="str"), volume=dict(required=True, type="str"), async_bool=dict(required=False, type="bool", default=False), comment=dict(required=False, type="str"), snapmirror_label=dict(required=False, type="str"), ignore_owners=dict(required=False, type="bool", default=False), snapshot_instance_uuid=dict(required=False, type="str"), vserver=dict(required=True, type="str"), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) return def get_snapshot(self, snapshot_name=None): """ Checks to see if a snapshot exists or not :return: Return True if a snapshot exists, False if it doesn't """ if snapshot_name is None: snapshot_name = self.parameters['snapshot'] snapshot_obj = netapp_utils.zapi.NaElement("snapshot-get-iter") desired_attr = netapp_utils.zapi.NaElement("desired-attributes") snapshot_info = netapp_utils.zapi.NaElement('snapshot-info') comment = netapp_utils.zapi.NaElement('comment') snapmirror_label = netapp_utils.zapi.NaElement('snapmirror-label') # add more desired attributes that are allowed to be modified snapshot_info.add_child_elem(comment) snapshot_info.add_child_elem(snapmirror_label) desired_attr.add_child_elem(snapshot_info) snapshot_obj.add_child_elem(desired_attr) # compose query query = netapp_utils.zapi.NaElement("query") snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") snapshot_info_obj.add_new_child("name", snapshot_name) snapshot_info_obj.add_new_child("volume", self.parameters['volume']) snapshot_info_obj.add_new_child("vserver", self.parameters['vserver']) query.add_child_elem(snapshot_info_obj) snapshot_obj.add_child_elem(query) result = self.server.invoke_successfully(snapshot_obj, True) return_value = None if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: attributes_list = result.get_child_by_name('attributes-list') snap_info = attributes_list.get_child_by_name('snapshot-info') return_value = {'comment': snap_info.get_child_content('comment')} if snap_info.get_child_by_name('snapmirror-label'): return_value['snapmirror_label'] = snap_info.get_child_content( 'snapmirror-label') else: return_value['snapmirror_label'] = None return return_value def create_snapshot(self): """ Creates a new snapshot """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-create") # set up required variables to create a snapshot snapshot_obj.add_new_child("snapshot", self.parameters['snapshot']) snapshot_obj.add_new_child("volume", self.parameters['volume']) # Set up optional variables to create a snapshot if self.parameters.get('async_bool'): snapshot_obj.add_new_child("async", str(self.parameters['async_bool'])) if self.parameters.get('comment'): snapshot_obj.add_new_child("comment", self.parameters['comment']) if self.parameters.get('snapmirror_label'): snapshot_obj.add_new_child("snapmirror-label", self.parameters['snapmirror_label']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating snapshot %s: %s' % (self.parameters['snapshot'], to_native(error)), exception=traceback.format_exc()) def delete_snapshot(self): """ Deletes an existing snapshot """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-delete") # Set up required variables to delete a snapshot snapshot_obj.add_new_child("snapshot", self.parameters['snapshot']) snapshot_obj.add_new_child("volume", self.parameters['volume']) # set up optional variables to delete a snapshot if self.parameters.get('ignore_owners'): snapshot_obj.add_new_child("ignore-owners", str(self.parameters['ignore_owners'])) if self.parameters.get('snapshot_instance_uuid'): snapshot_obj.add_new_child( "snapshot-instance-uuid", self.parameters['snapshot_instance_uuid']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting snapshot %s: %s' % (self.parameters['snapshot'], to_native(error)), exception=traceback.format_exc()) def modify_snapshot(self): """ Modify an existing snapshot :return: """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-modify-iter") # Create query object, this is the existing object query = netapp_utils.zapi.NaElement("query") snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") snapshot_info_obj.add_new_child("name", self.parameters['snapshot']) snapshot_info_obj.add_new_child("vserver", self.parameters['vserver']) query.add_child_elem(snapshot_info_obj) snapshot_obj.add_child_elem(query) # this is what we want to modify in the snapshot object attributes = netapp_utils.zapi.NaElement("attributes") snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") snapshot_info_obj.add_new_child("name", self.parameters['snapshot']) if self.parameters.get('comment'): snapshot_info_obj.add_new_child("comment", self.parameters['comment']) if self.parameters.get('snapmirror_label'): snapshot_info_obj.add_new_child( "snapmirror-label", self.parameters['snapmirror_label']) attributes.add_child_elem(snapshot_info_obj) snapshot_obj.add_child_elem(attributes) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying snapshot %s: %s' % (self.parameters['snapshot'], to_native(error)), exception=traceback.format_exc()) def rename_snapshot(self): """ Rename the snapshot """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-rename") # set up required variables to rename a snapshot snapshot_obj.add_new_child("current-name", self.parameters['from_name']) snapshot_obj.add_new_child("new-name", self.parameters['snapshot']) snapshot_obj.add_new_child("volume", self.parameters['volume']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error renaming snapshot %s to %s: %s' % (self.parameters['from_name'], self.parameters['snapshot'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Check to see which play we should run """ current = self.get_snapshot() netapp_utils.ems_log_event("na_ontap_snapshot", self.server) rename, cd_action = None, None modify = {} if self.parameters.get('from_name'): current_old_name = self.get_snapshot(self.parameters['from_name']) rename = self.na_helper.is_rename_action(current_old_name, current) modify = self.na_helper.get_modified_attributes( current_old_name, self.parameters) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None: modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_snapshot() if cd_action == 'create': self.create_snapshot() elif cd_action == 'delete': self.delete_snapshot() elif modify: self.modify_snapshot() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapIgroupInitiator(object): def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), names=dict(required=True, type='list', aliases=['name']), initiator_group=dict(required=True, type='str'), vserver=dict(required=True, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def get_initiators(self): """ Get the existing list of initiators from an igroup :rtype: list() or None """ igroup_info = netapp_utils.zapi.NaElement('igroup-get-iter') attributes = dict( query={ 'initiator-group-info': { 'initiator-group-name': self.parameters['initiator_group'], 'vserver': self.parameters['vserver'] } }) igroup_info.translate_struct(attributes) result, current = None, [] try: result = self.server.invoke_successfully(igroup_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching igroup info %s: %s' % (self.parameters['initiator_group'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: igroup_info = result.get_child_by_name( 'attributes-list').get_child_by_name('initiator-group-info') if igroup_info.get_child_by_name('initiators') is not None: current = [ initiator['initiator-name'] for initiator in igroup_info['initiators'].get_children() ] return current def modify_initiator(self, initiator_name, zapi): """ Add or remove an initiator to/from an igroup """ options = { 'initiator-group-name': self.parameters['initiator_group'], 'initiator': initiator_name } initiator_modify = netapp_utils.zapi.NaElement.create_node_with_children( zapi, **options) try: self.server.invoke_successfully(initiator_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying igroup initiator %s: %s' % (initiator_name, to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): netapp_utils.ems_log_event("na_ontap_igroup_initiator", self.server) def apply(self): self.autosupport_log() initiators = self.get_initiators() for initiator in self.parameters['names']: present = None if initiator in initiators: present = True cd_action = self.na_helper.get_cd_action(present, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.modify_initiator(initiator, 'igroup-add') elif cd_action == 'delete': self.modify_initiator(initiator, 'igroup-remove') self.module.exit_json(changed=self.na_helper.changed)
class AwsCvsNetappFileSystem(object): """ Contains methods to parse arguments, derive details of AWS_CVS objects and send requests to AWS CVS via the restApi """ def __init__(self): """ Parse arguments, setup state variables, check parameters and ensure request module is installed """ self.argument_spec = netapp_utils.aws_cvs_host_argument_spec() self.argument_spec.update( dict( state=dict(required=True, choices=['present', 'absent']), region=dict(required=True, type='str'), creationToken=dict(required=True, type='str'), quotaInBytes=dict(required=False, type='int'), serviceLevel=dict(required=False, choices=['standard', 'premium', 'extreme']), exportPolicy=dict( type='dict', options=dict(rules=dict( type='list', options=dict( allowedClients=dict(required=False, type='str'), cifs=dict(required=False, type='bool'), nfsv3=dict(required=False, type='bool'), nfsv4=dict(required=False, type='bool'), ruleIndex=dict(required=False, type='int'), unixReadOnly=dict(required=False, type='bool'), unixReadWrite=dict(required=False, type='bool'))))), )) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[ ('state', 'present', [ 'region', 'creationToken', 'quotaInBytes' ]), ], supports_check_mode=True) self.na_helper = NetAppModule() # set up state variables self.parameters = self.na_helper.set_parameters(self.module.params) # Calling generic AWSCVS restApi class self.restApi = AwsCvsRestAPI(self.module) self.data = {} for key in self.parameters.keys(): self.data[key] = self.parameters[key] def get_filesystemId(self): # Check given FileSystem is exists # Return fileSystemId is found, None otherwise list_filesystem, error = self.restApi.get('FileSystems') if error: self.module.fail_json(msg=error) for FileSystem in list_filesystem: if FileSystem['creationToken'] == self.parameters['creationToken']: return FileSystem['fileSystemId'] return None def get_filesystem(self, fileSystemId): # Get FileSystem information by fileSystemId # Return fileSystem Information filesystemInfo, error = self.restApi.get('FileSystems/%s' % fileSystemId) if error: self.module.fail_json(msg=error) else: return filesystemInfo return None def is_job_done(self, response): # check jobId is present and equal to 'done' # return True on success, False otherwise try: job_id = response['jobs'][0]['jobId'] except TypeError: job_id = None if job_id is not None and self.restApi.get_state(job_id) == 'done': return True return False def create_fileSystem(self): # Create fileSystem api = 'FileSystems' response, error = self.restApi.post(api, self.data) if not error: if self.is_job_done(response): return error = "Error: unexpected response on FileSystems create: %s" % str( response) self.module.fail_json(msg=error) def delete_fileSystem(self, fileSystemId): # Delete FileSystem api = 'FileSystems/' + fileSystemId self.data = None response, error = self.restApi.delete(api, self.data) if not error: if self.is_job_done(response): return error = "Error: unexpected response on FileSystems delete: %s" % str( response) self.module.fail_json(msg=error) def update_fileSystem(self, fileSystemId): # Update FileSystem api = 'FileSystems/' + fileSystemId response, error = self.restApi.put(api, self.data) if not error: if self.is_job_done(response): return error = "Error: unexpected response on FileSystems update: %s" % str( response) self.module.fail_json(msg=error) def apply(self): """ Perform pre-checks, call functions and exit """ fileSystem = None fileSystemId = self.get_filesystemId() if fileSystemId: # Getting the FileSystem details fileSystem = self.get_filesystem(fileSystemId) cd_action = self.na_helper.get_cd_action(fileSystem, self.parameters) if cd_action is None and self.parameters['state'] == 'present': # Check if we need to update the fileSystem update_fileSystem = False if fileSystem['quotaInBytes'] is not None and 'quotaInBytes' in self.parameters \ and fileSystem['quotaInBytes'] != self.parameters['quotaInBytes']: update_fileSystem = True elif fileSystem['creationToken'] is not None and 'creationToken' in self.parameters \ and fileSystem['creationToken'] != self.parameters['creationToken']: update_fileSystem = True elif fileSystem['serviceLevel'] is not None and 'serviceLevel' in self.parameters \ and fileSystem['serviceLevel'] != self.parameters['serviceLevel']: update_fileSystem = True elif fileSystem['exportPolicy'][ 'rules'] is not None and 'exportPolicy' in self.parameters: for rule_org in fileSystem['exportPolicy']['rules']: for rule in self.parameters['exportPolicy']['rules']: if rule_org['allowedClients'] != rule['allowedClients']: update_fileSystem = True elif rule_org['unixReadOnly'] != rule['unixReadOnly']: update_fileSystem = True elif rule_org['unixReadWrite'] != rule['unixReadWrite']: update_fileSystem = True if update_fileSystem: self.na_helper.changed = True result_message = "" if self.na_helper.changed: if self.module.check_mode: # Skip changes result_message = "Check mode, skipping changes" else: if cd_action == "create": self.create_fileSystem() result_message = "FileSystem Created" elif cd_action == "delete": self.delete_fileSystem(fileSystemId) result_message = "FileSystem Deleted" else: # modify self.update_fileSystem(fileSystemId) result_message = "FileSystem Updated" self.module.exit_json(changed=self.na_helper.changed, msg=result_message)
class NetAppONTAPNVMENamespace(object): """ Class with NVME namespace methods """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), ostype=dict( required=False, type='str', choices=['windows', 'linux', 'vmware', 'xen', 'hyper_v']), path=dict(required=True, type='str'), size=dict(required=False, type='int'))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[('state', 'present', ['ostype', 'size'])], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def get_namespace(self): """ Get current namespace details :return: dict if namespace exists, None otherwise """ namespace_get = netapp_utils.zapi.NaElement('nvme-namespace-get-iter') query = { 'query': { 'nvme-namespace-info': { 'path': self.parameters['path'], 'vserver': self.parameters['vserver'] } } } namespace_get.translate_struct(query) try: result = self.server.invoke_successfully(namespace_get, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching namespace info: %s' % to_native(error), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: return result return None def create_namespace(self): """ Create a NVME Namespace """ options = { 'path': self.parameters['path'], 'ostype': self.parameters['ostype'], 'size': self.parameters['size'] } namespace_create = netapp_utils.zapi.NaElement('nvme-namespace-create') namespace_create.translate_struct(options) try: self.server.invoke_successfully(namespace_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating namespace for path %s: %s' % (self.parameters.get('path'), to_native(error)), exception=traceback.format_exc()) def delete_namespace(self): """ Delete a NVME Namespace """ options = {'path': self.parameters['path']} namespace_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'nvme-namespace-delete', **options) try: self.server.invoke_successfully(namespace_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting namespace for path %s: %s' % (self.parameters.get('path'), to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to NVME Namespace """ netapp_utils.ems_log_event("na_ontap_nvme_namespace", self.server) current = self.get_namespace() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_namespace() elif cd_action == 'delete': self.delete_namespace() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapInterface(object): ''' object to describe interface info ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, choices=[ 'present', 'absent'], default='present'), interface_name=dict(required=True, type='str'), home_node=dict(required=False, type='str', default=None), home_port=dict(required=False, type='str'), role=dict(required=False, type='str'), address=dict(required=False, type='str'), netmask=dict(required=False, type='str'), vserver=dict(required=True, type='str'), firewall_policy=dict(required=False, type='str', default=None), failover_policy=dict(required=False, type='str', default=None, choices=['disabled', 'system-defined', 'local-only', 'sfo-partner-only', 'broadcast-domain-wide']), admin_status=dict(required=False, choices=['up', 'down']), subnet_name=dict(required=False, type='str'), is_auto_revert=dict(required=False, type='bool', default=None), protocols=dict(required=False, type='list'), force_subnet_association=dict(required=False, type='bool', default=None), dns_domain_name=dict(required=False, type='str'), listen_for_dns_query=dict(required=False, type='bool'), is_dns_update_enabled=dict(required=False, type='bool') )) self.module = AnsibleModule( argument_spec=self.argument_spec, mutually_exclusive=[ ['subnet_name', 'address'], ['subnet_name', 'netmask'] ], supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def get_interface(self): """ Return details about the interface :param: name : Name of the name of the interface :return: Details about the interface. None if not found. :rtype: dict """ interface_info = netapp_utils.zapi.NaElement('net-interface-get-iter') interface_attributes = netapp_utils.zapi.NaElement('net-interface-info') interface_attributes.add_new_child('interface-name', self.parameters['interface_name']) interface_attributes.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(interface_attributes) interface_info.add_child_elem(query) result = self.server.invoke_successfully(interface_info, True) return_value = None if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) >= 1: interface_attributes = result.get_child_by_name('attributes-list').\ get_child_by_name('net-interface-info') return_value = { 'interface_name': self.parameters['interface_name'], 'admin_status': interface_attributes['administrative-status'], 'home_port': interface_attributes['home-port'], 'home_node': interface_attributes['home-node'], 'failover_policy': interface_attributes['failover-policy'].replace('_', '-'), 'is_auto_revert': True if interface_attributes['is-auto-revert'] == 'true' else False, } if interface_attributes.get_child_by_name('address'): return_value['address'] = interface_attributes['address'] if interface_attributes.get_child_by_name('netmask'): return_value['netmask'] = interface_attributes['netmask'] if interface_attributes.get_child_by_name('firewall-policy'): return_value['firewall_policy'] = interface_attributes['firewall-policy'] if interface_attributes.get_child_by_name('dns-domain-name') != 'none': return_value['dns_domain_name'] = interface_attributes['dns-domain-name'] else: return_value['dns_domain_name'] = None if interface_attributes.get_child_by_name('listen-for-dns-query'): return_value['listen_for_dns_query'] = self.na_helper.get_value_for_bool(True, interface_attributes['listen-for-dns-query']) if interface_attributes.get_child_by_name('is-dns-update-enabled'): return_value['is_dns_update_enabled'] = self.na_helper.get_value_for_bool(True, interface_attributes['is-dns-update-enabled']) return return_value @staticmethod def set_options(options, parameters): """ set attributes for create or modify """ if parameters.get('home_port') is not None: options['home-port'] = parameters['home_port'] if parameters.get('subnet_name') is not None: options['subnet-name'] = parameters['subnet_name'] if parameters.get('address') is not None: options['address'] = parameters['address'] if parameters.get('netmask') is not None: options['netmask'] = parameters['netmask'] if parameters.get('failover_policy') is not None: options['failover-policy'] = parameters['failover_policy'] if parameters.get('firewall_policy') is not None: options['firewall-policy'] = parameters['firewall_policy'] if parameters.get('is_auto_revert') is not None: options['is-auto-revert'] = 'true' if parameters['is_auto_revert'] is True else 'false' if parameters.get('admin_status') is not None: options['administrative-status'] = parameters['admin_status'] if parameters.get('force_subnet_association') is not None: options['force-subnet-association'] = 'true' if parameters['force_subnet_association'] else 'false' if parameters.get('dns_domain_name') is not None: options['dns-domain-name'] = parameters['dns_domain_name'] if parameters.get('listen_for_dns_query') is not None: options['listen-for-dns-query'] = str(parameters['listen_for_dns_query']) if parameters.get('is_dns_update_enabled') is not None: options['is-dns-update-enabled'] = str(parameters['is_dns_update_enabled']) def set_protocol_option(self, required_keys): """ set protocols for create """ if self.parameters.get('protocols') is not None: data_protocols_obj = netapp_utils.zapi.NaElement('data-protocols') for protocol in self.parameters.get('protocols'): if protocol.lower() in ['fc-nvme', 'fcp']: if 'address' in required_keys: required_keys.remove('address') if 'home_port' in required_keys: required_keys.remove('home_port') if 'netmask' in required_keys: required_keys.remove('netmask') not_required_params = set(['address', 'netmask', 'firewall_policy']) if not not_required_params.isdisjoint(set(self.parameters.keys())): self.module.fail_json(msg='Error: Following parameters for creating interface are not supported' ' for data-protocol fc-nvme: %s' % ', '.join(not_required_params)) data_protocols_obj.add_new_child('data-protocol', protocol) return data_protocols_obj return None def get_home_node_for_cluster(self): ''' get the first node name from this cluster ''' get_node = netapp_utils.zapi.NaElement('cluster-node-get-iter') attributes = { 'query': { 'cluster-node-info': {} } } get_node.translate_struct(attributes) try: result = self.server.invoke_successfully(get_node, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg='Error fetching node for interface %s: %s' % (self.parameters['interface_name'], to_native(exc)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1: attributes = result.get_child_by_name('attributes-list') return attributes.get_child_by_name('cluster-node-info').get_child_content('node-name') return None def validate_create_parameters(self, keys): ''' Validate if required parameters for create are present. Parameter requirement might vary based on given data-protocol. :return: None ''' if self.parameters.get('home_node') is None: node = self.get_home_node_for_cluster() if node is not None: self.parameters['home_node'] = node # validate if mandatory parameters are present for create if not keys.issubset(set(self.parameters.keys())) and self.parameters.get('subnet_name') is None: self.module.fail_json(msg='Error: Missing one or more required parameters for creating interface: %s' % ', '.join(keys)) # if role is intercluster, protocol cannot be specified if self.parameters['role'] == "intercluster" and self.parameters.get('protocols') is not None: self.module.fail_json(msg='Error: Protocol cannot be specified for intercluster role,' 'failed to create interface') def create_interface(self): ''' calling zapi to create interface ''' required_keys = set(['role', 'home_port']) data_protocols_obj = None if self.parameters.get('subnet_name') is None: required_keys.add('address') required_keys.add('netmask') data_protocols_obj = self.set_protocol_option(required_keys) self.validate_create_parameters(required_keys) options = {'interface-name': self.parameters['interface_name'], 'role': self.parameters['role'], 'home-node': self.parameters.get('home_node'), 'vserver': self.parameters['vserver']} NetAppOntapInterface.set_options(options, self.parameters) interface_create = netapp_utils.zapi.NaElement.create_node_with_children('net-interface-create', **options) if data_protocols_obj is not None: interface_create.add_child_elem(data_protocols_obj) try: self.server.invoke_successfully(interface_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg='Error Creating interface %s: %s' % (self.parameters['interface_name'], to_native(exc)), exception=traceback.format_exc()) def delete_interface(self, current_status): ''' calling zapi to delete interface ''' if current_status == 'up': self.parameters['admin_status'] = 'down' self.modify_interface({'admin_status': 'down'}) interface_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'net-interface-delete', **{'interface-name': self.parameters['interface_name'], 'vserver': self.parameters['vserver']}) try: self.server.invoke_successfully(interface_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg='Error deleting interface %s: %s' % (self.parameters['interface_name'], to_native(exc)), exception=traceback.format_exc()) def modify_interface(self, modify): """ Modify the interface. """ options = {'interface-name': self.parameters['interface_name'], 'vserver': self.parameters['vserver'] } NetAppOntapInterface.set_options(options, modify) interface_modify = netapp_utils.zapi.NaElement.create_node_with_children('net-interface-modify', **options) try: self.server.invoke_successfully(interface_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as err: self.module.fail_json(msg='Error modifying interface %s: %s' % (self.parameters['interface_name'], to_native(err)), exception=traceback.format_exc()) def autosupport_log(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_interface", cserver) def apply(self): ''' calling all interface features ''' self.autosupport_log() current = self.get_interface() # rename and create are mutually exclusive cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_interface() elif cd_action == 'delete': self.delete_interface(current['admin_status']) elif modify: self.modify_interface(modify) self.module.exit_json(changed=self.na_helper.changed)
class ElementSWVlan(object): """ class to handle VLAN operations """ def __init__(self): """ Setup Ansible parameters and ElementSW 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'), name=dict(required=False, type='str'), vlan_tag=dict(required=True, type='str'), svip=dict(required=False, type='str'), netmask=dict(required=False, type='str'), gateway=dict(required=False, type='str'), namespace=dict(required=False, type='bool'), attributes=dict(required=False, type='dict'), address_blocks=dict(required=False, type='list'))) 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.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.elementsw_helper = NaElementSWModule(self.elem) # add telemetry attributes if self.parameters.get('attributes') is not None: self.parameters['attributes'].update( self.elementsw_helper.set_element_attributes( source='na_elementsw_vlan')) else: self.parameters[ 'attributes'] = self.elementsw_helper.set_element_attributes( source='na_elementsw_vlan') def validate_keys(self): """ Validate if all required keys are present before creating """ required_keys = ['address_blocks', 'svip', 'netmask', 'name'] if all(item in self.parameters.keys() for item in required_keys) is False: self.module.fail_json( msg= "One or more required fields %s for creating VLAN is missing" % required_keys) addr_blk_fields = ['start', 'size'] for address in self.parameters['address_blocks']: if 'start' not in address or 'size' not in address: self.module.fail_json( msg= "One or more required fields %s for address blocks is missing" % addr_blk_fields) def create_network(self): """ Add VLAN """ try: self.validate_keys() create_params = self.parameters.copy() for key in [ 'username', 'hostname', 'password', 'state', 'vlan_tag' ]: del create_params[key] self.elem.add_virtual_network( virtual_network_tag=self.parameters['vlan_tag'], **create_params) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error creating VLAN %s" % self.parameters['vlan_tag'], exception=to_native(err)) def delete_network(self): """ Remove VLAN """ try: self.elem.remove_virtual_network( virtual_network_tag=self.parameters['vlan_tag']) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error deleting VLAN %s" % self.parameters['vlan_tag'], exception=to_native(err)) def modify_network(self, modify): """ Modify the VLAN """ try: self.elem.modify_virtual_network( virtual_network_tag=self.parameters['vlan_tag'], **modify) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error modifying VLAN %s" % self.parameters['vlan_tag'], exception=to_native(err)) def get_network_details(self): """ Check existing VLANs :return: vlan details if found, None otherwise :type: dict """ vlans = self.elem.list_virtual_networks( virtual_network_tag=self.parameters['vlan_tag']) vlan_details = dict() for vlan in vlans.virtual_networks: if vlan is not None: vlan_details['name'] = vlan.name vlan_details['address_blocks'] = list() for address in vlan.address_blocks: vlan_details['address_blocks'].append({ 'start': address.start, 'size': address.size }) vlan_details['svip'] = vlan.svip vlan_details['gateway'] = vlan.gateway vlan_details['netmask'] = vlan.netmask vlan_details['namespace'] = vlan.namespace vlan_details['attributes'] = vlan.attributes return vlan_details return None def apply(self): """ Call create / delete / modify vlan methods """ network = self.get_network_details() # calling helper to determine action cd_action = self.na_helper.get_cd_action(network, self.parameters) modify = self.na_helper.get_modified_attributes( network, self.parameters) if cd_action == "create": self.create_network() elif cd_action == "delete": self.delete_network() elif modify: self.modify_network(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPClusterPeer(object): """ Class with cluster peer methods """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), source_intercluster_lifs=dict( required=False, type='list', aliases=['source_intercluster_lif']), dest_intercluster_lifs=dict(required=False, type='list', aliases=['dest_intercluster_lif' ]), passphrase=dict(required=False, type='str', no_log=True), dest_hostname=dict(required=True, type='str'), dest_username=dict(required=False, type='str'), dest_password=dict(required=False, type='str', no_log=True), source_cluster_name=dict(required=False, type='str'), dest_cluster_name=dict(required=False, type='str'))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_together=[[ 'source_intercluster_lifs', 'dest_intercluster_lifs' ]], required_if=[('state', 'absent', [ 'source_cluster_name', 'dest_cluster_name' ])], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) # set destination server connection self.module.params['hostname'] = self.parameters['dest_hostname'] 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_server = netapp_utils.setup_na_ontap_zapi( module=self.module) # reset to source host connection for asup logs self.module.params['hostname'] = self.parameters['hostname'] def cluster_peer_get_iter(self, cluster): """ Compose NaElement object to query current source cluster using peer-cluster-name and peer-addresses parameters :param cluster: type of cluster (source or destination) :return: NaElement object for cluster-get-iter with query """ cluster_peer_get = netapp_utils.zapi.NaElement('cluster-peer-get-iter') query = netapp_utils.zapi.NaElement('query') cluster_peer_info = netapp_utils.zapi.NaElement('cluster-peer-info') if cluster == 'source': peer_lifs, peer_cluster = 'dest_intercluster_lifs', 'dest_cluster_name' else: peer_lifs, peer_cluster = 'source_intercluster_lifs', 'source_cluster_name' if self.parameters.get(peer_lifs): peer_addresses = netapp_utils.zapi.NaElement('peer-addresses') for peer in self.parameters.get(peer_lifs): peer_addresses.add_new_child('remote-inet-address', peer) cluster_peer_info.add_child_elem(peer_addresses) if self.parameters.get(peer_cluster): cluster_peer_info.add_new_child('cluster-name', self.parameters[peer_cluster]) query.add_child_elem(cluster_peer_info) cluster_peer_get.add_child_elem(query) return cluster_peer_get def cluster_peer_get(self, cluster): """ Get current cluster peer info :param cluster: type of cluster (source or destination) :return: Dictionary of current cluster peer details if query successful, else return None """ cluster_peer_get_iter = self.cluster_peer_get_iter(cluster) result, cluster_info = None, dict() if cluster == 'source': server = self.server else: server = self.dest_server try: result = server.invoke_successfully(cluster_peer_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching cluster peer %s: %s' % (self.parameters['dest_cluster_name'], to_native(error)), exception=traceback.format_exc()) # return cluster peer details if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) >= 1: cluster_peer_info = result.get_child_by_name( 'attributes-list').get_child_by_name('cluster-peer-info') cluster_info['cluster_name'] = cluster_peer_info.get_child_content( 'cluster-name') peers = cluster_peer_info.get_child_by_name('peer-addresses') cluster_info['peer-addresses'] = [ peer.get_content() for peer in peers.get_children() ] return cluster_info return None def cluster_peer_delete(self, cluster): """ Delete a cluster peer on source or destination For source cluster, peer cluster-name = destination cluster name and vice-versa :param cluster: type of cluster (source or destination) :return: """ if cluster == 'source': server, peer_cluster_name = self.server, self.parameters[ 'dest_cluster_name'] else: server, peer_cluster_name = self.dest_server, self.parameters[ 'source_cluster_name'] cluster_peer_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-peer-delete', **{'cluster-name': peer_cluster_name}) try: server.invoke_successfully(cluster_peer_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting cluster peer %s: %s' % (peer_cluster_name, to_native(error)), exception=traceback.format_exc()) def cluster_peer_create(self, cluster): """ Create a cluster peer on source or destination For source cluster, peer addresses = destination inter-cluster LIFs and vice-versa :param cluster: type of cluster (source or destination) :return: None """ cluster_peer_create = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-peer-create') if self.parameters.get('passphrase') is not None: cluster_peer_create.add_new_child('passphrase', self.parameters['passphrase']) peer_addresses = netapp_utils.zapi.NaElement('peer-addresses') if cluster == 'source': server, peer_address = self.server, self.parameters[ 'dest_intercluster_lifs'] else: server, peer_address = self.dest_server, self.parameters[ 'source_intercluster_lifs'] for each in peer_address: peer_addresses.add_new_child('remote-inet-address', each) cluster_peer_create.add_child_elem(peer_addresses) try: server.invoke_successfully(cluster_peer_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating cluster peer %s: %s' % (peer_address, to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to cluster peer :return: None """ self.asup_log_for_cserver("na_ontap_cluster_peer") source = self.cluster_peer_get('source') destination = self.cluster_peer_get('destination') source_action = self.na_helper.get_cd_action(source, self.parameters) destination_action = self.na_helper.get_cd_action( destination, self.parameters) self.na_helper.changed = False # create only if expected cluster peer relation is not present on both source and destination clusters if source_action == 'create' and destination_action == 'create': self.cluster_peer_create('source') self.cluster_peer_create('destination') self.na_helper.changed = True # delete peer relation in cluster where relation is present else: if source_action == 'delete': self.cluster_peer_delete('source') self.na_helper.changed = True if destination_action == 'delete': self.cluster_peer_delete('destination') self.na_helper.changed = True self.module.exit_json(changed=self.na_helper.changed) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver)
class NetAppOntapClusterHA(object): """ object initialize and class methods """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def modify_cluster_ha(self, configure): """ Enable or disable HA on cluster :return: None """ cluster_ha_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-ha-modify', **{'ha-configured': configure}) try: self.server.invoke_successfully(cluster_ha_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying cluster HA to %s: %s' % (configure, to_native(error)), exception=traceback.format_exc()) def get_cluster_ha_enabled(self): """ Get current cluster HA details :return: dict if enabled, None if disabled """ cluster_ha_get = netapp_utils.zapi.NaElement('cluster-ha-get') try: result = self.server.invoke_successfully(cluster_ha_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching cluster HA details', exception=traceback.format_exc()) cluster_ha_info = result.get_child_by_name( 'attributes').get_child_by_name('cluster-ha-info') if cluster_ha_info.get_child_content('ha-configured') == 'true': return {'ha-configured': True} return None def apply(self): """ Apply action to cluster HA """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_cluster_ha", cserver) current = self.get_cluster_ha_enabled() cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create': self.modify_cluster_ha("true") elif cd_action == 'delete': self.modify_cluster_ha("false") self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSVM(object): def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), root_volume=dict(type='str'), root_volume_aggregate=dict(type='str'), root_volume_security_style=dict( type='str', choices=['unix', 'ntfs', 'mixed', 'unified']), allowed_protocols=dict(type='list'), aggr_list=dict(type='list'), ipspace=dict(type='str', required=False), snapshot_policy=dict(type='str', required=False), language=dict(type='str', required=False), subtype=dict(choices=[ 'default', 'dp_destination', 'sync_source', 'sync_destination' ]), comment=dict(type="str", required=False))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def get_vserver(self, vserver_name=None): """ Checks if vserver exists. :return: vserver object if vserver found None if vserver is not found :rtype: object/None """ if vserver_name is None: vserver_name = self.parameters['name'] vserver_info = netapp_utils.zapi.NaElement('vserver-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-info', **{'vserver-name': vserver_name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) vserver_info.add_child_elem(query) result = self.server.invoke_successfully(vserver_info, enable_tunneling=False) vserver_details = None if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1): attributes_list = result.get_child_by_name('attributes-list') vserver_info = attributes_list.get_child_by_name('vserver-info') aggr_list = list() ''' vserver aggr-list can be empty by default''' get_list = vserver_info.get_child_by_name('aggr-list') if get_list is not None: aggregates = get_list.get_children() for aggr in aggregates: aggr_list.append(aggr.get_content()) protocols = list() '''allowed-protocols is not empty for data SVM, but is for node SVM''' allowed_protocols = vserver_info.get_child_by_name( 'allowed-protocols') if allowed_protocols is not None: get_protocols = allowed_protocols.get_children() for protocol in get_protocols: protocols.append(protocol.get_content()) vserver_details = { 'name': vserver_info.get_child_content('vserver-name'), 'root_volume': vserver_info.get_child_content('root-volume'), 'root_volume_aggregate': vserver_info.get_child_content('root-volume-aggregate'), 'root_volume_security_style': vserver_info.get_child_content('root-volume-security-style'), 'subtype': vserver_info.get_child_content('vserver-subtype'), 'aggr_list': aggr_list, 'language': vserver_info.get_child_content('language'), 'snapshot_policy': vserver_info.get_child_content('snapshot-policy'), 'allowed_protocols': protocols, 'ipspace': vserver_info.get_child_content('ipspace'), 'comment': vserver_info.get_child_content('comment') } return vserver_details def create_vserver(self): options = {'vserver-name': self.parameters['name']} self.add_parameter_to_dict(options, 'root_volume', 'root-volume') self.add_parameter_to_dict(options, 'root_volume_aggregate', 'root-volume-aggregate') self.add_parameter_to_dict(options, 'root_volume_security_style', 'root-volume-security-style') self.add_parameter_to_dict(options, 'language', 'language') self.add_parameter_to_dict(options, 'ipspace', 'ipspace') self.add_parameter_to_dict(options, 'snapshot_policy', 'snapshot-policy') self.add_parameter_to_dict(options, 'subtype', 'vserver-subtype') self.add_parameter_to_dict(options, 'comment', 'comment') vserver_create = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-create', **options) try: self.server.invoke_successfully(vserver_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error provisioning SVM %s: %s' % (self.parameters['name'], to_native(e)), exception=traceback.format_exc()) # add allowed-protocols, aggr-list after creation, # since vserver-create doesn't allow these attributes during creation options = dict() for key in ('allowed_protocols', 'aggr_list'): if self.parameters.get(key): options[key] = self.parameters[key] if options: self.modify_vserver(options) def delete_vserver(self): vserver_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-destroy', **{'vserver-name': self.parameters['name']}) try: self.server.invoke_successfully(vserver_delete, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error deleting SVM %s: %s' % (self.parameters['name'], to_native(e)), exception=traceback.format_exc()) def rename_vserver(self): vserver_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-rename', **{ 'vserver-name': self.parameters['from_name'], 'new-name': self.parameters['name'] }) try: self.server.invoke_successfully(vserver_rename, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error renaming SVM %s: %s' % (self.parameters['from_name'], to_native(e)), exception=traceback.format_exc()) def modify_vserver(self, modify): ''' Modify vserver. :param modify: list of modify attributes ''' vserver_modify = netapp_utils.zapi.NaElement('vserver-modify') vserver_modify.add_new_child('vserver-name', self.parameters['name']) for attribute in modify: if attribute == 'language': vserver_modify.add_new_child('language', self.parameters['language']) if attribute == 'snapshot_policy': vserver_modify.add_new_child( 'snapshot_policy', self.parameters['snapshot_policy']) if attribute == 'comment': vserver_modify.add_new_child('comment', self.parameters['comment']) if attribute == 'allowed_protocols': allowed_protocols = netapp_utils.zapi.NaElement( 'allowed-protocols') for protocol in self.parameters['allowed_protocols']: allowed_protocols.add_new_child('protocol', protocol) vserver_modify.add_child_elem(allowed_protocols) if attribute == 'aggr_list': aggregates = netapp_utils.zapi.NaElement('aggr-list') for aggr in self.parameters['aggr_list']: aggregates.add_new_child('aggr-name', aggr) vserver_modify.add_child_elem(aggregates) try: self.server.invoke_successfully(vserver_modify, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error modifying SVM %s: %s' % (self.parameters['name'], to_native(e)), exception=traceback.format_exc()) def add_parameter_to_dict(self, adict, name, key=None, tostr=False): ''' add defined parameter (not None) to adict using key. :param adict: a dictionary. :param name: name in self.parameters. :param key: key in adict. :param tostr: boolean. ''' if key is None: key = name if self.parameters.get(name) is not None: if tostr: adict[key] = str(self.parameters.get(name)) else: adict[key] = self.parameters.get(name) def apply(self): '''Call create/modify/delete operations.''' self.asup_log_for_cserver("na_ontap_svm") current = self.get_vserver() cd_action, rename = None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_vserver(self.parameters['from_name']), current) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes( current, self.parameters) for attribute in modify: if attribute in [ 'root_volume', 'root_volume_aggregate', 'root_volume_security_style', 'subtype', 'ipspace' ]: self.module.fail_json( msg='Error modifying SVM %s: can not modify %s.' % (self.parameters['name'], attribute)) if attribute == 'language': # Ontap documentation uses C.UTF-8, but actually stores as c.utf_8. if self.parameters['language'].lower() == 'c.utf-8': self.parameters['language'] = 'c.utf_8' if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_vserver() # If rename is True, cd_action is None, but modify could be true or false. if cd_action == 'create': self.create_vserver() elif cd_action == 'delete': self.delete_vserver() elif modify: self.modify_vserver(modify) self.module.exit_json(changed=self.na_helper.changed) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver)
class NetAppOntapIfGrp(object): """ Create, Modifies and Destroys a IfGrp """ def __init__(self): """ Initialize the Ontap IfGrp class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, choices=['present', 'absent'], default='present'), distribution_function=dict(required=False, type='str', choices=['mac', 'ip', 'sequential', 'port']), name=dict(required=True, type='str'), mode=dict(required=False, type='str'), node=dict(required=True, type='str'), ports=dict(required=False, type='list', aliases=["port"]), )) self.module = AnsibleModule( argument_spec=self.argument_spec, required_if=[ ('state', 'present', ['distribution_function', 'mode']) ], supports_check_mode=True ) # set up variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) return def get_if_grp(self): """ Return details about the if_group :param: name : Name of the if_group :return: Details about the if_group. None if not found. :rtype: dict """ if_group_iter = netapp_utils.zapi.NaElement('net-port-get-iter') if_group_info = netapp_utils.zapi.NaElement('net-port-info') if_group_info.add_new_child('port', self.parameters['name']) if_group_info.add_new_child('port-type', 'if_group') if_group_info.add_new_child('node', self.parameters['node']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(if_group_info) if_group_iter.add_child_elem(query) try: result = self.server.invoke_successfully(if_group_iter, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error getting if_group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) return_value = None if result.get_child_by_name('num-records') and int(result['num-records']) >= 1: if_group_attributes = result['attributes-list']['net-port-info'] return_value = { 'name': if_group_attributes['port'], 'distribution_function': if_group_attributes['ifgrp-distribution-function'], 'mode': if_group_attributes['ifgrp-mode'], 'node': if_group_attributes['node'], } return return_value def get_if_grp_ports(self): """ Return ports of the if_group :param: name : Name of the if_group :return: Ports of the if_group. None if not found. :rtype: dict """ if_group_iter = netapp_utils.zapi.NaElement('net-port-ifgrp-get') if_group_iter.add_new_child('ifgrp-name', self.parameters['name']) if_group_iter.add_new_child('node', self.parameters['node']) try: result = self.server.invoke_successfully(if_group_iter, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error getting if_group ports %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) port_list = [] if result.get_child_by_name('attributes'): if_group_attributes = result['attributes']['net-ifgrp-info'] if if_group_attributes.get_child_by_name('ports'): ports = if_group_attributes.get_child_by_name('ports').get_children() for each in ports: port_list.append(each.get_content()) return {'ports': port_list} def create_if_grp(self): """ Creates a new ifgrp """ route_obj = netapp_utils.zapi.NaElement("net-port-ifgrp-create") route_obj.add_new_child("distribution-function", self.parameters['distribution_function']) route_obj.add_new_child("ifgrp-name", self.parameters['name']) route_obj.add_new_child("mode", self.parameters['mode']) route_obj.add_new_child("node", self.parameters['node']) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating if_group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) for port in self.parameters.get('ports'): self.add_port_to_if_grp(port) def delete_if_grp(self): """ Deletes a ifgrp """ route_obj = netapp_utils.zapi.NaElement("net-port-ifgrp-destroy") route_obj.add_new_child("ifgrp-name", self.parameters['name']) route_obj.add_new_child("node", self.parameters['node']) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting if_group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def add_port_to_if_grp(self, port): """ adds port to a ifgrp """ route_obj = netapp_utils.zapi.NaElement("net-port-ifgrp-add-port") route_obj.add_new_child("ifgrp-name", self.parameters['name']) route_obj.add_new_child("port", port) route_obj.add_new_child("node", self.parameters['node']) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error adding port %s to if_group %s: %s' % (port, self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_ports(self, current_ports): add_ports = set(self.parameters['ports']) - set(current_ports) remove_ports = set(current_ports) - set(self.parameters['ports']) for port in add_ports: self.add_port_to_if_grp(port) for port in remove_ports: self.remove_port_to_if_grp(port) def remove_port_to_if_grp(self, port): """ removes port from a ifgrp """ route_obj = netapp_utils.zapi.NaElement("net-port-ifgrp-remove-port") route_obj.add_new_child("ifgrp-name", self.parameters['name']) route_obj.add_new_child("port", port) route_obj.add_new_child("node", self.parameters['node']) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error removing port %s to if_group %s: %s' % (port, self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_net_ifgrp", cserver) def apply(self): self.autosupport_log() current, modify = self.get_if_grp(), None cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': current_ports = self.get_if_grp_ports() modify = self.na_helper.get_modified_attributes(current_ports, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_if_grp() elif cd_action == 'delete': self.delete_if_grp() elif modify: self.modify_ports(current_ports['ports']) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapBroadcastDomain(object): """ Create, Modifies and Destroys a Broadcast domain """ def __init__(self): """ Initialize the ONTAP Broadcast Domain class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str', aliases=["broadcast_domain"]), ipspace=dict(required=False, type='str'), mtu=dict(required=False, type='str'), ports=dict(required=False, type='list'), from_name=dict(required=False, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) return def get_broadcast_domain(self, broadcast_domain=None): """ Return details about the broadcast domain :param broadcast_domain: specific broadcast domain to get. :return: Details about the broadcast domain. None if not found. :rtype: dict """ if broadcast_domain is None: broadcast_domain = self.parameters['name'] domain_get_iter = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-get-iter') broadcast_domain_info = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-info') broadcast_domain_info.add_new_child('broadcast-domain', broadcast_domain) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(broadcast_domain_info) domain_get_iter.add_child_elem(query) result = self.server.invoke_successfully(domain_get_iter, True) domain_exists = None # check if broadcast_domain exists if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: domain_info = result.get_child_by_name('attributes-list').\ get_child_by_name('net-port-broadcast-domain-info') domain_name = domain_info.get_child_content('broadcast-domain') domain_mtu = domain_info.get_child_content('mtu') domain_ipspace = domain_info.get_child_content('ipspace') domain_ports = domain_info.get_child_by_name('ports') if domain_ports is not None: ports = [ port.get_child_content('port') for port in domain_ports.get_children() ] else: ports = [] domain_exists = { 'domain-name': domain_name, 'mtu': domain_mtu, 'ipspace': domain_ipspace, 'ports': ports } return domain_exists def create_broadcast_domain(self): """ Creates a new broadcast domain """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-create') domain_obj.add_new_child("broadcast-domain", self.parameters['name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) if self.parameters.get('mtu'): domain_obj.add_new_child("mtu", self.parameters['mtu']) if self.parameters.get('ports'): ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in self.parameters['ports']: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_broadcast_domain(self, broadcast_domain=None): """ Deletes a broadcast domain """ if broadcast_domain is None: broadcast_domain = self.parameters['name'] domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-destroy') domain_obj.add_new_child("broadcast-domain", broadcast_domain) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) try: self.server.invoke_successfully(domain_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting broadcast domain %s: %s' % (broadcast_domain, to_native(error)), exception=traceback.format_exc()) def modify_broadcast_domain(self): """ Modifies ipspace and mtu options of a broadcast domain """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-modify') domain_obj.add_new_child("broadcast-domain", self.parameters['name']) if self.parameters.get('mtu'): domain_obj.add_new_child("mtu", self.parameters['mtu']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) try: self.server.invoke_successfully(domain_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def split_broadcast_domain(self): """ split broadcast domain """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-split') domain_obj.add_new_child("broadcast-domain", self.parameters['from_name']) domain_obj.add_new_child("new-broadcast-domain", self.parameters['name']) if self.parameters.get('ports'): ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in self.parameters['ports']: ports_obj.add_new_child('net-qualified-port-name', port) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) try: self.server.invoke_successfully(domain_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error splitting broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if len(self.get_broadcast_domain_ports( self.parameters['from_name'])) == 0: self.delete_broadcast_domain(self.parameters['from_name']) def modify_redirect(self, modify): """ :param modify: modify attributes. """ for attribute in modify.keys(): if attribute == 'mtu': self.modify_broadcast_domain() if attribute == 'ports': self.modify_broadcast_domain_ports() def get_modify_attributes(self, current, split): """ :param current: current state. :param split: True or False of split action. :return: list of modified attributes. """ modify = None if self.parameters['state'] == 'present': # split already handled ipspace and ports. if self.parameters.get('from_name'): current = self.get_broadcast_domain( self.parameters['from_name']) if split: modify = self.na_helper.get_modified_attributes( current, self.parameters) if modify.get('ipspace'): del modify['ipspace'] if modify.get('ports'): del modify['ports'] # ipspace can not be modified. else: modify = self.na_helper.get_modified_attributes( current, self.parameters) if modify.get('ipspace'): self.module.fail_json( msg= 'A domain ipspace can not be modified after the domain has been created.', exception=traceback.format_exc()) return modify def modify_broadcast_domain_ports(self): """ compare current and desire ports. Call add or remove ports methods if needed. :return: None. """ current_ports = self.get_broadcast_domain_ports() expect_ports = self.parameters['ports'] # if want to remove all ports, simply delete the broadcast domain. if len(expect_ports) == 0: self.delete_broadcast_domain() return ports_to_remove = list(set(current_ports) - set(expect_ports)) ports_to_add = list(set(expect_ports) - set(current_ports)) if len(ports_to_add) > 0: self.add_broadcast_domain_ports(ports_to_add) if len(ports_to_remove) > 0: self.delete_broadcast_domain_ports(ports_to_remove) def add_broadcast_domain_ports(self, ports): """ Creates new broadcast domain ports """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-add-ports') domain_obj.add_new_child("broadcast-domain", self.parameters['name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) if ports: ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in ports: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) return True except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating port for broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_broadcast_domain_ports(self, ports): """ Deletes broadcast domain ports :param: ports to be deleted. """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-remove-ports') domain_obj.add_new_child("broadcast-domain", self.parameters['name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) if ports: ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in ports: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) return True except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting port for broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def get_broadcast_domain_ports(self, broadcast_domain=None): """ Return details about the broadcast domain ports. :return: Details about the broadcast domain ports. None if not found. :rtype: list """ if broadcast_domain is None: broadcast_domain = self.parameters['name'] domain_get_iter = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-get-iter') broadcast_domain_info = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-info') broadcast_domain_info.add_new_child('broadcast-domain', broadcast_domain) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(broadcast_domain_info) domain_get_iter.add_child_elem(query) result = self.server.invoke_successfully(domain_get_iter, True) ports = [] if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: domain_info = result.get_child_by_name( 'attributes-list').get_child_by_name( 'net-port-broadcast-domain-info') domain_ports = domain_info.get_child_by_name('ports') if domain_ports is not None: ports = [ port.get_child_content('port') for port in domain_ports.get_children() ] return ports def apply(self): """ Run Module based on play book """ self.asup_log_for_cserver("na_ontap_broadcast_domain") current = self.get_broadcast_domain() cd_action, split = None, None cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create': # either create new domain or split domain. if self.parameters.get('from_name'): split = self.na_helper.is_rename_action( self.get_broadcast_domain(self.parameters['from_name']), current) if split is None: self.module.fail_json( msg='A domain can not be split if it does not exist.', exception=traceback.format_exc()) if split: cd_action = None modify = self.get_modify_attributes(current, split) if self.na_helper.changed: if self.module.check_mode: pass else: if split: self.split_broadcast_domain() if cd_action == 'create': self.create_broadcast_domain() elif cd_action == 'delete': self.delete_broadcast_domain() elif modify: self.modify_redirect(modify) self.module.exit_json(changed=self.na_helper.changed) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver)
class NetAppOntapAggregate(object): ''' object initialize and class methods ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(name=dict(required=True, type='str'), disks=dict(required=False, type='list'), disk_count=dict(required=False, type='int', default=None), disk_size=dict(required=False, type='int'), disk_type=dict(required=False, choices=[ 'ATA', 'BSAS', 'FCAL', 'FSAS', 'LUN', 'MSATA', 'SAS', 'SSD', 'VMDISK' ]), from_name=dict(required=False, type='str'), mirror_disks=dict(required=False, type='list'), nodes=dict(required=False, type='list'), is_mirrored=dict(required=False, type='bool'), raid_size=dict(required=False, type='int'), raid_type=dict(required=False, choices=['raid4', 'raid_dp', 'raid_tec']), service_state=dict(required=False, choices=['online', 'offline']), spare_pool=dict(required=False, choices=['Pool0', 'Pool1']), state=dict(required=False, choices=['present', 'absent'], default='present'), unmount_volumes=dict(required=False, type='bool'), wait_for_online=dict(required=False, type='bool', default=False), time_out=dict(required=False, type='int', default=100))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[ ('service_state', 'offline', ['unmount_volumes']), ], mutually_exclusive=[ ('is_mirrored', 'disks'), ('is_mirrored', 'mirror_disks'), ('is_mirrored', 'spare_pool'), ('spare_pool', 'disks') ], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get( 'mirror_disks' ) is not None and self.parameters.get('disks') is None: self.module.fail_json( mgs="mirror_disks require disks options to be set") if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def aggr_get_iter(self, name): """ Return aggr-get-iter query results :param name: Name of the aggregate :return: NaElement if aggregate found, None otherwise """ aggr_get_iter = netapp_utils.zapi.NaElement('aggr-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-attributes', **{'aggregate-name': name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) aggr_get_iter.add_child_elem(query) result = None try: result = self.server.invoke_successfully(aggr_get_iter, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: # Error 13040 denotes an aggregate not being found. if to_native(error.code) == "13040": pass else: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) return result def get_aggr(self, name=None): """ Fetch details if aggregate exists. :param name: Name of the aggregate to be fetched :return: Dictionary of current details if aggregate found None if aggregate is not found """ if name is None: name = self.parameters['name'] aggr_get = self.aggr_get_iter(name) if (aggr_get and aggr_get.get_child_by_name('num-records') and int(aggr_get.get_child_content('num-records')) >= 1): current_aggr = dict() attr = aggr_get.get_child_by_name( 'attributes-list').get_child_by_name('aggr-attributes') current_aggr['service_state'] = attr.get_child_by_name( 'aggr-raid-attributes').get_child_content('state') return current_aggr return None def aggregate_online(self): """ Set state of an offline aggregate to online :return: None """ online_aggr = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-online', **{ 'aggregate': self.parameters['name'], 'force-online': 'true' }) try: self.server.invoke_successfully(online_aggr, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error changing the state of aggregate %s to %s: %s' % (self.parameters['name'], self.parameters['service_state'], to_native(error)), exception=traceback.format_exc()) def aggregate_offline(self): """ Set state of an online aggregate to offline :return: None """ offline_aggr = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-offline', **{ 'aggregate': self.parameters['name'], 'force-offline': 'false', 'unmount-volumes': str(self.parameters['unmount_volumes']) }) try: self.server.invoke_successfully(offline_aggr, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error changing the state of aggregate %s to %s: %s' % (self.parameters['name'], self.parameters['service_state'], to_native(error)), exception=traceback.format_exc()) def create_aggr(self): """ Create aggregate :return: None """ if not self.parameters.get('disk_count'): self.module.fail_json(msg='Error provisioning aggregate %s: \ disk_count is required' % self.parameters['name']) options = { 'aggregate': self.parameters['name'], 'disk-count': str(self.parameters['disk_count']) } if self.parameters.get('disk_type'): options['disk-type'] = self.parameters['disk_type'] if self.parameters.get('raid_size'): options['raid-size'] = str(self.parameters['raid_size']) if self.parameters.get('raid_type'): options['raid-type'] = self.parameters['raid_type'] if self.parameters.get('disk_size'): options['disk-size'] = str(self.parameters['disk_size']) if self.parameters.get('is_mirrored'): options['is-mirrored'] = str(self.parameters['is_mirrored']) if self.parameters.get('spare_pool'): options['spare-pool'] = self.parameters['spare_pool'] if self.parameters.get('raid_type'): options['raid-type'] = self.parameters['raid_type'] aggr_create = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-create', **options) if self.parameters.get('nodes'): nodes_obj = netapp_utils.zapi.NaElement('nodes') aggr_create.add_child_elem(nodes_obj) for node in self.parameters['nodes']: nodes_obj.add_new_child('node-name', node) if self.parameters.get('disks'): disks_obj = netapp_utils.zapi.NaElement('disk-info') for disk in self.parameters.get('disks'): disks_obj.add_new_child('name', disk) aggr_create.add_child_elem(disks_obj) if self.parameters.get('mirror_disks'): mirror_disks_obj = netapp_utils.zapi.NaElement('disk-info') for disk in self.parameters.get('mirror_disks'): mirror_disks_obj.add_new_child('name', disk) aggr_create.add_child_elem(mirror_disks_obj) try: self.server.invoke_successfully(aggr_create, enable_tunneling=False) if self.parameters.get('wait_for_online'): # round off time_out retries = (self.parameters['time_out'] + 5) / 10 current = self.get_aggr() status = None if current is None else current['service_state'] while status != 'online' and retries > 0: time.sleep(10) retries = retries - 1 current = self.get_aggr() status = None if current is None else current[ 'service_state'] except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error provisioning aggregate %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_aggr(self): """ Delete aggregate. :return: None """ aggr_destroy = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-destroy', **{'aggregate': self.parameters['name']}) try: self.server.invoke_successfully(aggr_destroy, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error removing aggregate %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_aggregate(self): """ Rename aggregate. """ aggr_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-rename', **{ 'aggregate': self.parameters['from_name'], 'new-aggregate-name': self.parameters['name'] }) try: self.server.invoke_successfully(aggr_rename, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error renaming aggregate %s: %s" % (self.parameters['from_name'], to_native(error)), exception=traceback.format_exc()) def modify_aggr(self, modify): """ Modify state of the aggregate :param modify: dictionary of parameters to be modified :return: None """ if modify['service_state'] == 'offline': self.aggregate_offline() elif modify['service_state'] == 'online': self.aggregate_online() def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver) def apply(self): """ Apply action to the aggregate :return: None """ self.asup_log_for_cserver("na_ontap_aggregate") current = self.get_aggr() # rename and create are mutually exclusive rename, cd_action = None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_aggr(self.parameters['from_name']), current) if rename is None: self.module.fail_json( msg="Error renaming: aggregate %s does not exist" % self.parameters['from_name']) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_aggregate() elif cd_action == 'create': self.create_aggr() elif cd_action == 'delete': self.delete_aggr() elif modify: self.modify_aggr(modify) self.module.exit_json(changed=self.na_helper.changed)
class AwsCvsNetappSnapshot(object): """ Contains methods to parse arguments, derive details of AWS_CVS objects and send requests to AWS CVS via the restApi """ def __init__(self): """ Parse arguments, setup state variables, check parameters and ensure request module is installed """ self.argument_spec = netapp_utils.aws_cvs_host_argument_spec() self.argument_spec.update( dict(state=dict(required=True, choices=['present', 'absent']), region=dict(required=True, type='str'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), fileSystemId=dict(required=False, type='str'))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[ ('state', 'present', ['name', 'fileSystemId']), ], supports_check_mode=True) self.na_helper = NetAppModule() # set up state variables self.parameters = self.na_helper.set_parameters(self.module.params) # Calling generic AWSCVS restApi class self.restApi = AwsCvsRestAPI(self.module) # Checking for the parameters passed and create new parameters list self.data = {} for key in self.parameters.keys(): self.data[key] = self.parameters[key] def getSnapshotId(self, name): # Check if snapshot exists # Return snapshot Id If Snapshot is found, None otherwise list_snapshots, error = self.restApi.get('Snapshots') if error: self.module.fail_json(msg=error) for snapshot in list_snapshots: if snapshot['name'] == name: return snapshot['snapshotId'] return None def getfilesystemId(self): # Check given FileSystem is exists # Return fileSystemId is found, None otherwise list_filesystem, error = self.restApi.get('FileSystems') if error: self.module.fail_json(msg=error) for FileSystem in list_filesystem: if FileSystem['fileSystemId'] == self.parameters['fileSystemId']: return FileSystem['fileSystemId'] elif FileSystem['creationToken'] == self.parameters[ 'fileSystemId']: return FileSystem['fileSystemId'] return None def create_snapshot(self): # Create Snapshot api = 'Snapshots' response, error = self.restApi.post(api, self.data) if error: self.module.fail_json(msg=error) def rename_snapshot(self, snapshotId): # Rename Snapshot api = 'Snapshots/' + snapshotId response, error = self.restApi.put(api, self.data) if error: self.module.fail_json(msg=error) def delete_snapshot(self, snapshotId): # Delete Snapshot api = 'Snapshots/' + snapshotId data = None response, error = self.restApi.delete(api, self.data) if error: self.module.fail_json(msg=error) def apply(self): """ Perform pre-checks, call functions and exit """ self.snapshotId = self.getSnapshotId(self.data['name']) if self.snapshotId is None and 'fileSystemId' in self.data: self.fileSystemId = self.getfilesystemId() self.data['fileSystemId'] = self.fileSystemId if self.fileSystemId is None: self.module.fail_json( msg='Error: Specified filesystem id %s does not exist ' % self.data['fileSystemId']) cd_action = self.na_helper.get_cd_action(self.snapshotId, self.data) result_message = "" if self.na_helper.changed: if self.module.check_mode: # Skip changes result_message = "Check mode, skipping changes" else: if cd_action == "delete": self.delete_snapshot(self.snapshotId) result_message = "Snapshot Deleted" elif cd_action == "create": if 'from_name' in self.data: # If cd_action is create and from_name is given snapshotId = self.getSnapshotId(self.data['from_name']) if snapshotId is not None: # If resource pointed by from_name exists, rename the snapshot to name self.rename_snapshot(snapshotId) result_message = "Snapshot Updated" else: # If resource pointed by from_name does not exists, error out self.module.fail_json( msg="Resource does not exist : %s" % self.data['from_name']) else: self.create_snapshot() # If from_name is not defined, Create from scratch. result_message = "Snapshot Created" self.module.exit_json(changed=self.na_helper.changed, msg=result_message)
class NetAppOntapSubnet(object): """ Create, Modifies and Destroys a subnet """ def __init__(self): """ Initialize the ONTAP Subnet class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), broadcast_domain=dict(required=False, type='str'), gateway=dict(required=False, type='str'), ip_ranges=dict(required=False, type=list), ipspace=dict(required=False, type='str'), subnet=dict(required=False, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) return def get_subnet(self, name=None): """ Return details about the subnet :param: name : Name of the subnet :return: Details about the subnet. None if not found. :rtype: dict """ if name is None: name = self.parameters.get('name') subnet_iter = netapp_utils.zapi.NaElement('net-subnet-get-iter') subnet_info = netapp_utils.zapi.NaElement('net-subnet-info') subnet_info.add_new_child('subnet-name', name) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(subnet_info) subnet_iter.add_child_elem(query) result = self.server.invoke_successfully(subnet_iter, True) return_value = None # check if query returns the expected subnet if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: subnet_attributes = result.get_child_by_name('attributes-list').get_child_by_name('net-subnet-info') broadcast_domain = subnet_attributes.get_child_content('broadcast-domain') gateway = subnet_attributes.get_child_content('gateway') ipspace = subnet_attributes.get_child_content('ipspace') subnet = subnet_attributes.get_child_content('subnet') name = subnet_attributes.get_child_content('subnet-name') ip_ranges = [] range_obj = subnet_attributes.get_child_by_name('ip-ranges').get_children() for elem in range_obj: ip_ranges.append(elem.get_content()) return_value = { 'name': name, 'broadcast_domain': broadcast_domain, 'gateway': gateway, 'ip_ranges': ip_ranges, 'ipspace': ipspace, 'subnet': subnet } return return_value def create_subnet(self): """ Creates a new subnet """ options = {'subnet-name': self.parameters.get('name'), 'broadcast-domain': self.parameters.get('broadcast_domain'), 'subnet': self.parameters.get('subnet')} subnet_create = netapp_utils.zapi.NaElement.create_node_with_children( 'net-subnet-create', **options) if self.parameters.get('gateway'): subnet_create.add_new_child('gateway', self.parameters.get('gateway')) if self.parameters.get('ip_ranges'): subnet_ips = netapp_utils.zapi.NaElement('ip-ranges') subnet_create.add_child_elem(subnet_ips) for ip_range in self.parameters.get('ip_ranges'): subnet_ips.add_new_child('ip-range', ip_range) if self.parameters.get('ipspace'): subnet_create.add_new_child('ipspace', self.parameters.get('ipspace')) try: self.server.invoke_successfully(subnet_create, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating subnet %s: %s' % (self.parameters.get('name'), to_native(error)), exception=traceback.format_exc()) def delete_subnet(self): """ Deletes a subnet """ subnet_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'net-subnet-destroy', **{'subnet-name': self.parameters.get('name')}) try: self.server.invoke_successfully(subnet_delete, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting subnet %s: %s' % (self.parameters.get('name'), to_native(error)), exception=traceback.format_exc()) def modify_subnet(self): """ Modifies a subnet """ options = {'subnet-name': self.parameters.get('name')} subnet_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'net-subnet-modify', **options) if self.parameters.get('gateway'): subnet_modify.add_new_child('gateway', self.parameters.get('gateway')) if self.parameters.get('ip_ranges'): subnet_ips = netapp_utils.zapi.NaElement('ip-ranges') subnet_modify.add_child_elem(subnet_ips) for ip_range in self.parameters.get('ip_ranges'): subnet_ips.add_new_child('ip-range', ip_range) if self.parameters.get('ipspace'): subnet_modify.add_new_child('ipspace', self.parameters.get('ipspace')) if self.parameters.get('subnet'): subnet_modify.add_new_child('subnet', self.parameters.get('subnet')) try: self.server.invoke_successfully(subnet_modify, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying subnet %s: %s' % (self.parameters.get('name'), to_native(error)), exception=traceback.format_exc()) def rename_subnet(self): """ TODO """ options = {'subnet-name': self.parameters.get('from_name'), 'new-name': self.parameters.get('name')} subnet_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'net-subnet-rename', **options) if self.parameters.get('ipspace'): subnet_rename.add_new_child('ipspace', self.parameters.get('ipspace')) try: self.server.invoke_successfully(subnet_rename, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error renaming subnet %s: %s' % (self.parameters.get('name'), to_native(error)), exception=traceback.format_exc()) def apply(self): '''Apply action to subnet''' results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_net_subnet", cserver) current = self.get_subnet() cd_action, rename = None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action(self.get_subnet(self.parameters.get('from_name')), current) if rename is False: self.module.fail_json(msg="Error renaming: subnet %s does not exist" % self.parameters.get('from_name')) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes(current, self.parameters) for attribute in modify: if attribute in ['broadcast_domain']: self.module.fail_json(msg='Error modifying subnet %s: cannot modify broadcast_domain parameter.' % self.parameters.get('name')) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_subnet() # If rename is True, cd_action is NOne but modify could be true if cd_action == 'create': for attribute in ['subnet', 'broadcast_domain']: if not self.parameters.get(attribute): self.module.fail_json(msg='Error - missing required arguments: %s.' % attribute) self.create_subnet() elif cd_action == 'delete': self.delete_subnet() elif modify: self.modify_subnet() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapObjectStoreConfig(object): ''' object initialize and class methods ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( name=dict(required=True, type='str'), state=dict(required=False, choices=['present', 'absent'], default='present'), provider_type=dict(required=False, type='str'), server=dict(required=False, type='str'), container=dict(required=False, type='str'), access_key=dict(required=False, type='str'), secret_password=dict(required=False, type='str', no_log=True) )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def get_aggr_object_store(self): """ Fetch details if object store config exists. :return: Dictionary of current details if object store config found None if object store config is not found """ aggr_object_store_get_iter = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-object-store-config-get', **{'object-store-name': self.parameters['name']}) result = None try: result = self.server.invoke_successfully(aggr_object_store_get_iter, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: # Error 15661 denotes an object store not being found. if to_native(error.code) == "15661": pass else: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) return result def create_aggr_object_store(self): """ Create aggregate object store config :return: None """ required_keys = set(['provider_type', 'server', 'container', 'access_key']) if not required_keys.issubset(set(self.parameters.keys())): self.module.fail_json(msg='Error provisioning object store %s: one of the following parameters are missing ' '%s' % (self.parameters['name'], ', '.join(required_keys))) options = {'object-store-name': self.parameters['name'], 'provider-type': self.parameters['provider_type'], 'server': self.parameters['server'], 's3-name': self.parameters['container'], 'access-key': self.parameters['access_key']} if self.parameters.get('secret_password'): options['secret-password'] = self.parameters['secret_password'] object_store_create = netapp_utils.zapi.NaElement.create_node_with_children('aggr-object-store-config-create', **options) try: self.server.invoke_successfully(object_store_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error provisioning object store config %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_aggr_object_store(self): """ Delete aggregate object store config :return: None """ object_store_destroy = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-object-store-config-delete', **{'object-store-name': self.parameters['name']}) try: self.server.invoke_successfully(object_store_destroy, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error removing object store config %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver) def apply(self): """ Apply action to the object store config :return: None """ self.asup_log_for_cserver("na_ontap_object_store_config") current = self.get_aggr_object_store() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_aggr_object_store() elif cd_action == 'delete': self.delete_aggr_object_store() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapUnixUser(object): """ Common operations to manage users and roles. """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), group_id=dict(required=False, type='int'), id=dict(required=False, type='int'), full_name=dict(required=False, type='str'), vserver=dict(required=True, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def get_unix_user(self): """ Checks if the UNIX user exists. :return: dict() if user found None if user is not found """ get_unix_user = netapp_utils.zapi.NaElement( 'name-mapping-unix-user-get-iter') attributes = { 'query': { 'unix-user-info': { 'user-name': self.parameters['name'], 'vserver': self.parameters['vserver'], } } } get_unix_user.translate_struct(attributes) try: result = self.server.invoke_successfully(get_unix_user, enable_tunneling=True) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: user_info = result['attributes-list']['unix-user-info'] return { 'group_id': int(user_info['group-id']), 'id': int(user_info['user-id']), 'full_name': user_info['full-name'] } return None except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error getting UNIX user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_unix_user(self): """ Creates an UNIX user in the specified Vserver :return: None """ if self.parameters.get('group_id') is None or self.parameters.get( 'id') is None: self.module.fail_json( msg= 'Error: Missing one or more required parameters for create: (group_id, id)' ) user_create = netapp_utils.zapi.NaElement.create_node_with_children( 'name-mapping-unix-user-create', **{ 'user-name': self.parameters['name'], 'group-id': str(self.parameters['group_id']), 'user-id': str(self.parameters['id']) }) if self.parameters.get('full_name') is not None: user_create.add_new_child('full-name', self.parameters['full_name']) try: self.server.invoke_successfully(user_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating UNIX user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_unix_user(self): """ Deletes an UNIX user from a vserver :return: None """ user_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'name-mapping-unix-user-destroy', **{'user-name': self.parameters['name']}) try: self.server.invoke_successfully(user_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error removing UNIX user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_unix_user(self, params): user_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'name-mapping-unix-user-modify', **{'user-name': self.parameters['name']}) for key in params: if key == 'group_id': user_modify.add_new_child('group-id', str(params['group_id'])) if key == 'id': user_modify.add_new_child('user-id', str(params['id'])) if key == 'full_name': user_modify.add_new_child('full-name', params['full_name']) try: self.server.invoke_successfully(user_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying UNIX user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): """ Autosupport log for unix_user :return: None """ netapp_utils.ems_log_event("na_ontap_unix_user", self.server) def apply(self): """ Invoke appropriate action based on playbook parameters :return: None """ self.autosupport_log() current = self.get_unix_user() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.parameters['state'] == 'present' and cd_action is None: modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_unix_user() elif cd_action == 'delete': self.delete_unix_user() else: self.modify_unix_user(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPFlexCache(object): """ Class with FlexCache methods """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), origin_volume=dict(required=False, type='str'), origin_vserver=dict(required=False, type='str'), origin_cluster=dict(required=False, type='str'), auto_provision_as=dict(required=False, type='str'), volume=dict(required=True, type='str'), junction_path=dict(required=False, type='str'), size=dict(required=False, type='int'), size_unit=dict(default='gb', choices=[ 'bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb' ], type='str'), vserver=dict(required=True, type='str'), aggr_list=dict(required=False, type='list'), aggr_list_multiplier=dict(required=False, type='int'), force_offline=dict(required=False, type='bool', default=False), force_unmount=dict(required=False, type='bool', default=False), time_out=dict(required=False, type='int', default=180), )) self.module = AnsibleModule(argument_spec=self.argument_spec, mutually_exclusive=[ ('aggr_list', 'auto_provision_as'), ], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get('size'): self.parameters['size'] = self.parameters['size'] * \ netapp_utils.POW2_BYTE_MAP[self.parameters['size_unit']] # setup later if required self.origin_server = None if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def add_parameter_to_dict(self, adict, name, key=None, tostr=False): ''' add defined parameter (not None) to adict using key ''' if key is None: key = name if self.parameters.get(name) is not None: if tostr: adict[key] = str(self.parameters.get(name)) else: adict[key] = self.parameters.get(name) def get_job(self, jobid, server): """ Get job details by id """ job_get = netapp_utils.zapi.NaElement('job-get') job_get.add_new_child('job-id', jobid) try: result = server.invoke_successfully(job_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == "15661": # Not found return None self.module.fail_json(msg='Error fetching job info: %s' % to_native(error), exception=traceback.format_exc()) results = dict() job_info = result.get_child_by_name('attributes').get_child_by_name( 'job-info') results = { 'job-progress': job_info['job-progress'], 'job-state': job_info['job-state'] } if job_info.get_child_by_name('job-completion') is not None: results['job-completion'] = job_info['job-completion'] else: results['job-completion'] = None return results def check_job_status(self, jobid): """ Loop until job is complete """ server = self.server sleep_time = 5 time_out = self.parameters['time_out'] while time_out > 0: results = self.get_job(jobid, server) # If running as cluster admin, the job is owned by cluster vserver # rather than the target vserver. if results is None and server == self.server: results = netapp_utils.get_cserver(self.server) server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) continue if results is None: error = 'cannot locate job with id: %d' % jobid break if results['job-state'] in ('queued', 'running'): time.sleep(sleep_time) time_out -= sleep_time continue if results['job-state'] in ('success', 'failure'): break else: self.module.fail_json(msg='Unexpected job status in: %s' % repr(results)) if results is not None: if results['job-state'] == 'success': error = None elif results['job-state'] in ('queued', 'running'): error = 'job completion exceeded expected timer of: %s seconds' % \ self.parameters['time_out'] else: if results['job-completion'] is not None: error = results['job-completion'] else: error = results['job-progress'] return error def flexcache_get_iter(self): """ Compose NaElement object to query current FlexCache relation """ options = {'volume': self.parameters['volume']} self.add_parameter_to_dict(options, 'origin_volume', 'origin-volume') self.add_parameter_to_dict(options, 'origin_vserver', 'origin-vserver') self.add_parameter_to_dict(options, 'origin_cluster', 'origin-cluster') flexcache_info = netapp_utils.zapi.NaElement.create_node_with_children( 'flexcache-info', **options) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(flexcache_info) flexcache_get_iter = netapp_utils.zapi.NaElement('flexcache-get-iter') flexcache_get_iter.add_child_elem(query) return flexcache_get_iter def flexcache_get(self): """ Get current FlexCache relations :return: Dictionary of current FlexCache details if query successful, else None """ flexcache_get_iter = self.flexcache_get_iter() flex_info = dict() try: result = self.server.invoke_successfully(flexcache_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching FlexCache info: %s' % to_native(error), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: flexcache_info = result.get_child_by_name('attributes-list') \ .get_child_by_name('flexcache-info') flex_info['origin_cluster'] = flexcache_info.get_child_content( 'origin-cluster') flex_info['origin_volume'] = flexcache_info.get_child_content( 'origin-volume') flex_info['origin_vserver'] = flexcache_info.get_child_content( 'origin-vserver') flex_info['size'] = flexcache_info.get_child_content('size') flex_info['volume'] = flexcache_info.get_child_content('volume') flex_info['vserver'] = flexcache_info.get_child_content('vserver') flex_info['auto_provision_as'] = flexcache_info.get_child_content( 'auto-provision-as') return flex_info if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) > 1: msg = 'Multiple records found for %s:' % self.parameters['volume'] self.module.fail_json(msg='Error fetching FlexCache info: %s' % msg) return None def flexcache_create_async(self): """ Create a FlexCache relationship """ options = { 'origin-volume': self.parameters['origin_volume'], 'origin-vserver': self.parameters['origin_vserver'], 'volume': self.parameters['volume'] } self.add_parameter_to_dict(options, 'junction_path', 'junction-path') self.add_parameter_to_dict(options, 'auto_provision_as', 'auto-provision-as') self.add_parameter_to_dict(options, 'size', 'size', tostr=True) if self.parameters.get('aggr_list'): if self.parameters.get('aggr_list_multiplier'): self.tobytes_aggr_list_multiplier = bytes( self.parameters['aggr_list_multiplier']) self.add_parameter_to_dict(options, 'tobytes_aggr_list_multiplier', 'aggr-list-multiplier') flexcache_create = netapp_utils.zapi.NaElement.create_node_with_children( 'flexcache-create-async', **options) if self.parameters.get('aggr_list'): aggregates = netapp_utils.zapi.NaElement('aggr-list') for aggregate in self.parameters['aggr_list']: aggregates.add_new_child('aggr-name', aggregate) flexcache_create.add_child_elem(aggregates) try: result = self.server.invoke_successfully(flexcache_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating FlexCache %s' % to_native(error), exception=traceback.format_exc()) results = dict() for key in ('result-status', 'result-jobid'): if result.get_child_by_name(key): results[key] = result[key] return results def flexcache_create(self): """ Create a FlexCache relationship Check job status """ results = self.flexcache_create_async() status = results.get('result-status') if status == 'in_progress' and 'result-jobid' in results: if self.parameters['time_out'] == 0: # asynchronous call, assuming success! return error = self.check_job_status(results['result-jobid']) if error is None: return else: self.module.fail_json(msg='Error when creating flexcache: %s' % error) self.module.fail_json( msg='Unexpected error when creating flexcache: results is: %s' % repr(results)) def flexcache_delete_async(self): """ Delete FlexCache relationship at destination cluster """ options = {'volume': self.parameters['volume']} flexcache_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'flexcache-destroy-async', **options) try: result = self.server.invoke_successfully(flexcache_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting FlexCache : %s' % (to_native(error)), exception=traceback.format_exc()) results = dict() for key in ('result-status', 'result-jobid'): if result.get_child_by_name(key): results[key] = result[key] return results def volume_offline(self): """ Offline FlexCache volume at destination cluster """ options = {'name': self.parameters['volume']} xml = netapp_utils.zapi.NaElement.create_node_with_children( 'volume-offline', **options) try: self.server.invoke_successfully(xml, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error offlining FlexCache volume: %s' % (to_native(error)), exception=traceback.format_exc()) def volume_unmount(self): """ Unmount FlexCache volume at destination cluster """ options = {'volume-name': self.parameters['volume']} xml = netapp_utils.zapi.NaElement.create_node_with_children( 'volume-unmount', **options) try: self.server.invoke_successfully(xml, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error unmounting FlexCache volume: %s' % (to_native(error)), exception=traceback.format_exc()) def flexcache_delete_async(self): """ Delete FlexCache relationship at destination cluster """ options = {'volume': self.parameters['volume']} flexcache_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'flexcache-destroy-async', **options) try: result = self.server.invoke_successfully(flexcache_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting FlexCache : %s' % (to_native(error)), exception=traceback.format_exc()) results = dict() for key in ('result-status', 'result-jobid'): if result.get_child_by_name(key): results[key] = result[key] return results def flexcache_delete(self): """ Delete FlexCache relationship at destination cluster Check job status """ if self.parameters['force_unmount']: self.volume_unmount() if self.parameters['force_offline']: self.volume_offline() results = self.flexcache_delete_async() status = results.get('result-status') if status == 'in_progress' and 'result-jobid' in results: if self.parameters['time_out'] == 0: # asynchronous call, assuming success! return error = self.check_job_status(results['result-jobid']) if error is None: return else: self.module.fail_json(msg='Error when deleting flexcache: %s' % error) self.module.fail_json( msg='Unexpected error when deleting flexcache: results is: %s' % repr(results)) def check_parameters(self): """ Validate parameters and fail if one or more required params are missing """ missings = list() expected = ('origin_volume', 'origin_vserver') if self.parameters['state'] == 'present': for param in expected: if not self.parameters.get(param): missings.append(param) if missings: plural = 's' if len(missings) > 1 else '' msg = 'Missing parameter%s: %s' % (plural, ', '.join(missings)) self.module.fail_json(msg=msg) def apply(self): """ Apply action to FlexCache """ netapp_utils.ems_log_event("na_ontap_flexcache", self.server) current = self.flexcache_get() cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create': self.check_parameters() self.flexcache_create() elif cd_action == 'delete': self.flexcache_delete() self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPJob(object): '''Class with job schedule cron methods''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), job_minutes=dict(required=False, type='list'), job_months=dict(required=False, type='list'), job_hours=dict(required=False, type='list'), job_days_of_month=dict(required=False, type='list'), job_days_of_week=dict(required=False, type='list'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.set_playbook_zapi_key_map() if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def set_playbook_zapi_key_map(self): self.na_helper.zapi_string_keys = { 'name': 'job-schedule-name', } self.na_helper.zapi_list_keys = { 'job_minutes': ('job-schedule-cron-minute', 'cron-minute'), 'job_months': ('job-schedule-cron-month', 'cron-month'), 'job_hours': ('job-schedule-cron-hour', 'cron-hour'), 'job_days_of_month': ('job-schedule-cron-day', 'cron-day-of-month'), 'job_days_of_week': ('job-schedule-cron-day-of-week', 'cron-day-of-week') } def get_job_schedule(self): """ Return details about the job :param: name : Job name :return: Details about the Job. None if not found. :rtype: dict """ job_get_iter = netapp_utils.zapi.NaElement( 'job-schedule-cron-get-iter') job_get_iter.translate_struct({ 'query': { 'job-schedule-cron-info': { 'job-schedule-name': self.parameters['name'] } } }) result = self.server.invoke_successfully(job_get_iter, True) job_details = None # check if job exists if result.get_child_by_name('num-records') and int( result['num-records']) >= 1: job_info = result['attributes-list']['job-schedule-cron-info'] job_details = dict() for item_key, zapi_key in self.na_helper.zapi_string_keys.items(): job_details[item_key] = job_info[zapi_key] for item_key, zapi_key in self.na_helper.zapi_list_keys.items(): parent, dummy = zapi_key job_details[item_key] = self.na_helper.get_value_for_list( from_zapi=True, zapi_parent=job_info.get_child_by_name(parent)) # if any of the job_hours, job_minutes, job_months, job_days are empty: # it means the value is -1 for ZAPI if not job_details[item_key]: job_details[item_key] = ['-1'] return job_details def add_job_details(self, na_element_object, values): """ Add children node for create or modify NaElement object :param na_element_object: modify or create NaElement object :param values: dictionary of cron values to be added :return: None """ for item_key in values: if item_key in self.na_helper.zapi_string_keys: zapi_key = self.na_helper.zapi_string_keys.get(item_key) na_element_object[zapi_key] = values[item_key] elif item_key in self.na_helper.zapi_list_keys: parent_key, child_key = self.na_helper.zapi_list_keys.get( item_key) na_element_object.add_child_elem( self.na_helper.get_value_for_list( from_zapi=False, zapi_parent=parent_key, zapi_child=child_key, data=values.get(item_key))) def create_job_schedule(self): """ Creates a job schedule """ # job_minutes is mandatory for create if self.parameters.get('job_minutes') is None: self.module.fail_json( msg='Error: missing required parameter job_minutes for create') job_schedule_create = netapp_utils.zapi.NaElement( 'job-schedule-cron-create') self.add_job_details(job_schedule_create, self.parameters) try: self.server.invoke_successfully(job_schedule_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating job schedule %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_job_schedule(self): """ Delete a job schedule """ job_schedule_delete = netapp_utils.zapi.NaElement( 'job-schedule-cron-destroy') self.add_job_details(job_schedule_delete, self.parameters) try: self.server.invoke_successfully(job_schedule_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting job schedule %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_job_schedule(self, params): """ modify a job schedule """ job_schedule_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'job-schedule-cron-modify', **{'job-schedule-name': self.parameters['name']}) self.add_job_details(job_schedule_modify, params) try: self.server.invoke_successfully(job_schedule_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying job schedule %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): """ Autosupport log for job_schedule :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_job_schedule", cserver) def apply(self): """ Apply action to job-schedule """ self.autosupport_log() current = self.get_job_schedule() action = self.na_helper.get_cd_action(current, self.parameters) if action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if action == 'create': self.create_job_schedule() elif action == 'delete': self.delete_job_schedule() elif modify: self.modify_job_schedule(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPVserverPeer(object): """ Class with vserver peer methods """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), peer_vserver=dict(required=True, type='str'), peer_cluster=dict(required=False, type='str'), applications=dict(required=False, type='list', choices=['snapmirror', 'file_copy', 'lun_copy', 'flexcache']), dest_hostname=dict(required=False, 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 ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) if self.parameters.get('dest_hostname'): self.module.params['hostname'] = self.parameters['dest_hostname'] 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_server = netapp_utils.setup_na_ontap_zapi(module=self.module) # reset to source host connection for asup logs self.module.params['hostname'] = self.parameters['hostname'] def vserver_peer_get_iter(self): """ Compose NaElement object to query current vserver using peer-vserver and vserver parameters :return: NaElement object for vserver-get-iter with query """ vserver_peer_get = netapp_utils.zapi.NaElement('vserver-peer-get-iter') query = netapp_utils.zapi.NaElement('query') vserver_peer_info = netapp_utils.zapi.NaElement('vserver-peer-info') vserver_peer_info.add_new_child('peer-vserver', self.parameters['peer_vserver']) vserver_peer_info.add_new_child('vserver', self.parameters['vserver']) query.add_child_elem(vserver_peer_info) vserver_peer_get.add_child_elem(query) return vserver_peer_get def vserver_peer_get(self): """ Get current vserver peer info :return: Dictionary of current vserver peer details if query successful, else return None """ vserver_peer_get_iter = self.vserver_peer_get_iter() vserver_info = dict() try: result = self.server.invoke_successfully(vserver_peer_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching vserver peer %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) # return vserver peer details if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) > 0: vserver_peer_info = result.get_child_by_name('attributes-list').get_child_by_name('vserver-peer-info') vserver_info['peer_vserver'] = vserver_peer_info.get_child_content('peer-vserver') vserver_info['vserver'] = vserver_peer_info.get_child_content('vserver') vserver_info['peer_state'] = vserver_peer_info.get_child_content('peer-state') return vserver_info return None def vserver_peer_delete(self): """ Delete a vserver peer """ vserver_peer_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-peer-delete', **{'peer-vserver': self.parameters['peer_vserver'], 'vserver': self.parameters['vserver']}) try: self.server.invoke_successfully(vserver_peer_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting vserver peer %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) def get_peer_cluster_name(self): """ Get local cluster name :return: cluster name """ cluster_info = netapp_utils.zapi.NaElement('cluster-identity-get') try: result = self.server.invoke_successfully(cluster_info, enable_tunneling=True) return result.get_child_by_name('attributes').get_child_by_name( 'cluster-identity-info').get_child_content('cluster-name') except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching peer cluster name for peer vserver %s: %s' % (self.parameters['peer_vserver'], to_native(error)), exception=traceback.format_exc()) def vserver_peer_create(self): """ Create a vserver peer """ if self.parameters.get('applications') is None: self.module.fail_json(msg='applications parameter is missing') if self.parameters.get('peer_cluster') is not None and self.parameters.get('dest_hostname') is None: self.module.fail_json(msg='dest_hostname is required for peering a vserver in remote cluster') if self.parameters.get('peer_cluster') is None: self.parameters['peer_cluster'] = self.get_peer_cluster_name() vserver_peer_create = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-peer-create', **{'peer-vserver': self.parameters['peer_vserver'], 'vserver': self.parameters['vserver'], 'peer-cluster': self.parameters['peer_cluster']}) applications = netapp_utils.zapi.NaElement('applications') for application in self.parameters['applications']: applications.add_new_child('vserver-peer-application', application) vserver_peer_create.add_child_elem(applications) try: self.server.invoke_successfully(vserver_peer_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating vserver peer %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) def is_remote_peer(self): if self.parameters.get('dest_hostname') is None or \ (self.parameters['dest_hostname'] == self.parameters['hostname']): return False return True def vserver_peer_accept(self): """ Accept a vserver peer at destination """ # peer-vserver -> remote (source vserver is provided) # vserver -> local (destination vserver is provided) vserver_peer_accept = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-peer-accept', **{'peer-vserver': self.parameters['vserver'], 'vserver': self.parameters['peer_vserver']}) try: self.dest_server.invoke_successfully(vserver_peer_accept, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error accepting vserver peer %s: %s' % (self.parameters['peer_vserver'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to create/delete or accept vserver peer """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_vserver_peer", cserver) current = self.vserver_peer_get() cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create': self.vserver_peer_create() # accept only if the peer relationship is on a remote cluster if self.is_remote_peer(): self.vserver_peer_accept() elif cd_action == 'delete': self.vserver_peer_delete() self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPCifsShare(object): """ Methods to create/delete/modify(path) CIFS share """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), share_name=dict(required=True, type='str'), path=dict(required=False, type='str'), vserver=dict(required=True, type='str'), share_properties=dict(required=False, type='list'), symlink_properties=dict(required=False, type='list'), vscan_fileop_profile=dict( required=False, type='str', choices=['no_scan', 'standard', 'strict', 'writes_only']))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters.get('vserver')) def get_cifs_share(self): """ Return details about the cifs-share :param: name : Name of the cifs-share :return: Details about the cifs-share. None if not found. :rtype: dict """ cifs_iter = netapp_utils.zapi.NaElement('cifs-share-get-iter') cifs_info = netapp_utils.zapi.NaElement('cifs-share') cifs_info.add_new_child('share-name', self.parameters.get('share_name')) cifs_info.add_new_child('vserver', self.parameters.get('vserver')) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(cifs_info) cifs_iter.add_child_elem(query) result = self.server.invoke_successfully(cifs_iter, True) return_value = None # check if query returns the expected cifs-share if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: properties_list = [] symlink_list = [] cifs_attrs = result.get_child_by_name('attributes-list').\ get_child_by_name('cifs-share') if cifs_attrs.get_child_by_name('share-properties'): properties_attrs = cifs_attrs['share-properties'] if properties_attrs is not None: properties_list = [ property.get_content() for property in properties_attrs.get_children() ] if cifs_attrs.get_child_by_name('symlink-properties'): symlink_attrs = cifs_attrs['symlink-properties'] if symlink_attrs is not None: symlink_list = [ symlink.get_content() for symlink in symlink_attrs.get_children() ] return_value = { 'share': cifs_attrs.get_child_content('share-name'), 'path': cifs_attrs.get_child_content('path'), 'share_properties': properties_list, 'symlink_properties': symlink_list } if cifs_attrs.get_child_by_name('vscan-fileop-profile'): return_value['vscan_fileop_profile'] = cifs_attrs[ 'vscan-fileop-profile'] return return_value def create_cifs_share(self): """ Create CIFS share """ options = { 'share-name': self.parameters.get('share_name'), 'path': self.parameters.get('path') } cifs_create = netapp_utils.zapi.NaElement.create_node_with_children( 'cifs-share-create', **options) if self.parameters.get('share_properties'): property_attrs = netapp_utils.zapi.NaElement('share-properties') cifs_create.add_child_elem(property_attrs) for property in self.parameters.get('share_properties'): property_attrs.add_new_child('cifs-share-properties', property) if self.parameters.get('symlink_properties'): symlink_attrs = netapp_utils.zapi.NaElement('symlink-properties') cifs_create.add_child_elem(symlink_attrs) for symlink in self.parameters.get('symlink_properties'): symlink_attrs.add_new_child('cifs-share-symlink-properties', symlink) if self.parameters.get('vscan_fileop_profile'): fileop_attrs = netapp_utils.zapi.NaElement('vscan-fileop-profile') fileop_attrs.set_content(self.parameters['vscan_fileop_profile']) cifs_create.add_child_elem(fileop_attrs) try: self.server.invoke_successfully(cifs_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating cifs-share %s: %s' % (self.parameters.get('share_name'), to_native(error)), exception=traceback.format_exc()) def delete_cifs_share(self): """ Delete CIFS share """ cifs_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'cifs-share-delete', **{'share-name': self.parameters.get('share_name')}) try: self.server.invoke_successfully(cifs_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting cifs-share %s: %s' % (self.parameters.get('share_name'), to_native(error)), exception=traceback.format_exc()) def modify_cifs_share(self): """ modify path for the given CIFS share """ options = {'share-name': self.parameters.get('share_name')} cifs_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'cifs-share-modify', **options) if self.parameters.get('path'): cifs_modify.add_new_child('path', self.parameters.get('path')) if self.parameters.get('share_properties'): property_attrs = netapp_utils.zapi.NaElement('share-properties') cifs_modify.add_child_elem(property_attrs) for property in self.parameters.get('share_properties'): property_attrs.add_new_child('cifs-share-properties', property) if self.parameters.get('symlink_properties'): symlink_attrs = netapp_utils.zapi.NaElement('symlink-properties') cifs_modify.add_child_elem(symlink_attrs) for property in self.parameters.get('symlink_properties'): symlink_attrs.add_new_child('cifs-share-symlink-properties', property) if self.parameters.get('vscan_fileop_profile'): fileop_attrs = netapp_utils.zapi.NaElement('vscan-fileop-profile') fileop_attrs.set_content(self.parameters['vscan_fileop_profile']) cifs_modify.add_child_elem(fileop_attrs) try: self.server.invoke_successfully(cifs_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying cifs-share %s:%s' % (self.parameters.get('share_name'), to_native(error)), exception=traceback.format_exc()) def apply(self): '''Apply action to cifs share''' netapp_utils.ems_log_event("na_ontap_cifs", self.server) current = self.get_cifs_share() cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None: modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_cifs_share() elif cd_action == 'delete': self.delete_cifs_share() elif modify: self.modify_cifs_share() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSnapshotPolicy(object): """ Creates and deletes a Snapshot Policy """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type="str"), enabled=dict(required=False, type="bool"), # count is a list of integers count=dict(required=False, type="list", elements="int"), comment=dict(required=False, type="str"), schedule=dict(required=False, type="list", elements="str"), snapmirror_label=dict(required=False, type="list", elements="str"), vserver=dict(required=False, type="str"))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[ ('state', 'present', ['enabled', 'count', 'schedule']), ], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: if 'vserver' in self.parameters: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) return def get_snapshot_policy(self): """ Checks to see if a snapshot policy exists or not :return: Return policy details if a snapshot policy exists, None if it doesn't """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-policy-get-iter") # compose query query = netapp_utils.zapi.NaElement("query") snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-policy-info") snapshot_info_obj.add_new_child("policy", self.parameters['name']) if 'vserver' in self.parameters: snapshot_info_obj.add_new_child("vserver-name", self.parameters['vserver']) query.add_child_elem(snapshot_info_obj) snapshot_obj.add_child_elem(query) try: result = self.server.invoke_successfully(snapshot_obj, True) if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: snapshot_policy = result.get_child_by_name( 'attributes-list').get_child_by_name( 'snapshot-policy-info') current = {} current['name'] = snapshot_policy.get_child_content('policy') current['vserver'] = snapshot_policy.get_child_content( 'vserver-name') current[ 'enabled'] = False if snapshot_policy.get_child_content( 'enabled').lower() == 'false' else True current['comment'] = snapshot_policy.get_child_content( 'comment') or '' current['schedule'], current['count'], current[ 'snapmirror_label'] = [], [], [] if snapshot_policy.get_child_by_name( 'snapshot-policy-schedules'): for schedule in snapshot_policy[ 'snapshot-policy-schedules'].get_children(): current['schedule'].append( schedule.get_child_content('schedule')) current['count'].append( int(schedule.get_child_content('count'))) snapmirror_label = schedule.get_child_content( 'snapmirror-label') if snapmirror_label is None or snapmirror_label == '-': snapmirror_label = '' current['snapmirror_label'].append(snapmirror_label) return current except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) return None def validate_parameters(self): """ Validate if each schedule has a count associated :return: None """ if 'count' not in self.parameters or 'schedule' not in self.parameters or \ len(self.parameters['count']) > 5 or len(self.parameters['schedule']) > 5 or \ len(self.parameters['count']) < 1 or len(self.parameters['schedule']) < 1 or \ len(self.parameters['count']) != len(self.parameters['schedule']): self.module.fail_json( msg="Error: A Snapshot policy must have at least 1 " "schedule and can have up to a maximum of 5 schedules, with a count " "representing the maximum number of Snapshot copies for each schedule" ) if 'snapmirror_label' in self.parameters: if len(self.parameters['snapmirror_label']) != len( self.parameters['schedule']): self.module.fail_json( msg="Error: Each Snapshot Policy schedule must have an " "accompanying SnapMirror Label") def modify_snapshot_policy(self, current): """ Modifies an existing snapshot policy """ # Set up required variables to modify snapshot policy options = {'policy': self.parameters['name']} modify = False # Set up optional variables to modify snapshot policy if 'enabled' in self.parameters and self.parameters[ 'enabled'] != current['enabled']: options['enabled'] = str(self.parameters['enabled']) modify = True if 'comment' in self.parameters and self.parameters[ 'comment'] != current['comment']: options['comment'] = self.parameters['comment'] modify = True if modify: snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children( 'snapshot-policy-modify', **options) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying snapshot policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_snapshot_policy_schedules(self, current): """ Modify existing schedules in snapshot policy :return: None """ self.validate_parameters() delete_schedules, modify_schedules, add_schedules = [], [], [] if 'snapmirror_label' in self.parameters: snapmirror_labels = self.parameters['snapmirror_label'] else: # User hasn't supplied any snapmirror labels. snapmirror_labels = [None] * len(self.parameters['schedule']) # Identify schedules for deletion for schedule in current['schedule']: schedule = schedule.strip() if schedule not in [ item.strip() for item in self.parameters['schedule'] ]: options = {'policy': current['name'], 'schedule': schedule} delete_schedules.append(options) # Identify schedules to be modified or added for schedule, count, snapmirror_label in zip( self.parameters['schedule'], self.parameters['count'], snapmirror_labels): schedule = schedule.strip() if snapmirror_label is not None: snapmirror_label = snapmirror_label.strip() options = {'policy': current['name'], 'schedule': schedule} if schedule in current['schedule']: # Schedule exists. Only modify if it has changed. modify = False schedule_index = current['schedule'].index(schedule) if count != current['count'][schedule_index]: options['new-count'] = str(count) modify = True if snapmirror_label is not None: if snapmirror_label != current['snapmirror_label'][ schedule_index]: options['new-snapmirror-label'] = snapmirror_label modify = True if modify: modify_schedules.append(options) else: # New schedule options['count'] = str(count) if snapmirror_label is not None and snapmirror_label != '': options['snapmirror-label'] = snapmirror_label add_schedules.append(options) # Delete N-1 schedules no longer required. Must leave 1 schedule in policy # at any one time. Delete last one afterwards. while len(delete_schedules) > 1: options = delete_schedules.pop() self.modify_snapshot_policy_schedule( options, 'snapshot-policy-remove-schedule') # Modify schedules. while len(modify_schedules) > 0: options = modify_schedules.pop() self.modify_snapshot_policy_schedule( options, 'snapshot-policy-modify-schedule') # Add N-1 new schedules. Add last one after last schedule has been deleted. while len(add_schedules) > 1: options = add_schedules.pop() self.modify_snapshot_policy_schedule( options, 'snapshot-policy-add-schedule') # Delete last schedule no longer required. while len(delete_schedules) > 0: options = delete_schedules.pop() self.modify_snapshot_policy_schedule( options, 'snapshot-policy-remove-schedule') # Add last new schedule. while len(add_schedules) > 0: options = add_schedules.pop() self.modify_snapshot_policy_schedule( options, 'snapshot-policy-add-schedule') def modify_snapshot_policy_schedule(self, options, zapi): """ Add, modify or remove a schedule to/from a snapshot policy """ snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children( zapi, **options) try: self.server.invoke_successfully(snapshot_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying snapshot policy schedule %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_snapshot_policy(self): """ Creates a new snapshot policy """ # set up required variables to create a snapshot policy self.validate_parameters() options = { 'policy': self.parameters['name'], 'enabled': str(self.parameters['enabled']), } if 'snapmirror_label' in self.parameters: snapmirror_labels = self.parameters['snapmirror_label'] else: # User hasn't supplied any snapmirror labels. snapmirror_labels = [None] * len(self.parameters['schedule']) # zapi attribute for first schedule is schedule1, second is schedule2 and so on positions = [ str(i) for i in range(1, len(self.parameters['schedule']) + 1) ] for schedule, count, snapmirror_label, position in zip( self.parameters['schedule'], self.parameters['count'], snapmirror_labels, positions): schedule = schedule.strip() options['count' + position] = str(count) options['schedule' + position] = schedule if snapmirror_label is not None: snapmirror_label = snapmirror_label.strip() if snapmirror_label != '': options['snapmirror-label' + position] = snapmirror_label snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children( 'snapshot-policy-create', **options) # Set up optional variables to create a snapshot policy if self.parameters.get('comment'): snapshot_obj.add_new_child("comment", self.parameters['comment']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating snapshot policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_snapshot_policy(self): """ Deletes an existing snapshot policy """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-policy-delete") # Set up required variables to delete a snapshot policy snapshot_obj.add_new_child("policy", self.parameters['name']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting snapshot policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver) def apply(self): """ Check to see which play we should run """ self.asup_log_for_cserver("na_ontap_snapshot_policy") current = self.get_snapshot_policy() modify = None cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': # Don't sort schedule/count/snapmirror_label lists as it can # mess up the intended parameter order. modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_snapshot_policy() elif cd_action == 'delete': self.delete_snapshot_policy() if modify: self.modify_snapshot_policy(current) self.modify_snapshot_policy_schedules(current) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapNetRoutes(object): """ Create, Modifies and Destroys a Net Route """ def __init__(self): """ Initialize the Ontap Net Route class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), destination=dict(required=True, type='str'), gateway=dict(required=True, type='str'), metric=dict(required=False, type='str'), from_destination=dict(required=False, type='str', default=None), from_gateway=dict(required=False, type='str', default=None), from_metric=dict(required=False, type='str', default=None), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) return def create_net_route(self, current_metric=None): """ Creates a new Route """ route_obj = netapp_utils.zapi.NaElement('net-routes-create') route_obj.add_new_child("destination", self.parameters['destination']) route_obj.add_new_child("gateway", self.parameters['gateway']) if current_metric is None and self.parameters.get( 'metric') is not None: metric = self.parameters['metric'] else: metric = current_metric # Metric can be None, Can't set metric to none if metric is not None: route_obj.add_new_child("metric", metric) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating net route: %s' % (to_native(error)), exception=traceback.format_exc()) def delete_net_route(self, params=None): """ Deletes a given Route """ route_obj = netapp_utils.zapi.NaElement('net-routes-destroy') if params is None: params = self.parameters route_obj.add_new_child("destination", params['destination']) route_obj.add_new_child("gateway", params['gateway']) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting net route: %s' % (to_native(error)), exception=traceback.format_exc()) def modify_net_route(self, current, desired): """ Modify a net route """ # return if there is nothing to change for key, val in desired.items(): if val != current[key]: self.na_helper.changed = True break if not self.na_helper.changed: return # delete and re-create with new params self.delete_net_route(current) route_obj = netapp_utils.zapi.NaElement('net-routes-create') for attribute in ['metric', 'destination', 'gateway']: if desired.get(attribute) is not None: value = desired[attribute] else: value = current[attribute] route_obj.add_new_child(attribute, value) try: result = self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: # restore the old route, create the route with the existing metric self.create_net_route(current['metric']) # return if desired route already exists if to_native(error.code) == '13001': return # Invalid value specified for any of the attributes self.module.fail_json(msg='Error modifying net route: %s' % (to_native(error)), exception=traceback.format_exc()) def get_net_route(self, params=None): """ Checks to see if a route exist or not :return: NaElement object if a route exists, None otherwise """ if params is not None: # we need at least on of the new_destination or new_gateway to fetch desired route if params.get('destination') is None and params.get( 'gateway') is None: return None current = None route_obj = netapp_utils.zapi.NaElement('net-routes-get') for attr in ['destination', 'gateway']: if params and params.get(attr) is not None: value = params[attr] else: value = self.parameters[attr] route_obj.add_new_child(attr, value) try: result = self.server.invoke_successfully(route_obj, True) if result.get_child_by_name('attributes') is not None: route_info = result.get_child_by_name( 'attributes').get_child_by_name('net-vs-routes-info') current = { 'destination': route_info.get_child_content('destination'), 'gateway': route_info.get_child_content('gateway'), 'metric': route_info.get_child_content('metric') } except netapp_utils.zapi.NaApiError as error: # Error 13040 denotes a route doesn't exist. if to_native(error.code) == "15661": return None self.module.fail_json(msg='Error fetching net route: %s' % (to_native(error)), exception=traceback.format_exc()) return current def is_modify_action(self, current, desired): """ Get desired action to be applied for net routes Destination and gateway are unique params for a route and cannot be duplicated So if a route with desired destination or gateway exists already, we don't try to modify :param current: current details :param desired: desired details :return: create / delete / modify / None """ if current is None and desired is None: # this is invalid # cannot modify a non existent resource return None if current is None and desired is not None: # idempotency or duplication # we need not create return False if current is not None and desired is not None: # we can't modify an ambiguous route (idempotency/duplication) return False return True def get_params_to_be_modified(self, current): """ Get parameters and values that need to be modified :param current: current details :return: dict(), None """ if current is None: return None desired = dict() if self.parameters.get('new_destination') is not None and \ self.parameters['new_destination'] != current['destination']: desired['destination'] = self.parameters['new_destination'] if self.parameters.get('new_gateway') is not None and \ self.parameters['new_gateway'] != current['gateway']: desired['gateway'] = self.parameters['new_gateway'] if self.parameters.get('new_metric') is not None and \ self.parameters['new_metric'] != current['metric']: desired['metric'] = self.parameters['new_metric'] return desired def apply(self): """ Run Module based on play book """ netapp_utils.ems_log_event("na_ontap_net_routes", self.server) current = self.get_net_route() modify, cd_action = None, None modify_params = { 'destination': self.parameters.get('from_destination'), 'gateway': self.parameters.get('from_gateway'), 'metric': self.parameters.get('from_metric') } # if any from_* param is present in playbook, check for modify action if any(modify_params.values()): # destination and gateway combination is unique, and is considered like a id. so modify destination # or gateway is considered a rename action. metric is considered an attribute of the route so it is # considered as modify. if modify_params.get('metric') is not None: modify = True old_params = current else: # get parameters that are eligible for modify old_params = self.get_net_route(modify_params) modify = self.na_helper.is_rename_action(old_params, current) if modify is None: self.module.fail_json( msg="Error modifying: route %s does not exist" % self.parameters['from_destination']) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create': self.create_net_route() elif cd_action == 'delete': self.delete_net_route() elif modify: desired = {} for key, value in old_params.items(): desired[key] = value for key, value in modify_params.items(): if value is not None: desired[key] = self.parameters.get(key) self.modify_net_route(old_params, desired) self.module.exit_json(changed=self.na_helper.changed)
class NetAppAWSCVS(object): '''Class for Pool operations ''' def __init__(self): """ Parse arguments, setup state variables, """ self.argument_spec = netapp_utils.aws_cvs_host_argument_spec() self.argument_spec.update(dict( state=dict(required=True, choices=['present', 'absent']), region=dict(required=True, type='str'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), serviceLevel=dict(required=False, choices=['basic', 'standard', 'extreme'], type='str'), sizeInBytes=dict(required=False, type='int'), vendorID=dict(required=False, type='str'), )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.restApi = AwsCvsRestAPI(self.module) self.sizeInBytes_min_value = 4000000000000 def get_aws_netapp_cvs_pool(self, name=None): """ Returns Pool object if exists else Return None """ pool_info = None if name is None: name = self.parameters['name'] pools, error = self.restApi.get('Pools') if error is None and pools is not None: for pool in pools: if 'name' in pool and pool['region'] == self.parameters['region']: if pool['name'] == name: pool_info = pool break return pool_info def create_aws_netapp_cvs_pool(self): """ Create a pool """ api = 'Pools' for key in ['serviceLevel', 'sizeInBytes', 'vendorID']: if key not in self.parameters.keys() or self.parameters[key] is None: self.module.fail_json(changed=False, msg="Mandatory key '%s' required" % (key)) pool = { "name": self.parameters['name'], "region": self.parameters['region'], "serviceLevel": self.parameters['serviceLevel'], "sizeInBytes": self.parameters['sizeInBytes'], "vendorID": self.parameters['vendorID'] } response, error = self.restApi.post(api, pool) if error is not None: self.module.fail_json(changed=False, msg=error) def update_aws_netapp_cvs_pool(self, update_pool_info, pool_id): """ Update a pool """ api = 'Pools/' + pool_id pool = { "name": update_pool_info['name'], "region": self.parameters['region'], "serviceLevel": update_pool_info['serviceLevel'], "sizeInBytes": update_pool_info['sizeInBytes'], "vendorID": update_pool_info['vendorID'] } response, error = self.restApi.put(api, pool) if error is not None: self.module.fail_json(changed=False, msg=error) def delete_aws_netapp_cvs_pool(self, pool_id): """ Delete a pool """ api = 'Pools/' + pool_id data = None response, error = self.restApi.delete(api, data) if error is not None: self.module.fail_json(changed=False, msg=error) def apply(self): """ Perform pre-checks, call functions and exit """ update_required = False cd_action = None if 'sizeInBytes' in self.parameters.keys() and self.parameters['sizeInBytes'] < self.sizeInBytes_min_value: self.module.fail_json(changed=False, msg="sizeInBytes should be greater than or equal to %d" % (self.sizeInBytes_min_value)) current = self.get_aws_netapp_cvs_pool() if self.parameters.get('from_name'): existing = self.get_aws_netapp_cvs_pool(self.parameters['from_name']) rename = self.na_helper.is_rename_action(existing, current) if rename is None: self.module.fail_json(changed=False, msg="unable to rename pool: '%s' does not exist" % self.parameters['from_name']) if rename: current = existing else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': keys_to_check = ['name', 'vendorID', 'sizeInBytes', 'serviceLevel'] update_pool_info, update_required = self.na_helper.compare_and_update_values(current, self.parameters, keys_to_check) if update_required is True: self.na_helper.changed = True cd_action = 'update' if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'update': self.update_aws_netapp_cvs_pool(update_pool_info, current['poolId']) elif cd_action == 'create': self.create_aws_netapp_cvs_pool() elif cd_action == 'delete': self.delete_aws_netapp_cvs_pool(current['poolId']) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPVolumeClone(object): """ Creates a volume clone """ def __init__(self): """ Initialize the NetAppOntapVolumeClone class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, choices=['present'], default='present'), parent_volume=dict(required=True, type='str'), name=dict(required=True, type='str', aliases=["volume"]), vserver=dict(required=True, type='str'), parent_snapshot=dict(required=False, type='str', default=None), parent_vserver=dict(required=False, type='str', default=None), qos_policy_group_name=dict(required=False, type='str', default=None), space_reserve=dict(required=False, choices=['volume', 'none'], default=None), volume_type=dict(required=False, choices=['rw', 'dp']), junction_path=dict(required=False, type='str', default=None), uid=dict(required=False, type='int'), gid=dict(required=False, type='int') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, required_together=[ ['uid', 'gid'] ] ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) return def create_volume_clone(self): """ Creates a new volume clone """ clone_obj = netapp_utils.zapi.NaElement('volume-clone-create') clone_obj.add_new_child("parent-volume", self.parameters['parent_volume']) clone_obj.add_new_child("volume", self.parameters['volume']) if self.parameters.get('qos_policy_group_name'): clone_obj.add_new_child("qos-policy-group-name", self.parameters['qos_policy_group_name']) if self.parameters.get('space_reserve'): clone_obj.add_new_child("space-reserve", self.parameters['space_reserve']) if self.parameters.get('parent_snapshot'): clone_obj.add_new_child("parent-snapshot", self.parameters['parent_snapshot']) if self.parameters.get('parent_vserver'): clone_obj.add_new_child("parent-vserver", self.parameters['parent_vserver']) if self.parameters.get('volume_type'): clone_obj.add_new_child("volume-type", self.parameters['volume_type']) if self.parameters.get('junction_path'): clone_obj.add_new_child("junction-path", self.parameters['junction_path']) if self.parameters.get('uid'): clone_obj.add_new_child("uid", str(self.parameters['uid'])) clone_obj.add_new_child("gid", str(self.parameters['gid'])) try: self.server.invoke_successfully(clone_obj, True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg='Error creating volume clone: %s: %s' % (self.parameters['volume'], to_native(exc)), exception=traceback.format_exc()) def get_volume_clone(self): clone_obj = netapp_utils.zapi.NaElement('volume-clone-get') clone_obj.add_new_child("volume", self.parameters['volume']) try: results = self.server.invoke_successfully(clone_obj, True) if results.get_child_by_name('attributes'): attributes = results.get_child_by_name('attributes') info = attributes.get_child_by_name('volume-clone-info') parent_volume = info.get_child_content('parent-volume') # checking if clone volume name already used to create by same parent volume if parent_volume == self.parameters['parent_volume']: return results except netapp_utils.zapi.NaApiError as error: # Error 15661 denotes an volume clone not being found. if to_native(error.code) == "15661": pass else: self.module.fail_json(msg='Error fetching volume clone information %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) return None def apply(self): """ Run Module based on play book """ netapp_utils.ems_log_event("na_ontap_volume_clone", self.server) current = self.get_volume_clone() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_volume_clone() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapQTree(object): '''Class with qtree operations''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), flexvol_name=dict(type='str'), vserver=dict(required=True, type='str'), export_policy=dict(required=False, type='str'), security_style=dict(required=False, choices=['unix', 'ntfs', 'mixed']), oplocks=dict(required=False, choices=['enabled', 'disabled']), unix_permissions=dict(required=False, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[('state', 'present', ['flexvol_name'])], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def get_qtree(self, name=None): """ Checks if the qtree exists. :param: name : qtree name :return: Details about the qtree False if qtree is not found :rtype: bool """ if name is None: name = self.parameters['name'] qtree_list_iter = netapp_utils.zapi.NaElement('qtree-list-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'qtree-info', **{ 'vserver': self.parameters['vserver'], 'volume': self.parameters['flexvol_name'], 'qtree': name }) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) qtree_list_iter.add_child_elem(query) result = self.server.invoke_successfully(qtree_list_iter, enable_tunneling=True) return_q = None if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1): return_q = { 'export_policy': result['attributes-list']['qtree-info']['export-policy'], 'unix_permissions': result['attributes-list']['qtree-info']['mode'], 'oplocks': result['attributes-list']['qtree-info']['oplocks'], 'security_style': result['attributes-list']['qtree-info']['security-style'] } return return_q def create_qtree(self): """ Create a qtree """ options = { 'qtree': self.parameters['name'], 'volume': self.parameters['flexvol_name'] } if self.parameters.get('export_policy'): options['export-policy'] = self.parameters['export_policy'] if self.parameters.get('security_style'): options['security-style'] = self.parameters['security_style'] if self.parameters.get('oplocks'): options['oplocks'] = self.parameters['oplocks'] if self.parameters.get('unix_permissions'): options['mode'] = self.parameters['unix_permissions'] qtree_create = netapp_utils.zapi.NaElement.create_node_with_children( 'qtree-create', **options) try: self.server.invoke_successfully(qtree_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error provisioning qtree %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_qtree(self): """ Delete a qtree """ path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) qtree_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'qtree-delete', **{'qtree': path}) try: self.server.invoke_successfully(qtree_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error deleting qtree %s: %s" % (path, to_native(error)), exception=traceback.format_exc()) def rename_qtree(self): """ Rename a qtree """ path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['from_name']) new_path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) qtree_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'qtree-rename', **{ 'qtree': path, 'new-qtree-name': new_path }) try: self.server.invoke_successfully(qtree_rename, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error renaming qtree %s: %s" % (self.parameters['from_name'], to_native(error)), exception=traceback.format_exc()) def modify_qtree(self): """ Modify a qtree """ options = { 'qtree': self.parameters['name'], 'volume': self.parameters['flexvol_name'] } if self.parameters.get('export_policy'): options['export-policy'] = self.parameters['export_policy'] if self.parameters.get('security_style'): options['security-style'] = self.parameters['security_style'] if self.parameters.get('oplocks'): options['oplocks'] = self.parameters['oplocks'] if self.parameters.get('unix_permissions'): options['mode'] = self.parameters['unix_permissions'] qtree_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'qtree-modify', **options) try: self.server.invoke_successfully(qtree_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying qtree %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def apply(self): '''Call create/delete/modify/rename operations''' netapp_utils.ems_log_event("na_ontap_qtree", self.server) current = self.get_qtree() rename, cd_action, modify = None, None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_qtree(self.parameters['from_name']), current) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_qtree() if cd_action == 'create': self.create_qtree() elif cd_action == 'delete': self.delete_qtree() elif modify: self.modify_qtree() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapUnixGroup(object): """ Common operations to manage UNIX groups """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), id=dict(required=False, type='int'), skip_name_validation=dict(required=False, type='bool'), vserver=dict(required=True, type='str'), users=dict(required=False, type='list'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.set_playbook_zapi_key_map() if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def set_playbook_zapi_key_map(self): self.na_helper.zapi_string_keys = {'name': 'group-name'} self.na_helper.zapi_int_keys = {'id': 'group-id'} self.na_helper.zapi_bool_keys = { 'skip_name_validation': 'skip-name-validation' } def get_unix_group(self): """ Checks if the UNIX group exists. :return: dict() if group found None if group is not found """ get_unix_group = netapp_utils.zapi.NaElement( 'name-mapping-unix-group-get-iter') attributes = { 'query': { 'unix-group-info': { 'group-name': self.parameters['name'], 'vserver': self.parameters['vserver'], } } } get_unix_group.translate_struct(attributes) try: result = self.server.invoke_successfully(get_unix_group, enable_tunneling=True) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: group_info = result['attributes-list']['unix-group-info'] group_details = dict() else: return None except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error getting UNIX group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) for item_key, zapi_key in self.na_helper.zapi_string_keys.items(): group_details[item_key] = group_info[zapi_key] for item_key, zapi_key in self.na_helper.zapi_int_keys.items(): group_details[item_key] = self.na_helper.get_value_for_int( from_zapi=True, value=group_info[zapi_key]) if group_info.get_child_by_name('users') is not None: group_details['users'] = [ user.get_child_content('user-name') for user in group_info.get_child_by_name('users').get_children() ] else: group_details['users'] = None return group_details def create_unix_group(self): """ Creates an UNIX group in the specified Vserver :return: None """ if self.parameters.get('id') is None: self.module.fail_json( msg='Error: Missing a required parameter for create: (id)') group_create = netapp_utils.zapi.NaElement( 'name-mapping-unix-group-create') group_details = {} for item in self.parameters: if item in self.na_helper.zapi_string_keys: zapi_key = self.na_helper.zapi_string_keys.get(item) group_details[zapi_key] = self.parameters[item] elif item in self.na_helper.zapi_bool_keys: zapi_key = self.na_helper.zapi_bool_keys.get(item) group_details[zapi_key] = self.na_helper.get_value_for_bool( from_zapi=False, value=self.parameters[item]) elif item in self.na_helper.zapi_int_keys: zapi_key = self.na_helper.zapi_int_keys.get(item) group_details[zapi_key] = self.na_helper.get_value_for_int( from_zapi=True, value=self.parameters[item]) group_create.translate_struct(group_details) try: self.server.invoke_successfully(group_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating UNIX group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if self.parameters.get('users') is not None: self.modify_users_in_group() def delete_unix_group(self): """ Deletes an UNIX group from a vserver :return: None """ group_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'name-mapping-unix-group-destroy', **{'group-name': self.parameters['name']}) try: self.server.invoke_successfully(group_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error removing UNIX group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_unix_group(self, params): """ Modify an UNIX group from a vserver :param params: modify parameters :return: None """ # modify users requires separate zapi. if 'users' in params: self.modify_users_in_group() if len(params) == 1: return group_modify = netapp_utils.zapi.NaElement( 'name-mapping-unix-group-modify') group_details = {'group-name': self.parameters['name']} for key in params: if key in self.na_helper.zapi_int_keys: zapi_key = self.na_helper.zapi_int_keys.get(key) group_details[zapi_key] = self.na_helper.get_value_for_int( from_zapi=True, value=params[key]) group_modify.translate_struct(group_details) try: self.server.invoke_successfully(group_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying UNIX group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_users_in_group(self): """ Add/delete one or many users in a UNIX group :return: None """ current_users = self.get_unix_group().get('users') expect_users = self.parameters.get('users') if current_users is None: current_users = [] if expect_users[0] == '' and len(expect_users) == 1: expect_users = [] users_to_remove = list(set(current_users) - set(expect_users)) users_to_add = list(set(expect_users) - set(current_users)) if len(users_to_add) > 0: for user in users_to_add: add_user = netapp_utils.zapi.NaElement( 'name-mapping-unix-group-add-user') group_details = { 'group-name': self.parameters['name'], 'user-name': user } add_user.translate_struct(group_details) try: self.server.invoke_successfully(add_user, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error adding user %s to UNIX group %s: %s' % (user, self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if len(users_to_remove) > 0: for user in users_to_remove: delete_user = netapp_utils.zapi.NaElement( 'name-mapping-unix-group-delete-user') group_details = { 'group-name': self.parameters['name'], 'user-name': user } delete_user.translate_struct(group_details) try: self.server.invoke_successfully(delete_user, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting user %s from UNIX group %s: %s' % (user, self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): """ Autosupport log for unix_group :return: None """ netapp_utils.ems_log_event("na_ontap_unix_group", self.server) def apply(self): """ Invoke appropriate action based on playbook parameters :return: None """ self.autosupport_log() current = self.get_unix_group() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.parameters['state'] == 'present' and cd_action is None: modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_unix_group() elif cd_action == 'delete': self.delete_unix_group() else: self.modify_unix_group(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapKerberosRealm(object): ''' Kerberos Realm definition class ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( admin_server_ip=dict(required=False, default=None, type='str'), admin_server_port=dict(required=False, default=None, type='str'), clock_skew=dict(required=False, default=None, type='str'), comment=dict(required=False, default=None, type='str'), kdc_ip=dict(required_if=[["state", "present"]], default=None, type='str'), kdc_port=dict(required=False, default=None, type='str'), kdc_vendor=dict(required_if=[["state", "present"]], default=None, type='str', choices=['Microsoft', 'Other']), pw_server_ip=dict(required=False, default=None, type='str'), pw_server_port=dict(required=False, default=None, type='str'), realm=dict(required=True, type='str'), state=dict(required=False, choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, required_if=[('state', 'present', ['kdc_vendor', 'kdc_ip'])], ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) self.simple_attributes = [ 'admin_server_ip', 'admin_server_port', 'clock_skew', 'kdc_ip', 'kdc_port', 'kdc_vendor', ] def get_krbrealm(self, realm_name=None, vserver_name=None): ''' Checks if Kerberos Realm config exists. :return: kerberos realm object if found None if not found :rtype: object/None ''' # Make query krbrealm_info = netapp_utils.zapi.NaElement('kerberos-realm-get-iter') if realm_name is None: realm_name = self.parameters['realm'] if vserver_name is None: vserver_name = self.parameters['vserver'] query_details = netapp_utils.zapi.NaElement.create_node_with_children('kerberos-realm', **{'realm': realm_name, 'vserver-name': vserver_name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) krbrealm_info.add_child_elem(query) result = self.server.invoke_successfully(krbrealm_info, enable_tunneling=True) # Get Kerberos Realm details krbrealm_details = None if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1): attributes_list = result.get_child_by_name('attributes-list') config_info = attributes_list.get_child_by_name('kerberos-realm') krbrealm_details = { 'admin_server_ip': config_info.get_child_content('admin-server-ip'), 'admin_server_port': config_info.get_child_content('admin-server-port'), 'clock_skew': config_info.get_child_content('clock-skew'), 'kdc_ip': config_info.get_child_content('kdc-ip'), 'kdc_port': config_info.get_child_content('kdc-port'), 'kdc_vendor': config_info.get_child_content('kdc-vendor'), 'pw_server_ip': config_info.get_child_content('password-server-ip'), 'pw_server_port': config_info.get_child_content('password-server-port'), 'realm': config_info.get_child_content('realm'), 'vserver': config_info.get_child_content('vserver'), } return krbrealm_details def create_krbrealm(self): '''supported Create Kerberos Realm configuration ''' options = { 'realm': self.parameters['realm'] } # Other options/attributes for attribute in self.simple_attributes: if self.parameters.get(attribute) is not None: options[str(attribute).replace('_', '-')] = self.parameters[attribute] if self.parameters.get('pw_server_ip') is not None: options['password-server-ip'] = self.parameters['pw_server_ip'] if self.parameters.get('pw_server_port') is not None: options['password-server-port'] = self.parameters['pw_server_port'] # Initialize NaElement krbrealm_create = netapp_utils.zapi.NaElement.create_node_with_children('kerberos-realm-create', **options) # Try to create Kerberos Realm configuration try: self.server.invoke_successfully(krbrealm_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as errcatch: self.module.fail_json(msg='Error creating Kerberos Realm configuration %s: %s' % (self.parameters['realm'], to_native(errcatch)), exception=traceback.format_exc()) def delete_krbrealm(self): ''' Delete Kerberos Realm configuration ''' krbrealm_delete = netapp_utils.zapi.NaElement.create_node_with_children('kerberos-realm-delete', **{'realm': self.parameters['realm']}) try: self.server.invoke_successfully(krbrealm_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as errcatch: self.module.fail_json(msg='Error deleting Kerberos Realm configuration %s: %s' % ( self.parameters['realm'], to_native(errcatch)), exception=traceback.format_exc()) def modify_krbrealm(self, modify): ''' Modify Kerberos Realm :param modify: list of modify attributes ''' krbrealm_modify = netapp_utils.zapi.NaElement('kerberos-realm-modify') krbrealm_modify.add_new_child('realm', self.parameters['realm']) for attribute in modify: if attribute in self.simple_attributes: krbrealm_modify.add_new_child(str(attribute).replace('_', '-'), self.parameters[attribute]) if attribute == 'pw_server_ip': krbrealm_modify.add_new_child('password-server-ip', self.parameters['pw_server_ip']) if attribute == 'pw_server_port': krbrealm_modify.add_new_child('password-server-port', self.parameters['pw_server_port']) # Try to modify Kerberos Realm try: self.server.invoke_successfully(krbrealm_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as errcatch: self.module.fail_json(msg='Error modifying Kerberos Realm %s: %s' % (self.parameters['realm'], to_native(errcatch)), exception=traceback.format_exc()) def apply(self): '''Call create/modify/delete operations.''' current = self.get_krbrealm() cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes(current, self.parameters) # create an ems log event for users with auto support turned on netapp_utils.ems_log_event("na_ontap_kerberos_realm", self.server) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_krbrealm() elif cd_action == 'delete': self.delete_krbrealm() elif modify: self.modify_krbrealm(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPPortset(object): """ Methods to create or delete portset """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, default='present'), vserver=dict(required=True, type='str'), name=dict(required=True, type='str'), type=dict(required=False, type='str', choices=['fcp', 'iscsi', 'mixed']), force=dict(required=False, type='bool', default=False), ports=dict(required=False, type='list'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def portset_get_iter(self): """ Compose NaElement object to query current portset using vserver, portset-name and portset-type parameters :return: NaElement object for portset-get-iter with query """ portset_get = netapp_utils.zapi.NaElement('portset-get-iter') query = netapp_utils.zapi.NaElement('query') portset_info = netapp_utils.zapi.NaElement('portset-info') portset_info.add_new_child('vserver', self.parameters['vserver']) portset_info.add_new_child('portset-name', self.parameters['name']) if self.parameters.get('type'): portset_info.add_new_child('portset-type', self.parameters['type']) query.add_child_elem(portset_info) portset_get.add_child_elem(query) return portset_get def portset_get(self): """ Get current portset info :return: Dictionary of current portset details if query successful, else return None """ portset_get_iter = self.portset_get_iter() result, portset_info = None, dict() try: result = self.server.invoke_successfully(portset_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching portset %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) # return portset details if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) > 0: portset_get_info = result.get_child_by_name( 'attributes-list').get_child_by_name('portset-info') if int(portset_get_info.get_child_content( 'portset-port-total')) > 0: ports = portset_get_info.get_child_by_name('portset-port-info') portset_info['ports'] = [ port.get_content() for port in ports.get_children() ] else: portset_info['ports'] = [] return portset_info return None def create_portset(self): """ Create a portset """ if self.parameters.get('type') is None: self.module.fail_json( msg='Error: Missing required parameter for create (type)') portset_info = netapp_utils.zapi.NaElement("portset-create") portset_info.add_new_child("portset-name", self.parameters['name']) portset_info.add_new_child("portset-type", self.parameters['type']) try: self.server.invoke_successfully(portset_info, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error creating portset %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_portset(self): """ Delete a portset """ portset_info = netapp_utils.zapi.NaElement("portset-destroy") portset_info.add_new_child("portset-name", self.parameters['name']) if self.parameters.get('force'): portset_info.add_new_child("force", str(self.parameters['force'])) try: self.server.invoke_successfully(portset_info, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error deleting portset %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def remove_ports(self, ports): """ Removes all existing ports from portset :return: None """ for port in ports: self.modify_port(port, 'portset-remove', 'removing') def add_ports(self): """ Add the list of ports to portset :return: None """ # don't add if ports is empty string if self.parameters.get('ports') == [ '' ] or self.parameters.get('ports') is None: return for port in self.parameters['ports']: self.modify_port(port, 'portset-add', 'adding') def modify_port(self, port, zapi, action): """ Add or remove an port to/from a portset """ port.strip( ) # remove leading spaces if any (eg: if user types a space after comma in initiators list) options = { 'portset-name': self.parameters['name'], 'portset-port-name': port } portset_modify = netapp_utils.zapi.NaElement.create_node_with_children( zapi, **options) try: self.server.invoke_successfully(portset_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error %s port in portset %s: %s' % (action, self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Applies action from playbook """ netapp_utils.ems_log_event("na_ontap_autosupport", self.server) current, modify = self.portset_get(), None cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_portset() self.add_ports() elif cd_action == 'delete': self.delete_portset() elif modify: self.remove_ports(current['ports']) self.add_ports() self.module.exit_json(changed=self.na_helper.changed)