# -*- coding: UTF-8 -*-

from frame.rest import restData, restUtil
from regex import Regex
import config
import logger
import os
import resource
import time
import re
import subprocess
from frame.context import contextUtil

IPV4 = "v4"
IPV6 = "v6"
#IPv4正则表达式定义
IPV4_REGEX_DEFINE = "^((25[0-5]|2[0-4]\d|[0-1]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[0-1]?\d\d?)$"
#IPv6正则表达式定义
IPV6_REGEX_DEFINE = "^\s*((([0-9A-Fa-f]{1,4}:){7}(([0-9A-Fa-f]{1,4})|:))|(([0-9A-Fa-f]{1,4}:){6}(:|((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(:[0-9A-Fa-f]{1,4})))|(([0-9A-Fa-f]{1,4}:){5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4}){0,1}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)(:[0-9A-Fa-f]{1,4}){0,4}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(:(:[0-9A-Fa-f]{1,4}){0,5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))(%.+)?\s*$"

def getRes(lang, res, args="", resDict=resource.RESOURCE_DICT):
    '''
    @summary: 资源国际化
    @param lang: 语言lang
    @param res: 资源
    @param args: 资源对应的参数
    @param resDict: 资源字典
    @return: 经过国际化处理后的资源
    '''
    try:
        key = "%s_%s" % (res, lang)
        if not resDict.has_key(key):
            return ("--", "")
        
        context = resDict.get(key)
        if "%s" in context or "%i" in context:
            context = context % args
        
        return context
        
    except:
        return ("--", "")
    
def getMsg(lang, msg, errMsgArgs="", suggestionArgs="", msgDict=resource.MESSAGES_DICT):
    '''
    @summary: 错误消息和修复建议国际化
    @param lang: 语言lang
    @param msg: 消息
    @param errMsgArgs: 错误对应的参数
    @param suggestionArgs: 修复建议对应的参数，有多个参数时，以元组方式传入，单个参数时，以字符串方式传入
    @param msgDict: 消息和修复建议字典，有多个参数时，以元组方式传入，单个参数时，以字符串方式传入
    @return: 经过国际化处理后的消息和修复建议
    '''
    try:
        if not msgDict.has_key(msg):
            return ("--", "")
        
        localeDict = msgDict.get(msg)
        errMsg = localeDict.get("errMsg_%s" % lang, "--")
        suggestion = localeDict.get("suggestion_%s" % lang , "")
        
        if "%s" in errMsg or "%i" in errMsg:
            errMsg = errMsg % errMsgArgs
        if "%s" in suggestion or "%i" in suggestion:
            suggestion = suggestion % suggestionArgs
        
        return (errMsg, suggestion)
        
    except:
        return ("--", "")

def getBaseName(filePath):
    '''
    @summary: 返回文件路径的文件名，不包含后缀
    @param filePath: 文件路径
    @return: 返回不包含后缀的文件名字符串
    '''
    baseName, ext = os.path.splitext(os.path.basename(filePath))
    return baseName

def getLogger(loggerInstance, pyFilePath):
    '''
    @summary: 获取日志类
    @param loggerInstance: logger实例
    @param pyFilePath: py文件路径
    '''
    pyFileName = getBaseName(pyFilePath)
    return logger.Logger(loggerInstance, pyFileName)

def cmpPorts(port1, port2):
    '''
    @summary: 端口字典比较
    @param portDict1: 端口字典1
    @param portDict2: 端口字典2
    @return: 根据location进行比较的结果
    '''
    if port1["location"] < port2["location"]:
        return -1
    elif port1["location"] > port2["location"]:
        return 1
    return 0

def getPortSlot(portInfo):
    '''
    @summary: 根据端口信息，获取该端口的槽位号
    @param portInfo: 端口信息
    @return: 端口的槽位号
    '''
    return portInfo["location"].split(".")[-1]

def getVRCVersion(version):
    '''
    @summary: 获取产品的C版本信息
    @param version: 产品版本
    @return: 产品C版本
    '''
    if len(version) <= 11:
        return version
    else:
        return version[0:11]

def bit2GB(bit):
    '''
    @summary: 比特容量转换为GB容量
    '''
    return bit * 1.0 / 1024 / 1024 /1024

def isPureDigit(digitStr):
    '''
    @summary: 判断字符串是否为数字（如果字符串以0开头，不认为是数字）
    '''
    if not digitStr.isdigit():
        return False
    
    if len(digitStr) != len(str(int(digitStr))):
        return False
    
    return True
    
def getIpVer(accessIP):
    '''
    @summary: 获取IP地址版本号
    @param baseIpAddr: IP
    @return: 
        v4: IPv4地址
        v6: IPv6地址
    '''
    if ":" in accessIP:
        return IPV6
    return IPV4

def isIllegalManIpAddr(manIpAddr, ipVer):
    '''
    @summary: 校验管理IP地址是否合法（要求为合法的主机IP地址）
    @param manIpAddr: 管理IP
    @return: 
        True: 管理IP合法
        False: 管理IP不合法
    '''
    if ipVer is None:
        #IPv4地址
        if not ":" in manIpAddr:
            return isIllegalManIpv4Addr(manIpAddr)
        #考虑IPv6地址有兼容IPv4的情况
        return isIllegalManIpv4Addr(manIpAddr) or isIllegalManIpv6Addr(manIpAddr)
    
    if ipVer == IPV4:
        return isIllegalManIpv4Addr(manIpAddr)
    
    if ipVer == IPV6:
        return isIllegalManIpv6Addr(manIpAddr)
    
    return False

def isIllegalManIpv4Addr(manIpAddr):
    '''
    @summary: 校验管理IP地址是否为合法的IPv4地址
    @param manIpAddr: 管理IP
    @return: 
        True: 合法的IPv4地址
        False: 不合法的IPv4地址
    '''
    if not isIpV4(manIpAddr):
        return False
    
    addrs = manIpAddr.split(".")
        
    if int(addrs[0]) == 0:
        return False
    
    return True

def isIllegalManIpv6Addr(manIpAddr):
    '''
    @summary: 校验管理IP地址是否为合法的IPv6地址
    @param manIpAddr: 管理IP
    @return: 
        True: 合法的IPv6地址
        False: 不合法的IPv6地址
    '''
    return isIpV6(manIpAddr)
    
def isIllegalMask(mask, ipVer):
    '''
    @summary: 校验IPV4子网掩码/IPV6前缀是否合法
    @param mask: 子网掩码/前缀
    @return: 
        True: 子网掩码/前缀合法
        False: 子网掩码/前缀不合法
    '''
    if ipVer is None:
        return isIllegalSubnetMask(mask) or isIllegalPrefix(mask)
    
    if ipVer == IPV4:
        return isIllegalSubnetMask(mask)
    
    if ipVer == IPV6:
        return isIllegalPrefix(mask)
    
    return False

def isIllegalSubnetMask(mask):
    '''
    @summary: 校验IPV4子网掩码是否合法
    @param mask: IPV4子网掩码
    @return: 
        True: IPV4子网掩码合法
        False: IPV4子网掩码不合法
    '''
    if not isIpV4(mask):
        return False
    
    addrs = mask.split(".")
        
    if int(addrs[0]) == 0:
        return False
    
    return True

def isIllegalPrefix(mask):
    '''
    @summary: 校验IPV6前缀是否合法
    @param mask: IPV6前缀
    @return: 
        True: IPV6前缀合法
        False: IPV6前缀不合法
    '''
    if not isPureDigit(mask):
        return False
    
    if 0 <= int(mask) <= 128:
        return True
    else:
        return False

def isIllegalGateway(gateway, ipVer):
    '''
    @summary: 校验IPV4网关地址/IPV6网关地址是否合法
    @param mask: 网关地址
    @return: 
        True: 网关地址
        False: 网关地址
    '''
    if ipVer is None:
        return isIllegalIPv4Gateway(gateway) or isIllegalIPv6Gateway(gateway)
    
    if ipVer == IPV4:
        return isIllegalIPv4Gateway(gateway)
    
    if ipVer == IPV6:
        return isIllegalIPv6Gateway(gateway)
    
    return False

def isIllegalIPv4Gateway(gateway):
    '''
    @summary: 校验IPv4网关地址是否合法
    @param mask: IPv4网关地址
    @return: 
        True: IPv4网关地址合法
        False: IPv4网关地址不合法
    '''
    return isIpV4(gateway)

def isIllegalIPv6Gateway(gateway):
    '''
    @summary: 校验IPv6网关地址是否合法
    @param mask: IPv6网关地址
    @return: 
        True: IPv6网关地址合法
        False: IPv6网关地址不合法
    '''
    return isIpV6(gateway)


#---------New Frame---------
def isIpV4(ip):
    '''
    @summary: 检测IP地址是否为通用的IPv4地址
    '''
    return Regex.find(IPV4_REGEX_DEFINE, ip)
    
def isIpV6(ip):
    '''
    @summary: 检测IP地址是否为通用的IPv6地址
    '''
    return Regex.find(IPV6_REGEX_DEFINE, ip)

def getBaseUri(ip, devSN, port=config.REST_DEFAULT_PORT):
    '''
    @summary: 获取rest的基础链接（https://${ip}:${port}/deviceManager/rest/${deviceId}/）
    @param ip: 设备ip
    @param devSN: 设备SN
    @param port: rest使用的端口
    @return: rest及基础链接
    '''   
    return config.REST_BASE_URI % (ip, port, devSN)

def isSingleModel(rest):
    '''
    @summary: 判读系统是否为单控模式
    @param productModel: rest连接
    @return: 
        True: 系统未单控模式
        False: 系统不为单控模式
    '''
    record = restUtil.getSysRecord(rest)
    data = restUtil.getData(record)
    configModel = restUtil.getRecordValue(data, restData.System.CONFIG_MODEL)
    if configModel == restData.CofigModelEnum.SINGLE_CTRL:
        return True
    
    return False

def isHighEndDev(productModel):
    '''
    @summary: 判读是否为高端设备
    @param productModel: 产品型号
    @return: 
        True: 产品型号为高端
        False: 产品型号不为高端
    '''
    return productModel in config.HIGH_END_DEVS

def isArmDev(productModel):
    '''
    @summary: 判读是否为ARM设备
    @param productModel: 产品型号
    @return: 
        True: 产品型号为ARM设备
        False: 产品型号不为ARM设备
    '''
    return productModel in config.ARM_DEVS

def isArmDev2U(productModel):
    '''
    @summary: 判读是否为2U ARM设备
    @param productModel: 产品型号
    @return: 
        True: 产品型号为2U ARM设备
        False: 产品型号不为2U ARM设备
    '''
    return productModel in config.ARM_DEVS_2U

def isDoradoDev(productModel):
    '''
    @summary: 判读是否为Dorado设备
    @param productModel: 产品型号
    @return: 
        True: 产品型号为Dorado设备
        False: 产品型号不为Dorado设备
    '''
    return productModel in config.DORADO_DEVS

def safeSleep(seconds):
    '''
    @summary: 睡眠
    @param seconds: 睡眠时间
    '''
    try:
        time.sleep(seconds)
    except:
        pass

def checkFaultFru(record):
    '''
    @summary: 检查故障的FRU
    @param healthStatus: 健康状态
    @param runningStatus: 运行状态
    @return: 
        True: 备件故障
        False: 备件非故障
    '''
    data = restUtil.getData(record)
    healthStatus = restUtil.getRecordValue(data, restData.PublicAttributes.HEALTH_STATUS)
    runningStatus = restUtil.getRecordValue(data, restData.PublicAttributes.RUNNING_STATUS)
    
    if healthStatus in [restData.HealthStatusEnum.FAULT]:
        return True
    
    if runningStatus in [restData.RunningStatusEnum.POWER_OFF]:
        return True
        
    return False
 
def checkIBCPassword(record):
    '''
    @summary: 检查ibc密码是否为默认密码
    @param record: rest回显
    @return: 
        True: ibc密码为默认密码
        False: ibc密码不为默认密码
    '''
    data = restUtil.getData(record)
    pwdChanged = restUtil.getRecordValue(data, restData.IBC.PWD_CHANGED)
    if pwdChanged == restData.IbcEnum.PWD_CHANGED:
        return False
    return True

def checkIBCKeyPiar(record):
    '''
    @summary: 检查ibc公私钥对是否为默认
    @param record: rest回显
    @return:  True 公私钥对变更
              False 公私钥对未变更
    ''' 
    data = restUtil.getData(record)
    pwdChanged = restUtil.getRecordValue(data, restData.IBC.KEYPAIR_CHANGED)
    if pwdChanged == restData.IbcEnum.KEYPAIR_CHANGED:
        return False
    return True

def checkCpuUsage(rest, baseUri, logger):
    '''
    @summary: 检查cpu利用率
    @param record: 控制器信息回显
    @return: 
        result: True: 检查通过, False: 检查不通过
        failedList: cpu利用率过高的控制器信息
    '''
    
    result = True
    failedList = []
    
    cpuUsageDict = {}
    maxCpuUsageDict = {}
    ctrlRecords = restUtil.getCtrlRecords(rest)
    logger.logInfo('[check_cpu_usage] controller records:%s' %unicode(ctrlRecords))
    datas = restUtil.getData(ctrlRecords)
    for data in datas:
        encId = restUtil.getRecordValue(data, restData.Controller.PARENT_ID)
        cpuUsage = restUtil.getRecordValue(data, restData.Controller.CPU_USAGE)
        
        oldCpuUsage = cpuUsageDict.get(encId, 0)
        cpuUsageDict[encId] = oldCpuUsage + cpuUsage
        
        oldMaxCpuUsage = maxCpuUsageDict.get(encId, 0)
        maxCpuUsageDict[encId] = oldMaxCpuUsage + config.MAX_CPUUSAGE_PER_CTRL
        
    for enclosureId in cpuUsageDict.keys():
        curCpuUsage = cpuUsageDict[enclosureId]
        curMaxCpuUsage = maxCpuUsageDict[enclosureId]
        if curCpuUsage > curMaxCpuUsage:
            result = False
            failedList.append({enclosureId:curCpuUsage})
            
    return (result, failedList)

def checkBackIoCurrent(rest, baseUri, logger):
    '''
    @summary: 检查后端IO压力
    @param rest: rest连接
    @param baseUri: rest基础链接
    @param logger: 日志对象
    @return: 
       result: True: 检查通过, False: 检查不通过
       failedList: 压力过大的框列表
    '''
    result = True
    failedList = []
    
    ctrlRecords = restUtil.getCtrlRecords(rest)
    logger.logInfo('[check_backend_Io] controller records:%s' %unicode(ctrlRecords))
    
    #统计引擎下的总后端IO
    backEndIoDict = {}
    backEndIoLimitDict = {}
    ctrlIdDict = restUtil.getCtrlIdDict(ctrlRecords)
    for ctrlId in ctrlIdDict.keys():
        parentId = ctrlIdDict[ctrlId]
        record = restUtil.getCtrlIoRecord(rest, ctrlId)
        data = restUtil.getData(record)
        logger.logInfo('[check_backend_Io] controller records:%s' %unicode(record))
        
        #获取当前后端并发
        backEndIo = restUtil.getRecordValue(data, restData.ControllerIo.BACKEND_IO) 
        oldBackIo = backEndIoDict.get(parentId, 0)
        backEndIoDict[parentId] = oldBackIo + backEndIo
        
        #获取后端并发阈值
        backEndIoLimit = restUtil.getRecordValue(data, restData.ControllerIo.BACKEND_LIMIT) 
        oldBackEndIoLimit = backEndIoLimitDict.get(parentId, 0)
        backEndIoLimitDict[parentId] = oldBackEndIoLimit + backEndIoLimit
        
        logger.logInfo('[check_backend_Io] controller [%s] backend IO information [backend IO: %s, backend IO limit: %s.' \
                       %(ctrlId, unicode(backEndIo), unicode(backEndIoLimit)))
        
    #检查后端IO是否在限制标准以内
    for encId in backEndIoDict.keys():
        totalBackEndIo = backEndIoDict[encId]
        totalBackEndIoLimit = backEndIoLimitDict[encId]
        if totalBackEndIo > 0 and totalBackEndIo >= totalBackEndIoLimit:
            result = False
            failedList.append(encId)
            
    return (result, failedList)

def checkSysPressure(rest, baseUri, logger):
    '''
    @summary: 检查系统业务压力（包括CPU利用率和后端业务压力）
    @param rest: rest连接
    @param baseUri: rest基础链接
    @param logger: 日志对象
    @return: 
        True: 检查通过
        False: 检查不通过
    '''
    
    succCpuUsageNum = 0
    succBackIoNum = 0
    permitSuccNum = config.CHECK_SYS_PRESSURE_TIMES * config.CHECK_SUCCESS_RATE
    for i in range(0, config.CHECK_SYS_PRESSURE_TIMES):
        logger.logInfo('[sys_pressure] check process [%s/%s]' %(str(i), str(config.CHECK_SYS_PRESSURE_TIMES)))
        
        #检查CPU利用率
        checkCpuFlag, checkCpuInfo  = checkCpuUsage(rest, baseUri, logger)
        if not checkCpuFlag:
            logger.logInfo('[sys_pressure] check cpu usage failed:[%s]' %(','.join(checkCpuInfo)))
        else:
            succCpuUsageNum += 1
            
        #检查后端并发
        checkBackEndIoFlag, checkBackEndIoInfo = checkBackIoCurrent(rest, baseUri, logger)
        if not checkBackEndIoFlag:
            logger.logInfo('[sys_pressure] check backend IO failed:[%s]' %(','.join(checkBackEndIoInfo)))
        else:
            succBackIoNum += 1
        
        if (succCpuUsageNum >= permitSuccNum) and (succBackIoNum >= permitSuccNum):
            return True
        
        safeSleep(config.CHECK_SYS_PRESSURE_PERIOD)
        
    logger.logInfo('[sys_pressure] check failed')
    return False

def checkHealthStatus(healthStatus, normalStatusList = [restData.HealthStatusEnum.NORMAL]):
    '''
    @summary: 检查健康状态
    @param healthStatus: 当前健康状态
    @param normalStatusList: 为正常状态的列表
    @return: 
        True: 检查通过, 
        False: 检查不通过
    '''
    return healthStatus in normalStatusList

def checkRunningStatus(runningStatus, normalStatusList = [restData.RunningStatusEnum.ONLINE]):
    '''
    @summary: 检查运行状态
    @param healthStatus: 当前运行状态
    @param normalStatusList: 为正常状态的列表
    @return: 
        True: 检查通过, 
        False: 检查不通过
    '''
    return runningStatus in normalStatusList

def getFruRunningStatus(rest, Obj, FruId):
    '''
    @summary: 获取指定FRU运行状态
    @param Obj: FRU类型
    @param FruId: 指定FRU的ID
    @return: runningStatus：巡行状态
    '''
    
    uriParams = [FruId]
    uriParamDict = restUtil.getUriParamDict(Obj, uriParams)
    record = restUtil.execCmd(rest, uriParamDict, None, restData.RestMethod.GET)
    data = restUtil.getData(record)
    runningStatus = restUtil.getRecordValue(data, restData.PublicAttributes.RUNNING_STATUS)
    
    return runningStatus

def getLinkedPorts(rest, objType):
    """
    @summary: 根据type获取连接的端口
    """
    paramDict = restUtil.getUriParamDict(objType)
    data = restUtil.execCmd(rest, paramDict, None, restData.RestMethod.GET)
    records = restUtil.getData(data)
    resList = []
    if records is None:
        return resList
    
    for record in records:
        healthStatus = restUtil.getRecordValue(record, restData.PublicAttributes.HEALTH_STATUS)
        runningStatus = restUtil.getRecordValue(record, restData.PublicAttributes.RUNNING_STATUS)
        if healthStatus == restData.HealthStatusEnum.NORMAL and runningStatus == restData.RunningStatusEnum.LINK_UP:
            resList.append(record)
    return resList

def getFrontPorts(context, intfId):
    """
    @summary: 获取所有前端接口卡的端口
    """
    portIdList = []
    ethPortIdList = getPortIdOfIntf(context, intfId, restData.OBJ.ETH_PORT)
    FcPortIdList = getPortIdOfIntf(context, intfId, restData.OBJ.FC_PORT)
    FCoEPortIdList = getPortIdOfIntf(context, intfId, restData.OBJ.FCoE_PORT)
    portIdList.extend(ethPortIdList)
    portIdList.extend(FcPortIdList)
    portIdList.extend(FCoEPortIdList)
    return portIdList

def getPortIdOfIntf(context, intfId, portType):
    """
    @summary: 获取前端接口卡的端口
    @param intfId: 接口卡id
    @return: 端口列表 
    """
    retPortList = []
    #FC和ETH口均获取，规避一个接口板上出现两种端口的情况
    #获取输入该接口板的ETH端口
    rest = contextUtil.getRest(context)
    paramDict = restUtil.getUriParamDict(portType)
    data = restUtil.execCmd(rest, paramDict, None, restData.RestMethod.GET)
    records = restUtil.getData(data)
    for record in records:
        if restUtil.getRecordValue(record, restData.PublicAttributes.PARENT_ID) == intfId:
            retPortList.append(restUtil.getRecordValue(record, restData.PublicAttributes.ID))
    return retPortList

def getRedundCtrlId(curCtrlId):
    """
    @summary: 获取冗余控制器id(格式：*A, *B)
    @param curCtrlId: 当前控制器id
    @param curCtrlName: 当前控制器名称
    @return: 冗余控制器id 
    """
    
    curCtrlName = curCtrlId[-1].upper()
    curEnginId = curCtrlId[0:-1]
    redundCtrlName = config.CONTROLLER_PAIR.get(curCtrlName)   
    #pair对的控制器ID是相邻的
    redundCtrlId = curEnginId + redundCtrlName
    return redundCtrlId

def checkCtrlRunningStatus(rest, ctrlId): 
    '''
    @summary: 检查控制器运行状态
    @param ctrlId:
    '''
    errInfo = ""
    try:
        ctrlIds = restUtil.getCtrlIds(restUtil.getCtrlRecords(rest))
        #处理冗余控制器查询不到的场景
        if ctrlId not in ctrlIds:
            return (False, ctrlIds)
        
        #获取冗余控制器信息
        record = restUtil.getCtrlRecord(rest, ctrlId)
        data = restUtil.getData(record)
        runningStatus = restUtil.getRecordValue(data, restData.Controller.RUNNING_STATUS)
        if checkRunningStatus(runningStatus):
            return (True, "")
        
        errInfo = record
        return (False, errInfo)
    except Exception, e:
        raise e

def getEthPortIpInfo(records, portTypeList = [restData.EthPortLogicTypeEnum.MNGT, restData.EthPortLogicTypeEnum.MNGT_SRV]):
    """
    @summary: 获取ETH端口的ip信息
    @param record: rest回显
    @return: ip信息
                                    信息格式：{location:{'ipv4':ipv4,'ipv6':ipv6}}
    """
    ipInfo = {}
    datas = restUtil.getData(records)
    for data in datas:
        logicType = restUtil.getRecordValue(data, restData.EthPort.LOGIC_TYPE)
        if logicType not in portTypeList:
            continue
        
        location = restUtil.getRecordValue(data, restData.EthPort.LOCATION)
        ipv4 = restUtil.getRecordValue(data, restData.EthPort.IPV4_ADDR)
        ipv6 = restUtil.getRecordValue(data, restData.EthPort.IPV6_ADDR)
        ipInfo[location] = {"ipv4":ipv4, "ipv6":ipv6}
        
    return ipInfo

def getIpType(ip):
    """
    @summary: 获取IP类型
    @param ip: ip
    @return: ip类型
    """
    if isIpV4(ip):
        return restData.IpTypeEnum.IPV4
    
    if isIpV6(ip):
        return restData.IpTypeEnum.IPV6
    
    return ""

def getIpList(ipInfo, ipType):
    """
    @summary: 根据ip类型获取对应的ip地址列表
    @param ipInfo: ip信息，信息格式：{location:{'ipv4':ipv4,'ipv6':ipv6}}
    @param ipType: ip类型，为枚举值（ipv4或ipv6）
    @return: ip信息列表，
    """
    ipList = []
    ipKey = ""
    if ipType == restData.IpTypeEnum.IPV4:
        ipKey = "ipv4"
    elif ipType == restData.IpTypeEnum.IPV6:
        ipKey = "ipv6"
    
    for itemKey in ipInfo.keys():
        item = ipInfo[itemKey]
        ipList.append(item[ipKey])
        
    return ipList

def getUsableIpList(context, ipList):
    """
    @summary: 获取可用ip列表
    @param context: 上下文
    @param ipList: ip全集
    @return: 可用的ip列表 
    """
    
    usableIpList = []
    for ip in ipList:
        flag, restConn = contextUtil.createRest(context, ip)
        if flag:
            usableIpList.append(ip)
            
        restConn.close()
    
    return usableIpList

def checkMgmtPath(context, records, ipType):
    """
    @summary: 检查冗余管理ip
    @param context: 上下文
    @param record: 所有eth端口信息
    @return: 
        True: 存在冗余
        False: 不存在冗余
    """
    defaultUsableIpNum = 2
    ipInfo = getEthPortIpInfo(records)
    ipList = getIpList(ipInfo, ipType)
    usableIpList = getUsableIpList(context, ipList)
    if usableIpList is None or len(usableIpList) < defaultUsableIpNum:
        return False
    
    return True

def checkIpInReplacedCtrl(context, ctrlId):
    """
    @summary: 检查工具连接ip是否在连接待更换控制器（只适用2U设备）
    @param context: 上下文
    @param ctrlId: 待更换控制器id
    @return: 
        False:非待更换控制器ip
        True:为当前更换控制器ip
    """
    
    isExist = False
    location = ""
    ip = contextUtil.getIp(context)
    rest = contextUtil.getRest(context)
    record = restUtil.getEthPortRecords(rest)
    datas = restUtil.getData(record)
    for data in datas:
        location = restUtil.getRecordValue(data, restData.EthPort.LOCATION)
        ipv4 = restUtil.getRecordValue(data, restData.EthPort.IPV4_ADDR)
        ipv6 = restUtil.getRecordValue(data, restData.EthPort.IPV6_ADDR)
        if ip in [ipv4, ipv6]:
            isExist = True
            break     
    
    if isExist:
        ctrlName = ctrlId[-1].upper()
        pattern = ".%s" %ctrlName
        if re.search(location, pattern) is not None:
            return True
    
    return False

def checkSingleEngine(rest):
    """
    @summary: 检查系统单引擎
    @param rest: rest对象
    @return: 
        False:多引擎
        True:单引擎
    """
    ctrlIds = restUtil.getCtrlEnclosureIds(rest)
    if len(ctrlIds) > 1:
        return False
    
    return True
    
def getRegPatternList(patternList):
    """
    @summary: 获取正则匹配项
    """
    return [re.compile(pattern) for pattern in patternList]

def checkSingleAlarm(alarm, alarmRuleItems, valueFilterList = []):
    """
    @summary: 单个告警匹配过滤, 如果不需要过滤字段，直接返回匹配上的ID
    @param alarmRuleItems：配置的需要检查的告警id、检查那个字段，检查该字段中哪些值。
                                                   如64438009898: {"obj":restData.Alarm.EVENTPARAM ,"checkIndex": [1,2]},
           valuefilter: 用于过滤的值，其中该列表中的每条记录的每个元素都会和checkIndex中对应位置上的值做匹配。
                                               如 [DAE001,A]
    @return: True 存在告警，False 不存在告警
    """
    
    #需要配置的告警id
    alarmIds = alarmRuleItems.keys()
    alarmId = restUtil.getRecordValue(alarm, restData.Alarm.EVENTID)
    
    if alarmId not in alarmIds:
        return False
    
    rule = alarmRuleItems[alarmId]
    if len(rule) == 0:
        return False
        
    for filter in valueFilterList:
        regPatternList = getRegPatternList(filter)
        
        #获取出告警的字段值。
        checkValue = restUtil.getRecordValue(alarm, rule["obj"])
        indexList = rule["checkIndex"]
        
        if checkRegValue(checkValue, regPatternList, indexList):
            return True
    return False

def checkRegValue(checkValue, regList, indexList):
    """
    @summary: 单个告警匹配过滤，过滤字段
    @param alarm: 待过滤的告警
           alarmRuleItems：配置的需要检查的告警id、检查那个字段，检查该字段中哪些值。
                                                   如64438009898: {"obj":restData.Alarm.EVENTPARAM ,"checkIndex": [1,2]},
           regList: 用于过滤的正则，其中该列表中的每条记录的每个元素都会和checkIndex中对应位置上的值做匹配。
    @return: True 存在告警， False 不存在告警
    """
    checkValue = checkValue.split(",")
    valueList = []
    #取出对应位置上的值，存入valueList
    for index in indexList:
        if index >= len(checkValue):
            return False
        else:
            valueList.append(checkValue[index])
        
    for i in range(len(regList)):
        #只要有一个找不到就退出当前备件进入下一个记录，如从级联板DAE000.A记录的检查跳到DAE001.A
        if regList[i].search(valueList[i]) is None:
            return False
        
    #如果True表示该告警匹配目标备件所有规则都成功。
    return True

def checkAlarm(rest, alarmRuleItems, filterList = []):
    """
    @summary: 过滤告警，通过正则匹配符合规则的记录。
    @param rest: rest连接
           alarmRuleItems：配置的需要检查的告警id、检查那个字段，检查该字段中哪些值。
                                                   如64438009898: {"obj":restData.Alarm.EVENTPARAM ,"checkIndex": [1,2]},
           filterList: 用于过滤的值，其中该列表中的每条记录的每个元素都会和checkIndex中对应位置上的值做匹配。
                                               如 [[DAE000,A],[DAE001,A], [DAE001,A]]
    @return True: 匹配成功。
            False: 未匹配出告警
    """
    #查询出所有的告警
    alarmRecords = restUtil.getCurrentAlarm(rest)
    checkRes = []
    for alarm in alarmRecords:
        if checkSingleAlarm(alarm, alarmRuleItems, filterList):
            alarmId = restUtil.getRecordValue(alarm, restData.Alarm.STREVENTID)
            checkRes.append(alarmId)
    if len(checkRes) > 0:
        return (True, checkRes)
    else:
        return (False, checkRes)

def getLocation(portList):
    """
    @summary: 根据端口集合取得一个location的字典。
    @param ports: 传入的端口列表
    @return: 返回以location为key，以record为值的字典。
    """
    locationDict = {}
    if portList is None:
        return locationDict
    
    for record in portList:
        location = restUtil.getRecordValue(record, restData.PublicAttributes.LOCATION)
        locationDict[location] = record
    return locationDict

def getUnLinkedSasPortsLocation(beforePorts, afterPorts):
    """
    @summary: 检查端口一致性。
    @param beforePorts: 更换之前记录的端口
            afterPorts: 更换之后的端口
    @return: 未连接的所有端口的location列表
    """
    unlinkedSasPorts = set()
    locDictBefore = getLocation(beforePorts)
    locDictAfter = getLocation(afterPorts)
    locsAfter = locDictAfter.keys()
    for beforeLoc in locDictBefore.keys():
        if beforeLoc not in locsAfter:
            unlinkedSasPorts.add(beforeLoc)
        
    return list(unlinkedSasPorts)

def getEnclosureListByWwnList(sasPorts, allSasPorts):
    """
    @summary: 根据控制器上的SAS端口列表过滤出当前环路的硬盘框
    @param sasPorts: 当前控制器上的SAS端口
            allSasPorts: 系统中所有的SAS端口
    @return: 当前环路的硬盘框
    """
    encList = []
    wwnList = getSasPortWwnList(sasPorts)
    for sasPortRecord in allSasPorts:
        parentType = restUtil.getRecordValue(sasPortRecord, restData.SasPort.PARENT_TYPE)
        if parentType != restData.ParentTypeEnum.EXPBOARD:
            continue
        
        wwn = restUtil.getRecordValue(sasPortRecord, restData.SasPort.WWN)
        location = restUtil.getRecordValue(sasPortRecord, restData.SasPort.LOCATION)
        encName = location.split(".")[0]
        #数据支持，WWN 和 EnclosureWwnLis是后半部分一致
        #如：'ENCLOSURE_WWN_LIST': '0x5fce33caad2d003f,0x5fce33caacfe803f,',
        #而WWN 为：'WWN': '5fce33caad2d003f'
        for hexWwn in wwnList:
            if  wwn != '' and wwn in hexWwn and encName not in encList:
                #记录硬盘框号和级联板前两位，方便待会做对比
                encList.append(encName)
    return encList

def getExpBoardByPeerPortId(sasPorts, allSasPorts):
    '''
    @summary: 根据控制器上的SAS端口的对端端口ID所在的SAS硬盘框wwnList环路计算出环路的所有级联板
    @param sasPorts:当前控制器连接的端口
    @param allSasPorts:所有SAS端口，包含控制器上的和级联板上的。
    '''
    
    expList = []
    for sasPort in sasPorts:
        peerPortId = restUtil.getRecordValue(sasPort, restData.SasPort.CURRENT_PEER_PORT_ID)
        encOnCurrentSasPortList = getEnclosureListByWwnList([sasPort], allSasPorts)
        expId = ''
        for sasPortRecord in allSasPorts:
            parentType = restUtil.getRecordValue(sasPortRecord, restData.SasPort.PARENT_TYPE)
            portId = restUtil.getRecordValue(sasPortRecord, restData.SasPort.ID)
            location = restUtil.getRecordValue(sasPortRecord, restData.SasPort.LOCATION)
            locationList = location.split(".")
            if parentType != restData.ParentTypeEnum.EXPBOARD:
                continue
            if peerPortId == portId:
                expId = locationList[1]
        
        if expId == '':
            continue
        
        for currentEnc in encOnCurrentSasPortList:
            expBoardLocList = [currentEnc, expId]
            if expBoardLocList not in expList:
                expList.append(expBoardLocList)
        
    return expList

def getSasPortWwnList(sasPorts):
    """
    @summary: 获取端口的wwnList
    """
    resList = []
    for sasPort in sasPorts:
        wwnListStr = restUtil.getRecordValue(sasPort, restData.SasPort.enclosureWWNList)
        if wwnListStr == '':
            continue
        
        wwnList = wwnListStr.split(",")
        #wwnList以,结尾，需要去除最后的''元素
        if wwnList[-1] == '':
            wwnList = wwnList[:-1]
        resList = resList + wwnList
    return list(set(resList))

def lightCtrl(rest, ctrlId):
    '''
    @summary: 点控制器定位灯
    '''
    
    try:
        restUtil.lightCtrl(rest, ctrlId)
        return (True, "")
    except Exception, ex:
        return (False, ex)
        
   
def offLineController(rest, ctrlId, logger):
    """
    @summary: 离线控制器
    """    
    #参数合法性检查
    if None == ctrlId or "" == ctrlId:
        logger.logInfo('[offline_controller] the controller id(%s) is invalid.' %ctrlId)
        return False
    
    #根据系统模式下发离线命令
    singleFlag = isSingleModel(rest)
    logger.logInfo("[offline_controller] check single model result: %s" %str(singleFlag))
    if not singleFlag:
        #多控模式下离线控制器
        restUtil.offLineCtrl(rest, ctrlId)
        totalTime = config.OFFLINE_CTRL_TIMEOUT
    else:
        #单控模式下离线控制器
        restUtil.offLineCtrlForSingle(rest, ctrlId)
        return True
    
    #检查离线状态,通过条件：运行状态为offline或空，或查不到控制器信息  
    beginTime = time.time()
    while time.time() - beginTime <= totalTime:
        try:
            runningStatus = getFruRunningStatus(rest, restData.OBJ.CONTROLLER, ctrlId)
            logger.logInfo("[offline_controller] the controller runningStatus:%s" % runningStatus)
            if runningStatus == "" or checkRunningStatus(runningStatus ,[restData.RunningStatusEnum.OFFLINE]):
                return True
                
        except Exception, ex:
            logger.logInfo("[offline_controller] get controller error, the exception:%s" %unicode(ex))
            errCode = ex.args[0]
            if errCode == config.ERR_CODE_NOT_EXIST:
                #容错：兼容查询不到控制器信息
                return True
        
        safeSleep(config.OFFLINE_CTRL_INTERVAL)

    #超时报错
    logger.logInfo("[offline_controller] check controller timeout")
    return False
    
def removeUsersGroup(localDir, fileName, logger):
    """
    @summary: 移除本地文件用户组权限
    @param localDir: 本地目录
    @param fileName: 操作文件名
    @param logger: 日志对象
    """
    try:
        subprocess.check_call('icacls ' + localDir + '\\' + fileName + ' /inheritance:d')
        subprocess.check_call('icacls ' + localDir + '\\' + fileName + ' /remove:g Users')
    except Exception, e:
        logger.logInfo("[remove_user_group] remove Users failed: %s" %unicode(e))
    return


def precheckCBUForCtrl(rest, cntrlId):
    """
    @summary: 更换前检查CBU（只适用于ARM设备）
    @param record: rest回显
    @return: True:检查通过
            False:检查不通过
    """    
    cbuCount = 0
    abnormalCBUs = []
    record = restUtil.getBackupPowerRecords(rest)
    datas = restUtil.getData(record)
    for data in datas:
        parentId = restUtil.getRecordValue(data, restData.BackupPower.PARENT_ID)
        if parentId != cntrlId:
            continue
        
        cbuCount += 1
        location = restUtil.getRecordValue(data, restData.BackupPower.LOCATION)
        healthStatus = restUtil.getRecordValue(data, restData.BackupPower.HEALTH_STATUS)
        runningStatus = restUtil.getRecordValue(data, restData.BackupPower.RUNNING_STATUS)
        if healthStatus != restData.HealthStatusEnum.NORMAL or runningStatus != restData.RunningStatusEnum.ONLINE:
            abnormalCBUs.append(location)
    
    if len(abnormalCBUs) > 0 or cbuCount == 0:
        return (False, abnormalCBUs)    
     
    return (True, [])

def postcheckCBUForCtrl(rest, cntrlId):
    """
    @summary: 更换后检查CBU（只适用于ARM设备）
    @param record: rest回显
    @return: True:检查通过
            False:检查不通过
    """    
    cbuCount = 0
    abnormalCBUs = []
    record = restUtil.getBackupPowerRecords(rest)
    datas = restUtil.getData(record)
    for data in datas:
        parentId = restUtil.getRecordValue(data, restData.BackupPower.PARENT_ID)
        if parentId != cntrlId:
            continue
        
        cbuCount += 1
        location = restUtil.getRecordValue(data, restData.BackupPower.LOCATION)
        healthStatus = restUtil.getRecordValue(data, restData.BackupPower.HEALTH_STATUS)
        runningStatus = restUtil.getRecordValue(data, restData.BackupPower.RUNNING_STATUS)
        if healthStatus in [restData.HealthStatusEnum.FAULT,restData.HealthStatusEnum.PRE_FAIL] or runningStatus == restData.RunningStatusEnum.OFFLINE :
            abnormalCBUs.append(location)
    
    if len(abnormalCBUs) > 0 or cbuCount == 0:
        return (False, abnormalCBUs)    
     
    return (True, [])
def backup(rest, sftp, ctrlId, localDir, logger):
    """
    @summary: 备份系统DB
    @param record: rest回显
    @return: True:下载成功，检查通过
            False:检查不通过
    """
    #从内存导出数据
    record = restUtil.backUpConfigData(rest)
    data = restUtil.getData(record)
    fileInfo = restUtil.getRecordValue(data, restData.SfpOpticalTransceiver.CMO_EXPORT_DB_DATA_DIR)
    
    #高端使用内部ip，中低端使用工具连接ip(此时传None)
    remoteFilePath = fileInfo.split(":")[1]
    isSucc = sftp.download(remoteFilePath, localDir)
    logger.logInfo("[backup_db] download db file result: %s." %str(isSucc))
    if isSucc is True:
        dbFileName = remoteFilePath.split("/")[-1]
        removeUsersGroup(localDir, dbFileName, logger)
       
    #下载后删除阵列上的临时DB信息
    restUtil.clearDb(rest,ctrlId)
    return isSucc