#!/usr/bin/python
# Chopu NFS upgrade script

import logging
import os
import subprocess
import sys
import syslog
import time
import ctypes

from isi.app.lib import safelockfile

IFS_BACKUP_PATH = "/ifs/.ifsvar/tmp"
LOG_FILE = os.path.join(IFS_BACKUP_PATH, "chopu-nfs-upgrade.log")
NFS_UPGRADE_LOCKFILE = os.path.join(IFS_BACKUP_PATH, "chopu-nfs-upgraded")
ISI_CLI = 'isi_classic '

# sysctl.conf defines
SYSCTL_CONF_PATH = "/etc/mcp/override"
SYSCTL_CONF_FILE = "sysctl.conf"
SYSCTL_CONF = os.path.join(SYSCTL_CONF_PATH, SYSCTL_CONF_FILE)
SYSCTL_CONF_SAVED = os.path.join(IFS_BACKUP_PATH, SYSCTL_CONF_FILE)

# nfs_default.xml defines
NFS_DEFAULT_PATH = "/ifs/.ifsvar/modules/nfs"
NFS_DEFAULT_FILE = "nfs_default.xml"
NFS_DEFAULT = os.path.join(NFS_DEFAULT_PATH, NFS_DEFAULT_FILE)
NFS_DEFAULT_SAVED = os.path.join(IFS_BACKUP_PATH, NFS_DEFAULT_FILE)
NFS_UPGRADED_FILE = os.path.join(NFS_DEFAULT_PATH, "chopu-nfs-upgrade-complete")


logger = logging.getLogger("chopu_nfs_upgrade")

#
# Copy @src to @dst, return True or False, log errors
#
def copy_file(src, dst):
    if not os.path.exists(src):
        logger.error("%s: Unable to copy %s to %s, file does not exist" % (time.ctime(), src, dst))
        return False
    if not os.path.isfile(src):
        logger.error("%s: Unable to copy %s to %s, source is not a file" % (time.ctime(), src, dst))
        return False

    try:
        infile = open(src, 'r')
    except Exception, e:
        logger.error("%s: Unable to copy %s to %s, %s" % (time.ctime(), src, dst, str(e)))
        return False
        
    try:
        outfile = open(dst, 'w')
    except Exception, e:
        logger.error("%s: Unable to copy %s to %s, %s" % (time.ctime(), src, dst, str(e)))
        infile.close()
        return False

    for line in infile.readlines():
        outfile.write(line),

    outfile.close()
    infile.close()
    return True


#
# Attempt to restore the configuration files to their previous state (so that the
# upgrade could be reattempted. Log errors, return True or False.
#
def restore_configuration():
    was_successful = True
    if os.path.exists(NFS_DEFAULT) and os.path.exists(NFS_DEFAULT_SAVED):
        os.remove(NFS_DEFAULT)
        if not copy_file(NFS_DEFAULT_SAVED, NFS_DEFAULT):
            was_successful = False
        else:
            os.remove(NFS_DEFAULT_SAVED)

    if os.path.exists(SYSCTL_CONF) and os.path.exists(SYSCTL_CONF_SAVED):
        os.remove(SYSCTL_CONF)
        if not copy_file(SYSCTL_CONF_SAVED, SYSCTL_CONF):
            was_successful = False
        else:
            os.remove(SYSCTL_CONF_SAVED)

    return was_successful


#
# Attempt to save the configuration files that will be modified to recover from
# a failed upgrade. Log errors, return True or False.
#
def save_configuration():
    was_successful = True
    if os.path.exists(NFS_DEFAULT):
        if not copy_file(NFS_DEFAULT, NFS_DEFAULT_SAVED):
            was_successful = False
        else:
            os.chmod(NFS_DEFAULT_SAVED, 0640)

    if os.path.exists(SYSCTL_CONF):
        if not copy_file(SYSCTL_CONF, SYSCTL_CONF_SAVED):
            was_successful = False

    return was_successful

def _invalid_sysctl(outfile, line):
    outfile.write('# OneFS 6.5 invalid sysctl\n')
    outfile.write('#'+line+'\n')

def sysctl_upgrade():
    scriptname = 'chopu-nfs-upgrade'

    if not os.path.isfile(SYSCTL_CONF):
        logger.info("%s: File %s does not exist, no sysctl upgrade" % (time.ctime(), SYSCTL_CONF))
        return

    try:
        infile = open('/etc/mcp/override/sysctl.conf','r')
    except:
        logger.info("%s: NFS upgrade did not detect an /etc/mcp/override/sysctl.conf file" % scriptname)
        return

    try:
        outfile = open('/etc/mcp/override/sysctl.conf.upgrade','w')
    except:
        infile.close()
        raise RuntimeError('%s: NFS upgrade could not create sysctl.conf.upgrade' % scriptname)

    try:
        cmdfile = open('/etc/mcp/override/sysctl.conf.upgradecmd','w')
    except:
        outfile.close()
        infile.close()
        raise RuntimeError('%s: NFS upgrade could not create /etc/mcp/override/sysctl.conf.upgradecmd' % scriptname)

    changes = 0
    for line in infile.readlines():
        if len(line) > 0 and line[-1] == '\n':
            line = line[:-1]
        
        fullline = line
        try:
            line = line[0:line.index('#')]
        except ValueError:
            pass

        if line[0:10] != 'vfs.nfsrv.':
            outfile.write(fullline+'\n'),
            continue

        try:
            sysctl, value = line.split('=',2)
        except ValueError:
            _invalid_sysctl(outfile, fullline)
            continue
        line = fullline

        if sysctl == 'vfs.nfsrv.async':
            try:
                value = long(value)
            except ValueError:
                _invalid_sysctl(outfile, line)
                continue
            outfile.write('# OneFS 6.5 moved to export option\n')
            if value == 0:
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --commit-asynchronous=false\n')
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --setattr-asynchronous=false\n')
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --write-unstable-action=unstable\n')
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --write-datasync-action=datasync\n')
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --write-filesync-action=filesync\n')
            else:
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --commit-asynchronous=true\n')
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --setattr-asynchronous=true\n')
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --write-unstable-action=unstable\n')
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --write-datasync-action=unstable\n')
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --write-filesync-action=unstable\n')
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.do_32bit_fileid':
            try:
                value = long(value)
            except ValueError:
                _invalid_sysctl(outfile, line)
                continue
            outfile.write('# OneFS 6.5 moved to export option\n')
            if value == 0:
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --return-32bit-file-ids=false\n')
            else:
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --return-32bit-file-ids=true\n')
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.do_readdirplus':
            try:
                value = long(value)
            except ValueError:
                _invalid_sysctl(outfile, line)
                continue
            outfile.write('# OneFS 6.5 moved to export option\n')
            if value == 0:
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --readdirplus-enable=false\n')
            else:
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --readdirplus-enable=true\n')
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.macos_maxfsize':
            try:
                value = long(value)
            except ValueError:
                _invalid_sysctl(outfile, line)
                continue
            outfile.write('# OneFS 6.5 moved to export option\n')
            if value != 0:
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --maxfilesize=%d\n' % ctypes.c_uint64(-1).value)
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.max_rsize':
            outfile.write(line+'\n')
            continue
        if sysctl == 'vfs.nfsrv.max_wsize':
            outfile.write(line+'\n')
            continue
        if sysctl == 'vfs.nfsrv.readdirplus_prefetch':
            outfile.write('# OneFS 6.5 moved to export option\n')
            cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --readdirplus-prefetch=%s\n' % value)
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.realign_count':
            outfile.write('# OneFS 6.5 obsolete sysctl\n')
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.realign_test':
            outfile.write('# OneFS 6.5 obsolete sysctl\n')
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.request_space_high':
            outfile.write(line+'\n')
            continue
        if sysctl == 'vfs.nfsrv.request_space_low':
            outfile.write(line+'\n')
            continue
        if sysctl == 'vfs.nfsrv.server_v4enabled':
            outfile.write(line+'\n')
            continue
        if sysctl == 'vfs.nfsrv.request_space_throttled':
            outfile.write('# OneFS 6.5 obsolete sysctl (see vfs.nfsrv.rpc.request_space_throttled)\n')
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.request_space_throttle_count':
            outfile.write('# OneFS 6.5 obsolete sysctl (see vfs.nfsrv.rpc.request_space_throttle_count)\n')
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.request_space_used':
            outfile.write('# OneFS 6.5 obsolete sysctl (see vfs.nfsrv.rpc.request_space_used)\n')
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.request_space_used_highest':
            outfile.write('# OneFS 6.5 obsolete sysctl (see vfs.nfsrv.rpc.request_space_used_highest)\n')
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.sync_response':
            try:
                value = long(value)
            except ValueError:
                _invalid_sysctl(outfile, line)
                continue
            if value == 0:
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --write-unstable-reply=unstable\n')
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --write-datasync-reply=datasync\n')
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --write-filesync-reply=filesync\n')
            else:
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --write-unstable-reply=filesync\n')
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --write-datasync-reply=filesync\n')
                cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --write-filesync-reply=filesync\n')
            outfile.write('# OneFS 6.5 moved to export option\n')
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.use_local_nfsver':
            outfile.write('# OneFS 6.5 obsolete sysctl\n')
            outfile.write('#'+line+'\n')
            changes += 1
            continue
        if sysctl == 'vfs.nfsrv.usec_guard_check':
            cmdfile.write(ISI_CLI + 'nfs exports modify --site-default --ignoreconflicts --time-delta=0.001\n')
            outfile.write('# OneFS 6.5 moved to export option\n')
            outfile.write('#'+line+'\n')
            changes += 1
            continue

        outfile.write('# OneFS 6.5 unrecognized sysctl\n')
        outfile.write('#'+line+'\n')
        changes += 1

    cmdfile.close()
    outfile.close()
    infile.close()

    clean_cmdfile = 1
    clean_confile = 1

    if changes:
        process = subprocess.Popen('chmod a+rx /etc/mcp/override/sysctl.conf.upgradecmd', shell=True, stdout=subprocess.PIPE)
        for line in process.stdout:
            print line
        process.wait()
        if process.returncode == 0:
            process = subprocess.Popen('/etc/mcp/override/sysctl.conf.upgradecmd', shell=True, stdout=subprocess.PIPE)
            for line in process.stdout:
                print line
            process.wait()
            if process.returncode == 0:
                try:
                    os.rename('/etc/mcp/override/sysctl.conf.upgrade', '/etc/mcp/override/sysctl.conf')
                    clean_confile = 0
                except (OSError):
                    raise RuntimeError('%s: failed to replace /etc/mcp/override/sysctl.conf' % scriptname)
            else:
                raise RuntimeError("%s: failed to update export settings" % scriptname)
        else:
            raise RuntimeError('%s: failed to make /etc/mcp/override/sysctl.conf.upgradecmd executable' % scriptname)

    if clean_cmdfile:
        process = subprocess.Popen('rm -f /etc/mcp/override/sysctl.conf.upgradecmd', shell=True, stdout=subprocess.PIPE)
        for line in process.stdout:
            print line
        process.wait()
        if process.returncode != 0:
            logger.error('%s: Unable to delete sysctl.conf.upgradecmd' % scriptname)

    if clean_confile:
        process = subprocess.Popen('rm -f /etc/mcp/override/sysctl.conf.upgrade', shell=True, stdout=subprocess.PIPE)
        for line in process.stdout:
            print line
        process.wait()
        if process.returncode != 0:
            logger.error('%s: Unable to delete sysctl.conf.upgrade' % scriptname)

    return

def main():
    try:
        logger.setLevel(logging.DEBUG)
        fh = logging.FileHandler(LOG_FILE, 'a')
        fh.setLevel(logging.DEBUG)
        logger.addHandler(fh)

    except EnvironmentError, e:
        syslog.syslog(syslog.LOG_NOTICE,
            "Error running nfs upgrade script: %s" % e)
        return 1

    try:
        os.chmod(LOG_FILE, 0640)
    except OSError, e:
        # log chmod errors and continue
        syslog.syslog(syslog.LOG_NOTICE,
                      "Error chmod'ing %s (errno: %d)" % (LOG_FILE, e.errno))

    try:
        sentry = safelockfile(NFS_UPGRADE_LOCKFILE, 'w')
    except EnvironmentError, e:
        syslog.syslog(syslog.LOG_NOTICE,
            "Error running nfs upgrade script: %s" % e)
        return 1

    if os.path.exists(NFS_UPGRADED_FILE):
        logger.info("%s: NFS upgrade has already occurred successfully." %
                    time.ctime())
        sentry.close()
        return 0
        
    if save_configuration():
        try:
            sysctl_upgrade()
        except RuntimeError, e:
            logger.error('Error running nfs upgrade script: %s' % str(e))
            syslog.syslog(syslog.LOG_NOTICE,
                "Error running nfs upgrade script: %s" % str(e));
            restore_configuration()
            sentry.close()
            return 1

    else:
        logger.error("%s: Error running nfs upgrade script: existing configuration could not be saved" % time.ctime())
        syslog.syslog(syslog.LOG_NOTICE,
            "Error running nfs upgrade script: existing configuration could not be saved")
        sentry.close()
        return 1

    msg = "%s: Upgrade was successful" % time.ctime()
    logger.info(msg)
    print >>file(NFS_UPGRADED_FILE, 'w'), msg
    os.chmod(NFS_UPGRADED_FILE, 0640)
    sentry.close()
    return 0

if __name__ == "__main__":
    sys.exit(main())
