"""
Wrapper for Storage Connector API when running outside a HANA environment, e.g. during installation.
"""

import sys
import os
import types
import ConfigParser
import stat

sys.dont_write_bytecode = True

def main():
    configPath  = None
    clientLib   = None
    module      = None
    sid         = None
    datapath    = None
    logpath     = None
    tracelvl    = "WARNING"
    method      = "attach"
    partition   = None
    checkRoot   = False

    if len(sys.argv) <= 1 or "-h" in sys.argv or "--help" in sys.argv:
        print "Usage:"
        print "  python hdbmount.py --sid=<sid>|--configFiles=<path> --datapath=<path> --logpath=<path> --partition=<storage partition> [--detach] [--tracelevel=DEBUG|INFO|WARNING|ERROR|FATAL]"
        print
        print "    --sid              the SID of the installation (needed for determining the standard config file path; instead of --configFiles option)"
        print "    --configFiles      path to config files (instead of --sid option)"
        print "    --datapath         path to mntXXXXX directory of data volumes"
        print "    --logpath          path to mntXXXXX directory of log volumes"
        print "    --partition        storage partition number"
        print "    --detach           detach storage devices, optional"
        print "    --tracelevel       optional trace level, default = INFO"
        print
        print "  python hdbmount.py --sudoers --configFiles=<path>"
        print
        print "    --configFiles      path to config files"
        print "    --checkOwnership   check root ownership of configured storage connector"
        print
        print "  python hdbmount.py --listpartitions --configFiles=<path>"
        print
        print "    --configFiles      path to config files"
        exit(1)

    for p in sys.argv:
        if p.startswith("--sid="):
            sid = p[6:]
        if p.startswith("--configFiles="):
            configPath = p[14:]
        if p.startswith("--datapath="):
            datapath = p[11:]
        if p.startswith("--logpath="):
            logpath = p[10:]
        if p.startswith("--partition="):
            partition = p[12:]
        if p.startswith("--tracelevel="):
            tracelvl = p[13:]
        if p == "--detach":
            method = "detach"
        if p == "--sudoers":
            method = "sudoers"
        if p == "--listpartitions":
            method = "listpartitions"
        if p == "--checkOwnership":
            checkRoot = True

    if method == "sudoers" and configPath == None:
        print "--configFiles must be specified when using --sudoers option"
        exit(1)

    if method == "listpartitions" and configPath == None:
        print "--configFiles must be specified when using --listpartitions option"
        exit(1)

    if checkRoot and not method == "sudoers":
        print "--checkOwnership only allowed in combination with --sudoers option"
        exit(1)

    if configPath == None:
        configPath = "/usr/sap/%s/SYS/global/hdb/custom/config" % sid

    mountAction = True
    if method == "sudoers" or method == "listpartitions":
        mountAction = False
    elif sid == None or datapath == None or logpath == None or partition == None:
        print "--sid, --partition, --datapath and --logpath must be specified"
        exit(1)

    # put path of hdbmount to PYTHONPATH
    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
    sys.path.insert(0, os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), "hdb_ha"))
    sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(sys.argv[0]))))

    config = ConfigParser.ConfigParser()
    config.read(os.path.join(configPath, "global.ini"))
    try:
        syspath = config.get("storage", "ha_provider_path")
        if ":" in syspath:
            sp = syspath.split(":")
            for s in sp:
                sys.path.insert(0, s)
        else:
            sys.path.insert(0, syspath)
    except:
        pass

    if mountAction:
        print "PYTHONPATH=%s" % sys.path

    clientLib = config.get("storage", "ha_provider")
    if clientLib == "":
        print "no ha provider defined: missing global.ini/[storage]/ha_proider parameter"
        exit(1)

    if clientLib.endswith(".py"):
         clientLib = os.path.splitext(clientLib)[0]

    # import storage connector
    def importInDirectory(self):
        if "." in self.name:
            self.name = self.name.split(".")[1]
            return True
        else:
            return False

    importStrategies = [StorageLoadStrategy(clientLib,mountAction), StorageLoadStrategy(clientLib,mountAction,importInDirectory)]
    for strat in importStrategies:
        try:
            module = strat.tryImport()
            if module: break
        except Exception as e:
            strat.setError(str(e))

    if not module:
        StorageLoadStrategy.showErrorsAndExit("import",importStrategies)

    # load storage connector
    def loadInDirectory(self):
        if "." in self.name:
            self.name = self.name.split(".")[-1]
            return True
        else:
            return False

    loadStrategies = [StorageLoadStrategy(clientLib,mountAction), StorageLoadStrategy(clientLib,mountAction,loadInDirectory)]
    for strat in loadStrategies:
        try:
            clClass, providerFile = strat.tryLoad(module)
            if clClass:
                break
        except Exception as e:
            strat.setError(str(e))

    if not clClass:
        StorageLoadStrategy.showErrorsAndExit("load",loadStrategies)

    if method == "sudoers":
        if checkRoot:
            if providerFile.endswith(".pyc"):
                print "Error: Python cache file %s has to be deleted before execution" % providerFile
                exit(1)

            uid = os.stat(providerFile).st_uid
            gid = os.stat(providerFile).st_gid

            if not uid == 0 or not gid == 0:
                print "Error: File %s not owned by root (User ID=%d, Group ID=%d)" % (providerFile, uid, gid)
                exit(1)

            perms = os.stat(providerFile).st_mode
            if perms & stat.S_IWOTH:
                print "Error: File %s is writable by everyone" % providerFile
                exit(1)

        try:
            sudoersEntry = clClass.sudoers() # call staticmethod sudoers()
            print sudoersEntry.strip()
            exit(0)
        except Exception as e:
            print "could not find static method sudoers() in",str(clClass),":",str(e)
            exit(1)

    try:
        apiVersion = int(clClass.apiVersion)
    except:
        apiVersion = 1

    if mountAction:
        print "apiVersion = %s" % apiVersion
        print "calling Storage Connector ..."

    if apiVersion >= 2:
        cl = clClass(method, configPath, sid, datapath, logpath, tracelvl, partition)
    else:
        # skip mounting of too old scripts
        exit(0)

    if mountAction:
        print "... done"

# Import/Load storage connector with different search strategies. Own strategies may be supplied with parameter "prepareFunc"
class StorageLoadStrategy:
    errorText = ""
    verbose = False
    def __init__(self, storageName, verboseMode, prepareFunc=None):
        self.name = storageName
        self.verbose = verboseMode
        if prepareFunc:
            self.prepareFunc = types.MethodType(prepareFunc, self, self.__class__)

    def prepareFunc(self):
        return True

    def setError(self, text):
        self.errorText = text

    def tryImport(self):
        if self.prepareFunc():
            if self.verbose: print "trying: __import__(%s)" % (self.name)
            return __import__(self.name, globals(), locals())

    def tryLoad(self, module):
        if self.prepareFunc():
            try:
                if self.verbose: "trying: getattr(getattr(%s, '%s'), '%s')" % (module, self.name, self.name)
                storageClass = getattr(getattr(module, self.name), self.name)
                return (storageClass, getattr(module, self.name).__file__)
            except:
                if self.verbose: print "trying: getattr(%s, '%s')" % (module, self.name)
                return (getattr(module, self.name), module.__file__)

    @staticmethod
    def showErrorsAndExit(action, strategies):
        for strat in strategies:
            if len(strat.errorText)>0: print action, "attempt failed:", strat.errorText
        print "please check loadability of your Storage Connector"
        exit(1)


if __name__ == '__main__':
    main()
