#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: jabber.zhou
# do all thing about disk
# 谨慎使用try except,处理不了的都应该报错发现！
##################################

from __future__ import print_function
import os
import sys
import time
import distutils.spawn
import subprocess
from threading import Timer
import re
import string
import hashlib
import getopt
import socket
from multiprocessing.dummy import Pool
import traceback
import stat

if sys.version > '3':
    import urllib.request as request
else:
    import urllib2 as request

import json
from json import encoder
encoder.FLOAT_REPR = lambda o: format(o, '.2f')

################################
# 逻辑分层
################################
# constants 常量
# public functions 公共方法
# collector functions 采集
# monitor 告警
# interface 交互
################################


################################
# constants
################################
MANUFACTURE_MATCH=r'''
HGST      \bWUH721414ALE6L4
Intel     \bINTEL\b
Samsung   \bSAMSUNG\b
Micron    \bMicron\b
WD        \bWDC\b

HGST      \bHU
HGST      \bHS
HGST      \bHM
HGST      \bDKS2G
Intel     \bSSD
Memblaze  \bP5
Micron    \bMTF
Samsung   \bMZ
SanDisk   \bSDLF
Shannon   \bDirect
Seagate   \bST
Seagate   \bXA
WD        \bWD
WD        \bWU
'''

def model2manufacture(model):
    manufacture = 'unknown'
    for line in MANUFACTURE_MATCH.split('\n'):
        if not line:
            continue
        key,match = re.split('\s+',line)
        if re.search(match,model.replace('_',' ')):
            manufacture = key
            break
    return manufacture

SERISE_MATCH = r"""
SSD.Dell_Intel_S4510    \bSSDSC2KB\w+G8R\b
SSD.HP_Intel_S4500      \bVK\w+WJPE\b
SSD.Intel_S4500         \bSSDSC2KB\w+G7\b
SSD.Intel_S4510         \bSSDSC2KB\w+G8\b
SSD.Intel_S4520         \bSSDSC2KB\w+Z\b
SSD.Micron_5100_ECO     \bMTF\w+TCB\b
SSD.Micron_5100_ECO     \bMicron\w+TCB\b
SSD.Micron_5100_PRO     \bMTF\w+TBY\b
SSD.Micron_5100_PRO     \bMicron\w+TBY\b
SSD.Micron_5200_PRO     \bMTF\w+TDD\b
SSD.Micron_5200_PRO     \bMicron\w+TDD\b
SSD.Micron_5200_ECO     \bMTF\w+TDC\b
SSD.Micron_5200_ECO     \bMicron\w+TDC\b
SSD.Micron_5300_PRO     \bMTF\w+TDS\b
SSD.Micron_5300_PRO     \bMicron\w+TDS\b
SSD.Samsung_PM863       \bMZ7LM\w+HCGR
SSD.Samsung_PM863       \bMZ7LM\w+HCHP
SSD.Samsung_PM863       \bMZ7LM\w+HCJM
SSD.Samsung_PM863A      \bMZ7LM\w+HMHQ
SSD.Samsung_PM863A      \bMZ7LM\w+HMJP
SSD.Samsung_PM863A      \bMZ7LM\w+HMLP
SSD.Samsung_PM883       \bMZ7LH
SSD.Samsung_PM893       \bMZ7L3
NVME.DAPUSTOR_Haishen   \bDPH311
NVME.DERA_D5457         \bP34UTM
NVME.Huawei_ES3600V3    \bHWE32P43
NVME.Huawei_ES3600V5    \bHWE52P43
NVME.INSPUR_NS8500G1    \bINSPUR-NS8500G1
NVME.INSPUR_NS8600G1    \bINSPUR-NS8600G1
NVME.INSPUR_NS8510G1    \bINSPUR-NS8510G1
NVME.INSPUR_NS8610G1    \bINSPUR-NS8610G1
NVME.Intel_P4510        \bSSDPE2KX
NVME.Intel_P4610        \bSSDPE2KE
NVME.Intel_P5520        \SSDPF2KX076T1
NVME.Intel_P5510        \bSSDPF2KX
NVME.Intel_P4800X       \bSSDPE21K
NVME.Intel_P5800X       \bSSDPF21Q
NVME.Memblaze_D700      \bP50D70
NVME.Memblaze_D916      \bP5916
NVME.Memblaze_D920      \bP5920
NVME.Memblaze_D926      \bP5926
HDD.SEAGATE_Makara_plus \bST8000NM0055
HDD.TOSHIBA_MG06ACA     \bMG06ACA800EY
"""

def model2series(model):
    series = 'unknown'
    for line in SERISE_MATCH.split('\n'):
        if not line:
            continue
        key,match = re.split('\s+',line)
        if re.search(match,model.replace('_',' ')):
            series = key
            break
    return series

################################
# public functions
################################
def check_process(binary):
    binary = distutils.spawn.find_executable(binary)
    count = 0
    for pid in os.listdir('/proc'):
        if not pid.isdigit():
            continue
        try:
            b = os.readlink('/proc/{0}/exe'.format(pid))
        except OSError:
            pass
        else:
            if b == binary:
                count += 1
    return count

def wait_process(binary,timeout=10,max=0):
    while timeout > 0:
        count = check_process(binary)
        if count > max:
            timeout -= 1
            time.sleep(1)
        else:
            return True
    raise Exception('{0} other "{1}" still running'.format(count,binary))

def _getcmdout(cmd,ignore_error=False,timeout=30,ignore_code=[]):
    cmd = cmd.split(' ')
    obj = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    my_timer = Timer(timeout, obj.kill)
    try:
        my_timer.start()
        out,err = obj.communicate()
    finally:
        my_timer.cancel()

    rc = obj.wait()
    ignore_code.append(0)
    if not rc in ignore_code and ignore_error == False:
        raise subprocess.CalledProcessError(rc,cmd)
    if sys.version > '3':
        out = out.decode('utf-8')
    else:
        out = unicode(out,errors='ignore')
    return out

def getcmdout(cmd,cache=300,timeout=60,ignore_error=False,ignore_code=[],max=0):
    binary=cmd.split(' ')[0]
    cache_name=cmd.replace(' ','_').replace('/','_').replace(':','').replace(',','')
    cache_file='/dev/shm/cache_'+cache_name
    out = ''
    if os.path.isfile(cache_file):
        cache_time = os.path.getmtime(cache_file)
        if (time.time() - cache_time) < cache:
            with open(cache_file) as f:
                out = f.read()
            if out.find('CalledProcessError:') == 0:
                raise subprocess.CalledProcessError(-1,cmd)
    if not out:
        wait_process(binary,timeout=timeout,max=max)
        if 'storcli64' in binary or 'perccli64' in binary:
            wait_process('/opt/MegaRAID/MegaCli/MegaCli64',timeout=timeout,max=max)
        with open(cache_file, 'w+') as f:
            try:
                out = _getcmdout(cmd,timeout=timeout,ignore_error=ignore_error,ignore_code=ignore_code)
            except Exception as err:
                out = 'CalledProcessError:\n'
                out += str(err)
                f.write(out)
                raise err
            else:
                f.write(out)
    return out

def get_block_info(device,key):
    with open('/sys/block/{0}/{1}'.format(device,key)) as f:
        content = f.read()
    return content.strip()

def hex2ascii(hex_array):
    out = ''.join(hex_array).decode('hex')
    printable = set(string.printable)
    out = filter(lambda x:x in printable, out)
    return out.strip()

def hex2int(hex_array):
    hex_array.reverse()
    hex_string = ''.join(hex_array)
    out = int(hex_string,16)
    return out

def md5(fname):
    hash_md5 = hashlib.md5()
    with open(fname, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

class H3CPMCController:
    def __init__(self,ctrl_id=0):
        ipmi = get_ipmi_pwd()
        self.host = ipmi['IPMI']
        self.user = ipmi['IPMIUser']
        self.password = ipmi['IPMIPassword']
        self.fru_0 = self.getFru0()
        self.ctrl_id = ctrl_id
        if 'G2' in self.fru_0['Product Name']:
            self.mode = 'G2'
        elif 'G3' in self.fru_0['Product Name']:
            self.mode = 'G3'
        else:
            raise Exception('unsupport Product "{0}"'.format(self.fru_0['Product Name']))

    def cmd(self,command):
        cmd_line = 'ipmitool -I lanplus -H {host} -U {user} -P {password} {command}'.format(host=self.host,user=self.user,password=self.password,command=command)
        return _getcmdout(cmd_line)

    def getFru0(self):
        output = self.cmd('fru print 0')
        info = {}
        for line in output.split("\n"):
            if not line:
                continue
            line = line.strip()
            key, value = re.split('\s+:\s+',line)
            info[key] = value
        return info

    def rawCmd(self,cmd):
        command = []
        for i in cmd.split(' '):
            command.append('0x{0:02x}'.format(int(i,16)))
        output = self.cmd('raw {0}'.format(' '.join(command)))
        output = output.replace('\n','').strip()
        return output.split(' ')

    def getCtrl(self):
#        ctrl_raw = self.rawCmd('0x36 0x0b 0xa2 0x63 0x00 0x0c 0x06 0x00 0x00 0x04 0x00 0x00 0x00 0x00')
#        ctrl_stat = hex2int(ctrl_raw[8:9])
#        stat_list = {1:'OK',2:'Down',3:'Absent',4:'Error',5:'Bios Init'}
#        ctrl_stat = stat_list[ctrl_stat]
        pcie_num_raw = self.rawCmd('0x36 0x0b 0xa2 0x63 0x00 0x1f')
        pcie_num = hex2int(pcie_num_raw[3:4])
        ctrl_num = 0
        stat_list = {0:'OK',1:'Abnormal',2:'Absent'}
        for pcie_index in range(pcie_num):
            pcie_raw = self.rawCmd('0x36 0x0b 0xa2 0x63 0x00 0x20 {0}'.format(hex(pcie_index)))
            pcie_product = hex2ascii(pcie_raw[19:59])
            if len(pcie_raw) < 60:
                pcie_stat = 0
            else:
                pcie_stat = hex2int(pcie_raw[59:60])
            if 'RAID' in pcie_product or 'HBA' in pcie_product:
                ctrl_num += 1
                ctrl_stat = stat_list[pcie_stat]
        out = {
            'count': ctrl_num,
            'stat': ctrl_stat,
        }
        return out

    def info(self):
        cmd = '0x36 0x0b 0xa2 0x63 0x00 0x0c 0x00 0x00 0x00 0x04 {0} 0x00 0x00 0x00'.format(hex(self.ctrl_id))
        rawout = self.rawCmd(cmd)
        out = dict(
            name = hex2ascii(rawout[15:47]),
            sn = hex2ascii(rawout[47:95]),
            cache_size = hex2ascii(rawout[95:111]),
            cache_status = hex2ascii(rawout[111:127]),
            battery_status = hex2ascii(rawout[127:159]),
            battery = hex2ascii(rawout[159:175]),
            firmware = hex2ascii(rawout[175:207]),
            )
        return out

    def ldlist(self):
        cmd = '0x36 0x0b 0xa2 0x63 0x00 0x0c 0x01 0x00 0x00 0x04 {0} 0x00 0x00 0x00'.format(hex(self.ctrl_id))
        rawout = self.rawCmd(cmd)
        rawlist = rawout[11:]
        n=0
        devices = []
        while n < len(rawlist):
            device = hex2int(rawlist[n:n+2])
            devices.append(device)
            n += 2
        out = dict(
            number = hex2int(rawout[7:11]),
            devices = devices,
            )
        return out

    def ldinfo(self,device):
        '''device : device number in ldlist'''
        device_hex = '{0:04x}'.format(device)
        device = '0x{1} 0x{0}'.format(device_hex[:2],device_hex[2:])
        cmd = '0x36 0x0b 0xa2 0x63 0x00 0x0c 0x02 0x00 0x00 0x04 {0} {1} 0x00'.format(device,hex(self.ctrl_id))
        rawout = self.rawCmd(cmd)
        out = dict(
            device = hex2int(rawout[7:9]),
            status = hex2ascii(rawout[9:25]),
            raid_type = hex2ascii(rawout[25:57]),
            size = hex2ascii(rawout[57:73]),
            )
        return out

    def pdlist(self):
        '''只能获取离散(未做raid)的物理盘'''
        cmd = '0x36 0x0b 0xa2 0x63 0x00 0x0c 0x03 0x00 0x00 0x04 0x00 0x00 0x00 {0}'.format(hex(self.ctrl_id))
        rawout = self.rawCmd(cmd)
        rawlist = rawout[11:]
        n=0
        devices = []
        while n < len(rawlist):
            device = hex2int(rawlist[n:n+2])
            devices.append(device)
            n += 2
        out = dict(
            number = hex2int(rawout[7:11]),
            devices = devices,
            )
        return out

    def ldpdlist(self,device):
        '''device : device number in ldlist'''
        cmd = '0x36 0x0b 0xa2 0x63 0x00 0x0c 0x03 0x00 0x00 0x04 0x01 {0} 0x00 {1}'.format(hex(device),hex(self.ctrl_id))
        rawout = self.rawCmd(cmd)
        rawlist = rawout[11:]
        n=0
        devices = []
        while n < len(rawlist):
            device = hex2int(rawlist[n:n+2])
            devices.append(device)
            n += 2
        out = dict(
            number = hex2int(rawout[7:11]),
            devices = devices,
            )
        return out

    def pdinfo(self,device):
        '''device : device number in pdlist or ldpdlist'''
        device_hex = '{0:04x}'.format(device)
        device = '0x{1} 0x{0}'.format(device_hex[:2],device_hex[2:])
        cmd = '0x36 0x0b 0xa2 0x63 0x00 0x0c 0x04 0x00 0x00 0x04 0x00 {0} {1}'.format(device,hex(self.ctrl_id))
        rawout = self.rawCmd(cmd)
        if self.mode == 'G2':
            out = dict(
                controller = hex2int(rawout[7:9]),
                device_id = hex2int(rawout[9:11]),
                status = hex2ascii(rawout[11:43]),
                type = hex2ascii(rawout[43:75]),
                size = hex2ascii(rawout[75:91]),
                Manufacture = hex2ascii(rawout[91:103]),
                sn = hex2ascii(rawout[103:135]),
                firmware = hex2ascii(rawout[167:175]),
                enclosure = hex2ascii(rawout[175:183]),
                enclosure_name = hex2ascii(rawout[183:191]),
                bios_id = hex2int(rawout[193:195]),
                slot = hex2int(rawout[195:197]),
            )
        elif self.mode == 'G3':
            out = dict(
                controller = hex2int(rawout[7:9]),
                device_id = hex2int(rawout[9:11]),
                status = hex2ascii(rawout[11:43]),
                type = hex2ascii(rawout[43:75]),
                size = hex2ascii(rawout[75:91]),
                Manufacture = hex2ascii(rawout[91:103]),
                sn = hex2ascii(rawout[103:135]),
                firmware = hex2ascii(rawout[135:143]),
                enclosure = hex2ascii(rawout[143:151]),
                enclosure_name = hex2ascii(rawout[151:159]),
                bios_id = hex2int(rawout[159:161]),
                host_id = hex2int(rawout[161:163]),
                slot = hex2int(rawout[163:165]),
                product_id = hex2ascii(rawout[165:182]),
                node_id = hex2ascii(rawout[182:190]),
                product_size = hex2ascii(rawout[190:206]),
                temperature = hex2int(rawout[206:207]),
                life = hex2int(rawout[207:208]),
            )
        return out

def get_self_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(("192.168.168.106", 80))
    ip = s.getsockname()[0]
    s.close()
    return ip

def get_ipmi_pwd():
    timeout = 10
    socket.setdefaulttimeout(timeout)

    cache_file='/dev/shm/cache_ipmi_info'
    info = {}
    if os.path.isfile(cache_file):
        with open(cache_file) as f:
            out = f.read()
            info = json.loads(out)
        host = info['IPMI']
        user = info['IPMIUser']
        password = info['IPMIPassword']
        cmd_line = 'ipmitool -I lanplus -H {0} -U {1} -P {2} fru print 0'.format(host,user,password)
        try:
            _getcmdout(cmd_line)
        except:
            info = {}
    if not info:
        ip = get_self_ip()
        query = {"data":[ip],"type":"IP"}
        headers = {'Content-Type': 'application/json'}
        req = request.Request(url='http://udcm.ucloudadmin.com/api/ipmi/v1/getpwd/', headers=headers, data=json.dumps(query))
        response = request.urlopen(req)
        result = response.read()
        if response.getcode() >= 200 and response.getcode() < 400:
            info = json.loads(result)[ip]
        else:
            raise Exception('get ipmi info failed:\n'+result)
        out = json.dumps(info)
        with open(cache_file, 'w+') as f:
            f.write(out)
    return info

def mtrace(func,*args,**kwargs):
    try:
        return func(*args,**kwargs)
    except Exception as e:
        #print('Caught exception in worker thread (%{0}):'.format(func),file=sys.stderr)

        # This prints the type, value, and stack trace of the
        # current exception being handled.
        exc_type, exc_value, exc_traceback_obj = sys.exc_info()
        #traceback.print_exception(exc_type, exc_value, exc_traceback_obj, limit=2, file=sys.stderr)

        #print('',file=sys.stderr)
        raise e

def sizeof_fmt(num, suffix='B'):
    # 保留小数点后2位，需要补0
    for unit in ['','K','M','G','T','P','E','Z']:
        if abs(num) < 1000.0:
            return "{0:.2f}{1}{2}".format(round(num,2), unit, suffix)
        num /= 1000.0
    return "{0:.2f}{1}{2}".format(round(num,2), 'Y', suffix)

####################################
# collector functions
####################################
def get_types():
    out = getcmdout('lspci -nn')
    types = set()
    lines = [line for line in out.split('\n') if 'RAID bus controller' in line]
    for line in lines:
        if 'Adaptec' in line:
            types.add('pmc')
        elif 'Hewlett-Packard' in line:
            types.add('hp')
        elif 'LSI' in line:
            if 'Dell' in getcmdout('dmidecode -t1'):
                types.add('dell_raid')
            else:
                types.add('lsi_raid')
        elif 'Intel' in line:
            pass
        else:
            types.add('unknown RAID controller')

    lines = [line for line in out.split('\n') if 'Serial Attached SCSI controller' in line and '1000:00b2' not in line]
    for line in lines:
        if 'LSI' in line:
            if re.search('3\d{3}',line):
                types.add('lsi_sas3')
            elif re.search('2\d{3}',line):
                types.add('lsi_sas2')
            else:
                types.add('unknown LSI SCSI controller')
        elif 'Adaptec' in line:
            types.add('pmc')
        elif 'Intel' in line:
            pass
        elif 'Huawei' in line:
            pass
        else:
            types.add('unknow SCSI controller')

    if 'Non-Volatile memory controller' in out or os.path.isfile('/tmp/.monitor_nvme_0_pd_temp'):
        types.add('nvme')
    if 'Mass storage controller' in out:
        types.add('shannon')

    if os.path.isfile('/opt/disk_ctrl_type_blacklist'):
        with open('/opt/disk_ctrl_type_blacklist') as f:
            for line in f:
                types.discard(line.strip())

    return types

def get_lsi_raid(type_name='lsi_raid'):
    if type_name == 'lsi_raid':
        tool_path='/opt/MegaRAID/storcli/storcli64'
        if not os.path.isfile(tool_path):
            tool_path='/opt/MegaRAID/storcli/storcli64.bak'
    elif type_name == 'dell_raid':
        tool_path='/opt/MegaRAID/perccli/perccli64'
    info = []
    cmd = '{0} show J'.format(tool_path)
    ctrlinfo = json.loads(getcmdout(cmd))
    for i in ctrlinfo['Controllers'][0]['Response Data']['System Overview']:
        # 无视hba卡，get_lsi_hba会处理
        if not 'Hlth' in i:
            continue
        if i['Hlth'] == 'Opt':
            stat = 'OK'
        else:
            stat = 'Oops'
        data = {
            'type': 'lsi_raid',
            'ctrl_id': i['Ctl'],
            'info': {
                'stat': stat,
                },
            'pd': [],
            'vd': [],
            }
        cid = data['ctrl_id']

    #get base info
        cmd = '{0} /c{1} show J'.format(tool_path,cid)
        ctrl_info = json.loads(getcmdout(cmd,ignore_error=True))['Controllers'][0]['Response Data']
        data['info']['firmware'] = ctrl_info['FW Version']
        data['info']['PCI Address'] = ctrl_info['PCI Address']
        pci_info = getcmdout('lspci -s {0}:{1}:{2}.{3}'.format(*ctrl_info['PCI Address'].split(':')))
        model_match = re.search('LSI.*\s(?:SAS)?(\d{4}|\d{2}xx)',pci_info)
        if model_match:
            data['info']['model'] = model_match.group(1)
        else:
            raise Exception('can not get model!')

    #get bbu/cv
        cmd = '{0} /c{1}/bbu show J'.format(tool_path,cid)
        bbuinfo = json.loads(getcmdout(cmd,ignore_error=True))['Controllers'][0]
        if bbuinfo['Command Status']['Status'] == 'Success':
            data['info']['bbu'] = bbuinfo['Response Data']['BBU_Info'][0]['State']
            if data['info']['bbu'] == 'Optimal':
                data['info']['backpower'] = 'OK'
            else:
                data['info']['backpower'] = 'Oops'
        else:
            if bbuinfo['Command Status']['Detailed Status'][0]['ErrMsg'] == 'use /cx/cv':
                cmd = '{0} /c{1}/cv show J'.format(tool_path,cid)
                cvinfo = json.loads(getcmdout(cmd))['Controllers'][0]
                data['info']['cv'] = cvinfo['Response Data']['Cachevault_Info'][0]['State']
                if data['info']['cv'] == 'Optimal':
                    data['info']['backpower'] = 'OK'
                else:
                    data['info']['backpower'] = 'Oops'
            else:
                data['info']['cv'] = bbuinfo['Command Status']['Detailed Status'][0]['ErrMsg']
                data['info']['backpower'] = 'N/A'

    #get cc cron
        cmd = '{0} /c{1} show cc J'.format(tool_path,cid)
        cccron_info = json.loads(getcmdout(cmd,ignore_error=True))['Controllers'][0]
        ccn = {}
        for i in cccron_info['Response Data']['Controller Properties']:
            ccn[i['Ctrl_Prop']] = i['Value']
        data['info']['cc'] = ccn['CC Operation Mode']
        data['info']['cc_next_start'] = ccn.get('CC Next Starttime')

    #get pd
        cmd = '{0} /c{1}/eall/sall show J'.format(tool_path,cid)
        try:
            pdinfo = json.loads(getcmdout(cmd,ignore_code=[45,46]))['Controllers'][0]['Response Data']['Drive Information']
        except subprocess.CalledProcessError as err:
            data['info']['tool_health'] = False
            pdinfo = []
        except KeyError as err:
            data['info']['tool_health'] = False
            pdinfo = []
        else:
            data['info']['tool_health'] = True

        for pd in pdinfo:
            pd_data = {
                'location': pd['EID:Slt'],
                'protocol': pd['Intf'],
                'type': pd['Med'],
                'stat': pd['State'],
                'health': True,
                'size': pd['Size'],
                'disk': '',
                'info': {},
                'sn': ''
            }

            if pd_data['stat'] != 'Onln':
                pd_data['health'] = False

            pd_data['info']['did'] = pd['DID']
            pd_data['info']['DG'] = pd['DG']
            pd_data['info']['model'] = pd.get('Model','').strip()
            pd_data['info']['manufacture'] = model2manufacture(pd_data['info']['model'])
            pd_data['info']['series'] = model2series(pd_data['info']['model'])

            eid,sid = pd_data['location'].split(':')
            if pd['State'] != 'Failed':
                cmd = '{0} /c{1}/e{2}/s{3} show all J'.format(tool_path,cid,eid,sid)
                pd_detail = json.loads(getcmdout(cmd,ignore_code=[46]))['Controllers'][0].get('Response Data')
                if pd_detail:
                    pd_attr = pd_detail['Drive /c{0}/e{1}/s{2} - Detailed Information'.format(cid,eid,sid)]['Drive /c{0}/e{1}/s{2} Device attributes'.format(cid,eid,sid)]
                    pd_stat = pd_detail['Drive /c{0}/e{1}/s{2} - Detailed Information'.format(cid,eid,sid)]['Drive /c{0}/e{1}/s{2} State'.format(cid,eid,sid)]
                    pd_data['sn'] = pd_attr['SN'].strip().replace('-','')
                    pd_data['info']['firmware'] = pd_attr['Firmware Revision'].strip()

                    temperature = pd_stat['Drive Temperature'].strip()
                    if temperature == 'N/A':
                        temperature = None
                    else:
                        temperature = int(temperature[:temperature.find('C')])
                    pd_data['info']['temperature'] = temperature

                    pd_data['info']['Media Error Count'] = pd_stat['Media Error Count']
                    pd_data['info']['Other Error Count'] = pd_stat['Other Error Count']
                    pd_data['info']['Predictive Failure Count'] = pd_stat['Predictive Failure Count']
                    pd_data['drivegroup'] = pd_detail['Drive /c{0}/e{1}/s{2} - Detailed Information'.format(cid,eid,sid)].get('Drive /c{0}/e{1}/s{2} Policies/Settings'.format(cid,eid,sid),{}).get('Drive position','')

            data['pd'].append(pd_data)

    #get vd
        cmd = '{0} /c{1}/vall show J'.format(tool_path,cid)
        vdinfo_raw = json.loads(getcmdout(cmd,ignore_code=[46]))['Controllers'][0]
        if not 'Response Data' in vdinfo_raw:
            if vdinfo_raw['Command Status']['Description'] == 'No VDs have been configured':
                vdinfo = []
        else:
            vdinfo = vdinfo_raw['Response Data']['Virtual Drives']
        for vd in vdinfo:
            vd_data = {
                'type': vd['TYPE'],
                'vid': int(vd['DG/VD'].split('/')[1]),
                'stat': vd['State'],
                'health': True,
                'disk': '',
                'info': {
                    'DG': int(vd['DG/VD'].split('/')[0]),
                    },
                'size': vd['Size'],
                'cache': vd['Cache'],
                'pd': [],
            }
            if vd_data['stat'] != 'Optl':
                vd_data['health'] = False

            # get cc stat
            cmd = '{0} /c{1}/v{2} show cc J'.format(tool_path,cid,vd_data['vid'])
            cc_stat = json.loads(getcmdout(cmd,ignore_error=True))['Controllers'][0]['Response Data']['VD Operation Status'][0]
            vd_data['info']['cc_progress'] = cc_stat['Progress%']
            vd_data['info']['cc_stat'] = cc_stat['Status']
            vd_data['info']['cc_time_left'] = cc_stat['Estimated Time Left']

            cmd = '{0} /c{1}/v{2} show all J'.format(tool_path,cid,vd_data['vid'])
            vd_detail = json.loads(getcmdout(cmd,ignore_code=[45,46]))['Controllers'][0]['Response Data']
            vd_properties = vd_detail.get('VD{0} Properties'.format(vd_data['vid']))

            # get disk
            if vd_properties:
                if 'OS Drive Name'in vd_properties:
                    vd_data['disk'] = vd_properties.get('OS Drive Name','')
                elif 'SCSI NAA Id' in vd_properties:
                    wwn = vd_properties['SCSI NAA Id']
                    disk = os.path.realpath('/dev/disk/by-id/wwn-0x{0}'.format(wwn))
                    vd_data['disk'] = disk
            if not vd_data['disk']:
                scsi_hosts = getcmdout('lsscsi -H')
                for line in scsi_hosts.split('\n'):
                    if 'megaraid_sas' in line:
                        scsi_host = re.search('\[(\d+)\]',line).group(1)
                cmd = 'lsscsi {0}:{1}:{2}:0'.format(scsi_host,'*',vd_data['vid'])
                lsscsi_out = getcmdout(cmd)
                disk = lsscsi_out.strip().split(' ')[-1]
                vd_data['disk'] = disk

            for n,pd_data in enumerate(data['pd']):
                if pd_data['info']['DG'] == vd_data['info']['DG']:
                    pd_data['disk'] = vd_data['disk']
                    vd_data['pd'].append(pd_data)
                    data['pd'][n] = pd_data

            data['vd'].append(vd_data)

#        # get raid log
#        cmd = '{0} /c{1} show events type=sincereboot'.format(tool_path,cid)
#        try:
#            raidlog = getcmdout(cmd,timeout=3)
#        except:
#            raidlog = ''
#            data['info']['raidlog'] = 'too long'
#        else:
#            data['info']['raidlog'] = 'OK'
#
#        event_by_location = {}
#        events = re.split('seqNum',raidlog)
#        #events.reverse()
#        for event_block in events:
#            if not 'to ONLINE' in event_block and \
#            not 'Unexpected sense: PD' in event_block and \
#            not 'Command timeout' in event_block:
#                continue
#            event = {}
#            for line in event_block.split('\n'):
#                row = re.match('(.*?):\s+(.*)\s*',line)
#                if row:
#                    key = row.group(1)
#                    value = row.group(2).strip()
#                    event[key] = value
#            event_by_location[event['Enclosure Index']+':'+event['Slot Number']] = event
#        for n,pd in enumerate(data['pd']):
#            if pd['location'] in event_by_location:
#                pd['info']['last_event'] = event_by_location[pd['location']]['Event Description']
#                pd['info']['last_event_time'] = event_by_location[pd['location']].get('Time')
#            else:
#                pd['info']['last_event'] = ''
#            data['pd'][n] = pd

        info.append(data)
    return info

def get_hp():
    tool_path = 'ssacli'
    info = []

    try:
        ctrlinfo = getcmdout('{0} ctrl all show'.format(tool_path))
        if 'Error:' in ctrlinfo:
            raise Exception(ctrlinfo)
    except:
        data = {
            'type': 'hp_raid',
            'ctrl_id': 0,
            'info': {
                'tool_health': False,
                },
            'pd': [],
            'vd': [],
            }
        return [data]

    for line in ctrlinfo.split('\n'):
        slot_match = re.search('Slot (\d+)',line)
        if not slot_match:
            continue
        ctrl_id = int(slot_match.group(1))
        ctrl_detail = getcmdout('{0} ctrl slot={1} show detail'.format(tool_path,ctrl_id))
        ctrl = {}
        if 'Smart HBA' in ctrl_detail:
            type_name = 'hp_hba'
        elif 'Smart Array' in ctrl_detail:
            type_name = 'hp_raid'
        for line in ctrl_detail.split('\n'):
            row = re.match('\s+(.*?)\s*:\s+(.*)\s*',line)
            if row:
                key = row.group(1)
                value = row.group(2).strip()
                ctrl[key] = value
        data = {
            'type': type_name,
            'ctrl_id': ctrl_id,
            'info': {
                'tool_health': True,
                'PCI Address': ctrl['PCI Address (Domain:Bus:Device.Function)'],
                'stat': ctrl['Controller Status'],
                'Cache Status':ctrl.get('Cache Status'),
                'Battery/Capacitor Status':ctrl.get('Battery/Capacitor Status'),
                'firmware':ctrl['Firmware Version'],
                },
            'pd': [],
            'vd': [],
            }

        if data['info']['Battery/Capacitor Status'] == 'OK' or type_name == 'hp_hba':
            data['info']['backpower'] = 'OK'
        else:
            data['info']['backpower'] = 'Oops'

        # get model
        model_match = re.search('Smart (?:Array|HBA) (\w+)',ctrl_detail)
        if model_match:
            data['info']['model'] = model_match.group(1)

        #get pd
        pd_show = getcmdout('{0} ctrl slot={1} pd all show'.format(tool_path,ctrl_id))
        pd_array = pd_show.split('Array')[1:]
        for array in pd_array:
            array_name = array[:array.find('\n')].strip()
            pd_list = re.findall('physicaldrive\s+(\S+)\s+',array)
            for pd_location in pd_list:
                pd_info = getcmdout('{0} ctrl slot={1} pd {2} show'.format(tool_path,ctrl_id,pd_location))
                pd = {}
                for line in pd_info.split('\n'):
                    row = re.match('\s+(.*?)\s*:\s+(.*)\s*',line)
                    if row:
                        key = row.group(1)
                        value = row.group(2).strip()
                        pd[key] = value
                if pd['Interface Type'] == 'Solid State SATA':
                    protocol = 'SATA'
                    pd_type = 'SSD'
                elif pd['Interface Type'] == 'SATA':
                    protocol = 'SATA'
                    pd_type = 'HDD'
                else:
                    protocol = pd['Interface Type']
                    pd_type = pd['Interface Type']
                pd_data = {
                    'location': pd_location,
                    'protocol': protocol,
                    'type': pd_type,
                    'stat': pd['Status'],
                    'health': True,
                    'size': pd['Size'],
                    'disk': '',
                    'info': {
                        'Array': array_name,
                        'model': pd.get('Model','').strip(),
                        'wwn': pd['WWID'],
                        'temperature': int(pd.get('Current Temperature (C)',0)),
                        'firmware': pd.get('Firmware Revision'),
                        },
                    'sn': pd.get('Serial Number','').replace('-','')
                    }
                if pd_data['stat'] != 'OK':
                    pd_data['health'] = False

                if pd_type == 'SSD':
                    if 'Usage remaining' in pd:
                        pd_data['info']['life_remain'] = float(pd['Usage remaining'].replace('%',''))
                        manufacture = 'HP'
                    else:
                        manufacture = model2manufacture(pd_data['info']['model'])
                else:
                    manufacture = 'HP'
                pd_data['info']['manufacture'] = manufacture

                data['pd'].append(pd_data)

        #get vd
        ld_show = getcmdout('{0} ctrl slot={1} ld all show'.format(tool_path,ctrl_id))
        ld_array = ld_show.split('Array')[1:]
        for array in ld_array:
            array_name = array[:array.find('\n')].strip()
            ld_list = re.findall('logicaldrive\s+(\S+)\s+',array)
            for vid in ld_list:
                ld_info = getcmdout('{0} ctrl slot={1} ld {2} show'.format(tool_path,ctrl_id,vid))
                vd = {}
                for line in ld_info.split('\n'):
                    row = re.match('\s+(.*?)\s*:\s+(.*)\s*',line)
                    if row:
                        key = row.group(1)
                        value = row.group(2).strip()
                        vd[key] = value

                vd_data = {
                    'type': vd['Fault Tolerance'],
                    'vid': vid,
                    'stat': vd['Status'],
                    'health': True,
                    'disk': vd['Disk Name'],
                    'size': vd['Size'],
                    'cache': vd['Caching'],
                    'info': {
                        'Array': array_name,
                        },
                    'pd': [],
                }

                if vd_data['stat'] != 'OK':
                    vd_data['health'] = False

                # get pd in ld
                groups = re.findall('''
                \w+\sGroup\s(\d+):\n
                (.+?)
                \n^\s{9}(?=\S)
                ''',ld_info,flags=re.S|re.M|re.X)
                for group in groups:
                    group_id = group[0]
                    for line in group[1].split('\n'):
                        location_search = re.search('physicaldrive (\S+)',line)
                        if location_search:
                            location = location_search.group(1)
                        else:
                            continue

                        found = False
                        for n,pd in enumerate(data['pd']):
                            if location == pd['location']:
                                found = True
                                pd_data = pd
                                pd_index = n
                        if not found:
                            raise Exception('can not found pd {0}'.format(location))
                        pd_data['disk'] = vd_data['disk']
                        data['pd'][pd_index]['disk'] = vd_data['disk']
                        pd_data['drivegroup'] = group_id
                        vd_data['pd'].append(pd_data)
                # for hba
                if not vd_data['pd']:
                    for pd_index,pd_data in enumerate(data['pd']):
                        if vd_data['info']['Array'] == pd_data['info']['Array']:
                            pd_data['disk'] = vd_data['disk']
                            data['pd'][pd_index]['disk'] = vd_data['disk']
                            vd_data['pd'].append(pd_data)

                data['vd'].append(vd_data)

        info.append(data)
    return info

def get_lsi_hba(type_name='lsi_sas3'):
    if type_name == 'lsi_sas3':
        tool_path='/opt/op/sas3ircu'
        if not os.path.isfile(tool_path):
            tool_path='/opt/op/sas3ircu.bak'
    elif type_name == 'lsi_sas2':
        tool_path='/opt/op/sas2ircu'
        if not os.path.isfile(tool_path):
            tool_path='/opt/op/sas2ircu.bak'
    info = []
    # get scsi_host
    scsi_hosts = []
    lsscsi_out = getcmdout('lsscsi -H')
    for line in lsscsi_out.split('\n'):
        if not line:
            continue
        match = re.match('\[(\d+)\]\s+(\w+)',line)
        scsi_host = match.group(1)
        driver = match.group(2)
        if driver in ('mpt2sas','mpt3sas'):
            scsi_hosts.append([scsi_host,driver])

    ctrllist = getcmdout('{0} list'.format(tool_path))
    ctrllist = re.findall('\n[- ]+\n(.*)',ctrllist)
    for line in ctrllist:
        line = re.split('\s+',line.strip())
        if len(line) != 7:
            continue
        data = {
            'ctrl_id': int(line[0]),
            'type': 'lsi_hba',
            'info': {
                'model': line[1],
                'PCI Address': line[4].replace('h',''),
                },
            'pd':[],
            'vd':[],
        }

        tool_display = getcmdout('{0} {1} display'.format(tool_path,data['ctrl_id']))

        # get ctrl info
        ctrlpart = re.match('''.*
            Controller\sinformation\s*\n
            -{2,}\n
            (.*?)\n
            -{2,}\n
            .*
            ''',tool_display,flags=re.S|re.M|re.X)
        if ctrlpart:
            ctrlpart = ctrlpart.group(1)
        else:
            raise Exception('can not find "Controller information" in "{0} {1} display"'.format(tool_path,data['ctrl_id']))
        ctrl = {}
        for line in ctrlpart.split('\n'):
            row = re.match('\s*(.*?)\s+:\s+(.*)\s*',line)
            if row:
                key = row.group(1)
                value = row.group(2).strip()
                ctrl[key] = value
        data['info']['firmware'] = ctrl['Firmware version']
        data['info']['model'] = ctrl['Controller type']

        # get pd
        pdpart = re.match('''.*
            Physical\sdevice\sinformation\s*\n
            -{2,}\n
            (.*?)\n
            -{2,}\n
            .*
            ''',tool_display,flags=re.S|re.M|re.X)
        if pdpart:
            pdpart = pdpart.group(1)
        else:
            raise Exception('can not find "Physical device information" in "{0} {1} display"'.format(tool_path,data['ctrl_id']))
        for pd_row in re.split('\n\n',pdpart):
            pd = {}
            if not 'Device' in pd_row:
                continue
            if 'Device is a Enclosure services device' in pd_row:
                continue
            if 'Device is a Processor device' in pd_row:
                continue
            for line in pd_row.split('\n'):
                row = re.match('\s+(.*?)\s+:\s+(.*)\s*',line)
                if row:
                    key = row.group(1)
                    value = row.group(2).strip()
                    pd[key] = value
            if '_' in pd['Drive Type']:
                pd_type = pd['Drive Type'].split('_')[1]
            else:
                pd_type = pd['Drive Type']
            pd_data = {
                'location': pd['Enclosure #']+':'+pd['Slot #'],
                'size': '',
                'protocol': pd.get('Protocol'),
                'type': pd_type,
                'stat': pd['State'],
                'health': True,
                'sn': pd['Serial No'].replace('-',''),
                'disk': '',
                'info': {
                    'model': pd['Model Number'].strip(),
                    'firmware': pd['Firmware Revision'].strip(),
                    'sas_address': pd['SAS Address'].replace('-',''),
                    'wwn': pd['GUID'],
                    },
                }

            if not pd_data['stat'] in ('Optimal (OPT)','Ready (RDY)','Standby (SBY)'):
                pd_data['health'] = False

            pd_data['info']['manufacture'] = model2manufacture(pd_data['info']['model'])
            pd_data['info']['series'] = model2series(pd_data['info']['model'])
            if pd_data['info']['manufacture'] == 'unknown':
                pd_data['info']['manufacture'] = pd.get('Manufacturer')

            if pd.get('Size (in MB)/(in sectors)'):
                size = pd.get('Size (in MB)/(in sectors)').split('/')[0] + ' MB'
                pd_data['size'] = size

            scsi_host = scsi_hosts[data['ctrl_id']][0]
            lsscsi_out = getcmdout('lsscsi {0}:*:*:* -t -g'.format(scsi_host))
            for line in lsscsi_out.split('\n'):
                if pd_data['info']['sas_address'] in line:
                    pd_data['disk'] = re.split('\s+',line.strip())[-2]
                    if pd_data['disk'] == '-':
                        pd_data['disk'] = re.split('\s+',line.strip())[-1]
            #if not pd_data['disk'] and pd_data['health'] == True:
            #    raise Exception('can not get disk for lsi_hba {0}'.format(pd_data['location']))

            data['pd'].append(pd_data)

        #get vd
        vdpart = re.match('''.*
            IR\sVolume\sinformation\s*\n
            -{2,}\n
            (.*?)\n
            -{2,}\n
            .*
            ''',tool_display,flags=re.S|re.M|re.X)
        if vdpart:
            vdpart = vdpart.group(1)
            vd = {}
            for vd_row in vdpart.split('IR volume'):
                for line in vd_row.split('\n'):
                    row = re.match('\s*(.*?)\s+:\s+(.*)\s*',line)
                    if row:
                        key = row.group(1)
                        value = row.group(2).strip()
                        vd[key] = value
                if not vd:
                    continue
                vd_data = {
                    'type': vd['RAID level'],
                    'vid': int(vd['Volume ID']),
                    'stat': vd['Status of volume'],
                    'size': '',
                    'health': True,
                    'cache': 'N/A',
                    'disk': '',
                    'info': {
                        'wwn': vd['Volume wwid']
                        },
                    'pd': [],
                }
                if 'Size (in MB)' in vd:
                    vd_data['size'] = vd['Size (in MB)']+' MB'
                if vd_data['stat'] != 'Okay (OKY)':
                    vd_data['health'] = False

                lsscsi_out = getcmdout('lsscsi')
                for line in lsscsi_out.split('\n'):
                    if not 'LSI' in line:
                        continue
                    if not 'Logical Volume' in line:
                        continue
                    disk = re.split('\s+',line.strip())[-1]
                    vd_data['disk'] = disk

                # get pd in vd
                for key in vd:
                    if 'PHY[' in key:
                        location = vd[key]
                        found = False
                        for n,pd in enumerate(data['pd']):
                            if location == pd['location']:
                                pd_data = pd
                                found = True
                                if pd_data['location'] == '0:0' and pd_data['health'] == False:
                                    pd_data['location'] = '槽位不可信，请升级硬件组'
                                    data['pd'][n] = pd_data
                        vd_data['pd'].append(pd_data)
                data['vd'].append(vd_data)

        info.append(data)
    return info

def get_pmc():
    type_name = 'pmc'
    tool_path = 'arcconf'
    info = []
    ctrllist = getcmdout('{0} list'.format(tool_path))
    for line in ctrllist.split('\n'):
        ctrl_match = re.match('\s*Controller\s+(\d+):\s+:\s+(.*)',line)
        if not ctrl_match:
            continue
        ctrl_id = int(ctrl_match.group(1))
        ctrl_detail = getcmdout('{0} getconfig {1} ad'.format(tool_path,ctrl_id))
        ctrl = {}
        for line in ctrl_detail.split('\n'):
            row = re.match('\s+(.*?)\s*:\s+(.*)\s*',line)
            if row:
                key = row.group(1)
                value = row.group(2).strip()
                ctrl[key] = value
        data = {
            'type': 'pmc_raid',
            'ctrl_id': ctrl_id,
            'info': {
                'stat':ctrl['Controller Status'],
                'PCI Address': ctrl.get('PCI Address (Bus:Device:Function)',''),
                'temperature': ctrl.get('Temperature', ''),
                'Controller Mode': ctrl['Controller Mode'],
                'model': ctrl['Controller Model'].split(' ')[-1],
                'firmware': ctrl['Firmware'],
                'driver': ctrl['Driver'],
                'tool_health': True,
                },
            'pd': [],
            'vd': [],
            }

        if data['info']['stat'] == 'Optimal':
            data['info']['stat'] = 'OK'

        if 'RAID' in ctrl['Controller Mode']:
            lsscsi_out = getcmdout('lsscsi')
            disk_by_model = {}
            for line in lsscsi_out.split('\n'):
                if not '/dev' in line:
                    continue
                lsscsi_row = re.split('\s{2,}',line)
                disk_by_model[lsscsi_row[3]] = lsscsi_row[-1].strip()

        # get battery
        if 'RAID' in data['info']['model']:
            if 'Supercap Status' in ctrl:
                data['info']['Supercap Status'] = ctrl['Supercap Status']
                if ctrl['Supercap Status'] == 'Ready':
                    data['info']['backpower'] = 'OK'
                else:
                    data['info']['backpower'] = 'Oops'
            else:
                data['info']['Backup Power Status'] = ctrl['Backup Power Status']
                if ctrl['Backup Power Status'] == 'Ok':
                    data['info']['backpower'] = 'OK'
                else:
                    data['info']['backpower'] = 'Oops'
            if 'Cache Status' in ctrl:
                data['info']['Cache Status'] = ctrl['Cache Status']
        else:
            data['info']['backpower'] = 'N/A'
            data['type'] = 'pmc_hba'

        # get cc
        if data['type'] == 'pmc_raid':
            data['info']['cc'] = ctrl['Background consistency check']
            data['info']['cc period'] = ctrl.get('Background consistency check period')

        # get pd
        pd_list = getcmdout('{0} getconfig {1} pd'.format(tool_path,ctrl_id))
        for pd_part in pd_list.split('\n\n'):
            if 'Device is an Enclosure Services Device' in pd_part:
                continue
            if not 'Device #' in pd_part:
                continue
            pd = {}
            for line in pd_part.split('\n'):
                row = re.match('\s+(.*?)\s*:\s+(.*)\s*',line)
                if row:
                    key = row.group(1)
                    value = row.group(2).strip()
                    pd[key] = value

            if pd['SSD'] == 'Yes':
                pd_type = 'SSD'
            else:
                pd_type = 'HDD'
            did = re.match('\d+,(\d+).*',pd['Reported Channel,Device(T:L)']).group(1)

            location_match = re.match('Enclosure\s([\s\w]+),\sSlot\s(\d+).*',pd['Reported Location'])
            if location_match:
                eid = location_match.group(1)
                sid = location_match.group(2)
                location = eid+':'+sid
            else:
                location = pd['Reported Location']

            pd_data = {
                'location': location,
                'protocol': 'SATA',
                'type': pd_type,
                'stat': pd['State'],
                'health': True,
                'size': pd['Total Size'],
                'sn': pd.get('Serial number','N/A').replace('-',''),
                'disk': '',
                'info': {
                    'model': pd['Model'],
                    'firmware': pd['Firmware'],
                    'did': did,
                    'Reported Channel,Device(T:L)': pd['Reported Channel,Device(T:L)'],
                    },
            }

            if not pd_data['stat'] in ('Online','optimal','Ready'):
                pd_data['health'] = False

            pd_data['info']['manufacture'] = model2manufacture(pd_data['info']['model'])
            pd_data['info']['series'] = model2series(pd_data['info']['model'])

            temperature = pd.get('Temperature',pd.get('Current Temperature'))
            if temperature:
                try:
                    temperature = int(temperature[:temperature.find(' ')])
                except:
                    pd_data['info']['temperature'] = None
                else:
                    pd_data['info']['temperature'] = temperature


            data['pd'].append(pd_data)

        #get vd
        ld_list = getcmdout('{0} getconfig {1} ld'.format(tool_path,ctrl_id))
        for ld_part in ld_list.split('\n\n'):
            if not 'Logical Device number' in ld_part:
                continue
            ld = {}
            for line in ld_part.split('\n'):
                row = re.match('\s+(.*?)\s*:\s+(.*)\s*',line)
                if row:
                    key = row.group(1)
                    value = row.group(2).strip()
                    ld[key] = value
            vd_data = {
                'type': ld['RAID level'],
                'vid': ld.get('Array',ld['Logical Device name']),
                'stat': ld['Status of Logical Device'],
                'health': True,
                'size': ld['Size'],
                'disk': '',
                'cache': 'N/A',
                'info': {
                    'Interface Type': ld['Interface Type'],
                    'Device Type': ld['Device Type']
                    },
                'pd': [],
            }
            if not vd_data['stat'] in ('Online','optimal','Optimal'):
                vd_data['health'] = False

            if 'Disk Name' in ld:
                vd_data['disk'] = ld['Disk Name']
            else:
                vd_data['disk'] = disk_by_model.get(ld['Logical Device name'])

            if data['type'] == 'pmc_raid':
                if 'Caching' in ld:
                    vd_data['cache'] = ld['Caching']
                else:
                    vd_data['cache'] = ld['Read-cache status']+','+ld['Write-cache status']

            if 'Volume Unique Identifier' in ld:
                vd_data['info']['wwn'] = ld['Volume Unique Identifier']

            # get pd in vd
            ldpart_in_vd = re.match('''.*
                \s+.*Device.*information\n
                \s+-{2,}\n
                (.*)
                ''',ld_part,flags=re.S|re.M|re.X|re.I).group(1)
            for line in ldpart_in_vd.split('\n'):
                row = re.match('\s+(.*?)\s*:\s+(.*)\s*',line)
                if not row:
                    continue
                drivegroup = row.group(1)

                if 'Missing' in line:
                    pd = {
                        'location': '',
                        'protocol': '',
                        'type': '',
                        'stat': 'Missing',
                        'health': False,
                        'size': '',
                        'sn': '',
                        'disk': '',
                        'info': {
                            },
                    }
                    pd['disk'] = vd_data['disk']
                    pd['drivegroup'] = drivegroup
                    vd_data['pd'].append(pd)
                else:
                    location_match = re.match('.*Enclosure:(\d+),\sSlot:(\d+).*',line)
                    if location_match:
                        eid = location_match.group(1)
                        sid = location_match.group(2)
                        location = eid+':'+sid
                    else:
                        connector_match = re.match('.*Connector:(\d+),\sDevice:(\d+).*',line)
                        if connector_match:
                            connector = connector_match.group(1)
                            device = connector_match.group(2)
                            location = 'Connector {0}, Device {1}'.format(connector,device)
                        else:
                            location = None
                            channel_match = re.match('.*Channel:(\d+),\sDevice:(\d+).*',line)
                            if channel_match:
                                channel = channel_match.group(1)
                                device = channel_match.group(2)
                                channel_location = channel+','+device

                    found = False
                    for n,pd_data in enumerate(data['pd']):
                        if location:
                            if pd_data['location'] != location:
                                continue
                        else:
                            if not channel_location in pd_data['info']['Reported Channel,Device(T:L)']:
                                continue
                        found = True
                        pd = pd_data
                        pd['disk'] = vd_data['disk']
                        pd['drivegroup'] = drivegroup
                        vd_data['pd'].append(pd)
                        data['pd'][n] = pd
                    if not found:
                        raise Exception('not found {0}:{1} in pd'.format(eid,sid))

            data['vd'].append(vd_data)

        info.append(data)

    return info

def get_pmc_ipmi():
    type_name = 'pmc_ipmi'
    info = []

    # get ctrl
    try:
        controller = H3CPMCController()
    except:
        data = {
            'type': 'pmc_raid',
            'ctrl_id': 0,
            'info': {
                'source': 'ipmi',
                'tool_health': False,
                },
            'pd': [],
            'vd': [],
            }
        return [data]

    ctrl_sum = controller.getCtrl()

    for ctrl_id in range(ctrl_sum['count']):

        controller.ctrl_id = ctrl_id
        ctrl = controller.info()
        data = {
            'type': 'pmc_raid',
            'ctrl_id': ctrl_id,
            'info': {
                'stat': ctrl_sum['stat'],
                'model': ctrl['name'],
                'firmware': ctrl['firmware'],
                'cache_size': ctrl['cache_size'],
                'cache_status': ctrl['cache_status'],
                'battery': ctrl['battery'],
                'battery_status': ctrl['battery_status'],
                'sn': ctrl['sn'],
                'source': 'ipmi',
                'tool_health': True,
                },
            'pd': [],
            'vd': [],
            }
        if ctrl['name'] == 'HBA-H460-M1':
            data['info']['backpower'] = 'N/A'
            data['type'] = 'pmc_hba'
        else:
            if ctrl['battery_status'] in ('completed','Completed'):
                data['info']['backpower'] = 'OK'
            else:
                data['info']['backpower'] = 'Oops'

        # get pd
        pdlist = controller.pdlist()
        for device in pdlist['devices']:
            pd = controller.pdinfo(device)
            if 'SATA' in pd['type']:
                protocol = 'SATA'
            elif 'SAS' in pd['type']:
                protocol = 'SAS'
            else:
                protocol = ''
            if 'HDD' in pd['type']:
                pd_type = 'HDD'
            elif 'SSD' in pd['type']:
                pd_type = 'SSD'
            else:
                pd_type = ''

            pd_data = {
                'location': '{0}:{1}'.format(pd['enclosure'], pd['slot']),
                'protocol': protocol,
                'type': pd_type,
                'stat': pd['status'],
                'health': True,
                'size': pd['size'],
                'sn': pd['sn'].replace('-',''),
                'disk': '',
                'info': {
                    'did': pd['device_id'],
                    'manufacture': pd['Manufacture'],
                    'host_id': pd['host_id'],
                    'life': pd.get('life'),
                    'Temperature': pd.get('temperature'),
                    },
                }
            if not pd_data['stat'] in ('Online','optimal','Ready','Optimal'):
                pd_data['health'] = False
            data['pd'].append(pd_data)

        # get vd
        ldlist = controller.ldlist()
        for ld_index in ldlist['devices']:
            ld = controller.ldinfo(ld_index)
            vd_data = {
                'type': ld['raid_type'],
                'vid': ld['device'],
                'stat': ld['status'],
                'health': True,
                'size': ld['size'],
                'disk': '',
                'cache': '',
                'info': {},
                'pd': [],
            }
            if not vd_data['stat'] in ('Online','optimal','Optimal'):
                vd_data['health'] = False

            # get pd in vd
            ldpdlist = controller.ldpdlist(ld_index)
            for pd_index in ldpdlist['devices']:
                pd = controller.pdinfo(pd_index)
                if 'SATA' in pd['type']:
                    protocol = 'SATA'
                else:
                    protocol = 'SAS'
                if 'HDD' in pd['type']:
                    pd_type = 'HDD'
                else:
                    pd_type = 'SSD'
                pd_data = {
                    'location': '{0}:{1}'.format(pd['enclosure'], pd['slot']),
                    'protocol': protocol,
                    'type': pd_type,
                    'stat': pd['status'],
                    'health': True,
                    'size': pd['size'],
                    'sn': pd['sn'].replace('-',''),
                    'disk': '',
                    'info': {
                        'did': pd['device_id'],
                        'manufacture': pd.get('Manufacture'),
                        'life': pd.get('life'),
                        'Temperature': pd.get('temperature'),
                        },
                    }
                if not pd_data['stat'] in ('Online','optimal','Ready','Optimal'):
                    pd_data['health'] = False
                pd_data['drivegroup'] = ld['device']
                data['pd'].append(pd_data.copy())
                vd_data['pd'].append(pd_data)
            data['vd'].append(vd_data)
        info.append(data)
    return info

def get_nvme():
    info = []
    tool_path = 'nvme'

    data = {
        'type': 'nvme',
        'ctrl_id': 0,
        'info': {
            },
        'vd': [],
        'pd': [],
        }

    # get pci
    lspci_out = getcmdout('lspci')
    sys_class_out = getcmdout('ls -l /sys/class/nvme/')
    # get pd
    nvme_list_out = getcmdout('{0} list -o json'.format(tool_path))
    list_info = json.loads(nvme_list_out)
    index = -1
    for line in lspci_out.split('\n'):
        if not 'Non-Volatile memory controller' in line:
            continue
        pci_addr = line.split(' ')[0]
        disk = ''
        for class_line in sys_class_out.split('\n'):
            if pci_addr in class_line:
                disk = class_line.split('/')[-1]
        pd_data = {
            'location': pci_addr,
            'protocol': 'pcie',
            'type': 'SSD',
            'stat': 'N/A',
            'health': True,
            'size': 0,
            'sn': 'N/A',
            'disk': disk,
            'info': {
                },
            }
        # 用户态的取不到其他信息，就到这了
        if not disk:
            data['pd'].append(pd_data)
            continue

        for pd in list_info.get('Devices',[]):
            if pd['Index'] == index:
                continue
            else:
                index = pd['Index']
            # 排除udisk的盘,非本地设备
            if 'udisk' in pd['ModelNumber']:
                continue
            if not disk in pd['DevicePath']:
                continue
            pd_data.update({
                'size': sizeof_fmt(pd.get('PhysicalSize',0)),
                'sn': pd.get('SerialNumber','N/A'),
                'info': {
                    'model': pd['ModelNumber'],
                    'firmware': pd['Firmware'],
                    },
                })

            pd_data['info']['manufacture'] = model2manufacture(pd_data['info']['model'])
            pd_data['info']['series'] = model2series(pd_data['info']['model'])

            smart_log_out = getcmdout('{0} smart-log {1} -o json'.format(tool_path,pd['DevicePath']))
            smart_info = json.loads(smart_log_out)
            pd_data['info']['avail_spare'] = smart_info['avail_spare']
            pd_data['info']['critical_warning'] = smart_info['critical_warning']
            pd_data['info']['life_remain'] = 100 - smart_info['percent_used']
            pd_data['info']['media_errors'] = smart_info['media_errors']
            pd_data['info']['temperature'] = int(round(smart_info['temperature']-273.15))

        data['pd'].append(pd_data)

    info.append(data)
    return info

def get_shannon():
    info = []
    data = {
        'type': 'shannon',
        'ctrl_id': 0,
        'info': {
            },
        'vd': [],
        'pd': [],
        }
    # get pd
    devices = os.listdir('/dev/')
    for item in devices:
        if item.find('df') != 0:
            continue
        device = os.path.join('/dev',item)
        mode = os.stat(device).st_mode
        if not stat.S_ISBLK(mode):
            continue
        if not re.match('df[a-z]+$',item):
            continue
        
        pd_data = {
            'location': item,
            'protocol': 'pcie',
            'type': 'SSD',
            'stat': get_block_info(item,'shannon/access_mode'),
            'health': True,
            'size': sizeof_fmt(int(get_block_info(item,'size'))),
            'sn': get_block_info(item,'shannon/serial_number'),
            'disk': device,
            'info': {
                'PCI Address': get_block_info(item,'shannon/pci_info/pci_address'),
                'model': get_block_info(item,'shannon/model'),
                'firmware': get_block_info(item,'shannon/firmware_version'),
                'life_remain': int(get_block_info(item,'shannon/estimated_life_left')),
                'temperature_int': int(get_block_info(item,'shannon/temperature_int')),
                'temperature_flash': int(get_block_info(item,'shannon/temperature_flash')),
                'seu_crc_error': int(get_block_info(item,'shannon/seu_crc_error')),
                'seu_ecc_error': int(get_block_info(item,'shannon/seu_ecc_error')),
                },
            }
        pd_data['info']['manufacture'] = model2manufacture(pd_data['info']['model'])
        pd_data['info']['series'] = model2series(pd_data['info']['model'])
        if not 'readwrite' in pd_data['stat']:
            pd_data['health'] = False

        data['pd'].append(pd_data)

    info.append(data)
    return info

def get_smart_info(disk_info):
    tool_path = '/usr/sbin/smartctl'
    if not os.path.isfile(tool_path):
        tool_path = '/usr/sbin/smartctl.bak'
    ignore = [-9] + list(range(2,2**8))
    timeout = 15
    max_proc = 1
    pool = Pool(processes=1)

    def get_pd_smart(pd,ctrl,all_ctrl):
        smartinfo = {}

        # get device_type
        if ctrl['type'] == 'lsi_raid':
            if pd['protocol'] == 'SATA':
                device_type = 'sat+megaraid'
            elif pd['protocol'] == 'SAS':
                device_type = 'megaraid'
        elif ctrl['type'] in ('hp_raid','hp_hba'):
            device_type = 'cciss'
        elif ctrl['type'] == 'pmc_raid':
            if 'P460' in ctrl['info']['model']:
                device_type = 'cciss'
            else:
                device_type = 'aacraid,0,0'
        elif ctrl['type'] == 'pmc_hba':
            device_type = 'cciss'

        # get device
        if ctrl['type'] in ('lsi_hba'):
            device = pd['disk']
        elif device_type == 'cciss':
            pd_num = 0
            for i in all_ctrl:
                if i['type'] in ('hp_raid','hp_hba','pmc_raid','pmc_hba'):
                    pd_num += len(i['pd'])
            for cciss_index in range(pd_num):
                device = '-d {0},{1} /dev/sda'.format(device_type,cciss_index)
                smart_out = getcmdout('{0} {1} -j -i'.format(tool_path,device),ignore_code=ignore,max=max_proc,timeout=timeout) or '{}'
                smartinfo = json.loads(smart_out)
                if pd['sn'] == smartinfo.get('serial_number','').replace('-',''):
                    break
                else:
                    device = None
        else:
            device = '-d {0},{1} /dev/sda'.format(device_type,pd['info']['did'])

        if not 'smart' in pd:
            pd['smart'] = {}

        if not device:
            pd['smart']['stat'] = 'Device not found'
            return pd

        # get smart health
        smart_out = getcmdout('{0} {1} -j -H'.format(tool_path,device),ignore_code=ignore,max=max_proc,timeout=10) or '{}'
        smartinfo = json.loads(smart_out)
        if not smartinfo:
            pd['smart']['stat'] = '{0} -H open timeout'.format(device)
            return pd
        if not 'smart_status' in smartinfo:
            pd['smart']['stat'] = '{0} -H open failed'.format(device)
            return pd

        pd['smart']['stat'] = 'Ok'
        pd['smart']['health'] = smartinfo['smart_status']['passed']

        # get smart info
        smart_out = getcmdout('{0} {1} -j -i'.format(tool_path,device),ignore_code=ignore,max=max_proc,timeout=timeout) or '{}'
        smartinfo = json.loads(smart_out)
        if not smartinfo:
            pd['smart']['stat'] = '{0} -i open timeout'.format(device)
            return pd
        if not 'serial_number' in smartinfo:
            pd['smart']['stat'] = '{0} -i open failed'.format(device)
            return pd

        pd['smart']['sn'] = smartinfo.get('serial_number','').replace('-','')
        if pd['smart']['sn'].find(pd['sn']) != 0:
            raise Exception('SN {0} and smart SN {1} not match!'.format(pd['sn'], pd['smart']['sn']))
        elif pd['sn'] != pd['smart']['sn']:
            pd['sn'] = pd['smart']['sn']

        pd['smart']['model_name'] = smartinfo['model_name']
        pd['info']['model'] = smartinfo['model_name']
        pd['info']['series'] = model2series(smartinfo['model_name'])
        if 'ata_additional_product_id' in smartinfo:
            pd['smart']['ata_additional_product_id'] = smartinfo.get('ata_additional_product_id')

        # get manufacture
        if ctrl['info'].get('source') == 'ipmi':
            pd['info']['model_name'] = smartinfo['model_name']
            pd['info']['manufacture'] = model2manufacture(smartinfo['model_name'])
            pd['info']['series'] = model2series(smartinfo['model_name'])

        # get attr
        smart_out = getcmdout('{0} {1} -j -A'.format(tool_path,device),ignore_code=ignore,max=max_proc,timeout=timeout) or '{}'
        smartinfo = json.loads(smart_out)
        if not smartinfo:
            pd['smart']['stat'] = '{0} -A open timeout'.format(device)
            return pd
        if not 'temperature' in smartinfo:
            pd['smart']['stat'] = '{0} -A open failed'.format(device)
            return pd
        pd['smart']['temperature'] = smartinfo['temperature']['current']


        if pd['type'] == 'SSD':
            attr = {}
            attr_alias = {
                'End-to-End_Error_Count': 'End-to-End_Error',
                'Uncorrectable_Error_Cnt': 'Reported_Uncorrect',
                'Percent_Life_Remaining': 'Media_Wearout_Indicator',
            }
            attr_id = {
                170: 'Reserved_Block_Pct',
                202: 'Percent_Lifetime_Remain',
            }
            for i in smartinfo['ata_smart_attributes']['table']:
                if i['name'] in attr_alias:
                    attr[attr_alias[i['name']]] = i
                elif i['id'] in attr_id and 'Unknown' in i['name']:
                    attr[attr_id[i['id']]] = i
                else:
                    attr[i['name']] = i

            manufacture = pd['info']['manufacture']

            if manufacture == 'Intel':
                pd['smart']['life_remain'] = attr['Media_Wearout_Indicator']['value']
                pd['smart']['end_to_end_err'] = attr['End-to-End_Error']['value']
                if not 'DELL' in pd['smart'].get('ata_additional_product_id',''):
                    pd['smart']['uecc'] = attr['Reported_Uncorrect']['raw']['value']
                    pd['smart']['unused_rsvd_blk'] = attr['Available_Reservd_Space']['value']
            elif manufacture == 'Samsung':
                pd['smart']['reallocated_sector'] = attr['Reallocated_Sector_Ct']['value']
                pd['smart']['life_remain'] = attr['Wear_Leveling_Count']['value']
                pd['smart']['unused_rsvd_blk'] = attr['Unused_Rsvd_Blk_Cnt_Tot']['value']
                pd['smart']['runtime_bad_block'] = attr['Runtime_Bad_Block']['value']
                pd['smart']['end_to_end_err'] = attr['End-to-End_Error']['value']
                pd['smart']['uecc'] = attr['Reported_Uncorrect']['raw']['value']
            elif manufacture == 'Micron':
                pd['smart']['raw_read_err_rate'] = attr['Raw_Read_Error_Rate']['value']
                pd['smart']['unused_rsvd_blk'] = attr['Reserved_Block_Pct']['value']
                pd['smart']['uecc'] = attr['Reported_Uncorrect']['raw']['value']
                pd['smart']['life_remain'] = attr['Percent_Lifetime_Remain']['value']
                pd['smart']['Reported_Uncorrect'] = attr['Reported_Uncorrect']['raw']['value']
                pd['smart']['Offline_Uncorrectable'] = attr['Offline_Uncorrectable']['raw']['value']
            elif manufacture == 'SanDisk':
                pd['smart']['reallocated_sector'] = attr['Reallocated_Sector_Ct']['value']
                pd['smart']['uecc'] = attr['Lifetime_UECC_Ct']['raw']['value']
                pd['smart']['runtime_bad_block'] = attr['Lifetime_Retried_Blk_Ct']['value']
                pd['smart']['life_remain'] = attr['Drive_Life_Remaining%']['value']
        return pd

    result = []
    for ctrl_i,ctrl_data in enumerate(disk_info):
        # h3c 固件和驱动不匹配 带内无法查看的，smart也无法查看
        if ctrl_data['info'].get('source') == 'ipmi':
            continue
        if ctrl_data['type'] in ('nvme','shannon'):
            continue
        for pd_i,pd_data in enumerate(ctrl_data['pd']):
            # 磁盘状态不正常的，smart无法查看
            if pd_data['stat'] in ('Failed','UBad'):
                continue

            res = pool.apply_async(mtrace,(get_pd_smart,pd_data,ctrl_data,disk_info))
            result.append([res,ctrl_i,pd_i])

    pool.close()
    pool.join()
    for item in result:
        res,ctrl_i,pd_i = item
        disk_info[ctrl_i]['pd'][pd_i] = res.get()

    return disk_info

def __get_info():
    types = get_types()
    info = []
    for t in types:
        old_len = len(info)
        if t == 'lsi_raid':
            info.extend(get_lsi_raid())
        if t == 'dell_raid':
            info.extend(get_lsi_raid(type_name=t))
        if t == 'lsi_sas3':
            info.extend(get_lsi_hba())
        elif t == 'lsi_sas2':
            info.extend(get_lsi_hba(type_name=t))
        if t == 'hp':
            info.extend(get_hp())
        if t == 'pmc':
            pmc_info = get_pmc()
            if not pmc_info:
                pmc_info = get_pmc_ipmi()
            info.extend(pmc_info)
        if t == 'nvme':
            info.extend(get_nvme())
        if t == 'shannon':
            info.extend(get_shannon())

        if len(info) == old_len:
            raise Exception('get {0} info failed'.format(t))

    info = get_smart_info(info)

    return info

def get_info(cache=300):
    '''
format:
    [
        {
            'type':'lsi_raid',
            'ctrl_id': 0,
            'info':{}
            'pd':[
                {
                    'location': '0:0',
                    'type': 'HDD',
                    'protocol': 'SATA',
                    'stat': 'Onln',
                    'health': true,
                    'info': {},
                    'smart': {},
                    'disk': '/dev/sda',
                    'size': '1.089 TB',
                    'sn': 'WFK2KYR50000E9209THC',
                },
                ...
            ],
            'vd':[
                {
                    'type':'RAID1',
                    'vid': '0',
                    'disk': 'sda',
                    'stat': 'Optl',
                    'health': true,
                    'info': {},
                    'size': '1.089 TB',
                    'cache': 'RWBD',
                    'pd': [
                       {
                           'location': '0:0',
                           'type': 'HDD',
                           'protocol': 'SATA',
                           'stat': 'Onln',
                           'health': true,
                           'info': {},
                           'disk': 'sda',
                           'size': '1.089 TB',
                           'sn': 'WFK2KYR50000E9209THC',
                           'drivegroup':'DiskGroup: 0, Span: 0, Arm: 0',
                       },
                       ...
                    ]
                },
                ...
            ]
        
        },
        ...
    ]
    '''
    cache_file = '/dev/shm/cache_disk_collect.json'
    self_md5 = md5(os.path.abspath(__file__))

    out = ''
    if os.path.isfile(cache_file):
        cache_time = os.path.getmtime(cache_file)
        if (time.time() - cache_time) < cache:
            with open(cache_file) as f:
                out = f.read()
    info = ''
    if out:
        data = json.loads(out)
        if data.get('sign') == self_md5:
            info = data['info']
    if not info:
        info = __get_info()
        data = {
             'info': info,
             'sign': self_md5,
        }
        out = json.dumps(data,indent=True)
        with open(cache_file, 'w+') as f:
            f.write(out)
    return info

#################################################
# monitor
#################################################
def get_monitor_value(key=None):
    values = {}
    item = '''
self_test
tool_health
smart.health
raid.backpower
raid.stat
raid.cache
pd.stat
pd.nofound
pd.temperature
pd.media_error
pd.pre_fail
pd.raidlog
vd.stat
ssd.life_remain
ssd.uecc
ssd.end_to_end_err
ssd.reallocated_sector
ssd.unused_rsvd_blk
ssd.runtime_bad_block
ssd.raw_read_err_rate
ssd.nvme.avail_spare
ssd.nvme.critical_warning
ssd.nvme.media_errors
ssd.nvme.temperature
ssd.shannon.temperature_int
ssd.shannon.temperature_flash
ssd.shannon.seu_crc_error
ssd.shannon.seu_ecc_error
'''
    for i in item.split('\n'):
        if not i:
            continue
        values[i.strip()] = []
    
    try:
        disk_info = get_info()
    except Exception as err:
        result = 'Oops'
        disk_info = []
    else:
        result = 'Ok'

    values['self_test'].append(mformat(result))

    for ctrl in disk_info:
        tags = {
            'ctrl_type':ctrl['type'],
            'ctrl_id':ctrl['ctrl_id'],
            }
        # tool_health
        if ctrl['type'] in ('lsi_raid','hp_raid','hp_hba'):
            tags['tool'] = 'backboard'
            values['tool_health'].append(mformat(ctrl['info']['tool_health'],tags))
            tags.pop('tool')
        if ctrl['type'] in ('pmc_raid','pmc_hba'):
            tags['tool'] = 'arcconf'
            if ctrl['info'].get('source') == 'ipmi':
                values['tool_health'].append(mformat(False,tags))
            else:
                values['tool_health'].append(mformat(True,tags))
            tags['tool'] = 'ipmi'
            values['tool_health'].append(mformat(ctrl['info']['tool_health'],tags))
            tags.pop('tool')
#        if ctrl['type'] in ('lsi_raid'):
#            tags['tool'] = 'raidlog'
#            if ctrl['info']['raidlog'] == 'OK':
#                values['tool_health'].append(mformat(0,tags))
#            else:
#                values['tool_health'].append(mformat(1,tags))
#            tags.pop('tool')

        # backpower
        if 'backpower' in ctrl['info']:
            if ctrl['info']['backpower'] == 'OK':
                values['raid.backpower'].append(mformat(0,tags))
            elif ctrl['info']['backpower'] == 'Oops':
                values['raid.backpower'].append(mformat(1,tags))
        # raid controller stat
        if 'stat' in ctrl['info']:
            if ctrl['info']['stat'] in ('OK',):
                values['raid.stat'].append(mformat(0,tags))
            else:
                values['raid.stat'].append(mformat(1,tags))
        # cache stat
        if ctrl['type'] == 'hp_raid' and 'Cache Status' in ctrl['info']:
            if ctrl['info']['Cache Status'] == 'OK':
                values['raid.cache'].append(mformat(0,tags))
            else:
                values['raid.cache'].append(mformat(1,tags))

        for vd in ctrl['vd']:
            tags = {
                'ctrl_type':ctrl['type'],
                'ctrl_id':ctrl['ctrl_id'],
                'vid':vd['vid'],
                'type': vd['type'],
                }
            values['vd.stat'].append(mformat(vd['health'],tags))

        # 监控物理盘掉盘，需要有一个持久的本地cache
        # 因为有多个ctrl，这个文件名需要带上ctrl type+id来区分
        pd_temp_file = '/tmp/.monitor_{0}_{1}_pd_temp'.format(ctrl['type'],ctrl['ctrl_id'])
        pd_temp = {}
        current_pd = {}
        if os.path.isfile(pd_temp_file):
            with open(pd_temp_file) as f:
                pd_temp = json.load(f)
        for pd in ctrl['pd']:
            tags = {
                'ctrl_type':ctrl['type'],
                'ctrl_id':ctrl['ctrl_id'],
                'location':pd['location'],
                'type': pd['type'],
                'protocol': pd['protocol'],
                'manufacture': pd['info'].get('manufacture','unknown'),
                }
            if ctrl['type'] in ('nvme','shannon'):
                tags['disk'] = pd['disk']
            current_pd[pd['location']] = tags
        if len(current_pd) > len(pd_temp):
            pd_temp.update(current_pd)
            with open(pd_temp_file,'w') as f:
                json.dump(pd_temp,f,indent=1)
        for location in pd_temp:
            if location in current_pd:
                values['pd.nofound'].append(mformat(0,pd_temp[location]))
            else:
                values['pd.nofound'].append(mformat(1,pd_temp[location]))

        for pd in ctrl['pd']:
            tags = {
                'ctrl_type':ctrl['type'],
                'ctrl_id':ctrl['ctrl_id'],
                'location':pd['location'],
                'type': pd['type'],
                'protocol': pd['protocol'],
                'manufacture': pd['info'].get('manufacture','unknown'),
                }
            if ctrl['type'] in ('nvme','shannon'):
                tags['disk'] = pd['disk']

            values['pd.stat'].append(mformat(pd['health'],tags))

            # 硬盘温度在坏盘的情况下可能取不到，逻辑不能异常
            if ctrl['type'] not in ('nvme','shannon'):
                if pd['info'].get('temperature'):
                    values['pd.temperature'].append(mformat(pd['info']['temperature'],tags))
                elif pd.get('smart',{}).get('temperature'):
                    values['pd.temperature'].append(mformat(pd['smart']['temperature'],tags))

            # for lsi
            if ctrl['type'] == 'lsi_raid':
                if 'Media Error Count' in pd['info']:
                    values['pd.media_error'].append(mformat(pd['info']['Media Error Count'],tags))
                    values['pd.pre_fail'].append(mformat(pd['info']['Predictive Failure Count'],tags))

#                if 'Unexpected sense: PD' in pd['info']['last_event'] or \
#                'Command timeout' in pd['info']['last_event']:
#                    values['pd.raidlog'].append(mformat(1,tags))
#                else:
#                    values['pd.raidlog'].append(mformat(0,tags))

            # for ssd
            if (pd['info']['manufacture'] == 'HP' or ctrl['type'] in ('nvme','shannon')) and pd['type'] == 'SSD':
                values['ssd.life_remain'].append(mformat(pd['info']['life_remain'],tags))

            if ctrl['type'] == 'nvme':
                values['ssd.nvme.avail_spare'].append(mformat(pd['info']['avail_spare'],tags))
                values['ssd.nvme.critical_warning'].append(mformat(pd['info']['critical_warning'],tags))
                values['ssd.nvme.media_errors'].append(mformat(pd['info']['media_errors'],tags))
                values['ssd.nvme.temperature'].append(mformat(pd['info']['temperature'],tags))
            if ctrl['type'] == 'shannon':
                values['ssd.shannon.temperature_int'].append(mformat(pd['info']['temperature_int'],tags))
                values['ssd.shannon.temperature_flash'].append(mformat(pd['info']['temperature_flash'],tags))
                values['ssd.shannon.seu_crc_error'].append(mformat(pd['info']['seu_crc_error'],tags))
                values['ssd.shannon.seu_ecc_error'].append(mformat(pd['info']['seu_ecc_error'],tags))

            #从ipmi获取信息的机器,smart相关指标都无效
            if ctrl['info'].get('source') == 'ipmi':
                continue

            #取不到smart信息的，跳过，取smart信息会跳过坏盘，这里不用管
            if not 'smart' in pd:
                continue

            if pd['smart']['stat'] != 'Ok':
                smart_health = 1
                values['smart.health'].append(mformat(smart_health,tags))
                continue
            if pd['smart']['health'] == True:
                smart_health = 0
            else:
                smart_health = 1
            values['smart.health'].append(mformat(smart_health,tags))

            if pd['type'] == 'SSD':
                if pd['info']['manufacture'] != 'HP':
                    values['ssd.life_remain'].append(mformat(pd['smart']['life_remain'],tags))
                    if not 'DELL' in pd['smart'].get('ata_additional_product_id',''):
                        values['ssd.uecc'].append(mformat(pd['smart']['uecc'],tags))
                if pd['info']['manufacture'] in ('Intel','Samsung'):
                    values['ssd.end_to_end_err'].append(mformat(pd['smart']['end_to_end_err'],tags))
                if pd['info']['manufacture'] in ('Samsung','Micron','Intel'):
                    if not 'DELL' in pd['smart'].get('ata_additional_product_id',''):
                        values['ssd.unused_rsvd_blk'].append(mformat(pd['smart']['unused_rsvd_blk'],tags))
                if pd['info']['manufacture'] in ('Samsung','SanDisk',):
                    values['ssd.reallocated_sector'].append(mformat(pd['smart']['reallocated_sector'],tags))
                    values['ssd.runtime_bad_block'].append(mformat(pd['smart']['runtime_bad_block'],tags))
                if pd['info']['manufacture'] in ('Micron',):
                    values['ssd.raw_read_err_rate'].append(mformat(pd['smart']['raw_read_err_rate'],tags))

    if key == 'all':
        output = json.dumps(values,indent=True)
    else:
        if key in values:
            output = json.dumps(values[key],indent=True)
        else:
            key_help = []
            for i in values:
                if key in i:
                    key_help.append(i)
            print('The monitor NAME should be:\n    ',end='')
            if key_help:
                print('\n    '.join(key_help))
            else:
                print('\n    '.join(values.keys()))
            sys.exit(2)
    return output

def mformat(value,tags=[]):
    if type(value) == str:
        if value.lower() == 'ok':
            value = 0
        else:
            value = 1
    elif type(value) == bool:
        # True转0, False转1
        value = int(1-value)

    if not type(value) in (int,float):
        raise Exception('value is not a number!')
    output = {'value': value}
    if tags:
        tag_list = []
        for key in tags:
            if tags[key] == '':
                tags[key] = None
            tag_list.append('{0}={1}'.format(key,str(tags[key]).replace(',',':')))
        output['tags'] = ','.join(tag_list)
    return output

#########################################################
# interface
#########################################################

def main(argv):
    usage = '''Usage:
    {0} -h , --help
            Print this

    {0} -i , --info
            Print disk info in json

    {0} -m NAME , --monitor NAME
            Print monitor value
'''.format(argv[0])
    try:
        opts, args = getopt.getopt(argv[1:],"him:",['help',"info","monitor="])
    except getopt.GetoptError:
        print(usage)
        sys.exit(2)

    opts = dict(opts)
    if '-h' in opts or '--help' in opts:
        print(usage)
    elif '-i' in opts or '--info' in opts:
        print(json.dumps(get_info(),indent=True))
    elif '-m' in opts or '--monitor' in opts:
        key = opts.get('-m') or opts.get('--monitor')
        print(get_monitor_value(key))
    else:
        print(usage)

if __name__ == '__main__':
    main(sys.argv)

