class NetAppONTAPCluster(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, type='str', choices=['present', 'absent'], default='present'), cluster_name=dict(required=False, type='str'), cluster_ip_address=dict(required=False, type='str'), cluster_location=dict(required=False, type='str'), cluster_contact=dict(required=False, type='str'), single_node_cluster=dict(required=False, type='bool', default=False), node_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) self.warnings = list() if self.parameters['state'] == 'absent' and self.parameters.get( 'node_name') is not None and self.parameters.get( 'cluster_ip_address') is not None: msg = 'when state is "absent", parameters are mutually exclusive: cluster_ip_address|node_name' self.module.fail_json(msg=msg) if self.parameters.get( 'node_name') is not None and '-' in self.parameters.get( 'node_name'): self.warnings.append( 'ONTAP ZAPI converts "-" to "_", node_name: %s may be changed or not matched' % self.parameters.get('node_name')) 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_cluster_identity(self, ignore_error=True): ''' get cluster information, but the cluster may not exist yet return: None if the cluster cannot be reached a dictionary of attributes ''' zapi = netapp_utils.zapi.NaElement('cluster-identity-get') try: result = self.server.invoke_successfully(zapi, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if ignore_error: return None self.module.fail_json( msg='Error fetching cluster identity info: %s' % to_native(error), exception=traceback.format_exc()) cluster_identity = dict() if result.get_child_by_name('attributes'): identity_info = result.get_child_by_name( 'attributes').get_child_by_name('cluster-identity-info') if identity_info: cluster_identity[ 'cluster_contact'] = identity_info.get_child_content( 'cluster-contact') cluster_identity[ 'cluster_location'] = identity_info.get_child_content( 'cluster-location') cluster_identity[ 'cluster_name'] = identity_info.get_child_content( 'cluster-name') return cluster_identity return None def get_cluster_nodes(self, ignore_error=True): ''' get cluster node names, but the cluster may not exist yet return: None if the cluster cannot be reached a list of nodes ''' zapi = netapp_utils.zapi.NaElement('cluster-node-get-iter') try: result = self.server.invoke_successfully(zapi, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if ignore_error: return None self.module.fail_json( msg='Error fetching cluster identity info: %s' % to_native(error), exception=traceback.format_exc()) cluster_nodes = list() if result.get_child_by_name('attributes-list'): for node_info in result.get_child_by_name( 'attributes-list').get_children(): node_name = node_info.get_child_content('node-name') if node_name is not None: cluster_nodes.append(node_name) return cluster_nodes return None def get_cluster_ip_addresses(self, cluster_ip_address, ignore_error=True): ''' get list of IP addresses for this cluster return: a list of dictionaries ''' if_infos = list() zapi = netapp_utils.zapi.NaElement('net-interface-get-iter') if cluster_ip_address is not None: query = netapp_utils.zapi.NaElement('query') net_info = netapp_utils.zapi.NaElement('net-interface-info') net_info.add_new_child('address', cluster_ip_address) query.add_child_elem(net_info) zapi.add_child_elem(query) try: result = self.server.invoke_successfully(zapi, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if ignore_error: return if_infos self.module.fail_json(msg='Error getting IP addresses: %s' % to_native(error), exception=traceback.format_exc()) if result.get_child_by_name('attributes-list'): for net_info in result.get_child_by_name( 'attributes-list').get_children(): if net_info: if_info = dict() if_info['address'] = net_info.get_child_content('address') if_info['home_node'] = net_info.get_child_content( 'home-node') if_infos.append(if_info) return if_infos def get_cluster_ip_address(self, cluster_ip_address, ignore_error=True): ''' get node information if it is discoverable return: None if the cluster cannot be reached a dictionary of attributes ''' if cluster_ip_address is None: return None nodes = self.get_cluster_ip_addresses(cluster_ip_address, ignore_error=ignore_error) return nodes if len(nodes) > 0 else None def create_cluster(self): """ Create a cluster """ dummy, minor = self.server.get_api_version() # Note: cannot use node_name here: # 13001:The "-node-names" parameter must be used with either the "-node-uuids" or the "-cluster-ips" parameters. options = {'cluster-name': self.parameters['cluster_name']} if minor >= 140: options['single-node-cluster'] = str( self.parameters.get('single_node_cluster')) cluster_create = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-create', **options) try: self.server.invoke_successfully(cluster_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: # Error 36503 denotes node already being used. if to_native(error.code) == "36503": return False self.module.fail_json( msg='Error creating cluster %s: %s' % (self.parameters['cluster_name'], to_native(error)), exception=traceback.format_exc()) return True def add_node(self, older_api=False): """ Add a node to an existing cluster 9.2 and 9.3 do not support cluster-ips so fallback to node-ip """ if self.parameters.get('cluster_ip_address') is not None: cluster_add_node = netapp_utils.zapi.NaElement('cluster-add-node') if older_api: cluster_add_node.add_new_child( 'node-ip', self.parameters.get('cluster_ip_address')) else: cluster_ips = netapp_utils.zapi.NaElement('cluster-ips') cluster_ips.add_new_child( 'ip-address', self.parameters.get('cluster_ip_address')) cluster_add_node.add_child_elem(cluster_ips) if self.parameters.get('node_name') is not None: node_names = netapp_utils.zapi.NaElement('node-names') node_names.add_new_child('string', self.parameters.get('node_name')) cluster_add_node.add_child_elem(node_names) else: return False try: self.server.invoke_successfully(cluster_add_node, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if error.message == "Extra input: cluster-ips": return self.add_node(older_api=True) # skip if error says no failed operations to retry. if to_native( error ) == "NetApp API failed. Reason - 13001:There are no failed \"cluster create\" or \"cluster add-node\" operations to retry.": return False self.module.fail_json( msg='Error adding node with ip %s: %s' % (self.parameters.get('cluster_ip_address'), to_native(error)), exception=traceback.format_exc()) return True def remove_node(self): """ Remove a node from an existing cluster """ cluster_remove_node = netapp_utils.zapi.NaElement( 'cluster-remove-node') from_node = '' # cluster-ip and node-name are mutually exclusive: # 13115:Element "cluster-ip" within "cluster-remove-node" has been excluded by another element. if self.parameters.get('cluster_ip_address') is not None: cluster_remove_node.add_new_child( 'cluster-ip', self.parameters.get('cluster_ip_address')) from_node = 'IP: %s' % self.parameters.get('cluster_ip_address') elif self.parameters.get('node_name') is not None: cluster_remove_node.add_new_child('node', self.parameters.get('node_name')) from_node = 'name: %s' % self.parameters.get('node_name') try: self.server.invoke_successfully(cluster_remove_node, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if error.message == "Unable to find API: cluster-remove-node": msg = 'Error: ZAPI is not available. Removing a node requires ONTAP 9.4 or newer.' self.module.fail_json(msg=msg) self.module.fail_json(msg='Error removing node with %s: %s' % (from_node, to_native(error)), exception=traceback.format_exc()) def modify_cluster_identity(self, modify): """ Modifies the cluster identity """ cluster_modify = netapp_utils.zapi.NaElement('cluster-identity-modify') if modify.get('cluster_name') is not None: cluster_modify.add_new_child("cluster-name", modify.get('cluster_name')) if modify.get('cluster_location') is not None: cluster_modify.add_new_child("cluster-location", modify.get('cluster_location')) if modify.get('cluster_contact') is not None: cluster_modify.add_new_child("cluster-contact", modify.get('cluster_contact')) try: self.server.invoke_successfully(cluster_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying cluster idetity details %s: %s' % (self.parameters['cluster_name'], to_native(error)), exception=traceback.format_exc()) return True def cluster_create_wait(self): """ Wait whilst cluster creation completes """ cluster_wait = netapp_utils.zapi.NaElement( 'cluster-create-join-progress-get') is_complete = False status = '' wait = False # do not wait on the first call while not is_complete and status not in ('failed', 'success'): if wait: time.sleep(10) else: wait = True try: result = self.server.invoke_successfully(cluster_wait, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating cluster %s: %s' % (self.parameters.get('cluster_name'), to_native(error)), exception=traceback.format_exc()) clus_progress = result.get_child_by_name('attributes') result = clus_progress.get_child_by_name( 'cluster-create-join-progress-info') is_complete = self.na_helper.get_value_for_bool( from_zapi=True, value=result.get_child_content('is-complete')) status = result.get_child_content('status') if not is_complete and status != 'success': current_status_message = result.get_child_content( 'current-status-message') self.module.fail_json( msg='Failed to create cluster %s: %s' % (self.parameters.get('cluster_name'), current_status_message)) return is_complete def node_add_wait(self): """ Wait whilst node is being added to the existing cluster """ cluster_node_status = netapp_utils.zapi.NaElement( 'cluster-add-node-status-get-iter') node_status_info = netapp_utils.zapi.NaElement( 'cluster-create-add-node-status-info') node_status_info.add_new_child( 'cluster-ip', self.parameters.get('cluster_ip_address')) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(node_status_info) cluster_node_status.add_child_elem(query) is_complete = None failure_msg = None wait = False # do not wait on the first call while is_complete != 'success' and is_complete != 'failure': if wait: time.sleep(10) else: wait = True try: result = self.server.invoke_successfully(cluster_node_status, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if error.message == "Unable to find API: cluster-add-node-status-get-iter": # This API is not supported for 9.3 or earlier releases, just wait a bit time.sleep(60) return self.module.fail_json( msg='Error adding node with ip address %s: %s' % (self.parameters.get('cluster_ip_address'), to_native(error)), exception=traceback.format_exc()) attributes_list = result.get_child_by_name('attributes-list') join_progress = attributes_list.get_child_by_name( 'cluster-create-add-node-status-info') is_complete = join_progress.get_child_content('status') failure_msg = join_progress.get_child_content('failure-msg') if is_complete != 'success': if 'Node is already in a cluster' in failure_msg: return else: self.module.fail_json( msg='Error adding node with ip address %s' % (self.parameters.get('cluster_ip_address'))) def node_remove_wait(self): ''' wait for node name or clister IP address to disappear ''' node_name = self.parameters.get('node_name') node_ip = self.parameters.get('cluster_ip_address') timer = 180 # 180 seconds while timer > 0: if node_name is not None and node_name not in self.get_cluster_nodes( ): return if node_ip is not None and self.get_cluster_ip_address( node_ip) is None: return time.sleep(30) timer -= 30 self.module.fail_json( msg='Timeout waiting for node to be removed from cluster.') def autosupport_log(self): """ Autosupport log for cluster :return: """ 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", cserver) def apply(self): """ Apply action to cluster """ cluster_action = None node_action = None cluster_identity = self.get_cluster_identity(ignore_error=True) if self.parameters.get('cluster_name') is not None: cluster_action = self.na_helper.get_cd_action( cluster_identity, self.parameters) if self.parameters.get('cluster_ip_address') is not None: existing_interfaces = self.get_cluster_ip_address( self.parameters.get('cluster_ip_address')) if self.parameters.get('state') == 'present': node_action = 'add_node' if existing_interfaces is None else None else: node_action = 'remove_node' if existing_interfaces is not None else None if self.parameters.get('node_name') is not None and self.parameters[ 'state'] == 'absent': nodes = self.get_cluster_nodes() if self.parameters.get('node_name') in nodes: node_action = 'remove_node' modify = self.na_helper.get_modified_attributes( cluster_identity, self.parameters) if node_action is not None: self.na_helper.changed = True if not self.module.check_mode: if cluster_action == 'create': if self.create_cluster(): self.cluster_create_wait() if node_action == 'add_node': if self.add_node(): self.node_add_wait() elif node_action == 'remove_node': self.remove_node() self.node_remove_wait() if modify: self.modify_cluster_identity(modify) self.autosupport_log() self.module.exit_json(changed=self.na_helper.changed, warnings=self.warnings)
class NetAppONTAPCluster(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, type='str', choices=['present'], default='present'), cluster_name=dict(required=False, type='str'), cluster_ip_address=dict(required=False, type='str'), cluster_location=dict(required=False, type='str'), cluster_contact=dict(required=False, type='str'), single_node_cluster=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_cluster_identity(self, ignore_error=True): ''' get cluster information, but the cluster may not exist yet return: None if the cluster cannot be reached a dictionary of attributes ''' zapi = netapp_utils.zapi.NaElement('cluster-identity-get') try: result = self.server.invoke_successfully(zapi, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if ignore_error: return None self.module.fail_json( msg='Error fetching cluster identity info: %s' % to_native(error), exception=traceback.format_exc()) cluster_identity = dict() if result.get_child_by_name('attributes'): identity_info = result.get_child_by_name( 'attributes').get_child_by_name('cluster-identity-info') if identity_info: cluster_identity[ 'cluster_contact'] = identity_info.get_child_content( 'cluster-contact') cluster_identity[ 'cluster_location'] = identity_info.get_child_content( 'cluster-location') cluster_identity[ 'cluster_name'] = identity_info.get_child_content( 'cluster-name') return cluster_identity return None def create_cluster(self): """ Create a cluster """ dummy, minor = self.server.get_api_version() options = {'cluster-name': self.parameters['cluster_name']} if minor >= 140: options['single-node-cluster'] = str( self.parameters.get('single_node_cluster')) cluster_create = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-create', **options) try: self.server.invoke_successfully(cluster_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: # Error 36503 denotes node already being used. if to_native(error.code) == "36503": return False else: self.module.fail_json( msg='Error creating cluster %s: %s' % (self.parameters['cluster_name'], to_native(error)), exception=traceback.format_exc()) return True def cluster_join(self): """ Add a node to an existing cluster """ if self.parameters.get('cluster_ip_address') is not None: cluster_add_node = netapp_utils.zapi.NaElement('cluster-add-node') cluster_ips = netapp_utils.zapi.NaElement('cluster-ips') cluster_ips.add_new_child( 'ip-address', self.parameters.get('cluster_ip_address')) cluster_add_node.add_child_elem(cluster_ips) else: return False try: self.server.invoke_successfully(cluster_add_node, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: # skip if error says no failed operations to retry. if to_native( error ) == "NetApp API failed. Reason - 13001:There are no failed \"cluster create\" or \"cluster add-node\" operations to retry.": return False else: self.module.fail_json( msg='Error adding node with ip %s: %s' % (self.parameters.get('cluster_ip_address'), to_native(error)), exception=traceback.format_exc()) return True def modify_cluster_identity(self, modify): """ Modifies the cluster identity """ cluster_modify = netapp_utils.zapi.NaElement('cluster-identity-modify') if modify.get('cluster_name') is not None: cluster_modify.add_new_child("cluster-name", modify.get('cluster_name')) if modify.get('cluster_location') is not None: cluster_modify.add_new_child("cluster-location", modify.get('cluster_location')) if modify.get('cluster_contact') is not None: cluster_modify.add_new_child("cluster-contact", modify.get('cluster_contact')) try: self.server.invoke_successfully(cluster_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying cluster idetity details %s: %s' % (self.parameters['cluster_name'], to_native(error)), exception=traceback.format_exc()) return True def cluster_create_wait(self): """ Wait whilst cluster creation completes """ cluster_wait = netapp_utils.zapi.NaElement( 'cluster-create-join-progress-get') is_complete = False status = '' wait = False # do not wait on the first call while not is_complete and status not in ('failed', 'success'): if wait: time.sleep(10) else: wait = True try: result = self.server.invoke_successfully(cluster_wait, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating cluster %s: %s' % (self.parameters.get('cluster_name'), to_native(error)), exception=traceback.format_exc()) clus_progress = result.get_child_by_name('attributes') result = clus_progress.get_child_by_name( 'cluster-create-join-progress-info') is_complete = self.na_helper.get_value_for_bool( from_zapi=True, value=result.get_child_content('is-complete')) status = result.get_child_content('status') if not is_complete and status != 'success': current_status_message = result.get_child_content( 'current-status-message') self.module.fail_json( msg='Failed to create cluster %s: %s' % (self.parameters.get('cluster_name'), current_status_message)) return is_complete def node_add_wait(self): """ Wait whilst node is being added to the existing cluster """ cluster_node_status = netapp_utils.zapi.NaElement( 'cluster-add-node-status-get-iter') node_status_info = netapp_utils.zapi.NaElement( 'cluster-create-add-node-status-info') node_status_info.add_new_child( 'cluster-ip', self.parameters.get('cluster_ip_address')) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(node_status_info) cluster_node_status.add_child_elem(query) result = self.server.invoke_successfully(cluster_node_status, True) is_complete = None failure_msg = None wait = False # do not wait on the first call while is_complete != 'success' and is_complete != 'failure': if wait: time.sleep(10) else: wait = True try: result = self.server.invoke_successfully(cluster_node_status, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error adding node with ip address %s: %s' % (self.parameters.get('cluster_ip_address'), to_native(error)), exception=traceback.format_exc()) attributes_list = result.get_child_by_name('attributes-list') join_progress = attributes_list.get_child_by_name( 'cluster-create-add-node-status-info') is_complete = join_progress.get_child_content('status') failure_msg = join_progress.get_child_content('failure-msg') if is_complete != 'success': if 'Node is already in a cluster' in failure_msg: return is_complete else: self.module.fail_json( msg='Error adding node with ip address %s' % (self.parameters.get('cluster_ip_address'))) return is_complete def autosupport_log(self): """ Autosupport log for cluster :return: """ 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", cserver) def apply(self): """ Apply action to cluster """ changed = False create_flag = False cluster_identity = self.get_cluster_identity(ignore_error=True) modify = self.na_helper.get_modified_attributes( cluster_identity, self.parameters) # temporary hack to fix check_mode for modify changed = self.na_helper.changed # TODO: JIRA-3048 fix check_mode for create and join if not self.module.check_mode: if self.parameters.get('state') == 'present': if self.parameters.get('cluster_name') is not None: create_flag = self.create_cluster() if create_flag: cluster_wait = self.cluster_create_wait() changed = True if cluster_wait else changed if not create_flag: join_flag = self.cluster_join() if join_flag: join_wait_flag = self.node_add_wait() changed = True if join_wait_flag == 'success' else changed if modify: self.modify_cluster_identity(modify) self.autosupport_log() self.module.exit_json(changed=changed)
class NetAppOntapSnapMirrorPolicy(object): """ Create, Modifies and Destroys a SnapMirror policy """ def __init__(self): """ Initialize the Ontap SnapMirror policy class """ self.use_rest = False 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'), policy_name=dict(required=True, type='str'), comment=dict(required=False, type='str'), policy_type=dict(required=False, type='str', choices=['vault', 'async_mirror', 'mirror_vault', 'strict_sync_mirror', 'sync_mirror']), tries=dict(required=False, type='str'), transfer_priority=dict(required=False, type='str', choices=['low', 'normal']), common_snapshot_schedule=dict(required=False, type='str'), ignore_atime=dict(required=False, type='bool'), is_network_compression_enabled=dict(required=False, type='bool'), owner=dict(required=False, type='str', choices=['cluster_admin', 'vserver_admin']), restart=dict(required=False, type='str', choices=['always', 'never', 'default']), )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) # set up variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # API should be used for ONTAP 9.6 or higher, Zapi for lower version self.restApi = OntapRestAPI(self.module) # some attributes are not supported in earlier REST implementation unsupported_rest_properties = ['owner', 'restart', 'transfer_priority', 'tries', 'ignore_atime', 'common_snapshot_schedule'] used_unsupported_rest_properties = [x for x in unsupported_rest_properties if x in self.parameters] self.use_rest, error = self.restApi.is_rest(used_unsupported_rest_properties) if error: self.module.fail_json(msg=error) if not self.use_rest: 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_snapmirror_policy(self): if self.use_rest: data = {'fields': 'uuid,name,svm.name,comment,network_compression_enabled,type', 'name': self.parameters['policy_name'], 'svm.name': self.parameters['vserver']} api = "snapmirror/policies" message, error = self.restApi.get(api, data) if error: self.module.fail_json(msg=error) if len(message['records']) != 0: return message['records'][0] return None else: return_value = None snapmirror_policy_get_iter = netapp_utils.zapi.NaElement('snapmirror-policy-get-iter') snapmirror_policy_info = netapp_utils.zapi.NaElement('snapmirror-policy-info') snapmirror_policy_info.add_new_child('policy-name', self.parameters['policy_name']) snapmirror_policy_info.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(snapmirror_policy_info) snapmirror_policy_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully(snapmirror_policy_get_iter, True) if result.get_child_by_name('attributes-list'): snapmirror_policy_attributes = result['attributes-list']['snapmirror-policy-info'] return_value = { 'policy_name': snapmirror_policy_attributes['policy-name'], 'tries': snapmirror_policy_attributes['tries'], 'transfer_priority': snapmirror_policy_attributes['transfer-priority'], 'is_network_compression_enabled': self.na_helper.get_value_for_bool (True, snapmirror_policy_attributes['is-network-compression-enabled']), 'restart': snapmirror_policy_attributes['restart'], 'ignore_atime': self.na_helper.get_value_for_bool(True, snapmirror_policy_attributes['ignore-atime']), 'vserver': snapmirror_policy_attributes['vserver-name'], } if snapmirror_policy_attributes.get_child_content('comment') is not None: return_value['comment'] = snapmirror_policy_attributes['comment'] if snapmirror_policy_attributes.get_child_content('type') is not None: return_value['policy_type'] = snapmirror_policy_attributes['type'] except netapp_utils.zapi.NaApiError as error: if 'NetApp API failed. Reason - 13001:' in to_native(error): # Policy does not exist pass else: self.module.fail_json(msg='Error getting snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) return return_value def create_snapmirror_policy(self): """ Creates a new storage efficiency policy """ if self.use_rest: data = {'name': self.parameters['policy_name'], 'svm': {'name': self.parameters['vserver']}} if 'policy_type' in self.parameters.keys(): if 'async_mirror' in self.parameters['policy_type']: data['type'] = 'async' elif 'sync_mirror' in self.parameters['policy_type']: data['type'] = 'sync' data['sync_type'] = 'sync' else: self.module.fail_json(msg='policy type in REST only supports options async_mirror or sync_mirror, given %s' % (self.parameters['policy_type'])) data = self.create_snapmirror_policy_obj_for_rest(data, data['type']) api = "snapmirror/policies" message, error = self.restApi.post(api, data) if error: self.module.fail_json(msg=error) else: snapmirror_policy_obj = netapp_utils.zapi.NaElement("snapmirror-policy-create") snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) if 'policy_type' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("type", self.parameters['policy_type']) snapmirror_policy_obj = self.create_snapmirror_policy_obj(snapmirror_policy_obj) try: self.server.invoke_successfully(snapmirror_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def create_snapmirror_policy_obj(self, snapmirror_policy_obj): if 'comment' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("comment", self.parameters['comment']) if 'common_snapshot_schedule' in self.parameters.keys() and 'sync_mirror' in self.parameters['policy_type']: snapmirror_policy_obj.add_new_child("common-snapshot-schedule", self.parameters['common_snapshot_schedule']) if 'ignore_atime' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("ignore-atime", self.na_helper.get_value_for_bool(False, self.parameters['ignore_atime'])) if 'is_network_compression_enabled' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("is-network-compression-enabled", self.na_helper.get_value_for_bool(False, self.parameters['is_network_compression_enabled'])) if 'owner' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("owner", self.parameters['owner']) if 'restart' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("restart", self.parameters['restart']) if 'transfer_priority' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("transfer-priority", self.parameters['transfer_priority']) if 'tries' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("tries", self.parameters['tries']) return snapmirror_policy_obj def create_snapmirror_policy_obj_for_rest(self, snapmirror_policy_obj, type=None): if 'comment' in self.parameters.keys(): snapmirror_policy_obj["comment"] = self.parameters['comment'] if 'is_network_compression_enabled' in self.parameters: if 'async' in type: snapmirror_policy_obj["network_compression_enabled"] = self.parameters['is_network_compression_enabled'] elif 'sync' in type: self.module.fail_json(msg="Input parameter network_compression_enabled is not valid for SnapMirror policy type sync") return snapmirror_policy_obj def delete_snapmirror_policy(self, uuid=None): """ Deletes a snapmirror policy """ if self.use_rest: api = "snapmirror/policies" data = {'uuid': uuid} message, error = self.restApi.delete(api, data) if error: self.module.fail_json(msg=error) else: snapmirror_policy_obj = netapp_utils.zapi.NaElement("snapmirror-policy-delete") snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) try: self.server.invoke_successfully(snapmirror_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def modify_snapmirror_policy(self, uuid=None, type=None): """ Modifies a snapmirror policy """ if self.use_rest: api = "snapmirror/policies/" + uuid data = self.create_snapmirror_policy_obj_for_rest(dict(), type) message, error = self.restApi.patch(api, data) if error: self.module.fail_json(msg=error) else: snapmirror_policy_obj = netapp_utils.zapi.NaElement("snapmirror-policy-modify") snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) snapmirror_policy_obj = self.create_snapmirror_policy_obj(snapmirror_policy_obj) try: self.server.invoke_successfully(snapmirror_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def asup_log_for_cserver(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("snapmirror_policy", cserver) def apply(self): uuid = None if not self.use_rest: self.asup_log_for_cserver() current, modify = self.get_snapmirror_policy(), None cd_action = self.na_helper.get_cd_action(current, self.parameters) if current and 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_snapmirror_policy() elif cd_action == 'delete': if self.use_rest: uuid = current['uuid'] self.delete_snapmirror_policy(uuid) elif modify: if self.use_rest: uuid = current['uuid'] self.modify_snapmirror_policy(uuid, current['type']) else: self.modify_snapmirror_policy() self.module.exit_json(changed=self.na_helper.changed)
class NetAppontapExportRule(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, type='str', choices=['present', 'absent'], default='present'), name=dict(required=True, type='str', aliases=['policy_name']), protocol=dict(required=False, type='list', elements='str', default=None, choices=[ 'any', 'nfs', 'nfs3', 'nfs4', 'cifs', 'flexcache' ]), client_match=dict(required=False, type='list', elements='str'), ro_rule=dict(required=False, type='list', elements='str', default=None, choices=[ 'any', 'none', 'never', 'krb5', 'krb5i', 'krb5p', 'ntlm', 'sys' ]), rw_rule=dict(required=False, type='list', elements='str', default=None, choices=[ 'any', 'none', 'never', 'krb5', 'krb5i', 'krb5p', 'ntlm', 'sys' ]), super_user_security=dict(required=False, type='list', elements='str', default=None, choices=[ 'any', 'none', 'never', 'krb5', 'krb5i', 'krb5p', 'ntlm', 'sys' ]), allow_suid=dict(required=False, type='bool'), rule_index=dict(required=False, type='int'), anonymous_user_id=dict(required=False, type='int'), 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) 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 = { 'client_match': 'client-match', 'name': 'policy-name' } self.na_helper.zapi_list_keys = { 'protocol': ('protocol', 'access-protocol'), 'ro_rule': ('ro-rule', 'security-flavor'), 'rw_rule': ('rw-rule', 'security-flavor'), 'super_user_security': ('super-user-security', 'security-flavor'), } self.na_helper.zapi_bool_keys = { 'allow_suid': 'is-allow-set-uid-enabled' } self.na_helper.zapi_int_keys = { 'rule_index': 'rule-index', 'anonymous_user_id': 'anonymous-user-id' } def set_query_parameters(self): """ Return dictionary of query parameters and :return: """ query = { 'policy-name': self.parameters['name'], 'vserver': self.parameters['vserver'] } if self.parameters.get('rule_index'): query['rule-index'] = self.parameters['rule_index'] elif self.parameters.get('client_match'): query['client-match'] = self.parameters['client_match'] else: self.module.fail_json( msg= "Need to specify at least one of the rule_index and client_match option." ) attributes = {'query': {'export-rule-info': query}} return attributes def get_export_policy_rule(self): """ Return details about the export policy rule :param: name : Name of the export_policy :return: Details about the export_policy. None if not found. :rtype: dict """ current, result = None, None rule_iter = netapp_utils.zapi.NaElement('export-rule-get-iter') rule_iter.translate_struct(self.set_query_parameters()) try: result = self.server.invoke_successfully(rule_iter, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error getting export policy rule %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if result is not None and \ result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1: current = dict() rule_info = result.get_child_by_name( 'attributes-list').get_child_by_name('export-rule-info') for item_key, zapi_key in self.na_helper.zapi_string_keys.items(): current[item_key] = rule_info.get_child_content(zapi_key) for item_key, zapi_key in self.na_helper.zapi_bool_keys.items(): current[item_key] = self.na_helper.get_value_for_bool( from_zapi=True, value=rule_info[zapi_key]) for item_key, zapi_key in self.na_helper.zapi_int_keys.items(): current[item_key] = self.na_helper.get_value_for_int( from_zapi=True, value=rule_info[zapi_key]) for item_key, zapi_key in self.na_helper.zapi_list_keys.items(): parent, dummy = zapi_key current[item_key] = self.na_helper.get_value_for_list( from_zapi=True, zapi_parent=rule_info.get_child_by_name(parent)) current['num_records'] = int( result.get_child_content('num-records')) if not self.parameters.get('rule_index'): self.parameters['rule_index'] = current['rule_index'] return current def get_export_policy(self): """ Return details about the export-policy :param: name : Name of the export-policy :return: Details about the export-policy. None if not found. :rtype: dict """ export_policy_iter = netapp_utils.zapi.NaElement( 'export-policy-get-iter') attributes = { 'query': { 'export-policy-info': { 'policy-name': self.parameters['name'], 'vserver': self.parameters['vserver'] } } } export_policy_iter.translate_struct(attributes) try: result = self.server.invoke_successfully(export_policy_iter, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error getting export policy %s: %s' % (self.parameters['name'], 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 add_parameters_for_create_or_modify(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 key in values: if key in self.na_helper.zapi_string_keys: zapi_key = self.na_helper.zapi_string_keys.get(key) na_element_object[zapi_key] = values[key] elif key in self.na_helper.zapi_list_keys: parent_key, child_key = self.na_helper.zapi_list_keys.get(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[key])) elif key in self.na_helper.zapi_int_keys: zapi_key = self.na_helper.zapi_int_keys.get(key) na_element_object[zapi_key] = self.na_helper.get_value_for_int( from_zapi=False, value=values[key]) elif key in self.na_helper.zapi_bool_keys: zapi_key = self.na_helper.zapi_bool_keys.get(key) na_element_object[ zapi_key] = self.na_helper.get_value_for_bool( from_zapi=False, value=values[key]) def create_export_policy_rule(self): """ create rule for the export policy. """ for key in ['client_match', 'ro_rule', 'rw_rule']: if self.parameters.get(key) is None: self.module.fail_json( msg= 'Error: Missing required param for creating export policy rule %s' % key) export_rule_create = netapp_utils.zapi.NaElement('export-rule-create') self.add_parameters_for_create_or_modify(export_rule_create, self.parameters) try: self.server.invoke_successfully(export_rule_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating export policy rule %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_export_policy(self): """ Creates an export policy """ export_policy_create = netapp_utils.zapi.NaElement.create_node_with_children( 'export-policy-create', **{'policy-name': self.parameters['name']}) try: self.server.invoke_successfully(export_policy_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating export-policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_export_policy_rule(self, rule_index): """ delete rule for the export policy. """ export_rule_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'export-rule-destroy', **{ 'policy-name': self.parameters['name'], 'rule-index': str(rule_index) }) try: self.server.invoke_successfully(export_rule_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting export policy rule %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_export_policy_rule(self, params): ''' Modify an existing export policy rule :param params: dict() of attributes with desired values :return: None ''' export_rule_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'export-rule-modify', **{ 'policy-name': self.parameters['name'], 'rule-index': str(self.parameters['rule_index']) }) self.add_parameters_for_create_or_modify(export_rule_modify, params) try: self.server.invoke_successfully(export_rule_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying allow_suid %s: %s' % (self.parameters['allow_suid'], to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): netapp_utils.ems_log_event("na_ontap_export_policy_rules", self.server) def apply(self): ''' Apply required action from the play''' self.autosupport_log() # convert client_match list to comma-separated string if self.parameters.get('client_match') is not None: self.parameters['client_match'] = ','.join( self.parameters['client_match']) self.parameters['client_match'] = self.parameters[ 'client_match'].replace(' ', '') current, modify = self.get_export_policy_rule(), None 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: # create export policy (if policy doesn't exist) only when changed=True if action == 'create': if not self.get_export_policy(): self.create_export_policy() self.create_export_policy_rule() elif action == 'delete': if current['num_records'] > 1: self.module.fail_json( msg='Multiple export policy rules exist.' 'Please specify a rule_index to delete') self.delete_export_policy_rule(current['rule_index']) elif modify: self.modify_export_policy_rule(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapServiceProcessorNetwork(object): """ Modify a Service Processor Network """ def __init__(self): """ Initialize the NetAppOntapServiceProcessorNetwork class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, type='str', choices=['present'], default='present'), address_type=dict(required=True, type='str', choices=['ipv4', 'ipv6']), is_enabled=dict(required=True, type='bool'), node=dict(required=True, type='str'), dhcp=dict(required=False, type='str', choices=['v4', 'none']), gateway_ip_address=dict(required=False, type='str'), ip_address=dict(required=False, type='str'), netmask=dict(required=False, type='str'), prefix_length=dict(required=False, type='int'), wait_for_completion=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) 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=None) return def set_playbook_zapi_key_map(self): self.na_helper.zapi_string_keys = { 'address_type': 'address-type', 'node': 'node', 'dhcp': 'dhcp', 'gateway_ip_address': 'gateway-ip-address', 'ip_address': 'ip-address', 'netmask': 'netmask' } self.na_helper.zapi_int_keys = { 'prefix_length': 'prefix-length' } self.na_helper.zapi_bool_keys = { 'is_enabled': 'is-enabled', } self.na_helper.zapi_required = { 'address_type': 'address-type', 'node': 'node', 'is_enabled': 'is-enabled' } def get_sp_network_status(self): """ Return status of service processor network :param: name : name of the node :return: Status of the service processor network :rtype: dict """ spn_get_iter = netapp_utils.zapi.NaElement('service-processor-network-get-iter') query_info = { 'query': { 'service-processor-network-info': { 'node': self.parameters['node'], 'address-type': self.parameters['address_type'] } } } spn_get_iter.translate_struct(query_info) result = self.server.invoke_successfully(spn_get_iter, True) if int(result['num-records']) >= 1: sp_attr_info = result['attributes-list']['service-processor-network-info'] return sp_attr_info.get_child_content('setup-status') return None def get_service_processor_network(self): """ Return details about service processor network :param: name : name of the node :return: Details about service processor network. None if not found. :rtype: dict """ spn_get_iter = netapp_utils.zapi.NaElement('service-processor-network-get-iter') query_info = { 'query': { 'service-processor-network-info': { 'node': self.parameters['node'] } } } spn_get_iter.translate_struct(query_info) result = self.server.invoke_successfully(spn_get_iter, True) sp_details = None # check if job exists if int(result['num-records']) >= 1: sp_details = dict() sp_attr_info = result['attributes-list']['service-processor-network-info'] for item_key, zapi_key in self.na_helper.zapi_string_keys.items(): sp_details[item_key] = sp_attr_info.get_child_content(zapi_key) for item_key, zapi_key in self.na_helper.zapi_bool_keys.items(): sp_details[item_key] = self.na_helper.get_value_for_bool(from_zapi=True, value=sp_attr_info.get_child_content(zapi_key)) for item_key, zapi_key in self.na_helper.zapi_int_keys.items(): sp_details[item_key] = self.na_helper.get_value_for_int(from_zapi=True, value=sp_attr_info.get_child_content(zapi_key)) return sp_details def modify_service_processor_network(self, params=None): """ Modify a service processor network. :param params: A dict of modified options. When dhcp is not set to v4, ip_address, netmask, and gateway_ip_address must be specified even if remains the same. """ if self.parameters['is_enabled'] is False: if params.get('is_enabled') and len(params) > 1: self.module.fail_json(msg='Error: Cannot modify any other parameter for a service processor network if option "is_enabled" is set to false.') elif params.get('is_enabled') is None and len(params) > 0: self.module.fail_json(msg='Error: Cannot modify a service processor network if it is disabled.') sp_modify = netapp_utils.zapi.NaElement('service-processor-network-modify') sp_modify.add_new_child("node", self.parameters['node']) sp_modify.add_new_child("address-type", self.parameters['address_type']) sp_attributes = dict() for item_key in self.parameters: if item_key in self.na_helper.zapi_string_keys: zapi_key = self.na_helper.zapi_string_keys.get(item_key) sp_attributes[zapi_key] = self.parameters[item_key] elif item_key in self.na_helper.zapi_bool_keys: zapi_key = self.na_helper.zapi_bool_keys.get(item_key) sp_attributes[zapi_key] = self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters[item_key]) elif item_key in self.na_helper.zapi_int_keys: zapi_key = self.na_helper.zapi_int_keys.get(item_key) sp_attributes[zapi_key] = self.na_helper.get_value_for_int(from_zapi=False, value=self.parameters[item_key]) sp_modify.translate_struct(sp_attributes) try: self.server.invoke_successfully(sp_modify, enable_tunneling=True) if self.parameters.get('wait_for_completion'): retries = 10 while self.get_sp_network_status() == 'in_progress' and retries > 0: time.sleep(10) retries = retries - 1 except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying service processor network: %s' % (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_service_processor_network", cserver) def apply(self): """ Run Module based on play book """ self.autosupport_log() current = self.get_service_processor_network() modify = self.na_helper.get_modified_attributes(current, self.parameters) if not current: self.module.fail_json(msg='Error No Service Processor for node: %s' % self.parameters['node']) if self.na_helper.changed: if self.module.check_mode: pass else: self.modify_service_processor_network(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPSnapmirror(object): """ Class with Snapmirror 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_vserver=dict(required=False, type='str'), destination_vserver=dict(required=False, type='str'), source_volume=dict(required=False, type='str'), destination_volume=dict(required=False, type='str'), source_path=dict(required=False, type='str'), destination_path=dict(required=False, type='str'), schedule=dict(required=False, type='str'), policy=dict(required=False, type='str'), relationship_type=dict(required=False, type='str', choices=[ 'data_protection', 'load_sharing', 'vault', 'restore', 'transition_data_protection', 'extended_data_protection' ]), source_hostname=dict(required=False, type='str'), connection_type=dict(required=False, type='str', choices=[ 'ontap_ontap', 'elementsw_ontap', 'ontap_elementsw' ], default='ontap_ontap'), source_username=dict(required=False, type='str'), source_password=dict(required=False, type='str', no_log=True), max_transfer_rate=dict(required=False, type='int'), initialize=dict(required=False, type='bool', default=True), update=dict(required=False, type='bool', default=True), identity_preserve=dict(required=False, type='bool'), relationship_state=dict(required=False, type='str', choices=['active', 'broken'], default='active'), relationship_info_only=dict(required=False, type='bool', default=False))) self.module = AnsibleModule( argument_spec=self.argument_spec, required_together=(['source_volume', 'destination_volume'], ['source_vserver', 'destination_vserver']), supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # setup later if required self.source_server = None # only for ElementSW -> ONTAP snapmirroring, validate if ElementSW SDK is available if self.parameters.get('connection_type') in [ 'elementsw_ontap', 'ontap_elementsw' ]: if HAS_SF_SDK is False: self.module.fail_json( msg="Unable to import the SolidFire Python SDK") if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") if self.parameters.get('connection_type') != 'ontap_elementsw': self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) else: if self.parameters.get('source_username'): self.module.params['username'] = self.parameters[ 'source_username'] if self.parameters.get('source_password'): self.module.params['password'] = self.parameters[ 'source_password'] self.module.params['hostname'] = self.parameters['source_hostname'] self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def set_element_connection(self, kind): if kind == 'source': self.module.params['hostname'] = self.parameters['source_hostname'] self.module.params['username'] = self.parameters['source_username'] self.module.params['password'] = self.parameters['source_password'] elif kind == 'destination': self.module.params['hostname'] = self.parameters['hostname'] self.module.params['username'] = self.parameters['username'] self.module.params['password'] = self.parameters['password'] elem = netapp_utils.create_sf_connection(module=self.module) elementsw_helper = NaElementSWModule(elem) return elementsw_helper, elem def snapmirror_get_iter(self, destination=None): """ Compose NaElement object to query current SnapMirror relations using destination-path SnapMirror relation for a destination path is unique :return: NaElement object for SnapMirror-get-iter """ snapmirror_get_iter = netapp_utils.zapi.NaElement( 'snapmirror-get-iter') query = netapp_utils.zapi.NaElement('query') snapmirror_info = netapp_utils.zapi.NaElement('snapmirror-info') if destination is None: destination = self.parameters['destination_path'] snapmirror_info.add_new_child('destination-location', destination) query.add_child_elem(snapmirror_info) snapmirror_get_iter.add_child_elem(query) return snapmirror_get_iter def snapmirror_get(self, destination=None): """ Get current SnapMirror relations :return: Dictionary of current SnapMirror details if query successful, else None """ snapmirror_get_iter = self.snapmirror_get_iter(destination) snap_info = dict() try: result = self.server.invoke_successfully(snapmirror_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching snapmirror 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')) > 0: snapmirror_info = result.get_child_by_name( 'attributes-list').get_child_by_name('snapmirror-info') snap_info['mirror_state'] = snapmirror_info.get_child_content( 'mirror-state') snap_info['status'] = snapmirror_info.get_child_content( 'relationship-status') snap_info['schedule'] = snapmirror_info.get_child_content( 'schedule') snap_info['policy'] = snapmirror_info.get_child_content('policy') snap_info['relationship'] = snapmirror_info.get_child_content( 'relationship-type') if snapmirror_info.get_child_by_name('max-transfer-rate'): snap_info['max_transfer_rate'] = int( snapmirror_info.get_child_content('max-transfer-rate')) if snap_info['schedule'] is None: snap_info['schedule'] = "" return snap_info return None def check_if_remote_volume_exists(self): """ Validate existence of source volume :return: True if volume exists, False otherwise """ self.set_source_cluster_connection() # do a get volume to check if volume exists or not volume_info = netapp_utils.zapi.NaElement('volume-get-iter') volume_attributes = netapp_utils.zapi.NaElement('volume-attributes') volume_id_attributes = netapp_utils.zapi.NaElement( 'volume-id-attributes') volume_id_attributes.add_new_child('name', self.parameters['source_volume']) # if source_volume is present, then source_vserver is also guaranteed to be present volume_id_attributes.add_new_child('vserver-name', self.parameters['source_vserver']) volume_attributes.add_child_elem(volume_id_attributes) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(volume_attributes) volume_info.add_child_elem(query) try: result = self.source_server.invoke_successfully(volume_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching source volume details %s : %s' % (self.parameters['source_volume'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) > 0: return True return False def snapmirror_create(self): """ Create a SnapMirror relationship """ if self.parameters.get('source_hostname') and self.parameters.get( 'source_volume'): if not self.check_if_remote_volume_exists(): self.module.fail_json( msg= 'Source volume does not exist. Please specify a volume that exists' ) options = { 'source-location': self.parameters['source_path'], 'destination-location': self.parameters['destination_path'] } snapmirror_create = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-create', **options) if self.parameters.get('relationship_type'): snapmirror_create.add_new_child( 'relationship-type', self.parameters['relationship_type']) if self.parameters.get('schedule'): snapmirror_create.add_new_child('schedule', self.parameters['schedule']) if self.parameters.get('policy'): snapmirror_create.add_new_child('policy', self.parameters['policy']) if self.parameters.get('max_transfer_rate'): snapmirror_create.add_new_child( 'max-transfer-rate', str(self.parameters['max_transfer_rate'])) if self.parameters.get('identity_preserve'): snapmirror_create.add_new_child( 'identity-preserve', str(self.parameters['identity_preserve'])) try: self.server.invoke_successfully(snapmirror_create, enable_tunneling=True) if self.parameters['initialize']: self.snapmirror_initialize() except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating SnapMirror %s' % to_native(error), exception=traceback.format_exc()) def set_source_cluster_connection(self): """ Setup ontap ZAPI server connection for source hostname :return: None """ if self.parameters.get('source_username'): self.module.params['username'] = self.parameters['source_username'] if self.parameters.get('source_password'): self.module.params['password'] = self.parameters['source_password'] self.module.params['hostname'] = self.parameters['source_hostname'] self.source_server = netapp_utils.setup_na_ontap_zapi( module=self.module) def delete_snapmirror(self, is_hci, relationship_type, mirror_state): """ Delete a SnapMirror relationship #1. Quiesce the SnapMirror relationship at destination #2. Break the SnapMirror relationship at the destination #3. Release the SnapMirror at source #4. Delete SnapMirror at destination """ if not is_hci: if not self.parameters.get('source_hostname'): self.module.fail_json( msg='Missing parameters for delete: Please specify the ' 'source cluster hostname to release the SnapMirror relationship' ) # Quiesce at destination self.snapmirror_quiesce() # Break at destination if relationship_type not in [ 'load_sharing', 'vault' ] and mirror_state not in ['uninitialized', 'broken-off']: self.snapmirror_break() # if source is ONTAP, release the destination at source cluster if not is_hci: self.set_source_cluster_connection() if self.get_destination(): # Release at source self.snapmirror_release() # Delete at destination self.snapmirror_delete() def snapmirror_quiesce(self): """ Quiesce SnapMirror relationship - disable all future transfers to this destination """ result = None options = {'destination-location': self.parameters['destination_path']} snapmirror_quiesce = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-quiesce', **options) try: result = self.server.invoke_successfully(snapmirror_quiesce, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error Quiescing SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) # checking if quiesce was passed successfully if result is not None and result['status'] == 'passed': return elif result is not None and result['status'] != 'passed': retries = 5 while retries > 0: time.sleep(5) retries = retries - 1 status = self.snapmirror_get() if status['status'] == 'quiesced': return if retries == 0: self.module.fail_json( msg= 'Taking a long time to Quiescing SnapMirror, try again later' ) def snapmirror_delete(self): """ Delete SnapMirror relationship at destination cluster """ options = {'destination-location': self.parameters['destination_path']} snapmirror_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-destroy', **options) try: self.server.invoke_successfully(snapmirror_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_break(self, destination=None): """ Break SnapMirror relationship at destination cluster #1. Quiesce the SnapMirror relationship at destination #2. Break the SnapMirror relationship at the destination """ self.snapmirror_quiesce() if destination is None: destination = self.parameters['destination_path'] options = {'destination-location': destination} snapmirror_break = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-break', **options) try: self.server.invoke_successfully(snapmirror_break, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error breaking SnapMirror relationship : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_release(self): """ Release SnapMirror relationship from source cluster """ options = { 'destination-location': self.parameters['destination_path'], 'relationship-info-only': self.na_helper.get_value_for_bool( False, self.parameters['relationship_info_only']) } snapmirror_release = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-release', **options) try: self.source_server.invoke_successfully(snapmirror_release, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error releasing SnapMirror relationship : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_abort(self): """ Abort a SnapMirror relationship in progress """ options = {'destination-location': self.parameters['destination_path']} snapmirror_abort = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-abort', **options) try: self.server.invoke_successfully(snapmirror_abort, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error aborting SnapMirror relationship : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_initialize(self): """ Initialize SnapMirror based on relationship type """ current = self.snapmirror_get() if current['mirror_state'] != 'snapmirrored': initialize_zapi = 'snapmirror-initialize' if self.parameters.get('relationship_type') and self.parameters[ 'relationship_type'] == 'load_sharing': initialize_zapi = 'snapmirror-initialize-ls-set' options = {'source-location': self.parameters['source_path']} else: options = { 'destination-location': self.parameters['destination_path'] } snapmirror_init = netapp_utils.zapi.NaElement.create_node_with_children( initialize_zapi, **options) try: self.server.invoke_successfully(snapmirror_init, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error initializing SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_resync(self): """ resync SnapMirror based on relationship type """ options = {'destination-location': self.parameters['destination_path']} snapmirror_resync = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-resync', **options) try: self.server.invoke_successfully(snapmirror_resync, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error resyncing SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_modify(self, modify): """ Modify SnapMirror schedule or policy """ options = {'destination-location': self.parameters['destination_path']} snapmirror_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-modify', **options) if modify.get('schedule') is not None: snapmirror_modify.add_new_child('schedule', modify.get('schedule')) if modify.get('policy'): snapmirror_modify.add_new_child('policy', modify.get('policy')) if modify.get('max_transfer_rate'): snapmirror_modify.add_new_child( 'max-transfer-rate', str(modify.get('max_transfer_rate'))) try: self.server.invoke_successfully(snapmirror_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying SnapMirror schedule or policy : %s' % (to_native(error)), exception=traceback.format_exc()) def snapmirror_update(self): """ Update data in destination endpoint """ options = {'destination-location': self.parameters['destination_path']} snapmirror_update = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-update', **options) try: result = self.server.invoke_successfully(snapmirror_update, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error updating SnapMirror : %s' % (to_native(error)), exception=traceback.format_exc()) def check_parameters(self): """ Validate parameters and fail if one or more required params are missing Update source and destination path from vserver and volume parameters """ if self.parameters['state'] == 'present'\ and (self.parameters.get('source_path') or self.parameters.get('destination_path')): if not self.parameters.get( 'destination_path') or not self.parameters.get( 'source_path'): self.module.fail_json( msg='Missing parameters: Source path or Destination path') elif self.parameters.get('source_volume'): if not self.parameters.get( 'source_vserver') or not self.parameters.get( 'destination_vserver'): self.module.fail_json( msg= 'Missing parameters: source vserver or destination vserver or both' ) self.parameters['source_path'] = self.parameters[ 'source_vserver'] + ":" + self.parameters['source_volume'] self.parameters['destination_path'] = self.parameters['destination_vserver'] + ":" +\ self.parameters['destination_volume'] elif self.parameters.get('source_vserver'): self.parameters[ 'source_path'] = self.parameters['source_vserver'] + ":" self.parameters['destination_path'] = self.parameters[ 'destination_vserver'] + ":" def get_destination(self): result = None release_get = netapp_utils.zapi.NaElement( 'snapmirror-get-destination-iter') query = netapp_utils.zapi.NaElement('query') snapmirror_dest_info = netapp_utils.zapi.NaElement( 'snapmirror-destination-info') snapmirror_dest_info.add_new_child('destination-location', self.parameters['destination_path']) query.add_child_elem(snapmirror_dest_info) release_get.add_child_elem(query) try: result = self.source_server.invoke_successfully( release_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching snapmirror destinations 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')) > 0: return True return None @staticmethod def element_source_path_format_matches(value): return re.match( pattern=r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\/lun\/[0-9]+", string=value) def check_elementsw_parameters(self, kind='source'): """ Validate all ElementSW cluster parameters required for managing the SnapMirror relationship Validate if both source and destination paths are present Validate if source_path follows the required format Validate SVIP Validate if ElementSW volume exists :return: None """ path = None if kind == 'destination': path = self.parameters.get('destination_path') elif kind == 'source': path = self.parameters.get('source_path') if path is None: self.module.fail_json( msg="Error: Missing required parameter %s_path for " "connection_type %s" % (kind, self.parameters['connection_type'])) else: if NetAppONTAPSnapmirror.element_source_path_format_matches( path) is None: self.module.fail_json( msg="Error: invalid %s_path %s. " "If the path is a ElementSW cluster, the value should be of the format" " <Element_SVIP>:/lun/<Element_VOLUME_ID>" % (kind, path)) # validate source_path elementsw_helper, elem = self.set_element_connection(kind) self.validate_elementsw_svip(path, elem) self.check_if_elementsw_volume_exists(path, elementsw_helper) def validate_elementsw_svip(self, path, elem): """ Validate ElementSW cluster SVIP :return: None """ result = None try: result = elem.get_cluster_info() except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error fetching SVIP", exception=to_native(err)) if result and result.cluster_info.svip: cluster_svip = result.cluster_info.svip svip = path.split(':')[0] # split IP address from source_path if svip != cluster_svip: self.module.fail_json(msg="Error: Invalid SVIP") def check_if_elementsw_volume_exists(self, path, elementsw_helper): """ Check if remote ElementSW volume exists :return: None """ volume_id, vol_id = None, path.split('/')[-1] try: volume_id = elementsw_helper.volume_id_exists(int(vol_id)) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error fetching Volume details", exception=to_native(err)) if volume_id is None: self.module.fail_json( msg= "Error: Source volume does not exist in the ElementSW cluster") 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) if results is None: # We may be running on a vserser try: netapp_utils.ems_log_event(event_name, self.server) except netapp_utils.zapi.NaApiError: # Don't fail if we cannot log usage pass else: 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 SnapMirror """ self.asup_log_for_cserver("na_ontap_snapmirror") # source is ElementSW if self.parameters['state'] == 'present' and self.parameters.get( 'connection_type') == 'elementsw_ontap': self.check_elementsw_parameters() elif self.parameters.get('connection_type') == 'ontap_elementsw': self.check_elementsw_parameters('destination') else: self.check_parameters() if self.parameters['state'] == 'present' and self.parameters.get( 'connection_type') == 'ontap_elementsw': current_elementsw_ontap = self.snapmirror_get( self.parameters['source_path']) if current_elementsw_ontap is None: self.module.fail_json( msg= 'Error: creating an ONTAP to ElementSW snapmirror relationship requires an ' 'established SnapMirror relation from ElementSW to ONTAP cluster' ) current = self.snapmirror_get() cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes( current, self.parameters) element_snapmirror = False if cd_action == 'create': self.snapmirror_create() elif cd_action == 'delete': if current['status'] == 'transferring': self.snapmirror_abort() else: if self.parameters.get('connection_type') == 'elementsw_ontap': element_snapmirror = True self.delete_snapmirror(element_snapmirror, current['relationship'], current['mirror_state']) else: if modify: self.snapmirror_modify(modify) # break relationship when 'relationship_state' == 'broken' if current and self.parameters[ 'state'] == 'present' and self.parameters[ 'relationship_state'] == 'broken': if current['mirror_state'] == 'uninitialized': self.module.fail_json( msg= 'SnapMirror relationship cannot be broken if mirror state is uninitialized' ) elif current['relationship'] in ['load_sharing', 'vault']: self.module.fail_json( msg= 'SnapMirror break is not allowed in a load_sharing or vault relationship' ) elif current['mirror_state'] != 'broken-off': self.snapmirror_break() self.na_helper.changed = True # check for initialize elif current and self.parameters['initialize'] and self.parameters['relationship_state'] == 'active'\ and current['mirror_state'] == 'uninitialized': self.snapmirror_initialize() # set changed explicitly for initialize self.na_helper.changed = True if self.parameters['state'] == 'present' and self.parameters[ 'relationship_state'] == 'active': # resync when state is broken-off if current['mirror_state'] == 'broken-off': self.snapmirror_resync() # set changed explicitly for resync self.na_helper.changed = True # Update when create is called again, or modify is being called elif self.parameters['update']: current = self.snapmirror_get() if current['mirror_state'] == 'snapmirrored': self.snapmirror_update() self.na_helper.changed = True 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'), is_shared=dict(required=False, type='bool'), 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'), 'is_shared': self.na_helper.get_value_for_bool( True, policy_info.get_child_content('is-shared')) } 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']) if self.parameters.get('is_shared') is not None: policy_group.add_new_child( 'is-shared', self.na_helper.get_value_for_bool( False, self.parameters['is_shared'])) 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. """ if 'is_shared' in modify: self.module.fail_json( msg='Error cannot modify is_shared attribute.') if any([ attribute in modify for 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 cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create' and self.parameters.get('from_name'): # create policy by renaming an existing one old_policy = self.get_policy_group(self.parameters['from_name']) rename = self.na_helper.is_rename_action(old_policy, current) if rename: current = old_policy cd_action = None if rename is None: self.module.fail_json( msg='Error renaming qos policy group: cannot find %s' % self.parameters['from_name']) modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed and not self.module.check_mode: 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 NetAppOntapLDAPClient(object): ''' LDAP Client definition class ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(ad_domain=dict(required=False, default=None, type='str'), base_dn=dict(required=False, type='str'), base_scope=dict(required=False, default=None, choices=['subtree', 'onelevel', 'base']), bind_as_cifs_server=dict(required=False, type='bool'), bind_dn=dict(required=False, default=None, type='str'), bind_password=dict(type='str', required=False, default=None, no_log=True), name=dict(required=True, type='str'), ldap_servers=dict(required=False, type='list', elements='str'), min_bind_level=dict(required=False, default=None, choices=['anonymous', 'simple', 'sasl']), preferred_ad_servers=dict(required=False, type='list', elements='str'), port=dict(required=False, default=None, type='int'), query_timeout=dict(required=False, default=None, type='int'), referral_enabled=dict(required=False, type='bool'), schema=dict( required=False, default=None, choices=['AD-IDMU', 'AD-SFU', 'MS-AD-BIS', 'RFC-2307']), session_security=dict(required=False, default=None, choices=['none', 'sign', 'seal']), state=dict(required=False, choices=['present', 'absent'], default='present'), use_start_tls=dict(required=False, type='bool'), vserver=dict(required=True, type='str'))) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, required_if=[ ('state', 'present', ['schema']), ], mutually_exclusive=[['ldap_servers', 'ad_domain'], ['ldap_servers', 'preferred_ad_servers']], ) 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 = [ 'ad_domain', 'base_dn', 'base_scope', 'bind_as_cifs_server', 'bind_dn', 'bind_password', 'min_bind_level', 'port', 'query_timeout', 'referral_enabled', 'session_security', 'use_start_tls' ] def get_ldap_client(self, client_config_name=None, vserver_name=None): ''' Checks if LDAP client config exists. :return: ldap client config object if found None if not found :rtype: object/None ''' # Make query client_config_info = netapp_utils.zapi.NaElement( 'ldap-client-get-iter') if client_config_name is None: client_config_name = self.parameters['name'] if vserver_name is None: vserver_name = '*' query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'ldap-client', **{ 'ldap-client-config': client_config_name, 'vserver': vserver_name }) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) client_config_info.add_child_elem(query) result = self.server.invoke_successfully(client_config_info, enable_tunneling=False) # Get LDAP client configuration details client_config_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') client_config_info = attributes_list.get_child_by_name( 'ldap-client') # Get LDAP servers list ldap_server_list = list() get_list = client_config_info.get_child_by_name('ldap-servers') if get_list is not None: ldap_server_list = [ x.get_content() for x in get_list.get_children() ] preferred_ad_servers_list = list() get_pref_ad_server_list = client_config_info.get_child_by_name( 'preferred-ad-servers') if get_pref_ad_server_list is not None: preferred_ad_servers_list = [ x.get_content() for x in get_pref_ad_server_list.get_children() ] # Define config details structure client_config_details = { 'name': client_config_info.get_child_content('ldap-client-config'), 'ldap_servers': ldap_server_list, 'ad_domain': client_config_info.get_child_content('ad-domain'), 'base_dn': client_config_info.get_child_content('base-dn'), 'base_scope': client_config_info.get_child_content('base-scope'), 'bind_as_cifs_server': self.na_helper.get_value_for_bool( from_zapi=True, value=client_config_info.get_child_content( 'bind-as-cifs-server')), 'bind_dn': client_config_info.get_child_content('bind-dn'), 'bind_password': client_config_info.get_child_content('bind-password'), 'min_bind_level': client_config_info.get_child_content('min-bind-level'), 'port': self.na_helper.get_value_for_int( from_zapi=True, value=client_config_info.get_child_content('port')), 'preferred_ad_servers': preferred_ad_servers_list, 'query_timeout': self.na_helper.get_value_for_int( from_zapi=True, value=client_config_info.get_child_content( 'query-timeout')), 'referral_enabled': self.na_helper.get_value_for_bool( from_zapi=True, value=client_config_info.get_child_content( 'referral-enabled')), 'schema': client_config_info.get_child_content('schema'), 'session_security': client_config_info.get_child_content('session-security'), 'use_start_tls': self.na_helper.get_value_for_bool( from_zapi=True, value=client_config_info.get_child_content( 'use-start-tls')) } return client_config_details def create_ldap_client(self): ''' Create LDAP client configuration ''' options = { 'ldap-client-config': self.parameters['name'], 'schema': self.parameters['schema'], } # Other options/attributes for attribute in self.simple_attributes: if self.parameters.get(attribute) is not None: options[str(attribute).replace('_', '-')] = str( self.parameters[attribute]) # Initialize NaElement ldap_client_create = netapp_utils.zapi.NaElement.create_node_with_children( 'ldap-client-create', **options) # LDAP servers NaElement if self.parameters.get('ldap_servers') is not None: ldap_servers_element = netapp_utils.zapi.NaElement('ldap-servers') for ldap_server_name in self.parameters['ldap_servers']: ldap_servers_element.add_new_child('string', ldap_server_name) ldap_client_create.add_child_elem(ldap_servers_element) # preferred_ad_servers if self.parameters.get('preferred_ad_servers') is not None: preferred_ad_servers_element = netapp_utils.zapi.NaElement( 'preferred-ad-servers') for pref_ad_server in self.parameters['preferred_ad_servers']: preferred_ad_servers_element.add_new_child( 'ip-address', pref_ad_server) ldap_client_create.add_child_elem(preferred_ad_servers_element) # Try to create LDAP configuration try: self.server.invoke_successfully(ldap_client_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as errcatch: self.module.fail_json( msg='Error creating LDAP client %s: %s' % (self.parameters['name'], to_native(errcatch)), exception=traceback.format_exc()) def delete_ldap_client(self): ''' Delete LDAP client configuration ''' ldap_client_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'ldap-client-delete', **{'ldap-client-config': self.parameters['name']}) try: self.server.invoke_successfully(ldap_client_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as errcatch: self.module.fail_json( msg='Error deleting LDAP client configuration %s: %s' % (self.parameters['name'], to_native(errcatch)), exception=traceback.format_exc()) def modify_ldap_client(self, modify): ''' Modify LDAP client :param modify: list of modify attributes ''' ldap_client_modify = netapp_utils.zapi.NaElement('ldap-client-modify') ldap_client_modify.add_new_child('ldap-client-config', self.parameters['name']) for attribute in modify: # LDAP_servers if attribute == 'ldap_servers': ldap_servers_element = netapp_utils.zapi.NaElement( 'ldap-servers') for ldap_server_name in self.parameters['ldap_servers']: ldap_servers_element.add_new_child('string', ldap_server_name) ldap_client_modify.add_child_elem(ldap_servers_element) # preferred_ad_servers if attribute == 'preferred_ad_servers': preferred_ad_servers_element = netapp_utils.zapi.NaElement( 'preferred-ad-servers') ldap_client_modify.add_child_elem(preferred_ad_servers_element) for pref_ad_server in self.parameters['preferred_ad_servers']: preferred_ad_servers_element.add_new_child( 'ip-address', pref_ad_server) # Simple attributes if attribute in self.simple_attributes: ldap_client_modify.add_new_child( str(attribute).replace('_', '-'), str(self.parameters[attribute])) # Try to modify LDAP client try: self.server.invoke_successfully(ldap_client_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as errcatch: self.module.fail_json( msg='Error modifying LDAP client %s: %s' % (self.parameters['name'], to_native(errcatch)), exception=traceback.format_exc()) def apply(self): '''Call create/modify/delete operations.''' current = self.get_ldap_client() cd_action = self.na_helper.get_cd_action(current, self.parameters) # state is present, either ldap_servers or ad_domain is required if self.parameters['state'] == 'present' and not self.parameters.get('ldap_servers') \ and self.parameters.get('ad_domain') is None: self.module.fail_json( msg='Required one of ldap_servers or ad_domain') if self.parameters['state'] == 'present' and cd_action is None: 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_ldap_client", self.server) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_ldap_client() elif cd_action == 'delete': self.delete_ldap_client() elif modify: self.modify_ldap_client(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapVolumeEfficiency(object): """ Creates, Modifies and Disables a Volume Efficiency """ def __init__(self): """ Initialize the ONTAP Volume Efficiency 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'), path=dict(required=True, type='str'), schedule=dict(required=False, type='str'), policy=dict(required=False, choices=['auto', 'default', 'inline-only', '-'], type='str'), enable_inline_compression=dict(required=False, type='bool'), enable_compression=dict(required=False, type='bool'), enable_inline_dedupe=dict(required=False, type='bool'), enable_data_compaction=dict(required=False, type='bool'), enable_cross_volume_inline_dedupe=dict(required=False, type='bool'), enable_cross_volume_background_dedupe=dict(required=False, type='bool') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, mutually_exclusive=[('policy', 'schedule')] ) # set up variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters['state'] == 'present': self.parameters['enabled'] = 'enabled' else: self.parameters['enabled'] = 'disabled' self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: 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_volume_efficiency(self): """ get the storage efficiency for a given path :return: dict of sis if exist, None if not """ return_value = None if self.use_rest: api = 'private/cli/volume/efficiency' query = { 'fields': 'path,volume,state,schedule,compression,inline_compression,inline_dedupe,policy,data_compaction,' 'cross_volume_inline_dedupe,cross_volume_background_dedupe', 'path': self.parameters['path'], 'vserver': self.parameters['vserver'] } message, error = self.rest_api.get(api, query) if error: self.module.fail_json(msg=error) if len(message.keys()) == 0: return None if 'records' in message and len(message['records']) == 0: return None if 'records' not in message: error = "Unexpected response in api call from %s: %s" % (api, repr(message)) self.module.fail_json(msg=error) return_value = { 'path': message['records'][0]['path'], 'enabled': message['records'][0]['state'], 'schedule': message['records'][0]['schedule'], 'enable_inline_compression': message['records'][0]['inline_compression'], 'enable_compression': message['records'][0]['compression'], 'enable_inline_dedupe': message['records'][0]['inline_dedupe'], 'enable_data_compaction': message['records'][0]['data_compaction'], 'enable_cross_volume_inline_dedupe': message['records'][0]['cross_volume_inline_dedupe'], 'enable_cross_volume_background_dedupe': message['records'][0]['cross_volume_background_dedupe'] } if 'policy' in message['records'][0]: return_value['policy'] = message['records'][0]['policy'] else: return_value['policy'] = '-' return return_value else: sis_get_iter = netapp_utils.zapi.NaElement('sis-get-iter') sis_status_info = netapp_utils.zapi.NaElement('sis-status-info') sis_status_info.add_new_child('path', self.parameters['path']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(sis_status_info) sis_get_iter.add_child_elem(query) result = self.server.invoke_successfully(sis_get_iter, True) try: if result.get_child_by_name('attributes-list'): sis_status_attributes = result['attributes-list']['sis-status-info'] return_value = { 'path': sis_status_attributes['path'], 'enabled': sis_status_attributes['state'], 'schedule': sis_status_attributes['schedule'], 'enable_inline_compression': self.na_helper.get_value_for_bool( True, sis_status_attributes.get_child_content('is-inline-compression-enabled') ), 'enable_compression': self.na_helper.get_value_for_bool(True, sis_status_attributes.get_child_content('is-compression-enabled')), 'enable_inline_dedupe': self.na_helper.get_value_for_bool(True, sis_status_attributes.get_child_content('is-inline-dedupe-enabled')), 'enable_data_compaction': self.na_helper.get_value_for_bool( True, sis_status_attributes.get_child_content('is-data-compaction-enabled') ), 'enable_cross_volume_inline_dedupe': self.na_helper.get_value_for_bool( True, sis_status_attributes.get_child_content('is-cross-volume-inline-dedupe-enabled') ), 'enable_cross_volume_background_dedupe': self.na_helper.get_value_for_bool( True, sis_status_attributes.get_child_content('is-cross-volume-background-dedupe-enabled') ) } if sis_status_attributes.get_child_by_name('policy'): return_value['policy'] = sis_status_attributes['policy'] else: return_value['policy'] = '-' except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error getting volume efficiency for path %s on vserver %s: %s' % ( self.parameters['path'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc() ) return return_value def enable_volume_efficiency(self): """ Enables Volume efficiency for a given volume by path """ if self.use_rest: api = 'private/cli/volume/efficiency/on' body = dict() query = { 'path': self.parameters['path'], 'vserver': self.parameters['vserver'] } message, error = self.rest_api.patch(api, body, query) if error: self.module.fail_json(msg=error) elif message['num_records'] == 0: error = 'Error enabling storage efficiency for path %s on vserver %s as the path provided does not exist.' % (self.parameters['path'], self.parameters['vserver']) self.module.fail_json(msg=error) else: sis_enable = netapp_utils.zapi.NaElement("sis-enable") sis_enable.add_new_child("path", self.parameters['path']) try: self.server.invoke_successfully(sis_enable, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error enabling storage efficiency for path %s on vserver %s: %s' % (self.parameters['path'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) def disable_volume_efficiency(self): """ Disables Volume efficiency for a given volume by path """ if self.use_rest: api = 'private/cli/volume/efficiency/off' body = dict() query = { 'path': self.parameters['path'], 'vserver': self.parameters['vserver'] } dummy, error = self.rest_api.patch(api, body, query) if error: self.module.fail_json(msg=error) else: sis_disable = netapp_utils.zapi.NaElement("sis-disable") sis_disable.add_new_child("path", self.parameters['path']) try: self.server.invoke_successfully(sis_disable, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error disabling storage efficiency for path %s: %s' % (self.parameters['path'], to_native(error)), exception=traceback.format_exc()) def modify_volume_efficiency(self): """ Modifies volume efficiency settings for a given volume by path """ if self.use_rest: api = 'private/cli/volume/efficiency' body = dict() query = { 'path': self.parameters['path'], 'vserver': self.parameters['vserver'] } if 'schedule' in self.parameters: body['schedule'] = self.parameters['schedule'] if 'policy' in self.parameters: body['policy'] = self.parameters['policy'] if 'enable_compression' in self.parameters: body['compression'] = self.parameters['enable_compression'] if 'enable_inline_compression' in self.parameters: body['inline_compression'] = self.parameters['enable_inline_compression'] if 'enable_inline_dedupe' in self.parameters: body['inline_dedupe'] = self.parameters['enable_inline_dedupe'] if 'enable_data_compaction' in self.parameters: body['data_compaction'] = self.parameters['enable_data_compaction'] if 'enable_cross_volume_inline_dedupe' in self.parameters: body['cross_volume_inline_dedupe'] = self.parameters['enable_cross_volume_inline_dedupe'] if 'enable_cross_volume_background_dedupe' in self.parameters: body['cross_volume_background_dedupe'] = self.parameters['enable_cross_volume_background_dedupe'] dummy, error = self.rest_api.patch(api, body, query) if error: self.module.fail_json(msg=error) else: sis_config_obj = netapp_utils.zapi.NaElement("sis-set-config") sis_config_obj.add_new_child('path', self.parameters['path']) if 'schedule' in self.parameters: sis_config_obj.add_new_child('schedule', self.parameters['schedule']) if 'policy' in self.parameters: sis_config_obj.add_new_child('policy-name', self.parameters['policy']) if 'enable_compression' in self.parameters: sis_config_obj.add_new_child('enable-compression', self.na_helper.get_value_for_bool(False, self.parameters['enable_compression'])) if 'enable_inline_compression' in self.parameters: sis_config_obj.add_new_child('enable-inline-compression', self.na_helper.get_value_for_bool( False, self.parameters['enable_inline_compression']) ) if 'enable_inline_dedupe' in self.parameters: sis_config_obj.add_new_child('enable-inline-dedupe', self.na_helper.get_value_for_bool( False, self.parameters['enable_inline_dedupe']) ) if 'enable_data_compaction' in self.parameters: sis_config_obj.add_new_child('enable-data-compaction', self.na_helper.get_value_for_bool( False, self.parameters['enable_data_compaction']) ) if 'enable_cross_volume_inline_dedupe' in self.parameters: sis_config_obj.add_new_child('enable-cross-volume-inline-dedupe', self.na_helper.get_value_for_bool( False, self.parameters['enable_cross_volume_inline_dedupe']) ) if 'enable_cross_volume_background_dedupe' in self.parameters: sis_config_obj.add_new_child('enable-cross-volume-background-dedupe', self.na_helper.get_value_for_bool( False, self.parameters['enable_cross_volume_background_dedupe']) ) try: self.server.invoke_successfully(sis_config_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying storage efficiency for path %s: %s' % (self.parameters['path'], to_native(error)), exception=traceback.format_exc()) def apply(self): if not self.use_rest: netapp_utils.ems_log_event("na_ontap_volume_efficiency", self.server) current = self.get_volume_efficiency() # If the volume efficiency does not exist for a given path to create this current is set to disabled # this is for ONTAP systems that do not enable efficiency by default. if current is None: current = {'enabled': 'disabled'} modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if not self.module.check_mode: if self.parameters['state'] == 'present' and current['enabled'] == 'disabled': self.enable_volume_efficiency() # Checking to see if there are any additional parameters that need to be set after enabling volume efficiency required for Non-AFF systems current = self.get_volume_efficiency() modify = self.na_helper.get_modified_attributes(current, self.parameters) elif self.parameters['state'] == 'absent' and current['enabled'] == 'enabled': self.disable_volume_efficiency() if 'enabled' in modify: del modify['enabled'] # Removed the enabled key if there is anything remaining in the modify dict we need to modify. if modify: self.modify_volume_efficiency() 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, type='str', 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', elements='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.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 NetAppOntapSecurityConfig(object): """ Modifies SSL Security Config """ def __init__(self): """ Initialize the ONTAP Security Config class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(name=dict(required=False, type='str', default='ssl'), is_fips_enabled=dict(required=False, type='bool'), supported_ciphers=dict(required=False, type='str'), supported_protocols=dict( required=False, type='list', elements='str', choices=['TLSv1.2', 'TLSv1.1', 'TLSv1']))) 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 'is_fips_enabled' in self.parameters and 'supported_ciphers' in self.parameters: # if fips is enabled, supported ciphers should not be specified. if self.parameters['is_fips_enabled']: self.module.fail_json( msg= 'is_fips_enabled was specified as true and supported_ciphers was specified. \ If fips is enabled then supported ciphers should not be specified' ) if 'is_fips_enabled' in self.parameters and 'supported_protocols' in self.parameters: # if fips is enabled, TLSv1 is not a supported protocol. if self.parameters[ 'is_fips_enabled'] and 'TLSv1' in self.parameters[ 'supported_protocols']: self.module.fail_json( msg= 'is_fips_enabled was specified as true and TLSv1 was specified as a supported protocol. \ If fips is enabled then TLSv1 is not a supported protocol') if 'supported_ciphers' in self.parameters: self.parameters['supported_ciphers'] = self.parameters[ 'supported_ciphers'].replace('\\', '') self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: 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_security_config(self): """ Get the current security configuration """ if self.use_rest: api = "private/cli/security/config" query = { 'fields': 'interface,is-fips-enabled,supported-protocols,supported-ciphers' } message, error = self.rest_api.get(api, query) if error: self.module.fail_json(msg=error) if not message: self.module.fail_json( msg="get_security_config expected a message") return_value = { 'name': message['records'][0]['interface'], 'is_fips_enabled': message['records'][0]['is_fips_enabled'], 'supported_ciphers': message['records'][0]['supported_ciphers'], 'supported_protocols': message['records'][0]['supported_protocols'] } else: return_value = None security_config_get_iter = netapp_utils.zapi.NaElement( 'security-config-get') security_config_info = netapp_utils.zapi.NaElement( 'desired-attributes') if 'is_fips_enabled' in self.parameters: security_config_info.add_new_child( 'is-fips-enabled', self.na_helper.get_value_for_bool( from_zapi=False, value=self.parameters['is_fips_enabled'])) if 'supported_ciphers' in self.parameters: security_config_info.add_new_child( 'supported-ciphers', self.parameters['supported_ciphers']) if 'supported_protocols' in self.parameters: security_config_info.add_new_child( 'supported-protocols', ','.join(self.parameters['supported_protocols'])) security_config_get_iter.add_child_elem(security_config_info) security_config_get_iter.add_new_child('interface', self.parameters['name']) try: result = self.server.invoke_successfully( security_config_get_iter, True) security_supported_protocols = [] if result.get_child_by_name('attributes'): attributes = result.get_child_by_name('attributes') security_config_attributes = attributes.get_child_by_name( 'security-config-info') supported_protocols = security_config_attributes.get_child_by_name( 'supported-protocols') for supported_protocol in supported_protocols.get_children( ): security_supported_protocols.append( supported_protocol.get_content()) return_value = { 'name': security_config_attributes['interface'], 'is_fips_enabled': self.na_helper.get_value_for_bool( from_zapi=True, value=security_config_attributes['is-fips-enabled'] ), 'supported_ciphers': security_config_attributes['supported-ciphers'], 'supported_protocols': security_supported_protocols, } except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error getting security config for interface %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) return return_value def modify_security_config(self): """ Modifies the security configuration. """ if self.use_rest: # url contains the value for 'name' due to the interface not being supported through body using the api. api = "private/cli/security/config?interface=%s" % ( self.parameters['name']) body = {} if 'is_fips_enabled' in self.parameters: body['is_fips_enabled'] = self.parameters['is_fips_enabled'] if 'supported_ciphers' in self.parameters: body['supported_ciphers'] = self.parameters[ 'supported_ciphers'] if 'supported_protocols' in self.parameters: body['supported_protocols'] = self.parameters[ 'supported_protocols'] dummy, error = self.rest_api.patch(api, body) if error: self.module.fail_json(msg=error) else: security_config_obj = netapp_utils.zapi.NaElement( "security-config-modify") security_config_obj.add_new_child("interface", self.parameters['name']) if 'is_fips_enabled' in self.parameters: self.parameters[ 'is_fips_enabled'] = self.na_helper.get_value_for_bool( from_zapi=False, value=self.parameters['is_fips_enabled']) security_config_obj.add_new_child( 'is-fips-enabled', self.parameters['is_fips_enabled']) if 'supported_ciphers' in self.parameters: security_config_obj.add_new_child( 'supported-ciphers', self.parameters['supported_ciphers']) if 'supported_protocols' in self.parameters: supported_protocol_obj = netapp_utils.zapi.NaElement( "supported-protocols") for protocol in self.parameters['supported_protocols']: supported_protocol_obj.add_new_child('string', protocol) security_config_obj.add_child_elem(supported_protocol_obj) try: self.server.invoke_successfully(security_config_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying security config for interface %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def ems_log_event(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_security_config", cserver) def apply(self): if not self.use_rest: self.ems_log_event() current = self.get_security_config() modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if not self.module.check_mode: self.modify_security_config() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapStorageAutoGiveback(object): """ Enable or disable storage failover for a specified node """ def __init__(self): """ Initialize the ONTAP Storage auto giveback class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(name=dict(required=True, type='str'), auto_giveback_enabled=dict(required=True, type='bool'), auto_giveback_after_panic_enabled=dict(required=False, type='bool'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) # set up variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: if not netapp_utils.has_netapp_lib(): 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_storage_auto_giveback(self): """ get the storage failover giveback options for a given node :return: dict for options """ return_value = None if self.use_rest: api = "private/cli/storage/failover" query = { 'fields': 'node,auto_giveback,auto_giveback_after_panic', 'node': self.parameters['name'], } message, error = self.rest_api.get(api, query) records, error = rrh.check_for_0_or_1_records(api, message, error) if error is None and records is not None: return_value = { 'name': message['records'][0]['node'], 'auto_giveback_enabled': message['records'][0]['auto_giveback'], 'auto_giveback_after_panic_enabled': message['records'][0]['auto_giveback_after_panic'] } if error: self.module.fail_json(msg=error) if not records: error = "REST API did not return failover options for node %s" % ( self.parameters['name']) self.module.fail_json(msg=error) else: storage_auto_giveback_get_iter = netapp_utils.zapi.NaElement( 'cf-get-iter') try: result = self.server.invoke_successfully( storage_auto_giveback_get_iter, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error getting auto giveback info for node %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('attributes-list'): attributes_list = result.get_child_by_name('attributes-list') for storage_failover_info_attributes in attributes_list.get_children( ): sfo_node_info = storage_failover_info_attributes.get_child_by_name( 'sfo-node-info') node_related_info = sfo_node_info.get_child_by_name( 'node-related-info') if node_related_info.get_child_content( 'node') == self.parameters['name']: sfo_options_info = storage_failover_info_attributes.get_child_by_name( 'sfo-options-info') options_related_info = sfo_options_info.get_child_by_name( 'options-related-info') sfo_giveback_options_info = options_related_info.get_child_by_name( 'sfo-giveback-options-info') giveback_options = sfo_giveback_options_info.get_child_by_name( 'giveback-options') return_value = { 'name': node_related_info.get_child_content('node'), 'auto_giveback_enabled': self.na_helper.get_value_for_bool( True, options_related_info.get_child_content( 'auto-giveback-enabled')), 'auto_giveback_after_panic_enabled': self.na_helper.get_value_for_bool( True, giveback_options.get_child_content( 'auto-giveback-after-panic-enabled')), } break return return_value def modify_storage_auto_giveback(self): """ Modifies storage failover giveback options for a specified node """ if self.use_rest: api = "private/cli/storage/failover" body = dict() query = {'node': self.parameters['name']} body['auto_giveback'] = self.parameters['auto_giveback_enabled'] if 'auto_giveback_after_panic_enabled' in self.parameters: body['auto_giveback_after_panic'] = self.parameters[ 'auto_giveback_after_panic_enabled'] dummy, error = self.rest_api.patch(api, body, query) if error: self.module.fail_json(msg=error) else: storage_auto_giveback_enable = netapp_utils.zapi.NaElement( 'cf-modify-iter') attributes_info = netapp_utils.zapi.NaElement( 'options-related-info-modify') query_info = netapp_utils.zapi.NaElement( 'options-related-info-modify') attributes_info.add_new_child('node', self.parameters['name']) attributes_info.add_new_child( 'auto-giveback-enabled', self.na_helper.get_value_for_bool( from_zapi=False, value=self.parameters['auto_giveback_enabled'])) if 'auto_giveback_after_panic_enabled' in self.parameters: sfo_give_back_options_info_modify = netapp_utils.zapi.NaElement( 'sfo-giveback-options-info-modify') give_back_options_modify = netapp_utils.zapi.NaElement( 'giveback-options-modify') give_back_options_modify.add_new_child( 'auto-giveback-after-panic-enabled', self.na_helper.get_value_for_bool( from_zapi=False, value=self. parameters['auto_giveback_after_panic_enabled'])) sfo_give_back_options_info_modify.add_child_elem( give_back_options_modify) attributes_info.add_child_elem( sfo_give_back_options_info_modify) query = netapp_utils.zapi.NaElement('query') attributes = netapp_utils.zapi.NaElement("attributes") query.add_child_elem(query_info) attributes.add_child_elem(attributes_info) storage_auto_giveback_enable.add_child_elem(query) storage_auto_giveback_enable.add_child_elem(attributes) try: result = self.server.invoke_successfully( storage_auto_giveback_enable, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying auto giveback for node %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def ems_log_event(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) return netapp_utils.ems_log_event("na_ontap_storage_auto_giveback", cserver) def apply(self): if not self.use_rest: self.ems_log_event() current = self.get_storage_auto_giveback() modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if not self.module.check_mode: self.modify_storage_auto_giveback() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSnapMirrorPolicy(object): """ Create, Modifies and Destroys a SnapMirror policy """ def __init__(self): """ Initialize the Ontap SnapMirror policy class """ self.use_rest = False 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'), policy_name=dict(required=True, type='str'), comment=dict(required=False, type='str'), policy_type=dict(required=False, type='str', choices=[ 'vault', 'async_mirror', 'mirror_vault', 'strict_sync_mirror', 'sync_mirror' ]), tries=dict(required=False, type='str'), transfer_priority=dict(required=False, type='str', choices=['low', 'normal']), common_snapshot_schedule=dict(required=False, type='str'), ignore_atime=dict(required=False, type='bool'), is_network_compression_enabled=dict(required=False, type='bool'), owner=dict(required=False, type='str', choices=['cluster_admin', 'vserver_admin']), restart=dict(required=False, type='str', choices=['always', 'never', 'default']), snapmirror_label=dict(required=False, type="list", elements="str"), keep=dict(required=False, type="list", elements="int"), prefix=dict(required=False, type="list", elements="str"), schedule=dict(required=False, type="list", elements="str"), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) # set up variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # API should be used for ONTAP 9.6 or higher, Zapi for lower version self.restApi = OntapRestAPI(self.module) # some attributes are not supported in earlier REST implementation unsupported_rest_properties = [ 'owner', 'restart', 'transfer_priority', 'tries', 'ignore_atime', 'common_snapshot_schedule' ] used_unsupported_rest_properties = [ x for x in unsupported_rest_properties if x in self.parameters ] self.use_rest, error = self.restApi.is_rest( used_unsupported_rest_properties) if error: self.module.fail_json(msg=error) if not self.use_rest: 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_snapmirror_policy(self): if self.use_rest: data = { 'fields': 'uuid,name,svm.name,comment,network_compression_enabled,type,retention', 'name': self.parameters['policy_name'], 'svm.name': self.parameters['vserver'] } api = "snapmirror/policies" message, error = self.restApi.get(api, data) if error: self.module.fail_json(msg=error) if len(message['records']) != 0: return_value = { 'uuid': message['records'][0]['uuid'], 'vserver': message['records'][0]['svm']['name'], 'policy_name': message['records'][0]['name'], 'comment': '', 'is_network_compression_enabled': message['records'][0]['network_compression_enabled'], 'snapmirror_label': list(), 'keep': list(), 'prefix': list(), 'schedule': list() } if 'type' in message['records'][0]: policy_type = message['records'][0]['type'] if policy_type == 'async': policy_type = 'async_mirror' elif policy_type == 'sync': policy_type = 'sync_mirror' return_value['policy_type'] = policy_type if 'comment' in message['records'][0]: return_value['comment'] = message['records'][0]['comment'] if 'retention' in message['records'][0]: for rule in message['records'][0]['retention']: return_value['snapmirror_label'].append(rule['label']) return_value['keep'].append(int(rule['count'])) if rule['prefix'] == '-': return_value['prefix'].append('') else: return_value['prefix'].append(rule['prefix']) if rule['creation_schedule']['name'] == '-': return_value['schedule'].append('') else: return_value['schedule'].append( rule['creation_schedule']['name']) return return_value return None else: return_value = None snapmirror_policy_get_iter = netapp_utils.zapi.NaElement( 'snapmirror-policy-get-iter') snapmirror_policy_info = netapp_utils.zapi.NaElement( 'snapmirror-policy-info') snapmirror_policy_info.add_new_child( 'policy-name', self.parameters['policy_name']) snapmirror_policy_info.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(snapmirror_policy_info) snapmirror_policy_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully( snapmirror_policy_get_iter, True) if result.get_child_by_name('attributes-list'): snapmirror_policy_attributes = result['attributes-list'][ 'snapmirror-policy-info'] return_value = { 'policy_name': snapmirror_policy_attributes['policy-name'], 'tries': snapmirror_policy_attributes['tries'], 'transfer_priority': snapmirror_policy_attributes['transfer-priority'], 'is_network_compression_enabled': self.na_helper.get_value_for_bool( True, snapmirror_policy_attributes[ 'is-network-compression-enabled']), 'restart': snapmirror_policy_attributes['restart'], 'ignore_atime': self.na_helper.get_value_for_bool( True, snapmirror_policy_attributes['ignore-atime']), 'vserver': snapmirror_policy_attributes['vserver-name'], 'comment': '', 'snapmirror_label': list(), 'keep': list(), 'prefix': list(), 'schedule': list() } if snapmirror_policy_attributes.get_child_content( 'comment') is not None: return_value['comment'] = snapmirror_policy_attributes[ 'comment'] if snapmirror_policy_attributes.get_child_content( 'type') is not None: return_value[ 'policy_type'] = snapmirror_policy_attributes[ 'type'] if snapmirror_policy_attributes.get_child_by_name( 'snapmirror-policy-rules'): for rule in snapmirror_policy_attributes[ 'snapmirror-policy-rules'].get_children(): # Ignore builtin rules if rule.get_child_content('snapmirror-label') == "sm_created" or \ rule.get_child_content('snapmirror-label') == "all_source_snapshots": continue return_value['snapmirror_label'].append( rule.get_child_content('snapmirror-label')) return_value['keep'].append( int(rule.get_child_content('keep'))) prefix = rule.get_child_content('prefix') if prefix is None or prefix == '-': prefix = '' return_value['prefix'].append(prefix) schedule = rule.get_child_content('schedule') if schedule is None or schedule == '-': schedule = '' return_value['schedule'].append(schedule) except netapp_utils.zapi.NaApiError as error: if 'NetApp API failed. Reason - 13001:' in to_native(error): # Policy does not exist pass else: self.module.fail_json( msg='Error getting snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) return return_value def validate_parameters(self): """ Validate snapmirror policy rules :return: None """ # For snapmirror policy rules, 'snapmirror_label' is required. if 'snapmirror_label' in self.parameters: # Check size of 'snapmirror_label' list is 0-10. Can have zero rules. # Take builtin 'sm_created' rule into account for 'mirror_vault'. if (('policy_type' in self.parameters and self.parameters['policy_type'] == 'mirror_vault' and len(self.parameters['snapmirror_label']) > 9) or len(self.parameters['snapmirror_label']) > 10): self.module.fail_json( msg="Error: A SnapMirror Policy can have up to a maximum of " "10 rules (including builtin rules), with a 'keep' value " "representing the maximum number of Snapshot copies for each rule" ) # 'keep' must be supplied as long as there is at least one snapmirror_label if len(self.parameters['snapmirror_label'] ) > 0 and 'keep' not in self.parameters: self.module.fail_json( msg="Error: Missing 'keep' parameter. When specifying the " "'snapmirror_label' parameter, the 'keep' parameter must " "also be supplied") # Make sure other rule values match same number of 'snapmirror_label' values. for rule_parameter in ['keep', 'prefix', 'schedule']: if rule_parameter in self.parameters: if len(self.parameters['snapmirror_label']) > len( self.parameters[rule_parameter]): self.module.fail_json( msg="Error: Each 'snapmirror_label' value must have " "an accompanying '%s' value" % rule_parameter) if len(self.parameters[rule_parameter]) > len( self.parameters['snapmirror_label']): self.module.fail_json( msg= "Error: Each '%s' value must have an accompanying " "'snapmirror_label' value" % rule_parameter) else: # 'snapmirror_label' not supplied. # Bail out if other rule parameters have been supplied. for rule_parameter in ['keep', 'prefix', 'schedule']: if rule_parameter in self.parameters: self.module.fail_json( msg="Error: Missing 'snapmirror_label' parameter. When " "specifying the '%s' parameter, the 'snapmirror_label' " "parameter must also be supplied" % rule_parameter) # Schedule must be supplied if prefix is supplied. if 'prefix' in self.parameters and 'schedule' not in self.parameters: self.module.fail_json( msg="Error: Missing 'schedule' parameter. When " "specifying the 'prefix' parameter, the 'schedule' " "parameter must also be supplied") def create_snapmirror_policy(self): """ Creates a new storage efficiency policy """ self.validate_parameters() if self.use_rest: data = { 'name': self.parameters['policy_name'], 'svm': { 'name': self.parameters['vserver'] } } if 'policy_type' in self.parameters.keys(): if 'async_mirror' in self.parameters['policy_type']: data['type'] = 'async' elif 'sync_mirror' in self.parameters['policy_type']: data['type'] = 'sync' data['sync_type'] = 'sync' else: self.module.fail_json( msg= 'policy type in REST only supports options async_mirror or sync_mirror, given %s' % (self.parameters['policy_type'])) data = self.create_snapmirror_policy_obj_for_rest( data, data['type']) else: data = self.create_snapmirror_policy_obj_for_rest(data) api = "snapmirror/policies" response, error = self.restApi.post(api, data) if error: self.module.fail_json(msg=error) if 'job' in response: self.restApi.wait_on_job(response['job'], increment=5) else: snapmirror_policy_obj = netapp_utils.zapi.NaElement( "snapmirror-policy-create") snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) if 'policy_type' in self.parameters.keys(): snapmirror_policy_obj.add_new_child( "type", self.parameters['policy_type']) snapmirror_policy_obj = self.create_snapmirror_policy_obj( snapmirror_policy_obj) try: self.server.invoke_successfully(snapmirror_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def create_snapmirror_policy_obj(self, snapmirror_policy_obj): if 'comment' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("comment", self.parameters['comment']) if 'common_snapshot_schedule' in self.parameters.keys( ) and 'sync_mirror' in self.parameters['policy_type']: snapmirror_policy_obj.add_new_child( "common-snapshot-schedule", self.parameters['common_snapshot_schedule']) if 'ignore_atime' in self.parameters.keys(): snapmirror_policy_obj.add_new_child( "ignore-atime", self.na_helper.get_value_for_bool( False, self.parameters['ignore_atime'])) if 'is_network_compression_enabled' in self.parameters.keys(): snapmirror_policy_obj.add_new_child( "is-network-compression-enabled", self.na_helper.get_value_for_bool( False, self.parameters['is_network_compression_enabled'])) if 'owner' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("owner", self.parameters['owner']) if 'restart' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("restart", self.parameters['restart']) if 'transfer_priority' in self.parameters.keys(): snapmirror_policy_obj.add_new_child( "transfer-priority", self.parameters['transfer_priority']) if 'tries' in self.parameters.keys(): snapmirror_policy_obj.add_new_child("tries", self.parameters['tries']) return snapmirror_policy_obj def create_snapmirror_policy_obj_for_rest(self, snapmirror_policy_obj, policy_type=None): if 'comment' in self.parameters.keys(): snapmirror_policy_obj["comment"] = self.parameters['comment'] if 'is_network_compression_enabled' in self.parameters: if policy_type == 'async': snapmirror_policy_obj[ "network_compression_enabled"] = self.parameters[ 'is_network_compression_enabled'] elif policy_type == 'sync': self.module.fail_json( msg= "Input parameter network_compression_enabled is not valid for SnapMirror policy type sync" ) return snapmirror_policy_obj def create_snapmirror_policy_retention_obj_for_rest(self, rules=None): """ Create SnapMirror policy retention REST object. :param list rules: e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': 'daily', 'schedule': 'daily'}, ... ] :return: List of retention REST objects. e.g. [{'label': 'daily', 'count': 7, 'prefix': 'daily', 'creation_schedule': {'name': 'daily'}}, ... ] """ snapmirror_policy_retention_objs = list() if rules is not None: for rule in rules: retention = { 'label': rule['snapmirror_label'], 'count': str(rule['keep']) } if 'prefix' in rule and rule['prefix'] != '': retention['prefix'] = rule['prefix'] if 'schedule' in rule and rule['schedule'] != '': retention['creation_schedule'] = {'name': rule['schedule']} snapmirror_policy_retention_objs.append(retention) return snapmirror_policy_retention_objs def delete_snapmirror_policy(self, uuid=None): """ Deletes a snapmirror policy """ if self.use_rest: api = "snapmirror/policies" data = {'uuid': uuid} dummy, error = self.restApi.delete(api, data) if error: self.module.fail_json(msg=error) else: snapmirror_policy_obj = netapp_utils.zapi.NaElement( "snapmirror-policy-delete") snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) try: self.server.invoke_successfully(snapmirror_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def modify_snapmirror_policy(self, uuid=None, policy_type=None): """ Modifies a snapmirror policy """ if self.use_rest: api = "snapmirror/policies/" + uuid data = self.create_snapmirror_policy_obj_for_rest( dict(), policy_type) dummy, error = self.restApi.patch(api, data) if error: self.module.fail_json(msg=error) else: snapmirror_policy_obj = netapp_utils.zapi.NaElement( "snapmirror-policy-modify") snapmirror_policy_obj = self.create_snapmirror_policy_obj( snapmirror_policy_obj) # Only modify snapmirror policy if a specific snapmirror policy attribute needs # modifying. It may be that only snapmirror policy rules are being modified. if snapmirror_policy_obj.get_children(): snapmirror_policy_obj.add_new_child( "policy-name", self.parameters['policy_name']) try: self.server.invoke_successfully(snapmirror_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def identify_new_snapmirror_policy_rules(self, current=None): """ Identify new rules that should be added. :return: List of new rules to be added e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ] """ new_rules = list() if 'snapmirror_label' in self.parameters: for snapmirror_label in self.parameters['snapmirror_label']: snapmirror_label = snapmirror_label.strip() # Construct new rule. prefix and schedule are optional. snapmirror_label_index = self.parameters[ 'snapmirror_label'].index(snapmirror_label) rule = dict({ 'snapmirror_label': snapmirror_label, 'keep': self.parameters['keep'][snapmirror_label_index] }) if 'prefix' in self.parameters: rule['prefix'] = self.parameters['prefix'][ snapmirror_label_index] else: rule['prefix'] = '' if 'schedule' in self.parameters: rule['schedule'] = self.parameters['schedule'][ snapmirror_label_index] else: rule['schedule'] = '' if current is not None and 'snapmirror_label' in current: if snapmirror_label not in current['snapmirror_label']: # Rule doesn't exist. Add new rule. new_rules.append(rule) else: # No current or any rules. Add new rule. new_rules.append(rule) return new_rules def identify_obsolete_snapmirror_policy_rules(self, current=None): """ Identify existing rules that should be deleted :return: List of rules to be deleted e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ] """ obsolete_rules = list() if 'snapmirror_label' in self.parameters: if current is not None and 'snapmirror_label' in current: # Iterate existing rules. for snapmirror_label in current['snapmirror_label']: snapmirror_label = snapmirror_label.strip() if snapmirror_label not in [ item.strip() for item in self.parameters['snapmirror_label'] ]: # Existing rule isn't in parameters. Delete existing rule. current_snapmirror_label_index = current[ 'snapmirror_label'].index(snapmirror_label) rule = dict({ 'snapmirror_label': snapmirror_label, 'keep': current['keep'][current_snapmirror_label_index], 'prefix': current['prefix'][current_snapmirror_label_index], 'schedule': current['schedule'][current_snapmirror_label_index] }) obsolete_rules.append(rule) return obsolete_rules def identify_modified_snapmirror_policy_rules(self, current=None): """ Identify self.parameters rules that will be modified or not. :return: List of 'modified' rules and a list of 'unmodified' rules e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ] """ modified_rules = list() unmodified_rules = list() if 'snapmirror_label' in self.parameters: for snapmirror_label in self.parameters['snapmirror_label']: snapmirror_label = snapmirror_label.strip() if current is not None and 'snapmirror_label' in current: if snapmirror_label in current['snapmirror_label']: # Rule exists. Identify whether it requires modification or not. modified = False rule = dict() rule['snapmirror_label'] = snapmirror_label # Get indexes of current and supplied rule. current_snapmirror_label_index = current[ 'snapmirror_label'].index(snapmirror_label) snapmirror_label_index = self.parameters[ 'snapmirror_label'].index(snapmirror_label) # Check if keep modified if self.parameters['keep'][ snapmirror_label_index] != current['keep'][ current_snapmirror_label_index]: modified = True rule['keep'] = self.parameters['keep'][ snapmirror_label_index] else: rule['keep'] = current['keep'][ current_snapmirror_label_index] # Check if prefix modified if 'prefix' in self.parameters: if self.parameters['prefix'][ snapmirror_label_index] != current[ 'prefix'][ current_snapmirror_label_index]: modified = True rule['prefix'] = self.parameters['prefix'][ snapmirror_label_index] else: rule['prefix'] = current['prefix'][ current_snapmirror_label_index] else: rule['prefix'] = current['prefix'][ current_snapmirror_label_index] # Check if schedule modified if 'schedule' in self.parameters: if self.parameters['schedule'][ snapmirror_label_index] != current[ 'schedule'][ current_snapmirror_label_index]: modified = True rule['schedule'] = self.parameters['schedule'][ snapmirror_label_index] else: rule['schedule'] = current['schedule'][ current_snapmirror_label_index] else: rule['schedule'] = current['schedule'][ current_snapmirror_label_index] if modified: modified_rules.append(rule) else: unmodified_rules.append(rule) return modified_rules, unmodified_rules def identify_snapmirror_policy_rules_with_schedule(self, rules=None): """ Identify rules that are using a schedule or not. At least one non-schedule rule must be added to a policy before schedule rules are added. :return: List of rules with schedules and a list of rules without schedules e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': 'daily', 'schedule': 'daily'}, ... ], [{'snapmirror_label': 'weekly', 'keep': 5, 'prefix': '', 'schedule': ''}, ... ] """ schedule_rules = list() non_schedule_rules = list() if rules is not None: for rule in rules: if 'schedule' in rule: schedule_rules.append(rule) else: non_schedule_rules.append(rule) return schedule_rules, non_schedule_rules def modify_snapmirror_policy_rules(self, current=None, uuid=None): """ Modify existing rules in snapmirror policy :return: None """ self.validate_parameters() # Need 'snapmirror_label' to add/modify/delete rules if 'snapmirror_label' not in self.parameters: return obsolete_rules = self.identify_obsolete_snapmirror_policy_rules( current) new_rules = self.identify_new_snapmirror_policy_rules(current) modified_rules, unmodified_rules = self.identify_modified_snapmirror_policy_rules( current) if self.use_rest: api = "snapmirror/policies/" + uuid data = {'retention': list()} # As rule 'prefix' can't be unset, have to delete existing rules first. # Builtin rules remain. dummy, error = self.restApi.patch(api, data) if error: self.module.fail_json(msg=error) # Re-add desired rules. rules = unmodified_rules + modified_rules + new_rules data[ 'retention'] = self.create_snapmirror_policy_retention_obj_for_rest( rules) if len(data['retention']) > 0: dummy, error = self.restApi.patch(api, data) if error: self.module.fail_json(msg=error) else: delete_rules = obsolete_rules + modified_rules add_schedule_rules, add_non_schedule_rules = self.identify_snapmirror_policy_rules_with_schedule( new_rules + modified_rules) # Delete rules no longer required or modified rules that will be re-added. for rule in delete_rules: options = { 'policy-name': self.parameters['policy_name'], 'snapmirror-label': rule['snapmirror_label'] } self.modify_snapmirror_policy_rule( options, 'snapmirror-policy-remove-rule') # Add rules. At least one non-schedule rule must exist before # a rule with a schedule can be added, otherwise zapi will complain. for rule in add_non_schedule_rules + add_schedule_rules: options = { 'policy-name': self.parameters['policy_name'], 'snapmirror-label': rule['snapmirror_label'], 'keep': str(rule['keep']) } if 'prefix' in rule and rule['prefix'] != '': options['prefix'] = rule['prefix'] if 'schedule' in rule and rule['schedule'] != '': options['schedule'] = rule['schedule'] self.modify_snapmirror_policy_rule( options, 'snapmirror-policy-add-rule') def modify_snapmirror_policy_rule(self, options, zapi): """ Add, modify or remove a rule to/from a snapmirror policy """ snapmirror_obj = netapp_utils.zapi.NaElement.create_node_with_children( zapi, **options) try: self.server.invoke_successfully(snapmirror_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying snapmirror policy rule %s: %s' % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) def asup_log_for_cserver(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_snapmirror_policy", cserver) def apply(self): uuid = None if not self.use_rest: self.asup_log_for_cserver() current, modify = self.get_snapmirror_policy(), None cd_action = self.na_helper.get_cd_action(current, self.parameters) if current and 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_snapmirror_policy() if self.use_rest: current = self.get_snapmirror_policy() uuid = current['uuid'] self.modify_snapmirror_policy_rules(current, uuid) else: self.modify_snapmirror_policy_rules(current) elif cd_action == 'delete': if self.use_rest: uuid = current['uuid'] self.delete_snapmirror_policy(uuid) elif modify: if self.use_rest: uuid = current['uuid'] self.modify_snapmirror_policy(uuid, current['policy_type']) self.modify_snapmirror_policy_rules(current, uuid) else: self.modify_snapmirror_policy() self.modify_snapmirror_policy_rules(current) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPQuotas(object): '''Class with quotas 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'), vserver=dict(required=True, type='str'), volume=dict(required=True, type='str'), quota_target=dict(required=False, type='str'), qtree=dict(required=False, type='str', default=""), type=dict(required=False, type='str', choices=['user', 'group', 'tree']), policy=dict(required=False, type='str'), set_quota_status=dict(required=False, type='bool'), perform_user_mapping=dict(required=False, type='bool'), file_limit=dict(required=False, type='str'), disk_limit=dict(required=False, type='str'), soft_file_limit=dict(required=False, type='str'), soft_disk_limit=dict(required=False, type='str'), threshold=dict(required=False, type='str'), activate_quota_on_change=dict(required=False, type='str', choices=['resize', 'reinitialize', 'none'], default='resize') )) self.module = AnsibleModule( argument_spec=self.argument_spec, required_by={ 'policy': ['quota_target', 'type'], 'perform_user_mapping': ['quota_target', 'type'], 'file_limit': ['quota_target', 'type'], 'disk_limit': ['quota_target', 'type'], 'soft_file_limit': ['quota_target', 'type'], 'soft_disk_limit': ['quota_target', 'type'], 'threshold': ['quota_target', 'type'], }, required_together=[['quota_target', 'type']], supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # converted blank parameter to * as shown in vsim if self.parameters.get('quota_target') == "": self.parameters['quota_target'] = '*' 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_quota_status(self): """ Return details about the quota status :param: name : volume name :return: status of the quota. None if not found. :rtype: dict """ quota_status_get = netapp_utils.zapi.NaElement('quota-status') quota_status_get.translate_struct({ 'volume': self.parameters['volume'] }) try: result = self.server.invoke_successfully(quota_status_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching quotas status info: %s' % to_native(error), exception=traceback.format_exc()) if result: return result['status'] return None def get_quotas(self): """ Get quota details :return: name of volume if quota exists, None otherwise """ if self.parameters.get('type') is None: return None quota_get = netapp_utils.zapi.NaElement('quota-list-entries-iter') query = { 'query': { 'quota-entry': { 'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'vserver': self.parameters['vserver'] } } } quota_get.translate_struct(query) if self.parameters.get('policy'): quota_get['query']['quota-entry'].add_new_child('policy', self.parameters['policy']) try: result = self.server.invoke_successfully(quota_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching quotas 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: # if quota-target is '*', the query treats it as a wildcard. But a blank entry is represented as '*'. # Hence the need to loop through all records to find a match. for quota_entry in result.get_child_by_name('attributes-list').get_children(): quota_target = quota_entry.get_child_content('quota-target') if quota_target == self.parameters['quota_target']: return_values = {'volume': quota_entry.get_child_content('volume'), 'file_limit': quota_entry.get_child_content('file-limit'), 'disk_limit': quota_entry.get_child_content('disk-limit'), 'soft_file_limit': quota_entry.get_child_content('soft-file-limit'), 'soft_disk_limit': quota_entry.get_child_content('soft-disk-limit'), 'threshold': quota_entry.get_child_content('threshold')} value = self.na_helper.safe_get(quota_entry, ['perform-user-mapping']) if value is not None: return_values['perform_user_mapping'] = self.na_helper.get_value_for_bool(True, value) return return_values return None def quota_entry_set(self): """ Adds a quota entry """ options = {'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree']} if self.parameters.get('file_limit'): options['file-limit'] = self.parameters['file_limit'] if self.parameters.get('disk_limit'): options['disk-limit'] = self.parameters['disk_limit'] if self.parameters.get('perform_user_mapping') is not None: options['perform-user-mapping'] = str(self.parameters['perform_user_mapping']) if self.parameters.get('soft_file_limit'): options['soft-file-limit'] = self.parameters['soft_file_limit'] if self.parameters.get('soft_disk_limit'): options['soft-disk-limit'] = self.parameters['soft_disk_limit'] if self.parameters.get('threshold'): options['threshold'] = self.parameters['threshold'] if self.parameters.get('policy'): options['policy'] = self.parameters['policy'] set_entry = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-set-entry', **options) try: self.server.invoke_successfully(set_entry, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error adding/modifying quota entry %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def quota_entry_delete(self): """ Deletes a quota entry """ options = {'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree']} set_entry = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-delete-entry', **options) if self.parameters.get('policy'): set_entry.add_new_child('policy', self.parameters['policy']) try: self.server.invoke_successfully(set_entry, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting quota entry %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def quota_entry_modify(self, modify_attrs): """ Modifies a quota entry """ options = {'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree']} options.update(modify_attrs) if self.parameters.get('file_limit'): options['file-limit'] = self.parameters['file_limit'] if self.parameters.get('disk_limit'): options['disk-limit'] = self.parameters['disk_limit'] if self.parameters.get('perform_user_mapping') is not None: options['perform-user-mapping'] = str(self.parameters['perform_user_mapping']) if self.parameters.get('soft_file_limit'): options['soft-file-limit'] = self.parameters['soft_file_limit'] if self.parameters.get('soft_disk_limit'): options['soft-disk-limit'] = self.parameters['soft_disk_limit'] if self.parameters.get('threshold'): options['threshold'] = self.parameters['threshold'] if self.parameters.get('policy'): options['policy'] = str(self.parameters['policy']) modify_entry = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-modify-entry', **options) try: self.server.invoke_successfully(modify_entry, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying quota entry %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def on_or_off_quota(self, status): """ on or off quota """ quota = netapp_utils.zapi.NaElement.create_node_with_children( status, **{'volume': self.parameters['volume']}) try: self.server.invoke_successfully(quota, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error setting %s for %s: %s' % (status, self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def resize_quota(self): """ resize quota """ quota = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-resize', **{'volume': self.parameters['volume']}) try: self.server.invoke_successfully(quota, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error setting %s for %s: %s' % ('quota-resize', self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to quotas """ netapp_utils.ems_log_event("na_ontap_quotas", self.server) cd_action = None modify_quota_status = None modify_quota = None quota_status = None current = self.get_quotas() if self.parameters.get('type') is not None: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None: modify_quota = self.na_helper.get_modified_attributes(current, self.parameters) if 'set_quota_status' in self.parameters or modify_quota: quota_status = self.get_quota_status() if 'set_quota_status' in self.parameters and quota_status is not None: quota_status_action = self.na_helper.get_modified_attributes( {'set_quota_status': True if quota_status == 'on' else False}, self.parameters) if quota_status_action: modify_quota_status = 'quota-on' if quota_status_action['set_quota_status'] else 'quota-off' if modify_quota is not None and modify_quota_status is None and quota_status == 'on': # do we need to resize or reinitialize: if self.parameters['activate_quota_on_change'] in ['resize', 'reinitialize']: modify_quota_status = self.parameters['activate_quota_on_change'] if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.quota_entry_set() elif cd_action == 'delete': self.quota_entry_delete() elif modify_quota is not None: for key in list(modify_quota): modify_quota[key.replace("_", "-")] = modify_quota.pop(key) self.quota_entry_modify(modify_quota) if modify_quota_status in ['quota-off', 'quota-on']: self.on_or_off_quota(modify_quota_status) elif modify_quota_status == 'resize': self.resize_quota() elif modify_quota_status == 'reinitialize': self.on_or_off_quota('quota-off') time.sleep(10) # status switch interval self.on_or_off_quota('quota-on') self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapLUN(object): ''' create, modify, delete LUN ''' 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'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), size=dict(type='int'), size_unit=dict(default='gb', choices=[ 'bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb' ], type='str'), force_resize=dict(default=False, type='bool'), force_remove=dict(default=False, type='bool'), force_remove_fenced=dict(default=False, type='bool'), flexvol_name=dict(type='str'), vserver=dict(required=True, type='str'), os_type=dict(required=False, type='str', aliases=['ostype']), qos_policy_group=dict(required=False, type='str'), space_reserve=dict(required=False, type='bool', default=True), space_allocation=dict(required=False, type='bool', default=False), use_exact_size=dict(required=False, type='bool', default=True), san_application_template=dict( type='dict', options=dict( use_san_application=dict(type='bool', default=True), name=dict(required=True, type='str'), igroup_name=dict(type='str'), lun_count=dict(type='int'), protection_type=dict( type='dict', options=dict(local_policy=dict(type='str'), )), storage_service=dict( type='str', choices=['value', 'performance', 'extreme']), tiering=dict( type='dict', options=dict( control=dict(type='str', choices=[ 'required', 'best_effort', 'disallowed' ]), policy=dict(type='str', choices=[ 'all', 'auto', 'none', 'snapshot-only' ]), object_stores=dict( type='list', elements='str') # create only )), )))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) # set up state variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get('size') is not None: self.parameters['size'] *= netapp_utils.POW2_BYTE_MAP[ self.parameters['size_unit']] 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']) # REST API for application/applications if needed self.rest_api, self.rest_app = self.setup_rest_application() def setup_rest_application(self): use_application_template = self.na_helper.safe_get( self.parameters, ['san_application_template', 'use_san_application']) rest_api, rest_app = None, None if use_application_template: if self.parameters.get('flexvol_name') is not None: self.module.fail_json( msg= "'flexvol_name' option is not supported when san_application_template is present" ) rest_api = netapp_utils.OntapRestAPI(self.module) name = self.na_helper.safe_get( self.parameters, ['san_application_template', 'name'], allow_sparse_dict=False) rest_app = RestApplication(rest_api, self.parameters['vserver'], name) elif self.parameters.get('flexvol_name') is None: self.module.fail_json( msg= "flexvol_name option is required when san_application_template is not present" ) return rest_api, rest_app def get_luns(self, lun_path=None): """ Return list of LUNs matching vserver and volume names. :return: list of LUNs in XML format. :rtype: list """ luns = [] tag = None if lun_path is None and self.parameters.get('flexvol_name') is None: return luns query_details = netapp_utils.zapi.NaElement('lun-info') query_details.add_new_child('vserver', self.parameters['vserver']) if lun_path is not None: query_details.add_new_child('lun_path', lun_path) else: query_details.add_new_child('volume', self.parameters['flexvol_name']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) while True: lun_info = netapp_utils.zapi.NaElement('lun-get-iter') lun_info.add_child_elem(query) if tag: lun_info.add_new_child('tag', tag, True) result = self.server.invoke_successfully(lun_info, True) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: attr_list = result.get_child_by_name('attributes-list') luns.extend(attr_list.get_children()) tag = result.get_child_content('next-tag') if tag is None: break return luns def get_lun_details(self, lun): """ Extract LUN details, from XML to python dict :return: Details about the lun :rtype: dict """ return_value = dict() return_value['size'] = int(lun.get_child_content('size')) bool_attr_map = { 'is-space-alloc-enabled': 'space_allocation', 'is-space-reservation-enabled': 'space_reserve' } for attr in bool_attr_map: value = lun.get_child_content(attr) if value is not None: return_value[ bool_attr_map[attr]] = self.na_helper.get_value_for_bool( True, value) str_attr_map = { 'name': 'name', 'path': 'path', 'qos-policy-group': 'qos_policy_group', 'multiprotocol-type': 'os_type' } for attr in str_attr_map: value = lun.get_child_content(attr) if value is not None: return_value[str_attr_map[attr]] = value # Find out if the lun is attached attached_to = None lun_id = None if lun.get_child_content('mapped') == 'true': lun_map_list = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-map-list-info', **{'path': lun.get_child_content('path')}) result = self.server.invoke_successfully(lun_map_list, enable_tunneling=True) igroups = result.get_child_by_name('initiator-groups') if igroups: for igroup_info in igroups.get_children(): igroup = igroup_info.get_child_content( 'initiator-group-name') attached_to = igroup lun_id = igroup_info.get_child_content('lun-id') return_value.update({'attached_to': attached_to, 'lun_id': lun_id}) return return_value def find_lun(self, luns, name, lun_path=None): """ Return lun record matching name or path :return: lun record :rtype: XML or None if not found """ for lun in luns: path = lun.get_child_content('path') if lun_path is not None: if lun_path == path: return lun else: if name == path: return lun _rest, _splitter, found_name = path.rpartition('/') if found_name == name: return lun return None def get_lun(self, name, lun_path=None): """ Return details about the LUN :return: Details about the lun :rtype: dict """ luns = self.get_luns(lun_path) lun = self.find_lun(luns, name, lun_path) if lun is not None: return self.get_lun_details(lun) return None def get_luns_from_app(self): app_details, error = self.rest_app.get_application_details() self.fail_on_error(error) if app_details is not None: app_details['paths'] = self.get_lun_paths_from_app() return app_details def get_lun_paths_from_app(self): """Get luns path for SAN application""" backing_storage, error = self.rest_app.get_application_component_backing_storage( ) self.fail_on_error(error) # {'luns': [{'path': '/vol/ansibleLUN/ansibleLUN_1', ... if backing_storage is not None: return [lun['path'] for lun in backing_storage.get('luns', [])] return None def get_lun_path_from_backend(self, name): """returns lun path matching name if found in backing_storage retruns None if not found """ lun_paths = self.get_lun_paths_from_app() match = "/%s" % name for path in lun_paths: if path.endswith(match): return path return None def create_san_app_component(self): '''Create SAN application component''' required_options = ('name', 'size') for option in required_options: if self.parameters.get(option) is None: self.module.fail_json( msg='Error: "%s" is required to create san application.' % option) application_component = dict( name=self.parameters['name'], total_size=self.parameters['size'], lun_count=1 # default value, may be overriden below ) for attr in ('igroup_name', 'lun_count', 'storage_service'): value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr]) if value is not None: application_component[attr] = value for attr in ('os_type', 'qos_policy_group'): value = self.na_helper.safe_get(self.parameters, [attr]) if value is not None: if attr == 'qos_policy_group': attr = 'qos' value = dict(policy=dict(name=value)) application_component[attr] = value tiering = self.na_helper.safe_get( self.parameters, ['nas_application_template', 'tiering']) if tiering is not None: application_component['tiering'] = dict() for attr in ('control', 'policy', 'object_stores'): value = tiering.get(attr) if attr == 'object_stores' and value is not None: value = [dict(name=x) for x in value] if value is not None: application_component['tiering'][attr] = value return application_component def create_san_app_body(self): '''Create body for san template''' # TODO: # Should we support new_igroups? # It may raise idempotency issues if the REST call fails if the igroup already exists. # And we already have na_ontap_igroups. san = { 'application_components': [self.create_san_app_component()], } for attr in ('protection_type', ): value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr]) if value is not None: # we expect value to be a dict, but maybe an empty dict value = self.na_helper.filter_out_none_entries(value) if value: san[attr] = value for attr in ('os_type', ): value = self.na_helper.safe_get(self.parameters, [attr]) if value is not None: san[attr] = value body, error = self.rest_app.create_application_body('san', san) return body, error def create_san_application(self): '''Use REST application/applications san template to create one or more LUNs''' body, error = self.create_san_app_body() self.fail_on_error(error) dummy, error = self.rest_app.create_application(body) self.fail_on_error(error) def delete_san_application(self): '''Use REST application/applications san template to delete one or more LUNs''' dummy, error = self.rest_app.delete_application() self.fail_on_error(error) def create_lun(self): """ Create LUN with requested name and size """ path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) options = { 'path': path, 'size': str(self.parameters['size']), 'space-reservation-enabled': str(self.parameters['space_reserve']), 'space-allocation-enabled': str(self.parameters['space_allocation']), 'use-exact-size': str(self.parameters['use_exact_size']) } if self.parameters.get('os_type') is not None: options['ostype'] = self.parameters['os_type'] if self.parameters.get('qos_policy_group') is not None: options['qos-policy-group'] = self.parameters['qos_policy_group'] lun_create = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-create-by-size', **options) try: self.server.invoke_successfully(lun_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json( msg="Error provisioning lun %s of size %s: %s" % (self.parameters['name'], self.parameters['size'], to_native(exc)), exception=traceback.format_exc()) def delete_lun(self, path): """ Delete requested LUN """ lun_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-destroy', **{ 'path': path, 'force': str(self.parameters['force_remove']), 'destroy-fenced-lun': str(self.parameters['force_remove_fenced']) }) try: self.server.invoke_successfully(lun_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error deleting lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) def resize_lun(self, path): """ Resize requested LUN. :return: True if LUN was actually re-sized, false otherwise. :rtype: bool """ lun_resize = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-resize', **{ 'path': path, 'size': str(self.parameters['size']), 'force': str(self.parameters['force_resize']) }) try: self.server.invoke_successfully(lun_resize, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: if to_native(exc.code) == "9042": # Error 9042 denotes the new LUN size being the same as the # old LUN size. This happens when there's barely any difference # in the two sizes. For example, from 8388608 bytes to # 8194304 bytes. This should go away if/when the default size # requested/reported to/from the controller is changed to a # larger unit (MB/GB/TB). return False else: self.module.fail_json(msg="Error resizing lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) return True def set_lun_value(self, path, key, value): key_to_zapi = dict(qos_policy_group=('lun-set-qos-policy-group', 'qos-policy-group'), space_allocation=('lun-set-space-alloc', 'enable'), space_reserve=('lun-set-space-reservation-info', 'enable')) if key in key_to_zapi: zapi, option = key_to_zapi[key] else: self.module.fail_json(msg="option %s cannot be modified to %s" % (key, value)) options = dict(path=path) if option == 'enable': options[option] = self.na_helper.get_value_for_bool(False, value) else: options[option] = value lun_set = netapp_utils.zapi.NaElement.create_node_with_children( zapi, **options) try: self.server.invoke_successfully(lun_set, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error setting lun option %s: %s" % (key, to_native(exc)), exception=traceback.format_exc()) return def modify_lun(self, path, modify): """ update LUN properties (except size or name) """ for key, value in modify.items(): self.set_lun_value(path, key, value) def rename_lun(self, path, new_path): """ rename LUN """ lun_move = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-move', **{ 'path': path, 'new-path': new_path }) try: self.server.invoke_successfully(lun_move, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error moving lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) def fail_on_error(self, error, stack=False): if error is None: return elements = dict(msg="Error: %s" % error) if stack: elements['stack'] = traceback.format_stack() self.module.fail_json(**elements) def apply(self): results = dict() warnings = list() netapp_utils.ems_log_event("na_ontap_lun", self.server) app_cd_action = None if self.rest_app: app_current, error = self.rest_app.get_application_uuid() self.fail_on_error(error) app_cd_action = self.na_helper.get_cd_action( app_current, self.parameters) if app_cd_action == 'create' and self.parameters.get( 'size') is None: self.module.fail_json( msg="size is a required parameter for create.") # For LUNs created using a SAN application, we're getting lun paths from the backing storage lun_path, from_lun_path = None, None from_name = self.parameters.get('from_name') if self.rest_app and app_cd_action is None and app_current: lun_path = self.get_lun_path_from_backend(self.parameters['name']) if from_name is not None: from_lun_path = self.get_lun_path_from_backend(from_name) if app_cd_action is None: # actions at LUN level current = self.get_lun(self.parameters['name'], lun_path) if current is not None and lun_path is None: lun_path = current['path'] cd_action = self.na_helper.get_cd_action(current, self.parameters) modify, rename = None, None if cd_action == 'create' and from_name is not None: # create by renaming existing LUN, if it really exists old_lun = self.get_lun(from_name, from_lun_path) rename = self.na_helper.is_rename_action(old_lun, current) if rename is None: self.module.fail_json( msg="Error renaming lun: %s does not exist" % from_name) if rename: current = old_lun if from_lun_path is None: from_lun_path = current['path'] head, _sep, tail = from_lun_path.rpartition(from_name) if tail: self.module.fail_json( msg= "Error renaming lun: %s does not match lun_path %s" % (from_name, from_lun_path)) lun_path = head + self.parameters['name'] results['renamed'] = True cd_action = None if cd_action == 'create' and self.parameters.get('size') is None: self.module.fail_json( msg="size is a required parameter for create.") if cd_action is None and self.parameters['state'] == 'present': # we already handled rename if required current.pop('name', None) modify = self.na_helper.get_modified_attributes( current, self.parameters) results['modify'] = dict(modify) if cd_action and self.rest_app and app_cd_action is None and app_current: msg = 'This module does not support %s a LUN by name %s a SAN application.' %\ ('adding', 'to') if cd_action == 'create' else ('removing', 'from') warnings.append(msg) cd_action = None self.na_helper.changed = False if self.na_helper.changed and not self.module.check_mode: if app_cd_action == 'create': self.create_san_application() elif app_cd_action == 'delete': self.rest_app.delete_application() elif cd_action == 'create': self.create_lun() elif cd_action == 'delete': self.delete_lun(lun_path) else: if rename: self.rename_lun(from_lun_path, lun_path) size_changed = False if modify and 'size' in modify: # Ensure that size was actually changed. Please # read notes in 'resize_lun' function for details. size_changed = self.resize_lun(lun_path) modify.pop('size') if modify: self.modify_lun(lun_path, modify) if not modify and not rename: # size may not have changed self.na_helper.changed = size_changed results['changed'] = self.na_helper.changed self.module.exit_json(**results)
class NetAppOntapEfficiencyPolicy(object): """ Create, delete and modify efficiency 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'), policy_name=dict(required=True, type='str'), comment=dict(required=False, type='str'), duration=dict(required=False, type='int'), enabled=dict(required=False, type='bool'), policy_type=dict(required=False, choices=['threshold', 'scheduled']), qos_policy=dict(required=False, choices=['background', 'best_effort']), schedule=dict(reuired=False, type='str'), vserver=dict(required=True, type='str'), changelog_threshold_percent=dict(required=False, type='int'))) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, mutually_exclusive=[('changelog_threshold_percent', 'duration'), ('changelog_threshold_percent', 'schedule')]) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.set_playbook_zapi_key_map() if self.parameters.get('policy_type'): if self.parameters['policy_type'] == 'threshold': if self.parameters.get('duration'): self.module.fail_json( msg="duration cannot be set if policy_type is threshold" ) if self.parameters.get('schedule'): self.module.fail_json( msg='schedule cannot be set if policy_type is threshold' ) # if policy_type is 'scheduled' else: if self.parameters.get('changelog_threshold_percent'): self.module.fail_json( msg= 'changelog_threshold_percent cannot be set if policy_type is scheduled' ) 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_int_keys = { 'changelog_threshold_percent': 'changelog-threshold-percent', 'duration': 'duration', } self.na_helper.zapi_str_keys = { 'policy_name': 'policy-name', 'comment': 'comment', 'policy_type': 'policy-type', 'qos_policy': 'qos-policy', 'schedule': 'schedule' } self.na_helper.zapi_bool_keys = {'enabled': 'enabled'} def get_efficiency_policy(self): """ Get a efficiency policy :return: a efficiency-policy info """ sis_policy_obj = netapp_utils.zapi.NaElement("sis-policy-get-iter") query = netapp_utils.zapi.NaElement("query") sis_policy_info = netapp_utils.zapi.NaElement("sis-policy-info") sis_policy_info.add_new_child("policy-name", self.parameters['policy_name']) sis_policy_info.add_new_child("vserver", self.parameters['vserver']) query.add_child_elem(sis_policy_info) sis_policy_obj.add_child_elem(query) try: results = self.server.invoke_successfully(sis_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error searching for efficiency policy %s: %s" % (self.parameters['policy_name'], to_native(error)), exception=traceback.format_exc()) return_value = {} if results.get_child_by_name('num-records') and int( results.get_child_content('num-records')) == 1: attributes_list = results.get_child_by_name('attributes-list') sis_info = attributes_list.get_child_by_name('sis-policy-info') for option, zapi_key in self.na_helper.zapi_int_keys.items(): return_value[option] = self.na_helper.get_value_for_int( from_zapi=True, value=sis_info.get_child_content(zapi_key)) for option, zapi_key in self.na_helper.zapi_bool_keys.items(): return_value[option] = self.na_helper.get_value_for_bool( from_zapi=True, value=sis_info.get_child_content(zapi_key)) for option, zapi_key in self.na_helper.zapi_str_keys.items(): return_value[option] = sis_info.get_child_content(zapi_key) return return_value return None def create_efficiency_policy(self): """ Creates a efficiency policy :return: None """ sis_policy_obj = netapp_utils.zapi.NaElement("sis-policy-create") for option, zapi_key in self.na_helper.zapi_int_keys.items(): if self.parameters.get(option): sis_policy_obj.add_new_child( zapi_key, self.na_helper.get_value_for_int( from_zapi=False, value=self.parameters[option])) for option, zapi_key in self.na_helper.zapi_bool_keys.items(): if self.parameters.get(option): sis_policy_obj.add_new_child( zapi_key, self.na_helper.get_value_for_bool( from_zapi=False, value=self.parameters[option])) for option, zapi_key in self.na_helper.zapi_str_keys.items(): if self.parameters.get(option): sis_policy_obj.add_new_child(zapi_key, str(self.parameters[option])) try: self.server.invoke_successfully(sis_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error creating efficiency policy %s: %s" % (self.parameters["policy_name"], to_native(error)), exception=traceback.format_exc()) def delete_efficiency_policy(self): """ Delete a efficiency Policy :return: None """ sis_policy_obj = netapp_utils.zapi.NaElement("sis-policy-delete") sis_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) try: self.server.invoke_successfully(sis_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error deleting efficiency policy %s: %s" % (self.parameters["policy_name"], to_native(error)), exception=traceback.format_exc()) def modify_efficiency_policy(self, current, modify): """ Modify a efficiency policy :return: None """ sis_policy_obj = netapp_utils.zapi.NaElement("sis-policy-modify") sis_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) # sis-policy-create zapi pre-checks the options and fails if it's not supported. # sis-policy-modify pre-checks one of the options, but tries to modify the others even it's not supported. And it will mess up the vsim. # Do the checks before sending to the zapi. if current['policy_type'] == 'scheduled' and self.parameters.get( 'policy_type') != 'threshold': if modify.get('changelog_threshold_percent'): self.module.fail_json( msg= "changelog_threshold_percent cannot be set if policy_type is scheduled" ) elif current['policy_type'] == 'threshold' and self.parameters.get( 'policy_type') != 'scheduled': if modify.get('duration'): self.module.fail_json( msg="duration cannot be set if policy_type is threshold") elif modify.get('schedule'): self.module.fail_json( msg="schedule cannot be set if policy_type is threshold") for attribute in modify: sis_policy_obj.add_new_child(self.attribute_to_name(attribute), str(self.parameters[attribute])) try: self.server.invoke_successfully(sis_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error modifying efficiency policy %s: %s" % (self.parameters["policy_name"], to_native(error)), exception=traceback.format_exc()) @staticmethod def attribute_to_name(attribute): return str.replace(attribute, '_', '-') def apply(self): netapp_utils.ems_log_event("na_ontap_efficiency_policy", self.server) current = self.get_efficiency_policy() 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_efficiency_policy() elif cd_action == 'delete': self.delete_efficiency_policy() elif modify: self.modify_efficiency_policy(current, modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapUser(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'), applications=dict( required=True, type='list', aliases=['application'], choices=[ 'console', 'http', 'ontapi', 'rsh', 'snmp', 'sp', 'service-processor', 'ssh', 'telnet' ], ), authentication_method=dict(required=True, type='str', choices=[ 'community', 'password', 'publickey', 'domain', 'nsswitch', 'usm', 'cert' ]), set_password=dict(required=False, type='str', no_log=True), role_name=dict(required=False, type='str'), lock_user=dict(required=False, type='bool'), vserver=dict(required=True, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[('state', 'present', ['role_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_user(self, application=None): """ Checks if the user exists. :param: application: application to grant access to :return: Dictionary if user found None if user is not found """ security_login_get_iter = netapp_utils.zapi.NaElement( 'security-login-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-account-info', **{ 'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'], 'authentication-method': self.parameters['authentication_method'] }) if application is not None: query_details.add_new_child('application', application) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) security_login_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully(security_login_get_iter, enable_tunneling=False) 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('security-login-account-info') return_value = { 'lock_user': interface_attributes.get_child_content('is-locked'), 'role_name': interface_attributes.get_child_content('role-name') } return return_value return None except netapp_utils.zapi.NaApiError as error: # Error 16034 denotes a user not being found. if to_native(error.code) == "16034": return None # Error 16043 denotes the user existing, but the application missing elif to_native(error.code) == "16043": return None else: self.module.fail_json( msg='Error getting user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_user(self, application): """ creates the user for the given application and authentication_method :param: application: application to grant access to """ user_create = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-create', **{ 'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'], 'application': application, 'authentication-method': self.parameters['authentication_method'], 'role-name': self.parameters.get('role_name') }) if self.parameters.get('set_password') is not None: user_create.add_new_child('password', self.parameters.get('set_password')) try: self.server.invoke_successfully(user_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def lock_given_user(self): """ locks the user :return: True if user locked False if lock user is not performed :rtype: bool """ user_lock = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-lock', **{ 'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'] }) try: self.server.invoke_successfully(user_lock, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error locking user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def unlock_given_user(self): """ unlocks the user :return: True if user unlocked False if unlock user is not performed :rtype: bool """ user_unlock = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-unlock', **{ 'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'] }) try: self.server.invoke_successfully(user_unlock, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == '13114': return False else: self.module.fail_json( msg='Error unlocking user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) return True def delete_user(self, application): """ deletes the user for the given application and authentication_method :param: application: application to grant access to """ user_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-delete', **{ 'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'], 'application': application, 'authentication-method': self.parameters['authentication_method'] }) try: self.server.invoke_successfully(user_delete, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error removing user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def change_password(self): """ Changes the password :return: True if password updated False if password is not updated :rtype: bool """ # self.server.set_vserver(self.parameters['vserver']) modify_password = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-modify-password', **{ 'new-password': str(self.parameters.get('set_password')), 'user-name': self.parameters['name'] }) try: self.server.invoke_successfully(modify_password, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == '13114': return False # if the user give the same password, instead of returning an error, return ok if to_native(error.code) == '13214' and \ (error.message.startswith('New password must be different than last 6 passwords.') or error.message.startswith('New password must be different than the old password.')): return False self.module.fail_json( msg='Error setting password for user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) self.server.set_vserver(None) return True def modify_user(self, application): """ Modify user """ user_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-modify', **{ 'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'], 'application': application, 'authentication-method': self.parameters['authentication_method'], 'role-name': self.parameters.get('role_name') }) try: self.server.invoke_successfully(user_modify, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def apply(self): create_delete_decision = {} modify_decision = {} netapp_utils.ems_log_event("na_ontap_user", self.server) for application in self.parameters['applications']: current = self.get_user(application) if current is not None: current['lock_user'] = self.na_helper.get_value_for_bool( True, current['lock_user']) cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is not None: create_delete_decision[application] = cd_action else: modify_decision[ application] = self.na_helper.get_modified_attributes( current, self.parameters) if not create_delete_decision and self.parameters.get( 'state') == 'present': if self.parameters.get('set_password') is not None: self.na_helper.changed = True if self.na_helper.changed: if self.module.check_mode: pass else: for application in create_delete_decision: if create_delete_decision[application] == 'create': self.create_user(application) elif create_delete_decision[application] == 'delete': self.delete_user(application) lock_user = False for application in modify_decision: if 'role_name' in modify_decision[application]: self.modify_user(application) if 'lock_user' in modify_decision[application]: lock_user = True if lock_user: if self.parameters.get('lock_user'): self.lock_given_user() else: self.unlock_given_user() if not create_delete_decision and self.parameters.get( 'set_password') is not None: # if change password return false nothing has changed so we need to set changed to False self.na_helper.changed = self.change_password() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapLogForward(object): def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(choices=['present', 'absent'], default='present'), destination=dict(required=True, type='str'), port=dict(required=True, type='int'), facility=dict(required=False, type='str', choices=[ 'kern', 'user', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7' ]), force=dict(required=False, type='bool'), protocol=dict(required=False, type='str', choices=[ 'udp_unencrypted', 'tcp_unencrypted', 'tcp_encrypted' ]), verify_server=dict(required=False, type='bool'))) 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.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: 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_log_forward_config(self): """ gets log forward configuration :return: dict of log forward properties if exist, None if not """ if self.use_rest: log_forward_config = None api = "security/audit/destinations" query = { 'fields': 'port,protocol,facility,address,verify_server', 'address': self.parameters['destination'], 'port': self.parameters['port'] } message, error = self.rest_api.get(api, query) if error: self.module.fail_json(msg=error) if len(message.keys()) == 0: return None elif 'records' in message and len(message['records']) == 0: return None elif 'records' not in message: error = "Unexpected response in get_security_key_manager from %s: %s" % ( api, repr(message)) self.module.fail_json(msg=error) log_forward_config = { 'destination': message['records'][0]['address'], 'facility': message['records'][0]['facility'], 'port': message['records'][0]['port'], 'protocol': message['records'][0]['protocol'], 'verify_server': message['records'][0]['verify_server'] } return log_forward_config else: log_forward_config = None log_forward_get = netapp_utils.zapi.NaElement( 'cluster-log-forward-get') log_forward_get.add_new_child('destination', self.parameters['destination']) log_forward_get.add_new_child( 'port', self.na_helper.get_value_for_int(False, self.parameters['port'])) try: result = self.server.invoke_successfully(log_forward_get, True) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == "15661": # config doesnt exist return None else: self.module.fail_json( msg= 'Error getting log forward configuration for destination %s on port %s: %s' % (self.parameters['destination'], self.na_helper.get_value_for_int( False, self.parameters['port']), to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('attributes'): log_forward_attributes = result.get_child_by_name('attributes') cluster_log_forward_info = log_forward_attributes.get_child_by_name( 'cluster-log-forward-info') log_forward_config = { 'destination': cluster_log_forward_info.get_child_content('destination'), 'facility': cluster_log_forward_info.get_child_content('facility'), 'port': self.na_helper.get_value_for_int( True, cluster_log_forward_info.get_child_content('port')), 'protocol': cluster_log_forward_info.get_child_content('protocol'), 'verify_server': self.na_helper.get_value_for_bool( True, cluster_log_forward_info.get_child_content( 'verify-server')) } return log_forward_config def create_log_forward_config(self): """ Creates a log forward config :return: nothing """ if self.use_rest: api = "security/audit/destinations" body = dict() body['address'] = self.parameters['destination'] body['port'] = self.parameters['port'] for attr in ('protocol', 'facility', 'verify_server', 'force'): if attr in self.parameters: body[attr] = self.parameters[attr] dummy, error = self.rest_api.post(api, body) if error: self.module.fail_json(msg=error) else: log_forward_config_obj = netapp_utils.zapi.NaElement( 'cluster-log-forward-create') log_forward_config_obj.add_new_child( 'destination', self.parameters['destination']) log_forward_config_obj.add_new_child( 'port', self.na_helper.get_value_for_int(False, self.parameters['port'])) if 'facility' in self.parameters: log_forward_config_obj.add_new_child( 'facility', self.parameters['facility']) if 'force' in self.parameters: log_forward_config_obj.add_new_child( 'force', self.na_helper.get_value_for_bool( False, self.parameters['force'])) if 'protocol' in self.parameters: log_forward_config_obj.add_new_child( 'protocol', self.parameters['protocol']) if 'verify_server' in self.parameters: log_forward_config_obj.add_new_child( 'verify-server', self.na_helper.get_value_for_bool( False, self.parameters['verify_server'])) try: self.server.invoke_successfully(log_forward_config_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg= 'Error creating log forward config with destination %s on port %s: %s' % (self.parameters['destination'], self.na_helper.get_value_for_int( False, self.parameters['port']), to_native(error)), exception=traceback.format_exc()) def modify_log_forward_config(self): # need to recreate as protocol can't be changed self.destroy_log_forward_config() self.create_log_forward_config() def destroy_log_forward_config(self): """ Delete a log forward configuration :return: nothing """ if self.use_rest: api = "security/audit/destinations/%s/%s" % ( self.parameters['destination'], self.parameters['port']) body = None query = {'return_timeout': 3} dummy, error = self.rest_api.delete(api, body, query) if error: self.module.fail_json(msg=error) else: log_forward_config_obj = netapp_utils.zapi.NaElement( 'cluster-log-forward-destroy') log_forward_config_obj.add_new_child( 'destination', self.parameters['destination']) log_forward_config_obj.add_new_child( 'port', self.na_helper.get_value_for_int(False, self.parameters['port'])) try: self.server.invoke_successfully(log_forward_config_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg= 'Error destroying log forward destination %s on port %s: %s' % (self.parameters['destination'], self.na_helper.get_value_for_int( False, self.parameters['port']), to_native(error)), exception=traceback.format_exc()) def ems_log_event(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) return netapp_utils.ems_log_event("na_ontap_log_forward", cserver) def apply(self): if not self.use_rest: self.ems_log_event() current = self.get_log_forward_config() 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 not self.module.check_mode: if cd_action == 'create': self.create_log_forward_config() elif cd_action == 'delete': self.destroy_log_forward_config() elif modify: self.modify_log_forward_config() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapLUN(object): ''' create, modify, delete LUN ''' 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'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), size=dict(type='int'), size_unit=dict(default='gb', choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'], type='str'), comment=dict(required=False, type='str'), force_resize=dict(default=False, type='bool'), force_remove=dict(default=False, type='bool'), force_remove_fenced=dict(default=False, type='bool'), flexvol_name=dict(type='str'), vserver=dict(required=True, type='str'), os_type=dict(required=False, type='str', aliases=['ostype']), qos_policy_group=dict(required=False, type='str'), qos_adaptive_policy_group=dict(required=False, type='str'), space_reserve=dict(required=False, type='bool', default=True), space_allocation=dict(required=False, type='bool', default=False), use_exact_size=dict(required=False, type='bool', default=True), san_application_template=dict(type='dict', options=dict( use_san_application=dict(type='bool', default=True), name=dict(required=True, type='str'), igroup_name=dict(type='str'), lun_count=dict(type='int'), protection_type=dict(type='dict', options=dict( local_policy=dict(type='str'), )), storage_service=dict(type='str', choices=['value', 'performance', 'extreme']), tiering=dict(type='dict', options=dict( control=dict(type='str', choices=['required', 'best_effort', 'disallowed']), policy=dict(type='str', choices=['all', 'auto', 'none', 'snapshot-only']), object_stores=dict(type='list', elements='str') # create only )), total_size=dict(type='int'), total_size_unit=dict(choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'], type='str'), scope=dict(type='str', choices=['application', 'auto', 'lun'], default='auto'), )) )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, mutually_exclusive=[('qos_policy_group', 'qos_adaptive_policy_group')] ) # set up state variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get('size') is not None: self.parameters['size'] *= netapp_utils.POW2_BYTE_MAP[self.parameters['size_unit']] if self.na_helper.safe_get(self.parameters, ['san_application_template', 'total_size']) is not None: unit = self.na_helper.safe_get(self.parameters, ['san_application_template', 'total_size_unit']) if unit is None: unit = self.parameters['size_unit'] self.parameters['san_application_template']['total_size'] *= netapp_utils.POW2_BYTE_MAP[unit] self.warnings = list() self.debug = dict() # self.debug['got'] = 'empty' # uncomment to enable collecting data 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']) # REST API for application/applications if needed self.rest_api, self.rest_app = self.setup_rest_application() def setup_rest_application(self): use_application_template = self.na_helper.safe_get(self.parameters, ['san_application_template', 'use_san_application']) rest_api, rest_app = None, None if use_application_template: if self.parameters.get('flexvol_name') is not None: self.module.fail_json(msg="'flexvol_name' option is not supported when san_application_template is present") rest_api = netapp_utils.OntapRestAPI(self.module) name = self.na_helper.safe_get(self.parameters, ['san_application_template', 'name'], allow_sparse_dict=False) rest_app = RestApplication(rest_api, self.parameters['vserver'], name) elif self.parameters.get('flexvol_name') is None: self.module.fail_json(msg="flexvol_name option is required when san_application_template is not present") return rest_api, rest_app def get_luns(self, lun_path=None): """ Return list of LUNs matching vserver and volume names. :return: list of LUNs in XML format. :rtype: list """ luns = [] tag = None if lun_path is None and self.parameters.get('flexvol_name') is None: return luns query_details = netapp_utils.zapi.NaElement('lun-info') query_details.add_new_child('vserver', self.parameters['vserver']) if lun_path is not None: query_details.add_new_child('lun_path', lun_path) else: query_details.add_new_child('volume', self.parameters['flexvol_name']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) while True: lun_info = netapp_utils.zapi.NaElement('lun-get-iter') lun_info.add_child_elem(query) if tag: lun_info.add_new_child('tag', tag, True) result = self.server.invoke_successfully(lun_info, True) if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1: attr_list = result.get_child_by_name('attributes-list') luns.extend(attr_list.get_children()) tag = result.get_child_content('next-tag') if tag is None: break return luns def get_lun_details(self, lun): """ Extract LUN details, from XML to python dict :return: Details about the lun :rtype: dict """ return_value = dict() return_value['size'] = int(lun.get_child_content('size')) bool_attr_map = { 'is-space-alloc-enabled': 'space_allocation', 'is-space-reservation-enabled': 'space_reserve' } for attr in bool_attr_map: value = lun.get_child_content(attr) if value is not None: return_value[bool_attr_map[attr]] = self.na_helper.get_value_for_bool(True, value) str_attr_map = { 'comment': 'comment', 'multiprotocol-type': 'os_type', 'name': 'name', 'path': 'path', 'qos-policy-group': 'qos_policy_group', 'qos-adaptive-policy-group': 'qos_adaptive_policy_group', } for attr in str_attr_map: value = lun.get_child_content(attr) if value is None and attr in ('comment', 'qos-policy-group', 'qos-adaptive-policy-group'): value = '' if value is not None: return_value[str_attr_map[attr]] = value # Find out if the lun is attached attached_to = None lun_id = None if lun.get_child_content('mapped') == 'true': lun_map_list = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-map-list-info', **{'path': lun.get_child_content('path')}) result = self.server.invoke_successfully( lun_map_list, enable_tunneling=True) igroups = result.get_child_by_name('initiator-groups') if igroups: for igroup_info in igroups.get_children(): igroup = igroup_info.get_child_content( 'initiator-group-name') attached_to = igroup lun_id = igroup_info.get_child_content('lun-id') return_value.update({ 'attached_to': attached_to, 'lun_id': lun_id }) return return_value def find_lun(self, luns, name, lun_path=None): """ Return lun record matching name or path :return: lun record :rtype: XML or None if not found """ for lun in luns: path = lun.get_child_content('path') if lun_path is not None: if lun_path == path: return lun else: if name == path: return lun _rest, _splitter, found_name = path.rpartition('/') if found_name == name: return lun return None def get_lun(self, name, lun_path=None): """ Return details about the LUN :return: Details about the lun :rtype: dict """ luns = self.get_luns(lun_path) lun = self.find_lun(luns, name, lun_path) if lun is not None: return self.get_lun_details(lun) return None def get_luns_from_app(self): app_details, error = self.rest_app.get_application_details() self.fail_on_error(error) if app_details is not None: app_details['paths'] = self.get_lun_paths_from_app() return app_details def get_lun_paths_from_app(self): """Get luns path for SAN application""" backing_storage, error = self.rest_app.get_application_component_backing_storage() self.fail_on_error(error) # {'luns': [{'path': '/vol/ansibleLUN/ansibleLUN_1', ... if backing_storage is not None: return [lun['path'] for lun in backing_storage.get('luns', [])] return None def get_lun_path_from_backend(self, name): """returns lun path matching name if found in backing_storage retruns None if not found """ lun_paths = self.get_lun_paths_from_app() match = "/%s" % name for path in lun_paths: if path.endswith(match): return path return None def create_san_app_component(self, modify): '''Create SAN application component''' if modify: required_options = ['name'] action = 'modify' if 'lun_count' in modify: required_options.append('total_size') else: required_options = ('name', 'total_size') action = 'create' for option in required_options: if self.parameters.get(option) is None: self.module.fail_json(msg="Error: '%s' is required to %s a san application." % (option, action)) application_component = dict(name=self.parameters['name']) if not modify: application_component['lun_count'] = 1 # default value for create, may be overriden below for attr in ('igroup_name', 'lun_count', 'storage_service'): if not modify or attr in modify: value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr]) if value is not None: application_component[attr] = value for attr in ('os_type', 'qos_policy_group', 'qos_adaptive_policy_group', 'total_size'): if not modify or attr in modify: value = self.na_helper.safe_get(self.parameters, [attr]) if value is not None: # only one of them can be present at most if attr in ('qos_policy_group', 'qos_adaptive_policy_group'): attr = 'qos' value = dict(policy=dict(name=value)) application_component[attr] = value tiering = self.na_helper.safe_get(self.parameters, ['nas_application_template', 'tiering']) if tiering is not None and not modify: application_component['tiering'] = dict() for attr in ('control', 'policy', 'object_stores'): value = tiering.get(attr) if attr == 'object_stores' and value is not None: value = [dict(name=x) for x in value] if value is not None: application_component['tiering'][attr] = value return application_component def create_san_app_body(self, modify=None): '''Create body for san template''' # TODO: # Should we support new_igroups? # It may raise idempotency issues if the REST call fails if the igroup already exists. # And we already have na_ontap_igroups. san = { 'application_components': [self.create_san_app_component(modify)], } for attr in ('protection_type',): if not modify or attr in modify: value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr]) if value is not None: # we expect value to be a dict, but maybe an empty dict value = self.na_helper.filter_out_none_entries(value) if value: san[attr] = value for attr in ('os_type',): if not modify: # not supported for modify operation, but required at applicaiton component level value = self.na_helper.safe_get(self.parameters, [attr]) if value is not None: san[attr] = value body, error = self.rest_app.create_application_body('san', san) return body, error def create_san_application(self): '''Use REST application/applications san template to create one or more LUNs''' body, error = self.create_san_app_body() self.fail_on_error(error) dummy, error = self.rest_app.create_application(body) self.fail_on_error(error) def modify_san_application(self, modify): '''Use REST application/applications san template to add one or more LUNs''' body, error = self.create_san_app_body(modify) self.fail_on_error(error) # these cannot be present when using PATCH body.pop('name') body.pop('svm') body.pop('smart_container') dummy, error = self.rest_app.patch_application(body) self.fail_on_error(error) def convert_to_san_application(self, scope): '''First convert volume to smart container using POST Second modify app to add new luns using PATCH ''' # dummy modify, so that we don't fill in the body modify = dict(dummy='dummy') body, error = self.create_san_app_body(modify) self.fail_on_error(error) dummy, error = self.rest_app.create_application(body) self.fail_on_error(error) app_current, error = self.rest_app.get_application_uuid() self.fail_on_error(error) if app_current is None: self.module.fail_json('Error: failed to create smart container for %s' % self.parameters['name']) app_modify, app_modify_warning = self.app_changes(scope) if app_modify_warning is not None: self.warnings.append(app_modify_warning) if app_modify: self.modify_san_application(app_modify) def delete_san_application(self): '''Use REST application/applications san template to delete one or more LUNs''' dummy, error = self.rest_app.delete_application() self.fail_on_error(error) def create_lun(self): """ Create LUN with requested name and size """ path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) options = {'path': path, 'size': str(self.parameters['size']), 'space-reservation-enabled': str(self.parameters['space_reserve']), 'space-allocation-enabled': str(self.parameters['space_allocation']), 'use-exact-size': str(self.parameters['use_exact_size'])} if self.parameters.get('comment') is not None: options['comment'] = self.parameters['comment'] if self.parameters.get('os_type') is not None: options['ostype'] = self.parameters['os_type'] if self.parameters.get('qos_policy_group') is not None: options['qos-policy-group'] = self.parameters['qos_policy_group'] if self.parameters.get('qos_adaptive_policy_group') is not None: options['qos-adaptive-policy-group'] = self.parameters['qos_adaptive_policy_group'] lun_create = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-create-by-size', **options) try: self.server.invoke_successfully(lun_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error provisioning lun %s of size %s: %s" % (self.parameters['name'], self.parameters['size'], to_native(exc)), exception=traceback.format_exc()) def delete_lun(self, path): """ Delete requested LUN """ lun_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-destroy', **{'path': path, 'force': str(self.parameters['force_remove']), 'destroy-fenced-lun': str(self.parameters['force_remove_fenced'])}) try: self.server.invoke_successfully(lun_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error deleting lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) def resize_lun(self, path): """ Resize requested LUN. :return: True if LUN was actually re-sized, false otherwise. :rtype: bool """ lun_resize = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-resize', **{'path': path, 'size': str(self.parameters['size']), 'force': str(self.parameters['force_resize'])}) try: self.server.invoke_successfully(lun_resize, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: if to_native(exc.code) == "9042": # Error 9042 denotes the new LUN size being the same as the # old LUN size. This happens when there's barely any difference # in the two sizes. For example, from 8388608 bytes to # 8194304 bytes. This should go away if/when the default size # requested/reported to/from the controller is changed to a # larger unit (MB/GB/TB). return False else: self.module.fail_json(msg="Error resizing lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) return True def set_lun_value(self, path, key, value): key_to_zapi = dict( comment=('lun-set-comment', 'comment'), # The same ZAPI is used for both QOS attributes qos_policy_group=('lun-set-qos-policy-group', 'qos-policy-group'), qos_adaptive_policy_group=('lun-set-qos-policy-group', 'qos-adaptive-policy-group'), space_allocation=('lun-set-space-alloc', 'enable'), space_reserve=('lun-set-space-reservation-info', 'enable') ) if key in key_to_zapi: zapi, option = key_to_zapi[key] else: self.module.fail_json(msg="option %s cannot be modified to %s" % (key, value)) options = dict(path=path) if option == 'enable': options[option] = self.na_helper.get_value_for_bool(False, value) else: options[option] = value lun_set = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options) try: self.server.invoke_successfully(lun_set, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error setting lun option %s: %s" % (key, to_native(exc)), exception=traceback.format_exc()) return def modify_lun(self, path, modify): """ update LUN properties (except size or name) """ for key, value in modify.items(): self.set_lun_value(path, key, value) def rename_lun(self, path, new_path): """ rename LUN """ lun_move = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-move', **{'path': path, 'new-path': new_path}) try: self.server.invoke_successfully(lun_move, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error moving lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) def fail_on_error(self, error, stack=False): if error is None: return elements = dict(msg="Error: %s" % error) if stack: elements['stack'] = traceback.format_stack() self.module.fail_json(**elements) def set_total_size(self, validate): # fix total_size attribute, report error if total_size is missing (or size is missing) attr = 'total_size' value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr]) if value is not None or not validate: self.parameters[attr] = value return lun_count = self.na_helper.safe_get(self.parameters, ['san_application_template', 'lun_count']) value = self.parameters.get('size') if value is not None and (lun_count is None or lun_count == 1): self.parameters[attr] = value return self.module.fail_json("Error: 'total_size' is a required SAN application template attribute when creating a LUN application") def validate_app_create(self): # fix total_size attribute self.set_total_size(validate=True) def validate_app_changes(self, modify, warning): errors = list() for key in modify: if key not in ('lun_count', 'total_size'): errors.append("Error: the following application parameter cannot be modified: %s: %s." % (key, modify[key])) if 'lun_count' in modify: for attr in ('total_size', 'os_type', 'igroup_name'): value = self.parameters.get(attr) if value is None: value = self.na_helper.safe_get(self.parameters['san_application_template'], [attr]) if value is None: errors.append('Error: %s is a required parameter when increasing lun_count.' % attr) else: modify[attr] = value if warning: errors.append('Error: %s' % warning) if errors: self.module.fail_json(msg='\n'.join(errors)) if 'total_size' in modify: self.set_total_size(validate=False) if warning: # can't change total_size, let's ignore it self.warnings.append(warning) modify.pop('total_size') def app_changes(self, scope): # find and validate app changes app_current, error = self.rest_app.get_application_details('san') self.fail_on_error(error) # save application name, as it is overriden in the flattening operation app_name = app_current['name'] # there is an issue with total_size not reflecting the real total_size, and some additional overhead provisioned_size = self.na_helper.safe_get(app_current, ['statistics', 'space', 'provisioned']) if provisioned_size is None: provisioned_size = 0 if self.debug: self.debug['app_current'] = app_current # will be updated below as it is mutable self.debug['got'] = copy.deepcopy(app_current) # fixed copy # flatten app_current = app_current['san'] # app template app_current.update(app_current['application_components'][0]) # app component del app_current['application_components'] # if component name does not match, assume a change at LUN level comp_name = app_current['name'] if comp_name != self.parameters['name']: msg = "desired component/volume name: %s does not match existing component name: %s" % (self.parameters['name'], comp_name) if scope == 'application': self.module.fail_json(msg='Error: ' + msg + ". scope=%s" % scope) return None, msg + ". scope=%s, assuming 'lun' scope." % scope # restore app name app_current['name'] = app_name # ready to compare, except for a quirk in size handling total_size = app_current['total_size'] desired = dict(self.parameters['san_application_template']) desired_size = desired.get('total_size') warning = None if desired_size is not None: if desired_size < total_size: self.module.fail_json("Error: can't reduce size: total_size=%d, provisioned=%d, requested=%d" % (total_size, provisioned_size, desired_size)) elif desired_size > total_size and desired_size < provisioned_size: # we can't increase, but we can't say it is a problem, as the size is already bigger! warning = "requested size is too small: total_size=%d, provisioned=%d, requested=%d" % (total_size, provisioned_size, desired_size) # preserve change state before calling modify in case an ignorable total_size change is the only change changed = self.na_helper.changed app_modify = self.na_helper.get_modified_attributes(app_current, desired) self.validate_app_changes(app_modify, warning) if not app_modify: self.na_helper.changed = changed app_modify = None return app_modify, None def apply(self): results = dict() netapp_utils.ems_log_event("na_ontap_lun", self.server) app_cd_action, app_modify, lun_cd_action, lun_modify, lun_rename = None, None, None, None, None app_modify_warning = None actions = list() if self.rest_app: scope = self.na_helper.safe_get(self.parameters, ['san_application_template', 'scope']) app_current, error = self.rest_app.get_application_uuid() self.fail_on_error(error) if scope == 'lun' and app_current is None: self.module.fail_json('Application not found: %s. scope=%s.' % (self.na_helper.safe_get(self.parameters, ['san_application_template', 'name']), scope)) else: # no application template, fall back to LUN only scope = 'lun' if self.rest_app and scope != 'lun': app_cd_action = self.na_helper.get_cd_action(app_current, self.parameters) if app_cd_action == 'create': # check if target volume already exists cp_volume_name = self.parameters['name'] volume, error = rest_volume.get_volume(self.rest_api, self.parameters['vserver'], cp_volume_name) self.fail_on_error(error) if volume is not None: if scope == 'application': # volume already exists, but not as part of this application app_cd_action = 'convert' else: # default name already in use, ask user to clarify intent msg = "Error: volume '%s' already exists. Please use a different group name, or use 'application' scope. scope=%s" self.module.fail_json(msg=msg % (cp_volume_name, scope)) if app_cd_action is not None: actions.append('app_%s' % app_cd_action) if app_cd_action == 'create': self.validate_app_create() if app_cd_action is None and app_current is not None: app_modify, app_modify_warning = self.app_changes(scope) if app_modify: actions.append('app_modify') results['app_modify'] = dict(app_modify) if app_cd_action is None and scope != 'application': # actions at LUN level lun_path, from_lun_path = None, None from_name = self.parameters.get('from_name') if self.rest_app and app_current: # For LUNs created using a SAN application, we're getting lun paths from the backing storage lun_path = self.get_lun_path_from_backend(self.parameters['name']) if from_name is not None: from_lun_path = self.get_lun_path_from_backend(from_name) current = self.get_lun(self.parameters['name'], lun_path) if current is not None and lun_path is None: lun_path = current['path'] lun_cd_action = self.na_helper.get_cd_action(current, self.parameters) if lun_cd_action == 'create' and from_name is not None: # create by renaming existing LUN, if it really exists old_lun = self.get_lun(from_name, from_lun_path) lun_rename = self.na_helper.is_rename_action(old_lun, current) if lun_rename is None: self.module.fail_json(msg="Error renaming lun: %s does not exist" % from_name) if lun_rename: current = old_lun if from_lun_path is None: from_lun_path = current['path'] head, _sep, tail = from_lun_path.rpartition(from_name) if tail: self.module.fail_json(msg="Error renaming lun: %s does not match lun_path %s" % (from_name, from_lun_path)) lun_path = head + self.parameters['name'] lun_cd_action = None actions.append('lun_rename') app_modify_warning = None # reset warning as we found a match if lun_cd_action is not None: actions.append('lun_%s' % lun_cd_action) if lun_cd_action is None and self.parameters['state'] == 'present': # we already handled rename if required current.pop('name', None) lun_modify = self.na_helper.get_modified_attributes(current, self.parameters) if lun_modify: actions.append('lun_modify') results['lun_modify'] = dict(lun_modify) app_modify_warning = None # reset warning as we found a match if lun_cd_action and self.rest_app and app_current: msg = 'This module does not support %s a LUN by name %s a SAN application.' %\ ('adding', 'to') if lun_cd_action == 'create' else ('removing', 'from') if scope == 'auto': # ignore LUN not found, as name can be a group name self.warnings.append(msg + ". scope=%s, assuming 'application'" % scope) if not app_modify: self.na_helper.changed = False elif scope == 'lun': self.module.fail_json(msg=msg + ". scope=%s." % scope) lun_cd_action = None if lun_cd_action == 'create' and self.parameters.get('size') is None: self.module.fail_json(msg="size is a required parameter for create.") if self.na_helper.changed and not self.module.check_mode: if app_cd_action == 'create': self.create_san_application() elif app_cd_action == 'convert': self.convert_to_san_application(scope) elif app_cd_action == 'delete': self.rest_app.delete_application() elif lun_cd_action == 'create': self.create_lun() elif lun_cd_action == 'delete': self.delete_lun(lun_path) else: if app_modify: self.modify_san_application(app_modify) if lun_rename: self.rename_lun(from_lun_path, lun_path) size_changed = False if lun_modify and 'size' in lun_modify: # Ensure that size was actually changed. Please # read notes in 'resize_lun' function for details. size_changed = self.resize_lun(lun_path) lun_modify.pop('size') if lun_modify: self.modify_lun(lun_path, lun_modify) if not lun_modify and not lun_rename and not app_modify: # size may not have changed self.na_helper.changed = size_changed if app_modify_warning: self.warnings.append(app_modify_warning) results['changed'] = self.na_helper.changed results['actions'] = actions if self.warnings: results['warnings'] = self.warnings results.update(self.debug) self.module.exit_json(**results)
class NetAppONTAPCifsSecurity(object): ''' modify vserver cifs security ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( vserver=dict(required=True, type='str'), kerberos_clock_skew=dict(required=False, type='int'), kerberos_ticket_age=dict(required=False, type='int'), kerberos_renew_age=dict(required=False, type='int'), kerberos_kdc_timeout=dict(required=False, type='int'), is_signing_required=dict(required=False, type='bool'), is_password_complexity_required=dict(required=False, type='bool'), is_aes_encryption_enabled=dict(required=False, type='bool'), is_smb_encryption_required=dict(required=False, type='bool'), lm_compatibility_level=dict(required=False, choices=['lm_ntlm_ntlmv2_krb', 'ntlm_ntlmv2_krb', 'ntlmv2_krb', 'krb']), referral_enabled_for_ad_ldap=dict(required=False, type='bool'), session_security_for_ad_ldap=dict(required=False, choices=['none', 'sign', 'seal']), smb1_enabled_for_dc_connections=dict(required=False, choices=['false', 'true', 'system_default']), smb2_enabled_for_dc_connections=dict(required=False, choices=['false', 'true', 'system_default']), use_start_tls_for_ad_ldap=dict(required=False, type='bool') )) 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_int_keys = { 'kerberos_clock_skew': 'kerberos-clock-skew', 'kerberos_ticket_age': 'kerberos-ticket-age', 'kerberos_renew_age': 'kerberos-renew-age', 'kerberos_kdc_timeout': 'kerberos-kdc-timeout' } self.na_helper.zapi_bool_keys = { 'is_signing_required': 'is-signing-required', 'is_password_complexity_required': 'is-password-complexity-required', 'is_aes_encryption_enabled': 'is-aes-encryption-enabled', 'is_smb_encryption_required': 'is-smb-encryption-required', 'referral_enabled_for_ad_ldap': 'referral-enabled-for-ad-ldap', 'use_start_tls_for_ad_ldap': 'use-start-tls-for-ad-ldap' } self.na_helper.zapi_str_keys = { 'lm_compatibility_level': 'lm-compatibility-level', 'session_security_for_ad_ldap': 'session-security-for-ad-ldap', 'smb1_enabled_for_dc_connections': 'smb1-enabled-for-dc-connections', 'smb2_enabled_for_dc_connections': 'smb2-enabled-for-dc-connections' } def cifs_security_get_iter(self): """ get current vserver cifs security. :return: a dict of vserver cifs security """ cifs_security_get = netapp_utils.zapi.NaElement('cifs-security-get-iter') query = netapp_utils.zapi.NaElement('query') cifs_security = netapp_utils.zapi.NaElement('cifs-security') cifs_security.add_new_child('vserver', self.parameters['vserver']) query.add_child_elem(cifs_security) cifs_security_get.add_child_elem(query) cifs_security_details = dict() try: result = self.server.invoke_successfully(cifs_security_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching cifs security from %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0: cifs_security_info = result.get_child_by_name('attributes-list').get_child_by_name('cifs-security') for option, zapi_key in self.na_helper.zapi_int_keys.items(): cifs_security_details[option] = self.na_helper.get_value_for_int(from_zapi=True, value=cifs_security_info.get_child_content(zapi_key)) for option, zapi_key in self.na_helper.zapi_bool_keys.items(): cifs_security_details[option] = self.na_helper.get_value_for_bool(from_zapi=True, value=cifs_security_info.get_child_content(zapi_key)) for option, zapi_key in self.na_helper.zapi_str_keys.items(): if cifs_security_info.get_child_content(zapi_key) is None: cifs_security_details[option] = None else: cifs_security_details[option] = cifs_security_info.get_child_content(zapi_key) return cifs_security_details return None def cifs_security_modify(self, modify): """ :param modify: A list of attributes to modify :return: None """ cifs_security_modify = netapp_utils.zapi.NaElement('cifs-security-modify') for attribute in modify: cifs_security_modify.add_new_child(self.attribute_to_name(attribute), str(self.parameters[attribute])) try: self.server.invoke_successfully(cifs_security_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error modifying cifs security on %s: %s' % (self.parameters['vserver'], to_native(e)), exception=traceback.format_exc()) @staticmethod def attribute_to_name(attribute): return str.replace(attribute, '_', '-') def apply(self): """Call modify operations.""" self.asup_log_for_cserver("na_ontap_vserver_cifs_security") current = self.cifs_security_get_iter() modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if modify: self.cifs_security_modify(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 NetAppOntapUser(object): """ Common operations to manage users and roles. """ def __init__(self): self.use_rest = False 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'), applications=dict(required=True, type='list', elements='str', aliases=['application'], choices=['console', 'http', 'ontapi', 'rsh', 'snmp', 'sp', 'service-processor', 'ssh', 'telnet'],), authentication_method=dict(required=True, type='str', choices=['community', 'password', 'publickey', 'domain', 'nsswitch', 'usm', 'cert']), set_password=dict(required=False, type='str', no_log=True), role_name=dict(required=False, type='str'), lock_user=dict(required=False, type='bool'), vserver=dict(required=True, type='str', aliases=['svm']), authentication_protocol=dict(required=False, type='str', choices=['none', 'md5', 'sha', 'sha2-256']), authentication_password=dict(required=False, type='str', no_log=True), engine_id=dict(required=False, type='str'), privacy_protocol=dict(required=False, type='str', choices=['none', 'des', 'aes128']), privacy_password=dict(required=False, type='str', no_log=True), remote_switch_ipaddress=dict(required=False, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, required_if=[ ('state', 'present', ['role_name']) ], supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # REST API should be used for ONTAP 9.6 or higher self.restApi = OntapRestAPI(self.module) # some attributes are not supported in earlier REST implementation unsupported_rest_properties = ['authentication_password', 'authentication_protocol', 'engine_id', 'privacy_password', 'privacy_protocol'] used_unsupported_rest_properties = [x for x in unsupported_rest_properties if x in self.parameters] self.use_rest, error = self.restApi.is_rest(used_unsupported_rest_properties) if error is not None: self.module.fail_json(msg=error) if not self.use_rest: if not HAS_NETAPP_LIB: 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']) else: if 'snmp' in self.parameters['applications']: self.module.fail_json(msg="Snmp as application is not supported in REST.") def get_user_rest(self): api = 'security/accounts' params = { 'name': self.parameters['name'] } if self.parameters.get('vserver') is None: # vserser is empty for cluster params['scope'] = 'cluster' else: params['owner.name'] = self.parameters['vserver'] message, error = self.restApi.get(api, params) if error: self.module.fail_json(msg='Error while fetching user info: %s' % error) if message['num_records'] == 1: return message['records'][0]['owner']['uuid'], message['records'][0]['name'] if message['num_records'] > 1: self.module.fail_json(msg='Error while fetching user info, found multiple entries: %s' % repr(message)) return None def get_user_details_rest(self, name, uuid): params = { 'fields': 'role,applications,locked' } api = "security/accounts/%s/%s" % (uuid, name) message, error = self.restApi.get(api, params) if error: self.module.fail_json(msg='Error while fetching user details: %s' % error) if message: return_value = { 'role_name': message['role']['name'], 'applications': [app['application'] for app in message['applications']] } if "locked" in message: return_value['lock_user'] = message['locked'] return return_value def get_user(self, application=None): """ Checks if the user exists. :param: application: application to grant access to :return: Dictionary if user found None if user is not found """ security_login_get_iter = netapp_utils.zapi.NaElement('security-login-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-account-info', **{'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'], 'authentication-method': self.parameters['authentication_method']}) if application is not None: query_details.add_new_child('application', application) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) security_login_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully(security_login_get_iter, enable_tunneling=False) 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('security-login-account-info') return_value = { 'lock_user': interface_attributes.get_child_content('is-locked'), 'role_name': interface_attributes.get_child_content('role-name') } return return_value return None except netapp_utils.zapi.NaApiError as error: # Error 16034 denotes a user not being found. if to_native(error.code) == "16034": return None # Error 16043 denotes the user existing, but the application missing elif to_native(error.code) == "16043": return None else: self.module.fail_json(msg='Error getting user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_user_rest(self, apps=None): app_list = list() if apps is not None: for app in apps: mydict = { "application": app, "authentication_methods": self.parameters['authentication_method'].split(), } app_list.append(mydict) api = 'security/accounts' params = { 'name': self.parameters['name'], 'role.name': self.parameters['role_name'], 'applications': app_list } if self.parameters.get('vserver') is not None: # vserser is empty for cluster params['owner.name'] = self.parameters['vserver'] if 'set_password' in self.parameters: params['password'] = self.parameters['set_password'] if 'lock_user' in self.parameters: params['locked'] = self.parameters['lock_user'] dummy, error = self.restApi.post(api, params) if error: self.module.fail_json(msg='Error while creating user: %s' % error) def create_user(self, application): """ creates the user for the given application and authentication_method :param: application: application to grant access to """ user_create = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-create', **{'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'], 'application': application, 'authentication-method': self.parameters['authentication_method'], 'role-name': self.parameters.get('role_name')}) if self.parameters.get('set_password') is not None: user_create.add_new_child('password', self.parameters.get('set_password')) if self.parameters.get('authentication_method') == 'usm': if self.parameters.get('remote_switch_ipaddress') is not None: user_create.add_new_child('remote-switch-ipaddress', self.parameters.get('remote_switch_ipaddress')) snmpv3_login_info = netapp_utils.zapi.NaElement('snmpv3-login-info') if self.parameters.get('authentication_password') is not None: snmpv3_login_info.add_new_child('authentication-password', self.parameters['authentication_password']) if self.parameters.get('authentication_protocol') is not None: snmpv3_login_info.add_new_child('authentication-protocol', self.parameters['authentication_protocol']) if self.parameters.get('engine_id') is not None: snmpv3_login_info.add_new_child('engine-id', self.parameters['engine_id']) if self.parameters.get('privacy_password') is not None: snmpv3_login_info.add_new_child('privacy-password', self.parameters['privacy_password']) if self.parameters.get('privacy_protocol') is not None: snmpv3_login_info.add_new_child('privacy-protocol', self.parameters['privacy_protocol']) user_create.add_child_elem(snmpv3_login_info) try: self.server.invoke_successfully(user_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def lock_unlock_user_rest(self, useruuid, username, value=None): data = { 'locked': value } params = { 'name': self.parameters['name'], 'owner.uuid': useruuid, } api = "security/accounts/%s/%s" % (useruuid, username) dummy, error = self.restApi.patch(api, data, params) if error: self.module.fail_json(msg='Error while locking/unlocking user: %s' % error) def lock_given_user(self): """ locks the user :return: True if user locked False if lock user is not performed :rtype: bool """ user_lock = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-lock', **{'vserver': self.parameters['vserver'], 'user-name': self.parameters['name']}) try: self.server.invoke_successfully(user_lock, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error locking user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def unlock_given_user(self): """ unlocks the user :return: True if user unlocked False if unlock user is not performed :rtype: bool """ user_unlock = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-unlock', **{'vserver': self.parameters['vserver'], 'user-name': self.parameters['name']}) try: self.server.invoke_successfully(user_unlock, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == '13114': return False else: self.module.fail_json(msg='Error unlocking user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) return True def delete_user_rest(self): uuid, username = self.get_user_rest() data = {} params = { 'name': username, 'owner.uuid': uuid, } api = "security/accounts/%s/%s" % (uuid, username) dummy, error = self.restApi.delete(api, data, params) if error: self.module.fail_json(msg='Error while deleting user : %s' % error) def delete_user(self, application): """ deletes the user for the given application and authentication_method :param: application: application to grant access to """ user_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-delete', **{'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'], 'application': application, 'authentication-method': self.parameters['authentication_method']}) try: self.server.invoke_successfully(user_delete, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error removing user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) @staticmethod def is_repeated_password(message): return message.startswith('New password must be different than last 6 passwords.') \ or message.startswith('New password must be different from last 6 passwords.') \ or message.startswith('New password must be different than the old password.') \ or message.startswith('New password must be different from the old password.') def change_password_rest(self, useruuid, username): data = { 'password': self.parameters['set_password'], } params = { 'name': self.parameters['name'], 'owner.uuid': useruuid, } api = "security/accounts/%s/%s" % (useruuid, username) dummy, error = self.restApi.patch(api, data, params) if error: if 'message' in error and self.is_repeated_password(error['message']): # if the password is reused, assume idempotency return False else: self.module.fail_json(msg='Error while updating user password: %s' % error) return True def change_password(self): """ Changes the password :return: True if password updated False if password is not updated :rtype: bool """ # self.server.set_vserver(self.parameters['vserver']) modify_password = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-modify-password', **{ 'new-password': str(self.parameters.get('set_password')), 'user-name': self.parameters['name']}) try: self.server.invoke_successfully(modify_password, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == '13114': return False # if the user give the same password, instead of returning an error, return ok if to_native(error.code) == '13214' and self.is_repeated_password(error.message): return False self.module.fail_json(msg='Error setting password for user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) self.server.set_vserver(None) return True def modify_apps_rest(self, useruuid, username, apps=None): app_list = list() if apps is not None: for app in apps: mydict = { "application": app, "authentication_methods": self.parameters['authentication_method'].split(), } app_list.append(mydict) data = { 'role.name': self.parameters['role_name'], 'applications': app_list } params = { 'name': self.parameters['name'], 'owner.uuid': useruuid, } api = "security/accounts/%s/%s" % (useruuid, username) dummy, error = self.restApi.patch(api, data, params) if error: self.module.fail_json(msg='Error while modifying user details: %s' % error) def modify_user(self, application): """ Modify user """ user_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-modify', **{'vserver': self.parameters['vserver'], 'user-name': self.parameters['name'], 'application': application, 'authentication-method': self.parameters['authentication_method'], 'role-name': self.parameters.get('role_name')}) try: self.server.invoke_successfully(user_modify, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying user %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def apply_for_rest(self): current = self.get_user_rest() if current is not None: uuid, name = current current = self.get_user_details_rest(name, uuid) cd_action = self.na_helper.get_cd_action(current, self.parameters) modify_decision = self.na_helper.get_modified_attributes(current, self.parameters) if current and 'lock_user' not in current: # REST does not return locked if password is not set if cd_action is None and self.parameters.get('lock_user') is not None: if self.parameters.get('set_password') is None: self.module.fail_json(msg='Error: cannot modify lock state if password is not set.') modify_decision['lock_user'] = self.parameters['lock_user'] self.na_helper.changed = True if self.na_helper.changed and not self.module.check_mode: if cd_action == 'create': self.create_user_rest(self.parameters['applications']) elif cd_action == 'delete': self.delete_user_rest() elif modify_decision: if 'role_name' in modify_decision or 'applications' in modify_decision: self.modify_apps_rest(uuid, name, self.parameters['applications']) if cd_action is None and self.parameters.get('set_password') is not None: # if check_mode, don't attempt to change the password, but assume it would be changed if self.module.check_mode or self.change_password_rest(uuid, name): self.na_helper.changed = True if cd_action is None and self.na_helper.changed and not self.module.check_mode: # lock/unlock actions require password to be set if modify_decision and 'lock_user' in modify_decision: self.lock_unlock_user_rest(uuid, name, self.parameters['lock_user']) self.module.exit_json(changed=self.na_helper.changed) def apply(self): if self.use_rest: self.apply_for_rest() else: create_delete_decision = {} modify_decision = {} netapp_utils.ems_log_event("na_ontap_user", self.server) for application in self.parameters['applications']: current = self.get_user(application) if current is not None: current['lock_user'] = self.na_helper.get_value_for_bool(True, current['lock_user']) cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is not None: create_delete_decision[application] = cd_action else: modify_decision[application] = self.na_helper.get_modified_attributes(current, self.parameters) if not create_delete_decision and self.parameters.get('state') == 'present': if self.parameters.get('set_password') is not None: self.na_helper.changed = True if self.na_helper.changed: if self.module.check_mode: pass else: for application in create_delete_decision: if create_delete_decision[application] == 'create': self.create_user(application) elif create_delete_decision[application] == 'delete': self.delete_user(application) lock_user = False for application in modify_decision: if 'role_name' in modify_decision[application]: self.modify_user(application) if 'lock_user' in modify_decision[application]: lock_user = True if not create_delete_decision and self.parameters.get('set_password') is not None: # if change password return false nothing has changed so we need to set changed to False self.na_helper.changed = self.change_password() # NOTE: unlock has to be performed after setting a password if lock_user: if self.parameters.get('lock_user'): self.lock_given_user() else: self.unlock_given_user() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapStorageFailover(object): """ Enable or disable storage failover for a specified node """ def __init__(self): """ Initialize the Ontap Storage failover 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'), node_name=dict(required=True, type='str'), )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) # set up variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters['state'] == 'present': self.parameters['is_enabled'] = True else: self.parameters['is_enabled'] = False self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: 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_storage_failover(self): """ get the storage failover for a given node :return: dict of is-enabled: true if enabled is true None if not """ if self.use_rest: return_value = None api = "cluster/nodes" query = { 'fields': 'uuid,ha.enabled', 'name': self.parameters['node_name'] } message, error = self.rest_api.get(api, query) records, error = rrh.check_for_0_or_1_records(api, message, error) if error is None and records is not None: return_value = { 'uuid': message['records'][0]['uuid'], 'is_enabled': message['records'][0]['ha']['enabled'] } if error: self.module.fail_json(msg=error) if not records: error = "REST API did not return failover details for node %s" % (self.parameters['node_name']) self.module.fail_json(msg=error) return return_value else: storage_failover_get_iter = netapp_utils.zapi.NaElement('cf-status') storage_failover_get_iter.add_new_child('node', self.parameters['node_name']) try: result = self.server.invoke_successfully(storage_failover_get_iter, True) return_value = {'is_enabled': self.na_helper.get_value_for_bool(True, result.get_child_content('is-enabled'))} except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error getting storage failover info for node %s: %s' % ( self.parameters['node_name'], to_native(error)), exception=traceback.format_exc()) return return_value def modify_storage_failover(self, current): """ Modifies storage failover for a specified node """ if self.use_rest: api = "cluster/nodes" query = { 'uuid': current['uuid'], 'return_timeout': 60 # Timeout added to allow for return messages } body = {'ha': {'enabled': self.parameters['is_enabled']}} message, error = self.rest_api.patch(api, body, query) if error: self.module.fail_json(msg=error) else: if self.parameters['state'] == 'present': cf_service = 'cf-service-enable' else: cf_service = 'cf-service-disable' storage_failover_modify = netapp_utils.zapi.NaElement(cf_service) storage_failover_modify.add_new_child('node', self.parameters['node_name']) try: result = self.server.invoke_successfully(storage_failover_modify, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying storage failover for node %s: %s' % ( self.parameters['node_name'], to_native(error)), exception=traceback.format_exc()) def ems_log_event(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) return netapp_utils.ems_log_event("na_ontap_storage_failover", cserver) def apply(self): if not self.use_rest: self.ems_log_event() current = self.get_storage_failover() modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if not self.module.check_mode: self.modify_storage_failover(current) 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'), is_ipv4_link_local=dict(required=False, type='bool', default=None), 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' ]), failover_group=dict(required=False, type='str'), 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'), service_policy=dict(required=False, type='str', default=None))) self.module = AnsibleModule( argument_spec=self.argument_spec, mutually_exclusive=[['subnet_name', 'address'], ['subnet_name', 'netmask'], ['is_ipv4_link_local', 'address'], ['is_ipv4_link_local', 'netmask'], ['is_ipv4_link_local', 'subnet_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) def get_interface(self): """ Return details about the interface :param: name : 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('_', '-'), } if interface_attributes.get_child_by_name('is-auto-revert'): return_value['is_auto_revert'] = True if interface_attributes[ 'is-auto-revert'] == 'true' else False if interface_attributes.get_child_by_name('failover-group'): return_value['failover_group'] = interface_attributes[ 'failover-group'] 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']) if interface_attributes.get_child_by_name('service-policy'): return_value['service_policy'] = interface_attributes[ 'service-policy'] 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('failover_group') is not None: options['failover-group'] = parameters['failover_group'] 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']) if parameters.get('is_ipv4_link_local') is not None: options['is-ipv4-link-local'] = 'true' if parameters[ 'is_ipv4_link_local'] else 'false' if parameters.get('service_policy') is not None: options['service-policy'] = parameters['service_policy'] 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: if self.parameters.get('is_ipv4_link_local') is not None: if not self.parameters.get('is_ipv4_link_local'): 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: # msg: "Error Creating interface ansible_interface: NetApp API failed. Reason - 17:A LIF with the same name already exists" if to_native(exc.code) == "17": self.na_helper.changed = False else: 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 ''' # Checking to see if autosupport_log() can be ran as this is a post cluster setup request. try: self.autosupport_log() except netapp_utils.zapi.NaApiError as error: # Error 13003 denotes cluster does not exist. It happens when running operations on a node not in cluster. if to_native(error.code) == "13003": pass else: self.module.fail_json( msg='Error calling autosupport_log(): %s' % (to_native(error)), exception=traceback.format_exc()) current = self.get_interface() 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 NetAppOntapLUN(object): ''' create, modify, delete LUN ''' 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'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), size=dict(type='int'), size_unit=dict(default='gb', choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'], type='str'), force_resize=dict(default=False, type='bool'), force_remove=dict(default=False, type='bool'), force_remove_fenced=dict(default=False, type='bool'), flexvol_name=dict(required=True, type='str'), vserver=dict(required=True, type='str'), ostype=dict(required=False, type='str', default='image'), qos_policy_group=dict(required=False, type='str'), space_reserve=dict(required=False, type='bool', default=True), space_allocation=dict(required=False, type='bool', default=False), use_exact_size=dict(required=False, type='bool', default=True), )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) # set up state variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get('size') is not None: self.parameters['size'] *= netapp_utils.POW2_BYTE_MAP[self.parameters['size_unit']] 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_luns(self): """ Return list of LUNs matching vserver and volume names. :return: list of LUNs in XML format. :rtype: list """ luns = [] tag = None while True: lun_info = netapp_utils.zapi.NaElement('lun-get-iter') if tag: lun_info.add_new_child('tag', tag, True) query_details = netapp_utils.zapi.NaElement('lun-info') query_details.add_new_child('vserver', self.parameters['vserver']) query_details.add_new_child('volume', self.parameters['flexvol_name']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) lun_info.add_child_elem(query) result = self.server.invoke_successfully(lun_info, True) if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1: attr_list = result.get_child_by_name('attributes-list') luns.extend(attr_list.get_children()) tag = result.get_child_content('next-tag') if tag is None: break return luns def get_lun_details(self, name, lun): """ Extract LUN details, from XML to python dict :return: Details about the lun :rtype: dict """ return_value = dict(name=name) return_value['size'] = int(lun.get_child_content('size')) bool_attr_map = { 'is-space-alloc-enabled': 'space_allocation', 'is-space-reservation-enabled': 'space_reserve' } for attr in bool_attr_map: value = lun.get_child_content(attr) if value is not None: return_value[bool_attr_map[attr]] = self.na_helper.get_value_for_bool(True, value) str_attr_map = { 'qos-policy-group': 'qos_policy_group', 'multiprotocol-type': 'ostype' } for attr in str_attr_map: value = lun.get_child_content(attr) if value is not None: return_value[str_attr_map[attr]] = value # Find out if the lun is attached attached_to = None lun_id = None if lun.get_child_content('mapped') == 'true': lun_map_list = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-map-list-info', **{'path': lun.get_child_content('path')}) result = self.server.invoke_successfully( lun_map_list, enable_tunneling=True) igroups = result.get_child_by_name('initiator-groups') if igroups: for igroup_info in igroups.get_children(): igroup = igroup_info.get_child_content( 'initiator-group-name') attached_to = igroup lun_id = igroup_info.get_child_content('lun-id') return_value.update({ 'attached_to': attached_to, 'lun_id': lun_id }) return return_value def get_lun(self, name): """ Return details about the LUN :return: Details about the lun :rtype: dict """ return_value = dict() luns = self.get_luns() # The LUNs have been extracted. # Find the specified lun and extract details. for lun in luns: path = lun.get_child_content('path') _rest, _splitter, found_name = path.rpartition('/') if found_name == name: return_value = self.get_lun_details(found_name, lun) break return return_value if return_value else None def create_lun(self): """ Create LUN with requested name and size """ path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) options = {'path': path, 'size': str(self.parameters['size']), 'ostype': self.parameters['ostype'], 'space-reservation-enabled': str(self.parameters['space_reserve']), 'space-allocation-enabled': str(self.parameters['space_allocation']), 'use-exact-size': str(self.parameters['use_exact_size'])} if self.parameters.get('qos_policy_group') is not None: options['qos-policy-group'] = self.parameters['qos_policy_group'] lun_create = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-create-by-size', **options) try: self.server.invoke_successfully(lun_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error provisioning lun %s of size %s: %s" % (self.parameters['name'], self.parameters['size'], to_native(exc)), exception=traceback.format_exc()) def delete_lun(self): """ Delete requested LUN """ path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) lun_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-destroy', **{'path': path, 'force': str(self.parameters['force_remove']), 'destroy-fenced-lun': str(self.parameters['force_remove_fenced'])}) try: self.server.invoke_successfully(lun_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error deleting lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) def resize_lun(self): """ Resize requested LUN. :return: True if LUN was actually re-sized, false otherwise. :rtype: bool """ path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) lun_resize = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-resize', **{'path': path, 'size': str(self.parameters['size']), 'force': str(self.parameters['force_resize'])}) try: self.server.invoke_successfully(lun_resize, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: if to_native(exc.code) == "9042": # Error 9042 denotes the new LUN size being the same as the # old LUN size. This happens when there's barely any difference # in the two sizes. For example, from 8388608 bytes to # 8194304 bytes. This should go away if/when the default size # requested/reported to/from the controller is changed to a # larger unit (MB/GB/TB). return False else: self.module.fail_json(msg="Error resizing lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) return True def set_lun_value(self, key, value): key_to_zapi = dict( qos_policy_group=('lun-set-qos-policy-group', 'qos-policy-group'), space_allocation=('lun-set-space-alloc', 'enable'), space_reserve=('lun-set-space-reservation-info', 'enable') ) if key in key_to_zapi: zapi, option = key_to_zapi[key] else: self.module.fail_json(msg="option %s cannot be modified to %s" % (key, value)) path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) options = dict(path=path) if option == 'enable': options[option] = self.na_helper.get_value_for_bool(False, value) else: options[option] = value lun_set = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options) try: self.server.invoke_successfully(lun_set, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error setting lun option %s: %s" % (key, to_native(exc)), exception=traceback.format_exc()) return def modify_lun(self, modify): """ update LUN properties (except size or name) """ for key, value in modify.items(): self.set_lun_value(key, value) def rename_lun(self): """ rename LUN """ path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['from_name']) new_path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) lun_move = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-move', **{'path': path, 'new-path': new_path}) try: self.server.invoke_successfully(lun_move, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error moving lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) def apply(self): results = dict() netapp_utils.ems_log_event("na_ontap_lun", self.server) current = self.get_lun(self.parameters['name']) cd_action = self.na_helper.get_cd_action(current, self.parameters) modify, rename = None, None from_name = self.parameters.get('from_name') if cd_action == 'create' and from_name is not None: # create by renaming existing LUN, if it really exists old_lun = self.get_lun(from_name) rename = self.na_helper.is_rename_action(old_lun, current) if rename is None: self.module.fail_json(msg="Error renaming lun: %s does not exist" % self.parameters['from_name']) if rename: current = old_lun cd_action = None if cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes(current, self.parameters) results['modify'] = dict(modify) if cd_action == 'create' and self.parameters.get('size') is None: self.module.fail_json(msg="size is a required parameter for create.") if self.na_helper.changed and not self.module.check_mode: if cd_action == 'create': self.create_lun() elif cd_action == 'delete': self.delete_lun() else: if rename: self.rename_lun() size_changed = False if 'size' in modify: # Ensure that size was actually changed. Please # read notes in 'resize_lun' function for details. size_changed = self.resize_lun() modify.pop('size') if modify: # we already handled rename if required modify.pop('name', None) self.modify_lun(modify) if not modify and not rename: # size may not have changed self.na_helper.changed = size_changed results['changed'] = self.na_helper.changed self.module.exit_json(**results)
class NetAppONTAPNVMe(object): """ Class with NVMe service 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'), status_admin=dict(required=False, type='bool'))) 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_nvme(self): """ Get current nvme details :return: dict if nvme exists, None otherwise """ nvme_get = netapp_utils.zapi.NaElement('nvme-get-iter') query = { 'query': { 'nvme-target-service-info': { 'vserver': self.parameters['vserver'] } } } nvme_get.translate_struct(query) try: result = self.server.invoke_successfully(nvme_get, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching nvme 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: attributes_list = result.get_child_by_name('attributes-list') nvme_info = attributes_list.get_child_by_name( 'nvme-target-service-info') return_value = { 'status_admin': nvme_info.get_child_content('is-available') } return return_value return None def create_nvme(self): """ Create NVMe service """ nvme_create = netapp_utils.zapi.NaElement('nvme-create') if self.parameters.get('status_admin') is not None: options = {'is-available': self.parameters['status_admin']} nvme_create.translate_struct(options) try: self.server.invoke_successfully(nvme_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating nvme for vserver %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) def delete_nvme(self): """ Delete NVMe service """ nvme_delete = netapp_utils.zapi.NaElement('nvme-delete') try: self.server.invoke_successfully(nvme_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting nvme for vserver %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) def modify_nvme(self, status=None): """ Modify NVMe service """ if status is None: status = self.parameters['status_admin'] options = {'is-available': status} nvme_modify = netapp_utils.zapi.NaElement('nvme-modify') nvme_modify.translate_struct(options) try: self.server.invoke_successfully(nvme_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying nvme for vserver %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to NVMe service """ netapp_utils.ems_log_event("na_ontap_nvme", self.server) current = self.get_nvme() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.parameters.get('status_admin') is not None: self.parameters[ 'status_admin'] = self.na_helper.get_value_for_bool( False, self.parameters['status_admin']) 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_nvme() elif cd_action == 'delete': # NVMe status_admin needs to be down before deleting it self.modify_nvme('false') self.delete_nvme() elif modify: self.modify_nvme() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapVolumeSnaplock(object): '''Class with volume operations''' def __init__(self): '''Initialize module parameters''' self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( name=dict(required=True, type='str'), vserver=dict(required=True, type='str'), default_retention_period=dict(required=False, type='str'), maximum_retention_period=dict(required=False, type='str'), minimum_retention_period=dict(required=False, type='str'), autocommit_period=dict(required=False, type='str'), is_volume_append_mode_enabled=dict(required=False, type='bool'), )) 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") self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def get_volume_snaplock_attrs(self): """ Return volume-get-snaplock-attrs query results :param vol_name: name of the volume :return: dict of the volume snaplock attrs """ volume_snaplock = netapp_utils.zapi.NaElement( 'volume-get-snaplock-attrs') volume_snaplock.add_new_child('volume', self.parameters['name']) try: result = self.server.invoke_successfully(volume_snaplock, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching snaplock attributes for volume %s : %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) return_value = None if result.get_child_by_name('snaplock-attrs'): volume_snaplock_attributes = result['snaplock-attrs'][ 'snaplock-attrs-info'] return_value = { 'autocommit_period': volume_snaplock_attributes['autocommit-period'], 'default_retention_period': volume_snaplock_attributes['default-retention-period'], 'is_volume_append_mode_enabled': self.na_helper.get_value_for_bool( True, volume_snaplock_attributes['is-volume-append-mode-enabled'] ), 'maximum_retention_period': volume_snaplock_attributes['maximum-retention-period'], 'minimum_retention_period': volume_snaplock_attributes['minimum-retention-period'], } return return_value def set_volume_snaplock_attrs(self, modify): '''Set ONTAP volume snaplock attributes''' volume_snaplock_obj = netapp_utils.zapi.NaElement( 'volume-set-snaplock-attrs') volume_snaplock_obj.add_new_child('volume', self.parameters['name']) if modify.get('autocommit_period') is not None: volume_snaplock_obj.add_new_child( 'autocommit-period', self.parameters['autocommit_period']) if modify.get('default_retention_period') is not None: volume_snaplock_obj.add_new_child( 'default-retention-period', self.parameters['default_retention_period']) if modify.get('is_volume_append_mode_enabled') is not None: volume_snaplock_obj.add_new_child( 'is-volume-append-mode-enabled', self.na_helper.get_value_for_bool( False, self.parameters['is_volume_append_mode_enabled'])) if modify.get('maximum_retention_period') is not None: volume_snaplock_obj.add_new_child( 'maximum-retention-period', self.parameters['maximum_retention_period']) if modify.get('minimum_retention_period') is not None: volume_snaplock_obj.add_new_child( 'minimum-retention-period', self.parameters['minimum_retention_period']) try: self.server.invoke_successfully(volume_snaplock_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error setting snaplock attributes for volume %s : %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def apply(self): netapp_utils.ems_log_event("na_ontap_volume_snaplock", self.server) current, modify = self.get_volume_snaplock_attrs(), None modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: self.set_volume_snaplock_attrs(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPasup(object): """Class with autosupport 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'), node_name=dict(required=True, type='str'), transport=dict(required=False, type='str', choices=['smtp', 'http', 'https']), noteto=dict(required=False, type='list'), post_url=dict(reuired=False, type='str'), support=dict(required=False, type='bool'), mail_hosts=dict(required=False, type='list'), from_address=dict(required=False, type='str'), partner_addresses=dict(required=False, type='list'), to_addresses=dict(required=False, type='list'), proxy_url=dict(required=False, type='str'), hostname_in_subject=dict(required=False, type='bool'), )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=False ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # present or absent requires modifying state to enabled or disabled self.parameters['service_state'] = 'started' if self.parameters['state'] == 'present' else 'stopped' 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 = { 'node_name': 'node-name', 'transport': 'transport', 'post_url': 'post-url', 'from_address': 'from', 'proxy_url': 'proxy-url' } self.na_helper.zapi_list_keys = { 'noteto': ('noteto', 'mail-address'), 'mail_hosts': ('mail-hosts', 'string'), 'partner_addresses': ('partner-address', 'mail-address'), 'to_addresses': ('to', 'mail-address'), } self.na_helper.zapi_bool_keys = { 'support': 'is-support-enabled', 'hostname_in_subject': 'is-node-in-subject' } def get_autosupport_config(self): """ Invoke zapi - get current autosupport details :return: dict() """ asup_details = netapp_utils.zapi.NaElement('autosupport-config-get') asup_details.add_new_child('node-name', self.parameters['node_name']) asup_info = dict() try: result = self.server.invoke_successfully(asup_details, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='%s' % to_native(error), exception=traceback.format_exc()) # zapi invoke successful asup_attr_info = result.get_child_by_name('attributes').get_child_by_name('autosupport-config-info') asup_info['service_state'] = 'started' if asup_attr_info['is-enabled'] == 'true' else 'stopped' for item_key, zapi_key in self.na_helper.zapi_string_keys.items(): asup_info[item_key] = asup_attr_info[zapi_key] for item_key, zapi_key in self.na_helper.zapi_bool_keys.items(): asup_info[item_key] = self.na_helper.get_value_for_bool(from_zapi=True, value=asup_attr_info[zapi_key]) for item_key, zapi_key in self.na_helper.zapi_list_keys.items(): parent, dummy = zapi_key asup_info[item_key] = self.na_helper.get_value_for_list(from_zapi=True, zapi_parent=asup_attr_info.get_child_by_name(parent) ) return asup_info def modify_autosupport_config(self, modify): """ Invoke zapi - modify autosupport config @return: NaElement object / FAILURE with an error_message """ asup_details = {'node-name': self.parameters['node_name']} if modify.get('service_state'): asup_details['is-enabled'] = 'true' if modify.get('service_state') == 'started' else 'false' asup_config = netapp_utils.zapi.NaElement('autosupport-config-modify') for item_key in modify: if item_key in self.na_helper.zapi_string_keys: zapi_key = self.na_helper.zapi_string_keys.get(item_key) asup_details[zapi_key] = modify[item_key] elif item_key in self.na_helper.zapi_bool_keys: zapi_key = self.na_helper.zapi_bool_keys.get(item_key) asup_details[zapi_key] = self.na_helper.get_value_for_bool(from_zapi=False, value=modify[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) asup_config.add_child_elem(self.na_helper.get_value_for_list(from_zapi=False, zapi_parent=parent_key, zapi_child=child_key, data=modify.get(item_key))) asup_config.translate_struct(asup_details) try: return self.server.invoke_successfully(asup_config, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='%s' % 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_autosupport", cserver) def apply(self): """ Apply action to autosupport """ current = self.get_autosupport_config() modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: self.modify_autosupport_config(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapFpolicyPolicy(): def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), name=dict(required=True, type='str'), allow_privileged_access=dict(required=False, type='bool'), engine=dict(required=False, type='str'), events=dict(required=True, type='list', elements='str'), is_mandatory=dict(required=False, type='bool'), is_passthrough_read_enabled=dict(required=False, type='bool'), privileged_user_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) self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() if not self.use_rest: if not netapp_utils.has_netapp_lib(): self.module.fail_json(msg=netapp_utils.netapp_lib_is_required()) else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) def get_fpolicy_policy(self): """ Check if FPolicy policy exists, if it exists get the current state of the policy. """ if self.use_rest: api = "/private/cli/vserver/fpolicy/policy" query = { 'vserver': self.parameters['vserver'], 'policy-name': self.parameters['name'], 'fields': 'events,engine,allow-privileged-access,is-mandatory,is-passthrough-read-enabled,privileged-user-name' } message, error = self.rest_api.get(api, query) if error: self.module.fail_json(msg=error) if len(message.keys()) == 0: return None if 'records' in message and len(message['records']) == 0: return None if 'records' not in message: error = "Unexpected response in get_fpolicy_policy from %s: %s" % (api, repr(message)) self.module.fail_json(msg=error) return_value = { 'vserver': message['records'][0]['vserver'], 'name': message['records'][0]['policy_name'], 'events': message['records'][0]['events'], 'allow_privileged_access': message['records'][0]['allow_privileged_access'], 'engine': message['records'][0]['engine'], 'is_mandatory': message['records'][0]['is_mandatory'], 'is_passthrough_read_enabled': message['records'][0]['is_passthrough_read_enabled'] } if 'privileged_user_name' in message['records'][0]: return_value['privileged_user_name'] = message['records'][0]['privileged_user_name'] return return_value else: return_value = None fpolicy_policy_obj = netapp_utils.zapi.NaElement('fpolicy-policy-get-iter') fpolicy_policy_config = netapp_utils.zapi.NaElement('fpolicy-policy-info') fpolicy_policy_config.add_new_child('policy-name', self.parameters['name']) fpolicy_policy_config.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(fpolicy_policy_config) fpolicy_policy_obj.add_child_elem(query) try: result = self.server.invoke_successfully(fpolicy_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error searching for fPolicy policy %s on vserver %s: %s' % (self.parameters['name'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('attributes-list'): fpolicy_policy_attributes = result['attributes-list']['fpolicy-policy-info'] events = [] if fpolicy_policy_attributes.get_child_by_name('events'): for event in fpolicy_policy_attributes.get_child_by_name('events').get_children(): events.append(event.get_content()) return_value = { 'vserver': fpolicy_policy_attributes.get_child_content('vserver'), 'name': fpolicy_policy_attributes.get_child_content('policy-name'), 'events': events, 'allow_privileged_access': self.na_helper.get_value_for_bool( from_zapi=True, value=fpolicy_policy_attributes.get_child_content('allow-privileged-access')), 'engine': fpolicy_policy_attributes.get_child_content('engine-name'), 'is_mandatory': self.na_helper.get_value_for_bool( from_zapi=True, value=fpolicy_policy_attributes.get_child_content('is-mandatory')), 'is_passthrough_read_enabled': self.na_helper.get_value_for_bool( from_zapi=True, value=fpolicy_policy_attributes.get_child_content('is-passthrough-read-enabled')), 'privileged_user_name': fpolicy_policy_attributes.get_child_content('privileged-user-name') } return return_value def create_fpolicy_policy(self): """ Create an FPolicy policy. """ if self.use_rest: api = "/private/cli/vserver/fpolicy/policy" body = { 'vserver': self.parameters['vserver'], 'policy-name': self.parameters['name'], 'events': self.parameters['events'] } for parameter in ('engine', 'allow_privileged_access', 'is_mandatory', 'is_passthrough_read_enabled', 'privileged_user_name'): if parameter in self.parameters: body[parameter.replace('_', '-')] = self.parameters[parameter] dummy, error = self.rest_api.post(api, body) if error: self.module.fail_json(msg=error) else: fpolicy_policy_obj = netapp_utils.zapi.NaElement('fpolicy-policy-create') fpolicy_policy_obj.add_new_child('policy-name', self.parameters['name']) if 'is_mandatory' in self.parameters: fpolicy_policy_obj.add_new_child('is-mandatory', self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters['is_mandatory'])) if 'engine' in self.parameters: fpolicy_policy_obj.add_new_child('engine-name', self.parameters['engine']) if 'allow_privileged_access' in self.parameters: fpolicy_policy_obj.add_new_child( 'allow-privileged-access', self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters['allow_privileged_access']) ) if 'is_passthrough_read_enabled' in self.parameters: fpolicy_policy_obj.add_new_child( 'is-passthrough-read-enabled', self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters['is_passthrough_read_enabled']) ) events_obj = netapp_utils.zapi.NaElement('events') for event in self.parameters['events']: events_obj.add_new_child('event-name', event) fpolicy_policy_obj.add_child_elem(events_obj) if 'privileged_user_name' in self.parameters: fpolicy_policy_obj.add_new_child('privileged-user-name', self.parameters['privileged_user_name']) try: self.server.invoke_successfully(fpolicy_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating fPolicy policy %s on vserver %s: %s' % (self.parameters['name'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc() ) def modify_fpolicy_policy(self, modify): """ Modify an FPolicy policy. """ if self.use_rest: api = "/private/cli/vserver/fpolicy/policy" query = {'vserver': self.parameters['vserver']} query['policy-name'] = self.parameters['name'] dummy, error = self.rest_api.patch(api, modify, query) if error: self.module.fail_json(msg=error) else: fpolicy_policy_obj = netapp_utils.zapi.NaElement('fpolicy-policy-modify') fpolicy_policy_obj.add_new_child('policy-name', self.parameters['name']) if 'is_mandatory' in self.parameters: fpolicy_policy_obj.add_new_child('is-mandatory', self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters['is_mandatory'])) if 'engine' in self.parameters: fpolicy_policy_obj.add_new_child('engine-name', self.parameters['engine']) if 'allow_privileged_access' in self.parameters: fpolicy_policy_obj.add_new_child( 'allow-privileged-access', self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters['allow_privileged_access']) ) if 'is_passthrough_read_enabled' in self.parameters: fpolicy_policy_obj.add_new_child( 'is-passthrough-read-enabled', self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters['is_passthrough_read_enabled']) ) events_obj = netapp_utils.zapi.NaElement('events') for event in self.parameters['events']: events_obj.add_new_child('event-name', event) fpolicy_policy_obj.add_child_elem(events_obj) if 'privileged_user_name' in self.parameters: fpolicy_policy_obj.add_new_child('privileged-user-name', self.parameters['privileged_user_name']) try: self.server.invoke_successfully(fpolicy_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying fPolicy policy %s on vserver %s: %s' % (self.parameters['name'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc() ) def delete_fpolicy_policy(self): """ Delete an FPolicy policy. """ if self.use_rest: api = "/private/cli/vserver/fpolicy/policy" body = { 'vserver': self.parameters['vserver'], 'policy-name': self.parameters['name'] } dummy, error = self.rest_api.delete(api, body) if error: self.module.fail_json(msg=error) else: fpolicy_policy_obj = netapp_utils.zapi.NaElement('fpolicy-policy-delete') fpolicy_policy_obj.add_new_child('policy-name', self.parameters['name']) try: self.server.invoke_successfully(fpolicy_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting fPolicy policy %s on vserver %s: %s' % (self.parameters['name'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc() ) def apply(self): if not self.use_rest: netapp_utils.ems_log_event("na_ontap_fpolicy_policy", self.server) current = self.get_fpolicy_policy() 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 not self.module.check_mode: if cd_action == 'create': self.create_fpolicy_policy() elif cd_action == 'delete': self.delete_fpolicy_policy() elif modify: self.modify_fpolicy_policy(modify) self.module.exit_json(changed=self.na_helper.changed)