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', elements='str'))), 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 fuctionality. 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 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 ElementSWInfo(object): ''' Element Software Initialize node with ownership for cluster formation ''' def __init__(self): self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update(dict( gather_subsets=dict(type='list', elements='str', aliases=['gather_subset'], default='all'), filter=dict(type='dict'), fail_on_error=dict(type='bool', default=False), fail_on_key_not_found=dict(type='bool', default=True), fail_on_record_not_found=dict(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.debug = list() if HAS_SF_SDK is False: self.module.fail_json(msg="Unable to import the SolidFire Python SDK") # 442 for node APIs, 443 (default) for cluster APIs for role, port in [('node', 442), ('cluster', 443)]: try: conn = netapp_utils.create_sf_connection(module=self.module, raise_on_connection_error=True, port=port) if role == 'node': self.sfe_node = conn else: self.sfe_cluster = conn except netapp_utils.solidfire.common.ApiConnectionError as exc: if str(exc) == "Bad Credentials": msg = ' Make sure to use valid %s credentials for username and password.' % 'node' if port == 442 else 'cluster' msg += '%s reported: %s' % ('Node' if port == 442 else 'Cluster', repr(exc)) else: msg = 'Failed to create connection for %s:%d - %s' % (self.parameters['hostname'], port, repr(exc)) self.module.fail_json(msg=msg) except Exception as exc: self.module.fail_json(msg='Failed to connect for %s:%d - %s' % (self.parameters['hostname'], port, repr(exc))) # TODO: add new node methods here self.node_methods = dict( node_config=self.sfe_node.get_config, ) # TODO: add new cluster methods here self.cluster_methods = dict( cluster_accounts=self.sfe_cluster.list_accounts ) self.methods = dict(self.node_methods) self.methods.update(self.cluster_methods) # add telemetry attributes - does not matter if we are using cluster or node here # TODO: most if not all get and list APIs do not have an attributes parameter def get_info(self, name): ''' Get Element Info run a cluster or node list method return output as json ''' info = None if name not in self.methods: msg = 'Error: unknown subset %s.' % name msg += ' Known_subsets: %s' % ', '.join(self.methods.keys()) self.module.fail_json(msg=msg, debug=self.debug) try: info = self.methods[name]() return info.to_json() except netapp_utils.solidfire.common.ApiServerError as exc: if 'err_json=500 xUnknownAPIMethod method=' in str(exc): info = 'Error (API not in scope?)' else: info = 'Error' msg = '%s for subset: %s: %s' % (info, name, repr(exc)) if self.parameters['fail_on_error']: self.module.fail_json(msg=msg) self.debug.append(msg) return info def filter_list_of_dict_by_key(self, records, key, value): matched = list() for record in records: if key in record and record[key] == value: matched.append(record) if key not in record and self.parameters['fail_on_key_not_found']: msg = 'Error: key %s not found in %s' % (key, repr(record)) self.module.fail_json(msg=msg) return matched def filter_records(self, records, filter_dict): if isinstance(records, dict): if len(records) == 1: key, value = list(records.items())[0] return dict({key: self.filter_records(value, filter_dict)}) if not isinstance(records, list): return records matched = records for key, value in filter_dict.items(): matched = self.filter_list_of_dict_by_key(matched, key, value) if self.parameters['fail_on_record_not_found'] and len(matched) == 0: msg = 'Error: no match for %s out of %d records' % (repr(self.parameters['filter']), len(records)) self.debug.append('Unmatched records: %s' % repr(records)) self.module.fail_json(msg=msg, debug=self.debug) return matched def get_and_filter_info(self, name): ''' Get data If filter is present, only return the records that are matched return output as json ''' records = self.get_info(name) if self.parameters.get('filter') is None: return records matched = self.filter_records(records, self.parameters.get('filter')) return matched def apply(self): ''' Check connection and initialize node with cluster ownership ''' changed = False info = dict() my_subsets = ('all', 'all_clusters', 'all_nodes') if any(x in self.parameters['gather_subsets'] for x in my_subsets) and len(self.parameters['gather_subsets']) > 1: msg = 'When any of %s is used, no other subset is allowed' % repr(my_subsets) self.module.fail_json(msg=msg) if 'all' in self.parameters['gather_subsets']: self.parameters['gather_subsets'] = self.methods.keys() if 'all_clusters' in self.parameters['gather_subsets']: self.parameters['gather_subsets'] = self.cluster_methods.keys() if 'all_nodes' in self.parameters['gather_subsets']: self.parameters['gather_subsets'] = self.node_methods.keys() for name in self.parameters['gather_subsets']: info[name] = self.get_and_filter_info(name) self.module.exit_json(changed=changed, info=info, debug=self.debug)
class ElementSWQosPolicy(object): """ Element Software QOS Policy """ def __init__(self): self.argument_spec = netapp_utils.ontap_sf_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'), qos=dict(required=False, type='dict'), )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) # Set up state variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.qos_policy_id = None if HAS_SF_SDK is False: self.module.fail_json(msg="Unable to import the SolidFire Python SDK") else: self.sfe = netapp_utils.create_sf_connection(module=self.module) self.elementsw_helper = NaElementSWModule(self.sfe) # add telemetry attributes self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_qos_policy') def get_qos_policy(self, name): """ Get QOS Policy """ policy, error = self.elementsw_helper.get_qos_policy(name) if error is not None: self.module.fail_json(msg=error, exception=traceback.format_exc()) return policy def create_qos_policy(self, name, qos): """ Create the QOS Policy """ try: self.sfe.create_qos_policy(name=name, qos=qos) except (solidfire.common.ApiServerError, solidfire.common.ApiConnectionError) as exc: self.module.fail_json(msg="Error creating qos policy: %s: %s" % (name, to_native(exc)), exception=traceback.format_exc()) def update_qos_policy(self, qos_policy_id, modify, name=None): """ Update the QOS Policy if the policy already exists """ options = dict( qos_policy_id=qos_policy_id ) if name is not None: options['name'] = name if 'qos' in modify: options['qos'] = modify['qos'] try: self.sfe.modify_qos_policy(**options) except (solidfire.common.ApiServerError, solidfire.common.ApiConnectionError) as exc: self.module.fail_json(msg="Error updating qos policy: %s: %s" % (self.parameters['from_name'] if name is not None else self.parameters['name'], to_native(exc)), exception=traceback.format_exc()) def delete_qos_policy(self, qos_policy_id): """ Delete the QOS Policy """ try: self.sfe.delete_qos_policy(qos_policy_id=qos_policy_id) except (solidfire.common.ApiServerError, solidfire.common.ApiConnectionError) as exc: self.module.fail_json(msg="Error deleting qos policy: %s: %s" % (self.parameters['name'], to_native(exc)), exception=traceback.format_exc()) def apply(self): """ Process the create/delete/rename/modify actions for qos policy on the Element Software Cluster """ modify = dict() current = self.get_qos_policy(self.parameters['name']) qos_policy_id = None if current is None else current['qos_policy_id'] cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes(current, self.parameters) if cd_action == 'create' and self.parameters.get('from_name') is not None: from_qos_policy = self.get_qos_policy(self.parameters['from_name']) if from_qos_policy is None: self.module.fail_json(msg="Error renaming qos policy, no existing policy with name/id: %s" % self.parameters['from_name']) cd_action = 'rename' qos_policy_id = from_qos_policy['qos_policy_id'] self.na_helper.changed = True modify = self.na_helper.get_modified_attributes(from_qos_policy, self.parameters) if cd_action == 'create' and 'qos' not in self.parameters: self.module.fail_json(msg="Error creating qos policy: %s, 'qos:' option is required" % self.parameters['name']) if not self.module.check_mode: if cd_action == 'create': self.create_qos_policy(self.parameters['name'], self.parameters['qos']) elif cd_action == 'delete': self.delete_qos_policy(qos_policy_id) elif cd_action == 'rename': self.update_qos_policy(qos_policy_id, modify, name=self.parameters['name']) elif modify: self.update_qos_policy(qos_policy_id, modify) self.module.exit_json(changed=self.na_helper.changed)
class ElementSWClusterSnmp(object): """ Element Software Configure Element SW Cluster SnmpNetwork """ def __init__(self): self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update( dict( state=dict(type='str', choices=['present', 'absent'], default='present'), snmp_v3_enabled=dict(type='bool'), networks=dict(type='dict', options=dict(access=dict( type='str', choices=['ro', 'rw', 'rosys']), cidr=dict(type='int', default=None), community=dict(type='str', default=None), network=dict(type='str', default=None))), usm_users=dict( type='dict', options=dict( access=dict(type='str', choices=['rouser', 'rwuser', 'rosys']), name=dict(type='str', default=None), password=dict(type='str', default=None), passphrase=dict(type='str', default=None), secLevel=dict(type='str', choices=['auth', 'noauth', 'priv']))), )) self.module = AnsibleModule( argument_spec=self.argument_spec, required_if=[('state', 'present', ['snmp_v3_enabled']), ('snmp_v3_enabled', True, ['usm_users']), ('snmp_v3_enabled', False, ['networks'])], supports_check_mode=True) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get('state') == "present": if self.parameters.get('usm_users') is not None: # Getting the configuration details to configure SNMP Version3 self.access_usm = self.parameters.get('usm_users')['access'] self.name = self.parameters.get('usm_users')['name'] self.password = self.parameters.get('usm_users')['password'] self.passphrase = self.parameters.get( 'usm_users')['passphrase'] self.secLevel = self.parameters.get('usm_users')['secLevel'] if self.parameters.get('networks') is not None: # Getting the configuration details to configure SNMP Version2 self.access_network = self.parameters.get('networks')['access'] self.cidr = self.parameters.get('networks')['cidr'] self.community = self.parameters.get('networks')['community'] self.network = self.parameters.get('networks')['network'] 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 enable_snmp(self): """ enable snmp feature """ try: self.sfe.enable_snmp( snmp_v3_enabled=self.parameters.get('snmp_v3_enabled')) except Exception as exception_object: self.module.fail_json(msg='Error enabling snmp feature %s' % to_native(exception_object), exception=traceback.format_exc()) def disable_snmp(self): """ disable snmp feature """ try: self.sfe.disable_snmp() except Exception as exception_object: self.module.fail_json(msg='Error disabling snmp feature %s' % to_native(exception_object), exception=traceback.format_exc()) def configure_snmp(self, actual_networks, actual_usm_users): """ Configure snmp """ try: self.sfe.set_snmp_acl(networks=[actual_networks], usm_users=[actual_usm_users]) except Exception as exception_object: self.module.fail_json(msg='Error Configuring snmp feature %s' % to_native(exception_object.message), exception=traceback.format_exc()) def apply(self): """ Cluster SNMP configuration """ changed = False result_message = None update_required = False version_change = False is_snmp_enabled = self.sfe.get_snmp_state().enabled if is_snmp_enabled is True: # IF SNMP is already enabled if self.parameters.get('state') == 'absent': # Checking for state change(s) here, and applying it later in the code allows us to support # check_mode changed = True elif self.parameters.get('state') == 'present': # Checking if SNMP configuration needs to be updated, is_snmp_v3_enabled = self.sfe.get_snmp_state().snmp_v3_enabled if is_snmp_v3_enabled != self.parameters.get( 'snmp_v3_enabled'): # Checking if there any version changes required version_change = True changed = True if is_snmp_v3_enabled is True: # Checking If snmp configuration for usm_users needs modification if len(self.sfe.get_snmp_info().usm_users) == 0: # If snmp is getting configured for first time update_required = True changed = True else: for usm_user in self.sfe.get_snmp_info().usm_users: if usm_user.access != self.access_usm or usm_user.name != self.name or usm_user.password != self.password or \ usm_user.passphrase != self.passphrase or usm_user.sec_level != self.secLevel: update_required = True changed = True else: # Checking If snmp configuration for networks needs modification for snmp_network in self.sfe.get_snmp_info().networks: if snmp_network.access != self.access_network or snmp_network.cidr != self.cidr or \ snmp_network.community != self.community or snmp_network.network != self.network: update_required = True changed = True else: if self.parameters.get('state') == 'present': changed = True result_message = "" if changed: if self.module.check_mode is True: result_message = "Check mode, skipping changes" else: if self.parameters.get('state') == "present": # IF snmp is not enabled, then enable and configure snmp if self.parameters.get('snmp_v3_enabled') is True: # IF SNMP is enabled with version 3 usm_users = { 'access': self.access_usm, 'name': self.name, 'password': self.password, 'passphrase': self.passphrase, 'secLevel': self.secLevel } networks = None else: # IF SNMP is enabled with version 2 usm_users = None networks = { 'access': self.access_network, 'cidr': self.cidr, 'community': self.community, 'network': self.network } if is_snmp_enabled is False or version_change is True: # Enable and configure snmp self.enable_snmp() self.configure_snmp(networks, usm_users) result_message = "SNMP is enabled and configured" elif update_required is True: # If snmp is already enabled, update the configuration if required self.configure_snmp(networks, usm_users) result_message = "SNMP is configured" elif is_snmp_enabled is True and self.parameters.get( 'state') == "absent": # If snmp is enabled and state is absent, disable snmp self.disable_snmp() result_message = "SNMP is disabled" self.module.exit_json(changed=changed, msg=result_message)
class ElementSWVlan(object): """ class to handle VLAN operations """ def __init__(self): """ Setup Ansible parameters and ElementSW connection """ self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), name=dict(required=False, type='str'), vlan_tag=dict(required=True, type='str'), svip=dict(required=False, type='str'), netmask=dict(required=False, type='str'), gateway=dict(required=False, type='str'), namespace=dict(required=False, type='bool'), attributes=dict(required=False, type='dict'), address_blocks=dict(required=False, type='list'))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) if HAS_SF_SDK is False: self.module.fail_json( msg="Unable to import the SolidFire Python SDK") else: self.elem = netapp_utils.create_sf_connection(module=self.module) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.elementsw_helper = NaElementSWModule(self.elem) # add telemetry attributes if self.parameters.get('attributes') is not None: self.parameters['attributes'].update( self.elementsw_helper.set_element_attributes( source='na_elementsw_vlan')) else: self.parameters[ 'attributes'] = self.elementsw_helper.set_element_attributes( source='na_elementsw_vlan') def validate_keys(self): """ Validate if all required keys are present before creating """ required_keys = ['address_blocks', 'svip', 'netmask', 'name'] if all(item in self.parameters.keys() for item in required_keys) is False: self.module.fail_json( msg= "One or more required fields %s for creating VLAN is missing" % required_keys) addr_blk_fields = ['start', 'size'] for address in self.parameters['address_blocks']: if 'start' not in address or 'size' not in address: self.module.fail_json( msg= "One or more required fields %s for address blocks is missing" % addr_blk_fields) def create_network(self): """ Add VLAN """ try: self.validate_keys() create_params = self.parameters.copy() for key in [ 'username', 'hostname', 'password', 'state', 'vlan_tag' ]: del create_params[key] self.elem.add_virtual_network( virtual_network_tag=self.parameters['vlan_tag'], **create_params) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error creating VLAN %s" % self.parameters['vlan_tag'], exception=to_native(err)) def delete_network(self): """ Remove VLAN """ try: self.elem.remove_virtual_network( virtual_network_tag=self.parameters['vlan_tag']) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error deleting VLAN %s" % self.parameters['vlan_tag'], exception=to_native(err)) def modify_network(self, modify): """ Modify the VLAN """ try: self.elem.modify_virtual_network( virtual_network_tag=self.parameters['vlan_tag'], **modify) except solidfire.common.ApiServerError as err: self.module.fail_json(msg="Error modifying VLAN %s" % self.parameters['vlan_tag'], exception=to_native(err)) def get_network_details(self): """ Check existing VLANs :return: vlan details if found, None otherwise :type: dict """ vlans = self.elem.list_virtual_networks( virtual_network_tag=self.parameters['vlan_tag']) vlan_details = dict() for vlan in vlans.virtual_networks: if vlan is not None: vlan_details['name'] = vlan.name vlan_details['address_blocks'] = list() for address in vlan.address_blocks: vlan_details['address_blocks'].append({ 'start': address.start, 'size': address.size }) vlan_details['svip'] = vlan.svip vlan_details['gateway'] = vlan.gateway vlan_details['netmask'] = vlan.netmask vlan_details['namespace'] = vlan.namespace vlan_details['attributes'] = dict() for key in vlan.attributes.__dict__.keys(): vlan_details['attributes'][key] = vlan.attributes.key return vlan_details return None def apply(self): """ Call create / delete / modify vlan methods """ network = self.get_network_details() # calling helper to determine action cd_action = self.na_helper.get_cd_action(network, self.parameters) modify = self.na_helper.get_modified_attributes( network, self.parameters) if cd_action == "create": self.create_network() elif cd_action == "delete": self.delete_network() elif modify: self.modify_network(modify) self.module.exit_json(changed=self.na_helper.changed)
class ElementSWClusterPair(object): """ class to handle cluster pairing operations """ def __init__(self): """ Setup Ansible parameters and ElementSW connection """ self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update( dict(state=dict(required=False, choices=['present', 'absent'], default='present'), 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, paired_clusters, hostname): for pair in paired_clusters.cluster_pairs: if pair.mvip == hostname: return pair.cluster_pair_id return None def get_src_pair_id(self): """ Check for idempotency """ # src cluster and dest cluster exist paired_clusters = self.elem.list_cluster_pairs() return self.check_if_already_paired(paired_clusters, self.parameters['dest_mvip']) def get_dest_pair_id(self): """ Getting destination cluster_pair_id """ paired_clusters = self.dest_elem.list_cluster_pairs() return self.check_if_already_paired(paired_clusters, self.parameters['hostname']) def pair_clusters(self): """ Start cluster pairing on source, and complete on target cluster """ try: pair_key = self.elem.start_cluster_pairing() self.dest_elem.complete_cluster_pairing( cluster_pairing_key=pair_key.cluster_pairing_key) except solidfire.common.ApiServerError as err: self.module.fail_json( msg="Error pairing cluster %s and %s" % (self.parameters['hostname'], self.parameters['dest_mvip']), exception=to_native(err)) def unpair_clusters(self, pair_id_source, pair_id_dest): """ Delete cluster pair """ try: self.elem.remove_cluster_pair(cluster_pair_id=pair_id_source) self.dest_elem.remove_cluster_pair(cluster_pair_id=pair_id_dest) except solidfire.common.ApiServerError as err: self.module.fail_json( msg="Error unpairing cluster %s and %s" % (self.parameters['hostname'], self.parameters['dest_mvip']), exception=to_native(err)) def apply(self): """ Call create / delete cluster pair methods """ pair_id_source = self.get_src_pair_id() # If already paired, find the cluster_pair_id of destination cluster if pair_id_source: pair_id_dest = self.get_dest_pair_id() # calling helper to determine action cd_action = self.na_helper.get_cd_action(pair_id_source, self.parameters) if cd_action == "create": self.pair_clusters() elif cd_action == "delete": self.unpair_clusters(pair_id_source, pair_id_dest) self.module.exit_json(changed=self.na_helper.changed)
class ElementSWInitiators(object): """ Element Software Manage Element SW initiators """ def __init__(self): self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() self.argument_spec.update(dict( initiators=dict( type='list', elements='dict', options=dict( name=dict(type='str', required=True), alias=dict(type='str', default=None), initiator_id=dict(type='int', default=None), volume_access_group_id=dict(type='int', default=None), attributes=dict(type='dict', default=None), ) ), state=dict(choices=['present', 'absent'], default='present'), )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.debug = list() if HAS_SF_SDK is False: self.module.fail_json(msg="Unable to import the SolidFire Python SDK") else: self.sfe = netapp_utils.create_sf_connection(module=self.module) self.elementsw_helper = NaElementSWModule(self.sfe) # iterate over each user-provided initiator for initiator in self.parameters.get('initiators'): # add telemetry attributes if 'attributes' in initiator and initiator['attributes']: initiator['attributes'].update(self.elementsw_helper.set_element_attributes(source='na_elementsw_initiators')) else: initiator['attributes'] = self.elementsw_helper.set_element_attributes(source='na_elementsw_initiators') def compare_initiators(self, user_initiator, existing_initiator): """ compare user input initiator with existing dict :return: True if matched, False otherwise """ if user_initiator is None or existing_initiator is None: return False changed = False for param in user_initiator: # lookup initiator_name instead of name if param == 'name': if user_initiator['name'] == existing_initiator['initiator_name']: pass elif param == 'initiator_id': # can't change the key pass elif user_initiator[param] == existing_initiator[param]: pass else: self.debug.append('Initiator: %s. Changed: %s from: %s to %s' % (user_initiator['name'], param, str(existing_initiator[param]), str(user_initiator[param]))) changed = True return changed def initiator_to_dict(self, initiator_obj): """ converts initiator class object to dict :return: reconstructed initiator dict """ known_params = ['initiator_name', 'alias', 'initiator_id', 'volume_access_groups', 'volume_access_group_id', 'attributes'] initiator_dict = {} # missing parameter cause error # so assign defaults for param in known_params: initiator_dict[param] = getattr(initiator_obj, param, None) if initiator_dict['volume_access_groups'] is not None: if len(initiator_dict['volume_access_groups']) == 1: initiator_dict['volume_access_group_id'] = initiator_dict['volume_access_groups'][0] elif len(initiator_dict['volume_access_groups']) > 1: self.module.fail_json(msg="Only 1 access group is supported, found: %s" % repr(initiator_obj)) del initiator_dict['volume_access_groups'] return initiator_dict def find_initiator(self, id=None, name=None): """ find a specific initiator :return: initiator dict """ initiator_details = None if self.all_existing_initiators is None: return initiator_details for initiator in self.all_existing_initiators: # if name is provided or # if id is provided if name is not None: if initiator.initiator_name == name: initiator_details = self.initiator_to_dict(initiator) elif id is not None: if initiator.initiator_id == id: initiator_details = self.initiator_to_dict(initiator) else: # if neither id nor name provided # return everything initiator_details = self.all_existing_initiators return initiator_details @staticmethod def rename_key(obj, old_name, new_name): obj[new_name] = obj.pop(old_name) def create_initiator(self, initiator): """ create initiator """ # SF SDK is using camelCase for this one self.rename_key(initiator, 'volume_access_group_id', 'volumeAccessGroupID') # create_initiators needs an array initiator_list = [initiator] try: self.sfe.create_initiators(initiator_list) except Exception as exception_object: self.module.fail_json(msg='Error creating initiator %s' % (to_native(exception_object)), exception=traceback.format_exc()) def delete_initiator(self, initiator): """ delete initiator """ # delete_initiators needs an array initiator_id_array = [initiator] try: self.sfe.delete_initiators(initiator_id_array) except Exception as exception_object: self.module.fail_json(msg='Error deleting initiator %s' % (to_native(exception_object)), exception=traceback.format_exc()) def modify_initiator(self, initiator, existing_initiator): """ modify initiator """ # create the new initiator dict # by merging old and new values merged_initiator = existing_initiator.copy() # can't change the key del initiator['initiator_id'] merged_initiator.update(initiator) # we MUST create an object before sending # the new initiator to modify_initiator initiator_object = ModifyInitiator(initiator_id=merged_initiator['initiator_id'], alias=merged_initiator['alias'], volume_access_group_id=merged_initiator['volume_access_group_id'], attributes=merged_initiator['attributes']) initiator_list = [initiator_object] try: self.sfe.modify_initiators(initiators=initiator_list) except Exception as exception_object: self.module.fail_json(msg='Error modifying initiator: %s' % (to_native(exception_object)), exception=traceback.format_exc()) def apply(self): """ configure initiators """ changed = False result_message = None # get all user provided initiators input_initiators = self.parameters.get('initiators') # get all initiators # store in a cache variable self.all_existing_initiators = self.sfe.list_initiators().initiators # iterate over each user-provided initiator for in_initiator in input_initiators: if self.parameters.get('state') == 'present': # check if initiator_id is provided and exists if 'initiator_id' in in_initiator and in_initiator['initiator_id'] is not None and \ self.find_initiator(id=in_initiator['initiator_id']) is not None: if self.compare_initiators(in_initiator, self.find_initiator(id=in_initiator['initiator_id'])): changed = True result_message = 'modifying initiator(s)' self.modify_initiator(in_initiator, self.find_initiator(id=in_initiator['initiator_id'])) # otherwise check if name is provided and exists elif 'name' in in_initiator and in_initiator['name'] is not None and self.find_initiator(name=in_initiator['name']) is not None: if self.compare_initiators(in_initiator, self.find_initiator(name=in_initiator['name'])): changed = True result_message = 'modifying initiator(s)' self.modify_initiator(in_initiator, self.find_initiator(name=in_initiator['name'])) # this is a create op if initiator doesn't exist else: changed = True result_message = 'creating initiator(s)' self.create_initiator(in_initiator) elif self.parameters.get('state') == 'absent': # delete_initiators only processes ids # so pass ids of initiators to method if 'name' in in_initiator and in_initiator['name'] is not None and \ self.find_initiator(name=in_initiator['name']) is not None: changed = True result_message = 'deleting initiator(s)' self.delete_initiator(self.find_initiator(name=in_initiator['name'])['initiator_id']) elif 'initiator_id' in in_initiator and in_initiator['initiator_id'] is not None and \ self.find_initiator(id=in_initiator['initiator_id']) is not None: changed = True result_message = 'deleting initiator(s)' self.delete_initiator(in_initiator['initiator_id']) if self.module.check_mode is True: result_message = "Check mode, skipping changes" if self.debug: result_message += ". %s" % self.debug self.module.exit_json(changed=changed, msg=result_message)