class AwsCvsNetappActiveDir(object): """ Contains methods to parse arguments, derive details of AWS_CVS objects and send requests to AWS CVS via the restApi """ def __init__(self): """ Parse arguments, setup state variables, check paramenters and ensure request module is installed """ self.argument_spec = netapp_utils.aws_cvs_host_argument_spec() self.argument_spec.update( dict(state=dict(required=True, choices=['present', 'absent'], type='str'), region=dict(required=True, type='str'), DNS=dict(required=False, type='str'), domain=dict(required=False, type='str'), password=dict(required=False, type='str', no_log=True), netBIOS=dict(required=False, type='str'), username=dict(required=False, type='str'))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[ ('state', 'present', ['region', 'domain']), ], supports_check_mode=True) self.na_helper = NetAppModule() # set up state variables self.parameters = self.na_helper.set_parameters(self.module.params) # Calling generic AWSCVS restApi class self.restApi = AwsCvsRestAPI(self.module) def get_activedirectoryId(self): # Check if ActiveDirectory exists # Return UUID for ActiveDirectory is found, None otherwise try: list_activedirectory, error = self.restApi.get( 'Storage/ActiveDirectory') except Exception as e: return None for ActiveDirectory in list_activedirectory: if ActiveDirectory['region'] == self.parameters['region']: return ActiveDirectory['UUID'] return None def get_activedirectory(self, activeDirectoryId=None): if activeDirectoryId is None: return None else: ActiveDirectoryInfo, error = self.restApi.get( 'Storage/ActiveDirectory/%s' % activeDirectoryId) if not error: return ActiveDirectoryInfo return None def create_activedirectory(self): # Create ActiveDirectory api = 'Storage/ActiveDirectory' data = { "region": self.parameters['region'], "DNS": self.parameters['DNS'], "domain": self.parameters['domain'], "username": self.parameters['username'], "password": self.parameters['password'], "netBIOS": self.parameters['netBIOS'] } response, error = self.restApi.post(api, data) if not error: return response else: self.module.fail_json(msg=response['message']) def delete_activedirectory(self): activedirectoryId = self.get_activedirectoryId() # Delete ActiveDirectory if activedirectoryId: api = 'Storage/ActiveDirectory/' + activedirectoryId data = None response, error = self.restApi.delete(api, data) if not error: return response else: self.module.fail_json(msg=response['message']) else: self.module.fail_json(msg="Active Directory does not exist") def update_activedirectory(self, activedirectoryId, updated_activedirectory): # Update ActiveDirectory api = 'Storage/ActiveDirectory/' + activedirectoryId data = { "region": self.parameters['region'], "DNS": updated_activedirectory['DNS'], "domain": updated_activedirectory['domain'], "username": updated_activedirectory['username'], "password": updated_activedirectory['password'], "netBIOS": updated_activedirectory['netBIOS'] } response, error = self.restApi.put(api, data) if not error: return response else: self.module.fail_json(msg=response['message']) def apply(self): """ Perform pre-checks, call functions and exit """ modify = False activeDirectoryId = self.get_activedirectoryId() current = self.get_activedirectory(activeDirectoryId) cd_action = self.na_helper.get_cd_action(current, self.parameters) if current and self.parameters['state'] != 'absent': keys_to_check = [ 'DNS', 'domain', 'username', 'password', 'netBIOS' ] updated_active_directory, modify = self.na_helper.compare_and_update_values( current, self.parameters, keys_to_check) if modify is True: self.na_helper.changed = True if 'domain' in self.parameters and self.parameters[ 'domain'] is not None: ad_exists = self.get_activedirectory( updated_active_directory['domain']) if ad_exists: modify = False self.na_helper.changed = False if self.na_helper.changed: if self.module.check_mode: pass else: if modify is True: self.update_activedirectory(activeDirectoryId, updated_active_directory) elif cd_action == 'create': self.create_activedirectory() elif cd_action == 'delete': self.delete_activedirectory() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapIpspace(object): '''Class with ipspace operations''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) return def 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.self.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'] 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 """ 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.self.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 """ 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.self.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 """ 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 NetAppOntapVscan(object): def __init__(self): self.use_rest = False self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( enable=dict(type='bool', default=True), 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) # 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, vserver=self.parameters['vserver']) def get_vscan(self): if self.use_rest: params = { 'fields': 'svm,enabled', "svm.name": self.parameters['vserver'] } api = "protocols/vscan" message, error = self.restApi.get(api, params) if error: self.module.fail_json(msg=error) return message['records'][0] else: vscan_status_iter = netapp_utils.zapi.NaElement( 'vscan-status-get-iter') vscan_status_info = netapp_utils.zapi.NaElement( 'vscan-status-info') vscan_status_info.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(vscan_status_info) vscan_status_iter.add_child_elem(query) try: result = self.server.invoke_successfully( vscan_status_iter, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error getting Vscan info for Vserver %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: return result.get_child_by_name( 'attributes-list').get_child_by_name('vscan-status-info') def enable_vscan(self, uuid=None): if self.use_rest: params = {"svm.name": self.parameters['vserver']} data = {"enabled": self.parameters['enable']} api = "protocols/vscan/" + uuid message, error = self.restApi.patch(api, data, params) if error is not None: self.module.fail_json(msg=error) # self.module.fail_json(msg=repr(self.restApi.errors), log=repr(self.restApi.debug_logs)) else: vscan_status_obj = netapp_utils.zapi.NaElement( "vscan-status-modify") vscan_status_obj.add_new_child('is-vscan-enabled', str(self.parameters['enable'])) try: self.server.invoke_successfully(vscan_status_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error Enable/Disabling Vscan: %s" % to_native(error), exception=traceback.format_exc()) def asup_log(self): if self.use_rest: # TODO: logging for Rest return else: # Either we are using ZAPI, or REST failed when it should not try: netapp_utils.ems_log_event("na_ontap_vscan", self.server) except Exception: # TODO: we may fail to connect to REST or ZAPI, the line below shows REST issues only # self.module.fail_json(msg=repr(self.restApi.errors), log=repr(self.restApi.debug_logs)) pass def apply(self): changed = False self.asup_log() current = self.get_vscan() if self.use_rest: if current['enabled'] != self.parameters['enable']: if not self.module.check_mode: self.enable_vscan(current['svm']['uuid']) changed = True else: if current.get_child_content('is-vscan-enabled') != str( self.parameters['enable']).lower(): if not self.module.check_mode: self.enable_vscan() changed = True self.module.exit_json(changed=changed)
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, choices=['present'], default='present'), cluster_name=dict(required=False, type='str'), cluster_ip_address=dict(required=False, type='str'), license_code=dict(required=False, type='str'), license_package=dict(required=False, type='str'), node_serial_number=dict(required=False, type='str'))) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, required_together=[['license_package', 'node_serial_number']]) 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_licensing_status(self): """ Check licensing status :return: package (key) and licensing status (value) :rtype: dict """ license_status = netapp_utils.zapi.NaElement( 'license-v2-status-list-info') try: result = self.server.invoke_successfully(license_status, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error checking license status: %s" % to_native(error), exception=traceback.format_exc()) return_dictionary = {} license_v2_status = result.get_child_by_name('license-v2-status') if license_v2_status: for license_v2_status_info in license_v2_status.get_children(): package = license_v2_status_info.get_child_content('package') status = license_v2_status_info.get_child_content('method') return_dictionary[package] = status return return_dictionary def create_cluster(self): """ Create a cluster """ cluster_create = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-create', **{'cluster-name': self.parameters['cluster_name']}) try: self.server.invoke_successfully(cluster_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: # Error 36503 denotes node already being used. if to_native(error.code) == "36503": return False else: self.module.fail_json( msg='Error creating cluster %s: %s' % (self.parameters['cluster_name'], to_native(error)), exception=traceback.format_exc()) return True def cluster_join(self): """ Add a node to an existing cluster """ if self.parameters.get('cluster_ip_address') is not None: cluster_add_node = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-join', **{ 'cluster-ip-address': self.parameters['cluster_ip_address'] }) for_fail_attribute = self.parameters.get('cluster_ip_address') elif self.parameters.get('cluster_name') is not None: cluster_add_node = netapp_utils.zapi.NaElement.create_node_with_children( 'cluster-join', **{'cluster-name': self.parameters['cluster_name']}) for_fail_attribute = self.parameters.get('cluster_name') else: return False try: self.server.invoke_successfully(cluster_add_node, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: # Error 36503 denotes node already being used. if to_native(error.code) == "36503": return False else: self.module.fail_json( msg='Error adding node to cluster %s: %s' % (for_fail_attribute, to_native(error)), exception=traceback.format_exc()) return True def license_v2_add(self): """ Apply a license to cluster """ license_add = netapp_utils.zapi.NaElement.create_node_with_children( 'license-v2-add') license_add.add_node_with_children( 'codes', **{'license-code-v2': self.parameters['license_code']}) try: self.server.invoke_successfully(license_add, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error adding license %s: %s' % (self.parameters['license_code'], to_native(error)), exception=traceback.format_exc()) def license_v2_delete(self): """ Delete license from cluster """ license_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'license-v2-delete', **{ 'package': self.parameters['license_package'], 'serial-number': self.parameters['node_serial_number'] }) try: self.server.invoke_successfully(license_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting license : %s' % (to_native(error)), exception=traceback.format_exc()) 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 """ property_changed = False create_flag = False join_flag = False self.autosupport_log() license_status = self.get_licensing_status() if self.module.check_mode: pass else: if self.parameters.get('state') == 'present': if self.parameters.get('cluster_name') is not None: create_flag = self.create_cluster() if not create_flag: join_flag = self.cluster_join() if self.parameters.get('license_code') is not None: self.license_v2_add() property_changed = True if self.parameters.get('license_package') is not None and\ self.parameters.get('node_serial_number') is not None: if license_status.get( str(self.parameters.get( 'license_package')).lower()) != 'none': self.license_v2_delete() property_changed = True if property_changed: new_license_status = self.get_licensing_status() if local_cmp(license_status, new_license_status) == 0: property_changed = False changed = property_changed or create_flag or join_flag self.module.exit_json(changed=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, 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 NetAppOntapAggregate(object): ''' object initialize and class methods ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(name=dict(required=True, type='str'), disks=dict(required=False, type='list'), disk_count=dict(required=False, type='int', default=None), disk_size=dict(required=False, type='int'), disk_type=dict(required=False, choices=[ 'ATA', 'BSAS', 'FCAL', 'FSAS', 'LUN', 'MSATA', 'SAS', 'SSD', 'VMDISK' ]), from_name=dict(required=False, type='str'), mirror_disks=dict(required=False, type='list'), nodes=dict(required=False, type='list'), is_mirrored=dict(required=False, type='bool'), raid_size=dict(required=False, type='int'), raid_type=dict(required=False, choices=['raid4', 'raid_dp', 'raid_tec']), service_state=dict(required=False, choices=['online', 'offline']), spare_pool=dict(required=False, choices=['Pool0', 'Pool1']), state=dict(required=False, choices=['present', 'absent'], default='present'), unmount_volumes=dict(required=False, type='bool'), wait_for_online=dict(required=False, type='bool', default=False), time_out=dict(required=False, type='int', default=100))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[ ('service_state', 'offline', ['unmount_volumes']), ], mutually_exclusive=[ ('is_mirrored', 'disks'), ('is_mirrored', 'mirror_disks'), ('is_mirrored', 'spare_pool'), ('spare_pool', 'disks') ], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get( 'mirror_disks' ) is not None and self.parameters.get('disks') is None: self.module.fail_json( mgs="mirror_disks require disks options to be set") if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def aggr_get_iter(self, name): """ Return aggr-get-iter query results :param name: Name of the aggregate :return: NaElement if aggregate found, None otherwise """ aggr_get_iter = netapp_utils.zapi.NaElement('aggr-get-iter') query_details = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-attributes', **{'aggregate-name': name}) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) aggr_get_iter.add_child_elem(query) result = None try: result = self.server.invoke_successfully(aggr_get_iter, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: # Error 13040 denotes an aggregate not being found. if to_native(error.code) == "13040": pass else: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) return result def get_aggr(self, name=None): """ Fetch details if aggregate exists. :param name: Name of the aggregate to be fetched :return: Dictionary of current details if aggregate found None if aggregate is not found """ if name is None: name = self.parameters['name'] aggr_get = self.aggr_get_iter(name) if (aggr_get and aggr_get.get_child_by_name('num-records') and int(aggr_get.get_child_content('num-records')) >= 1): current_aggr = dict() attr = aggr_get.get_child_by_name( 'attributes-list').get_child_by_name('aggr-attributes') current_aggr['service_state'] = attr.get_child_by_name( 'aggr-raid-attributes').get_child_content('state') return current_aggr return None def aggregate_online(self): """ Set state of an offline aggregate to online :return: None """ online_aggr = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-online', **{ 'aggregate': self.parameters['name'], 'force-online': 'true' }) try: self.server.invoke_successfully(online_aggr, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error changing the state of aggregate %s to %s: %s' % (self.parameters['name'], self.parameters['service_state'], to_native(error)), exception=traceback.format_exc()) def aggregate_offline(self): """ Set state of an online aggregate to offline :return: None """ offline_aggr = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-offline', **{ 'aggregate': self.parameters['name'], 'force-offline': 'false', 'unmount-volumes': str(self.parameters['unmount_volumes']) }) try: self.server.invoke_successfully(offline_aggr, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error changing the state of aggregate %s to %s: %s' % (self.parameters['name'], self.parameters['service_state'], to_native(error)), exception=traceback.format_exc()) def create_aggr(self): """ Create aggregate :return: None """ if not self.parameters.get('disk_count'): self.module.fail_json(msg='Error provisioning aggregate %s: \ disk_count is required' % self.parameters['name']) options = { 'aggregate': self.parameters['name'], 'disk-count': str(self.parameters['disk_count']) } if self.parameters.get('disk_type'): options['disk-type'] = self.parameters['disk_type'] if self.parameters.get('raid_size'): options['raid-size'] = str(self.parameters['raid_size']) if self.parameters.get('raid_type'): options['raid-type'] = self.parameters['raid_type'] if self.parameters.get('disk_size'): options['disk-size'] = str(self.parameters['disk_size']) if self.parameters.get('is_mirrored'): options['is-mirrored'] = str(self.parameters['is_mirrored']) if self.parameters.get('spare_pool'): options['spare-pool'] = self.parameters['spare_pool'] if self.parameters.get('raid_type'): options['raid-type'] = self.parameters['raid_type'] aggr_create = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-create', **options) if self.parameters.get('nodes'): nodes_obj = netapp_utils.zapi.NaElement('nodes') aggr_create.add_child_elem(nodes_obj) for node in self.parameters['nodes']: nodes_obj.add_new_child('node-name', node) if self.parameters.get('disks'): disks_obj = netapp_utils.zapi.NaElement('disk-info') for disk in self.parameters.get('disks'): disks_obj.add_new_child('name', disk) aggr_create.add_child_elem(disks_obj) if self.parameters.get('mirror_disks'): mirror_disks_obj = netapp_utils.zapi.NaElement('disk-info') for disk in self.parameters.get('mirror_disks'): mirror_disks_obj.add_new_child('name', disk) aggr_create.add_child_elem(mirror_disks_obj) try: self.server.invoke_successfully(aggr_create, enable_tunneling=False) if self.parameters.get('wait_for_online'): # round off time_out retries = (self.parameters['time_out'] + 5) / 10 current = self.get_aggr() status = None if current is None else current['service_state'] while status != 'online' and retries > 0: time.sleep(10) retries = retries - 1 current = self.get_aggr() status = None if current is None else current[ 'service_state'] except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error provisioning aggregate %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_aggr(self): """ Delete aggregate. :return: None """ aggr_destroy = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-destroy', **{'aggregate': self.parameters['name']}) try: self.server.invoke_successfully(aggr_destroy, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error removing aggregate %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_aggregate(self): """ Rename aggregate. """ aggr_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'aggr-rename', **{ 'aggregate': self.parameters['from_name'], 'new-aggregate-name': self.parameters['name'] }) try: self.server.invoke_successfully(aggr_rename, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg="Error renaming aggregate %s: %s" % (self.parameters['from_name'], to_native(error)), exception=traceback.format_exc()) def modify_aggr(self, modify): """ Modify state of the aggregate :param modify: dictionary of parameters to be modified :return: None """ if modify['service_state'] == 'offline': self.aggregate_offline() elif modify['service_state'] == 'online': self.aggregate_online() def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver) def apply(self): """ Apply action to the aggregate :return: None """ self.asup_log_for_cserver("na_ontap_aggregate") current = self.get_aggr() # rename and create are mutually exclusive rename, cd_action = None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_aggr(self.parameters['from_name']), current) if rename is None: self.module.fail_json( msg="Error renaming: aggregate %s does not exist" % self.parameters['from_name']) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_aggregate() elif cd_action == 'create': self.create_aggr() elif cd_action == 'delete': self.delete_aggr() elif modify: self.modify_aggr(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapDisks(object): ''' object initialize and class methods ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(node=dict(required=True, type='str'), disk_count=dict(required=False, type='int'))) 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_unassigned_disk_count(self): """ Check for free disks """ disk_iter = netapp_utils.zapi.NaElement('storage-disk-get-iter') disk_storage_info = netapp_utils.zapi.NaElement('storage-disk-info') disk_raid_info = netapp_utils.zapi.NaElement('disk-raid-info') disk_raid_info.add_new_child('container-type', 'unassigned') disk_storage_info.add_child_elem(disk_raid_info) disk_query = netapp_utils.zapi.NaElement('query') disk_query.add_child_elem(disk_storage_info) disk_iter.add_child_elem(disk_query) try: result = self.server.invoke_successfully(disk_iter, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error getting disk information: %s' % (to_native(error)), exception=traceback.format_exc()) return int(result.get_child_content('num-records')) def get_owned_disk_count(self): """ Check for owned disks """ disk_iter = netapp_utils.zapi.NaElement('storage-disk-get-iter') disk_storage_info = netapp_utils.zapi.NaElement('storage-disk-info') disk_ownership_info = netapp_utils.zapi.NaElement( 'disk-ownership-info') disk_ownership_info.add_new_child('home-node-name', self.parameters['node']) disk_storage_info.add_child_elem(disk_ownership_info) disk_query = netapp_utils.zapi.NaElement('query') disk_query.add_child_elem(disk_storage_info) disk_iter.add_child_elem(disk_query) try: result = self.server.invoke_successfully(disk_iter, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error getting disk information: %s' % (to_native(error)), exception=traceback.format_exc()) return int(result.get_child_content('num-records')) def disk_assign(self, needed_disks): """ Set node as disk owner. """ if needed_disks > 0: assign_disk = netapp_utils.zapi.NaElement.create_node_with_children( 'disk-sanown-assign', **{ 'owner': self.parameters['node'], 'disk-count': str(needed_disks) }) else: assign_disk = netapp_utils.zapi.NaElement.create_node_with_children( 'disk-sanown-assign', **{ 'node-name': self.parameters['node'], 'all': 'true' }) try: self.server.invoke_successfully(assign_disk, enable_tunneling=True) return True except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == "13001": # Error 13060 denotes aggregate is already online return False else: self.module.fail_json(msg='Error assigning disks %s' % (to_native(error)), exception=traceback.format_exc()) def apply(self): '''Apply action to disks''' changed = False 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_disks", cserver) # check if anything needs to be changed (add/delete/update) unowned_disks = self.get_unassigned_disk_count() owned_disks = self.get_owned_disk_count() if 'disk_count' in self.parameters: if self.parameters['disk_count'] < owned_disks: self.module.fail_json( msg="Fewer disks than are currently owned was requested. " "This module does not do any disk removing. " "All disk removing will need to be done manually.") if self.parameters['disk_count'] > owned_disks + unowned_disks: self.module.fail_json( msg="Not enough unowned disks remain to fulfill request") if unowned_disks >= 1: if 'disk_count' in self.parameters: if self.parameters['disk_count'] > owned_disks: needed_disks = self.parameters['disk_count'] - owned_disks self.disk_assign(needed_disks) changed = True else: self.disk_assign(0) changed = True self.module.exit_json(changed=changed)
class NetAppONTAPCifsShare(object): """ Methods to create/delete/modify(path) CIFS share """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, type='str', choices=[ 'present', 'absent'], default='present'), share_name=dict(required=True, type='str'), path=dict(required=False, type='str'), vserver=dict(required=True, type='str'), share_properties=dict(required=False, type='list'), symlink_properties=dict(required=False, type='list'), vscan_fileop_profile=dict(required=False, type='str', choices=['no_scan', 'standard', 'strict', 'writes_only']) )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters.get('vserver')) def get_cifs_share(self): """ Return details about the cifs-share :param: name : Name of the cifs-share :return: Details about the cifs-share. None if not found. :rtype: dict """ cifs_iter = netapp_utils.zapi.NaElement('cifs-share-get-iter') cifs_info = netapp_utils.zapi.NaElement('cifs-share') cifs_info.add_new_child('share-name', self.parameters.get('share_name')) cifs_info.add_new_child('vserver', self.parameters.get('vserver')) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(cifs_info) cifs_iter.add_child_elem(query) result = self.server.invoke_successfully(cifs_iter, True) return_value = None # check if query returns the expected cifs-share if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: properties_list = [] symlink_list = [] cifs_attrs = result.get_child_by_name('attributes-list').\ get_child_by_name('cifs-share') if cifs_attrs.get_child_by_name('share-properties'): properties_attrs = cifs_attrs['share-properties'] if properties_attrs is not None: properties_list = [property.get_content() for property in properties_attrs.get_children()] if cifs_attrs.get_child_by_name('symlink-properties'): symlink_attrs = cifs_attrs['symlink-properties'] if symlink_attrs is not None: symlink_list = [symlink.get_content() for symlink in symlink_attrs.get_children()] return_value = { 'share': cifs_attrs.get_child_content('share-name'), 'path': cifs_attrs.get_child_content('path'), 'share_properties': properties_list, 'symlink_properties': symlink_list } if cifs_attrs.get_child_by_name('vscan-fileop-profile'): return_value['vscan_fileop_profile'] = cifs_attrs['vscan-fileop-profile'] return return_value def create_cifs_share(self): """ Create CIFS share """ options = {'share-name': self.parameters.get('share_name'), 'path': self.parameters.get('path')} cifs_create = netapp_utils.zapi.NaElement.create_node_with_children( 'cifs-share-create', **options) if self.parameters.get('share_properties'): property_attrs = netapp_utils.zapi.NaElement('share-properties') cifs_create.add_child_elem(property_attrs) for property in self.parameters.get('share_properties'): property_attrs.add_new_child('cifs-share-properties', property) if self.parameters.get('symlink_properties'): symlink_attrs = netapp_utils.zapi.NaElement('symlink-properties') cifs_create.add_child_elem(symlink_attrs) for symlink in self.parameters.get('symlink_properties'): symlink_attrs.add_new_child('cifs-share-symlink-properties', symlink) if self.parameters.get('vscan_fileop_profile'): fileop_attrs = netapp_utils.zapi.NaElement('vscan-fileop-profile') fileop_attrs.set_content(self.parameters['vscan_fileop_profile']) cifs_create.add_child_elem(fileop_attrs) try: self.server.invoke_successfully(cifs_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating cifs-share %s: %s' % (self.parameters.get('share_name'), to_native(error)), exception=traceback.format_exc()) def delete_cifs_share(self): """ Delete CIFS share """ cifs_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'cifs-share-delete', **{'share-name': self.parameters.get('share_name')}) try: self.server.invoke_successfully(cifs_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting cifs-share %s: %s' % (self.parameters.get('share_name'), to_native(error)), exception=traceback.format_exc()) def modify_cifs_share(self): """ modify path for the given CIFS share """ options = {'share-name': self.parameters.get('share_name')} cifs_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'cifs-share-modify', **options) if self.parameters.get('path'): cifs_modify.add_new_child('path', self.parameters.get('path')) if self.parameters.get('share_properties'): property_attrs = netapp_utils.zapi.NaElement('share-properties') cifs_modify.add_child_elem(property_attrs) for property in self.parameters.get('share_properties'): property_attrs.add_new_child('cifs-share-properties', property) if self.parameters.get('symlink_properties'): symlink_attrs = netapp_utils.zapi.NaElement('symlink-properties') cifs_modify.add_child_elem(symlink_attrs) for property in self.parameters.get('symlink_properties'): symlink_attrs.add_new_child('cifs-share-symlink-properties', property) if self.parameters.get('vscan_fileop_profile'): fileop_attrs = netapp_utils.zapi.NaElement('vscan-fileop-profile') fileop_attrs.set_content(self.parameters['vscan_fileop_profile']) cifs_modify.add_child_elem(fileop_attrs) try: self.server.invoke_successfully(cifs_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying cifs-share %s:%s' % (self.parameters.get('share_name'), to_native(error)), exception=traceback.format_exc()) def apply(self): '''Apply action to cifs share''' netapp_utils.ems_log_event("na_ontap_cifs", self.server) current = self.get_cifs_share() cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None: modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_cifs_share() elif cd_action == 'delete': self.delete_cifs_share() elif modify: self.modify_cifs_share() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapNetRoutes(object): """ Create, Modifies and Destroys a Net Route """ def __init__(self): """ Initialize the Ontap Net Route class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, choices=[ 'present', 'absent'], default='present'), vserver=dict(required=True, type='str'), destination=dict(required=True, type='str'), gateway=dict(required=True, type='str'), metric=dict(required=False, type='str'), from_destination=dict(required=False, type='str', default=None), from_gateway=dict(required=False, type='str', default=None), from_metric=dict(required=False, type='str', default=None), )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json(msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) return def create_net_route(self, current_metric=None): """ Creates a new Route """ route_obj = netapp_utils.zapi.NaElement('net-routes-create') route_obj.add_new_child("destination", self.parameters['destination']) route_obj.add_new_child("gateway", self.parameters['gateway']) if current_metric is None and self.parameters.get('metric') is not None: metric = self.parameters['metric'] else: metric = current_metric # Metric can be None, Can't set metric to none if metric is not None: route_obj.add_new_child("metric", metric) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating net route: %s' % (to_native(error)), exception=traceback.format_exc()) def delete_net_route(self, params=None): """ Deletes a given Route """ route_obj = netapp_utils.zapi.NaElement('net-routes-destroy') if params is None: params = self.parameters route_obj.add_new_child("destination", params['destination']) route_obj.add_new_child("gateway", params['gateway']) try: self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting net route: %s' % (to_native(error)), exception=traceback.format_exc()) def modify_net_route(self, current, desired): """ Modify a net route """ # return if there is nothing to change for key, val in desired.items(): if val != current[key]: self.na_helper.changed = True break if not self.na_helper.changed: return # delete and re-create with new params self.delete_net_route(current) route_obj = netapp_utils.zapi.NaElement('net-routes-create') for attribute in ['metric', 'destination', 'gateway']: if desired.get(attribute) is not None: value = desired[attribute] else: value = current[attribute] route_obj.add_new_child(attribute, value) try: result = self.server.invoke_successfully(route_obj, True) except netapp_utils.zapi.NaApiError as error: # restore the old route, create the route with the existing metric self.create_net_route(current['metric']) # return if desired route already exists if to_native(error.code) == '13001': return # Invalid value specified for any of the attributes self.module.fail_json(msg='Error modifying net route: %s' % (to_native(error)), exception=traceback.format_exc()) def get_net_route(self, params=None): """ Checks to see if a route exist or not :return: NaElement object if a route exists, None otherwise """ if params is not None: # we need at least on of the new_destination or new_gateway to fetch desired route if params.get('destination') is None and params.get('gateway') is None: return None current = None route_obj = netapp_utils.zapi.NaElement('net-routes-get') for attr in ['destination', 'gateway']: if params and params.get(attr) is not None: value = params[attr] else: value = self.parameters[attr] route_obj.add_new_child(attr, value) try: result = self.server.invoke_successfully(route_obj, True) if result.get_child_by_name('attributes') is not None: route_info = result.get_child_by_name('attributes').get_child_by_name('net-vs-routes-info') current = { 'destination': route_info.get_child_content('destination'), 'gateway': route_info.get_child_content('gateway'), 'metric': route_info.get_child_content('metric') } except netapp_utils.zapi.NaApiError as error: # Error 13040 denotes a route doesn't exist. if to_native(error.code) == "15661": return None self.module.fail_json(msg='Error fetching net route: %s' % (to_native(error)), exception=traceback.format_exc()) return current def is_modify_action(self, current, desired): """ Get desired action to be applied for net routes Destination and gateway are unique params for a route and cannot be duplicated So if a route with desired destination or gateway exists already, we don't try to modify :param current: current details :param desired: desired details :return: create / delete / modify / None """ if current is None and desired is None: # this is invalid # cannot modify a non existent resource return None if current is None and desired is not None: # idempotency or duplication # we need not create return False if current is not None and desired is not None: # we can't modify an ambiguous route (idempotency/duplication) return False return True def get_params_to_be_modified(self, current): """ Get parameters and values that need to be modified :param current: current details :return: dict(), None """ if current is None: return None desired = dict() if self.parameters.get('new_destination') is not None and \ self.parameters['new_destination'] != current['destination']: desired['destination'] = self.parameters['new_destination'] if self.parameters.get('new_gateway') is not None and \ self.parameters['new_gateway'] != current['gateway']: desired['gateway'] = self.parameters['new_gateway'] if self.parameters.get('new_metric') is not None and \ self.parameters['new_metric'] != current['metric']: desired['metric'] = self.parameters['new_metric'] return desired def apply(self): """ Run Module based on play book """ netapp_utils.ems_log_event("na_ontap_net_routes", self.server) current = self.get_net_route() modify, cd_action = None, None modify_params = {'destination': self.parameters.get('from_destination'), 'gateway': self.parameters.get('from_gateway'), 'metric': self.parameters.get('from_metric')} # if any from_* param is present in playbook, check for modify action if any(modify_params.values()): # destination and gateway combination is unique, and is considered like a id. so modify destination # or gateway is considered a rename action. metric is considered an attribute of the route so it is # considered as modify. if modify_params.get('metric') is not None: modify = True old_params = current else: # get parameters that are eligible for modify old_params = self.get_net_route(modify_params) modify = self.na_helper.is_rename_action(old_params, current) if modify is None: self.module.fail_json(msg="Error modifying: route %s does not exist" % self.parameters['from_destination']) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create': self.create_net_route() elif cd_action == 'delete': self.delete_net_route() elif modify: desired = {} for key, value in old_params.items(): desired[key] = value for key, value in modify_params.items(): if value is not None: desired[key] = self.parameters.get(key) self.modify_net_route(old_params, desired) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSnapshotPolicy(object): """ Creates and deletes a Snapshot Policy """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type="str"), enabled=dict(required=False, type="bool"), # count is a list of integers count=dict(required=False, type="list", elements="int"), comment=dict(required=False, type="str"), schedule=dict(required=False, type="list", elements="str"), snapmirror_label=dict(required=False, type="list", elements="str"), vserver=dict(required=False, type="str"))) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[ ('state', 'present', ['enabled', 'count', 'schedule']), ], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: if 'vserver' in self.parameters: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) return def get_snapshot_policy(self): """ Checks to see if a snapshot policy exists or not :return: Return policy details if a snapshot policy exists, None if it doesn't """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-policy-get-iter") # compose query query = netapp_utils.zapi.NaElement("query") snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-policy-info") snapshot_info_obj.add_new_child("policy", self.parameters['name']) if 'vserver' in self.parameters: snapshot_info_obj.add_new_child("vserver-name", self.parameters['vserver']) query.add_child_elem(snapshot_info_obj) snapshot_obj.add_child_elem(query) try: result = self.server.invoke_successfully(snapshot_obj, True) if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: snapshot_policy = result.get_child_by_name( 'attributes-list').get_child_by_name( 'snapshot-policy-info') current = {} current['name'] = snapshot_policy.get_child_content('policy') current['vserver'] = snapshot_policy.get_child_content( 'vserver-name') current[ 'enabled'] = False if snapshot_policy.get_child_content( 'enabled').lower() == 'false' else True current['comment'] = snapshot_policy.get_child_content( 'comment') or '' current['schedule'], current['count'], current[ 'snapmirror_label'] = [], [], [] if snapshot_policy.get_child_by_name( 'snapshot-policy-schedules'): for schedule in snapshot_policy[ 'snapshot-policy-schedules'].get_children(): current['schedule'].append( schedule.get_child_content('schedule')) current['count'].append( int(schedule.get_child_content('count'))) snapmirror_label = schedule.get_child_content( 'snapmirror-label') if snapmirror_label is None or snapmirror_label == '-': snapmirror_label = '' current['snapmirror_label'].append(snapmirror_label) return current except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg=to_native(error), exception=traceback.format_exc()) return None def validate_parameters(self): """ Validate if each schedule has a count associated :return: None """ if 'count' not in self.parameters or 'schedule' not in self.parameters or \ len(self.parameters['count']) > 5 or len(self.parameters['schedule']) > 5 or \ len(self.parameters['count']) < 1 or len(self.parameters['schedule']) < 1 or \ len(self.parameters['count']) != len(self.parameters['schedule']): self.module.fail_json( msg="Error: A Snapshot policy must have at least 1 " "schedule and can have up to a maximum of 5 schedules, with a count " "representing the maximum number of Snapshot copies for each schedule" ) if 'snapmirror_label' in self.parameters: if len(self.parameters['snapmirror_label']) != len( self.parameters['schedule']): self.module.fail_json( msg="Error: Each Snapshot Policy schedule must have an " "accompanying SnapMirror Label") def modify_snapshot_policy(self, current): """ Modifies an existing snapshot policy """ # Set up required variables to modify snapshot policy options = {'policy': self.parameters['name']} modify = False # Set up optional variables to modify snapshot policy if 'enabled' in self.parameters and self.parameters[ 'enabled'] != current['enabled']: options['enabled'] = str(self.parameters['enabled']) modify = True if 'comment' in self.parameters and self.parameters[ 'comment'] != current['comment']: options['comment'] = self.parameters['comment'] modify = True if modify: snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children( 'snapshot-policy-modify', **options) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying snapshot policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_snapshot_policy_schedules(self, current): """ Modify existing schedules in snapshot policy :return: None """ self.validate_parameters() delete_schedules, modify_schedules, add_schedules = [], [], [] if 'snapmirror_label' in self.parameters: snapmirror_labels = self.parameters['snapmirror_label'] else: # User hasn't supplied any snapmirror labels. snapmirror_labels = [None] * len(self.parameters['schedule']) # Identify schedules for deletion for schedule in current['schedule']: schedule = schedule.strip() if schedule not in [ item.strip() for item in self.parameters['schedule'] ]: options = {'policy': current['name'], 'schedule': schedule} delete_schedules.append(options) # Identify schedules to be modified or added for schedule, count, snapmirror_label in zip( self.parameters['schedule'], self.parameters['count'], snapmirror_labels): schedule = schedule.strip() if snapmirror_label is not None: snapmirror_label = snapmirror_label.strip() options = {'policy': current['name'], 'schedule': schedule} if schedule in current['schedule']: # Schedule exists. Only modify if it has changed. modify = False schedule_index = current['schedule'].index(schedule) if count != current['count'][schedule_index]: options['new-count'] = str(count) modify = True if snapmirror_label is not None: if snapmirror_label != current['snapmirror_label'][ schedule_index]: options['new-snapmirror-label'] = snapmirror_label modify = True if modify: modify_schedules.append(options) else: # New schedule options['count'] = str(count) if snapmirror_label is not None and snapmirror_label != '': options['snapmirror-label'] = snapmirror_label add_schedules.append(options) # Delete N-1 schedules no longer required. Must leave 1 schedule in policy # at any one time. Delete last one afterwards. while len(delete_schedules) > 1: options = delete_schedules.pop() self.modify_snapshot_policy_schedule( options, 'snapshot-policy-remove-schedule') # Modify schedules. while len(modify_schedules) > 0: options = modify_schedules.pop() self.modify_snapshot_policy_schedule( options, 'snapshot-policy-modify-schedule') # Add N-1 new schedules. Add last one after last schedule has been deleted. while len(add_schedules) > 1: options = add_schedules.pop() self.modify_snapshot_policy_schedule( options, 'snapshot-policy-add-schedule') # Delete last schedule no longer required. while len(delete_schedules) > 0: options = delete_schedules.pop() self.modify_snapshot_policy_schedule( options, 'snapshot-policy-remove-schedule') # Add last new schedule. while len(add_schedules) > 0: options = add_schedules.pop() self.modify_snapshot_policy_schedule( options, 'snapshot-policy-add-schedule') def modify_snapshot_policy_schedule(self, options, zapi): """ Add, modify or remove a schedule to/from a snapshot policy """ snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children( zapi, **options) try: self.server.invoke_successfully(snapshot_obj, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying snapshot policy schedule %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_snapshot_policy(self): """ Creates a new snapshot policy """ # set up required variables to create a snapshot policy self.validate_parameters() options = { 'policy': self.parameters['name'], 'enabled': str(self.parameters['enabled']), } if 'snapmirror_label' in self.parameters: snapmirror_labels = self.parameters['snapmirror_label'] else: # User hasn't supplied any snapmirror labels. snapmirror_labels = [None] * len(self.parameters['schedule']) # zapi attribute for first schedule is schedule1, second is schedule2 and so on positions = [ str(i) for i in range(1, len(self.parameters['schedule']) + 1) ] for schedule, count, snapmirror_label, position in zip( self.parameters['schedule'], self.parameters['count'], snapmirror_labels, positions): schedule = schedule.strip() options['count' + position] = str(count) options['schedule' + position] = schedule if snapmirror_label is not None: snapmirror_label = snapmirror_label.strip() if snapmirror_label != '': options['snapmirror-label' + position] = snapmirror_label snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children( 'snapshot-policy-create', **options) # Set up optional variables to create a snapshot policy if self.parameters.get('comment'): snapshot_obj.add_new_child("comment", self.parameters['comment']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating snapshot policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_snapshot_policy(self): """ Deletes an existing snapshot policy """ snapshot_obj = netapp_utils.zapi.NaElement("snapshot-policy-delete") # Set up required variables to delete a snapshot policy snapshot_obj.add_new_child("policy", self.parameters['name']) try: self.server.invoke_successfully(snapshot_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting snapshot policy %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver) def apply(self): """ Check to see which play we should run """ self.asup_log_for_cserver("na_ontap_snapshot_policy") current = self.get_snapshot_policy() modify = None cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': # Don't sort schedule/count/snapmirror_label lists as it can # mess up the intended parameter order. modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_snapshot_policy() elif cd_action == 'delete': self.delete_snapshot_policy() if modify: self.modify_snapshot_policy(current) self.modify_snapshot_policy_schedules(current) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSubnet(object): """ Create, Modifies and Destroys a subnet """ def __init__(self): """ Initialize the ONTAP Subnet class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), broadcast_domain=dict(required=False, type='str'), gateway=dict(required=False, type='str'), ip_ranges=dict(required=False, type=list), ipspace=dict(required=False, type='str'), subnet=dict(required=False, type='str'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) return def get_subnet(self, name=None): """ Return details about the subnet :param: name : Name of the subnet :return: Details about the subnet. None if not found. :rtype: dict """ if name is None: name = self.parameters.get('name') subnet_iter = netapp_utils.zapi.NaElement('net-subnet-get-iter') subnet_info = netapp_utils.zapi.NaElement('net-subnet-info') subnet_info.add_new_child('subnet-name', name) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(subnet_info) subnet_iter.add_child_elem(query) result = self.server.invoke_successfully(subnet_iter, True) return_value = None # check if query returns the expected subnet if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: subnet_attributes = result.get_child_by_name( 'attributes-list').get_child_by_name('net-subnet-info') broadcast_domain = subnet_attributes.get_child_content( 'broadcast-domain') gateway = subnet_attributes.get_child_content('gateway') ipspace = subnet_attributes.get_child_content('ipspace') subnet = subnet_attributes.get_child_content('subnet') name = subnet_attributes.get_child_content('subnet-name') ip_ranges = [] range_obj = subnet_attributes.get_child_by_name( 'ip-ranges').get_children() for elem in range_obj: ip_ranges.append(elem.get_content()) return_value = { 'name': name, 'broadcast_domain': broadcast_domain, 'gateway': gateway, 'ip_ranges': ip_ranges, 'ipspace': ipspace, 'subnet': subnet } return return_value def create_subnet(self): """ Creates a new subnet """ options = { 'subnet-name': self.parameters.get('name'), 'broadcast-domain': self.parameters.get('broadcast_domain'), 'subnet': self.parameters.get('subnet') } subnet_create = netapp_utils.zapi.NaElement.create_node_with_children( 'net-subnet-create', **options) if self.parameters.get('gateway'): subnet_create.add_new_child('gateway', self.parameters.get('gateway')) if self.parameters.get('ip_ranges'): subnet_ips = netapp_utils.zapi.NaElement('ip-ranges') subnet_create.add_child_elem(subnet_ips) for ip_range in self.parameters.get('ip_ranges'): subnet_ips.add_new_child('ip-range', ip_range) if self.parameters.get('ipspace'): subnet_create.add_new_child('ipspace', self.parameters.get('ipspace')) try: self.server.invoke_successfully(subnet_create, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating subnet %s: %s' % (self.parameters.get('name'), to_native(error)), exception=traceback.format_exc()) def delete_subnet(self): """ Deletes a subnet """ subnet_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'net-subnet-destroy', **{'subnet-name': self.parameters.get('name')}) try: self.server.invoke_successfully(subnet_delete, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting subnet %s: %s' % (self.parameters.get('name'), to_native(error)), exception=traceback.format_exc()) def modify_subnet(self): """ Modifies a subnet """ options = {'subnet-name': self.parameters.get('name')} subnet_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'net-subnet-modify', **options) if self.parameters.get('gateway'): subnet_modify.add_new_child('gateway', self.parameters.get('gateway')) if self.parameters.get('ip_ranges'): subnet_ips = netapp_utils.zapi.NaElement('ip-ranges') subnet_modify.add_child_elem(subnet_ips) for ip_range in self.parameters.get('ip_ranges'): subnet_ips.add_new_child('ip-range', ip_range) if self.parameters.get('ipspace'): subnet_modify.add_new_child('ipspace', self.parameters.get('ipspace')) if self.parameters.get('subnet'): subnet_modify.add_new_child('subnet', self.parameters.get('subnet')) try: self.server.invoke_successfully(subnet_modify, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying subnet %s: %s' % (self.parameters.get('name'), to_native(error)), exception=traceback.format_exc()) def rename_subnet(self): """ TODO """ options = { 'subnet-name': self.parameters.get('from_name'), 'new-name': self.parameters.get('name') } subnet_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'net-subnet-rename', **options) if self.parameters.get('ipspace'): subnet_rename.add_new_child('ipspace', self.parameters.get('ipspace')) try: self.server.invoke_successfully(subnet_rename, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error renaming subnet %s: %s' % (self.parameters.get('name'), to_native(error)), exception=traceback.format_exc()) def apply(self): '''Apply action to subnet''' results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_net_subnet", cserver) current = self.get_subnet() cd_action, rename = None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_subnet(self.parameters.get('from_name')), current) if rename is False: self.module.fail_json( msg="Error renaming: subnet %s does not exist" % self.parameters.get('from_name')) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes( current, self.parameters) for attribute in modify: if attribute in ['broadcast_domain']: self.module.fail_json( msg= 'Error modifying subnet %s: cannot modify broadcast_domain parameter.' % self.parameters.get('name')) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_subnet() # If rename is True, cd_action is NOne but modify could be true if cd_action == 'create': for attribute in ['subnet', 'broadcast_domain']: if not self.parameters.get(attribute): self.module.fail_json( msg='Error - missing required arguments: %s.' % attribute) self.create_subnet() elif cd_action == 'delete': self.delete_subnet() elif modify: self.modify_subnet() self.module.exit_json(changed=self.na_helper.changed)
class ElementSWVolumePair(object): ''' class to handle volume pairing operations ''' def __init__(self): """ Setup Ansible parameters and SolidFire connection """ self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), src_volume=dict(required=True, type='str'), src_account=dict(required=True, type='str'), dest_volume=dict(required=True, type='str'), dest_account=dict(required=True, type='str'), mode=dict(required=False, type='str', choices=['async', 'sync', 'snapshotsonly'], default='async'), dest_mvip=dict(required=True, type='str'), dest_username=dict(required=False, type='str'), dest_password=dict(required=False, type='str', no_log=True))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) if HAS_SF_SDK is False: self.module.fail_json( msg="Unable to import the SolidFire Python SDK") else: self.elem = netapp_utils.create_sf_connection(module=self.module) self.elementsw_helper = NaElementSWModule(self.elem) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # get element_sw_connection for destination cluster # overwrite existing source host, user and password with destination credentials self.module.params['hostname'] = self.parameters['dest_mvip'] # username and password is same as source, # if dest_username and dest_password aren't specified if self.parameters.get('dest_username'): self.module.params['username'] = self.parameters['dest_username'] if self.parameters.get('dest_password'): self.module.params['password'] = self.parameters['dest_password'] self.dest_elem = netapp_utils.create_sf_connection(module=self.module) self.dest_elementsw_helper = NaElementSWModule(self.dest_elem) def check_if_already_paired(self, vol_id): """ Check for idempotency A volume can have only one pair Return paired-volume-id if volume is paired already None if volume is not paired """ paired_volumes = self.elem.list_volumes(volume_ids=[vol_id], is_paired=True) for vol in paired_volumes.volumes: for pair in vol.volume_pairs: if pair is not None: return pair.remote_volume_id return None def pair_volumes(self): """ Start volume pairing on source, and complete on target volume """ try: pair_key = self.elem.start_volume_pairing( volume_id=self.parameters['src_vol_id'], mode=self.parameters['mode']) self.dest_elem.complete_volume_pairing( volume_pairing_key=pair_key.volume_pairing_key, volume_id=self.parameters['dest_vol_id']) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error pairing volume id %s" % (self.parameters['src_vol_id']), exception=to_native(err)) def pairing_exists(self, src_id, dest_id): src_paired = self.check_if_already_paired( self.parameters['src_vol_id']) dest_paired = self.check_if_already_paired( self.parameters['dest_vol_id']) if src_paired is not None or dest_paired is not None: return True return None def unpair_volumes(self): """ Delete volume pair """ try: self.elem.remove_volume_pair( volume_id=self.parameters['src_vol_id']) self.dest_elem.remove_volume_pair( volume_id=self.parameters['dest_vol_id']) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error unpairing volume ids %s and %s" % (self.parameters['src_vol_id'], self.parameters['dest_vol_id']), exception=to_native(err)) def get_account_id(self, account, type): """ Get source and destination account IDs """ try: if type == 'src': self.parameters[ 'src_account_id'] = self.elementsw_helper.account_exists( account) elif type == 'dest': self.parameters[ 'dest_account_id'] = self.dest_elementsw_helper.account_exists( account) except solidfire.common.ApiServerError as err: self.module.fail_json( msg="Error: either account %s or %s does not exist" % (self.parameters['src_account'], self.parameters['dest_account']), exception=to_native(err)) def get_volume_id(self, volume, type): """ Get source and destination volume IDs """ if type == 'src': self.parameters[ 'src_vol_id'] = self.elementsw_helper.volume_exists( volume, self.parameters['src_account_id']) if self.parameters['src_vol_id'] is None: self.module.fail_json( msg="Error: source volume %s does not exist" % (self.parameters['src_volume'])) elif type == 'dest': self.parameters[ 'dest_vol_id'] = self.dest_elementsw_helper.volume_exists( volume, self.parameters['dest_account_id']) if self.parameters['dest_vol_id'] is None: self.module.fail_json( msg="Error: destination volume %s does not exist" % (self.parameters['dest_volume'])) def get_ids(self): """ Get IDs for volumes and accounts """ self.get_account_id(self.parameters['src_account'], 'src') self.get_account_id(self.parameters['dest_account'], 'dest') self.get_volume_id(self.parameters['src_volume'], 'src') self.get_volume_id(self.parameters['dest_volume'], 'dest') def apply(self): """ Call create / delete volume pair methods """ self.get_ids() paired = self.pairing_exists(self.parameters['src_vol_id'], self.parameters['dest_vol_id']) # calling helper to determine action cd_action = self.na_helper.get_cd_action(paired, self.parameters) if cd_action == "create": self.pair_volumes() elif cd_action == "delete": self.unpair_volumes() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapServiceProcessorNetwork(object): """ Modify a Service Processor Network """ def __init__(self): """ Initialize the NetAppOntapServiceProcessorNetwork class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present'], default='present'), address_type=dict(required=True, choices=['ipv4', 'ipv6']), is_enabled=dict(required=True, type='bool'), node=dict(required=True, type='str'), dhcp=dict(required=False, choices=['v4', 'none']), gateway_ip_address=dict(required=False, type='str'), ip_address=dict(required=False, type='str'), netmask=dict(required=False, type='str'), prefix_length=dict(required=False, type='int'), wait_for_completion=dict(required=False, type='bool', default=False))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.set_playbook_zapi_key_map() if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=None) return def set_playbook_zapi_key_map(self): self.na_helper.zapi_string_keys = { 'address_type': 'address-type', 'node': 'node', 'dhcp': 'dhcp', 'gateway_ip_address': 'gateway-ip-address', 'ip_address': 'ip-address', 'netmask': 'netmask' } self.na_helper.zapi_int_keys = {'prefix_length': 'prefix-length'} self.na_helper.zapi_bool_keys = { 'is_enabled': 'is-enabled', } self.na_helper.zapi_required = { 'address_type': 'address-type', 'node': 'node', 'is_enabled': 'is-enabled' } def get_sp_network_status(self): """ Return status of service processor network :param: name : name of the node :return: Status of the service processor network :rtype: dict """ spn_get_iter = netapp_utils.zapi.NaElement( 'service-processor-network-get-iter') query_info = { 'query': { 'service-processor-network-info': { 'node': self.parameters['node'], 'address-type': self.parameters['address_type'] } } } spn_get_iter.translate_struct(query_info) result = self.server.invoke_successfully(spn_get_iter, True) if int(result['num-records']) >= 1: sp_attr_info = result['attributes-list'][ 'service-processor-network-info'] return sp_attr_info.get_child_content('setup-status') return None def get_service_processor_network(self): """ Return details about service processor network :param: name : name of the node :return: Details about service processor network. None if not found. :rtype: dict """ spn_get_iter = netapp_utils.zapi.NaElement( 'service-processor-network-get-iter') query_info = { 'query': { 'service-processor-network-info': { 'node': self.parameters['node'] } } } spn_get_iter.translate_struct(query_info) result = self.server.invoke_successfully(spn_get_iter, True) sp_details = None # check if job exists if int(result['num-records']) >= 1: sp_details = dict() sp_attr_info = result['attributes-list'][ 'service-processor-network-info'] for item_key, zapi_key in self.na_helper.zapi_string_keys.items(): sp_details[item_key] = sp_attr_info.get_child_content(zapi_key) for item_key, zapi_key in self.na_helper.zapi_bool_keys.items(): sp_details[item_key] = self.na_helper.get_value_for_bool( from_zapi=True, value=sp_attr_info.get_child_content(zapi_key)) for item_key, zapi_key in self.na_helper.zapi_int_keys.items(): sp_details[item_key] = self.na_helper.get_value_for_int( from_zapi=True, value=sp_attr_info.get_child_content(zapi_key)) return sp_details def modify_service_processor_network(self, params=None): """ Modify a service processor network. :param params: A dict of modified options. When dhcp is not set to v4, ip_address, netmask, and gateway_ip_address must be specified even if remains the same. """ if self.parameters['is_enabled'] is False: if params.get('is_enabled') and len(params) > 1: self.module.fail_json( msg= 'Error: Cannot modify any other parameter for a service processor network if option "is_enabled" is set to false.' ) elif params.get('is_enabled') is None and len(params) > 0: self.module.fail_json( msg= 'Error: Cannot modify a service processor network if it is disabled.' ) sp_modify = netapp_utils.zapi.NaElement( 'service-processor-network-modify') sp_modify.add_new_child("node", self.parameters['node']) sp_modify.add_new_child("address-type", self.parameters['address_type']) sp_attributes = dict() for item_key in self.parameters: if item_key in self.na_helper.zapi_string_keys: zapi_key = self.na_helper.zapi_string_keys.get(item_key) sp_attributes[zapi_key] = self.parameters[item_key] elif item_key in self.na_helper.zapi_bool_keys: zapi_key = self.na_helper.zapi_bool_keys.get(item_key) sp_attributes[zapi_key] = self.na_helper.get_value_for_bool( from_zapi=False, value=self.parameters[item_key]) elif item_key in self.na_helper.zapi_int_keys: zapi_key = self.na_helper.zapi_int_keys.get(item_key) sp_attributes[zapi_key] = self.na_helper.get_value_for_int( from_zapi=False, value=self.parameters[item_key]) sp_modify.translate_struct(sp_attributes) try: self.server.invoke_successfully(sp_modify, enable_tunneling=True) if self.parameters.get('wait_for_completion'): retries = 10 while self.get_sp_network_status( ) == 'in_progress' and retries > 0: time.sleep(10) retries = retries - 1 except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying service processor network: %s' % (to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_service_processor_network", cserver) def apply(self): """ Run Module based on play book """ self.autosupport_log() current = self.get_service_processor_network() modify = self.na_helper.get_modified_attributes( current, self.parameters) if not current: self.module.fail_json( msg='Error No Service Processor for node: %s' % self.parameters['node']) if self.na_helper.changed: if self.module.check_mode: pass else: self.modify_service_processor_network(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPNdmp(object): ''' modify vserver cifs security ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.modifiable_options = dict( abort_on_disk_error=dict(required=False, type='bool'), authtype=dict(required=False, type='list'), backup_log_enable=dict(required=False, type='bool'), data_port_range=dict(required=False, type='str'), debug_enable=dict(required=False, type='bool'), debug_filter=dict(required=False, type='str'), dump_detailed_stats=dict(required=False, type='bool'), dump_logical_find=dict(required=False, type='str'), enable=dict(required=False, type='bool'), fh_dir_retry_interval=dict(required=False, type='int'), fh_node_retry_interval=dict(required=False, type='int'), ignore_ctime_enabled=dict(required=False, type='bool'), is_secure_control_connection_enabled=dict(required=False, type='bool'), offset_map_enable=dict(required=False, type='bool'), per_qtree_exclude_enable=dict(required=False, type='bool'), preferred_interface_role=dict(required=False, type='list'), restore_vm_cache_size=dict(required=False, type='int'), secondary_debug_filter=dict(required=False, type='str'), tcpnodelay=dict(required=False, type='bool'), tcpwinsize=dict(required=False, type='int')) self.argument_spec.update( dict(vserver=dict(required=True, type='str'))) self.argument_spec.update(self.modifiable_options) 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 ndmp_get_iter(self): """ get current vserver ndmp attributes. :return: a dict of ndmp attributes. """ ndmp_get = netapp_utils.zapi.NaElement( 'ndmp-vserver-attributes-get-iter') query = netapp_utils.zapi.NaElement('query') ndmp_info = netapp_utils.zapi.NaElement('ndmp-vserver-attributes-info') ndmp_info.add_new_child('vserver', self.parameters['vserver']) query.add_child_elem(ndmp_info) ndmp_get.add_child_elem(query) ndmp_details = dict() try: result = self.server.invoke_successfully(ndmp_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error fetching ndmp from %s: %s' % (self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) > 0: ndmp_attributes = result.get_child_by_name( 'attributes-list').get_child_by_name( 'ndmp-vserver-attributes-info') self.get_ndmp_details(ndmp_details, ndmp_attributes) return ndmp_details def get_ndmp_details(self, ndmp_details, ndmp_attributes): """ :param ndmp_details: a dict of current ndmp. :param ndmp_attributes: ndmp returned from api call in xml format. :return: None """ for option in self.modifiable_options.keys(): option_type = self.modifiable_options[option]['type'] if option_type == 'bool': ndmp_details[option] = self.str_to_bool( ndmp_attributes.get_child_content( self.attribute_to_name(option))) elif option_type == 'int': ndmp_details[option] = int( ndmp_attributes.get_child_content( self.attribute_to_name(option))) elif option_type == 'list': child_list = ndmp_attributes.get_child_by_name( self.attribute_to_name(option)) values = [ child.get_content() for child in child_list.get_children() ] ndmp_details[option] = values else: ndmp_details[option] = ndmp_attributes.get_child_content( self.attribute_to_name(option)) def modify_ndmp(self, modify): """ :param modify: A list of attributes to modify :return: None """ ndmp_modify = netapp_utils.zapi.NaElement( 'ndmp-vserver-attributes-modify') for attribute in modify: if attribute == 'authtype': authtypes = netapp_utils.zapi.NaElement('authtype') types = self.parameters['authtype'] for authtype in types: authtypes.add_new_child('ndmpd-authtypes', authtype) ndmp_modify.add_child_elem(authtypes) elif attribute == 'preferred_interface_role': preferred_interface_roles = netapp_utils.zapi.NaElement( 'preferred-interface-role') roles = self.parameters['preferred_interface_role'] for role in roles: preferred_interface_roles.add_new_child( 'netport-role', role) ndmp_modify.add_child_elem(preferred_interface_roles) else: ndmp_modify.add_new_child(self.attribute_to_name(attribute), str(self.parameters[attribute])) try: self.server.invoke_successfully(ndmp_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg='Error modifying ndmp on %s: %s' % (self.parameters['vserver'], to_native(e)), exception=traceback.format_exc()) @staticmethod def attribute_to_name(attribute): return str.replace(attribute, '_', '-') @staticmethod def str_to_bool(s): if s == 'true': return True else: return False def apply(self): """Call modify operations.""" self.asup_log_for_cserver("na_ontap_ndmp") current = self.ndmp_get_iter() modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if modify: self.modify_ndmp(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 NetAppOntapInterface(object): ''' object to describe interface info ''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, choices=[ 'present', 'absent'], default='present'), interface_name=dict(required=True, type='str'), home_node=dict(required=False, type='str', default=None), home_port=dict(required=False, type='str'), role=dict(required=False, type='str'), address=dict(required=False, type='str'), netmask=dict(required=False, type='str'), vserver=dict(required=True, type='str'), firewall_policy=dict(required=False, type='str', default=None), failover_policy=dict(required=False, type='str', default=None, choices=['disabled', 'system-defined', 'local-only', 'sfo-partner-only', 'broadcast-domain-wide']), admin_status=dict(required=False, choices=['up', 'down']), subnet_name=dict(required=False, type='str'), is_auto_revert=dict(required=False, type='bool', default=None), protocols=dict(required=False, type='list'), force_subnet_association=dict(required=False, type='bool', default=None), dns_domain_name=dict(required=False, type='str'), listen_for_dns_query=dict(required=False, type='bool'), is_dns_update_enabled=dict(required=False, type='bool') )) self.module = AnsibleModule( argument_spec=self.argument_spec, mutually_exclusive=[ ['subnet_name', 'address'], ['subnet_name', 'netmask'] ], supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def get_interface(self): """ Return details about the interface :param: name : Name of the name of the interface :return: Details about the interface. None if not found. :rtype: dict """ interface_info = netapp_utils.zapi.NaElement('net-interface-get-iter') interface_attributes = netapp_utils.zapi.NaElement('net-interface-info') interface_attributes.add_new_child('interface-name', self.parameters['interface_name']) interface_attributes.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(interface_attributes) interface_info.add_child_elem(query) result = self.server.invoke_successfully(interface_info, True) return_value = None if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) >= 1: interface_attributes = result.get_child_by_name('attributes-list').\ get_child_by_name('net-interface-info') return_value = { 'interface_name': self.parameters['interface_name'], 'admin_status': interface_attributes['administrative-status'], 'home_port': interface_attributes['home-port'], 'home_node': interface_attributes['home-node'], 'failover_policy': interface_attributes['failover-policy'].replace('_', '-'), 'is_auto_revert': True if interface_attributes['is-auto-revert'] == 'true' else False, } if interface_attributes.get_child_by_name('address'): return_value['address'] = interface_attributes['address'] if interface_attributes.get_child_by_name('netmask'): return_value['netmask'] = interface_attributes['netmask'] if interface_attributes.get_child_by_name('firewall-policy'): return_value['firewall_policy'] = interface_attributes['firewall-policy'] if interface_attributes.get_child_by_name('dns-domain-name') != 'none': return_value['dns_domain_name'] = interface_attributes['dns-domain-name'] else: return_value['dns_domain_name'] = None if interface_attributes.get_child_by_name('listen-for-dns-query'): return_value['listen_for_dns_query'] = self.na_helper.get_value_for_bool(True, interface_attributes['listen-for-dns-query']) if interface_attributes.get_child_by_name('is-dns-update-enabled'): return_value['is_dns_update_enabled'] = self.na_helper.get_value_for_bool(True, interface_attributes['is-dns-update-enabled']) return return_value @staticmethod def set_options(options, parameters): """ set attributes for create or modify """ if parameters.get('home_port') is not None: options['home-port'] = parameters['home_port'] if parameters.get('subnet_name') is not None: options['subnet-name'] = parameters['subnet_name'] if parameters.get('address') is not None: options['address'] = parameters['address'] if parameters.get('netmask') is not None: options['netmask'] = parameters['netmask'] if parameters.get('failover_policy') is not None: options['failover-policy'] = parameters['failover_policy'] if parameters.get('firewall_policy') is not None: options['firewall-policy'] = parameters['firewall_policy'] if parameters.get('is_auto_revert') is not None: options['is-auto-revert'] = 'true' if parameters['is_auto_revert'] is True else 'false' if parameters.get('admin_status') is not None: options['administrative-status'] = parameters['admin_status'] if parameters.get('force_subnet_association') is not None: options['force-subnet-association'] = 'true' if parameters['force_subnet_association'] else 'false' if parameters.get('dns_domain_name') is not None: options['dns-domain-name'] = parameters['dns_domain_name'] if parameters.get('listen_for_dns_query') is not None: options['listen-for-dns-query'] = str(parameters['listen_for_dns_query']) if parameters.get('is_dns_update_enabled') is not None: options['is-dns-update-enabled'] = str(parameters['is_dns_update_enabled']) def set_protocol_option(self, required_keys): """ set protocols for create """ if self.parameters.get('protocols') is not None: data_protocols_obj = netapp_utils.zapi.NaElement('data-protocols') for protocol in self.parameters.get('protocols'): if protocol.lower() in ['fc-nvme', 'fcp']: if 'address' in required_keys: required_keys.remove('address') if 'home_port' in required_keys: required_keys.remove('home_port') if 'netmask' in required_keys: required_keys.remove('netmask') not_required_params = set(['address', 'netmask', 'firewall_policy']) if not not_required_params.isdisjoint(set(self.parameters.keys())): self.module.fail_json(msg='Error: Following parameters for creating interface are not supported' ' for data-protocol fc-nvme: %s' % ', '.join(not_required_params)) data_protocols_obj.add_new_child('data-protocol', protocol) return data_protocols_obj return None def get_home_node_for_cluster(self): ''' get the first node name from this cluster ''' get_node = netapp_utils.zapi.NaElement('cluster-node-get-iter') attributes = { 'query': { 'cluster-node-info': {} } } get_node.translate_struct(attributes) try: result = self.server.invoke_successfully(get_node, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg='Error fetching node for interface %s: %s' % (self.parameters['interface_name'], to_native(exc)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1: attributes = result.get_child_by_name('attributes-list') return attributes.get_child_by_name('cluster-node-info').get_child_content('node-name') return None def validate_create_parameters(self, keys): ''' Validate if required parameters for create are present. Parameter requirement might vary based on given data-protocol. :return: None ''' if self.parameters.get('home_node') is None: node = self.get_home_node_for_cluster() if node is not None: self.parameters['home_node'] = node # validate if mandatory parameters are present for create if not keys.issubset(set(self.parameters.keys())) and self.parameters.get('subnet_name') is None: self.module.fail_json(msg='Error: Missing one or more required parameters for creating interface: %s' % ', '.join(keys)) # if role is intercluster, protocol cannot be specified if self.parameters['role'] == "intercluster" and self.parameters.get('protocols') is not None: self.module.fail_json(msg='Error: Protocol cannot be specified for intercluster role,' 'failed to create interface') def create_interface(self): ''' calling zapi to create interface ''' required_keys = set(['role', 'home_port']) data_protocols_obj = None if self.parameters.get('subnet_name') is None: required_keys.add('address') required_keys.add('netmask') data_protocols_obj = self.set_protocol_option(required_keys) self.validate_create_parameters(required_keys) options = {'interface-name': self.parameters['interface_name'], 'role': self.parameters['role'], 'home-node': self.parameters.get('home_node'), 'vserver': self.parameters['vserver']} NetAppOntapInterface.set_options(options, self.parameters) interface_create = netapp_utils.zapi.NaElement.create_node_with_children('net-interface-create', **options) if data_protocols_obj is not None: interface_create.add_child_elem(data_protocols_obj) try: self.server.invoke_successfully(interface_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg='Error Creating interface %s: %s' % (self.parameters['interface_name'], to_native(exc)), exception=traceback.format_exc()) def delete_interface(self, current_status): ''' calling zapi to delete interface ''' if current_status == 'up': self.parameters['admin_status'] = 'down' self.modify_interface({'admin_status': 'down'}) interface_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'net-interface-delete', **{'interface-name': self.parameters['interface_name'], 'vserver': self.parameters['vserver']}) try: self.server.invoke_successfully(interface_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg='Error deleting interface %s: %s' % (self.parameters['interface_name'], to_native(exc)), exception=traceback.format_exc()) def modify_interface(self, modify): """ Modify the interface. """ options = {'interface-name': self.parameters['interface_name'], 'vserver': self.parameters['vserver'] } NetAppOntapInterface.set_options(options, modify) interface_modify = netapp_utils.zapi.NaElement.create_node_with_children('net-interface-modify', **options) try: self.server.invoke_successfully(interface_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as err: self.module.fail_json(msg='Error modifying interface %s: %s' % (self.parameters['interface_name'], to_native(err)), exception=traceback.format_exc()) def autosupport_log(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_interface", cserver) def apply(self): ''' calling all interface features ''' self.autosupport_log() current = self.get_interface() # rename and create are mutually exclusive cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_interface() elif cd_action == 'delete': self.delete_interface(current['admin_status']) elif modify: self.modify_interface(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapLUNCopy(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'], default='present'), destination_vserver=dict(required=True, type='str'), destination_path=dict(required=True, type='str'), source_path=dict(required=True, type='str'), source_vserver=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['destination_vserver']) def get_lun(self): """ Check if the LUN exists :return: true is it exists, false otherwise :rtype: bool """ return_value = False lun_info = netapp_utils.zapi.NaElement('lun-get-iter') query_details = netapp_utils.zapi.NaElement('lun-info') query_details.add_new_child('path', self.parameters['destination_path']) query_details.add_new_child('vserver', self.parameters['destination_vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) lun_info.add_child_elem(query) try: result = self.server.invoke_successfully(lun_info, True) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg="Error getting lun info %s for verver %s: %s" % (self.parameters['destination_path'], self.parameters['destination_vserver'], to_native(e)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1: return_value = True return return_value def copy_lun(self): """ Copy LUN with requested path and vserver """ lun_copy = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-copy-start', **{'source-vserver': self.parameters['source_vserver']}) path_obj = netapp_utils.zapi.NaElement('paths') pair = netapp_utils.zapi.NaElement('lun-path-pair') pair.add_new_child('destination-path', self.parameters['destination_path']) pair.add_new_child('source-path', self.parameters['source_path']) path_obj.add_child_elem(pair) lun_copy.add_child_elem(path_obj) try: self.server.invoke_successfully(lun_copy, enable_tunneling=True) except netapp_utils.zapi.NaApiError as e: self.module.fail_json(msg="Error copying lun from %s to vserver %s: %s" % (self.parameters['source_vserver'], self.parameters['destination_vserver'], to_native(e)), exception=traceback.format_exc()) def apply(self): netapp_utils.ems_log_event("na_ontap_lun_copy", self.server) if self.get_lun(): # lun already exists at destination changed = False else: changed = True if self.module.check_mode: pass else: # need to copy lun if self.parameters['state'] == 'present': self.copy_lun() self.module.exit_json(changed=changed)
class NetAppAWSCVS(object): '''Class for Pool operations ''' def __init__(self): """ Parse arguments, setup state variables, """ self.argument_spec = netapp_utils.aws_cvs_host_argument_spec() self.argument_spec.update( dict( state=dict(required=True, choices=['present', 'absent']), region=dict(required=True, type='str'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), serviceLevel=dict(required=False, choices=['basic', 'standard', 'extreme'], type='str'), sizeInBytes=dict(required=False, type='int'), vendorID=dict(required=False, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.restApi = AwsCvsRestAPI(self.module) self.sizeInBytes_min_value = 4000000000000 def get_aws_netapp_cvs_pool(self, name=None): """ Returns Pool object if exists else Return None """ pool_info = None if name is None: name = self.parameters['name'] pools, error = self.restApi.get('Pools') if error is None and pools is not None: for pool in pools: if 'name' in pool and pool['region'] == self.parameters[ 'region']: if pool['name'] == name: pool_info = pool break return pool_info def create_aws_netapp_cvs_pool(self): """ Create a pool """ api = 'Pools' for key in ['serviceLevel', 'sizeInBytes', 'vendorID']: if key not in self.parameters.keys( ) or self.parameters[key] is None: self.module.fail_json(changed=False, msg="Mandatory key '%s' required" % (key)) pool = { "name": self.parameters['name'], "region": self.parameters['region'], "serviceLevel": self.parameters['serviceLevel'], "sizeInBytes": self.parameters['sizeInBytes'], "vendorID": self.parameters['vendorID'] } response, error = self.restApi.post(api, pool) if error is not None: self.module.fail_json(changed=False, msg=error) def update_aws_netapp_cvs_pool(self, update_pool_info, pool_id): """ Update a pool """ api = 'Pools/' + pool_id pool = { "name": update_pool_info['name'], "region": self.parameters['region'], "serviceLevel": update_pool_info['serviceLevel'], "sizeInBytes": update_pool_info['sizeInBytes'], "vendorID": update_pool_info['vendorID'] } response, error = self.restApi.put(api, pool) if error is not None: self.module.fail_json(changed=False, msg=error) def delete_aws_netapp_cvs_pool(self, pool_id): """ Delete a pool """ api = 'Pools/' + pool_id data = None response, error = self.restApi.delete(api, data) if error is not None: self.module.fail_json(changed=False, msg=error) def apply(self): """ Perform pre-checks, call functions and exit """ update_required = False cd_action = None if 'sizeInBytes' in self.parameters.keys( ) and self.parameters['sizeInBytes'] < self.sizeInBytes_min_value: self.module.fail_json( changed=False, msg="sizeInBytes should be greater than or equal to %d" % (self.sizeInBytes_min_value)) current = self.get_aws_netapp_cvs_pool() if self.parameters.get('from_name'): existing = self.get_aws_netapp_cvs_pool( self.parameters['from_name']) rename = self.na_helper.is_rename_action(existing, current) if rename is None: self.module.fail_json( changed=False, msg="unable to rename pool: '%s' does not exist" % self.parameters['from_name']) if rename: current = existing else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': keys_to_check = ['name', 'vendorID', 'sizeInBytes', 'serviceLevel'] update_pool_info, update_required = self.na_helper.compare_and_update_values( current, self.parameters, keys_to_check) if update_required is True: self.na_helper.changed = True cd_action = 'update' if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'update': self.update_aws_netapp_cvs_pool(update_pool_info, current['poolId']) elif cd_action == 'create': self.create_aws_netapp_cvs_pool() elif cd_action == 'delete': self.delete_aws_netapp_cvs_pool(current['poolId']) self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPQuotas(object): '''Class with quotas methods''' def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), vserver=dict(required=True, type='str'), volume=dict(required=True, type='str'), quota_target=dict(required=True, type='str'), qtree=dict(required=False, type='str', default=""), type=dict(required=True, type='str', choices=['user', 'group', 'tree']), policy=dict(required=False, type='str'), set_quota_status=dict(required=False, type='bool'), file_limit=dict(required=False, type='str', default='-'), disk_limit=dict(required=False, type='str', default='-'), threshold=dict(required=False, type='str', default='-'))) 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_quota_status(self): """ Return details about the quota status :param: name : volume name :return: status of the quota. None if not found. :rtype: dict """ quota_status_get = netapp_utils.zapi.NaElement('quota-status') quota_status_get.translate_struct( {'volume': self.parameters['volume']}) try: result = self.server.invoke_successfully(quota_status_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching quotas status info: %s' % to_native(error), exception=traceback.format_exc()) if result: return result['status'] return None def get_quotas(self): """ Get quota details :return: name of volume if quota exists, None otherwise """ quota_get = netapp_utils.zapi.NaElement('quota-list-entries-iter') query = { 'query': { 'quota-entry': { 'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'vserver': self.parameters['vserver'] } } } quota_get.translate_struct(query) if self.parameters.get('policy'): quota_get['query']['quota-entry'].add_new_child( 'policy', self.parameters['policy']) try: result = self.server.invoke_successfully(quota_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching quotas info: %s' % to_native(error), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: return_values = { 'volume': result['attributes-list']['quota-entry']['volume'], 'file_limit': result['attributes-list']['quota-entry']['file-limit'], 'disk_limit': result['attributes-list']['quota-entry']['disk-limit'], 'threshold': result['attributes-list']['quota-entry']['threshold'] } return return_values return None def quota_entry_set(self): """ Adds a quota entry """ options = { 'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree'], 'file-limit': self.parameters['file_limit'], 'disk-limit': self.parameters['disk_limit'], 'threshold': self.parameters['threshold'] } if self.parameters.get('policy'): options['policy'] = self.parameters['policy'] set_entry = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-set-entry', **options) try: self.server.invoke_successfully(set_entry, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error adding/modifying quota entry %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def quota_entry_delete(self): """ Deletes a quota entry """ options = { 'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree'] } set_entry = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-delete-entry', **options) if self.parameters.get('policy'): set_entry.add_new_child('policy', self.parameters['policy']) try: self.server.invoke_successfully(set_entry, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting quota entry %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def quota_entry_modify(self, modify_attrs): """ Modifies a quota entry """ options = { 'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree'] } options.update(modify_attrs) if self.parameters.get('policy'): options['policy'] = str(self.parameters['policy']) modify_entry = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-modify-entry', **options) try: self.server.invoke_successfully(modify_entry, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying quota entry %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def on_or_off_quota(self, status): """ on or off quota """ quota = netapp_utils.zapi.NaElement.create_node_with_children( status, **{'volume': self.parameters['volume']}) try: self.server.invoke_successfully(quota, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error setting %s for %s: %s' % (status, self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to quotas """ netapp_utils.ems_log_event("na_ontap_quotas", self.server) modify_quota_status = None modify_quota = None current = self.get_quotas() if 'set_quota_status' in self.parameters: quota_status = self.get_quota_status() if quota_status is not None: quota_status_action = self.na_helper.get_modified_attributes( { 'set_quota_status': True if quota_status == 'on' else False }, self.parameters) if quota_status_action: modify_quota_status = 'quota-on' if quota_status_action[ 'set_quota_status'] else 'quota-off' cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None: modify_quota = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.quota_entry_set() elif cd_action == 'delete': self.quota_entry_delete() elif modify_quota is not None: for key in list(modify_quota): modify_quota[key.replace("_", "-")] = modify_quota.pop(key) self.quota_entry_modify(modify_quota) if modify_quota_status is not None: self.on_or_off_quota(modify_quota_status) self.module.exit_json(changed=self.na_helper.changed)
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 ElementSWClusterConfig(object): """ Element Software Configure Element SW Cluster """ def __init__(self): self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update( dict(modify_cluster_full_threshold=dict( type='dict', options=dict(stage2_aware_threshold=dict(type='int', default=None), stage3_block_threshold_percent=dict(type='int', default=None), max_metadata_over_provision_factor=dict( type='int', default=None))), encryption_at_rest=dict(type='str', choices=['present', 'absent']), set_ntp_info=dict(type='dict', options=dict( broadcastclient=dict(type='bool', default=False), ntp_servers=dict(type='list'))), enable_virtual_volumes=dict(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 HAS_SF_SDK is False: self.module.fail_json( msg="Unable to import the SolidFire Python SDK") else: self.sfe = netapp_utils.create_sf_connection(module=self.module) def get_ntp_details(self): """ get ntp info """ # Get ntp details ntp_details = self.sfe.get_ntp_info() return ntp_details def cmp(self, provided_ntp_servers, existing_ntp_servers): # As python3 doesn't have default cmp function, defining manually to provide same functionality. return (provided_ntp_servers > existing_ntp_servers) - ( provided_ntp_servers < existing_ntp_servers) def get_cluster_details(self): """ get cluster info """ cluster_details = self.sfe.get_cluster_info() return cluster_details def get_vvols_status(self): """ get vvols status """ feature_status = self.sfe.get_feature_status(feature='vvols') if feature_status is not None: return feature_status.features[0].enabled return None def get_cluster_full_threshold_status(self): """ get cluster full threshold """ cluster_full_threshold_status = self.sfe.get_cluster_full_threshold() return cluster_full_threshold_status def setup_ntp_info(self, servers, broadcastclient=None): """ configure ntp """ # Set ntp servers try: self.sfe.set_ntp_info(servers, broadcastclient) except Exception as exception_object: self.module.fail_json(msg='Error configuring ntp %s' % (to_native(exception_object)), exception=traceback.format_exc()) def set_encryption_at_rest(self, state=None): """ enable/disable encryption at rest """ try: if state == 'present': encryption_state = 'enable' self.sfe.enable_encryption_at_rest() elif state == 'absent': encryption_state = 'disable' self.sfe.disable_encryption_at_rest() except Exception as exception_object: self.module.fail_json( msg='Failed to %s rest encryption %s' % (encryption_state, to_native(exception_object)), exception=traceback.format_exc()) def enable_feature(self, feature): """ enable feature """ try: self.sfe.enable_feature(feature=feature) except Exception as exception_object: self.module.fail_json(msg='Error enabling %s %s' % (feature, to_native(exception_object)), exception=traceback.format_exc()) def set_cluster_full_threshold(self, stage2_aware_threshold=None, stage3_block_threshold_percent=None, max_metadata_over_provision_factor=None): """ modify cluster full threshold """ try: self.sfe.modify_cluster_full_threshold( stage2_aware_threshold=stage2_aware_threshold, stage3_block_threshold_percent=stage3_block_threshold_percent, max_metadata_over_provision_factor= max_metadata_over_provision_factor) except Exception as exception_object: self.module.fail_json( msg='Failed to modify cluster full threshold %s' % (to_native(exception_object)), exception=traceback.format_exc()) def apply(self): """ Cluster configuration """ changed = False result_message = None if self.parameters.get('modify_cluster_full_threshold') is not None: # get cluster full threshold cluster_full_threshold_details = self.get_cluster_full_threshold_status( ) # maxMetadataOverProvisionFactor current_mmopf = cluster_full_threshold_details.max_metadata_over_provision_factor # stage3BlockThresholdPercent current_s3btp = cluster_full_threshold_details.stage3_block_threshold_percent # stage2AwareThreshold current_s2at = cluster_full_threshold_details.stage2_aware_threshold # is cluster full threshold state change required? if self.parameters.get("modify_cluster_full_threshold")['max_metadata_over_provision_factor'] is not None and \ current_mmopf != self.parameters['modify_cluster_full_threshold']['max_metadata_over_provision_factor'] or \ self.parameters.get("modify_cluster_full_threshold")['stage3_block_threshold_percent'] is not None and \ current_s3btp != self.parameters['modify_cluster_full_threshold']['stage3_block_threshold_percent'] or \ self.parameters.get("modify_cluster_full_threshold")['stage2_aware_threshold'] is not None and \ current_s2at != self.parameters['modify_cluster_full_threshold']['stage2_aware_threshold']: changed = True self.set_cluster_full_threshold( self.parameters['modify_cluster_full_threshold'] ['stage2_aware_threshold'], self.parameters['modify_cluster_full_threshold'] ['stage3_block_threshold_percent'], self.parameters['modify_cluster_full_threshold'] ['max_metadata_over_provision_factor']) if self.parameters.get('encryption_at_rest') is not None: # get all cluster info cluster_info = self.get_cluster_details() # register rest state current_encryption_at_rest_state = cluster_info.cluster_info.encryption_at_rest_state # is encryption state change required? if current_encryption_at_rest_state == 'disabled' and self.parameters['encryption_at_rest'] == 'present' or \ current_encryption_at_rest_state == 'enabled' and self.parameters['encryption_at_rest'] == 'absent': changed = True self.set_encryption_at_rest( self.parameters['encryption_at_rest']) if self.parameters.get('set_ntp_info') is not None: # get all ntp details ntp_details = self.get_ntp_details() # register list of ntp servers ntp_servers = ntp_details.servers # broadcastclient broadcast_client = ntp_details.broadcastclient # has either the broadcastclient or the ntp server list changed? if self.parameters.get('set_ntp_info')['broadcastclient'] != broadcast_client or \ self.cmp(self.parameters.get('set_ntp_info')['ntp_servers'], ntp_servers) != 0: changed = True self.setup_ntp_info( self.parameters.get('set_ntp_info')['ntp_servers'], self.parameters.get('set_ntp_info')['broadcastclient']) if self.parameters.get('enable_virtual_volumes') is not None: # check vvols status current_vvols_status = self.get_vvols_status() # has the vvols state changed? if current_vvols_status is False and self.parameters.get( 'enable_virtual_volumes') is True: changed = True self.enable_feature('vvols') elif current_vvols_status is True and self.parameters.get( 'enable_virtual_volumes') is not True: # vvols, once enabled, cannot be disabled self.module.fail_json( msg='Error disabling vvols: this feature cannot be undone') if self.module.check_mode is True: result_message = "Check mode, skipping changes" self.module.exit_json(changed=changed, msg=result_message)
class NetAppOntapIgroup(object): def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, choices=[ 'present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str', default=None), ostype=dict(required=False, type='str'), initiator_group_type=dict(required=False, type='str', choices=['fcp', 'iscsi', 'mixed']), initiators=dict(required=False, type='list', aliases=['initiator']), vserver=dict(required=True, type='str'), force_remove_initiator=dict(required=False, type='bool', default=False), bind_portset=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_igroup(self, name): """ Return details about the igroup :param: name : Name of the igroup :return: Details about the igroup. None if not found. :rtype: dict """ igroup_info = netapp_utils.zapi.NaElement('igroup-get-iter') attributes = dict(query={'initiator-group-info': {'initiator-group-name': name, 'vserver': self.parameters['vserver']}}) igroup_info.translate_struct(attributes) result, current = None, 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['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: igroup = result.get_child_by_name('attributes-list').get_child_by_name('initiator-group-info') initiators = [] if igroup.get_child_by_name('initiators'): current_initiators = igroup['initiators'].get_children() for initiator in current_initiators: initiators.append(initiator['initiator-name']) current = { 'initiators': initiators } return current def add_initiators(self): """ Add the list of initiators to igroup :return: None """ # don't add if initiators is empty string if self.parameters.get('initiators') == [''] or self.parameters.get('initiators') is None: return for initiator in self.parameters['initiators']: self.modify_initiator(initiator, 'igroup-add') def remove_initiators(self, initiators): """ Removes all existing initiators from igroup :return: None """ for initiator in initiators: self.modify_initiator(initiator, 'igroup-remove') def modify_initiator(self, initiator, zapi): """ Add or remove an initiator to/from an igroup """ initiator.strip() # remove leading spaces if any (eg: if user types a space after comma in initiators list) options = {'initiator-group-name': self.parameters['name'], 'initiator': initiator} igroup_modify = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options) try: self.server.invoke_successfully(igroup_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying igroup initiator %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_igroup(self): """ Create the igroup. """ options = {'initiator-group-name': self.parameters['name']} if self.parameters.get('ostype') is not None: options['os-type'] = self.parameters['ostype'] if self.parameters.get('initiator_group_type') is not None: options['initiator-group-type'] = self.parameters['initiator_group_type'] if self.parameters.get('bind_portset') is not None: options['bind-portset'] = self.parameters['bind_portset'] igroup_create = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-create', **options) try: self.server.invoke_successfully(igroup_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error provisioning igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) self.add_initiators() def delete_igroup(self): """ Delete the igroup. """ igroup_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-destroy', **{'initiator-group-name': self.parameters['name'], 'force': 'true' if self.parameters['force_remove_initiator'] else 'false'}) try: self.server.invoke_successfully(igroup_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_igroup(self): """ Rename the igroup. """ igroup_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-rename', **{'initiator-group-name': self.parameters['from_name'], 'initiator-group-new-name': str(self.parameters['name'])}) try: self.server.invoke_successfully(igroup_rename, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error renaming igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): netapp_utils.ems_log_event("na_ontap_igroup", self.server) def apply(self): self.autosupport_log() current = self.get_igroup(self.parameters['name']) # rename and create are mutually exclusive rename, cd_action, modify = None, None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action(self.get_igroup(self.parameters['from_name']), current) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_igroup() elif cd_action == 'create': self.create_igroup() elif cd_action == 'delete': self.delete_igroup() if modify: self.remove_initiators(current['initiators']) self.add_initiators() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapUnixGroup(object): """ Common operations to manage UNIX groups """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), id=dict(required=False, type='int'), skip_name_validation=dict(required=False, type='bool'), vserver=dict(required=True, type='str'), users=dict(required=False, type='list'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.set_playbook_zapi_key_map() if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def set_playbook_zapi_key_map(self): self.na_helper.zapi_string_keys = {'name': 'group-name'} self.na_helper.zapi_int_keys = {'id': 'group-id'} self.na_helper.zapi_bool_keys = { 'skip_name_validation': 'skip-name-validation' } def get_unix_group(self): """ Checks if the UNIX group exists. :return: dict() if group found None if group is not found """ get_unix_group = netapp_utils.zapi.NaElement( 'name-mapping-unix-group-get-iter') attributes = { 'query': { 'unix-group-info': { 'group-name': self.parameters['name'], 'vserver': self.parameters['vserver'], } } } get_unix_group.translate_struct(attributes) try: result = self.server.invoke_successfully(get_unix_group, enable_tunneling=True) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: group_info = result['attributes-list']['unix-group-info'] group_details = dict() else: return None except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error getting UNIX group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) for item_key, zapi_key in self.na_helper.zapi_string_keys.items(): group_details[item_key] = group_info[zapi_key] for item_key, zapi_key in self.na_helper.zapi_int_keys.items(): group_details[item_key] = self.na_helper.get_value_for_int( from_zapi=True, value=group_info[zapi_key]) if group_info.get_child_by_name('users') is not None: group_details['users'] = [ user.get_child_content('user-name') for user in group_info.get_child_by_name('users').get_children() ] else: group_details['users'] = None return group_details def create_unix_group(self): """ Creates an UNIX group in the specified Vserver :return: None """ if self.parameters.get('id') is None: self.module.fail_json( msg='Error: Missing a required parameter for create: (id)') group_create = netapp_utils.zapi.NaElement( 'name-mapping-unix-group-create') group_details = {} for item in self.parameters: if item in self.na_helper.zapi_string_keys: zapi_key = self.na_helper.zapi_string_keys.get(item) group_details[zapi_key] = self.parameters[item] elif item in self.na_helper.zapi_bool_keys: zapi_key = self.na_helper.zapi_bool_keys.get(item) group_details[zapi_key] = self.na_helper.get_value_for_bool( from_zapi=False, value=self.parameters[item]) elif item in self.na_helper.zapi_int_keys: zapi_key = self.na_helper.zapi_int_keys.get(item) group_details[zapi_key] = self.na_helper.get_value_for_int( from_zapi=True, value=self.parameters[item]) group_create.translate_struct(group_details) try: self.server.invoke_successfully(group_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating UNIX group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if self.parameters.get('users') is not None: self.modify_users_in_group() def delete_unix_group(self): """ Deletes an UNIX group from a vserver :return: None """ group_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'name-mapping-unix-group-destroy', **{'group-name': self.parameters['name']}) try: self.server.invoke_successfully(group_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error removing UNIX group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_unix_group(self, params): """ Modify an UNIX group from a vserver :param params: modify parameters :return: None """ # modify users requires separate zapi. if 'users' in params: self.modify_users_in_group() if len(params) == 1: return group_modify = netapp_utils.zapi.NaElement( 'name-mapping-unix-group-modify') group_details = {'group-name': self.parameters['name']} for key in params: if key in self.na_helper.zapi_int_keys: zapi_key = self.na_helper.zapi_int_keys.get(key) group_details[zapi_key] = self.na_helper.get_value_for_int( from_zapi=True, value=params[key]) group_modify.translate_struct(group_details) try: self.server.invoke_successfully(group_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying UNIX group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def modify_users_in_group(self): """ Add/delete one or many users in a UNIX group :return: None """ current_users = self.get_unix_group().get('users') expect_users = self.parameters.get('users') if current_users is None: current_users = [] if expect_users[0] == '' and len(expect_users) == 1: expect_users = [] users_to_remove = list(set(current_users) - set(expect_users)) users_to_add = list(set(expect_users) - set(current_users)) if len(users_to_add) > 0: for user in users_to_add: add_user = netapp_utils.zapi.NaElement( 'name-mapping-unix-group-add-user') group_details = { 'group-name': self.parameters['name'], 'user-name': user } add_user.translate_struct(group_details) try: self.server.invoke_successfully(add_user, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error adding user %s to UNIX group %s: %s' % (user, self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if len(users_to_remove) > 0: for user in users_to_remove: delete_user = netapp_utils.zapi.NaElement( 'name-mapping-unix-group-delete-user') group_details = { 'group-name': self.parameters['name'], 'user-name': user } delete_user.translate_struct(group_details) try: self.server.invoke_successfully(delete_user, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting user %s from UNIX group %s: %s' % (user, self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): """ Autosupport log for unix_group :return: None """ netapp_utils.ems_log_event("na_ontap_unix_group", self.server) def apply(self): """ Invoke appropriate action based on playbook parameters :return: None """ self.autosupport_log() current = self.get_unix_group() cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.parameters['state'] == 'present' and cd_action is None: modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_unix_group() elif cd_action == 'delete': self.delete_unix_group() else: self.modify_unix_group(modify) self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapQosPolicyGroup(object): """ Create, delete, modify and rename a policy group. """ def __init__(self): """ Initialize the Ontap qos policy group class. """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), name=dict(required=True, type='str'), from_name=dict(required=False, type='str'), vserver=dict(required=True, type='str'), max_throughput=dict(required=False, type='str'), min_throughput=dict(required=False, type='str'), force=dict(required=False, type='bool', default=False) )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) def get_policy_group(self, policy_group_name=None): """ Return details of a policy group. :param policy_group_name: policy group name :return: policy group details. :rtype: dict. """ if policy_group_name is None: policy_group_name = self.parameters['name'] policy_group_get_iter = netapp_utils.zapi.NaElement('qos-policy-group-get-iter') policy_group_info = netapp_utils.zapi.NaElement('qos-policy-group-info') policy_group_info.add_new_child('policy-group', policy_group_name) policy_group_info.add_new_child('vserver', self.parameters['vserver']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(policy_group_info) policy_group_get_iter.add_child_elem(query) result = self.server.invoke_successfully(policy_group_get_iter, True) policy_group_detail = None if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) == 1: policy_info = result.get_child_by_name('attributes-list').get_child_by_name('qos-policy-group-info') policy_group_detail = { 'name': policy_info.get_child_content('policy-group'), 'vserver': policy_info.get_child_content('vserver'), 'max_throughput': policy_info.get_child_content('max-throughput'), 'min_throughput': policy_info.get_child_content('min-throughput') } return policy_group_detail def create_policy_group(self): """ create a policy group name. """ policy_group = netapp_utils.zapi.NaElement('qos-policy-group-create') policy_group.add_new_child('policy-group', self.parameters['name']) policy_group.add_new_child('vserver', self.parameters['vserver']) if self.parameters.get('max_throughput'): policy_group.add_new_child('max-throughput', self.parameters['max_throughput']) if self.parameters.get('min_throughput'): policy_group.add_new_child('min-throughput', self.parameters['min_throughput']) try: self.server.invoke_successfully(policy_group, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating qos policy group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_policy_group(self, policy_group=None): """ delete an existing policy group. :param policy_group: policy group name. """ if policy_group is None: policy_group = self.parameters['name'] policy_group_obj = netapp_utils.zapi.NaElement('qos-policy-group-delete') policy_group_obj.add_new_child('policy-group', policy_group) if self.parameters.get('force'): policy_group_obj.add_new_child('force', str(self.parameters['force'])) try: self.server.invoke_successfully(policy_group_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting qos policy group %s: %s' % (policy_group, to_native(error)), exception=traceback.format_exc()) def modify_policy_group(self): """ Modify policy group. """ policy_group_obj = netapp_utils.zapi.NaElement('qos-policy-group-modify') policy_group_obj.add_new_child('policy-group', self.parameters['name']) if self.parameters.get('max_throughput'): policy_group_obj.add_new_child('max-throughput', self.parameters['max_throughput']) if self.parameters.get('min_throughput'): policy_group_obj.add_new_child('min-throughput', self.parameters['min_throughput']) try: self.server.invoke_successfully(policy_group_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying qos policy group %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_policy_group(self): """ Rename policy group name. """ rename_obj = netapp_utils.zapi.NaElement('qos-policy-group-rename') rename_obj.add_new_child('new-name', self.parameters['name']) rename_obj.add_new_child('policy-group-name', self.parameters['from_name']) try: self.server.invoke_successfully(rename_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error renaming qos policy group %s: %s' % (self.parameters['from_name'], to_native(error)), exception=traceback.format_exc()) def modify_helper(self, modify): """ helper method to modify policy group. :param modify: modified attributes. """ for attribute in modify.keys(): if attribute in ['max_throughput', 'min_throughput']: self.modify_policy_group() def apply(self): """ Run module based on playbook """ self.asup_log_for_cserver("na_ontap_qos_policy_group") current = self.get_policy_group() rename, cd_action = None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action(self.get_policy_group(self.parameters['from_name']), current) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_policy_group() if cd_action == 'create': self.create_policy_group() elif cd_action == 'delete': self.delete_policy_group() elif modify: self.modify_helper(modify) self.module.exit_json(changed=self.na_helper.changed) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver)
class NetAppONTAPasup(object): """Class with autosupport methods""" def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), node_name=dict(required=True, type='str'), transport=dict(required=False, type='str', choices=['smtp', 'http', 'https']), noteto=dict(required=False, type='list'), post_url=dict(required=False, type='str'), support=dict(required=False, type='bool'), mail_hosts=dict(required=False, type='list'), from_address=dict(required=False, type='str'), partner_addresses=dict(required=False, type='list'), to_addresses=dict(required=False, type='list'), proxy_url=dict(required=False, type='str'), hostname_in_subject=dict(required=False, type='bool'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=False) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # present or absent requires modifying state to enabled or disabled self.parameters['service_state'] = 'started' if self.parameters[ 'state'] == 'present' else 'stopped' self.set_playbook_zapi_key_map() if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) def set_playbook_zapi_key_map(self): self.na_helper.zapi_string_keys = { 'node_name': 'node-name', 'transport': 'transport', 'post_url': 'post-url', 'from_address': 'from', 'proxy_url': 'proxy-url' } self.na_helper.zapi_list_keys = { 'noteto': ('noteto', 'mail-address'), 'mail_hosts': ('mail-hosts', 'string'), 'partner_addresses': ('partner-address', 'mail-address'), 'to_addresses': ('to', 'mail-address'), } self.na_helper.zapi_bool_keys = { 'support': 'is-support-enabled', 'hostname_in_subject': 'is-node-in-subject' } def get_autosupport_config(self): """ Invoke zapi - get current autosupport details :return: dict() """ asup_details = netapp_utils.zapi.NaElement('autosupport-config-get') asup_details.add_new_child('node-name', self.parameters['node_name']) asup_info = dict() try: result = self.server.invoke_successfully(asup_details, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='%s' % to_native(error), exception=traceback.format_exc()) # zapi invoke successful asup_attr_info = result.get_child_by_name( 'attributes').get_child_by_name('autosupport-config-info') asup_info['service_state'] = 'started' if asup_attr_info[ 'is-enabled'] == 'true' else 'stopped' for item_key, zapi_key in self.na_helper.zapi_string_keys.items(): asup_info[item_key] = asup_attr_info[zapi_key] for item_key, zapi_key in self.na_helper.zapi_bool_keys.items(): asup_info[item_key] = self.na_helper.get_value_for_bool( from_zapi=True, value=asup_attr_info[zapi_key]) for item_key, zapi_key in self.na_helper.zapi_list_keys.items(): parent, dummy = zapi_key asup_info[item_key] = self.na_helper.get_value_for_list( from_zapi=True, zapi_parent=asup_attr_info.get_child_by_name(parent)) return asup_info def modify_autosupport_config(self, modify): """ Invoke zapi - modify autosupport config @return: NaElement object / FAILURE with an error_message """ asup_details = {'node-name': self.parameters['node_name']} if modify.get('service_state'): asup_details['is-enabled'] = 'true' if modify.get( 'service_state') == 'started' else 'false' asup_config = netapp_utils.zapi.NaElement('autosupport-config-modify') for item_key in modify: if item_key in self.na_helper.zapi_string_keys: zapi_key = self.na_helper.zapi_string_keys.get(item_key) asup_details[zapi_key] = modify[item_key] elif item_key in self.na_helper.zapi_bool_keys: zapi_key = self.na_helper.zapi_bool_keys.get(item_key) asup_details[zapi_key] = self.na_helper.get_value_for_bool( from_zapi=False, value=modify[item_key]) elif item_key in self.na_helper.zapi_list_keys: parent_key, child_key = self.na_helper.zapi_list_keys.get( item_key) asup_config.add_child_elem( self.na_helper.get_value_for_list( from_zapi=False, zapi_parent=parent_key, zapi_child=child_key, data=modify.get(item_key))) asup_config.translate_struct(asup_details) try: return self.server.invoke_successfully(asup_config, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='%s' % to_native(error), exception=traceback.format_exc()) def autosupport_log(self): results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event("na_ontap_autosupport", cserver) def apply(self): """ Apply action to autosupport """ current = self.get_autosupport_config() modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: self.modify_autosupport_config(modify) self.module.exit_json(changed=self.na_helper.changed)
class AwsCvsNetappFileSystem(object): """ Contains methods to parse arguments, derive details of AWS_CVS objects and send requests to AWS CVS via the restApi """ def __init__(self): """ Parse arguments, setup state variables, check parameters and ensure request module is installed """ self.argument_spec = netapp_utils.aws_cvs_host_argument_spec() self.argument_spec.update( dict( state=dict(required=True, choices=['present', 'absent']), region=dict(required=True, type='str'), creationToken=dict(required=True, type='str'), quotaInBytes=dict(required=False, type='int'), serviceLevel=dict(required=False, choices=['standard', 'premium', 'extreme']), exportPolicy=dict( type='dict', options=dict(rules=dict( type='list', options=dict( allowedClients=dict(required=False, type='str'), cifs=dict(required=False, type='bool'), nfsv3=dict(required=False, type='bool'), nfsv4=dict(required=False, type='bool'), ruleIndex=dict(required=False, type='int'), unixReadOnly=dict(required=False, type='bool'), unixReadWrite=dict(required=False, type='bool'))))), )) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[ ('state', 'present', [ 'region', 'creationToken', 'quotaInBytes' ]), ], supports_check_mode=True) self.na_helper = NetAppModule() # set up state variables self.parameters = self.na_helper.set_parameters(self.module.params) # Calling generic AWSCVS restApi class self.restApi = AwsCvsRestAPI(self.module) self.data = {} for key in self.parameters.keys(): self.data[key] = self.parameters[key] def get_filesystemId(self): # Check given FileSystem is exists # Return fileSystemId is found, None otherwise list_filesystem, error = self.restApi.get('FileSystems') if error: self.module.fail_json(msg=error) for FileSystem in list_filesystem: if FileSystem['creationToken'] == self.parameters['creationToken']: return FileSystem['fileSystemId'] return None def get_filesystem(self, fileSystemId): # Get FileSystem information by fileSystemId # Return fileSystem Information filesystemInfo, error = self.restApi.get('FileSystems/%s' % fileSystemId) if error: self.module.fail_json(msg=error) else: return filesystemInfo return None def is_job_done(self, response): # check jobId is present and equal to 'done' # return True on success, False otherwise try: job_id = response['jobs'][0]['jobId'] except TypeError: job_id = None if job_id is not None and self.restApi.get_state(job_id) == 'done': return True return False def create_fileSystem(self): # Create fileSystem api = 'FileSystems' response, error = self.restApi.post(api, self.data) if not error: if self.is_job_done(response): return error = "Error: unexpected response on FileSystems create: %s" % str( response) self.module.fail_json(msg=error) def delete_fileSystem(self, fileSystemId): # Delete FileSystem api = 'FileSystems/' + fileSystemId self.data = None response, error = self.restApi.delete(api, self.data) if not error: if self.is_job_done(response): return error = "Error: unexpected response on FileSystems delete: %s" % str( response) self.module.fail_json(msg=error) def update_fileSystem(self, fileSystemId): # Update FileSystem api = 'FileSystems/' + fileSystemId response, error = self.restApi.put(api, self.data) if not error: if self.is_job_done(response): return error = "Error: unexpected response on FileSystems update: %s" % str( response) self.module.fail_json(msg=error) def apply(self): """ Perform pre-checks, call functions and exit """ fileSystem = None fileSystemId = self.get_filesystemId() if fileSystemId: # Getting the FileSystem details fileSystem = self.get_filesystem(fileSystemId) cd_action = self.na_helper.get_cd_action(fileSystem, self.parameters) if cd_action is None and self.parameters['state'] == 'present': # Check if we need to update the fileSystem update_fileSystem = False if fileSystem['quotaInBytes'] is not None and 'quotaInBytes' in self.parameters \ and fileSystem['quotaInBytes'] != self.parameters['quotaInBytes']: update_fileSystem = True elif fileSystem['creationToken'] is not None and 'creationToken' in self.parameters \ and fileSystem['creationToken'] != self.parameters['creationToken']: update_fileSystem = True elif fileSystem['serviceLevel'] is not None and 'serviceLevel' in self.parameters \ and fileSystem['serviceLevel'] != self.parameters['serviceLevel']: update_fileSystem = True elif fileSystem['exportPolicy'][ 'rules'] is not None and 'exportPolicy' in self.parameters: for rule_org in fileSystem['exportPolicy']['rules']: for rule in self.parameters['exportPolicy']['rules']: if rule_org['allowedClients'] != rule['allowedClients']: update_fileSystem = True elif rule_org['unixReadOnly'] != rule['unixReadOnly']: update_fileSystem = True elif rule_org['unixReadWrite'] != rule['unixReadWrite']: update_fileSystem = True if update_fileSystem: self.na_helper.changed = True result_message = "" if self.na_helper.changed: if self.module.check_mode: # Skip changes result_message = "Check mode, skipping changes" else: if cd_action == "create": self.create_fileSystem() result_message = "FileSystem Created" elif cd_action == "delete": self.delete_fileSystem(fileSystemId) result_message = "FileSystem Deleted" else: # modify self.update_fileSystem(fileSystemId) result_message = "FileSystem Updated" self.module.exit_json(changed=self.na_helper.changed, msg=result_message)
class NetAppONTAPSoftwareUpdate(object): """ Class with ONTAP software update 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'), nodes=dict(required=False, type='list', aliases=["node"]), package_version=dict(required=True, type='str'), package_url=dict(required=True, type='str'), ignore_validation_warning=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 cluster_image_get_iter(self): """ Compose NaElement object to query current version :return: NaElement object for cluster-image-get-iter with query """ cluster_image_get = netapp_utils.zapi.NaElement('cluster-image-get-iter') query = netapp_utils.zapi.NaElement('query') cluster_image_info = netapp_utils.zapi.NaElement('cluster-image-info') query.add_child_elem(cluster_image_info) cluster_image_get.add_child_elem(query) return cluster_image_get def cluster_image_get(self): """ Get current cluster image info :return: True if query successful, else return None """ cluster_image_get_iter = self.cluster_image_get_iter() try: result = self.server.invoke_successfully(cluster_image_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching cluster image details: %s: %s' % (self.parameters['package_version'], to_native(error)), exception=traceback.format_exc()) # return cluster image details if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) > 0: return True return None def cluster_image_get_for_node(self, node_name): """ Get current cluster image info for given node """ cluster_image_get = netapp_utils.zapi.NaElement('cluster-image-get') cluster_image_get.add_new_child('node-id', node_name) try: self.server.invoke_successfully(cluster_image_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching cluster image details for %s: %s' % (node_name, to_native(error)), exception=traceback.format_exc()) def cluster_image_update_progress_get(self): """ Get current cluster image update progress info :return: Dictionary of cluster image update progress if query successful, else return None """ cluster_update_progress_get = netapp_utils.zapi.NaElement('cluster-image-update-progress-info') cluster_update_progress_info = dict() try: result = self.server.invoke_successfully(cluster_update_progress_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: # return empty dict on error to satisfy package delete upon image update if to_native(error.code) == 'Unexpected error' and self.parameters.get('https') is True: return cluster_update_progress_info else: self.module.fail_json(msg='Error fetching cluster image update progress details: %s' % (to_native(error)), exception=traceback.format_exc()) # return cluster image update progress details if result.get_child_by_name('attributes').get_child_by_name('ndu-progress-info'): update_progress_info = result.get_child_by_name('attributes').get_child_by_name('ndu-progress-info') cluster_update_progress_info['overall_status'] = update_progress_info.get_child_content('overall-status') cluster_update_progress_info['completed_node_count'] = update_progress_info.\ get_child_content('completed-node-count') return cluster_update_progress_info def cluster_image_update(self): """ Update current cluster image """ cluster_update_info = netapp_utils.zapi.NaElement('cluster-image-update') cluster_update_info.add_new_child('package-version', self.parameters['package_version']) cluster_update_info.add_new_child('ignore-validation-warning', str(self.parameters['ignore_validation_warning'])) if self.parameters.get('nodes'): cluster_nodes = netapp_utils.zapi.NaElement('nodes') for node in self.parameters['nodes']: cluster_nodes.add_new_child('node-name', node) cluster_update_info.add_child_elem(cluster_nodes) try: self.server.invoke_successfully(cluster_update_info, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error updating cluster image for %s: %s' % (self.parameters['package_version'], to_native(error)), exception=traceback.format_exc()) def cluster_image_package_download(self): """ Get current cluster image package download :return: True if package already exists, else return False """ cluster_image_package_download_info = netapp_utils.zapi.NaElement('cluster-image-package-download') cluster_image_package_download_info.add_new_child('package-url', self.parameters['package_url']) try: self.server.invoke_successfully(cluster_image_package_download_info, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: # Error 18408 denotes Package image with the same name already exists if to_native(error.code) == "18408": return True else: self.module.fail_json(msg='Error downloading cluster image package for %s: %s' % (self.parameters['package_url'], to_native(error)), exception=traceback.format_exc()) return False def cluster_image_package_delete(self): """ Delete current cluster image package """ cluster_image_package_delete_info = netapp_utils.zapi.NaElement('cluster-image-package-delete') cluster_image_package_delete_info.add_new_child('package-version', self.parameters['package_version']) try: self.server.invoke_successfully(cluster_image_package_delete_info, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting cluster image package for %s: %s' % (self.parameters['package_version'], to_native(error)), exception=traceback.format_exc()) def cluster_image_package_download_progress(self): """ Get current cluster image package download progress :return: Dictionary of cluster image download progress if query successful, else return None """ cluster_image_package_download_progress_info = netapp_utils.zapi.\ NaElement('cluster-image-get-download-progress') try: result = self.server.invoke_successfully( cluster_image_package_download_progress_info, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching cluster image package download progress for %s: %s' % (self.parameters['package_url'], to_native(error)), exception=traceback.format_exc()) # return cluster image download progress details cluster_download_progress_info = dict() if result.get_child_by_name('progress-status'): cluster_download_progress_info['progress_status'] = result.get_child_content('progress-status') cluster_download_progress_info['progress_details'] = result.get_child_content('progress-details') cluster_download_progress_info['failure_reason'] = result.get_child_content('failure-reason') return cluster_download_progress_info return None def autosupport_log(self): """ Autosupport log for software_update :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_software_update", cserver) def apply(self): """ Apply action to update ONTAP software """ if self.parameters.get('https') is not True: self.module.fail_json(msg='https parameter must be True') changed = False self.autosupport_log() current = self.cluster_image_get() if self.parameters.get('nodes'): for node in self.parameters['nodes']: self.cluster_image_get_for_node(node) if self.parameters.get('state') == 'present' and current: package_exists = self.cluster_image_package_download() if package_exists is False: cluster_download_progress = self.cluster_image_package_download_progress() while cluster_download_progress.get('progress_status') == 'async_pkg_get_phase_running': time.sleep(5) cluster_download_progress = self.cluster_image_package_download_progress() if cluster_download_progress.get('progress_status') == 'async_pkg_get_phase_complete': self.cluster_image_update() changed = True else: self.module.fail_json(msg='Error downloading package: %s' % (cluster_download_progress['failure_reason'])) else: self.cluster_image_update() changed = True # delete package once update is completed cluster_update_progress = self.cluster_image_update_progress_get() while not cluster_update_progress or cluster_update_progress.get('overall_status') == 'in_progress': time.sleep(25) cluster_update_progress = self.cluster_image_update_progress_get() if cluster_update_progress.get('overall_status') == 'completed': self.cluster_image_package_delete() self.module.exit_json(changed=changed)
class NetAppOntapPorts(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'), vserver=dict(required=False, type='str'), names=dict(required=True, type='list'), resource_name=dict(required=True, type='str'), resource_type=dict(required=True, type='str', choices=['broadcast_domain', 'portset']), ipspace=dict(required=False, type='str'), portset_type=dict(required=False, type='str', choices=['fcp', 'iscsi', 'mixed']), )) self.module = AnsibleModule(argument_spec=self.argument_spec, required_if=[ ('resource_type', 'portset', ['vserver']), ], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: if self.parameters['resource_type'] == 'broadcast_domain': self.server = netapp_utils.setup_na_ontap_zapi( module=self.module) elif self.parameters['resource_type'] == 'portset': self.server = netapp_utils.setup_na_ontap_zapi( module=self.module, vserver=self.parameters['vserver']) def add_broadcast_domain_ports(self, ports): """ Add broadcast domain ports :param: ports to be added. """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-add-ports') domain_obj.add_new_child("broadcast-domain", self.parameters['resource_name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in ports: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) return True except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error adding port for broadcast domain %s: %s' % (self.parameters['resource_name'], to_native(error)), exception=traceback.format_exc()) def remove_broadcast_domain_ports(self, ports): """ Deletes broadcast domain ports :param: ports to be removed. """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-remove-ports') domain_obj.add_new_child("broadcast-domain", self.parameters['resource_name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in ports: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) return True except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error removing port for broadcast domain %s: %s' % (self.parameters['resource_name'], to_native(error)), exception=traceback.format_exc()) def get_broadcast_domain_ports(self): """ Return details about the broadcast domain ports. :return: Details about the broadcast domain ports. [] if not found. :rtype: list """ domain_get_iter = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-get-iter') broadcast_domain_info = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-info') broadcast_domain_info.add_new_child('broadcast-domain', self.parameters['resource_name']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(broadcast_domain_info) domain_get_iter.add_child_elem(query) result = self.server.invoke_successfully(domain_get_iter, True) ports = [] if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: domain_info = result.get_child_by_name( 'attributes-list').get_child_by_name( 'net-port-broadcast-domain-info') domain_ports = domain_info.get_child_by_name('ports') if domain_ports is not None: ports = [ port.get_child_content('port') for port in domain_ports.get_children() ] return ports def remove_portset_ports(self, port): """ Removes all existing ports from portset :return: None """ options = { 'portset-name': self.parameters['resource_name'], 'portset-port-name': port.strip() } portset_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'portset-remove', **options) try: self.server.invoke_successfully(portset_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error removing port in portset %s: %s' % (self.parameters['resource_name'], to_native(error)), exception=traceback.format_exc()) def add_portset_ports(self, port): """ Add the list of ports to portset :return: None """ options = { 'portset-name': self.parameters['resource_name'], 'portset-port-name': port.strip() } portset_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'portset-add', **options) try: self.server.invoke_successfully(portset_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error adding port in portset %s: %s' % (self.parameters['resource_name'], to_native(error)), exception=traceback.format_exc()) 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['resource_name']) if self.parameters.get('portset_type'): portset_info.add_new_child('portset-type', self.parameters['portset_type']) query.add_child_elem(portset_info) portset_get.add_child_elem(query) return portset_get def portset_get(self): """ Get current portset info :return: List of current ports if query successful, else return [] """ portset_get_iter = self.portset_get_iter() result, ports = None, [] 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['resource_name'], to_native(error)), exception=traceback.format_exc()) # return portset details if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) > 0: portset_get_info = result.get_child_by_name( 'attributes-list').get_child_by_name('portset-info') if int(portset_get_info.get_child_content( 'portset-port-total')) > 0: port_info = portset_get_info.get_child_by_name( 'portset-port-info') ports = [ port.get_content() for port in port_info.get_children() ] return ports def modify_broadcast_domain_ports(self): """ compare current and desire ports. Call add or remove ports methods if needed. :return: None. """ current_ports = self.get_broadcast_domain_ports() cd_ports = self.parameters['names'] if self.parameters['state'] == 'present': ports_to_add = [ port for port in cd_ports if port not in current_ports ] if len(ports_to_add) > 0: self.add_broadcast_domain_ports(ports_to_add) self.na_helper.changed = True if self.parameters['state'] == 'absent': ports_to_remove = [ port for port in cd_ports if port in current_ports ] if len(ports_to_remove) > 0: self.remove_broadcast_domain_ports(ports_to_remove) self.na_helper.changed = True def modify_portset_ports(self): current_ports = self.portset_get() cd_ports = self.parameters['names'] if self.parameters['state'] == 'present': ports_to_add = [ port for port in cd_ports if port not in current_ports ] if len(ports_to_add) > 0: for port in ports_to_add: self.add_portset_ports(port) self.na_helper.changed = True if self.parameters['state'] == 'absent': ports_to_remove = [ port for port in cd_ports if port in current_ports ] if len(ports_to_remove) > 0: for port in ports_to_remove: self.remove_portset_ports(port) self.na_helper.changed = True def apply(self): self.asup_log_for_cserver("na_ontap_ports") if self.parameters['resource_type'] == 'broadcast_domain': self.modify_broadcast_domain_ports() elif self.parameters['resource_type'] == 'portset': self.modify_portset_ports() 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 NetAppONTAPNVMESubsystem(object): """ Class with NVME subsytem 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'), subsystem=dict(required=True, type='str'), ostype=dict( required=False, type='str', choices=['windows', 'linux', 'vmware', 'xen', 'hyper_v']), skip_host_check=dict(required=False, type='bool', default=False), skip_mapped_check=dict(required=False, type='bool', default=False), hosts=dict(required=False, type='list'), paths=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_subsystem(self): """ Get current subsystem details :return: dict if subsystem exists, None otherwise """ subsystem_get = netapp_utils.zapi.NaElement('nvme-subsystem-get-iter') query = { 'query': { 'nvme-subsytem-info': { 'subsystem': self.parameters.get('subsystem') } } } subsystem_get.translate_struct(query) try: result = self.server.invoke_successfully(subsystem_get, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching subsystem 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 True return None def create_subsystem(self): """ Create a NVME Subsystem """ if self.parameters.get('ostype') is None: self.module.fail_json( msg= "Error: Missing required parameter 'os_type' for creating subsystem" ) options = { 'subsystem': self.parameters['subsystem'], 'ostype': self.parameters['ostype'] } subsystem_create = netapp_utils.zapi.NaElement('nvme-subsystem-create') subsystem_create.translate_struct(options) try: self.server.invoke_successfully(subsystem_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating subsystem for %s: %s' % (self.parameters.get('subsystem'), to_native(error)), exception=traceback.format_exc()) def delete_subsystem(self): """ Delete a NVME subsystem """ options = { 'subsystem': self.parameters['subsystem'], 'skip-host-check': 'true' if self.parameters.get('skip_host_check') else 'false', 'skip-mapped-check': 'true' if self.parameters.get('skip_mapped_check') else 'false', } subsystem_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'nvme-subsystem-delete', **options) try: self.server.invoke_successfully(subsystem_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting subsystem for %s: %s' % (self.parameters.get('subsystem'), to_native(error)), exception=traceback.format_exc()) def get_subsystem_host_map(self, type): """ Get current subsystem host details :return: list if host exists, None otherwise """ if type == 'hosts': zapi_get, zapi_info, zapi_type = 'nvme-subsystem-host-get-iter', 'nvme-target-subsystem-host-info',\ 'host-nqn' elif type == 'paths': zapi_get, zapi_info, zapi_type = 'nvme-subsystem-map-get-iter', 'nvme-target-subsystem-map-info', 'path' subsystem_get = netapp_utils.zapi.NaElement(zapi_get) query = { 'query': { zapi_info: { 'subsystem': self.parameters.get('subsystem') } } } subsystem_get.translate_struct(query) try: result = self.server.invoke_successfully(subsystem_get, enable_tunneling=False) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching subsystem 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: attrs_list = result.get_child_by_name('attributes-list') return_list = [] for item in attrs_list.get_children(): return_list.append(item[zapi_type]) return {type: return_list} return None def add_subsystem_host_map(self, data, type): """ Add a NVME Subsystem host/map :param: data: list of hosts/paths to be added :param: type: hosts/paths """ if type == 'hosts': zapi_add, zapi_type = 'nvme-subsystem-host-add', 'host-nqn' elif type == 'paths': zapi_add, zapi_type = 'nvme-subsystem-map-add', 'path' for item in data: options = { 'subsystem': self.parameters['subsystem'], zapi_type: item } subsystem_add = netapp_utils.zapi.NaElement.create_node_with_children( zapi_add, **options) try: self.server.invoke_successfully(subsystem_add, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error adding %s for subsystem %s: %s' % (item, self.parameters.get('subsystem'), to_native(error)), exception=traceback.format_exc()) def remove_subsystem_host_map(self, data, type): """ Remove a NVME Subsystem host/map :param: data: list of hosts/paths to be added :param: type: hosts/paths """ if type == 'hosts': zapi_remove, zapi_type = 'nvme-subsystem-host-remove', 'host-nqn' elif type == 'paths': zapi_remove, zapi_type = 'nvme-subsystem-map-remove', 'path' for item in data: options = { 'subsystem': self.parameters['subsystem'], zapi_type: item } subsystem_remove = netapp_utils.zapi.NaElement.create_node_with_children( zapi_remove, **options) try: self.server.invoke_successfully(subsystem_remove, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error removing %s for subsystem %s: %s' % (item, self.parameters.get('subsystem'), to_native(error)), exception=traceback.format_exc()) def associate_host_map(self, types): """ Check if there are hosts or paths to be associated with the subsystem """ action_add_dict = {} action_remove_dict = {} for type in types: if self.parameters.get(type): current = self.get_subsystem_host_map(type) if current: add_items = self.na_helper.\ get_modified_attributes(current, self.parameters, get_list_diff=True).get(type) remove_items = [ item for item in current[type] if item not in self.parameters.get(type) ] else: add_items = self.parameters[type] remove_items = {} if add_items: action_add_dict[type] = add_items self.na_helper.changed = True if remove_items: action_remove_dict[type] = remove_items self.na_helper.changed = True return action_add_dict, action_remove_dict def modify_host_map(self, add_host_map, remove_host_map): for type, data in add_host_map.items(): self.add_subsystem_host_map(data, type) for type, data in remove_host_map.items(): self.remove_subsystem_host_map(data, type) def apply(self): """ Apply action to NVME subsystem """ netapp_utils.ems_log_event("na_ontap_nvme_subsystem", self.server) types = ['hosts', 'paths'] current = self.get_subsystem() add_host_map, remove_host_map = dict(), dict() cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action != 'delete' and self.parameters['state'] == 'present': add_host_map, remove_host_map = self.associate_host_map(types) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.create_subsystem() self.modify_host_map(add_host_map, remove_host_map) elif cd_action == 'delete': self.delete_subsystem() elif cd_action is None: self.modify_host_map(add_host_map, remove_host_map) 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, choices=['present', 'absent'], default='present'), allow_list=dict(required=False, type="list"), policy=dict(required=False, type='str'), service=dict(required=False, type='str', choices=['dns', 'http', 'https', 'ndmp', 'ndmps', 'ntp', '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 NetAppOntapBroadcastDomain(object): """ Create, Modifies and Destroys a Broadcast domain """ def __init__(self): """ Initialize the ONTAP Broadcast Domain class """ self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update( dict( state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=True, type='str', aliases=["broadcast_domain"]), ipspace=dict(required=False, type='str'), mtu=dict(required=False, type='str'), ports=dict(required=False, type='list'), from_name=dict(required=False, type='str'), )) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if HAS_NETAPP_LIB is False: self.module.fail_json( msg="the python NetApp-Lib module is required") else: self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) return def get_broadcast_domain(self, broadcast_domain=None): """ Return details about the broadcast domain :param broadcast_domain: specific broadcast domain to get. :return: Details about the broadcast domain. None if not found. :rtype: dict """ if broadcast_domain is None: broadcast_domain = self.parameters['name'] domain_get_iter = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-get-iter') broadcast_domain_info = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-info') broadcast_domain_info.add_new_child('broadcast-domain', broadcast_domain) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(broadcast_domain_info) domain_get_iter.add_child_elem(query) result = self.server.invoke_successfully(domain_get_iter, True) domain_exists = None # check if broadcast_domain exists if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: domain_info = result.get_child_by_name('attributes-list').\ get_child_by_name('net-port-broadcast-domain-info') domain_name = domain_info.get_child_content('broadcast-domain') domain_mtu = domain_info.get_child_content('mtu') domain_ipspace = domain_info.get_child_content('ipspace') domain_ports = domain_info.get_child_by_name('ports') if domain_ports is not None: ports = [ port.get_child_content('port') for port in domain_ports.get_children() ] else: ports = [] domain_exists = { 'domain-name': domain_name, 'mtu': domain_mtu, 'ipspace': domain_ipspace, 'ports': ports } return domain_exists def create_broadcast_domain(self): """ Creates a new broadcast domain """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-create') domain_obj.add_new_child("broadcast-domain", self.parameters['name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) if self.parameters.get('mtu'): domain_obj.add_new_child("mtu", self.parameters['mtu']) if self.parameters.get('ports'): ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in self.parameters['ports']: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_broadcast_domain(self, broadcast_domain=None): """ Deletes a broadcast domain """ if broadcast_domain is None: broadcast_domain = self.parameters['name'] domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-destroy') domain_obj.add_new_child("broadcast-domain", broadcast_domain) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) try: self.server.invoke_successfully(domain_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting broadcast domain %s: %s' % (broadcast_domain, to_native(error)), exception=traceback.format_exc()) def modify_broadcast_domain(self): """ Modifies ipspace and mtu options of a broadcast domain """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-modify') domain_obj.add_new_child("broadcast-domain", self.parameters['name']) if self.parameters.get('mtu'): domain_obj.add_new_child("mtu", self.parameters['mtu']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) try: self.server.invoke_successfully(domain_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def split_broadcast_domain(self): """ split broadcast domain """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-split') domain_obj.add_new_child("broadcast-domain", self.parameters['from_name']) domain_obj.add_new_child("new-broadcast-domain", self.parameters['name']) if self.parameters.get('ports'): ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in self.parameters['ports']: ports_obj.add_new_child('net-qualified-port-name', port) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) try: self.server.invoke_successfully(domain_obj, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error splitting broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if len(self.get_broadcast_domain_ports( self.parameters['from_name'])) == 0: self.delete_broadcast_domain(self.parameters['from_name']) def modify_redirect(self, modify): """ :param modify: modify attributes. """ for attribute in modify.keys(): if attribute == 'mtu': self.modify_broadcast_domain() if attribute == 'ports': self.modify_broadcast_domain_ports() def get_modify_attributes(self, current, split): """ :param current: current state. :param split: True or False of split action. :return: list of modified attributes. """ modify = None if self.parameters['state'] == 'present': # split already handled ipspace and ports. if self.parameters.get('from_name'): current = self.get_broadcast_domain( self.parameters['from_name']) if split: modify = self.na_helper.get_modified_attributes( current, self.parameters) if modify.get('ipspace'): del modify['ipspace'] if modify.get('ports'): del modify['ports'] # ipspace can not be modified. else: modify = self.na_helper.get_modified_attributes( current, self.parameters) if modify.get('ipspace'): self.module.fail_json( msg= 'A domain ipspace can not be modified after the domain has been created.', exception=traceback.format_exc()) return modify def modify_broadcast_domain_ports(self): """ compare current and desire ports. Call add or remove ports methods if needed. :return: None. """ current_ports = self.get_broadcast_domain_ports() expect_ports = self.parameters['ports'] # if want to remove all ports, simply delete the broadcast domain. if len(expect_ports) == 0: self.delete_broadcast_domain() return ports_to_remove = list(set(current_ports) - set(expect_ports)) ports_to_add = list(set(expect_ports) - set(current_ports)) if len(ports_to_add) > 0: self.add_broadcast_domain_ports(ports_to_add) if len(ports_to_remove) > 0: self.delete_broadcast_domain_ports(ports_to_remove) def add_broadcast_domain_ports(self, ports): """ Creates new broadcast domain ports """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-add-ports') domain_obj.add_new_child("broadcast-domain", self.parameters['name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) if ports: ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in ports: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) return True except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error creating port for broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_broadcast_domain_ports(self, ports): """ Deletes broadcast domain ports :param: ports to be deleted. """ domain_obj = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-remove-ports') domain_obj.add_new_child("broadcast-domain", self.parameters['name']) if self.parameters.get('ipspace'): domain_obj.add_new_child("ipspace", self.parameters['ipspace']) if ports: ports_obj = netapp_utils.zapi.NaElement('ports') domain_obj.add_child_elem(ports_obj) for port in ports: ports_obj.add_new_child('net-qualified-port-name', port) try: self.server.invoke_successfully(domain_obj, True) return True except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error deleting port for broadcast domain %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def get_broadcast_domain_ports(self, broadcast_domain=None): """ Return details about the broadcast domain ports. :return: Details about the broadcast domain ports. None if not found. :rtype: list """ if broadcast_domain is None: broadcast_domain = self.parameters['name'] domain_get_iter = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-get-iter') broadcast_domain_info = netapp_utils.zapi.NaElement( 'net-port-broadcast-domain-info') broadcast_domain_info.add_new_child('broadcast-domain', broadcast_domain) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(broadcast_domain_info) domain_get_iter.add_child_elem(query) result = self.server.invoke_successfully(domain_get_iter, True) ports = [] if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) == 1: domain_info = result.get_child_by_name( 'attributes-list').get_child_by_name( 'net-port-broadcast-domain-info') domain_ports = domain_info.get_child_by_name('ports') if domain_ports is not None: ports = [ port.get_child_content('port') for port in domain_ports.get_children() ] return ports def apply(self): """ Run Module based on play book """ self.asup_log_for_cserver("na_ontap_broadcast_domain") current = self.get_broadcast_domain() cd_action, split = None, None cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create': # either create new domain or split domain. if self.parameters.get('from_name'): split = self.na_helper.is_rename_action( self.get_broadcast_domain(self.parameters['from_name']), current) if split is None: self.module.fail_json( msg='A domain can not be split if it does not exist.', exception=traceback.format_exc()) if split: cd_action = None modify = self.get_modify_attributes(current, split) if self.na_helper.changed: if self.module.check_mode: pass else: if split: self.split_broadcast_domain() if cd_action == 'create': self.create_broadcast_domain() elif cd_action == 'delete': self.delete_broadcast_domain() elif modify: self.modify_redirect(modify) self.module.exit_json(changed=self.na_helper.changed) def asup_log_for_cserver(self, event_name): """ Fetch admin vserver for the given cluster Create and Autosupport log event with the given module name :param event_name: Name of the event log :return: None """ results = netapp_utils.get_cserver(self.server) cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) netapp_utils.ems_log_event(event_name, cserver)