#!/usr/bin/env python3

## It requires python >= 2.6
## With python < 2.6 because of check_mk_agent close
## standard input and error the check wont show anything
## if executed within the agent itself.

import subprocess
import re
import os

segmentfind = re.compile(r'(\d+,\d+)')

def check_arcconf():
    arcconf = None
    for p in os.environ['PATH'].split(os.path.pathsep)+['/usr/StorMan/']:
        if os.path.isfile(os.path.join(p, 'arcconf')):
            arcconf = os.path.join(p, 'arcconf')
            break
    return arcconf

if not hasattr(subprocess, 'check_output'):
    # see https://gist.github.com/839684
    def check_output(*popenargs, **kwargs):
        if 'stdout' in kwargs:
            raise ValueError('stdout argument not allowed, it will be overridden.')
        process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
        output, unused_err = process.communicate()
        retcode = process.poll()
        if retcode:
            cmd = kwargs.get("args")
            if cmd is None:
                cmd = popenargs[0]
            raise subprocess.CalledProcessError(retcode, cmd)
        return output
    subprocess.check_output = check_output

def get_val(string):
    return ':'.join(string.split(':')[1:]).strip()

def get_num_controllers():
    cmd = [arcconf, 'GETVERSION']
    ret = subprocess.check_output(cmd)
    ret = ret.decode()
    #ret = ret2
    for line in ret.splitlines():
        if line.startswith('Controllers found:'):
            num = get_val(line)
            return int(num)
    return 0

def get_controller_info(controllerid):
    cmd = [arcconf, 'GETCONFIG', str(controllerid), 'AD']
    ret = subprocess.check_output(cmd)
    ret = ret.decode()
    #ret = ret2
    model = serial = bios = firmware = driver = bootflash = lds = status = None
    id = 'c%i' % controllerid
    for line in ret.splitlines():
        line = line.strip()
        if line.startswith('Controller Model'):
            model = get_val(line)
        elif line.startswith('Controller Serial Number'):
            serial = get_val(line)
        elif line.startswith('BIOS'):
            bios = get_val(line)
        elif line.startswith('Firmware'):
            firmware = get_val(line)
        elif line.startswith('Driver'):
            driver = get_val(line)
        elif line.startswith('Boot Flash'):
            bootflash = get_val(line)
        elif line.startswith('Logical devices'):
            lds = int(get_val(line).split('/')[0])
        elif line.startswith('Controller Status'):
            status = get_val(line)
    return {'id': id, 'model': model, 'serial': serial, 'bios': bios, 'firmware': firmware,
            'driver': driver, 'bootflash': bootflash, 'lds': lds, 'status': status,
            'numid': controllerid}

def get_logicaldevice_info(controllerid, ldid):
    cmd = [arcconf, 'GETCONFIG', str(controllerid), 'LD', str(ldid)]
    ret = subprocess.check_output(cmd)
    ret = ret.decode()
    #ret = ret2
    id = 'c%iu%i' % (controllerid, ldid)
    level = status = size = None
    members = []
    for line in ret.splitlines():
        line = line.strip()
        if line.startswith('RAID level'):
            level = get_val(line)
        elif line.startswith('Status of logical device'):
            status = get_val(line)
        elif line.startswith('Status of Logical Device'):
            status = get_val(line)
        elif line.startswith('Size'):
            size = get_val(line)
        elif line.startswith('Segment') or line.startswith('Group'):
            segment = get_val(line)
            members.append(segment)
#            members.append(segmentfind.search(segment).group(1))
    return {'id': id, 'level': level, 'status': status, 'size': size, 'members': members}

def get_disks_info(controllerid):
    cmd = [arcconf, 'GETCONFIG', str(controllerid), 'PD']
    ret = subprocess.check_output(cmd)
    ret = ret.decode()
    #ret = ret2
    diskid = vendor = model = status = None
    disks = {}
    for line in ret.splitlines():
        line = line.strip()
        if line.startswith('Reported Channel,Device(T:L)'):
            line = line.replace('T:L', 'TL')
            diskid = get_val(line)
            diskid = diskid.split('(')[0]
        elif line.startswith('State'):
            status = get_val(line)
        elif line.startswith('Vendor'):
            vendor = get_val(line)
            if not vendor:
                vendor = "UNKNOWN"
        elif line.startswith('Model'):
            model = get_val(line)
            if not model:
                model = "UNKNOWN"
        if diskid and model and vendor and status:
            id = 'c%ip%s' % (controllerid, diskid.split(',')[1])
            disk = {'id': id, 'diskid': diskid, 'status': status, 'vendor': vendor, 'model': model}
            disks[diskid] = disk
            diskid = vendor = model = status = None
    return disks

def pretty_info(controllerid):
    output = []
    i = get_controller_info(controllerid)
    output.append("-- Controller information --")
    output.append("-- ID | Model | Status")
    output.append("%(id)s | %(model)s | %(status)s" % i)
    disks = get_disks_info(controllerid)
    output.append('-- Array information --')
    output.append('-- ID | Type | Size | Status')
    for l in range(0,i['lds']):
        li = get_logicaldevice_info(controllerid,l)
        output.append('%(id)s | %(level)s | %(size)s | %(status)s' % li)
        output.append('-- Disk information')
        output.append('-- ID | Vendor | Model | Status')
        for member in li['members']:
            output.append('%(id)s | %(vendor)s | %(model)s | %(status)s ' % disks[member])
    return '\n'.join(output)

def cmk_info(controllerid):
    output = []
    # CONTROLLER
    cinfo = get_controller_info(controllerid)
    status = 0
    statustxt = 'OK'
    if cinfo['status'] != 'Optimal':
        status = 2
        statustxt = 'CRITICAL'
    item_name = 'Controller_'+str(controllerid)
    performance_data = '-'
    check_output = statustxt + " Model:%(model)s Serial:%(serial)s Bios:%(bios)s Firmware:%(firmware)s Driver:%(driver)s LDs:%(lds)s Status:%(status)s" % cinfo
    output.append("%s %s %s %s" % (status,item_name,performance_data,check_output) )

    # LOGICAL DEVICES
    for l in range(0,cinfo['lds']):
        linfo = get_logicaldevice_info(controllerid,l)
        status = 0
        statustxt = 'OK'
        if linfo['status'] != 'Optimal':
            status = 2
            statustxt = 'CRITICAL'
        item_name = 'LogicalDevice_'+linfo['id']
        performance_data = '-'
        check_output = statustxt + " RAID:%(level)s Size:%(size)s Status:%(status)s" % linfo
        output.append("%s %s %s %s" % (status,item_name,performance_data,check_output) )

    # LOGICAL DEVICE SEGMENTS
    ldsegs = linfo['members']
    segn = 0
    for m in ldsegs:
        status = 0
        statustxt = 'OK'
        if m.startswith('Missing'):
            status = 2
            statustxt = 'CRITICAL'
        item_name = 'LogicalDevice_'+linfo['id']+'_Segment_'+str(segn)
        performance_data = '-'
        check_output = statustxt + " " +  m
        output.append("%s %s %s %s" % (status,item_name,performance_data,check_output) )
        segn += 1
    # DISKS
    disks = get_disks_info(controllerid)
    disk_keys = disks.keys()
    # AttributeError: 'dict_keys' object has no attribute 'sort'
    #disk_keys.sort()
    for k in disk_keys:
        status = 0
        statustxt = 'OK'
        if disks[k]['status'] not in ["Online", "Dedicated Hot-Spare", "Ready", "Online (JBOD)" ]:
            status = 2
            statustxt = 'CRITICAL'
        item_name = 'Disk_'+disks[k]['id']
        performance_data = '-'
        check_output = statustxt + " DiskID:%(diskid)s Model:%(model)s Vendor:%(vendor)s Status:%(status)s" % disks[k]
        output.append("%s %s %s %s" % (status,item_name,performance_data,check_output) )
    return '\n'.join(output)

if __name__ == '__main__':
    arcconf = check_arcconf()
    if arcconf:
        n = get_num_controllers()
        for c in range(1,n+1):
            print (cmk_info(c))
    else:
        print ("3 No_arcconf_Found - UNKNOWN (No arcconf binary Found or no Adaptec Controller present (remove check from local checks))")

