#!/usr/bin/env python3
from pyVmomi import vim
import pyVim.connect
import sys
import atexit
import argparse
import re
import siteconf

class Main:
    def __init__(self):
        self.ok = []
        self.warning = []
        self.critical = []
        self.site_conf = siteconf.read()
        self.get_args()

    def get_args(self):
        parser = argparse.ArgumentParser(
            description='check multipath redundancy')

        parser.add_argument('-s', '--site',
                            required=True,
                            action='store',
                            help='name of the site to connect to')

        parser.add_argument('-H', '--host',
                            required=True,
                            action='store',
                            help='host to check')

        self.args = parser.parse_args()

    def run(self):
        self.connect()
        host = self.get_obj([vim.HostSystem], self.args.host)
        if not host:
            print("host not found")
            exit(1)
        self.pathcheck(host)
        self.status_exit()

    def map_uuid_to_datastore(self, host):
        canonical_datastore = {}
        for ds in host.datastore:
            try:
                vmfs = ds.info.vmfs
            except:
                continue # not vmfs
            for ext in vmfs.extent:
                canonical_datastore[ext.diskName] = {
                    'name': vmfs.name,
                    'local': vmfs.local
                    }

        self.uuid_datastore = {}
        for lun in host.config.storageDevice.scsiLun:
            ds = canonical_datastore.get(lun.canonicalName)
            if not ds:
                continue # not a datastore
            if lun.localDisk:
                ds['local'] = True
            #print(f"uuid {lun.uuid} name {ds['name']} local {ds['local']}")
            self.uuid_datastore[lun.uuid] = ds

    def pathcheck(self, host):
        self.map_uuid_to_datastore(host)
        storage_system = host.configManager.storageSystem
        multipath_info = storage_system.storageDeviceInfo.multipathInfo
        if multipath_info is None:
            self.critical.append("no multipath info")
            return
        for lun in multipath_info.lun:
            ds = self.uuid_datastore.get(lun.id)
            if not ds:
                #print(f"lun {lun.id} is not a datastore")
                continue # not a datastore

            active, standby, dead, other = [], [], [], []
            for path in lun.path:
                #print(f"{path.name} {path.pathState} {path.adapter} {path.transport.__class__.__name__ if path.transport else '?'}")
                if path.pathState == 'active':
                    active.append(path)
                elif path.pathState == 'standby':
                    standby.append(path)
                elif path.pathState == 'dead':
                    dead.append(path)
                else:
                    other.append(path)

            if ds['local']:
                minpath = 1
                prefix = f"{ds['name']} (local)"
            #elif re.search(r'-local\d?$', ds['name']):
            #    minpath = 1
            #    prefix = f"{ds['name']} (local-by-name)"
            else:
                minpath = 2
                prefix = f"{ds['name']}"
            paths = f"{len(active)}+{len(standby)} active/standby"
            if len(dead) > 0:
                paths += f", {len(dead)}"
            if len(other) > 0:
                for path in other:
                    paths += f", 1 {path.pathState}"
            paths += " paths"
            if len(active) + len(standby) < minpath:
                self.critical.append(f"{prefix} NOT REDUNDANT: {paths}")
            elif len(dead) > 0:
                self.critical.append(f"{prefix} DEAD PATH: {paths}")
            elif len(other) > 0:
                self.warning.append(f"{prefix} OTHER STATE: {paths}")
            else:
                self.ok.append(f"{prefix}: {paths}")

    def connect(self):
        if not self.args.site in self.site_conf:
            print(f"site not found: {self.args.site}")
            exit(1)
        host = self.site_conf[self.args.site]['vcenter']
        user = self.site_conf[self.args.site]['vcenter_user']
        passwd = self.site_conf[self.args.site]['vcenter_passwd']

        si = pyVim.connect.SmartConnect(
            host = host,
            user = user,
            pwd = passwd,
            disableSslCertValidation = True
            )
        atexit.register(pyVim.connect.Disconnect, si)
        self.content = si.RetrieveContent()

    def get_obj(self, vimtype, name):
        """
        Return an object by name, if name is None the
        first found object is returned
        """
        obj = None
        container = self.content.viewManager.CreateContainerView(
            self.content.rootFolder, vimtype, True)
        for c in container.view:
            if name:
                if c.name == name:
                    obj = c
                    break
            else:
                obj = c
                break

        return obj

    def status_exit(self):
        message = []
        exitcode = 0
        if len(self.critical) > 0:
            exitcode = 2
            message.append("CRITICAL: "+ " / ".join(self.critical))
        if len(self.warning) > 0:
            if exitcode == 0:
                exitcode = 1
            message.append("WARNING: "+ " / ".join(self.warning))
        if exitcode > 0:
            print(" / ".join(message))
            sys.exit(exitcode)

        print("OK: "+ " / ".join(self.ok))
        sys.exit(exitcode)


if __name__ == "__main__":
    try:
        Main().run()
    except Exception as e:
        print(f"UNKNOWN: {e}")
        print(type(e))
        sys.exit(3)

# vim: set tabstop=4 shiftwidth=4 expandtab smarttab:
