# -*- coding: utf-8 -*-
"""A Fabric fabfile with functionality to prepare, install, and configure
BigchainDB, including its storage backend (RethinkDB).
"""

from __future__ import with_statement, unicode_literals

from os import environ  # a mapping (like a dict)
import sys

from fabric.api import sudo, env, hosts
from fabric.api import task, parallel
from fabric.contrib.files import sed
from fabric.operations import run, put
from fabric.context_managers import settings

from hostlist import public_dns_names
from ssh_key import ssh_key_path

# Ignore known_hosts
# http://docs.fabfile.org/en/1.10/usage/env.html#disable-known-hosts
env.disable_known_hosts = True

# What remote servers should Fabric connect to? With what usernames?
env.user = 'ubuntu'
env.hosts = public_dns_names

# SSH key files to try when connecting:
# http://docs.fabfile.org/en/1.10/usage/env.html#key-filename
env.key_filename = ssh_key_path


######################################################################

# DON'T PUT @parallel
@task
def set_host(host_index):
    """A helper task to change env.hosts from the
    command line. It will only "stick" for the duration
    of the fab command that called it.

    Args:
        host_index (int): 0, 1, 2, 3, etc.
    Example:
        fab set_host:4 fab_task_A fab_task_B
        will set env.hosts = [public_dns_names[4]]
        but only for doing fab_task_A and fab_task_B
    """
    env.hosts = [public_dns_names[int(host_index)]]


@task
def test_ssh():
    run('echo "If you see this, then SSH to a remote host worked."')


# Install base software
@task
@parallel
def install_base_software():
    # This deletes the dir where "apt-get update" stores the list of packages
    sudo('rm -rf /var/lib/apt/lists/')
    # Re-create that directory, and its subdirectory named "partial"
    sudo('mkdir -p /var/lib/apt/lists/partial/')
    # Repopulate the list of packages in /var/lib/apt/lists/
    # See https://tinyurl.com/zjvj9g3
    sudo('apt-get -y update')
    # Configure all unpacked but unconfigured packages.
    # See https://tinyurl.com/zf24hm5
    sudo('dpkg --configure -a')
    # Attempt to correct a system with broken dependencies in place.
    # See https://tinyurl.com/zpktd7l
    sudo('apt-get -y -f install')
    # For some reason, repeating the last three things makes this
    # installation process more reliable...
    sudo('apt-get -y update')
    sudo('dpkg --configure -a')
    sudo('apt-get -y -f install')
    # Install the base dependencies not already installed.
    sudo('apt-get -y install git g++ python3-dev libffi-dev')
    sudo('apt-get -y -f install')


# Get an up-to-date Python 3 version of pip
@task
@parallel
def get_pip3():
    # One way:
    # sudo('apt-get -y install python3-setuptools')
    # sudo('easy_install3 pip')
    # Another way:
    sudo('apt-get -y install python3-pip')
    # Upgrade pip
    sudo('pip3 install --upgrade pip')
    # Check the version of pip3
    run('pip3 --version')


# Upgrade setuptools
@task
@parallel
def upgrade_setuptools():
    sudo('pip3 install --upgrade setuptools')


# Prepare RethinkDB storage
@task
@parallel
def prep_rethinkdb_storage(USING_EBS):
    """Prepare RethinkDB storage"""
    # Convert USING_EBS from a string to a bool
    USING_EBS = (USING_EBS.lower() == 'true')

    # Make the /data directory for RethinkDB data
    sudo("mkdir -p /data")

    # OLD: with settings(warn_only=True):
    if USING_EBS:  # on /dev/xvdp
        # See https://tinyurl.com/h2nut68
        sudo("mkfs -t ext4 /dev/xvdp")
        sudo("mount /dev/xvdp /data")
        # To mount this EBS volume on every system reboot,
        # add an entry for the device to the /etc/fstab file.
        # First, make a copy of the current /etc/fstab file
        sudo("cp /etc/fstab /etc/fstab.orig")
        # Append a line to /etc/fstab
        sudo("echo '/dev/xvdp  /data  ext4  defaults,nofail,nobootwait  0  2' >> /etc/fstab")
        # Veryify the /etc/fstab file. If something is wrong with it,
        # then this should produce an error:
        sudo("mount -a")
        # Set the I/O scheduler for /dev/xdvp to deadline
        with settings(sudo_user='root'):
            sudo("echo deadline > /sys/block/xvdp/queue/scheduler")
    else:  # not using EBS.
        # Using the "instance store" that comes with the instance.
        # If the instance store comes with more than one volume,
        # this only mounts ONE of them: /dev/xvdb
        # For example, m3.2xlarge instances have /dev/xvdb and /dev/xvdc
        #  and /mnt is mounted on /dev/xvdb by default.
        try:
            sudo("umount /mnt")
            sudo("mkfs -t ext4 /dev/xvdb")
            sudo("mount /dev/xvdb /data")
        except:
            pass
        sudo("rm -rf /etc/fstab")
        sudo("echo 'LABEL=cloudimg-rootfs	/	 ext4     defaults,discard    0   0' >> /etc/fstab")
        sudo("echo '/dev/xvdb  /data        ext4    defaults,noatime    0   0' >> /etc/fstab")
        # Set the I/O scheduler for /dev/xdvb to deadline
        with settings(sudo_user='root'):
            sudo("echo deadline > /sys/block/xvdb/queue/scheduler")


# Install RethinkDB
@task
@parallel
def install_rethinkdb():
    """Install RethinkDB"""
    # Old way:
    # sudo("echo 'deb http://download.rethinkdb.com/apt trusty main' | sudo tee /etc/apt/sources.list.d/rethinkdb.list")
    # New way: (from https://www.rethinkdb.com/docs/install/ubuntu/ )
    sudo('source /etc/lsb-release && '
         'echo "deb http://download.rethinkdb.com/apt $DISTRIB_CODENAME main" | '
         'sudo tee /etc/apt/sources.list.d/rethinkdb.list')
    sudo("wget -qO- http://download.rethinkdb.com/apt/pubkey.gpg | sudo apt-key add -")
    sudo("apt-get update")
    sudo("apt-get -y install rethinkdb")
    # Change owner:group of the RethinkDB data directory to rethinkdb:rethinkdb
    sudo('chown -R rethinkdb:rethinkdb /data')


# Configure RethinkDB
@task
@parallel
def configure_rethinkdb():
    """Copy the RethinkDB config file to the remote host"""
    put('conf/rethinkdb.conf',
        '/etc/rethinkdb/instances.d/instance1.conf',
        mode=0600,
        use_sudo=True)


# Delete RethinkDB data
@task
@parallel
def delete_rethinkdb_data():
    """Delete the contents of the RethinkDB /data directory
    but not the directory itself.
    """
    sudo('rm -rf /data/*')


# Start RethinkDB
@task
@parallel
def start_rethinkdb():
    """Start RethinkDB"""
    sudo('/etc/init.d/rethinkdb restart')


# Install BigchainDB from PyPI
@task
@parallel
def install_bigchaindb_from_pypi():
    sudo('pip3 install bigchaindb')


# Install BigchainDB from a Git archive file
# named bigchaindb-archive.tar.gz
@task
@parallel
def install_bigchaindb_from_git_archive():
    put('bigchaindb-archive.tar.gz')
    run('tar xvfz bigchaindb-archive.tar.gz')
    sudo('pip3 install .')
    # sudo('python3 setup.py install')
    run('rm bigchaindb-archive.tar.gz')


# Configure BigchainDB
@task
@parallel
def configure_bigchaindb():
    run('bigchaindb -y configure rethinkdb', pty=False)


# Send the specified configuration file to
# the remote host and save it there in
# ~/.bigchaindb
# Use in conjunction with set_host()
# No @parallel
@task
def send_confile(confile):
    put('confiles/' + confile, 'tempfile')
    run('mv tempfile ~/.bigchaindb')
    print('For this node, bigchaindb show-config says:')
    run('bigchaindb show-config')


# Initialize BigchainDB
# i.e. create the database, the tables,
# the indexes, and the genesis block.
# (The @hosts decorator is used to make this
# task run on only one node. See http://tinyurl.com/h9qqf3t )
@task
@hosts(public_dns_names[0])
def init_bigchaindb():
    run('bigchaindb init', pty=False)


# Set the number of shards (in all tables)
@task
@hosts(public_dns_names[0])
def set_shards(num_shards):
    run('bigchaindb set-shards {}'.format(num_shards))


# Set the number of replicas (in all tables)
@task
@hosts(public_dns_names[0])
def set_replicas(num_replicas):
    run('bigchaindb set-replicas {}'.format(num_replicas))


# Start BigchainDB using screen
@task
@parallel
def start_bigchaindb():
    sudo('screen -d -m bigchaindb -y start &', pty=False)


# Install and run New Relic
@task
@parallel
def install_newrelic():
    newrelic_license_key = environ.get('NEWRELIC_KEY')
    if newrelic_license_key is None:
        sys.exit('The NEWRELIC_KEY environment variable is not set')
    else:
        # Andreas had this "with settings(..." line, but I'm not sure why:
        # with settings(warn_only=True):
        # Use the installation instructions from NewRelic:
        # http://tinyurl.com/q9kyrud
        # ...with some modifications
        sudo("echo 'deb http://apt.newrelic.com/debian/ newrelic non-free' >> "
             "/etc/apt/sources.list.d/newrelic.list")
        sudo('wget -O- https://download.newrelic.com/548C16BF.gpg | '
             'apt-key add -')
        sudo('apt-get update')
        sudo('apt-get -y --force-yes install newrelic-sysmond')
        sudo('nrsysmond-config --set license_key=' + newrelic_license_key)
        sudo('/etc/init.d/newrelic-sysmond start')


###########################
# Security / Firewall Stuff
###########################

@task
def harden_sshd():
    """Security harden sshd.
    """
    # Disable password authentication
    sed('/etc/ssh/sshd_config',
        '#PasswordAuthentication yes',
        'PasswordAuthentication no',
        use_sudo=True)
    # Deny root login
    sed('/etc/ssh/sshd_config',
        'PermitRootLogin yes',
        'PermitRootLogin no',
        use_sudo=True)


@task
def disable_root_login():
    """Disable `root` login for even more security. Access to `root` account
    is now possible by first connecting with your dedicated maintenance
    account and then running ``sudo su -``.
    """
    sudo('passwd --lock root')


@task
def set_fw():
    # snmp
    sudo('iptables -A INPUT -p tcp --dport 161 -j ACCEPT')
    sudo('iptables -A INPUT -p udp --dport 161 -j ACCEPT')
    # dns
    sudo('iptables -A OUTPUT -p udp -o eth0 --dport 53 -j ACCEPT')
    sudo('iptables -A INPUT -p udp -i eth0 --sport 53 -j ACCEPT')
    # rethinkdb
    sudo('iptables -A INPUT -p tcp --dport 28015 -j ACCEPT')
    sudo('iptables -A INPUT -p udp --dport 28015 -j ACCEPT')
    sudo('iptables -A INPUT -p tcp --dport 29015 -j ACCEPT')
    sudo('iptables -A INPUT -p udp --dport 29015 -j ACCEPT')
    sudo('iptables -A INPUT -p tcp --dport 8080 -j ACCEPT')
    sudo('iptables -A INPUT -i eth0 -p tcp --dport 8080 -j DROP')
    sudo('iptables -I INPUT -i eth0 -s 127.0.0.1 -p tcp --dport 8080 -j ACCEPT')
    # save rules
    sudo('iptables-save >  /etc/sysconfig/iptables')


#########################################################
# Some helper-functions to handle bad behavior of cluster
#########################################################

# rebuild indexes
@task
@parallel
def rebuild_indexes():
    run('rethinkdb index-rebuild -n 2')


@task
def stopdb():
    sudo('service rethinkdb stop')


@task
def startdb():
    sudo('service rethinkdb start')


@task
def restartdb():
    sudo('/etc/init.d/rethinkdb restart')