#!/usr/bin/python
# Script to setup basic vdsm environment and register the VDSM with VDC.
# Input: none.
# Output: none.
#
# Steps to perform: Initiate Certificate Initalization
#   a. Find menagement bridge and rename it to MGT_BRIDGE_NAME.
#   b. Create .ssh directory and fetch authorized_keys
#   c. Call VDC registration.
#   d. Set time according to rhev-m time.

import errno
import sys
import getopt
import os
import socket
import httplib
import time
import logging
import logging.config
import urllib
import shutil
import ssl
import tempfile
from config import config
import deployUtil
import createDaemon
from deployUtil import MGT_BRIDGE_NAME

from ConfigParser import NoOptionError
from ovirtnode import ovirtfunctions

TICKET_RETRIES=3
DEFAULT_CONFIG_FILE="/etc/vdsm-reg/vdsm-reg.conf"
VDSM_CONF="/etc/vdsm/vdsm.conf"
SCRIPT_NAME_SAVE="vdsm-store-net-config"

class Setup:
    """
    Makes sure that vdsmd has its Certificate in place
    """
    def __init__(self, ticket=""):
        logging.debug("__init__ begin.")
        self.registered = False
        self.fInitOK = True
        self.vdcURL = "None"
        self.vdcName = config.get('vars', 'vdc_host_name')

        try:
            self.cfg_fprint = config.get('vars', 'fingerprint')
        except NoOptionError:
            self.cfg_fprint = "None"

        _, _, self.engineWebCACert = deployUtil.certPaths('')

        if self.vdcName != "None":
            try: self.vdcURL = socket.gethostbyname(self.vdcName)
            except: self.vdcURL = "None"
        else:
            self.vdcURL = config.get('vars', 'vdc_host_ip')

        self.vdsmDir = config.get('vars', 'vdsm_dir')
        if self.vdcURL != "None":
            self.ovirtURL = deployUtil.getMGTIP(self.vdsmDir, self.vdcName)
            self.ovirtName = socket.gethostname()
            self.ovirtUID = deployUtil.getHostID()
        else:
            self.ovirtURL = "None"
            self.ovirtName = "None"
            self.ovirtUID = "None"

        self.vdcPORT = config.get('vars', 'vdc_host_port')
        self.vdcURI = config.get('vars', 'vdc_reg_uri')
        self.vdcRegPort = config.get('vars', 'vdc_reg_port')
        self.ticket = ticket
        self.VDCTime = None
        logging.debug("Setup::__init__ vars: %s", self.__dict__)

    def validateData(self):
        logging.debug("validate start")
        fReturn = True

        if self.ovirtURL == None or self.ovirtURL == "" or self.ovirtURL == "None" or \
           self.ovirtName  == None or self.ovirtName == "" or self.ovirtName == "None":
            fReturn = False

        logging.debug("validate end. return: %s", fReturn)
        return fReturn

    def renameBridge(self):
        """
            Rename oVirt-node default bridge to MGT_BRIDGE_NAME.
        """
        logging.debug("renameBridge begin.")
        fReturn = True

        #Rename existing bridge
        fReturn = deployUtil.makeBridge(self.vdcName, self.vdsmDir)
        if not fReturn:
            logging.error("renameBridge Failed to rename existing bridge!")

        #Persist changes
        if fReturn:
            try:
                out, err, ret = deployUtil._logExec([os.path.join(self.vdsmDir, SCRIPT_NAME_SAVE)])
                if ret:
                    fReturn = False
                    logging.error("renameBridge Failed to persist %s "
                            "bridge changes. out=%s\nerr=%s\nret=%d",
                            MGT_BRIDGE_NAME,
                            out, err,
                            ret)
            except:
                fReturn = False
                logging.error("renameBridge Failed to persist bridge changes. "
                        "out=%s\nerr=%s\nret=%d", out, err, ret)

        #Fix file permissions to relevant mask
        if fReturn:
            try:
                os.chmod("/config/etc/sysconfig/network-scripts/ifcfg-" + MGT_BRIDGE_NAME, 0o644)
            except:
                fReturn = False
                logging.error("renameBridge: failed to chmod bridge file")

        logging.debug("renameBridge return.")
        return fReturn

    def registerVDS(self):
        logging.debug("registerVDS begin.")
        params = {
                'vds_ip': self.ovirtURL,
                'vds_name': self.ovirtName,
                'vds_unique_id': self.ovirtUID,
                'port': self.vdcRegPort,
                '__VIEWSTATE': '',
                'ticket': self.ticket if self.ticket else "",
        }
        strFullURI = self.vdcURI + '?' + urllib.urlencode(params)
        logging.debug("registerVDS URI= %s\n", strFullURI)
        if self.ovirtUID == "None" and "localhost.localdomain" in self.ovirtName:
            logging.warn("WARNING! registering RHEV-H with no UUID and no unique host-name!")

        fReturn = True
        res = None
        nTimeout = int(config.get('vars', 'test_socket_timeout'))
        old_timeout = socket.getdefaulttimeout()
        socket.setdefaulttimeout(nTimeout)
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        try:
            sock.connect((self.vdcURL, int(self.vdcPORT)))
            conn = httplib.HTTPSConnection(self.vdcURL + ":" + self.vdcPORT)
            conn.sock = ssl.wrap_socket(sock)
            conn.request("GET", strFullURI)
            res = conn.getresponse()
        except:
           logging.debug("registerVDS failed in HTTPS. Retrying using HTTP.")
           try:
                conn = None
                conn = httplib.HTTPConnection(self.vdcURL + ":" + self.vdcPORT)
                conn.request("GET", strFullURI)
                res = conn.getresponse()
                logging.debug("registerVDS succeeded using HTTP.")
           except:
                fReturn = False
                logging.error("registerVDS failed using HTTP!", exc_info=True)

        else:
            logging.debug("registerVDS status: %d reason: %s", res.status,
                    res.reason)

        if res == None or res.status != 200:
            if conn != None: conn.close()
            fReturn = False

        if fReturn:
            try:
                try:
                    self.VDCTime = res.read()
                    logging.debug("registerVDS time read: %s", self.VDCTime)
                except:
                    fReturn = False
                    logging.error("registerVDS time read failed",
                            exc_info=True)
            finally:
                if conn != None: conn.close()

        socket.setdefaulttimeout(old_timeout)
        logging.debug("registerVDS end.")
        return fReturn

    def _compare_fingerprint(self, ca_path):
        """ Compare fingerprint provided configration against
        provided certificate.

        Args:
        ca_path -- Path to CA certificate

        Returns:
        True -- Fingerprint matched
        False -- Fingerprint didn't match
        """
        ret = False

        if self.cfg_fprint == "None":
            ret = True
        else:
            if not os.path.exists(ca_path):
                logging.error(
                    "Fingerprint requested but no certificate"
                )
            else:
                engine_fprint = deployUtil.generateFingerPrint(ca_path)
                ret = self.cfg_fprint.upper() == engine_fprint

                if not ret:
                    logging.error(
                        "Fingerprint differs from Engine\n"
                        "vdsm-reg.conf fingerprint [%s]\n"
                        "Engine fingerprint [%s]",
                        self.cfg_fprint.upper(),
                        engine_fprint
                    )

        return ret

    def execute(self):
        fOK = True
        fOKNow = True
        logging.debug("execute start.")
        self.registered = False

        if deployUtil.preventDuplicate():
            logging.debug("execute: found existing management bridge. Skipping rename.")
        else:
            fOK = self.renameBridge()
            logging.debug("execute: after renameBridge: %s", fOK)

        #
        # make sure we have correct
        # certificate at trust store
        # delete it if not.
        #
        if fOK and not self._compare_fingerprint(self.engineWebCACert) and \
                    os.path.exists(self.engineWebCACert):
                ovirtfunctions.ovirt_safe_delete_config(
                    self.engineWebCACert
                )
                os.unlink(self.engineWebCACert)

        #
        # Attempt to download certificate if not available.
        #
        if fOK and not os.path.exists(self.engineWebCACert):
            fd_tmp, file_tmp = tempfile.mkstemp()
            os.close(fd_tmp)

            try:
                #
                # this will only succeed if certificate
                # available via the ssl session
                #
                if not deployUtil.getRhevmCert(
                    IP=self.vdcName,
                    port=self.vdcPORT,
                    output=file_tmp
                ):
                    logging.info(
                        "Cannot download Engine Web CA certificate file: "
                        "%s:%s",
                        self.vdcName,
                        self.vdcPORT
                    )
                    os.unlink(file_tmp)

                #
                # if the fingerprint is incorrect, there
                # is no reason to re-try, just exit.
                #
                if fOK and not self._compare_fingerprint(file_tmp):
                    fOK = False
                    sys.exit(1)

                #
                # if we have certificate and all ok, then
                # we move to trust store.
                #
                if fOK and os.path.exists(file_tmp):
                    shutil.move(file_tmp, self.engineWebCACert)
                    ovirtfunctions.ovirt_store_config(
                        self.engineWebCACert
                    )

            finally:
                if os.path.exists(file_tmp):
                    os.unlink(file_tmp)

        if fOK:
            strKey = deployUtil.getAuthKeysFile(self.vdcURL, self.vdcPORT)
            if strKey is not None:
                fOKNow = deployUtil.handleSSHKey(strKey)
            else:
                fOKNow = False
            fOK = fOK and fOKNow
            logging.debug("execute: after getAuthKeysFile: %s", fOK)

        if fOK:
            fOKNow = self.registerVDS()
            fOK = fOK and fOKNow
            logging.debug("execute: after registerVDS: %s", fOK)

        if fOK:
            self.registered = True

        logging.debug("Registration status: %s", self.registered)

        # Utility settings. This will not fail the registration process.
        if self.registered == True:
            fOK = (fOK and deployUtil.setHostTime(self.VDCTime))
            if fOK:
                logging.debug("Node time in sync with RHSC server")
            else:
                logging.warning("Node time failed to sync with RHSC server. This may cause problems !")


def _createUpgradeDir(upgradeDir):
    if not os.path.exists(upgradeDir):
        try:
            os.makedirs(upgradeDir)
            os.chmod(upgradeDir, 0o755)
        except OSError as err:
            if err.errno != errno.EEXIST:
                logging.error(
                    "can not create vdsm upgrade dir: %s" % upgradeDir
                )


def run(confFile, daemonize):
    import random

    registered = False
    pidfile = False

    config.read([confFile])
    ticket = config.get('vars', 'ticket')
    ticketRetries = TICKET_RETRIES if ticket else 0

    vdsmDir = config.get('vars', 'vdsm_dir')
    sys.path.append(vdsmDir)

    pidfile = config.get('vars', 'pidfile')
    sleepTime = float(config.get('vars', 'reg_req_interval'))

    try:
        if daemonize:
            createDaemon.createDaemon()

        loggerConf = config.get('vars', 'logger_conf')
        logging.config.fileConfig(loggerConf, disable_existing_loggers=False)
        log = logging.getLogger('')

        if daemonize:
            log = logging.getLogger('vdsRegistrator')
        if not daemonize:
            log.handlers.append(logging.StreamHandler())

        _createUpgradeDir(
            os.path.dirname(config.get('vars', 'upgrade_iso_file')))

        log.info("After daemonize - My pid is %d", os.getpid())
        file(pidfile, 'w').write(str(os.getpid()) + "\n")
        os.chmod(pidfile, 0o664)

        itr = 0
        while daemonize and not registered:
            oSetup = Setup(ticket=ticket)
            if oSetup.validateData():
                oSetup.execute()
            if ticketRetries:
                ticketRetries = ticketRetries - 1
            registered = oSetup.registered
            oSetup = None
            if ticket:
                if registered or ticketRetries is 0:
                    config.set('vars', 'ticket', '')
                    config.write(file(confFile, 'w+'))
                    deployUtil.ovirtfunctions.ovirt_store_config(confFile)
                    ticket = ""
            itr += 1
            nRandom = random.randint(1, 5)
            if not registered:
                # wait random time, so multiple machines access randomly.
                time.sleep(sleepTime + nRandom)

            log.debug(
                "Total retry count: %d, waited: %d seconds.",
                itr,
                sleepTime+nRandom
            )
        log.info("Exiting ....")
    finally:
        if pidfile and os.path.exists(pidfile):
            os.unlink(pidfile)


def usage():
    print "Usage: %s [-c <config_file>]"
    print "    -c         - configuration file"
    print "    -l         - run local (= no daemon)"
    print "    -h,--help  - prints this usage"

if __name__ == "__main__":
    config_file = DEFAULT_CONFIG_FILE
    daemonize = True
    try:
        opts,args = getopt.getopt(sys.argv[1:], "hc:l",["help"])
        for o,v in opts:
            if o == "-h" or o == "--help":
                usage()
                sys.exit(0)
            elif o == "-c":
                config_file = v
                if not os.path.exists(config_file):
                    print "ERROR: file %s does not exist"%(config_file)
                    usage()
                    sys.exit(1)
            elif o == "-l":
                daemonize = False
    except getopt.GetoptError as e:
        print "ERROR: '%s'"%(e.msg)
        usage()
        sys.exit(1)
    run(config_file, daemonize)
