#!/usr/bin/python

# Copyright: (c) 2024, Dell Technologies
# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt)

""" Ansible module for managing SDS on Dell Technologies (Dell) PowerFlex"""

from __future__ import (absolute_import, division, print_function)

__metaclass__ = type

DOCUMENTATION = r'''
module: sds
version_added: '1.1.0'
short_description: Manage SDS on Dell PowerFlex
description:
- Managing SDS on PowerFlex storage system includes
  creating new SDS, getting details of SDS, adding/removing IP to/from SDS,
  modifying attributes of SDS, and deleting SDS.
author:
- Rajshree Khare (@khareRajshree) <ansible.team@dell.com>
- Trisha Datta (@trisha-dell) <ansible.team@dell.com>
extends_documentation_fragment:
  - dellemc.powerflex.powerflex
options:
  sds_name:
    description:
    - The name of the SDS.
    - Mandatory for create operation.
    - It is unique across the PowerFlex array.
    - Mutually exclusive with I(sds_id).
    type: str
  sds_id:
    description:
    - The ID of the SDS.
    - Except create operation, all other operations can be performed
      using I(sds_id).
    - Mutually exclusive with I(sds_name).
    type: str
  protection_domain_name:
    description:
    - The name of the protection domain.
    - Mutually exclusive with I(protection_domain_id).
    type: str
  protection_domain_id:
    description:
    - The ID of the protection domain.
    - Mutually exclusive with I(protection_domain_name).
    type: str
  sds_ip_list:
    description:
    - Dictionary of IPs and their roles for the SDS.
    - At least one IP-role is mandatory while creating a SDS.
    - IP-roles can be updated as well.
    type: list
    elements: dict
    suboptions:
      ip:
        description:
        - IP address of the SDS.
        type: str
        required: true
      role:
        description:
        - Role assigned to the SDS IP address.
        choices: ['sdsOnly', 'sdcOnly', 'all']
        type: str
        required: true
  sds_ip_state:
    description:
    - State of IP with respect to the SDS.
    choices: ['present-in-sds', 'absent-in-sds']
    type: str
  rfcache_enabled:
    description:
    - Whether to enable the Read Flash cache.
    type: bool
  rmcache_enabled:
    description:
    - Whether to enable the Read RAM cache.
    type: bool
  rmcache_size:
    description:
    - Read RAM cache size (in MB).
    - Minimum size is 128 MB.
    - Maximum size is 3911 MB.
    type: int
  sds_new_name:
    description:
    - SDS new name.
    type: str
  performance_profile:
    description:
    - Performance profile to apply to the SDS.
    - The HighPerformance profile configures a predefined set of parameters
      for very high performance use cases.
    - Default value by API is C(HighPerformance).
    choices: ['Compact', 'HighPerformance']
    type: str
  fault_set_name:
    description:
    - Name of the fault set.
    - Mutually exclusive with I(fault_set_id).
    type: str
  fault_set_id:
    description:
    - Unique identifier of the fault set.
    - Mutually exclusive with I(fault_set_name).
    type: str
  state:
    description:
    - State of the SDS.
    choices: ['present', 'absent']
    required: true
    type: str
notes:
  - The maximum limit for the IPs that can be associated with an SDS is 8.
  - There needs to be at least 1 IP for SDS communication and 1 for SDC
    communication.
  - If only 1 IP exists, it must be with role 'all'; else 1 IP
    can be with role 'all'and other IPs with role 'sdcOnly'; or 1 IP must be
    with role 'sdsOnly' and others with role 'sdcOnly'.
  - There can be 1 or more IPs with role 'sdcOnly'.
  - There must be only 1 IP with SDS role (either with role 'all' or
    'sdsOnly').
  - SDS can be created with RF cache disabled, but, be aware that the RF cache
    is not always updated. In this case, the user should re-try the operation.
  - The I(check_mode) is supported.
'''

EXAMPLES = r'''
- name: Create SDS
  dellemc.powerflex.sds:
    hostname: "{{hostname}}"
    username: "{{username}}"
    password: "{{password}}"
    validate_certs: "{{validate_certs}}"
    port: "{{port}}"
    sds_name: "node0"
    protection_domain_name: "domain1"
    sds_ip_list:
      - ip: "198.10.xxx.xxx"
        role: "all"
    sds_ip_state: "present-in-sds"
    state: "present"

- name: Create SDS with all parameters
  dellemc.powerflex.sds:
    hostname: "{{hostname}}"
    username: "{{username}}"
    password: "{{password}}"
    validate_certs: "{{validate_certs}}"
    port: "{{port}}"
    sds_name: "node1"
    protection_domain_name: "domain1"
    fault_set_name: "faultset1"
    sds_ip_list:
      - ip: "198.10.xxx.xxx"
        role: "sdcOnly"
    sds_ip_state: "present-in-sds"
    rmcache_enabled: true
    rmcache_size: 128
    performance_profile: "HighPerformance"
    state: "present"

- name: Get SDS details using name
  dellemc.powerflex.sds:
    hostname: "{{hostname}}"
    username: "{{username}}"
    password: "{{password}}"
    validate_certs: "{{validate_certs}}"
    port: "{{port}}"
    sds_name: "node0"
    state: "present"

- name: Get SDS details using ID
  dellemc.powerflex.sds:
    hostname: "{{hostname}}"
    username: "{{username}}"
    password: "{{password}}"
    validate_certs: "{{validate_certs}}"
    port: "{{port}}"
    sds_id: "5718253c00000004"
    state: "present"

- name: Modify SDS attributes using name
  dellemc.powerflex.sds:
    hostname: "{{hostname}}"
    username: "{{username}}"
    password: "{{password}}"
    validate_certs: "{{validate_certs}}"
    port: "{{port}}"
    sds_name: "node0"
    sds_new_name: "node0_new"
    rfcache_enabled: true
    rmcache_enabled: true
    rmcache_size: 256
    performance_profile: "HighPerformance"
    state: "present"

- name: Modify SDS attributes using ID
  dellemc.powerflex.sds:
    hostname: "{{hostname}}"
    username: "{{username}}"
    password: "{{password}}"
    validate_certs: "{{validate_certs}}"
    port: "{{port}}"
    sds_id: "5718253c00000004"
    sds_new_name: "node0_new"
    rfcache_enabled: true
    rmcache_enabled: true
    rmcache_size: 256
    performance_profile: "HighPerformance"
    state: "present"

- name: Add IP and role to an SDS
  dellemc.powerflex.sds:
    hostname: "{{hostname}}"
    username: "{{username}}"
    password: "{{password}}"
    validate_certs: "{{validate_certs}}"
    port: "{{port}}"
    sds_name: "node0"
    sds_ip_list:
      - ip: "198.10.xxx.xxx"
        role: "sdcOnly"
    sds_ip_state: "present-in-sds"
    state: "present"

- name: Remove IP and role from an SDS
  dellemc.powerflex.sds:
    hostname: "{{hostname}}"
    username: "{{username}}"
    password: "{{password}}"
    validate_certs: "{{validate_certs}}"
    port: "{{port}}"
    sds_name: "node0"
    sds_ip_list:
      - ip: "198.10.xxx.xxx"
        role: "sdcOnly"
    sds_ip_state: "absent-in-sds"
    state: "present"

- name: Delete SDS using name
  dellemc.powerflex.sds:
    hostname: "{{hostname}}"
    username: "{{username}}"
    password: "{{password}}"
    validate_certs: "{{validate_certs}}"
    port: "{{port}}"
    sds_name: "node0"
    state: "absent"

- name: Delete SDS using ID
  dellemc.powerflex.sds:
    hostname: "{{hostname}}"
    username: "{{username}}"
    password: "{{password}}"
    validate_certs: "{{validate_certs}}"
    port: "{{port}}"
    sds_id: "5718253c00000004"
    state: "absent"
'''

RETURN = r'''
changed:
    description: Whether or not the resource has changed.
    returned: always
    type: bool
    sample: 'false'
sds_details:
    description: Details of the SDS.
    returned: When SDS exists
    type: dict
    contains:
        authenticationError:
            description: Indicates authentication error.
            type: str
        certificateInfo:
            description: Information about certificate.
            type: str
        configuredDrlMode:
            description: Configured DRL mode.
            type: str
        drlMode:
            description: DRL mode.
            type: str
        faultSetId:
            description: Fault set ID.
            type: str
        fglMetadataCacheSize:
            description: FGL metadata cache size.
            type: int
        fglMetadataCacheState:
            description: FGL metadata cache state.
            type: str
        fglNumConcurrentWrites:
            description: FGL concurrent writes.
            type: int
        id:
            description: SDS ID.
            type: str
        ipList:
            description: SDS IP list.
            type: list
            contains:
                ip:
                    description: IP present in the SDS.
                    type: str
                role:
                    description: Role of the SDS IP.
                    type: str
        lastUpgradeTime:
            description: Last time SDS was upgraded.
            type: str
        links:
            description: SDS links.
            type: list
            contains:
                href:
                    description: SDS instance URL.
                    type: str
                rel:
                    description: SDS's relationship with different entities.
                    type: str
        maintenanceState:
            description: Maintenance state.
            type: str
        maintenanceType:
            description: Maintenance type.
            type: str
        mdmConnectionState:
            description: MDM connection state.
            type: str
        membershipState:
            description: Membership state.
            type: str
        name:
            description: Name of the SDS.
            type: str
        numOfIoBuffers:
            description: Number of IO buffers.
            type: int
        numRestarts:
            description: Number of restarts.
            type: int
        onVmWare:
            description: Presence on VMware.
            type: bool
        perfProfile:
            description: Performance profile.
            type: str
        port:
            description: SDS port.
            type: int
        protectionDomainId:
            description: Protection Domain ID.
            type: str
        protectionDomainName:
            description: Protection Domain Name.
            type: str
        raidControllers:
            description: Number of RAID controllers.
            type: int
        rfcacheEnabled:
            description: Whether RF cache is enabled or not.
            type: bool
        rfcacheErrorApiVersionMismatch:
            description: RF cache error for API version mismatch.
            type: bool
        rfcacheErrorDeviceDoesNotExist:
            description: RF cache error for device does not exist.
            type: bool
        rfcacheErrorInconsistentCacheConfiguration:
            description: RF cache error for inconsistent cache configuration.
            type: bool
        rfcacheErrorInconsistentSourceConfiguration:
            description: RF cache error for inconsistent source configuration.
            type: bool
        rfcacheErrorInvalidDriverPath:
            description: RF cache error for invalid driver path.
            type: bool
        rfcacheErrorLowResources:
            description: RF cache error for low resources.
            type: bool
        rmcacheEnabled:
            description: Whether Read RAM cache is enabled or not.
            type: bool
        rmcacheFrozen:
            description: RM cache frozen.
            type: bool
        rmcacheMemoryAllocationState:
            description: RM cache memory allocation state.
            type: bool
        rmcacheSizeInKb:
            description: RM cache size in KB.
            type: int
        rmcacheSizeInMb:
            description: RM cache size in MB.
            type: int
        sdsConfigurationFailure:
            description: SDS configuration failure.
            type: str
        sdsDecoupled:
            description: SDS decoupled.
            type: str
        sdsReceiveBufferAllocationFailures:
            description: SDS receive buffer allocation failures.
            type: str
        sdsState:
            description: SDS state.
            type: str
        softwareVersionInfo:
            description: SDS software version information.
            type: str
    sample: {
        "authenticationError": "None",
        "certificateInfo": null,
        "configuredDrlMode": "Volatile",
        "drlMode": "Volatile",
        "faultSetId": null,
        "fglMetadataCacheSize": 0,
        "fglMetadataCacheState": "Disabled",
        "fglNumConcurrentWrites": 1000,
        "id": "8f3bb0cc00000002",
        "ipList": [
            {
                "ip": "10.47.xxx.xxx",
                "role": "all"
            }
        ],
        "lastUpgradeTime": 0,
        "links": [
            {
                "href": "/api/instances/Sds::8f3bb0cc00000002",
                "rel": "self"
            },
            {
                "href": "/api/instances/Sds::8f3bb0cc00000002/relationships
                        /Statistics",
                "rel": "/api/Sds/relationship/Statistics"
            },
            {
                "href": "/api/instances/Sds::8f3bb0cc00000002/relationships
                        /SpSds",
                "rel": "/api/Sds/relationship/SpSds"
            },
            {
                "href": "/api/instances/Sds::8f3bb0cc00000002/relationships
                        /Device",
                "rel": "/api/Sds/relationship/Device"
            },
            {
                "href": "/api/instances/ProtectionDomain::9300c1f900000000",
                "rel": "/api/parent/relationship/protectionDomainId"
            }
        ],
        "maintenanceState": "NoMaintenance",
        "maintenanceType": "NoMaintenance",
        "mdmConnectionState": "Connected",
        "membershipState": "Joined",
        "name": "node0",
        "numOfIoBuffers": null,
        "numRestarts": 2,
        "onVmWare": true,
        "perfProfile": "HighPerformance",
        "port": 7072,
        "protectionDomainId": "9300c1f900000000",
        "protectionDomainName": "domain1",
        "raidControllers": null,
        "rfcacheEnabled": true,
        "rfcacheErrorApiVersionMismatch": false,
        "rfcacheErrorDeviceDoesNotExist": false,
        "rfcacheErrorInconsistentCacheConfiguration": false,
        "rfcacheErrorInconsistentSourceConfiguration": false,
        "rfcacheErrorInvalidDriverPath": false,
        "rfcacheErrorLowResources": false,
        "rmcacheEnabled": true,
        "rmcacheFrozen": false,
        "rmcacheMemoryAllocationState": "AllocationPending",
        "rmcacheSizeInKb": 131072,
        "rmcacheSizeInMb": 128,
        "sdsConfigurationFailure": null,
        "sdsDecoupled": null,
        "sdsReceiveBufferAllocationFailures": null,
        "sdsState": "Normal",
        "softwareVersionInfo": "R3_6.0.0"
    }
'''

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell\
    import utils
from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell.libraries.powerflex_base \
    import PowerFlexBase
from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell.libraries.configuration \
    import Configuration
import copy

LOG = utils.get_logger('sds')


class PowerFlexSDS(PowerFlexBase):
    """Class with SDS operations"""

    def __init__(self):
        """ Define all parameters required by this module"""
        self.module_params = utils.get_powerflex_gateway_host_parameters()
        self.module_params.update(get_powerflex_sds_parameters())

        mut_ex_args = [['sds_name', 'sds_id'],
                       ['protection_domain_name', 'protection_domain_id'],
                       ['fault_set_name', 'fault_set_id']]

        required_together_args = [['sds_ip_list', 'sds_ip_state']]

        required_one_of_args = [['sds_name', 'sds_id']]

        # initialize the Ansible module
        ansible_module_params = {
            'argument_spec': get_powerflex_sds_parameters(),
            'supports_check_mode': True,
            'mutually_exclusive': mut_ex_args,
            'required_one_of': required_one_of_args,
            'required_together': required_together_args
        }
        super().__init__(AnsibleModule, ansible_module_params)

        self.result = dict(
            changed=False,
            sds_details={}
        )

    def validate_rmcache_size_parameter(self, rmcache_enabled, rmcache_size):
        """Validate the input parameters"""

        # RM cache size cannot be set only when RM cache is enabled
        if rmcache_size is not None and rmcache_enabled is False:
            error_msg = "RM cache size can be set only when RM cache " \
                        "is enabled, please enable it along with RM " \
                        "cache size."
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

    def validate_ip_parameter(self, sds_ip_list):
        """Validate the input parameters"""

        if sds_ip_list is None or len(sds_ip_list) == 0:
            error_msg = "Provide valid values for " \
                        "sds_ip_list as 'ip' and 'role' for Create/Modify " \
                        "operations."
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

    def get_sds_details(self, sds_name=None, sds_id=None):
        """Get SDS details
            :param sds_name: Name of the SDS
            :type sds_name: str
            :param sds_id: ID of the SDS
            :type sds_id: str
            :return: Details of SDS if it exist
            :rtype: dict
        """

        id_or_name = sds_id if sds_id else sds_name

        try:
            if sds_name:
                sds_details = self.powerflex_conn.sds.get(
                    filter_fields={'name': sds_name})
            else:
                sds_details = self.powerflex_conn.sds.get(
                    filter_fields={'id': sds_id})

            if len(sds_details) == 0:
                msg = "SDS with identifier '%s' not found" % id_or_name
                LOG.info(msg)
                return None

            return sds_details[0]

        except Exception as e:
            error_msg = "Failed to get the SDS '%s' with error '%s'" \
                        % (id_or_name, str(e))
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

    def get_protection_domain(
        self, protection_domain_name=None, protection_domain_id=None
    ):
        """Get the details of a protection domain in a given PowerFlex storage
        system"""
        return Configuration(self.powerflex_conn, self.module).get_protection_domain(
            protection_domain_name=protection_domain_name, protection_domain_id=protection_domain_id)

    def get_fault_set(self, fault_set_name=None, fault_set_id=None, protection_domain_id=None):
        """Get fault set details
            :param fault_set_name: Name of the fault set
            :param fault_set_id: Id of the fault set
            :param protection_domain_id: ID of the protection domain
            :return: Fault set details
            :rtype: dict
        """
        return Configuration(self.powerflex_conn, self.module).get_fault_set(
            fault_set_name=fault_set_name, fault_set_id=fault_set_id, protection_domain_id=protection_domain_id)

    def restructure_ip_role_dict(self, sds_ip_list):
        """Restructure IP role dict
            :param sds_ip_list: List of one or more IP addresses and
                                their roles
            :type sds_ip_list: list[dict]
            :return: List of one or more IP addresses and their roles
            :rtype: list[dict]
        """
        new_sds_ip_list = []
        for item in sds_ip_list:
            new_sds_ip_list.append({"SdsIp": item})
        return new_sds_ip_list

    def validate_create(self, protection_domain_id, sds_ip_list, sds_ip_state, sds_name,
                        sds_id, sds_new_name, rmcache_enabled=None, rmcache_size=None,
                        fault_set_id=None):

        if sds_name is None or len(sds_name.strip()) == 0:
            error_msg = "Please provide valid sds_name value for " \
                        "creation of SDS."
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

        if protection_domain_id is None:
            error_msg = "Protection Domain is a mandatory parameter " \
                        "for creating an SDS. Please enter a valid value."
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

        if sds_ip_list is None or len(sds_ip_list) == 0:
            error_msg = "Please provide valid sds_ip_list values for " \
                        "creation of SDS."
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

        if sds_ip_state is not None and sds_ip_state != "present-in-sds":
            error_msg = "Incorrect IP state given for creation of SDS."
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

        if sds_id:
            error_msg = "Creation of SDS is allowed using sds_name " \
                        "only, sds_id given."
            LOG.info(error_msg)
            self.module.fail_json(msg=error_msg)

    def create_sds(self, protection_domain_id, sds_ip_list, sds_ip_state, sds_name,
                   sds_id, sds_new_name, rmcache_enabled=None, rmcache_size=None, fault_set_id=None):
        """Create SDS
            :param protection_domain_id: ID of the Protection Domain
            :type protection_domain_id: str
            :param sds_ip_list: List of one or more IP addresses associated
                                with the SDS over which the data will be
                                transferred.
            :type sds_ip_list: list[dict]
            :param sds_ip_state: SDS IP state
            :type sds_ip_state: str
            :param sds_name: SDS name
            :type sds_name: str
            :param rmcache_enabled: Whether to enable the Read RAM cache
            :type rmcache_enabled: bool
            :param rmcache_size: Read RAM cache size (in MB)
            :type rmcache_size: int
            :param fault_set_id: ID of the Fault Set
            :type fault_set_id: str
            :return: Boolean indicating if create operation is successful
        """
        try:

            # Restructure IP-role parameter format
            self.validate_create(protection_domain_id=protection_domain_id,
                                 sds_ip_list=sds_ip_list, sds_ip_state=sds_ip_state,
                                 sds_name=sds_name, sds_id=sds_id, sds_new_name=sds_new_name,
                                 rmcache_enabled=rmcache_enabled, rmcache_size=rmcache_size,
                                 fault_set_id=fault_set_id)

            self.validate_ip_parameter(sds_ip_list)

            if not self.module.check_mode:
                if sds_ip_list and sds_ip_state == "present-in-sds":
                    sds_ip_list = self.restructure_ip_role_dict(sds_ip_list)

                if rmcache_size is not None:
                    self.validate_rmcache_size_parameter(rmcache_enabled=rmcache_enabled,
                                                         rmcache_size=rmcache_size)
                    # set rmcache size in KB
                    rmcache_size = rmcache_size * 1024

                create_params = ("protection_domain_id: %s,"
                                 " sds_ip_list: %s,"
                                 " sds_name: %s,"
                                 " rmcache_enabled: %s, "
                                 " rmcache_size_KB: %s, "
                                 " fault_set_id: %s"
                                 % (protection_domain_id, sds_ip_list,
                                    sds_name, rmcache_enabled, rmcache_size,
                                    fault_set_id))
                LOG.info("Creating SDS with params: %s", create_params)

                self.powerflex_conn.sds.create(
                    protection_domain_id=protection_domain_id,
                    sds_ips=sds_ip_list,
                    name=sds_name,
                    rmcache_enabled=rmcache_enabled,
                    rmcache_size_in_kb=rmcache_size,
                    fault_set_id=fault_set_id)
            return self.get_sds_details(sds_name=sds_name)

        except Exception as e:
            error_msg = f"Create SDS {sds_name} operation failed with error {str(e)}"
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

    def to_modify(self, sds_details, sds_new_name, rfcache_enabled,
                  rmcache_enabled, rmcache_size, performance_profile):
        """
        :param sds_details: Details of the SDS
        :type sds_details: dict
        :param sds_new_name: New name of SDS
        :type sds_new_name: str
        :param rfcache_enabled: Whether to enable the Read Flash cache
        :type rfcache_enabled: bool
        :param rmcache_enabled: Whether to enable the Read RAM cache
        :type rmcache_enabled: bool
        :param rmcache_size: Read RAM cache size (in MB)
        :type rmcache_size: int
        :param performance_profile: Performance profile to apply to the SDS
        :type performance_profile: str
        :return: Dictionary containing the attributes of SDS which are to be
                 updated
        :rtype: dict
        """
        modify_dict = {}

        if sds_new_name is not None and \
                sds_new_name != sds_details['name']:
            modify_dict['name'] = sds_new_name

        param_input = dict()
        param_input['rfcacheEnabled'] = rfcache_enabled
        param_input['rmcacheEnabled'] = rmcache_enabled
        param_input['perfProfile'] = performance_profile

        param_list = ['rfcacheEnabled', 'rmcacheEnabled', 'perfProfile']
        for param in param_list:
            if param_input[param] is not None and \
                    sds_details[param] != param_input[param]:
                modify_dict[param] = param_input[param]

        if rmcache_size is not None:
            self.validate_rmcache_size_parameter(rmcache_enabled,
                                                 rmcache_size)
            exisitng_size_mb = sds_details['rmcacheSizeInKb'] / 1024
            if rmcache_size != exisitng_size_mb:
                if sds_details['rmcacheEnabled']:
                    modify_dict['rmcacheSizeInMB'] = rmcache_size
                else:
                    error_msg = "Failed to update RM cache size for the " \
                                "SDS '%s' as RM cache is disabled " \
                                "previously, please enable it before " \
                                "setting the size." \
                                % sds_details['name']
                    LOG.error(error_msg)
                    self.module.fail_json(msg=error_msg)

        return modify_dict

    def modify_sds_attributes(self, sds_id, modify_dict,
                              create_flag=False):
        """Modify SDS attributes
            :param sds_id: SDS ID
            :type sds_id: str
            :param modify_dict: Dictionary containing the attributes of SDS
                                which are to be updated
            :type modify_dict: dict
            :param create_flag: Flag to indicate whether modify operation is
                                followed by create operation or not
            :type create_flag: bool
            :return: Boolean indicating if the operation is successful
        """
        try:
            msg = "Dictionary containing attributes which are to be" \
                  " updated is '%s'." % (str(modify_dict))
            LOG.info(msg)

            if not self.module.check_mode:
                if 'name' in modify_dict:
                    self.powerflex_conn.sds.rename(sds_id, modify_dict['name'])
                    msg = "The name of the SDS is updated to '%s' successfully." \
                          % modify_dict['name']
                    LOG.info(msg)

                if 'rfcacheEnabled' in modify_dict:
                    self.powerflex_conn.sds.set_rfcache_enabled(
                        sds_id, modify_dict['rfcacheEnabled'])
                    msg = "The use RFcache is updated to '%s' successfully." \
                          % modify_dict['rfcacheEnabled']
                    LOG.info(msg)

                if 'rmcacheEnabled' in modify_dict:
                    self.powerflex_conn.sds.set_rmcache_enabled(
                        sds_id, modify_dict['rmcacheEnabled'])
                    msg = "The use RMcache is updated to '%s' successfully." \
                          % modify_dict['rmcacheEnabled']
                    LOG.info(msg)

                if 'rmcacheSizeInMB' in modify_dict:
                    self.powerflex_conn.sds.set_rmcache_size(
                        sds_id, modify_dict['rmcacheSizeInMB'])
                    msg = "The size of RMcache is updated to '%s' successfully." \
                          % modify_dict['rmcacheSizeInMB']
                    LOG.info(msg)

                if 'perfProfile' in modify_dict:
                    self.powerflex_conn.sds.set_performance_parameters(
                        sds_id, modify_dict['perfProfile'])
                    msg = "The performance profile is updated to '%s'" \
                          % modify_dict['perfProfile']
                    LOG.info(msg)

            return self.get_sds_details(sds_id=sds_id)
        except Exception as e:
            if create_flag:
                error_msg = "Create SDS is successful, but failed to update" \
                            " the SDS '%s' with error '%s'"\
                            % (sds_id, str(e))
            else:
                error_msg = "Failed to update the SDS '%s' with error '%s'" \
                            % (sds_id, str(e))
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

    def identify_ip_role_add(self, sds_ip_list, sds_details, sds_ip_state):
        # identify IPs to add or roles to update

        existing_ip_role_list = sds_details['ipList']
        update_role = []
        ips_to_add = []

        # identify IPs to add
        existing_ip_list = []
        if existing_ip_role_list:
            for ip in existing_ip_role_list:
                existing_ip_list.append(ip['ip'])
        for given_ip in sds_ip_list:
            ip = given_ip['ip']
            if ip not in existing_ip_list:
                ips_to_add.append(given_ip)
        LOG.info("IP(s) to be added: %s", ips_to_add)

        if len(ips_to_add) != 0:
            for ip in ips_to_add:
                sds_ip_list.remove(ip)

        # identify IPs whose role needs to be updated
        update_role = [ip for ip in sds_ip_list
                       if ip not in existing_ip_role_list]
        LOG.info("Role update needed for: %s", update_role)
        return ips_to_add, update_role

    def identify_ip_role_remove(self, sds_ip_list, sds_details, sds_ip_state):
        # identify IPs to remove

        existing_ip_role_list = sds_details['ipList']
        if sds_ip_state == "absent-in-sds":
            ips_to_remove = [ip for ip in existing_ip_role_list
                             if ip in sds_ip_list]
            if len(ips_to_remove) != 0:
                LOG.info("IP(s) to remove: %s", ips_to_remove)
                return ips_to_remove
            else:
                LOG.info("IP(s) do not exists.")
                return []

    def add_ip(self, sds_id, sds_ip_list):
        """Add IP to SDS
            :param sds_id: SDS ID
            :type sds_id: str
            :param sds_ip_list: List of one or more IP addresses and
                                their roles
            :type sds_ip_list: list[dict]
            :return: Boolean indicating if add IP operation is successful
        """
        try:
            if not self.module.check_mode:
                for ip in sds_ip_list:
                    LOG.info("IP to add: %s", ip)
                    self.powerflex_conn.sds.add_ip(sds_id=sds_id, sds_ip=ip)
                    LOG.info("IP added successfully.")
            return True
        except Exception as e:
            error_msg = "Add IP to SDS '%s' operation failed with " \
                        "error '%s'" % (sds_id, str(e))
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

    def update_role(self, sds_id, sds_ip_list):
        """Update IP's role for an SDS
            :param sds_id: SDS ID
            :type sds_id: str
            :param sds_ip_list: List of one or more IP addresses and
                                their roles
            :type sds_ip_list: list[dict]
            :return: Boolean indicating if add IP operation is successful
        """
        try:
            if not self.module.check_mode:
                LOG.info("Role updates for: %s", sds_ip_list)
                if len(sds_ip_list) != 0:
                    for ip in sds_ip_list:
                        LOG.info("ip-role: %s", ip)
                        self.powerflex_conn.sds.set_ip_role(sds_id, ip['ip'],
                                                            ip['role'])
                        msg = "The role '%s' for IP '%s' is updated " \
                              "successfully." % (ip['role'], ip['ip'])
                        LOG.info(msg)
            return True
        except Exception as e:
            error_msg = "Update role of IP for SDS '%s' operation failed " \
                        "with error '%s'" % (sds_id, str(e))
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

    def remove_ip(self, sds_id, sds_ip_list):
        """Remove IP from SDS
            :param sds_id: SDS ID
            :type sds_id: str
            :param sds_ip_list: List of one or more IP addresses and
                                their roles.
            :type sds_ip_list: list[dict]
            :return: Boolean indicating if remove IP operation is successful
        """
        try:
            if not self.module.check_mode:
                for ip in sds_ip_list:
                    LOG.info("IP to remove: %s", ip)
                    self.powerflex_conn.sds.remove_ip(sds_id=sds_id, ip=ip['ip'])
                    LOG.info("IP removed successfully.")
            return True
        except Exception as e:
            error_msg = "Remove IP from SDS '%s' operation failed with " \
                        "error '%s'" % (sds_id, str(e))
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

    def delete_sds(self, sds_id):
        """Delete SDS
            :param sds_id: SDS ID
            :type sds_id: str
            :return: Boolean indicating if delete operation is successful
        """
        try:
            if not self.module.check_mode:
                self.powerflex_conn.sds.delete(sds_id)
                return None
            return self.get_sds_details(sds_id=sds_id)
        except Exception as e:
            error_msg = "Delete SDS '%s' operation failed with error '%s'" \
                        % (sds_id, str(e))
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

    def show_output(self, sds_id):
        """Show SDS details
            :param sds_id: ID of the SDS
            :type sds_id: str
            :return: Details of SDS
            :rtype: dict
        """

        try:
            sds_details = self.powerflex_conn.sds.get(
                filter_fields={'id': sds_id})

            if len(sds_details) == 0:
                msg = "SDS with identifier '%s' not found" % sds_id
                LOG.error(msg)
                return None

            # Append protection domain name
            if 'protectionDomainId' in sds_details[0] \
                    and sds_details[0]['protectionDomainId']:
                pd_details = self.get_protection_domain(
                    protection_domain_id=sds_details[0]['protectionDomainId'])
                sds_details[0]['protectionDomainName'] = pd_details['name']

            # Append rmcache size in MB
            if 'rmcacheSizeInKb' in sds_details[0] \
                    and sds_details[0]['rmcacheSizeInKb']:
                rmcache_size_mb = sds_details[0]['rmcacheSizeInKb'] / 1024
                sds_details[0]['rmcacheSizeInMb'] = int(rmcache_size_mb)

            # Append fault set name
            if 'faultSetId' in sds_details[0] \
                    and sds_details[0]['faultSetId']:
                fs_details = self.get_fault_set(
                    fault_set_id=sds_details[0]['faultSetId'],
                    protection_domain_id=sds_details[0]['protectionDomainId'])
                sds_details[0]['faultSetName'] = fs_details['name']

            return sds_details[0]

        except Exception as e:
            error_msg = "Failed to get the SDS '%s' with error '%s'"\
                        % (sds_id, str(e))
            LOG.error(error_msg)
            self.module.fail_json(msg=error_msg)

    def validate_parameters(self, sds_params):
        params = [sds_params['sds_name'], sds_params['sds_new_name']]
        for param in params:
            if param is not None and len(param.strip()) == 0:
                error_msg = "Provide valid value for name for the " \
                            "creation/modification of the SDS."
                LOG.error(error_msg)
                self.module.fail_json(msg=error_msg)


def get_powerflex_sds_parameters():
    """This method provide parameter required for the SDS module on
    PowerFlex"""
    return dict(
        sds_name=dict(),
        sds_id=dict(),
        sds_new_name=dict(),
        protection_domain_name=dict(),
        protection_domain_id=dict(),
        sds_ip_list=dict(
            type='list', elements='dict', options=dict(
                ip=dict(required=True),
                role=dict(required=True, choices=['all', 'sdsOnly',
                                                  'sdcOnly'])
            )
        ),
        sds_ip_state=dict(choices=['present-in-sds', 'absent-in-sds']),
        rfcache_enabled=dict(type='bool'),
        rmcache_enabled=dict(type='bool'),
        rmcache_size=dict(type='int'),
        performance_profile=dict(choices=['Compact', 'HighPerformance']),
        fault_set_name=dict(),
        fault_set_id=dict(),
        state=dict(required=True, type='str', choices=['present', 'absent'])
    )


class SDSExitHandler():
    def handle(self, sds_obj, sds_details):
        if sds_details:
            sds_obj.result["sds_details"] = sds_obj.show_output(sds_id=sds_details['id'])
        else:
            sds_obj.result["sds_details"] = None
        sds_obj.module.exit_json(**sds_obj.result)


class SDSDeleteHandler():
    def handle(self, sds_obj, sds_params, sds_details):
        if sds_params['state'] == 'absent' and sds_details:
            sds_details = sds_obj.delete_sds(sds_details['id'])
            sds_obj.result['changed'] = True

        SDSExitHandler().handle(sds_obj, sds_details)


class SDSRemoveIPHandler():
    def handle(self, sds_obj, sds_params, sds_details, sds_ip_list):
        if sds_params['state'] == 'present' and sds_details:
            # remove IPs from SDS
            remove_ip_changed = False
            if sds_params['sds_ip_state'] == "absent-in-sds":
                sds_obj.validate_ip_parameter(sds_ip_list)
                ips_to_remove = sds_obj.identify_ip_role_remove(sds_ip_list, sds_details,
                                                                sds_params['sds_ip_state'])
                if ips_to_remove:
                    remove_ip_changed = sds_obj.remove_ip(sds_details['id'], ips_to_remove)

                if remove_ip_changed:
                    sds_obj.result['changed'] = True

        SDSDeleteHandler().handle(sds_obj, sds_params, sds_details)


class SDSAddIPHandler():
    def handle(self, sds_obj, sds_params, sds_details, sds_ip_list):
        if sds_params['state'] == 'present' and sds_details:
            # add IPs to SDS
            # update IP's role for an SDS
            add_ip_changed = False
            update_role_changed = False
            if sds_params['sds_ip_state'] == "present-in-sds":
                sds_obj.validate_ip_parameter(sds_ip_list)
                ips_to_add, roles_to_update = sds_obj.identify_ip_role_add(
                    sds_ip_list, sds_details, sds_params['sds_ip_state'])
                if ips_to_add:
                    add_ip_changed = sds_obj.add_ip(sds_details['id'], ips_to_add)
                if roles_to_update:
                    update_role_changed = sds_obj.update_role(sds_details['id'],
                                                              roles_to_update)

            if add_ip_changed or update_role_changed:
                sds_obj.result['changed'] = True

        SDSRemoveIPHandler().handle(sds_obj, sds_params, sds_details, sds_ip_list)


class SDSModifyHandler():
    def handle(self, sds_obj, sds_params, sds_details, create_flag, sds_ip_list):
        if sds_params['state'] == 'present' and sds_details:
            modify_dict = sds_obj.to_modify(sds_details=sds_details,
                                            sds_new_name=sds_params['sds_new_name'],
                                            rfcache_enabled=sds_params['rfcache_enabled'],
                                            rmcache_enabled=sds_params['rmcache_enabled'],
                                            rmcache_size=sds_params['rmcache_size'],
                                            performance_profile=sds_params['performance_profile'])
            if modify_dict:
                sds_details = sds_obj.modify_sds_attributes(sds_id=sds_details['id'],
                                                            modify_dict=modify_dict,
                                                            create_flag=create_flag)
                sds_obj.result['changed'] = True

        SDSAddIPHandler().handle(sds_obj, sds_params, sds_details, sds_ip_list)


class SDSCreateHandler():
    def handle(self, sds_obj, sds_params, sds_details, protection_domain_id, fault_set_id):
        create_flag = False
        sds_ip_list = copy.deepcopy(sds_params['sds_ip_list'])
        if sds_params['state'] == 'present' and not sds_details:
            sds_details = sds_obj.create_sds(sds_name=sds_params['sds_name'],
                                             sds_id=sds_params['sds_id'],
                                             sds_new_name=sds_params['sds_new_name'],
                                             protection_domain_id=protection_domain_id,
                                             sds_ip_list=sds_ip_list,
                                             sds_ip_state=sds_params['sds_ip_state'],
                                             rmcache_enabled=sds_params['rmcache_enabled'],
                                             rmcache_size=sds_params['rmcache_size'],
                                             fault_set_id=fault_set_id)
            sds_obj.result['changed'] = True
            create_flag = True

        SDSModifyHandler().handle(sds_obj, sds_params, sds_details, create_flag, sds_ip_list)


class SDSHandler():
    def handle(self, sds_obj, sds_params):
        sds_details = sds_obj.get_sds_details(sds_params['sds_name'], sds_params['sds_id'])
        sds_obj.validate_parameters(sds_params=sds_params)
        protection_domain_id = None
        if sds_params['protection_domain_id'] or sds_params['protection_domain_name']:
            protection_domain_id = sds_obj.get_protection_domain(
                protection_domain_id=sds_params['protection_domain_id'],
                protection_domain_name=sds_params['protection_domain_name'])['id']
        fault_set_id = None
        if sds_params['fault_set_name'] or sds_params['fault_set_id']:
            fault_set_details = sds_obj.get_fault_set(fault_set_name=sds_params['fault_set_name'],
                                                      fault_set_id=sds_params['fault_set_id'],
                                                      protection_domain_id=protection_domain_id)
            if fault_set_details is None:
                error_msg = "The specified Fault set is not in the specified Protection Domain."
                LOG.error(error_msg)
                sds_obj.module.fail_json(msg=error_msg)
            else:
                fault_set_id = fault_set_details['id']
        SDSCreateHandler().handle(sds_obj, sds_params, sds_details, protection_domain_id, fault_set_id)


def main():
    """ Create PowerFlex SDS object and perform action on it
        based on user input from playbook."""
    obj = PowerFlexSDS()
    SDSHandler().handle(obj, obj.module.params)


if __name__ == '__main__':
    main()
