Example #1
0
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)
Example #5
0
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)
Example #6
0
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)
Example #8
0
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)