class NetAppOntapObjectStoreConfig(object): ''' object initialize and class methods ''' def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(name=dict(required=True, type='str'), state=dict(required=False, choices=['present', 'absent'], default='present'), provider_type=dict(required=False, type='str'), server=dict(required=False, type='str'), container=dict(required=False, type='str'), access_key=dict(required=False, type='str'), secret_password=dict(required=False, type='str', no_log=True))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # API should be used for ONTAP 9.6 or higher, Zapi for lower version self.restApi = OntapRestAPI(self.module) if self.restApi.is_rest(): self.use_rest = True else: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) def get_aggr_object_store(self): """ Fetch details if object store config exists. :return: Dictionary of current details if object store config found None if object store config is not found """ if self.use_rest: data = {'fields': 'uuid,name', 'name': self.parameters['name']} api = "cloud/targets" 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: aggr_object_store_get_iter = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-object-store-config-get', **{'object-store-name': self.parameters['name']}) result = None try: result = self.server.invoke_successfully( aggr_object_store_get_iter, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: # Error 15661 denotes an object store not being found. if to_native(error.code) == "15661": pass else: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) return result def create_aggr_object_store(self): """ Create aggregate object store config :return: None """ required_keys = set( ['provider_type', 'server', 'container', 'access_key']) if not required_keys.issubset(set(self.parameters.keys())): self.module.fail_json( msg= 'Error provisioning object store %s: one of the following parameters are missing ' '%s' % (self.parameters['name'], ', '.join(required_keys))) if self.use_rest: data = { 'name': self.parameters['name'], 'provider_type': self.parameters['provider_type'], 'server': self.parameters['server'], 'container': self.parameters['container'], 'access_key': self.parameters['access_key'], 'owner': 'fabricpool' } if self.parameters.get('secret_password'): data['secret_password'] = self.parameters['secret_password'] api = "cloud/targets" message, error = self.restApi.post(api, data) if error: self.module.fail_json(msg=error) else: options = { 'object-store-name': self.parameters['name'], 'provider-type': self.parameters['provider_type'], 'server': self.parameters['server'], 's3-name': self.parameters['container'], 'access-key': self.parameters['access_key'] } if self.parameters.get('secret_password'): options['secret-password'] = self.parameters['secret_password'] object_store_create = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-object-store-config-create', **options) try: self.server.invoke_successfully(object_store_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error provisioning object store config %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_aggr_object_store(self, uuid=None): """ Delete aggregate object store config :return: None """ if self.use_rest: api = "cloud/targets/" data = {'uuid': uuid} message, error = self.restApi.delete(api, data) if error: self.module.fail_json(msg=error) else: object_store_destroy = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-object-store-config-delete', **{'object-store-name': self.parameters['name']}) try: self.server.invoke_successfully(object_store_destroy, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error removing object store config %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver) def apply(self): """ Apply action to the object store config :return: None """ uuid = None if not self.use_rest: self.asup_log_for_cserver("na_ontap_object_store_config") current = self.get_aggr_object_store() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_aggr_object_store() elif cd_action == 'delete': if self.use_rest: uuid = current['uuid'] self.delete_aggr_object_store(uuid) self.module.exit_json(changed=self.na_helper.changed)
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 NetAppOntapIpspace(object): '''Class with ipspace operations''' 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'), from_name=dict(required=False, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.rest_api = OntapRestAPI(self.module) if self.rest_api.is_rest(): self.use_rest = True else: if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) return def ipspace_get_iter(self, name): """ Return net-ipspaces-get-iter query results :param name: Name of the ipspace :return: NaElement if ipspace found, None otherwise """ ipspace_get_iter = netapp_utils.zapi.NaElement('net-ipspaces-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'net-ipspaces-info', **{'ipspace': name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) ipspace_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully(ipspace_get_iter, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: # Error 14636 denotes an ipspace does not exist # Error 13073 denotes an ipspace not found if to_native(error.code) == "14636" or to_native( error.code) == "13073": return None else: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) return result def get_ipspace(self, name=None): """ Fetch details if ipspace exists :param name: Name of the ipspace to be fetched :return: Dictionary of current details if ipspace found None if ipspace is not found """ if name is None: name = self.parameters['name'] if self.use_rest: api = 'network/ipspaces' params = None message, error = self.rest_api.get(api, params) 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 from %s: %s" % (api, repr(message)) self.module.fail_json(msg=error) for record in message['records']: if record['name'] == name: return record return None else: ipspace_get = self.ipspace_get_iter(name) if (ipspace_get and ipspace_get.get_child_by_name('num-records') and int(ipspace_get.get_child_content('num-records')) >= 1): current_ipspace = dict() attr_list = ipspace_get.get_child_by_name('attributes-list') attr = attr_list.get_child_by_name('net-ipspaces-info') current_ipspace['name'] = attr.get_child_content('ipspace') return current_ipspace return None def create_ipspace(self): """ Create ipspace :return: None """ if self.use_rest: api = 'network/ipspaces' params = {'name': self.parameters['name']} dummy, error = self.rest_api.post(api, params) if error: self.module.fail_json(msg=error) else: ipspace_create = netapp_utils.zapi.NaElement.create_node_with_children( 'net-ipspaces-create', **{'ipspace': self.parameters['name']}) try: self.server.invoke_successfully(ipspace_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error provisioning ipspace %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_ipspace(self): """ Destroy ipspace :return: None """ if self.use_rest: current = self.get_ipspace() if current is not None: uuid = current['uuid'] api = 'network/ipspaces/' + uuid dummy, error = self.rest_api.delete(api) if error: self.module.fail_json(msg=error) else: ipspace_destroy = netapp_utils.zapi.NaElement.create_node_with_children( 'net-ipspaces-destroy', **{'ipspace': self.parameters['name']}) try: self.server.invoke_successfully(ipspace_destroy, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error removing ipspace %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_ipspace(self): """ Rename an ipspace :return: Nothing """ if self.use_rest: current = self.get_ipspace(self.parameters['from_name']) if current is None: self.module.fail_json(msg="Error renaming ipspace %s" % (self.parameters['from_name'])) uuid = current['uuid'] api = 'network/ipspaces/' + uuid params = {'name': self.parameters['name']} dummy, error = self.rest_api.patch(api, params) if error: self.module.fail_json(msg=error) else: ipspace_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'net-ipspaces-rename', **{ 'ipspace': self.parameters['from_name'], 'new-name': self.parameters['name'] }) try: self.server.invoke_successfully(ipspace_rename, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error renaming ipspace %s: %s" % (self.parameters['from_name'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to the ipspace :return: Nothing """ current = self.get_ipspace() # rename and create are mutually exclusive rename, cd_action = None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_ipspace(self.parameters['from_name']), current) if rename is None: self.module.fail_json( msg="Error renaming: ipspace %s does not exist" % self.parameters['from_name']) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_ipspace() elif cd_action == 'create': self.create_ipspace() elif cd_action == 'delete': self.delete_ipspace() 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 NetAppONTAPFirewallPolicy(object): 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'), allow_list=dict(required=False, type='list', elements='str'), policy=dict(required=False, type='str'), service=dict(required=False, type='str', choices=[ 'dns', 'http', 'https', 'ndmp', 'ndmps', 'ntp', 'portmap', 'rsh', 'snmp', 'ssh', 'telnet' ]), vserver=dict(required=False, type="str"), enable=dict(required=False, type="str", choices=['enable', 'disable']), logging=dict(required=False, type="str", choices=['enable', 'disable']), node=dict(required=False, type="str"))) self.module = AnsibleModule( argument_spec=self.argument_spec, required_together=(['policy', 'service', 'vserver'], ['enable', 'node']), supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) if HAS_IPADDRESS_LIB is False: self.module.fail_json( msg="the python ipaddress lib is required for this module") return def validate_ip_addresses(self): ''' Validate if the given IP address is a network address (i.e. it's host bits are set to 0) ONTAP doesn't validate if the host bits are set, and hence doesn't add a new address unless the IP is from a different network. So this validation allows the module to be idempotent. :return: None ''' for ip in self.parameters['allow_list']: # create an IPv4 object for current IP address if sys.version_info[0] >= 3: ip_addr = str(ip) else: ip_addr = unicode(ip) # pylint: disable=undefined-variable # get network address from netmask, throw exception if address is not a network address try: ipaddress.ip_network(ip_addr) except ValueError as exc: self.module.fail_json( msg= 'Error: Invalid IP address value for allow_list parameter.' 'Please specify a network address without host bits set: %s' % (to_native(exc))) def get_firewall_policy(self): """ Get a firewall policy :return: returns a firewall policy object, or returns False if there are none """ net_firewall_policy_obj = netapp_utils.zapi.NaElement( "net-firewall-policy-get-iter") attributes = { 'query': { 'net-firewall-policy-info': self.firewall_policy_attributes() } } net_firewall_policy_obj.translate_struct(attributes) try: result = self.server.invoke_successfully(net_firewall_policy_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error getting firewall policy %s:%s" % (self.parameters['policy'], 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') policy_info = attributes_list.get_child_by_name( 'net-firewall-policy-info') ips = self.na_helper.get_value_for_list( from_zapi=True, zapi_parent=policy_info.get_child_by_name('allow-list')) return {'service': policy_info['service'], 'allow_list': ips} return None def create_firewall_policy(self): """ Create a firewall policy for given vserver :return: None """ net_firewall_policy_obj = netapp_utils.zapi.NaElement( "net-firewall-policy-create") net_firewall_policy_obj.translate_struct( self.firewall_policy_attributes()) if self.parameters.get('allow_list'): self.validate_ip_addresses() net_firewall_policy_obj.add_child_elem( self.na_helper.get_value_for_list( from_zapi=False, zapi_parent='allow-list', zapi_child='ip-and-mask', data=self.parameters['allow_list'])) try: self.server.invoke_successfully(net_firewall_policy_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error creating Firewall Policy: %s" % (to_native(error)), exception=traceback.format_exc()) def destroy_firewall_policy(self): """ Destroy a Firewall Policy from a vserver :return: None """ net_firewall_policy_obj = netapp_utils.zapi.NaElement( "net-firewall-policy-destroy") net_firewall_policy_obj.translate_struct( self.firewall_policy_attributes()) try: self.server.invoke_successfully(net_firewall_policy_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error destroying Firewall Policy: %s" % (to_native(error)), exception=traceback.format_exc()) def modify_firewall_policy(self, modify): """ Modify a firewall Policy on a vserver :return: none """ self.validate_ip_addresses() net_firewall_policy_obj = netapp_utils.zapi.NaElement( "net-firewall-policy-modify") net_firewall_policy_obj.translate_struct( self.firewall_policy_attributes()) net_firewall_policy_obj.add_child_elem( self.na_helper.get_value_for_list(from_zapi=False, zapi_parent='allow-list', zapi_child='ip-and-mask', data=modify['allow_list'])) try: self.server.invoke_successfully(net_firewall_policy_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error modifying Firewall Policy: %s" % (to_native(error)), exception=traceback.format_exc()) def firewall_policy_attributes(self): return { 'policy': self.parameters['policy'], 'service': self.parameters['service'], 'vserver': self.parameters['vserver'], } def get_firewall_config_for_node(self): """ Get firewall configuration on the node :return: dict() with firewall config details """ if self.parameters.get('logging'): if self.parameters.get('node') is None: self.module.fail_json( msg= 'Error: Missing parameter \'node\' to modify firewall logging' ) net_firewall_config_obj = netapp_utils.zapi.NaElement( "net-firewall-config-get") net_firewall_config_obj.add_new_child('node-name', self.parameters['node']) try: result = self.server.invoke_successfully(net_firewall_config_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error getting Firewall Configuration: %s" % (to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('attributes'): firewall_info = result['attributes'].get_child_by_name( 'net-firewall-config-info') return { 'enable': self.change_status_to_bool( firewall_info.get_child_content('is-enabled'), to_zapi=False), 'logging': self.change_status_to_bool( firewall_info.get_child_content('is-logging'), to_zapi=False) } return None def modify_firewall_config(self, modify): """ Modify the configuration of a firewall on node :return: None """ net_firewall_config_obj = netapp_utils.zapi.NaElement( "net-firewall-config-modify") net_firewall_config_obj.add_new_child('node-name', self.parameters['node']) if modify.get('enable'): net_firewall_config_obj.add_new_child( 'is-enabled', self.change_status_to_bool(self.parameters['enable'])) if modify.get('logging'): net_firewall_config_obj.add_new_child( 'is-logging', self.change_status_to_bool(self.parameters['logging'])) try: self.server.invoke_successfully(net_firewall_config_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error modifying Firewall Config: %s" % (to_native(error)), exception=traceback.format_exc()) def change_status_to_bool(self, input, to_zapi=True): if to_zapi: return 'true' if input == 'enable' else 'false' else: return 'enable' if input == 'true' else 'disable' 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_firewall_policy", cserver) def apply(self): self.autosupport_log() cd_action, modify, modify_config = None, None, None if self.parameters.get('policy'): current = self.get_firewall_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.parameters.get('node'): current_config = self.get_firewall_config_for_node() # firewall config for a node is always present, we cannot create or delete a firewall on a node modify_config = self.na_helper.get_modified_attributes( current_config, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_firewall_policy() elif cd_action == 'delete': self.destroy_firewall_policy() else: if modify: self.modify_firewall_policy(modify) if modify_config: self.modify_firewall_config(modify_config) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPExportPolicy(object): """ Class with export policy methods """ 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'), from_name=dict(required=False, type='str', default=None), 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.restApi = OntapRestAPI(self.module) if self.restApi.is_rest(): self.use_rest = True else: 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_export_policy(self, name=None, uuid=None): """ 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 """ if name is None: name = self.parameters['name'] if self.use_rest: params = {'fields': 'name', 'name': name, 'svm.uuid': uuid} api = 'protocols/nfs/export-policies/' message, error = self.restApi.get(api, params) if error is not None: self.module.fail_json(msg="Error on fetching export policy: %s" % error) if message['num_records'] > 0: return {'policy-name': message['records'][0]['name']} else: return None else: export_policy_iter = netapp_utils.zapi.NaElement('export-policy-get-iter') export_policy_info = netapp_utils.zapi.NaElement('export-policy-info') export_policy_info.add_new_child('policy-name', name) export_policy_info.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(export_policy_info) export_policy_iter.add_child_elem(query) result = self.server.invoke_successfully(export_policy_iter, True) return_value = None # check if query returns the expected export-policy if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: export_policy = result.get_child_by_name('attributes-list').get_child_by_name('export-policy-info').get_child_by_name('policy-name') return_value = { 'policy-name': export_policy } return return_value def create_export_policy(self, uuid=None): """ Creates an export policy """ if self.use_rest: params = {'name': self.parameters['name'], 'svm.uuid': uuid} api = 'protocols/nfs/export-policies' message, error = self.restApi.post(api, params) if error is not None: self.module.fail_json(msg="Error on creating export policy: %s" % error) else: 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 on creating export-policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_export_policy(self, policy_id=None): """ Delete export-policy """ if self.use_rest: params = {} api = 'protocols/nfs/export-policies/' + str(policy_id) message, error = self.restApi.delete(api, params) if error is not None: self.module.fail_json(msg=" Error on deleting export policy: %s" % error) else: export_policy_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'export-policy-destroy', **{'policy-name': self.parameters['name'], }) try: self.server.invoke_successfully(export_policy_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error on deleting export-policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_export_policy(self, policy_id=None): """ Rename the export-policy. """ if self.use_rest: params = {'name': self.parameters['name']} api = 'protocols/nfs/export-policies/' + str(policy_id) message, error = self.restApi.patch(api, params) if error is not None: self.module.fail_json(msg="Error on renaming export policy: %s" % error) else: export_policy_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'export-policy-rename', **{'policy-name': self.parameters['from_name'], 'new-policy-name': self.parameters['name']}) try: self.server.invoke_successfully(export_policy_rename, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error on renaming export-policy %s:%s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def get_export_policy_id(self, name=None): """ Get a export policy's id :return: id of the export policy """ if name is None: name = self.parameters['name'] params = {'fields': 'id', 'svm.name': self.parameters['vserver'], 'name': name } api = 'protocols/nfs/export-policies' message, error = self.restApi.get(api, params) if error is not None: self.module.fail_json(msg="%s" % error) if message['num_records'] == 0: return None else: return message['records'][0]['id'] def get_export_policy_svm_uuid(self): """ Get a svm's uuid :return: uuid of the svm """ params = {'svm.name': self.parameters['vserver']} api = 'protocols/nfs/export-policies' message, error = self.restApi.get(api, params) if error is not None: self.module.fail_json(msg="%s" % error) return message['records'][0]['svm']['uuid'] def apply(self): """ Apply action to export-policy """ policy_id, uuid = None, None cd_action, rename = None, None if not self.use_rest: netapp_utils.ems_log_event("na_ontap_export_policy", self.server) if self.use_rest: uuid = self.get_export_policy_svm_uuid() if self.parameters.get('from_name'): policy_id = self.get_export_policy_id(self.parameters['from_name']) else: policy_id = self.get_export_policy_id() current = self.get_export_policy(uuid=uuid) if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action(self.get_export_policy(self.parameters['from_name']), current) if rename is None: self.module.fail_json(msg="Error renaming: export policy %s does not exist" % self.parameters['from_name']) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_export_policy(policy_id=policy_id) elif cd_action == 'create': self.create_export_policy(uuid=uuid) elif cd_action == 'delete': self.delete_export_policy(policy_id=policy_id) self.module.exit_json(changed=self.na_helper.changed)
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 NetAppOntapUserRole(object): 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'), command_directory_name=dict(required=True, type='str'), access_level=dict(required=False, type='str', default='all', choices=['none', 'readonly', 'all']), vserver=dict(required=True, type='str'), query=dict(required=False, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) def get_role(self): """ Checks if the role exists for specific command-directory-name. :return: True if role found False if role is not found :rtype: bool """ options = {'vserver': self.parameters['vserver'], 'role-name': self.parameters['name'], 'command-directory-name': self.parameters['command_directory_name']} security_login_role_get_iter = netapp_utils.zapi.NaElement( 'security-login-role-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-role-info', **options) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) security_login_role_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully( security_login_role_get_iter, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: # Error 16031 denotes a role not being found. if to_native(e.code) == "16031": return None # Error 16039 denotes command directory not found. elif to_native(e.code) == "16039": return None else: self.module.fail_json(msg='Error getting role %s: %s' % (self.name, to_native(e)), exception=traceback.format_exc()) if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1): role_info = result.get_child_by_name('attributes-list').get_child_by_name('security-login-role-info') result = { 'name': role_info['role-name'], 'access_level': role_info['access-level'], 'command_directory_name': role_info['command-directory-name'], 'query': role_info['role-query'] } return result return None def create_role(self): options = {'vserver': self.parameters['vserver'], 'role-name': self.parameters['name'], 'command-directory-name': self.parameters['command_directory_name'], 'access-level': self.parameters['access_level']} if self.parameters.get('query'): options['role-query'] = self.parameters['query'] role_create = netapp_utils.zapi.NaElement.create_node_with_children('security-login-role-create', **options) try: self.server.invoke_successfully(role_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating role %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_role(self): role_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'security-login-role-delete', **{'vserver': self.parameters['vserver'], 'role-name': self.parameters['name'], 'command-directory-name': self.parameters['command_directory_name']}) try: self.server.invoke_successfully(role_delete, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error removing role %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_role(self, modify): options = {'vserver': self.parameters['vserver'], 'role-name': self.parameters['name'], 'command-directory-name': self.parameters['command_directory_name']} if 'access_level' in modify.keys(): options['access-level'] = self.parameters['access_level'] if 'query' in modify.keys(): options['role-query'] = self.parameters['query'] role_modify = netapp_utils.zapi.NaElement.create_node_with_children('security-login-role-modify', **options) try: self.server.invoke_successfully(role_modify, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying role %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def apply(self): self.asup_log_for_cserver('na_ontap_user_role') current = self.get_role() cd_action = self.na_helper.get_cd_action(current, self.parameters) # if desired state specify empty quote query and current query is None, set desired query to None. # otherwise na_helper.get_modified_attributes will detect a change. if self.parameters.get('query') == '' and current is not None: if current['query'] is None: self.parameters['query'] = 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_role() elif cd_action == 'delete': self.delete_role() elif modify: self.modify_role(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 """ netapp_utils.ems_log_event(event_name, self.server)
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 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 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 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 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 NetAppONTAPClusterPeer(object): """ Class with cluster peer methods """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), source_intercluster_lifs=dict( required=False, type='list', elements='str', aliases=['source_intercluster_lif']), dest_intercluster_lifs=dict(required=False, type='list', elements='str', aliases=['dest_intercluster_lif' ]), passphrase=dict(required=False, type='str', no_log=True), dest_hostname=dict(required=True, type='str'), dest_username=dict(required=False, type='str'), dest_password=dict(required=False, type='str', no_log=True), source_cluster_name=dict(required=False, type='str'), dest_cluster_name=dict(required=False, type='str'), ipspace=dict(required=False, type='str'), encryption_protocol_proposed=dict(required=False, type='str', choices=['tls_psk', 'none']))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_together=[[ 'source_intercluster_lifs', 'dest_intercluster_lifs' ]], required_if=[('state', 'absent', [ 'source_cluster_name', 'dest_cluster_name' ])], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) # set destination server connection self.module.params['hostname'] = self.parameters['dest_hostname'] if self.parameters.get('dest_username'): self.module.params['username'] = self.parameters[ 'dest_username'] if self.parameters.get('dest_password'): self.module.params['password'] = self.parameters[ 'dest_password'] self.dest_server = netapp_utils.setup_na_ontap_zapi( module=self.module) # reset to source host connection for asup logs self.module.params['hostname'] = self.parameters['hostname'] self.module.params['username'] = self.parameters['username'] self.module.params['password'] = self.parameters['password'] def cluster_peer_get_iter(self, cluster): """ Compose NaElement object to query current source cluster using peer-cluster-name and peer-addresses parameters :param cluster: type of cluster (source or destination) :return: NaElement object for cluster-get-iter with query """ cluster_peer_get = netapp_utils.zapi.NaElement('cluster-peer-get-iter') query = netapp_utils.zapi.NaElement('query') cluster_peer_info = netapp_utils.zapi.NaElement('cluster-peer-info') if cluster == 'source': peer_lifs, peer_cluster = 'dest_intercluster_lifs', 'dest_cluster_name' else: peer_lifs, peer_cluster = 'source_intercluster_lifs', 'source_cluster_name' if self.parameters.get(peer_lifs): peer_addresses = netapp_utils.zapi.NaElement('peer-addresses') for peer in self.parameters.get(peer_lifs): peer_addresses.add_new_child('remote-inet-address', peer) cluster_peer_info.add_child_elem(peer_addresses) if self.parameters.get(peer_cluster): cluster_peer_info.add_new_child('cluster-name', self.parameters[peer_cluster]) query.add_child_elem(cluster_peer_info) cluster_peer_get.add_child_elem(query) return cluster_peer_get def cluster_peer_get(self, cluster): """ Get current cluster peer info :param cluster: type of cluster (source or destination) :return: Dictionary of current cluster peer details if query successful, else return None """ cluster_peer_get_iter = self.cluster_peer_get_iter(cluster) result, cluster_info = None, dict() if cluster == 'source': server = self.server else: server = self.dest_server try: result = server.invoke_successfully(cluster_peer_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching cluster peer %s: %s' % (self.parameters['dest_cluster_name'], to_native(error)), exception=traceback.format_exc()) # return cluster peer details if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) >= 1: cluster_peer_info = result.get_child_by_name( 'attributes-list').get_child_by_name('cluster-peer-info') cluster_info['cluster_name'] = cluster_peer_info.get_child_content( 'cluster-name') peers = cluster_peer_info.get_child_by_name('peer-addresses') cluster_info['peer-addresses'] = [ peer.get_content() for peer in peers.get_children() ] return cluster_info return None def cluster_peer_delete(self, cluster): """ Delete a cluster peer on source or destination For source cluster, peer cluster-name = destination cluster name and vice-versa :param cluster: type of cluster (source or destination) :return: """ if cluster == 'source': server, peer_cluster_name = self.server, self.parameters[ 'dest_cluster_name'] else: server, peer_cluster_name = self.dest_server, self.parameters[ 'source_cluster_name'] cluster_peer_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-peer-delete', **{'cluster-name': peer_cluster_name}) try: server.invoke_successfully(cluster_peer_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting cluster peer %s: %s' % (peer_cluster_name, to_native(error)), exception=traceback.format_exc()) def cluster_peer_create(self, cluster): """ Create a cluster peer on source or destination For source cluster, peer addresses = destination inter-cluster LIFs and vice-versa :param cluster: type of cluster (source or destination) :return: None """ cluster_peer_create = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-peer-create') if self.parameters.get('passphrase') is not None: cluster_peer_create.add_new_child('passphrase', self.parameters['passphrase']) peer_addresses = netapp_utils.zapi.NaElement('peer-addresses') if cluster == 'source': server, peer_address = self.server, self.parameters[ 'dest_intercluster_lifs'] else: server, peer_address = self.dest_server, self.parameters[ 'source_intercluster_lifs'] for each in peer_address: peer_addresses.add_new_child('remote-inet-address', each) cluster_peer_create.add_child_elem(peer_addresses) if self.parameters.get('encryption_protocol_proposed') is not None: cluster_peer_create.add_new_child( 'encryption-protocol-proposed', self.parameters['encryption_protocol_proposed']) if self.parameters.get('ipspace') is not None: cluster_peer_create.add_new_child('ipspace-name', self.parameters['ipspace']) try: server.invoke_successfully(cluster_peer_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating cluster peer %s: %s' % (peer_address, to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to cluster peer :return: None """ self.asup_log_for_cserver("na_ontap_cluster_peer") source = self.cluster_peer_get('source') destination = self.cluster_peer_get('destination') source_action = self.na_helper.get_cd_action(source, self.parameters) destination_action = self.na_helper.get_cd_action( destination, self.parameters) self.na_helper.changed = False # create only if expected cluster peer relation is not present on both source and destination clusters if source_action == 'create' and destination_action == 'create': if not self.module.check_mode: self.cluster_peer_create('source') self.cluster_peer_create('destination') self.na_helper.changed = True # delete peer relation in cluster where relation is present else: if source_action == 'delete': if not self.module.check_mode: self.cluster_peer_delete('source') self.na_helper.changed = True if destination_action == 'delete': if not self.module.check_mode: self.cluster_peer_delete('destination') self.na_helper.changed = True self.module.exit_json(changed=self.na_helper.changed) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver)
class NetAppOntapNtfsSd(object): """ Creates, Modifies and Destroys a NTFS security descriptor """ def __init__(self): """ Initialize the Ontap NTFS Security Descriptor 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'), name=dict(required=True, type='str'), owner=dict(required=False, type='str'), group=dict(required=False, type='str'), control_flags_raw=dict(required=False, type='int'), )) 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 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_ntfs_sd(self): ntfs_sd_entry, result = None, None ntfs_sd_get_iter = netapp_utils.zapi.NaElement( 'file-directory-security-ntfs-get-iter') ntfs_sd_info = netapp_utils.zapi.NaElement( 'file-directory-security-ntfs') ntfs_sd_info.add_new_child('vserver', self.parameters['vserver']) ntfs_sd_info.add_new_child('ntfs-sd', self.parameters['name']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(ntfs_sd_info) ntfs_sd_get_iter.add_child_elem(query) try: result = self.server.invoke_successfully(ntfs_sd_get_iter, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching NTFS security descriptor %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: attributes_list = result.get_child_by_name('attributes-list') ntfs_sd = attributes_list.get_child_by_name( 'file-directory-security-ntfs') ntfs_sd_entry = { 'vserver': ntfs_sd.get_child_content('vserver'), 'name': ntfs_sd.get_child_content('ntfs-sd'), 'owner': ntfs_sd.get_child_content('owner'), 'group': ntfs_sd.get_child_content('group'), 'control_flags_raw': ntfs_sd.get_child_content('control-flags-raw'), } if ntfs_sd_entry.get('control_flags_raw'): ntfs_sd_entry['control_flags_raw'] = int( ntfs_sd_entry['control_flags_raw']) return ntfs_sd_entry return None def add_ntfs_sd(self): """ Adds a new NTFS security descriptor """ ntfs_sd_obj = netapp_utils.zapi.NaElement( "file-directory-security-ntfs-create") ntfs_sd_obj.add_new_child("ntfs-sd", self.parameters['name']) if self.parameters.get('control_flags_raw'): ntfs_sd_obj.add_new_child( "control-flags-raw", str(self.parameters['control_flags_raw'])) if self.parameters.get('owner'): ntfs_sd_obj.add_new_child("owner", self.parameters['owner']) if self.parameters.get('group'): ntfs_sd_obj.add_new_child("group", self.parameters['group']) if 'owner' not in self.parameters.keys( ) and 'group' not in self.parameters.keys(): self.module.fail_json( msg= 'Either owner or group must be specified when creating NTFS security descriptor.' ) try: self.server.invoke_successfully(ntfs_sd_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating NTFS security descriptor %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def remove_ntfs_sd(self): """ Deletes a NTFS security descriptor """ ntfs_sd_obj = netapp_utils.zapi.NaElement( "file-directory-security-ntfs-delete") ntfs_sd_obj.add_new_child("ntfs-sd", self.parameters['name']) try: self.server.invoke_successfully(ntfs_sd_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting NTFS security descriptor %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_ntfs_sd(self): """ Modifies a NTFS security descriptor """ ntfs_sd_obj = netapp_utils.zapi.NaElement( "file-directory-security-ntfs-modify") ntfs_sd_obj.add_new_child("ntfs-sd", self.parameters['name']) if self.parameters.get('control_flags_raw'): ntfs_sd_obj.add_new_child( 'control-flags-raw', str(self.parameters['control_flags_raw'])) if self.parameters.get('owner'): ntfs_sd_obj.add_new_child('owner', self.parameters['owner']) if self.parameters.get('group'): ntfs_sd_obj.add_new_child('group', self.parameters['group']) try: self.server.invoke_successfully(ntfs_sd_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying NTFS security descriptor %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def apply(self): netapp_utils.ems_log_event("na_ontap_ntfs_sd", self.server) current, modify = self.get_ntfs_sd(), None cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.add_ntfs_sd() elif cd_action == 'delete': self.remove_ntfs_sd() elif modify: self.modify_ntfs_sd() self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPNsswitch(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'), database_type=dict(required=True, type='str', choices=['hosts', 'group', 'passwd', 'netgroup', 'namemap']), sources=dict(required=False, type='list') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) def get_name_service_switch(self): """ get current name service switch config :return: dict of current name service switch """ nss_iter = netapp_utils.zapi.NaElement('nameservice-nsswitch-get-iter') nss_info = netapp_utils.zapi.NaElement('namservice-nsswitch-config-info') db_type = netapp_utils.zapi.NaElement('nameservice-database') db_type.set_content(self.parameters['database_type']) query = netapp_utils.zapi.NaElement('query') nss_info.add_child_elem(db_type) query.add_child_elem(nss_info) nss_iter.add_child_elem(query) result = self.server.invoke_successfully(nss_iter, True) return_value = None if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) == 1: nss_sources = result.get_child_by_name('attributes-list').get_child_by_name( 'namservice-nsswitch-config-info').get_child_by_name('nameservice-sources') sources = [sources.get_content() for sources in nss_sources.get_children()] return_value = { 'sources': sources } return return_value def create_name_service_switch(self): """ create name service switch config :return: None """ nss_create = netapp_utils.zapi.NaElement('nameservice-nsswitch-create') nss_create.add_new_child('nameservice-database', self.parameters['database_type']) nss_sources = netapp_utils.zapi.NaElement('nameservice-sources') nss_create.add_child_elem(nss_sources) for source in self.parameters['sources']: nss_sources.add_new_child('nss-source-type', source.strip()) try: self.server.invoke_successfully(nss_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error on creating name service switch config on vserver %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) def delete_name_service_switch(self): """ delete name service switch :return: None """ nss_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'nameservice-nsswitch-destroy', **{'nameservice-database': self.parameters['database_type']}) try: self.server.invoke_successfully(nss_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error on deleting name service switch config on vserver %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) def modify_name_service_switch(self, modify): """ modify name service switch :param modify: dict of modify attributes :return: None """ nss_modify = netapp_utils.zapi.NaElement('nameservice-nsswitch-modify') nss_modify.add_new_child('nameservice-database', self.parameters['database_type']) nss_sources = netapp_utils.zapi.NaElement('nameservice-sources') nss_modify.add_child_elem(nss_sources) if 'sources' in modify: for source in self.parameters['sources']: nss_sources.add_new_child('nss-source-type', source.strip()) try: self.server.invoke_successfully(nss_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error on modifying name service switch config on vserver %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) def apply(self): netapp_utils.ems_log_event("na_ontap_name_service_switch", self.server) current = self.get_name_service_switch() cd_action, modify = None, None 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_name_service_switch() elif cd_action == 'delete': self.delete_name_service_switch() elif modify: self.modify_name_service_switch(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPNVMENamespace(object): """ Class with NVME namespace methods """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), ostype=dict( required=False, type='str', choices=['windows', 'linux', 'vmware', 'xen', 'hyper_v']), path=dict(required=True, type='str'), size=dict(required=False, type='int'), size_unit=dict(default='b', choices=[ 'bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb' ], type='str'), block_size=dict(required=False, choices=[512, 4096], type='int'))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[('state', 'present', ['ostype', 'size'])], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get('size'): self.parameters['size'] = 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_namespace(self): """ Get current namespace details :return: dict if namespace exists, None otherwise """ namespace_get = netapp_utils.zapi.NaElement('nvme-namespace-get-iter') query = { 'query': { 'nvme-namespace-info': { 'path': self.parameters['path'], 'vserver': self.parameters['vserver'] } } } namespace_get.translate_struct(query) try: result = self.server.invoke_successfully(namespace_get, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching namespace info: %s' % to_native(error), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: return result return None def create_namespace(self): """ Create a NVME Namespace """ options = { 'path': self.parameters['path'], 'ostype': self.parameters['ostype'], 'size': self.parameters['size'] } if self.parameters.get('block_size'): options['block-size'] = self.parameters['block_size'] namespace_create = netapp_utils.zapi.NaElement('nvme-namespace-create') namespace_create.translate_struct(options) try: self.server.invoke_successfully(namespace_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating namespace for path %s: %s' % (self.parameters.get('path'), to_native(error)), exception=traceback.format_exc()) def delete_namespace(self): """ Delete a NVME Namespace """ options = {'path': self.parameters['path']} namespace_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'nvme-namespace-delete', **options) try: self.server.invoke_successfully(namespace_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting namespace for path %s: %s' % (self.parameters.get('path'), to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to NVME Namespace """ netapp_utils.ems_log_event("na_ontap_nvme_namespace", self.server) current = self.get_namespace() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_namespace() elif cd_action == 'delete': self.delete_namespace() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapMccipMediator(object): """ Mediator object for Add/Remove/Display """ 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'), mediator_address=dict(required=True, type='str'), mediator_user=dict(required=True, type='str'), mediator_password=dict(required=True, type='str', no_log=True), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.restApi = OntapRestAPI(self.module) self.use_rest = self.restApi.is_rest() if not self.use_rest: self.module.fail_json( msg="na_ontap_metrocluster only supports REST API") def add_mediator(self): """ Adds an ONTAP Mediator to MCC configuration """ api = 'cluster/mediators' params = { 'ip_address': self.parameters['mediator_address'], 'password': self.parameters['mediator_password'], 'user': self.parameters['mediator_user'] } message, error = self.restApi.post(api, params) if error: self.module.fail_json(msg=error) def remove_mediator(self, current_uuid): """ Removes the ONTAP Mediator from MCC configuration """ api = 'cluster/mediators' params = { 'ip_address': self.parameters['mediator_address'], 'password': self.parameters['mediator_password'], 'user': self.parameters['mediator_user'], 'uuid': current_uuid } message, error = self.restApi.delete(api, params) if error: self.module.fail_json(msg=error) def get_mediator(self): """ Determine if the MCC configuration has added an ONTAP Mediator """ api = "cluster/mediators" message, error = self.restApi.get(api, None) if error: self.module.fail_json(msg=error) if message['num_records'] > 0: return message['records'][0]['uuid'] return None def apply(self): """ Apply action to MCC Mediator """ current = self.get_mediator() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.add_mediator() elif cd_action == 'delete': self.remove_mediator(current) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapIgroupInitiator(object): def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), names=dict(required=True, type='list', aliases=['name']), initiator_group=dict(required=True, type='str'), vserver=dict(required=True, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def get_initiators(self): """ Get the existing list of initiators from an igroup :rtype: list() or None """ igroup_info = netapp_utils.zapi.NaElement('igroup-get-iter') attributes = dict( query={ 'initiator-group-info': { 'initiator-group-name': self.parameters['initiator_group'], 'vserver': self.parameters['vserver'] } }) igroup_info.translate_struct(attributes) result, current = None, [] try: result = self.server.invoke_successfully(igroup_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching igroup info %s: %s' % (self.parameters['initiator_group'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: igroup_info = result.get_child_by_name( 'attributes-list').get_child_by_name('initiator-group-info') if igroup_info.get_child_by_name('initiators') is not None: current = [ initiator['initiator-name'] for initiator in igroup_info['initiators'].get_children() ] return current def modify_initiator(self, initiator_name, zapi): """ Add or remove an initiator to/from an igroup """ options = { 'initiator-group-name': self.parameters['initiator_group'], 'initiator': initiator_name } initiator_modify = netapp_utils.zapi.NaElement.create_node_with_children( zapi, **options) try: self.server.invoke_successfully(initiator_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying igroup initiator %s: %s' % (initiator_name, to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): netapp_utils.ems_log_event("na_ontap_igroup_initiator", self.server) def apply(self): self.autosupport_log() initiators = self.get_initiators() for initiator in self.parameters['names']: present = None if initiator in initiators: present = True cd_action = self.na_helper.get_cd_action(present, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.modify_initiator(initiator, 'igroup-add') elif cd_action == 'delete': self.modify_initiator(initiator, 'igroup-remove') self.module.exit_json(changed=self.na_helper.changed)
class 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 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 NetAppOntapClusterHA(object): """ object initialize and class methods """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def modify_cluster_ha(self, configure): """ Enable or disable HA on cluster :return: None """ cluster_ha_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-ha-modify', **{'ha-configured': configure}) try: self.server.invoke_successfully(cluster_ha_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying cluster HA to %s: %s' % (configure, to_native(error)), exception=traceback.format_exc()) def get_cluster_ha_enabled(self): """ Get current cluster HA details :return: dict if enabled, None if disabled """ cluster_ha_get = netapp_utils.zapi.NaElement('cluster-ha-get') try: result = self.server.invoke_successfully(cluster_ha_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching cluster HA details', exception=traceback.format_exc()) cluster_ha_info = result.get_child_by_name( 'attributes').get_child_by_name('cluster-ha-info') if cluster_ha_info.get_child_content('ha-configured') == 'true': return {'ha-configured': True} return None def apply(self): """ Apply action to cluster HA """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_cluster_ha", cserver) current = self.get_cluster_ha_enabled() cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create': self.modify_cluster_ha("true") elif cd_action == 'delete': self.modify_cluster_ha("false") self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSVM(object): def __init__(self): self.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'), from_name=dict(required=False, type='str'), root_volume=dict(type='str'), root_volume_aggregate=dict(type='str'), root_volume_security_style=dict( type='str', choices=['unix', 'ntfs', 'mixed', 'unified']), allowed_protocols=dict(type='list', elements='str'), aggr_list=dict(type='list', elements='str'), ipspace=dict(type='str', required=False), snapshot_policy=dict(type='str', required=False), language=dict(type='str', required=False), subtype=dict(type='str', choices=[ 'default', 'dp_destination', 'sync_source', 'sync_destination' ]), comment=dict(type="str", required=False))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # Ontap documentation uses C.UTF-8, but actually stores as c.utf_8. if 'language' in self.parameters and self.parameters['language'].lower( ) == 'c.utf-8': self.parameters['language'] = 'c.utf_8' self.restApi = OntapRestAPI(self.module) # with REST, to force synchronous operations self.timeout = self.restApi.timeout # root volume not supported with rest api unsupported_rest_properties = [ 'root_volume', 'root_volume_aggregate', 'root_volume_security_style' ] 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 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) @staticmethod def clean_up_output(vserver_details): vserver_details['root_volume'] = None vserver_details['root_volume_aggregate'] = None vserver_details['root_volume_security_style'] = None vserver_details['aggr_list'] = [] for aggr in vserver_details['aggregates']: vserver_details['aggr_list'].append(aggr['name']) vserver_details.pop('aggregates') vserver_details['ipspace'] = vserver_details['ipspace']['name'] vserver_details['snapshot_policy'] = vserver_details[ 'snapshot_policy']['name'] vserver_details['allowed_protocols'] = [] if 'cifs' in vserver_details: if vserver_details['cifs']['enabled']: vserver_details['allowed_protocols'].append('cifs') vserver_details.pop('cifs') if 'fcp' in vserver_details: if vserver_details['fcp']['enabled']: vserver_details['allowed_protocols'].append('fcp') vserver_details.pop('fcp') if 'issi' in vserver_details: if vserver_details['iscsi']['enabled']: vserver_details['allowed_protocols'].append('iscsi') vserver_details.pop('iscsi') if 'nvme' in vserver_details: if vserver_details['nvme']['enabled']: vserver_details['allowed_protocols'].append('nvme') vserver_details.pop('nvme') if 'nfs' in vserver_details: if vserver_details['nfs']['enabled']: vserver_details['allowed_protocols'].append('nfs') vserver_details.pop('nfs') return vserver_details def get_vserver(self, vserver_name=None): """ Checks if vserver exists. :return: vserver object if vserver found None if vserver is not found :rtype: object/None """ if vserver_name is None: vserver_name = self.parameters['name'] if self.use_rest: api = 'svm/svms' params = { 'fields': 'subtype,aggregates,language,snapshot_policy,ipspace,comment,nfs,cifs,fcp,iscsi,nvme' } message, error = self.restApi.get(api, params) 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_net_route from %s: %s" % ( api, repr(message)) self.module.fail_json(msg=error) vserver_details = None for record in message['records']: if record['name'] == vserver_name: vserver_details = copy.deepcopy(record) break if vserver_details is None: return None return self.clean_up_output(vserver_details) else: vserver_info = netapp_utils.zapi.NaElement('vserver-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-info', **{'vserver-name': vserver_name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) vserver_info.add_child_elem(query) result = self.server.invoke_successfully(vserver_info, enable_tunneling=False) vserver_details = None if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1): attributes_list = result.get_child_by_name('attributes-list') vserver_info = attributes_list.get_child_by_name( 'vserver-info') aggr_list = list() ''' vserver aggr-list can be empty by default''' get_list = vserver_info.get_child_by_name('aggr-list') if get_list is not None: aggregates = get_list.get_children() for aggr in aggregates: aggr_list.append(aggr.get_content()) protocols = list() '''allowed-protocols is not empty for data SVM, but is for node SVM''' allowed_protocols = vserver_info.get_child_by_name( 'allowed-protocols') if allowed_protocols is not None: get_protocols = allowed_protocols.get_children() for protocol in get_protocols: protocols.append(protocol.get_content()) vserver_details = { 'name': vserver_info.get_child_content('vserver-name'), 'root_volume': vserver_info.get_child_content('root-volume'), 'root_volume_aggregate': vserver_info.get_child_content('root-volume-aggregate'), 'root_volume_security_style': vserver_info.get_child_content( 'root-volume-security-style'), 'subtype': vserver_info.get_child_content('vserver-subtype'), 'aggr_list': aggr_list, 'language': vserver_info.get_child_content('language'), 'snapshot_policy': vserver_info.get_child_content('snapshot-policy'), 'allowed_protocols': protocols, 'ipspace': vserver_info.get_child_content('ipspace'), 'comment': vserver_info.get_child_content('comment') } return vserver_details def create_vserver(self): if self.use_rest: api = 'svm/svms' params = {'name': self.parameters['name']} if self.parameters.get('language'): params['language'] = self.parameters['language'] if self.parameters.get('ipspace'): params['ipspace'] = self.parameters['ipspace'] if self.parameters.get('snapshot_policy'): params['snapshot_policy'] = self.parameters['snapshot_policy'] if self.parameters.get('subtype'): params['subtype'] = self.parameters['subtype'] if self.parameters.get('comment'): params['comment'] = self.parameters['comment'] if self.parameters.get('aggr_list'): params['aggregates'] = [] for aggr in self.parameters['aggr_list']: params['aggregates'].append({'name': aggr}) if self.parameters.get('allowed_protocols'): for protocol in self.parameters['allowed_protocols']: params[protocol] = {'enabled': 'true'} # for a sync operation data = {'return_timeout': self.timeout} __, error = self.restApi.post(api, params, data) if error: self.module.fail_json(msg=error) else: options = {'vserver-name': self.parameters['name']} self.add_parameter_to_dict(options, 'root_volume', 'root-volume') self.add_parameter_to_dict(options, 'root_volume_aggregate', 'root-volume-aggregate') self.add_parameter_to_dict(options, 'root_volume_security_style', 'root-volume-security-style') self.add_parameter_to_dict(options, 'language', 'language') self.add_parameter_to_dict(options, 'ipspace', 'ipspace') self.add_parameter_to_dict(options, 'snapshot_policy', 'snapshot-policy') self.add_parameter_to_dict(options, 'subtype', 'vserver-subtype') self.add_parameter_to_dict(options, 'comment', 'comment') vserver_create = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-create', **options) try: self.server.invoke_successfully(vserver_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error provisioning SVM %s: %s' % (self.parameters['name'], to_native(e)), exception=traceback.format_exc()) # add allowed-protocols, aggr-list after creation, # since vserver-create doesn't allow these attributes during creation options = dict() for key in ('allowed_protocols', 'aggr_list'): if self.parameters.get(key): options[key] = self.parameters[key] if options: self.modify_vserver(options) def delete_vserver(self, current=None): if self.use_rest: if current is None: self.module.fail_json( msg='Internal error, expecting SVM object in delete') api = 'svm/svms/%s' % current['uuid'] params = {} # for a sync operation data = {'return_timeout': self.timeout} __, error = self.restApi.delete(api, params, data) if error: self.module.fail_json(msg=error) else: vserver_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-destroy', **{'vserver-name': self.parameters['name']}) try: self.server.invoke_successfully(vserver_delete, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error deleting SVM %s: %s' % (self.parameters['name'], to_native(e)), exception=traceback.format_exc()) def rename_vserver(self, current=None): if self.use_rest: if current is None: self.module.fail_json( msg='Internal error, expecting SVM object in rename') api = 'svm/svms/%s' % current['uuid'] params = {'name': self.parameters['name']} # for a sync operation data = {'return_timeout': self.timeout} __, error = self.restApi.patch(api, params, data) if error: self.module.fail_json(msg=error) else: vserver_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'vserver-rename', **{ 'vserver-name': self.parameters['from_name'], 'new-name': self.parameters['name'] }) try: self.server.invoke_successfully(vserver_rename, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json( msg='Error renaming SVM %s: %s' % (self.parameters['from_name'], to_native(e)), exception=traceback.format_exc()) def modify_vserver(self, modify, current=None): ''' Modify vserver. :param modify: list of modify attributes :param current: with rest, SVM object to modify ''' if self.use_rest: if current is None: self.module.fail_json( msg='Internal error, expecting SVM object in modify') api = 'svm/svms/%s' % current['uuid'] for attribute in modify: if attribute == 'snapshot_policy' or attribute == 'allowed_protocols' or attribute == 'aggr_list': self.module.fail_json( msg='REST API does not support modify of %s' % attribute) # for a sync operation data = {'return_timeout': self.timeout} __, error = self.restApi.patch(api, modify, data) if error: self.module.fail_json(msg=error) else: vserver_modify = netapp_utils.zapi.NaElement('vserver-modify') vserver_modify.add_new_child('vserver-name', self.parameters['name']) for attribute in modify: if attribute == 'language': vserver_modify.add_new_child('language', self.parameters['language']) if attribute == 'snapshot_policy': vserver_modify.add_new_child( 'snapshot-policy', self.parameters['snapshot_policy']) if attribute == 'comment': vserver_modify.add_new_child('comment', self.parameters['comment']) if attribute == 'allowed_protocols': allowed_protocols = netapp_utils.zapi.NaElement( 'allowed-protocols') for protocol in self.parameters['allowed_protocols']: allowed_protocols.add_new_child('protocol', protocol) vserver_modify.add_child_elem(allowed_protocols) if attribute == 'aggr_list': aggregates = netapp_utils.zapi.NaElement('aggr-list') for aggr in self.parameters['aggr_list']: aggregates.add_new_child('aggr-name', aggr) vserver_modify.add_child_elem(aggregates) try: self.server.invoke_successfully(vserver_modify, enable_tunneling=False) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error modifying SVM %s: %s' % (self.parameters['name'], to_native(e)), exception=traceback.format_exc()) def add_parameter_to_dict(self, adict, name, key=None, tostr=False): ''' add defined parameter (not None) to adict using key. :param adict: a dictionary. :param name: name in self.parameters. :param key: key in adict. :param tostr: boolean. ''' if key is None: key = name if self.parameters.get(name) is not None: if tostr: adict[key] = str(self.parameters.get(name)) else: adict[key] = self.parameters.get(name) def apply(self): '''Call create/modify/delete operations.''' if not self.use_rest: self.asup_log_for_cserver("na_ontap_svm") current = self.get_vserver() cd_action, rename = None, None if self.parameters.get('from_name'): old_svm = self.get_vserver(self.parameters['from_name']) rename = self.na_helper.is_rename_action(old_svm, current) if rename is None: self.module.fail_json( msg='Error renaming SVM %s: no SVM with from_name %s.' % (self.parameters['name'], self.parameters['from_name'])) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes( current, self.parameters) for attribute in modify: if attribute in [ 'root_volume', 'root_volume_aggregate', 'root_volume_security_style', 'subtype', 'ipspace' ]: self.module.fail_json( msg='Error modifying SVM %s: can not modify %s.' % (self.parameters['name'], attribute)) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_vserver(old_svm) # If rename is True, cd_action is None, but modify could be true or false. if cd_action == 'create': self.create_vserver() elif cd_action == 'delete': self.delete_vserver(current) elif modify: self.modify_vserver(modify, current) 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 NetAppONTAPPortset(object): """ Methods to create or delete portset """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, type='str', default='present'), vserver=dict(required=True, type='str'), name=dict(required=True, type='str'), type=dict(required=False, type='str', choices=['fcp', 'iscsi', 'mixed']), force=dict(required=False, type='bool', default=False), ports=dict(required=False, type='list', 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) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def portset_get_iter(self): """ Compose NaElement object to query current portset using vserver, portset-name and portset-type parameters :return: NaElement object for portset-get-iter with query """ portset_get = netapp_utils.zapi.NaElement('portset-get-iter') query = netapp_utils.zapi.NaElement('query') portset_info = netapp_utils.zapi.NaElement('portset-info') portset_info.add_new_child('vserver', self.parameters['vserver']) portset_info.add_new_child('portset-name', self.parameters['name']) query.add_child_elem(portset_info) portset_get.add_child_elem(query) return portset_get def portset_get(self): """ Get current portset info :return: Dictionary of current portset details if query successful, else return None """ portset_get_iter = self.portset_get_iter() result, portset_info = None, dict() try: result = self.server.invoke_successfully(portset_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching portset %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) # return portset details if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) > 0: portset_get_info = result.get_child_by_name( 'attributes-list').get_child_by_name('portset-info') portset_info['type'] = portset_get_info.get_child_content( 'portset-type') if int(portset_get_info.get_child_content( 'portset-port-total')) > 0: ports = portset_get_info.get_child_by_name('portset-port-info') portset_info['ports'] = [ port.get_content() for port in ports.get_children() ] else: portset_info['ports'] = [] return portset_info return None def create_portset(self): """ Create a portset """ if self.parameters.get('type') is None: self.module.fail_json( msg='Error: Missing required parameter for create (type)') portset_info = netapp_utils.zapi.NaElement("portset-create") portset_info.add_new_child("portset-name", self.parameters['name']) portset_info.add_new_child("portset-type", self.parameters['type']) try: self.server.invoke_successfully(portset_info, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error creating portset %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_portset(self): """ Delete a portset """ portset_info = netapp_utils.zapi.NaElement("portset-destroy") portset_info.add_new_child("portset-name", self.parameters['name']) if self.parameters.get('force'): portset_info.add_new_child("force", str(self.parameters['force'])) try: self.server.invoke_successfully(portset_info, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error deleting portset %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def remove_ports(self, ports): """ Removes all existing ports from portset :return: None """ for port in ports: self.modify_port(port, 'portset-remove', 'removing') def add_ports(self): """ Add the list of ports to portset :return: None """ # don't add if ports is empty string if self.parameters.get('ports') == [ '' ] or self.parameters.get('ports') is None: return for port in self.parameters['ports']: self.modify_port(port, 'portset-add', 'adding') def modify_port(self, port, zapi, action): """ Add or remove an port to/from a portset """ port.strip( ) # remove leading spaces if any (eg: if user types a space after comma in initiators list) options = { 'portset-name': self.parameters['name'], 'portset-port-name': port } portset_modify = netapp_utils.zapi.NaElement.create_node_with_children( zapi, **options) try: self.server.invoke_successfully(portset_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error %s port in portset %s: %s' % (action, self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Applies action from playbook """ netapp_utils.ems_log_event("na_ontap_autosupport", self.server) current, modify = self.portset_get(), None cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': if self.parameters.get( 'type') and self.parameters['type'] != current['type']: self.module.fail_json( msg= "modify protocol(type) not supported and %s already exists in vserver %s under different type" % (self.parameters['name'], self.parameters['vserver'])) modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_portset() self.add_ports() elif cd_action == 'delete': self.delete_portset() elif modify: self.remove_ports(current['ports']) self.add_ports() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSecurityCertificates(object): ''' object initialize and class methods ''' def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( common_name=dict(required=False, type='str'), name=dict(required=False, type='str'), state=dict(required=False, choices=['present', 'absent'], default='present'), type=dict(required=False, choices=['client', 'server', 'client_ca', 'server_ca', 'root_ca']), svm=dict(required=False, type='str', aliases=['vserver']), public_certificate=dict(required=False, type='str'), private_key=dict(required=False, type='str'), signing_request=dict(required=False, type='str'), expiry_time=dict(required=False, type='str'), key_size=dict(required=False, type='int'), hash_function=dict(required=False, type='str'), intermediate_certificates=dict(required=False, type='list', elements='str'), ignore_name_if_not_supported=dict(required=False, type='bool', default=True) )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get('name') is None: if self.parameters.get('common_name') is None or self.parameters.get('type') is None: error = "'name' or ('common_name' and 'type') are required parameters." self.module.fail_json(msg=error) # ONTAP 9.6 and 9.7 do not support name. We'll change this to True if we detect an issue. self.ignore_name_param = False # API should be used for ONTAP 9.6 or higher self.rest_api = OntapRestAPI(self.module) if self.rest_api.is_rest(): self.use_rest = True else: self.module.fail_json(msg=self.rest_api.requires_ontap_9_6('na_ontap_security_certificates')) def get_certificate(self): """ Fetch uuid if certificate exists. NOTE: because of a bug in ONTAP 9.6 and 9.7, name is not supported. We are falling back to using common_name and type, but unicity is not guaranteed. :return: Dictionary if certificate with same name is found None if not found """ error = "'name' or ('common_name', 'type') are required." for key in ('name', 'common_name'): if self.parameters.get(key) is None: continue data = {'fields': 'uuid', key: self.parameters[key], } if self.parameters.get('svm') is not None: data['svm.name'] = self.parameters['svm'] else: data['scope'] = 'cluster' if key == 'common_name': if self.parameters.get('type') is not None: data['type'] = self.parameters['type'] else: error = "When using 'common_name', 'type' is required." break api = "security/certificates" message, error = self.rest_api.get(api, data) if error: try: name_not_supported_error = (key == 'name') and (error['message'] == 'Unexpected argument "name".') except (KeyError, TypeError): name_not_supported_error = False if name_not_supported_error: if self.parameters['ignore_name_if_not_supported'] and self.parameters.get('common_name') is not None: # let's attempt a retry using common_name self.ignore_name_param = True continue error = "ONTAP 9.6 and 9.7 do not support 'name'. Use 'common_name' and 'type' as a work-around." # report success, or any other error as is break if error: self.module.fail_json(msg='Error calling API: %s - %s' % (api, error)) if len(message['records']) == 1: return message['records'][0] if len(message['records']) > 1: error = 'Duplicate records with same common_name are preventing safe operations: %s' % repr(message) self.module.fail_json(msg=error) return None def create_or_install_certificate(self): """ Create or install certificate :return: message (should be empty dict) """ required_keys = ['type', 'common_name'] optional_keys = ['public_certificate', 'private_key', 'expiry_time', 'key_size', 'hash_function'] if not self.ignore_name_param: optional_keys.append('name') # special key: svm if not set(required_keys).issubset(set(self.parameters.keys())): self.module.fail_json(msg='Error creating or installing certificate: one or more of the following options are missing: %s' % (', '.join(required_keys))) data = dict() if self.parameters.get('svm') is not None: data['svm'] = {'name': self.parameters['svm']} for key in required_keys + optional_keys: if self.parameters.get(key) is not None: data[key] = self.parameters[key] api = "security/certificates" message, error = self.rest_api.post(api, data) if error: if self.parameters.get('svm') is None and error.get('target') == 'uuid': error['target'] = 'cluster' if error.get('message') == 'duplicate entry': error['message'] += '. Same certificate may already exist under a different name.' self.module.fail_json(msg="Error creating or installing certificate: %s" % error) return message def sign_certificate(self, uuid): """ sign certificate :return: a dictionary with key "public_certificate" """ api = "security/certificates/%s/sign" % uuid data = {'signing_request': self.parameters['signing_request']} optional_keys = ['expiry_time', 'hash_function'] for key in optional_keys: if self.parameters.get(key) is not None: data[key] = self.parameters[key] message, error = self.rest_api.post(api, data) if error: self.module.fail_json(msg="Error signing certificate: %s" % error) return message def delete_certificate(self, uuid): """ Delete certificate :return: message (should be empty dict) """ api = "security/certificates/%s" % uuid message, error = self.rest_api.delete(api) if error: self.module.fail_json(msg="Error deleting certificate: %s" % error) return message def apply(self): """ Apply action to create/install/sign/delete certificate :return: None """ # TODO: add telemetry for REST current = self.get_certificate() cd_action = self.na_helper.get_cd_action(current, self.parameters) message = None if self.parameters.get('signing_request') is not None: error = None if self.parameters['state'] == 'absent': error = "'signing_request' is not supported with 'state' set to 'absent'" elif current is None: scope = 'cluster' if self.parameters.get('svm') is None else "svm: %s" % self.parameters.get('svm') error = "signing certificate with name '%s' not found on %s" % (self.parameters.get('name'), scope) elif cd_action is not None: error = "'signing_request' is exclusive with other actions: create, install, delete" if error is not None: self.module.fail_json(msg=error) self.na_helper.changed = True if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': message = self.create_or_install_certificate() elif cd_action == 'delete': message = self.delete_certificate(current['uuid']) elif self.parameters.get('signing_request') is not None: message = self.sign_certificate(current['uuid']) results = {'changed': self.na_helper.changed} if message: results['ontap_info'] = message self.module.exit_json(**results)
class NetAppOntapSecurityKeyManager(object): '''class with key manager operations''' def __init__(self): '''Initialize module parameters''' self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(state=dict(required=False, choices=['present', 'absent'], default='present'), ip_address=dict(required=True, type='str'), node=dict(required=False, type='str'), tcp_port=dict(required=False, type='int', default=5696)) 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.cluster = netapp_utils.setup_na_ontap_zapi(module=self.module) def get_key_manager(self): """ get key manager by ip address. :return: a dict of key manager """ key_manager_info = netapp_utils.zapi.NaElement( 'security-key-manager-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'key-manager-info', **{'key-manager-ip-address': self.parameters['ip_address']}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) key_manager_info.add_child_elem(query) try: result = self.cluster.invoke_successfully(key_manager_info, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching key manager %s : %s' % (self.parameters['node'], to_native(error)), exception=traceback.format_exc()) return_value = None if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) > 0: key_manager = result.get_child_by_name( 'attributes-list').get_child_by_name('key-manager-info') return_value = {} if key_manager.get_child_by_name('key-manager-ip-address'): return_value['ip_address'] = key_manager.get_child_content( 'key-manager-ip-address') if key_manager.get_child_by_name('key-manager-server-status'): return_value['server_status'] = key_manager.get_child_content( 'key-manager-server-status') if key_manager.get_child_by_name('key-manager-tcp-port'): return_value['tcp_port'] = key_manager.get_child_content( 'key-manager-tcp-port') if key_manager.get_child_by_name('node-name'): return_value['node'] = key_manager.get_child_content( 'node-name') return return_value def key_manager_setup(self): """ set up external key manager. """ key_manager_setup = netapp_utils.zapi.NaElement( 'security-key-manager-setup') # if specify on-boarding passphrase, it is on-boarding key management. # it not, then it's external key management. try: self.cluster.invoke_successfully(key_manager_setup, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error setting up key manager %s : %s' % (self.parameters['node'], to_native(error)), exception=traceback.format_exc()) def create_key_manager(self): """ add key manager. """ key_manager_create = netapp_utils.zapi.NaElement( 'security-key-manager-add') key_manager_create.add_new_child('key-manager-ip-address', self.parameters['ip_address']) if self.parameters.get('tcp_port'): key_manager_create.add_new_child('key-manager-tcp-port', str(self.parameters['tcp_port'])) try: self.cluster.invoke_successfully(key_manager_create, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating key manager %s : %s' % (self.parameters['node'], to_native(error)), exception=traceback.format_exc()) def delete_key_manager(self): """ delete key manager. """ key_manager_delete = netapp_utils.zapi.NaElement( 'security-key-manager-delete') key_manager_delete.add_new_child('key-manager-ip-address', self.parameters['ip_address']) try: self.cluster.invoke_successfully(key_manager_delete, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting key manager %s : %s' % (self.parameters['node'], to_native(error)), exception=traceback.format_exc()) def apply(self): self.asup_log_for_cserver("na_ontap_security_key_manager") self.key_manager_setup() current = self.get_key_manager() cd_action = None cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_key_manager() elif cd_action == 'delete': self.delete_key_manager() 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.cluster) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver)
class NetAppOntapKerberosRealm(object): ''' Kerberos Realm definition class ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(admin_server_ip=dict(required=False, type='str'), admin_server_port=dict(required=False, type='str'), clock_skew=dict(required=False, type='str'), comment=dict(required=False, type='str'), kdc_ip=dict(required_if=[["state", "present"]], type='str'), kdc_port=dict(required=False, type='str'), kdc_vendor=dict(required_if=[["state", "present"]], type='str', choices=['microsoft', 'other']), pw_server_ip=dict(required=False, type='str'), pw_server_port=dict(required=False, type='str'), realm=dict(required=True, type='str'), state=dict(required=False, choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), ad_server_ip=dict(required=False, type='str'), ad_server_name=dict(required=False, type='str'))) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, required_if=[('state', 'present', ['kdc_vendor', 'kdc_ip']), ('kdc_vendor', 'microsoft', ['ad_server_ip', 'ad_server_name'])], ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) self.simple_attributes = [ 'admin_server_ip', 'admin_server_port', 'clock_skew', 'kdc_ip', 'kdc_port', 'kdc_vendor', ] def get_krbrealm(self, realm_name=None, vserver_name=None): ''' Checks if Kerberos Realm config exists. :return: kerberos realm object if found None if not found :rtype: object/None ''' # Make query krbrealm_info = netapp_utils.zapi.NaElement('kerberos-realm-get-iter') if realm_name is None: realm_name = self.parameters['realm'] if vserver_name is None: vserver_name = self.parameters['vserver'] query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'kerberos-realm', **{ 'realm': realm_name, 'vserver-name': vserver_name }) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) krbrealm_info.add_child_elem(query) result = self.server.invoke_successfully(krbrealm_info, enable_tunneling=True) # Get Kerberos Realm details krbrealm_details = None if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1): attributes_list = result.get_child_by_name('attributes-list') config_info = attributes_list.get_child_by_name('kerberos-realm') krbrealm_details = { 'admin_server_ip': config_info.get_child_content('admin-server-ip'), 'admin_server_port': config_info.get_child_content('admin-server-port'), 'clock_skew': config_info.get_child_content('clock-skew'), 'kdc_ip': config_info.get_child_content('kdc-ip'), 'kdc_port': config_info.get_child_content('kdc-port'), 'kdc_vendor': config_info.get_child_content('kdc-vendor'), 'pw_server_ip': config_info.get_child_content('password-server-ip'), 'pw_server_port': config_info.get_child_content('password-server-port'), 'realm': config_info.get_child_content('realm'), 'vserver': config_info.get_child_content('vserver-name'), 'ad_server_ip': config_info.get_child_content('ad-server-ip'), 'ad_server_name': config_info.get_child_content('ad-server-name') } return krbrealm_details def create_krbrealm(self): '''supported Create Kerberos Realm configuration ''' options = {'realm': self.parameters['realm']} # Other options/attributes for attribute in self.simple_attributes: if self.parameters.get(attribute) is not None: options[str(attribute).replace( '_', '-')] = self.parameters[attribute] if self.parameters.get('pw_server_ip') is not None: options['password-server-ip'] = self.parameters['pw_server_ip'] if self.parameters.get('pw_server_port') is not None: options['password-server-port'] = self.parameters['pw_server_port'] if self.parameters.get('ad_server_ip') is not None: options['ad-server-ip'] = self.parameters['ad_server_ip'] if self.parameters.get('ad_server_name') is not None: options['ad-server-name'] = self.parameters['ad_server_name'] # Initialize NaElement krbrealm_create = netapp_utils.zapi.NaElement.create_node_with_children( 'kerberos-realm-create', **options) # Try to create Kerberos Realm configuration try: self.server.invoke_successfully(krbrealm_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as errcatch: self.module.fail_json( msg='Error creating Kerberos Realm configuration %s: %s' % (self.parameters['realm'], to_native(errcatch)), exception=traceback.format_exc()) def delete_krbrealm(self): ''' Delete Kerberos Realm configuration ''' krbrealm_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'kerberos-realm-delete', **{'realm': self.parameters['realm']}) try: self.server.invoke_successfully(krbrealm_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as errcatch: self.module.fail_json( msg='Error deleting Kerberos Realm configuration %s: %s' % (self.parameters['realm'], to_native(errcatch)), exception=traceback.format_exc()) def modify_krbrealm(self, modify): ''' Modify Kerberos Realm :param modify: list of modify attributes ''' krbrealm_modify = netapp_utils.zapi.NaElement('kerberos-realm-modify') krbrealm_modify.add_new_child('realm', self.parameters['realm']) for attribute in modify: if attribute in self.simple_attributes: krbrealm_modify.add_new_child( str(attribute).replace('_', '-'), self.parameters[attribute]) if attribute == 'pw_server_ip': krbrealm_modify.add_new_child('password-server-ip', self.parameters['pw_server_ip']) if attribute == 'pw_server_port': krbrealm_modify.add_new_child( 'password-server-port', self.parameters['pw_server_port']) if attribute == 'ad_server_ip': krbrealm_modify.add_new_child('ad-server-ip', self.parameters['ad_server_ip']) if attribute == 'ad_server_name': krbrealm_modify.add_new_child( 'ad-server-name', self.parameters['ad_server_name']) # Try to modify Kerberos Realm try: self.server.invoke_successfully(krbrealm_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as errcatch: self.module.fail_json( msg='Error modifying Kerberos Realm %s: %s' % (self.parameters['realm'], to_native(errcatch)), exception=traceback.format_exc()) def apply(self): '''Call create/modify/delete operations.''' current = self.get_krbrealm() cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes( current, self.parameters) # create an ems log event for users with auto support turned on netapp_utils.ems_log_event("na_ontap_kerberos_realm", self.server) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_krbrealm() elif cd_action == 'delete': self.delete_krbrealm() elif modify: self.modify_krbrealm(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPMotd(object): def __init__(self): argument_spec = netapp_utils.na_ontap_host_argument_spec() argument_spec.update( dict(state=dict(required=False, default='present', choices=['present', 'absent']), vserver=dict(required=True, type='str'), motd_message=dict(default='', type='str', aliases=['message']), show_cluster_motd=dict(default=True, type='bool'))) self.module = AnsibleModule(argument_spec=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 motd_get_iter(self): """ Compose NaElement object to query current motd :return: NaElement object for vserver-motd-get-iter """ motd_get_iter = netapp_utils.zapi.NaElement('vserver-motd-get-iter') query = netapp_utils.zapi.NaElement('query') motd_info = netapp_utils.zapi.NaElement('vserver-motd-info') motd_info.add_new_child('is-cluster-message-enabled', str(self.parameters['show_cluster_motd'])) motd_info.add_new_child('vserver', self.parameters['vserver']) query.add_child_elem(motd_info) motd_get_iter.add_child_elem(query) return motd_get_iter def motd_get(self): """ Get current motd :return: Dictionary of current motd details if query successful, else None """ motd_get_iter = self.motd_get_iter() motd_result = dict() try: result = self.server.invoke_successfully(motd_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching motd 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: motd_info = result.get_child_by_name( 'attributes-list').get_child_by_name('vserver-motd-info') motd_result['motd_message'] = motd_info.get_child_content( 'message') motd_result['motd_message'] = str( motd_result['motd_message']).rstrip() motd_result[ 'show_cluster_motd'] = True if motd_info.get_child_content( 'is-cluster-message-enabled') == 'true' else False motd_result['vserver'] = motd_info.get_child_content('vserver') return motd_result return None def modify_motd(self): motd_create = netapp_utils.zapi.NaElement('vserver-motd-modify-iter') motd_create.add_new_child('message', self.parameters['motd_message']) motd_create.add_new_child( 'is-cluster-message-enabled', 'true' if self.parameters['show_cluster_motd'] is True else 'false') query = netapp_utils.zapi.NaElement('query') motd_info = netapp_utils.zapi.NaElement('vserver-motd-info') motd_info.add_new_child('vserver', self.parameters['vserver']) query.add_child_elem(motd_info) motd_create.add_child_elem(query) try: self.server.invoke_successfully(motd_create, enable_tunneling=False) except netapp_utils.zapi.NaApiError as err: self.module.fail_json(msg="Error creating motd: %s" % (to_native(err)), exception=traceback.format_exc()) return motd_create def apply(self): """ Applies action from playbook """ netapp_utils.ems_log_event("na_ontap_motd", self.server) current = self.motd_get() if self.parameters['state'] == 'absent': # Just make sure it is empty self.parameters['motd_message'] = '' if current and current['motd_message'] == 'None': current = None cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: self.modify_motd() self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPMetroCluster(object): def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(choices=['present'], default='present'), dr_pairs=dict(required=True, type='list', elements='dict', options=dict( node_name=dict(required=True, type='str'), partner_node_name=dict(required=True, type='str') )), partner_cluster_name=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.restApi = OntapRestAPI(self.module) self.use_rest = self.restApi.is_rest() if not self.use_rest: self.module.fail_json(msg="na_ontap_metrocluster only supports REST API") def get_metrocluster(self): attrs = None api = 'cluster/metrocluster' options = {'fields': '*'} message, error = self.restApi.get(api, options) if error: self.module.fail_json(msg=error) if message is not None: local = message['local'] if local['configuration_state'] != "not_configured": attrs = { 'configuration_state': local['configuration_state'], 'partner_cluster_reachable': local['partner_cluster_reachable'], 'partner_cluster_name': local['cluster']['name'] } return attrs def create_metrocluster(self): api = 'cluster/metrocluster' options = {} dr_pairs = [] for pair in self.parameters['dr_pairs']: dr_pairs.append({'node': {'name': pair['node_name']}, 'partner': {'name': pair['partner_node_name']}}) partner_cluster = {'name': self.parameters['partner_cluster_name']} data = {'dr_pairs': dr_pairs, 'partner_cluster': partner_cluster} message, error = self.restApi.post(api, data, options) if error is not None: self.module.fail_json(msg="%s" % error) self.restApi.wait_on_job(message['job'], self.parameters['hostname']) def apply(self): current = self.get_metrocluster() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_metrocluster() # Since there is no modify or delete, we will return no change else: self.module.fail_json(msg="Modify and Delete currently not support in API") self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapNetRoutes(object): """ Create, Modifies and Destroys a Net Route """ def __init__(self): """ Initialize the Ontap Net Route class """ self.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'), vserver=dict(required=True, type='str'), destination=dict(required=True, type='str'), gateway=dict(required=True, type='str'), metric=dict(required=False, type='int'), from_destination=dict(required=False, type='str', default=None), from_gateway=dict(required=False, type='str', default=None), from_metric=dict(required=False, type='int', default=None), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.restApi = OntapRestAPI(self.module) # some attributes are not supported in earlier REST implementation unsupported_rest_properties = ['metric', 'from_metric'] 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 HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) return def create_net_route(self, current_metric=None): """ Creates a new Route """ if self.use_rest: api = "network/ip/routes" params = { 'gateway': self.parameters['gateway'], 'svm': self.parameters['vserver'] } if self.parameters.get('destination') is not None: d = self.parameters['destination'].split('/') params['destination'] = {'address': d[0], 'netmask': d[1]} __, error = self.restApi.post(api, params) if error: self.module.fail_json(msg=error) else: route_obj = netapp_utils.zapi.NaElement('net-routes-create') route_obj.add_new_child("destination", self.parameters['destination']) route_obj.add_new_child("gateway", self.parameters['gateway']) if current_metric is None and self.parameters.get( 'metric') is not None: metric = self.parameters['metric'] else: metric = current_metric # Metric can be None, Can't set metric to none if metric is not None: route_obj.add_new_child("metric", str(metric)) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating net route: %s' % (to_native(error)), exception=traceback.format_exc()) def delete_net_route(self, params): """ Deletes a given Route """ if self.use_rest: uuid = params['uuid'] api = "network/ip/routes/" + uuid data = None message, error = self.restApi.delete(api, data) if error: self.module.fail_json(msg=error) else: route_obj = netapp_utils.zapi.NaElement('net-routes-destroy') if params is None: params = self.parameters route_obj.add_new_child("destination", params['destination']) route_obj.add_new_child("gateway", params['gateway']) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting net route: %s' % (to_native(error)), exception=traceback.format_exc()) def modify_net_route(self, current, desired): """ Modify a net route Since we cannot modify a route, we are deleting the existing route, and creating a new one. """ if self.use_rest: if desired.get('destination') is not None: d = desired['destination'].split('/') if d[0] != current['destination']['address'] or d[ 1] != current['destination']['netmask']: self.na_helper.changed = True self.parameters['destination'] = desired['destination'] else: self.parameters['destination'] = '%s/%s' % ( current['destination']['address'], current['destination']['netmask']) if desired.get('gateway') is not None: if desired['gateway'] != current['gateway']: self.na_helper.changed = True self.parameters['gateway'] = desired['gateway'] else: self.parameters['gateway'] = current['gateway'] if not self.na_helper.changed or self.module.check_mode: return params = { 'destination': '%s/%s' % (current['destination']['address'], current['destination']['netmask']), 'gateway': current['gateway'] } target = self.get_net_route(params) self.delete_net_route(target) self.create_net_route() return else: # return if there is nothing to change for key, val in desired.items(): if val != current[key]: self.na_helper.changed = True break if not self.na_helper.changed or self.module.check_mode: return # delete and re-create with new params self.delete_net_route(current) route_obj = netapp_utils.zapi.NaElement('net-routes-create') for attribute in ['metric', 'destination', 'gateway']: if desired.get(attribute) is not None: value = desired[attribute] else: value = current[attribute] route_obj.add_new_child(attribute, str(value)) try: result = self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: # restore the old route, create the route with the existing metric self.create_net_route(current['metric']) # return if desired route already exists if to_native(error.code) == '13001': return # Invalid value specified for any of the attributes self.module.fail_json(msg='Error modifying net route: %s' % (to_native(error)), exception=traceback.format_exc()) def get_net_route(self, params=None): """ Checks to see if a route exist or not :return: NaElement object if a route exists, None otherwise """ if params is not None: # we need either destination or gateway to fetch desired route if params.get('destination') is None and params.get( 'gateway') is None: return None if self.use_rest: api = "network/ip/routes" data = {'fields': 'destination,gateway,svm'} message, error = self.restApi.get(api, data) 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_net_route from %s: %s" % ( api, repr(message)) self.module.fail_json(msg=error) if params is None: params = self.parameters else: if params.get('destination') is None: params['destination'] = self.parameters['destination'] if params.get('gateway') is None: params['gateway'] = self.parameters['gateway'] params['vserver'] = self.parameters['vserver'] for record in message['records']: if record['gateway'] == params['gateway'] and \ record['destination']['address'] == params['destination'].split('/')[0] and \ record.get('svm') and record['svm']['name'] == params['vserver']: return record return None else: current = None route_obj = netapp_utils.zapi.NaElement('net-routes-get') for attr in ['destination', 'gateway']: if params and params.get(attr) is not None: value = params[attr] else: value = self.parameters[attr] route_obj.add_new_child(attr, value) try: result = self.server.invoke_successfully(route_obj, True) if result.get_child_by_name('attributes') is not None: route_info = result.get_child_by_name( 'attributes').get_child_by_name('net-vs-routes-info') current = { 'destination': route_info.get_child_content('destination'), 'gateway': route_info.get_child_content('gateway'), 'metric': int(route_info.get_child_content('metric')) } except netapp_utils.zapi.NaApiError as error: # Error 13040 denotes a route doesn't exist. if to_native(error.code) == "15661": return None self.module.fail_json(msg='Error fetching net route: %s' % (to_native(error)), exception=traceback.format_exc()) return current @staticmethod def is_modify_action(current, desired): """ Get desired action to be applied for net routes Destination and gateway are unique params for a route and cannot be duplicated So if a route with desired destination or gateway exists already, we don't try to modify :param current: current details :param desired: desired details :return: create / delete / modify / None """ if current is None and desired is None: # this is invalid # cannot modify a non existent resource return None if current is None and desired is not None: # idempotency or duplication # we need not create return False if current is not None and desired is not None: # we can't modify an ambiguous route (idempotency/duplication) return False return True def get_params_to_be_modified(self, current): """ Get parameters and values that need to be modified :param current: current details :return: dict(), None """ if current is None: return None desired = dict() if self.parameters.get('new_destination') is not None and \ self.parameters['new_destination'] != current['destination']: desired['destination'] = self.parameters['new_destination'] if self.parameters.get('new_gateway') is not None and \ self.parameters['new_gateway'] != current['gateway']: desired['gateway'] = self.parameters['new_gateway'] if self.parameters.get('new_metric') is not None and \ self.parameters['new_metric'] != current['metric']: desired['metric'] = self.parameters['new_metric'] return desired def apply(self): """ Run Module based on play book """ if not self.use_rest: netapp_utils.ems_log_event("na_ontap_net_routes", self.server) current = self.get_net_route() modify, cd_action = None, None if self.use_rest: modify_params = { 'gateway': self.parameters.get('from_gateway'), 'destination': self.parameters.get('from_destination') } if any(modify_params.values()): # destination and gateway combination is unique, and is considered like a id. so modify destination # or gateway is considered a rename action. old_params = self.get_net_route(modify_params) modify = self.na_helper.is_rename_action(old_params, current) if modify is None: self.module.fail_json( msg="Error modifying: route %s does not exist" % self.parameters['from_destination']) else: cd_action = self.na_helper.get_cd_action( current, self.parameters) else: modify_params = { 'destination': self.parameters.get('from_destination'), 'gateway': self.parameters.get('from_gateway'), 'metric': self.parameters.get('from_metric') } # if any from_* param is present in playbook, check for modify action if any(modify_params.values()): # destination and gateway combination is unique, and is considered like a id. so modify destination # or gateway is considered a rename action. metric is considered an attribute of the route so it is # considered as modify. if modify_params.get('metric') is not None: modify = True old_params = current else: # get parameters that are eligible for modify old_params = self.get_net_route(modify_params) modify = self.na_helper.is_rename_action( old_params, current) if modify is None: self.module.fail_json( msg="Error modifying: route %s does not exist" % self.parameters['from_destination']) else: cd_action = self.na_helper.get_cd_action( current, self.parameters) if cd_action == 'create': if not self.module.check_mode: self.create_net_route() elif cd_action == 'delete': if not self.module.check_mode: self.delete_net_route(current) elif modify: desired = {} for key, value in old_params.items(): desired[key] = value for key, value in modify_params.items(): if value is not None: desired[key] = self.parameters.get(key) self.modify_net_route(old_params, desired) self.module.exit_json(changed=self.na_helper.changed)