# Copyright (c) 2016-2021 VMware, Inc.  All rights reserved.
# All rights reserved. -- VMware Confidential
"""
Integrate netdumper service with the B2B patching framework
"""
import sys
import os
import shutil
import logging
import pwd
import grp

from patch_specs import PatchContext
from extensions import extend, Hook
from fss_utils import getTargetFSS
from l10n import msgMetadata as _T, localizedString as _
from reporting import getProgressReporter
from vcsa_utils import isDisruptiveUpgrade, getComponentDiscoveryResult
from . import netdumperUtils as utils

logger = logging.getLogger(__name__)
MY_PAYLOAD_DIR = os.path.dirname(__file__)
NETDUMPER_DIR = "/var/core/netdumps"
NETDUMPER_BACKUP_DIR = "/var/core/netdumps_backup"
NETDUMPER_CONFIG_FILE = "/etc/sysconfig/netdumper"
NETDUMPER_DIR_THRESHOLD_GB = 3

# Import patches python directory where all individual python module reside
sys.path.append(os.path.join(MY_PAYLOAD_DIR, "patches"))

# Define all the patches in the increasing order by the version
patches = [
    ("7.0.2.0", "patch1")
    ]

_CURRENT_SOURCE_VERSION_FILE = 'sourceVersion'


def getLatestPatchVersion():
    return patches[-1][0]


@extend(Hook.Discovery)
def discover(ctx):
    '''DiscoveryResult discover(PatchContext sharedCtx) throw UserUpgradeError'''

    if not os.path.exists(NETDUMPER_CONFIG_FILE):
        logger.info("netdumper config file is not available, so patch not "
                    "applicable")
        return None

    replicationConfig = {
        "/etc/sysconfig/netdumper" : None,
        "/etc/vmware/appliance/firewall/vmware-netdumper" : None,
        "/etc/sysconfig/netdumper.replicate" : "/etc/sysconfig/netdumper",
        "/etc/vmware/appliance/firewall/vmware-netdumper.replicate" : "/etc/vmware/appliance/firewall/vmware-netdumper",
        }

    currentVersion = utils.getSourceVersion()
    # Store the current version in the stageDirectory
    with open(getSourceVersionPersistenceLocation(ctx), "w") as f:
        f.write(currentVersion)

    return getComponentDiscoveryResult('netdumper', replicationConfig=replicationConfig)


@extend(Hook.Requirements)
def collectRequirements(ctx):
    '''RequirementsResult collectRequirements(PatchContext sharedCtx)'''
    pass


@extend(Hook.Validation)
def validate(ctx):
    '''ValidationResult validate(PatchContext sharedCtx)'''
    pass


@extend(Hook.Expand)
def expand(ctx):
    ''' Expand hook for netdumper '''

    progressReporter = getProgressReporter()
    progressReporter.updateProgress(0, _(_T("netdumper.expand.begin",
                                            'Start Netdumper expand')))
    logger.info("Starting the expand hook for Netdumper Service")

    if (getTargetFSS("NDU_Limited_Downtime") and
        (not isDisruptiveUpgrade(ctx)) ):
        try:
            netdumper_backup_dir_replicate = '/var/core/netdumps_backup.replicate'

            if os.listdir(NETDUMPER_DIR):
                logger.info("Backing up netdump core directory.")
                os.rename(NETDUMPER_DIR, netdumper_backup_dir_replicate)

                logger.info("Creating netdump core directory.")
                # Create the netdump core directory with the required permissions.
                uid = pwd.getpwnam("netdumper").pw_uid
                gid = grp.getgrnam("netdumper").gr_gid
                os.mkdir(NETDUMPER_DIR, 0o700)
                os.chown(NETDUMPER_DIR, uid, gid)
        except Exception as e:
            logger.error("Expand script failed with error : " + str(e))
            raise

        _doIncrementalPatching(ctx)

    else:
        try:
            logger.info("Backing up netdump core directory.")
            # Remove netdump core backup directory if exists.
            if os.path.exists(NETDUMPER_BACKUP_DIR):
                shutil.rmtree(NETDUMPER_BACKUP_DIR)
            # Rename the netdump core directory to netdump backup directory.
            os.rename(NETDUMPER_DIR, NETDUMPER_BACKUP_DIR)
            logger.info("Creating netdump core directory.")
            # Create the netdump core directory with the required permissions.
            uid = pwd.getpwnam("netdumper").pw_uid
            gid = grp.getgrnam("netdumper").gr_gid
            os.mkdir(NETDUMPER_DIR, 0o700)
            os.chown(NETDUMPER_DIR, uid, gid)
        except Exception as e:
            logger.error("Expand script failed with error : " + str(e))
            raise

    logger.info("Expand hook completed for Netdumper")
    progressReporter.updateProgress(100, _(_T("netdumper.expand.complete",
                                              'Completed Netdumper expand')))


@extend(Hook.Contract)
def contract(ctx):
    ''' Contract hook for Netdumper '''
    pass

@extend(Hook.Revert)
def revert(ctx):
    ''' Revert hook for Netdumper '''
    if getTargetFSS("NDU_Limited_Downtime"):
        logger.info("Starting the revert hook for netdumper.")
        netdumper_replicate_file = '/etc/sysconfig/netdumper.replicate'
        firewall_replicate_file = "/etc/vmware/appliance/firewall/vmware-netdumper.replicate"
        netdumper_backup_dir_replicate = "/var/core/netdumps_backup.replicate"
        if os.path.isfile(firewall_replicate_file):
            os.remove(firewall_replicate_file)
        if os.path.isfile(netdumper_replicate_file):
            os.remove(netdumper_replicate_file)
        if (os.path.isdir(netdumper_backup_dir_replicate) and
            os.listdir(netdumper_backup_dir_replicate) != [] and os.path.isdir(NETDUMPER_BACKUP_DIR)):
            shutil.rmtree(NETDUMPER_BACKUP_DIR)
            os.rename(netdumper_backup_dir_replicate, NETDUMPER_BACKUP_DIR)

        logger.info("Successfully reverted the netdumper patch.")
    else:
        logger.info("FSS is off, skipping revert hook")


def getNetdumpDirectorySize():
    '''
    Get current size of the netdump core directory
    :return:
    '''
    total_size = 0
    for path, dirs, files in os.walk(NETDUMPER_DIR):
        for f in files:
            fp = os.path.join(path, f)
            total_size += os.path.getsize(fp)
    return round(total_size / 1024 / 1024 / 1024)


def _doIncrementalPatching(ctx):
    # Get the current version from the stageDirectory
    with open(getSourceVersionPersistenceLocation(ctx), "r") as f:
        currVersion = f.readline()

    assert(currVersion)
    logger.info('Current version: %s' % (currVersion))

    # NETDUMPER_PROPERTIES file is always overwriten. Reset the source version
    utils.setSourceVersion(currVersion)

    patchCurrentVersion = False
    for ver, modulePath in patches:
        patchCurrentVersion |= currVersion <= ver

        if patchCurrentVersion:
            logger.info('Applying patch %s' % (ver))
            mod = __import__(modulePath)
            mod.doPatching(ctx)
            logger.info('Patch %s applied' % (modulePath))

    # Store the last patched version
    utils.setSourceVersion(ver)


def getSourceVersionPersistenceLocation(ctx):
    return os.path.join(ctx.stageDirectory, _CURRENT_SOURCE_VERSION_FILE)


@extend(Hook.Patch)
def patch(ctx):
    '''void patch(PatchContext sharedCtx) throw UserUpgradeError'''
    progressReporter = getProgressReporter()
    progressReporter.updateProgress(
        0, _(_T("netdumper.patch.begin", 'Starting Netdumper patching')))

    if not getTargetFSS("NDU_Limited_Downtime") or isDisruptiveUpgrade(ctx):
        _doIncrementalPatching(ctx)

    progressReporter.updateProgress(
        100, _(_T("netdumper.patch.complete", 'Patching Netdumper completed')))
