Пример #1
0
    def __init__(self):
        super(RealStorEnclosure, self).__init__()

        # WS Request common headers
        self.ws = WebServices()
        self.common_reqheaders = {}

        self.encl_conf = self.CONF_SECTION_MC

        self.system_persistent_cache = self.encl_cache + "system/"
        self.faults_persistent_cache = self.system_persistent_cache + "faults.json"

        # Read in mc value from configuration file
        self.mc1 = Conf.get(GLOBAL_CONF, CNTRLR_PRIMARY_IP_KEY,
                            self.DEFAULT_MC_IP)
        self.mc1_wsport = str(
            Conf.get(GLOBAL_CONF, CNTRLR_PRIMARY_PORT_KEY, ''))
        self.mc2 = Conf.get(GLOBAL_CONF, CNTRLR_SECONDARY_IP_KEY,
                            self.DEFAULT_MC_IP)
        self.mc2_wsport = str(
            Conf.get(GLOBAL_CONF, CNTRLR_SECONDARY_PORT_KEY, ''))

        self.active_ip = self.mc1
        self.active_wsport = self.mc1_wsport

        self.user = Conf.get(GLOBAL_CONF, CNTRLR_USER_KEY, self.DEFAULT_USER)
        _secret = Conf.get(GLOBAL_CONF, CNTRLR_SECRET_KEY, self.DEFAULT_PASSWD)

        self.mc_interface = Conf.get(SSPL_CONF,
                                     f"{STORAGE_ENCLOSURE}>{MGMT_INTERFACE}",
                                     "cliapi")

        self.pollfreq = int(
            Conf.get(SSPL_CONF,
                     f"{self.CONF_REALSTORSENSORS}>{POLLING_FREQUENCY}",
                     self.DEFAULT_POLL))

        self.site_id = Conf.get(GLOBAL_CONF, SITE_ID_KEY, "DC01")
        self.rack_id = Conf.get(GLOBAL_CONF, RACK_ID_KEY, "RC01")
        self.node_id = Conf.get(GLOBAL_CONF, NODE_ID_KEY, "SN01")
        self.cluster_id = Conf.get(GLOBAL_CONF, CLUSTER_ID_KEY, "CC01")
        # Decrypt MC secret
        decryption_key = encryptor.gen_key(
            ENCLOSURE, ServiceTypes.STORAGE_ENCLOSURE.value)
        self.__passwd = encryptor.decrypt(decryption_key, _secret,
                                          "RealStoreEncl")

        if self.mc_interface not in self.realstor_supported_interfaces:
            logger.error("Unspported Realstor interface configured,"
                         " monitoring and alerts generation may hamper")
            return

        # login to mc to get session key, required for querying resources
        # periodically
        self.login()
class RealStorEnclosure(StorageEnclosure):
    """RealStor Enclosure Monitor functions using CLI API Webservice Interface"""

    REALSTOR_MC_BOOTWAIT = 0
    DEFAULT_MC_IP = "127.0.0.1"
    WEBSERVICE_TIMEOUT = 20
    PERSISTENT_DATA_UPDATE_TIMEOUT = 5
    MAX_RETRIES = 1

    CONF_SECTION_MC = "STORAGE_ENCLOSURE"
    SYSTEM_INFORMATION = "SYSTEM_INFORMATION"
    CONF_REALSTORDISKSENSOR = "REALSTORDISKSENSOR"
    CONF_REALSTORCONTROLLERSENSOR = "REALSTORCONTROLLERSENSOR"
    CONF_REALSTORFANSENSOR = "REALSTORFANSENSOR"
    CONF_REALSTORPSUSENSOR = "REALSTORPSUSENSOR"
    CONF_REALSTORLOGICALVOLUMESENSOR = "REALSTORLOGICALVOLUMESENSOR"
    CONF_REALSTORSIDEPLANEEXPANDERSENSOR = "REALSTORSIDEPLANEEXPANDERSENSOR"
    CONF_REALSTORENCLOSURESENSOR = "REALSTORENCLOSURESENSOR"
    CONF_REALSTORSENSORS = "REALSTORSENSORS"
    DEFAULT_POLL = 30
    SITE_ID = "site_id"
    CLUSTER_ID = "cluster_id"
    NODE_ID = "node_id"
    RACK_ID = "rack_id"

    DEFAULT_USER = "******"
    DEFAULT_PASSWD = "!manage"

    # CLI APIs
    URI_CLIAPI_LOGIN = "******"
    URI_CLIAPI_SHOWDISKS = "/show/disks"
    URI_CLIAPI_SHOWSYSTEM = "/show/system"
    URI_CLIAPI_SHOWPSUS = "/show/power-supplies"
    URI_CLIAPI_SHOWCONTROLLERS = "/show/controllers"
    URI_CLIAPI_SHOWFANMODULES = "/show/fan-modules"
    URI_CLIAPI_SHOWENCLOSURE = "/show/enclosure"
    URI_CLIAPI_SHOWDISKGROUPS = "/show/disk-groups"
    URI_CLIAPI_SHOWVOLUMES = "/show/volumes"
    URI_CLIAPI_SHOWSENSORSTATUS = "/show/sensor-status"
    URI_CLIAPI_SASHEALTHSTATUS = "/show/sas-link-health"
    URI_CLIAPI_SHOWEVENTS = "/show/events"
    URI_CLIAPI_SHOWVERSION = "/show/version/detail"
    URI_CLIAPI_BASE = "/"
    URI_CLIAPI_DOWNLOADDEBUGDATA = "/downloadDebugData"

    URL_ENCLLOGS_POSTDATA = "/api/collectDebugData"

    # Realstor generic health states
    HEALTH_OK = "ok"
    HEALTH_FAULT = "fault"
    HEALTH_DEGRADED = "degraded"

    STATUS_NOTINSTALLED = "not installed"

    DATA_FORMAT_JSON = "json"
    FAULT_KEY = "unhealthy-component"

    # Current support for 'cliapi', future scope for 'rest', 'redfish' apis
    # once available
    realstor_supported_interfaces = ['cliapi']

    poll_system_ts = 0
    mc_timeout_counter = 0

    # resource inmemory cache
    latest_faults = {}

    # check fault irrespective, since memcache faults are mosty copy of latest
    # faults, so no comparison to check for new faults is feasible
    existing_faults = False

    def __init__(self):
        super(RealStorEnclosure, self).__init__()

        # WS Request common headers
        self.ws = WebServices()
        self.common_reqheaders = {}

        self.encl_conf = self.CONF_SECTION_MC

        self.system_persistent_cache = self.encl_cache + "system/"
        self.faults_persistent_cache = self.system_persistent_cache + "faults.json"

        # Read in mc value from configuration file
        self.mc1 = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(self.encl_conf).get("primary_controller_ip"),
            self.DEFAULT_MC_IP)
        self.mc1_wsport = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(self.encl_conf).get("primary_controller_port"),
            '')
        self.mc2 = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(self.encl_conf).get("secondary_controller_ip"),
            self.DEFAULT_MC_IP)
        self.mc2_wsport = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(
                self.encl_conf).get("secondary_controller_port"), '')

        self.active_ip = self.mc1
        self.active_wsport = self.mc1_wsport

        self.user = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(self.encl_conf).get("user"), self.DEFAULT_USER)
        self.passwd = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(self.encl_conf).get("password"),
            self.DEFAULT_PASSWD)

        self.mc_interface = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(self.encl_conf).get("mgmt_interface"), "cliapi")

        self.pollfreq = int(
            self.conf_reader._get_value_with_default(self.CONF_REALSTORSENSORS,
                                                     "polling_frequency",
                                                     self.DEFAULT_POLL))

        self.site_id = self.conf_reader._get_value_with_default(
            self.SYSTEM_INFORMATION,
            COMMON_CONFIGS.get(self.SYSTEM_INFORMATION).get(self.SITE_ID),
            '001')
        self.rack_id = self.conf_reader._get_value_with_default(
            self.SYSTEM_INFORMATION,
            COMMON_CONFIGS.get(self.SYSTEM_INFORMATION).get(self.RACK_ID),
            '001')
        self.node_id = self.conf_reader._get_value_with_default(
            self.SYSTEM_INFORMATION,
            COMMON_CONFIGS.get(self.SYSTEM_INFORMATION).get(self.NODE_ID),
            '001')
        # Need to keep cluster_id string here to generate decryption key
        self.cluster_id = self.conf_reader._get_value_with_default(
            self.SYSTEM_INFORMATION,
            COMMON_CONFIGS.get(self.SYSTEM_INFORMATION).get(self.CLUSTER_ID),
            '001')
        # Decrypt MC Password
        decryption_key = encryptor.gen_key(
            self.cluster_id, ServiceTypes.STORAGE_ENCLOSURE.value)
        self.passwd = encryptor.decrypt(decryption_key,
                                        self.passwd.encode('ascii'),
                                        "RealStoreEncl")

        if self.mc_interface not in self.realstor_supported_interfaces:
            logger.error("Unspported Realstor interface configured,"
                         " monitoring and alerts generation may hamper")
            return

        # login to mc to get session key, required for querying resources
        # periodically
        self.login()

    def _add_request_headers(self, sessionKey):
        """Add common request headers"""
        self.common_reqheaders['datatype'] = self.DATA_FORMAT_JSON
        self.common_reqheaders['sessionKey'] = sessionKey

    def build_url(self, uri):
        """Build request url"""

        wsport = ""

        if self.active_wsport.isdigit():
            wsport = ":" + self.active_wsport
        else:
            logger.warn("Non-numeric webservice port configured [%s], ignoring",\
                self.active_wsport)

        url = "http://" + self.active_ip + wsport + "/api" + uri

        return url

    def switch_to_alt_mc(self):
        """Switches active ip between primary and secondary management controller
           ips"""

        if self.mc1 == self.mc2 and \
            self.mc1_wsport == self.mc2_wsport:
            return

        if self.active_ip == self.mc1:
            self.active_ip = self.mc2
            self.active_wsport = self.mc2_wsport
        elif self.active_ip == self.mc2:
            self.active_ip = self.mc1
            self.active_wsport = self.mc1_wsport

        logger.debug("Current MC active ip {0}, active wsport {1}\
            ".format(self.active_ip, self.active_wsport))

    def ws_request(self, url, method, retry_count=MAX_RETRIES, post_data=""):
        """Make webservice requests using common utils"""
        response = None
        relogin = False
        tried_alt_ip = False

        while retry_count:
            response = self.ws.ws_request(method, url, self.common_reqheaders,
                                          post_data, self.WEBSERVICE_TIMEOUT)

            retry_count -= 1

            if response is None:
                continue

            if response.status_code != self.ws.HTTP_OK:

                # if call fails with invalid session key request or http 403
                # forbidden request, login & retry
                if response.status_code == self.ws.HTTP_FORBIDDEN and relogin is False:
                    logger.info("%s failed, retrying after login " % (url))

                    self.login()
                    relogin = True
                    continue

                elif (response.status_code == self.ws.HTTP_TIMEOUT or \
                         response.status_code == self.ws.HTTP_CONN_REFUSED or \
                         response.status_code == self.ws.HTTP_NO_ROUTE_TO_HOST) \
                         and tried_alt_ip is False:
                    self.switch_to_alt_mc()
                    tried_alt_ip = True
                    self.mc_timeout_counter += 1
                    continue
            else:
                self.mc_timeout_counter = 0

            break

        return response

    def login(self):
        """Perform realstor login to get session key & make it available
           in common request headers"""

        cli_api_auth = self.user + '_' + self.passwd

        url = self.build_url(self.URI_CLIAPI_LOGIN)
        auth_hash = hashlib.sha256(cli_api_auth.encode('utf-8')).hexdigest()
        headers = {'datatype': 'json'}

        response = self.ws.ws_get(url + auth_hash, headers, \
                       self.WEBSERVICE_TIMEOUT)

        if not response:
            logger.warn("Login webservice request failed {0}".format(url))
            return

        if response.status_code != self.ws.HTTP_OK:
            if response.status_code == self.ws.HTTP_TIMEOUT:
                self.mc_timeout_counter += 1
            logger.error("{0}:: http request for login failed with err {1}"\
                .format(self.LDR_R1_ENCL, response.status_code))
            return

        try:
            jresponse = json.loads(response.content)
        except ValueError as badjson:
            logger.error("%s returned mal-formed json:\n%s" % (url, badjson))

        if jresponse:
            if jresponse['status'][0]['return-code'] == 1:
                sessionKey = jresponse['status'][0]['response']
                self._add_request_headers(sessionKey)
            else:
                logger.error("realstor cli api login FAILED with api err %d" %
                             jresponse['status'][0]['return-code'])

    def check_system_faults_changed(self):
        """Check change in faults state"""

        changed = False

        if self.existing_faults:
            #logger.debug("existing_faults TRUE")
            return True

        if self.latest_faults != self.memcache_faults:
            changed = True
            logger.warn(
                "System faults state changed, updating cached faults!!")

        return changed

    def update_memcache_faults(self):
        self.memcache_faults = self.latest_faults

        #Update faults in persistent cache
        logger.info("Updating faults persistent cache!!")
        store.put(self.memcache_faults, self.faults_persistent_cache)

    def check_new_fault(self, fault):
        """Check if supplied is new fault"""

        if self.existing_faults:
            #logger.debug("existing_faults TRUE")
            return True

        newkid = False

        if fault not in self.memcache_faults:
            newkid = True
            for cached in self.memcache_faults:
                if fault["component-id"] == cached["component-id"] \
                    and fault["health"] == cached["health"] \
                    and fault["health-reason"] == cached["health-reason"]:
                    newkid = False
                    break

        return newkid

    def get_api_status(self, jresp):
        """Retreive realstor common api response for cli apis"""

        api_status = -1
        for status in jresp:
            if status["response-type"] != "Info":
                api_status = status["response-type-numeric"]
                break

        return api_status

    def get_system_status(self):
        """Retreive realstor system state info using cli api /show/system"""

        # poll system would get invoked through multiple realstor sensors
        # with less frequency compared to configured polling frequency
        # adding check to comply with polling frequency
        elapsed = time.time() - self.poll_system_ts

        if elapsed < self.pollfreq:
            logger.warn("/show/system request came in {0} seconds,"
                        "while configured polling frequency is {1} seconds,"
                        "ignoring".format(elapsed, self.pollfreq))
            return

        system = None

        # make ws request
        url = self.build_url(self.URI_CLIAPI_SHOWSYSTEM)
        #logger.info("show system url: %s" % url)

        response = self.ws_request(url, self.ws.HTTP_GET)

        if not response:
            logger.warn("System status unavailable as ws request failed")
            return

        if response.status_code != self.ws.HTTP_OK:
            logger.info("{0}:: http request {1} polling system status failed"
                " with http err {2}".format(self.LDR_R1_ENCL, url, \
                response.status_code))
            return

        self.poll_system_ts = time.time()

        try:
            jresponse = json.loads(response.content)
        except ValueError as badjson:
            logger.error("%s returned mal-formed json:\n%s" % (url, badjson))

        if jresponse:
            api_resp = self.get_api_status(jresponse['status'])

            if ((api_resp == -1)
                    and (response.status_code == self.ws.HTTP_OK)):
                logger.warn("/show/system api response unavailable, "
                            "marking success as http code is 200")
                api_resp = 0

            if api_resp == 0:
                system = jresponse['system'][0]
                self.memcache_system = system

            if system:
                # Check if fault exists
                # TODO: use self.FAULT_KEY in system: system.key() generates
                # list and find item in that.
                if not self.FAULT_KEY in system.keys():
                    logger.debug("{0} Healthy, no faults seen".format(
                        self.LDR_R1_ENCL))
                    self.latest_faults = {}
                    return

                # Extract system faults
                self.latest_faults = system[self.FAULT_KEY]

                #If no in-memory fault cache built yet!
                if not self.memcache_faults:
                    # build from persistent cache if available
                    logger.info(
                        "No cached faults, building from  persistent cache {0}"\
                        .format(self.faults_persistent_cache))

                    self.memcache_faults = store.get(
                        self.faults_persistent_cache)

                    # still if none, build from latest faults & persist
                    if not self.memcache_faults:
                        logger.info("No persistent faults cache, building "
                                    "cache from latest faults")

                        self.memcache_faults = self.latest_faults

                        # On SSPL boot, run through existing faults as no cache to
                        # verify with for new faults
                        self.existing_faults = True

                        #logger.debug("existing_faults {0}".\
                        #    format(self.existing_faults))

                        store.put(self.memcache_faults,
                                  self.faults_persistent_cache)
                else:
                    # Reset flag as existing faults processed by now
                    # and cached faults are built already
                    self.existing_faults = False
            else:
                logger.error("poll system failed with err %d" % api_resp)
    def __init__(self):
        super(RealStorEnclosure, self).__init__()

        # WS Request common headers
        self.ws = WebServices()
        self.common_reqheaders = {}

        self.encl_conf = self.CONF_SECTION_MC

        self.system_persistent_cache = self.encl_cache + "system/"
        self.faults_persistent_cache = self.system_persistent_cache + "faults.json"

        # Read in mc value from configuration file
        self.mc1 = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(self.encl_conf).get("primary_controller_ip"),
            self.DEFAULT_MC_IP)
        self.mc1_wsport = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(self.encl_conf).get("primary_controller_port"),
            '')
        self.mc2 = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(self.encl_conf).get("secondary_controller_ip"),
            self.DEFAULT_MC_IP)
        self.mc2_wsport = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(
                self.encl_conf).get("secondary_controller_port"), '')

        self.active_ip = self.mc1
        self.active_wsport = self.mc1_wsport

        self.user = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(self.encl_conf).get("user"), self.DEFAULT_USER)
        self.passwd = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(self.encl_conf).get("password"),
            self.DEFAULT_PASSWD)

        self.mc_interface = self.conf_reader._get_value_with_default(
            self.encl_conf,
            COMMON_CONFIGS.get(self.encl_conf).get("mgmt_interface"), "cliapi")

        self.pollfreq = int(
            self.conf_reader._get_value_with_default(self.CONF_REALSTORSENSORS,
                                                     "polling_frequency",
                                                     self.DEFAULT_POLL))

        self.site_id = self.conf_reader._get_value_with_default(
            self.SYSTEM_INFORMATION,
            COMMON_CONFIGS.get(self.SYSTEM_INFORMATION).get(self.SITE_ID),
            '001')
        self.rack_id = self.conf_reader._get_value_with_default(
            self.SYSTEM_INFORMATION,
            COMMON_CONFIGS.get(self.SYSTEM_INFORMATION).get(self.RACK_ID),
            '001')
        self.node_id = self.conf_reader._get_value_with_default(
            self.SYSTEM_INFORMATION,
            COMMON_CONFIGS.get(self.SYSTEM_INFORMATION).get(self.NODE_ID),
            '001')
        # Need to keep cluster_id string here to generate decryption key
        self.cluster_id = self.conf_reader._get_value_with_default(
            self.SYSTEM_INFORMATION,
            COMMON_CONFIGS.get(self.SYSTEM_INFORMATION).get(self.CLUSTER_ID),
            '001')
        # Decrypt MC Password
        decryption_key = encryptor.gen_key(
            self.cluster_id, ServiceTypes.STORAGE_ENCLOSURE.value)
        self.passwd = encryptor.decrypt(decryption_key,
                                        self.passwd.encode('ascii'),
                                        "RealStoreEncl")

        if self.mc_interface not in self.realstor_supported_interfaces:
            logger.error("Unspported Realstor interface configured,"
                         " monitoring and alerts generation may hamper")
            return

        # login to mc to get session key, required for querying resources
        # periodically
        self.login()
Пример #4
0
    def __init__(self):
        super(RealStorEnclosure, self).__init__()

        # WS Request common headers
        self.ws = WebServices()
        self.common_reqheaders = {}

        self.encl_conf = self.CONF_SECTION_MC

        self.system_persistent_cache = self.encl_cache + "system/"
        self.faults_persistent_cache = self.system_persistent_cache + "faults.json"

        # Read in mc value from configuration file
        self.mc1 = Conf.get(
            GLOBAL_CONF, f"{STORAGE}>{ENCLOSURE}>{CONTROLLER}>{PRIMARY}>{IP}",
            self.DEFAULT_MC_IP)
        self.mc1_wsport = str(
            Conf.get(GLOBAL_CONF,
                     f"{STORAGE}>{ENCLOSURE}>{CONTROLLER}>{PRIMARY}>{PORT}",
                     ''))
        self.mc2 = Conf.get(GLOBAL_CONF,
                            f"{STORAGE}>{ENCLOSURE}>{SECONDARY}>{IP}",
                            self.DEFAULT_MC_IP)
        self.mc2_wsport = str(
            Conf.get(GLOBAL_CONF,
                     f"{STORAGE}>{ENCLOSURE}>{CONTROLLER}>{SECONDARY}>{PORT}",
                     ''))

        self.active_ip = self.mc1
        self.active_wsport = self.mc1_wsport

        self.user = Conf.get(GLOBAL_CONF,
                             f"{STORAGE}>{ENCLOSURE}>{CONTROLLER}>{USER}",
                             self.DEFAULT_USER)
        self.passwd = Conf.get(GLOBAL_CONF,
                               f"{STORAGE}>{ENCLOSURE}>{CONTROLLER}>{SECRET}",
                               self.DEFAULT_PASSWD)

        self.mc_interface = Conf.get(SSPL_CONF,
                                     f"{STORAGE_ENCLOSURE}>{MGMT_INTERFACE}",
                                     "cliapi")

        self.pollfreq = int(
            Conf.get(SSPL_CONF,
                     f"{self.CONF_REALSTORSENSORS}>{POLLING_FREQUENCY}",
                     self.DEFAULT_POLL))

        self.site_id = Conf.get(GLOBAL_CONF,
                                f"{CLUSTER}>{SRVNODE}>{self.SITE_ID}", 'DC01')
        self.rack_id = Conf.get(GLOBAL_CONF,
                                f"{CLUSTER}>{SRVNODE}>{self.RACK_ID}", 'RC01')
        self.node_id = Conf.get(GLOBAL_CONF,
                                f"{CLUSTER}>{SRVNODE}>{self.NODE_ID}", 'SN01')
        # Need to keep cluster_id string here to generate decryption key
        self.cluster_id = Conf.get(GLOBAL_CONF, f"{CLUSTER}>{self.CLUSTER_ID}",
                                   'CC01')
        # Decrypt MC Password
        decryption_key = encryptor.gen_key(
            self.cluster_id, ServiceTypes.STORAGE_ENCLOSURE.value)
        self.passwd = encryptor.decrypt(decryption_key,
                                        self.passwd.encode('ascii'),
                                        "RealStoreEncl")

        if self.mc_interface not in self.realstor_supported_interfaces:
            logger.error("Unspported Realstor interface configured,"
                         " monitoring and alerts generation may hamper")
            return

        # login to mc to get session key, required for querying resources
        # periodically
        self.login()
class RealStorEnclosure(StorageEnclosure):
    """RealStor Enclosure Monitor functions using CLI API Webservice Interface"""

    REALSTOR_MC_BOOTWAIT = 0
    DEFAULT_MC_IP = "127.0.0.1"
    WEBSERVICE_TIMEOUT = 20
    PERSISTENT_DATA_UPDATE_TIMEOUT = 5
    MAX_RETRIES = 2

    CONF_SECTION_MC = "STORAGE_ENCLOSURE"
    SYSTEM_INFORMATION = "SYSTEM_INFORMATION"
    CONF_REALSTORDISKSENSOR = "REALSTORDISKSENSOR"
    CONF_REALSTORCONTROLLERSENSOR = "REALSTORCONTROLLERSENSOR"
    CONF_REALSTORFANSENSOR = "REALSTORFANSENSOR"
    CONF_REALSTORPSUSENSOR = "REALSTORPSUSENSOR"
    CONF_REALSTORLOGICALVOLUMESENSOR = "REALSTORLOGICALVOLUMESENSOR"
    CONF_REALSTORSIDEPLANEEXPANDERSENSOR = "REALSTORSIDEPLANEEXPANDERSENSOR"
    CONF_REALSTORENCLOSURESENSOR = "REALSTORENCLOSURESENSOR"
    CONF_REALSTORSENSORS = "REALSTORSENSORS"
    DEFAULT_POLL = 30

    DEFAULT_USER = "******"
    DEFAULT_PASSWD = "!manage"

    # CLI APIs
    URI_CLIAPI_LOGIN = "******"
    URI_CLIAPI_SHOWDISKS = "/show/disks"
    URI_CLIAPI_SHOWSYSTEM = "/show/system"
    URI_CLIAPI_SHOWPSUS = "/show/power-supplies"
    URI_CLIAPI_SHOWCONTROLLERS = "/show/controllers"
    URI_CLIAPI_SHOWFANMODULES = "/show/fan-modules"
    URI_CLIAPI_SHOWENCLOSURE = "/show/enclosure"
    URI_CLIAPI_SHOWDISKGROUPS = "/show/disk-groups"
    URI_CLIAPI_SHOWVOLUMES = "/show/volumes"
    URI_CLIAPI_SHOWSENSORSTATUS = "/show/sensor-status"
    URI_CLIAPI_SASHEALTHSTATUS = "/show/sas-link-health"
    URI_CLIAPI_NETWORKHEALTHSTATUS = "/show/network"
    URI_CLIAPI_SHOWEVENTS = "/show/events"
    URI_CLIAPI_SHOWVERSION = "/show/version/detail"
    URI_CLIAPI_SHOWFRUS = "/show/frus"
    URI_CLIAPI_BASE = "/"
    URI_CLIAPI_DOWNLOADDEBUGDATA = "/downloadDebugData"
    URL_ENCLLOGS_POSTDATA = "/api/collectDebugData"

    # CLI APIs Response status strings
    CLIAPI_RESP_INVSESSION = "Invalid sessionkey"
    CLIAPI_RESP_FAILURE = 2

    # Realstor generic health states
    HEALTH_OK = "ok"
    HEALTH_FAULT = "fault"
    HEALTH_DEGRADED = "degraded"

    STATUS_NOTINSTALLED = "not installed"

    DATA_FORMAT_JSON = "json"
    FAULT_KEY = "unhealthy-component"

    # Realstor FRUs mapping
    fru_mapping = {
        "fan": "FAN MODULE",
        "psu": "POWER_SUPPLY",
        "sideplane": "SIDEPLANE"
    }

    # Current support for 'cliapi', future scope for 'rest', 'redfish' apis
    # once available
    realstor_supported_interfaces = ['cliapi']

    poll_system_ts = 0
    mc_timeout_counter = 0
    # ws_response_status: HTTP response status code. eg: 200 for HTTP_OK
    # used in realstor_enclosure_sensor to decide on FAULT_RESOLVED condition.
    ws_response_status = None

    # resource inmemory cache
    latest_faults = {}

    # check fault irrespective, since memcache faults are mosty copy of latest
    # faults, so no comparison to check for new faults is feasible
    existing_faults = False

    def __init__(self):
        super(RealStorEnclosure, self).__init__()

        # WS Request common headers
        self.ws = WebServices()
        self.common_reqheaders = {}

        self.encl_conf = self.CONF_SECTION_MC

        self.system_persistent_cache = self.encl_cache + "system/"
        self.faults_persistent_cache = self.system_persistent_cache + "faults.json"

        # Read in mc value from configuration file
        self.mc1 = Conf.get(GLOBAL_CONF, CNTRLR_PRIMARY_IP_KEY,
                            self.DEFAULT_MC_IP)
        self.mc1_wsport = str(
            Conf.get(GLOBAL_CONF, CNTRLR_PRIMARY_PORT_KEY, ''))
        self.mc2 = Conf.get(GLOBAL_CONF, CNTRLR_SECONDARY_IP_KEY,
                            self.DEFAULT_MC_IP)
        self.mc2_wsport = str(
            Conf.get(GLOBAL_CONF, CNTRLR_SECONDARY_PORT_KEY, ''))

        self.active_ip = self.mc1
        self.active_wsport = self.mc1_wsport

        self.user = Conf.get(GLOBAL_CONF, CNTRLR_USER_KEY, self.DEFAULT_USER)
        _secret = Conf.get(GLOBAL_CONF, CNTRLR_SECRET_KEY, self.DEFAULT_PASSWD)

        self.mc_interface = Conf.get(SSPL_CONF,
                                     f"{STORAGE_ENCLOSURE}>{MGMT_INTERFACE}",
                                     "cliapi")

        self.pollfreq = int(
            Conf.get(SSPL_CONF,
                     f"{self.CONF_REALSTORSENSORS}>{POLLING_FREQUENCY}",
                     self.DEFAULT_POLL))

        # Decrypt MC secret
        decryption_key = encryptor.gen_key(
            ENCLOSURE, sspl_const.ServiceTypes.STORAGE_ENCLOSURE.value)
        self.__passwd = encryptor.decrypt(decryption_key, _secret,
                                          "RealStoreEncl")

        if self.mc_interface not in self.realstor_supported_interfaces:
            logger.error("Unsupported Realstor interface configured,"
                         " monitoring and alerts generation may hamper")
            return

        # login to mc to get session key, required for querying resources
        # periodically
        self.login()

    def _add_request_headers(self, sessionKey):
        """Add common request headers"""
        self.common_reqheaders['datatype'] = self.DATA_FORMAT_JSON
        self.common_reqheaders['sessionKey'] = sessionKey

    def build_url(self, uri):
        """Build request url"""

        wsport = ""

        if self.active_wsport.isdigit():
            wsport = ":" + self.active_wsport
        else:
            logger.warn(
                "Non-numeric webservice port configured [%s], ignoring",
                self.active_wsport)

        url = "http://" + self.active_ip + wsport + "/api" + uri

        return url

    def switch_to_alt_mc(self):
        """Switches active ip between primary and secondary management controller
           ips"""

        if self.mc1 == self.mc2 and \
            self.mc1_wsport == self.mc2_wsport:
            return

        if self.active_ip == self.mc1:
            self.active_ip = self.mc2
            self.active_wsport = self.mc2_wsport
        elif self.active_ip == self.mc2:
            self.active_ip = self.mc1
            self.active_wsport = self.mc1_wsport

        self.login()
        logger.debug("Current MC active ip {0}, active wsport {1}. Logged-in\
            ".format(self.active_ip, self.active_wsport))

    def ws_request(self, url, method, retry_count=MAX_RETRIES, post_data=""):
        """Make webservice requests using common utils"""
        response = None
        retried_login = False
        need_relogin = False
        tried_alt_ip = False

        while retry_count:
            if tried_alt_ip:
                # Extract show fru name from old URL to update alternative IP.
                url = self.build_url(url[url.index('/api/'):].replace(
                    '/api', ''))

            response = self.ws.ws_request(method, url, self.common_reqheaders,
                                          post_data, self.WEBSERVICE_TIMEOUT)

            retry_count -= 1

            if response is None:
                continue

            self.ws_response_status = response.status_code

            if response.status_code == self.ws.HTTP_OK:

                self.mc_timeout_counter = 0

                try:
                    jresponse = json.loads(response.content)

                    #TODO: Need a way to check return-code 2 in more optimal way if possible,
                    # currently being checked for all http 200 responses
                    if jresponse:

                        if jresponse['status'][0][
                                'return-code'] == self.CLIAPI_RESP_FAILURE:
                            response_status = jresponse['status'][0][
                                'response']

                            # if call fails with invalid session key request
                            # seen in G280 fw version
                            if self.CLIAPI_RESP_INVSESSION in response_status:
                                need_relogin = True

                except ValueError as badjson:
                    logger.error("%s returned mal-formed json:\n%s" %
                                 (url, badjson))

            # http 403 forbidden request, login & retry
            elif (response.status_code == self.ws.HTTP_FORBIDDEN or \
                need_relogin) and retried_login is False:
                logger.info("%s failed, retrying after login " % (url))

                self.login()
                retried_login = True
                need_relogin = False
                continue

            elif (response.status_code == self.ws.HTTP_TIMEOUT or \
                     response.status_code == self.ws.HTTP_CONN_REFUSED or \
                     response.status_code == self.ws.HTTP_NO_ROUTE_TO_HOST) \
                     and tried_alt_ip is False:
                self.switch_to_alt_mc()
                tried_alt_ip = True
                self.mc_timeout_counter += 1
                continue

            break

        return response

    def login(self):
        """Perform realstor login to get session key & make it available
           in common request headers"""

        cli_api_auth = self.user + '_' + self.__passwd

        url = self.build_url(self.URI_CLIAPI_LOGIN)
        auth_hash = hashlib.sha256(cli_api_auth.encode('utf-8')).hexdigest()
        headers = {'datatype': 'json'}

        response = self.ws.ws_get(url + auth_hash, headers, \
                       self.WEBSERVICE_TIMEOUT)

        if not response:
            logger.warn("Login webservice request failed {0}".format(url))
            return

        if response.status_code != self.ws.HTTP_OK:
            if response.status_code == self.ws.HTTP_TIMEOUT:
                self.mc_timeout_counter += 1
            logger.error("{0}:: http request for login failed with err {1}"\
                .format(self.LDR_R1_ENCL, response.status_code))
            return

        try:
            jresponse = json.loads(response.content)
        except ValueError as badjson:
            logger.error("%s returned mal-formed json:\n%s" % (url, badjson))

        if jresponse:

            if jresponse['status'][0]['return-code'] == 1:
                sessionKey = jresponse['status'][0]['response']
                self._add_request_headers(sessionKey)
            else:
                logger.error("realstor cli api login FAILED with api err %d" %
                             jresponse['status'][0]['return-code'])

    def check_system_faults_changed(self):
        """Check change in faults state"""

        changed = False

        if self.existing_faults:
            #logger.debug("existing_faults TRUE")
            return True

        if self.latest_faults != self.memcache_faults:
            changed = True
            logger.warn(
                "System faults state changed, updating cached faults!!")

        return changed

    def update_memcache_faults(self):
        self.memcache_faults = self.latest_faults

        #Update faults in persistent cache
        logger.info("Updating faults persistent cache!!")
        store.put(self.memcache_faults, self.faults_persistent_cache)

    def check_new_fault(self, fault):
        """Check if supplied is new fault"""

        if self.existing_faults:
            #logger.debug("existing_faults TRUE")
            return True

        newkid = False

        if fault not in self.memcache_faults:
            newkid = True
            for cached in self.memcache_faults:
                if fault["component-id"] == cached["component-id"] \
                    and fault["health"] == cached["health"] \
                    and fault["health-reason"] == cached["health-reason"]:
                    newkid = False
                    break

        return newkid

    def get_api_status(self, jresp):
        """Retreive realstor common api response for cli apis"""

        api_status = -1
        for status in jresp:
            if status["response-type"] != "Info":
                api_status = status["response-type-numeric"]
                break

        return api_status

    def get_system_status(self):
        """Retreive realstor system state info using cli api /show/system"""

        # poll system would get invoked through multiple realstor sensors
        # with less frequency compared to configured polling frequency
        # adding check to comply with polling frequency
        elapsed = time.time() - self.poll_system_ts

        if elapsed < self.pollfreq:
            logger.warn("/show/system request came in {0} seconds,"
                        "while configured polling frequency is {1} seconds,"
                        "ignoring".format(elapsed, self.pollfreq))
            return

        system = None

        # make ws request
        url = self.build_url(self.URI_CLIAPI_SHOWSYSTEM)
        #logger.info("show system url: %s" % url)

        response = self.ws_request(url, self.ws.HTTP_GET)

        if not response:
            logger.warn("System status unavailable as ws request failed")
            return

        if response.status_code != self.ws.HTTP_OK:
            logger.info("{0}:: http request {1} polling system status failed"
                " with http err {2}".format(self.LDR_R1_ENCL, url, \
                response.status_code))
            return

        self.poll_system_ts = time.time()

        try:
            jresponse = json.loads(response.content)
        except ValueError as badjson:
            logger.error("%s returned mal-formed json:\n%s" % (url, badjson))

        if jresponse:
            api_resp = self.get_api_status(jresponse['status'])

            if ((api_resp == -1)
                    and (response.status_code == self.ws.HTTP_OK)):
                logger.warn("/show/system api response unavailable, "
                            "marking success as http code is 200")
                api_resp = 0

            if api_resp == 0:
                system = jresponse['system'][0]
                self.memcache_system = system

            if system:
                # Check if fault exists
                # TODO: use self.FAULT_KEY in system: system.key() generates
                # list and find item in that.
                if not self.FAULT_KEY in system.keys():
                    logger.debug("{0} Healthy, no faults seen".format(
                        self.LDR_R1_ENCL))
                    self.latest_faults = {}
                    return

                # Extract system faults
                self.latest_faults = system[self.FAULT_KEY]

                #If no in-memory fault cache built yet!
                if not self.memcache_faults:
                    # build from persistent cache if available
                    logger.info(
                        "No cached faults, building from  persistent cache {0}"\
                        .format(self.faults_persistent_cache))

                    self.memcache_faults = store.get(
                        self.faults_persistent_cache)

                    # still if none, build from latest faults & persist
                    if not self.memcache_faults:
                        logger.info("No persistent faults cache, building "
                                    "cache from latest faults")

                        self.memcache_faults = self.latest_faults

                        # On SSPL boot, run through existing faults as no cache to
                        # verify with for new faults
                        self.existing_faults = True

                        #logger.debug("existing_faults {0}".\
                        #    format(self.existing_faults))

                        store.put(self.memcache_faults,
                                  self.faults_persistent_cache)
                else:
                    # Reset flag as existing faults processed by now
                    # and cached faults are built already
                    self.existing_faults = False
            else:
                logger.error("poll system failed with err %d" % api_resp)

    def get_realstor_encl_data(self, fru: str):
        """Fetch fru information through webservice API."""
        fru_data = []
        fru_uri_map = {
            "controllers": self.URI_CLIAPI_SHOWCONTROLLERS,
            "power-supplies": self.URI_CLIAPI_SHOWPSUS,
            "sensors": self.URI_CLIAPI_SHOWSENSORSTATUS,
            "volumes": self.URI_CLIAPI_SHOWVOLUMES,
            "disk-groups": self.URI_CLIAPI_SHOWDISKGROUPS,
            "enclosures": self.URI_CLIAPI_SHOWENCLOSURE,
            "network-parameters": self.URI_CLIAPI_NETWORKHEALTHSTATUS,
            "drives": self.URI_CLIAPI_SHOWDISKS,
            "expander-ports": self.URI_CLIAPI_SASHEALTHSTATUS,
            "fan-modules": self.URI_CLIAPI_SHOWFANMODULES,
            "frus": self.URI_CLIAPI_SHOWFRUS,
            "versions": self.URI_CLIAPI_SHOWVERSION
        }
        url = self.build_url(fru_uri_map.get(fru))
        response = self.ws_request(url, self.ws.HTTP_GET)
        if fru == "frus":
            fru = "enclosure-fru"

        if not response or response.status_code != self.ws.HTTP_OK:
            return []
        elif response or response.status_code == self.ws.HTTP_OK:
            response_data = json.loads(response.text)
            fru_data = response_data.get(fru)

        return fru_data

    def load_storage_fru_list(self):
        """Get Storage FRU list and merge it with storage_fru_list,
        maintained in global config, with which FRU list can be extended
        for a solution.

        Ex: Storage Enclosures not listing disk as FRU,
        though its most common FRU in storage, and
        practically it can be replaced easily.
        So if for a solution, FRU list needs to be extended
        beyond what publishes, 'storage_fru_list' from global config
        can be used.
        Some of the usual FRU examples are:- disk.

        """
        self.fru_list = []
        # Read FRU data using /show/frus cli api.
        fru_data = singleton_realstorencl.get_realstor_encl_data("frus")
        for fru_dict in fru_data:
            if "name" in fru_dict.keys():
                self.fru_list.append(fru_dict["name"])
        self.fru_list = list(set(self.fru_list))
        try:
            self.hot_swapped_frus = Conf.get(
                GLOBAL_CONF,
                "storage_enclosure>storage_fru_list>hot_swappable",
                ['disk', 'controller'])
            self.cold_swapped_frus = Conf.get(
                GLOBAL_CONF, "storage_enclosure>storage_fru_list", [])
        except ValueError as e:
            logger.error("Failed to get storage_fru_list from config."
                         f"Error:{e}")
        self.fru_list = list(
            set(self.fru_list + self.hot_swapped_frus +
                self.cold_swapped_frus))
        logger.info(f"Fetched Enclosure FRU list:{self.fru_list}")

    def is_storage_fru(self, fru):
        try:
            is_fru = True if fru in self.fru_list or \
                self.fru_mapping[fru] in self.fru_list else False
        except KeyError:
            is_fru = False

        fru_str = str(is_fru).lower()
        if is_fru:
            if fru in self.hot_swapped_frus:
                fru_str = str(is_fru).lower() + ":" + "hot_swappable"
            else:
                fru_str = str(is_fru).lower() + ":" + "cold_swappable"
        return fru_str

    def get_enclosure_logs(self, file_name, logger):
        """Accumulate enclosure logs & write to supplied file."""
        url = self.build_url(self.URI_CLIAPI_BASE)
        COLLECTING_DEBUG_LOG_STARTED = False
        for encl_trigger_log_retry_index in range(
                0, sspl_const.ENCL_TRIGGER_LOG_MAX_RETRY):
            post_data_string = '{0}/"{1}"{2}"{3}'.format(
                self.URL_ENCLLOGS_POSTDATA, sspl_const.SUPPORT_REQUESTOR_NAME,
                sspl_const.SUPPORT_EMAIL_ID, sspl_const.SUPPORT_CONTACT_NUMBER)
            response = self.ws_request(url,
                                       self.ws.HTTP_POST,
                                       post_data=post_data_string)

            if not response:
                logger.error("{0} status unavailable as ws request {1}"
                             " failed".format(self.LDR_R1_ENCL, url))
                break

            elif response.status_code != self.ws.HTTP_OK:
                logger.error("{0} http request {1} to get {3} failed "
                             "with error {2} enclosure trigger log retry "
                             "index {4}".format(self.LDR_R1_ENCL, url,
                                                response.status_code,
                                                "Debug log",
                                                encl_trigger_log_retry_index))

            else:
                response_data = response.json()
                if response_data["status"][0]["response-type"] == "Success" \
                        and response_data["status"][0]["response"] == "Collecting debug logs.":
                    logger.info("Collecting enclosure debug logs in progress")
                    COLLECTING_DEBUG_LOG_STARTED = True
                    break
                else:
                    logger.error(
                        "{0}:: http request {1} to get {3} failed with "
                        "response-type {2}".format(
                            self.LDR_R1_ENCL, url,
                            response_data["status"][0]["response-type"],
                            "Debug log"))

        if COLLECTING_DEBUG_LOG_STARTED is True:
            self.enclosure_wwn = self.get_enclosure_wwn(logger)
            url = self.build_url(self.URI_CLIAPI_DOWNLOADDEBUGDATA)
            for encl_download_retry_index in range(
                    0, sspl_const.ENCL_DOWNLOAD_LOG_MAX_RETRY):
                response = self.ws_request(url, self.ws.HTTP_GET)
                if not response:
                    logger.error(
                        "{0}:: {2} status unavailable as ws request {1}"
                        " failed".format(self.LDR_R1_ENCL, url, "Debug log"))
                elif response.status_code != self.ws.HTTP_OK:
                    logger.error("{0}:: http request {1} to get {3} failed "
                                 "with error {2}".format(
                                     self.LDR_R1_ENCL, url,
                                     response.status_code, "Debug log"))
                else:
                    if response.headers.get(
                            'Content-Type'
                    ) == 'application/json; charset="utf-8"':
                        response_data = response.json()
                        if response_data["status"][0][
                                "response-type"] == "Error":
                            time.sleep(
                                sspl_const.ENCL_DOWNLOAD_LOG_WAIT_BEFORE_RETRY)
                        else:
                            logger.error(
                                "ERR: Unexpected response-type {0} URL {1}".
                                format(
                                    response_data["status"][0]
                                    ["response-type"], url))
                            break
                    elif response.headers.get(
                            'Content-Type'
                    ) == 'IntentionallyUnknownMimeType; charset="utf-8"':
                        if response.headers.get(
                                'content-disposition'
                        ) == 'attachment; filename="store.zip"':
                            with open(file_name, 'wb') as enclosure_resp:
                                enclosure_resp.write(response.content)
                                enclosure_resp.close()
                                logger.info(
                                    "Enclosure debug logs saved successfully")
                        else:
                            logger.error(
                                "ERR: No attachment found::{0}".format(url))
                        break
                    else:
                        logger.error(
                            "ERR: Unknown Content-Type::{0}".format(url))
                        break
                if encl_download_retry_index == (
                        sspl_const.ENCL_DOWNLOAD_LOG_MAX_RETRY - 1):
                    logger.error("ERR: Enclosure debug logs retry count "
                                 "exceeded::{0}".format(url))

    def get_enclosure_wwn(self, logger):
        """Get enclosure wwn."""
        url = self.build_url(self.URI_CLIAPI_SHOWENCLOSURE)
        response = self.ws_request(url, self.ws.HTTP_GET)
        if not response:
            logger.error("{0}:: {2} status unavailable as ws request {1}"
                         " failed".format(self.EES_ENCL, url, fru))
            return
        if response.status_code != self.ws.HTTP_OK:
            if url.find(self.ws.LOOPBACK) == -1:
                logger.error("{0}:: http request {1} to get {3} failed with"
                             " err {2}".format(self.EES_ENCL, url,
                                               response.status_code, fru))
            return

        response_data = json.loads(response.text)
        enclosure_wwn = response_data.get("enclosures")[0]["enclosure-wwn"]
        return enclosure_wwn