#!/usr/bin/python
#
# Copyright 2009 Red Hat, Inc. and/or its affiliates.
#
# Licensed to you under the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.  See the files README and
# LICENSE_GPL_v2 which accompany this distribution.
#

from __future__ import print_function
import sys

# When using Python 2, we must monkey patch threading module before importing
# any other module.
if sys.version_info[0] == 2:
    import pthreading
    pthreading.monkey_patch()

import os
import getopt
import signal
import getpass
import pwd
import grp
import threading
import logging
import syslog
import resource
import tempfile
from logging import config as lconfig
import warnings

from vdsm import constants
from vdsm import schedule
from vdsm import utils
from vdsm.config import config
from vdsm import libvirtconnection
from vdsm import taskset
from vdsm.profiling import profile

from storage.dispatcher import Dispatcher
from storage.hsm import HSM

from vdsm.infra import sigutils
import vdsm.infra.zombiereaper as zombiereaper
from virt import periodic
import dsaversion

loggerConfFile = constants.P_VDSM_CONF + 'logger.conf'

if config.getboolean('devel', 'python_warnings_enable'):
    warnings.simplefilter("always")
    if hasattr(logging, 'captureWarnings'):
        # Python 2.6 does not have captureWarnings
        logging.captureWarnings(True)


def usage():
    print("Usage:  vdsm [OPTIONS]")
    print("     -h  - Display this help message")


def serve_clients(log):
    cif = None
    irs = None
    scheduler = None
    running = [True]

    def sigtermHandler(signum, frame):
        log.debug("Received signal %s" % signum)
        running[0] = False

    def sigusr1Handler(signum, frame):
        if irs:
            log.debug("Received signal %s" % signum)
            irs.spmStop(
                irs.getConnectedStoragePoolsList()['poollist'][0])

    sigutils.register()
    signal.signal(signal.SIGTERM, sigtermHandler)
    signal.signal(signal.SIGUSR1, sigusr1Handler)
    zombiereaper.registerSignalHandler()

    profile.start()

    libvirtconnection.start_event_loop()

    try:
        if config.getboolean('irs', 'irs_enable'):
            try:
                irs = Dispatcher(HSM())
            except:
                utils.panic("Error initializing IRS")

        scheduler = schedule.Scheduler(name="vdsm.Scheduler",
                                       clock=utils.monotonic_time)
        scheduler.start()

        from clientIF import clientIF  # must import after config is read
        cif = clientIF.getInstance(irs, log, scheduler)

        install_manhole({'irs': irs, 'cif': cif})

        cif.start()
        periodic.start(cif, scheduler)
        try:
            while running[0]:
                sigutils.wait_for_signal()

            profile.stop()
        finally:
            periodic.stop()
            cif.prepareForShutdown()
            scheduler.stop()
    finally:
        libvirtconnection.stop_event_loop(wait=False)


def run(pidfile=None):
    try:
        lconfig.fileConfig(loggerConfFile, disable_existing_loggers=False)
    except RuntimeError as e:
        syslog.syslog('Failed to initialize logging: %s.' % e.message)
        sys.exit(1)

    logging.addLevelName(5, 'TRACE')
    logging.TRACE = 5  # impolite but helpful

    # Used to debug vdsm. on production machines
    # vdsmDebugPlugin.py should not exists
    try:
        import vdsmDebugPlugin
        vdsmDebugPlugin.turnOnDebugPlugin()
    except ImportError:
        # This is OK, it just means the file isn't
        # there and we are not running in debug mode.
        # Any other error is an error in the debug
        # plugin and we would like to print that out.
        pass

    # Used to enable code coverage. On production machines
    # "coverage" should not exists and COVERAGE_PROCESS_START should not be
    # set.
    try:
        import coverage
        coverage.process_startup()
    except ImportError:
        pass

    log = logging.getLogger('vds')
    try:
        logging.root.handlers.append(logging.StreamHandler())
        log.handlers.append(logging.StreamHandler())

        pid = str(os.getpid())
        if pidfile:
            with open(pidfile, 'w') as f:
                f.write(pid + "\n")
            os.chmod(pidfile, 0o664)

        sysname, nodename, release, version, machine = os.uname()
        log.info('(PID: %s) I am the actual vdsm %s %s (%s)',
                 pid, dsaversion.raw_version_revision, nodename, release)

        __set_cpu_affinity()

        serve_clients(log)
    except:
        log.error("Exception raised", exc_info=True)

    log.info("VDSM main thread ended. Waiting for %d other threads..." %
             (threading.activeCount() - 1))
    for t in threading.enumerate():
        if hasattr(t, 'stop'):
            t.stop()
        log.info(str(t))


def parse_args():
    argDict = {}
    opts, args = getopt.getopt(sys.argv[1:], "h", ["help", "pidfile="])
    for o, v in opts:
        o = o.lower()
        if o == "-h" or o == "--help":
            usage()
            sys.exit(0)
        if o == "--pidfile":
            argDict['pidfile'] = v

    if len(args) >= 1:
        usage()
        sys.exit(1)

    return argDict


def install_manhole(locals):
    if not config.getboolean('devel', 'manhole_enable'):
        return

    import manhole

    # locals:             Set the locals in the manhole shell
    # socket_path:        Set to create secure and easy to use manhole socket,
    #                     instead of /tmp/manhole-<vdsm-pid>.
    # daemon_connection:  Enable to ensure that manhole connection thread will
    #                     not block shutdown.
    # patch_fork:         Disable to avoid creation of a manhole thread in the
    #                     child process after fork.
    # sigmask:            Disable to avoid pointless modification of the
    #                     process signal mask if signlfd module is available.
    # redirect_stderr:    Disable since Python prints ignored exepctions to
    #                     stderr.

    path = os.path.join(constants.P_VDSM_RUN, 'vdsmd.manhole')
    manhole.install(locals=locals, socket_path=path, daemon_connection=True,
                    patch_fork=False, sigmask=None, redirect_stderr=False)


def __assertLogPermission():
    if not os.access(constants.P_VDSM_LOG, os.W_OK):
        syslog.syslog("vdsm log directory is not accessible")
        sys.exit(1)

    logfile = constants.P_VDSM_LOG + "/vdsm.log"
    if not os.path.exists(logfile):
        # if file not exist, and vdsm has an access to log directory- continue
        return

    if not os.access(logfile, os.W_OK):
        syslog.syslog("error in accessing vdsm log file")
        sys.exit(1)


def __assertVdsmUser():
    username = getpass.getuser()
    if username != constants.VDSM_USER:
        syslog.syslog("VDSM failed to start: running user is not %s, trying "
                      "to run from user %s" % (constants.VDSM_USER, username))
        sys.exit(1)
    group = grp.getgrnam(constants.VDSM_GROUP)
    if (constants.VDSM_USER not in group.gr_mem) and \
       (pwd.getpwnam(constants.VDSM_USER).pw_gid != group.gr_gid):
        syslog.syslog("VDSM failed to start: vdsm user is not in KVM group")
        sys.exit(1)


def __assertSudoerPermissions():
    rc = 1
    with tempfile.NamedTemporaryFile() as dst:
        # This cmd choice is arbitrary to validate that sudoes.d/50_vdsm file
        # is read properly
        cmd = [constants.EXT_CHOWN, "%s:%s" %
               (constants.VDSM_USER, constants.QEMU_PROCESS_GROUP), dst.name]
        rc, _, stderr = utils.execCmd(cmd, sudo=True)

    if rc != 0:
        syslog.syslog("vdsm user could not manage to run sudo operation: "
                      "(stderr: %s). Verify sudoer rules configuration" %
                      (stderr))
        sys.exit(1)


def __set_cpu_affinity():
    cpu_affinity = config.get('vars', 'cpu_affinity')
    if cpu_affinity != "":
        cpu_set = frozenset(int(cpu.strip())
                            for cpu in cpu_affinity.split(","))

        log = logging.getLogger('vds')
        log.info('VDSM will run with cpu affinity: %s', cpu_set)

        # too early to use the facilities from caps.py
        if os.sysconf('SC_NPROCESSORS_ONLN') == 1:
            log.warning('Only one cpu detected: affinity disabled')
            return

        taskset.set(os.getpid(), cpu_set, all_tasks=True)


if __name__ == '__main__':
    __assertVdsmUser()
    __assertLogPermission()
    __assertSudoerPermissions()

    if not config.getboolean('vars', 'core_dump_enable'):
        resource.setrlimit(resource.RLIMIT_CORE, (0, 0))

    if os.getsid(0) != os.getpid():
        # Modern init systems such as systemd can provide us a clean daemon
        # environment. So we setpgrp only when run by traditional init system
        # that does not prepare the environment.
        os.setpgrp()
    argDict = parse_args()
    run(**argDict)
