#!/usr/bin/env python
#-
# Copyright (c) 2015 iXsystems, Inc.
# All rights reserved.
# This file is a part of TrueNAS
# and may not be copied and/or distributed
# without the express permission of iXsystems.

import datetime
import logging
import os
import sys

sys.path.extend([
    '/usr/local/www',
    '/usr/local/www/freenasUI',
])

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'freenasUI.settings')

# Make sure to load all modules
from django.db.models.loading import cache
cache.get_apps()

from freenasUI.storage.models import Replication
from freenasUI.common.pipesubr import pipeopen
from freenasUI.common.locks import mntlock
from freenasUI.common.system import send_mail
from freenasUI.middleware.notifier import notifier
from freenasUI.failover.models import Failover

log = logging.getLogger('tools.autorepl_check')

# Set to True if verbose log desired
debug = True

# Detect if another instance is running
def exit_if_running(pid):
    log.debug("Checking if process %d is still alive" % (pid, ))
    try:
        os.kill(pid, 0)
        # If we reached here, there is another process in progress
        log.debug("Process %d still working, quitting" % (pid, ))
        sys.exit(0)
    except OSError:
        log.debug("Process %d gone" % (pid, ))

def check_failover_status():
    """This method detects whether this unit is part of a HA pair
       and if so whether it is active or passive.  If the unit is
       if an HA pair and passive it returns False.
       If the unit is currently the master of an HA pair or not
       in an HA pair it returns True"""

    status = notifier().failover_status()
    failover = Failover.objects.all().exists()
    if failover and status != "MASTER":
        return False
    else:
        return True

# Check HA status before we do anything with locking, if we are the
# passive side of an HA pair we are going to bail out.  Doing this
# before doing anything with locking assures that we won't leave any
# dangling lockfiles behind

if not check_failover_status():
    sys.exit(0)

MNTLOCK = mntlock()

mypid = os.getpid()
templog = '/tmp/repl_check-%d' % (mypid)

now = datetime.datetime.now().replace(microsecond=0)
if now.second < 30 or now.minute == 59:
    now = now.replace(second=0)
else:
    now = now.replace(minute=now.minute + 1, second=0)
now = datetime.time(now.hour, now.minute)

# (mis)use MNTLOCK as PIDFILE lock.
locked = True
try:
    MNTLOCK.lock_try()
except IOError:
    locked = False
if not locked:
    sys.exit(0)

AUTOREPL_CHECK_PID = -1
try:
    with open('/var/run/autorepl_check.pid') as pidfile:
        AUTOREPL_CHECK_PID = int(pidfile.read())
except:
    pass

if AUTOREPL_CHECK_PID != -1:
    exit_if_running(AUTOREPL_CHECK_PID)

with open('/var/run/autorepl_check.pid', 'w') as pidfile:
    pidfile.write('%d' % mypid)

MNTLOCK.unlock()

# At this point, we are sure that only one autorepl instance is running.

log.debug("Autosnap replication check started")
log.debug("temp log file: %s" % (templog, ))

# Traverse all replication tasks
replication_tasks = Replication.objects.all()
for replication in replication_tasks:

    remote = replication.repl_remote.ssh_remote_hostname.__str__()
    remote_port = replication.repl_remote.ssh_remote_port
    remotefs = replication.repl_zfs.__str__()
    localfs = replication.repl_filesystem.__str__()
    last_snapshot = replication.repl_lastsnapshot.__str__()
    resetonce = replication.repl_resetonce

    sshcmd = ('/usr/bin/ssh -c arcfour256,arcfour128,blowfish-cbc,'
              'aes128-ctr,aes192-ctr,aes256-ctr -i /data/ssh/replication'
              ' -o BatchMode=yes -o StrictHostKeyChecking=yes -q')

    if replication.repl_userepl:
        Rflag = '-R '
    else:
        Rflag = ''

    wanted_list = []
    known_latest_snapshot = ''
    expected_local_snapshot = ''

    localfs_split = localfs.split('/')

    if len(localfs_split) > 1:
        remotefs_final = "%s/%s" % (remotefs, "/".join(localfs_split[1:]))
        if len(localfs_split) > 2:
            remotefs_parent = "%s/%s" % (remotefs, "/".join(localfs_split[1:-1]))
        else:
            remotefs_parent = "%s/%s" % (remotefs, localfs_split[1])
    else:
        remotefs_final = remotefs
        remotefs_parent = remotefs

    # Test if there is work to do, if so, own them
    MNTLOCK.lock()
    log.debug("Checking dataset %s" % (localfs))
    zfsproc = pipeopen('/sbin/zfs list -Ht snapshot -o name -r -S creation %s' % (localfs), debug)
    output, error = zfsproc.communicate()
    if zfsproc.returncode:
        log.warn('Could not determine last available snapshot for dataset %s: %s' % (
            localfs,
            error,
            ))
        MNTLOCK.unlock()
        continue
    if output != '':
        snapshots_set = set([x for x in output.split('\n') if x != ''])
    MNTLOCK.unlock()

    if localfs.find('/') > -1:
        localroot = localfs[:localfs.find('/')]
    else:
        localroot = localfs

    # If there is nothing to do, go through next replication entry
    if len(snapshots_set) == 0:
        continue

    rzfscmd = '"zfs list -Hr -o name -S creation -t snapshot %s"' % (remotefs_final)
    sshproc = pipeopen('%s -p %d %s %s' % (sshcmd, remote_port, remote, rzfscmd))
    output = sshproc.communicate()[0]
    if output != '':
        remote_snapshots_set = set()
        for snapshot_name in output.split('\n'):
            if snapshot_name != "":
                remote_snapshots_set.add("%s%s" % (localfs, snapshot_name[len(remotefs_final):]))

    missing_snapshots1 = snapshots_set - snapshots_set.intersection(remote_snapshots_set)
    missing_snapshots2 = remote_snapshots_set - remote_snapshots_set.intersection(snapshots_set)

    if (len(missing_snapshots1) + len(missing_snapshots2)) > 0:
        log.warn("Found mismatch for replication of %s" % (localfs))
        mismatch = ""
        if len(missing_snapshots1) > 0:
            mismatch = "Snapshots that are found only on local side:\n%s" % ('\n'.join(missing_snapshots1))
        if len(missing_snapshots2) > 0:
            mismatch = mismatch + "\nSnapshots that are found only on remote side:\n%s" % ('\n'.join(missing_snapshots2))
        # Report this situation
        text=\
                        """
Hello,
    The replication checker found mismatch for local ZFS %s.

%s
                        """ % (localfs, mismatch)
        error, errmsg = send_mail(subject="Replication mismatch found!", text=text, interval=datetime.timedelta(hours=2), channel='autorepl_check')

os.remove('/var/run/autorepl_check.pid')
log.debug("Autosnap replication check finished")
