# Copyright (c) 2017-2022 VMware, Inc.  All rights reserved.
# -- VMware Confidential

"""VCDB VMODL In-Place (B2B) Upgrade

This patch extension upgrades the XMLs of VMODL objects which are serialized in
VCDB. Those objects include events, alarms, host information, VSAN health data
and possibly more.
"""

import logging
import math
import os
import shutil
import sys

# In-Place Upgrade hooking imports
import extensions  # pylint: disable=import-error
import l10n  # pylint: disable=import-error
import patch_specs  # pylint: disable=import-error
import vcsa_utils  # pylint: disable=import-error
from fss_utils import getTargetFSS  # pylint: disable=E0401

currentDirectory = os.path.dirname(os.path.realpath(__file__))

# util, vcdb
sys.path.insert(0, currentDirectory)

# Crypto, six used in vcdb.py.
sys.path.insert(0, os.path.join(currentDirectory,
                                "site-packages"))

logger = logging.getLogger(__name__)
logger.info("sys.path: %s.", sys.path)

NDU_LIMITED_DOWNTIME_FSS = "NDU_Limited_Downtime"
DropDevVMODLsFSS = "DROP_DEV_VMODL_VCDB"


@extensions.extend(extensions.Hook.Discovery)
def discover(_):
    """DiscoveryResult discover(PatchContext ctx) throw UserError
    """

    logger.info("VCDB VMODL Extension Discovery Hook")

    preservePyVmomi()
    return vcsa_utils.getComponentDiscoveryResult(
        "dbconfig",
        displayName=l10n.localizedString(
            l10n.msgMetadata("vcdb_vmodl.b2b.patch.name",
                             "VMware vCenter VCDB VMODL")),
        componentId="vcdb_vmodl_patcher",
        patchServices=[])


def preservePyVmomi():
    """
    We need to preserve the old pyVmomi before the RPM installation or
    the migration to the new machine..
    """

    oldPyVmomi = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                              "oldPyVmomi")

    logger.info("Preserving existing pyVmomi.")

    if os.path.exists(oldPyVmomi):
        logger.info("Removing the already existing '%s'", oldPyVmomi)
        shutil.rmtree(oldPyVmomi)

    logger.info("Preserving the old pyVmomi directory '%s' to '%s'.",
                os.environ["VMWARE_PYTHON_PATH"], oldPyVmomi)
    shutil.copytree(os.environ["VMWARE_PYTHON_PATH"], oldPyVmomi)

    vsanmgmt = 'vsanmgmtObjects.py'  # @see PR 1904479, PR 1790123
    shutil.copyfile('/usr/lib/vmware-vpx/vsan-health/%s' % vsanmgmt,
                    os.path.join(oldPyVmomi, 'pyVmomi', vsanmgmt))

    logger.info("Preserving existing pyVmomi finished")


@extensions.extend(extensions.Hook.Requirements)
def collectRequirements(_):
    """RequirementsResult collectRequirements(PatchContext ctx)

    Note the DBLOG heuristics. It approximates satisfactory the following
    measurements from https://jira.eng.vmware.com/browse/VMACORE-141.

    SEAT (MB) |  DBLOG Inc (MB)
    10717     |  13664
    21024     |  35936
    30025     |  54177
    40196     |  59233


    [int((14 + 38 * log(x / 10000.0)) * 1000)
        for x in [300, 1000, 3000, 10717, 21024, 30025, 40196]]

    [-119249, -73498, -31750, 16631, 42237, 55778, 66864]

    Note that for less than 10 GB of events this approximation is wrong.
    Assume that DBLOG will double in this case.

    Note that for more than 40 GB of events this approximation is most likely
    wrong. However, currently we don't have any data to back this claim up.
    """

    import util

    logger.info("VCDB VMODL Extension Requirements Hook")

    partitions = [
        "/storage/core",  # Contains the temporary update.sql.
        "/storage/db",   # Contains the core VCDB tables.
        "/storage/dblog",  # Contains VCDB transaction log.
        "/storage/seat",   # Contains the VPXD events.
    ]

    usage = util.GetDiskUsage(partitions)

    # /storage/core currently contains the temporary update SQL.
    #
    # The temporary file contains all the update statements.
    # Ignoring usage["/storage/db"] as it should be negligable to the Events.

    # The core delta is a function of the seat size.
    coreIncrease = usage["/storage/seat"]

    # By analogy with seat (see below).
    dbIncrease = usage["/storage/db"]

    # Heuristics.
    dbLogIncrease = max(
        usage["/storage/dblog"],
        int((14 + 38 * math.log(usage["/storage/dblog"] / 10000.0)) * 1000))

    # Measurements from the Jira item VMACORE-141.
    seatIncrease = usage["/storage/seat"]  # We expect the seat to double.

    diskRequirements = {
        "/storage/core": "{}".format(coreIncrease),
        "/storage/db": "{}".format(dbIncrease),
        "/storage/dblog": "{}".format(dbLogIncrease),
        "/storage/seat": "{}".format(seatIncrease),
    }

    logger.info("Required Disk Space (MB): %s", diskRequirements)

    questions = []

    requirements = patch_specs.Requirements(requiredDiskSpace=diskRequirements,
                                            questions=questions,
                                            rebootRequired=False)
    patchInfo = patch_specs.PatchInfo()
    mismatches = []

    result = patch_specs.RequirementsResult(requirements=requirements,
                                            patchInfo=patchInfo,
                                            mismatches=mismatches)

    return result


@extensions.extend(extensions.Hook.SwitchOver)
def switchover(ctx):
    """void switchover(Switchover ctx) throw UserError'''

    This function only invokes the patch logic when it is NDU.
    """

    if getTargetFSS(NDU_LIMITED_DOWNTIME_FSS):
        patchAndMeasure(ctx)
    else:
        logger.info("FSS is off, all work is done in Patch hook")


@extensions.extend(extensions.Hook.Patch)
def patch(ctx):
    """void patch(PatchContext ctx) throw UserError

    The patch logic is designed to be run separately from the B2B framework.
    validating the transition to Versionless (version.x) VMODL in VCDB.
    """
    fssAllowsExecution = not getTargetFSS(NDU_LIMITED_DOWNTIME_FSS)
    isInplace = vcsa_utils.isDisruptiveUpgrade(ctx)

    if isInplace or fssAllowsExecution:
        patchAndMeasure(ctx)
    else:
        logger.info("All the work is done in the switchover hook.")


def patchAndMeasure(ctx):
    """
    After the patching is done, a verification is performed and all the VMODL
    XMLs that do *not* contain the destination stable version are logged.

    This last step is useful on its own. The reason to add it here is for
    validating the transition to Versionless (version.x) VMODL in VCDB.
    """
    if not getTargetFSS(DropDevVMODLsFSS):
        logger.info("Skipping the VMODL in VCDB cleanup scripts.")
        return

    import multiVmomi
    import vcdb

    vcdb.patchAndMeasure(multiVmomi.ImportSrcPyVmomi,
                         multiVmomi.ImportDstPyVmomi,
                         logger,
                         ctx.stageDirectory)
