#!/usr/bin/env python
import os
import sys
import time
import configparser
import paramiko
from multiprocessing import Pool
import subprocess


def fetchBackup(name, address, port, username, password, filename, destdir,
        cleanup=True, hook=None):
    print("%s (%s) - starting backup" % (name, address))
    try:
        ssh = paramiko.SSHClient()
        ssh.load_system_host_keys()

        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(address, username=username, password=password, port=port,
                # When using non-empty passwords, RouterOS show error
                # "ssh error  expected: 50 got: 5" and the connection failed.
                # With those parameters, it works.
                # See http://stackoverflow.com/questions/14274566
                allow_agent=False, look_for_keys=False)

        # Run the RouterOS commands to locally create the required files for backup
        stdin, stdout, stderr = ssh.exec_command('/export compact file=%s' % filename)
        # Block until command is finished
        while not stdout.channel.exit_status_ready():
            pass
        stdin.close(); stdout.close(); stderr.close()

        stdin, stdout, stderr = ssh.exec_command('/system backup save name=%s' % filename)
        # Block until command is finished
        while not stdout.channel.exit_status_ready():
            pass
        stdin.close(); stdout.close(); stderr.close()

        rsc_filename = '%s.rsc' % filename
        backup_filename = '%s.backup' % filename

        # Open SFTP Channel to download the created backup files
        ftp = ssh.open_sftp()

        # Wait until backupfiles exist - While RouterOS create the backup files,
        # the filename becomes the postfix ".in_progress". After the commands
        # /export and /system backup finished, it will take a few moments till the
        # files are renamed.
        for i in range(5):
            try:
                time.sleep(2) # sleep a second before trying, even the first time
                ftp.stat(rsc_filename)
                ftp.stat(backup_filename)
                break
            except FileNotFoundError:
                if i == 5:
                    print("%s (%s) - backup files does not exist on remote host. Stopping backup" % (name, address))
                    return 1
                else:
                    print("%s (%s) - backup files does not exist on remote host. retrying ..." % (name, address))

        # Download the remote backup files to local storage
        print("%s (%s) - downloading backup files" % (name, address))
        ftp.get(rsc_filename, os.path.join(destdir, rsc_filename))
        ftp.get(backup_filename, os.path.join(destdir, backup_filename))
        ftp.close()

        if cleanup:
            # Remove the previos create backup files from router
            print("%s (%s) - clean up files on remote host" % (name, address))
            stdin, stdout, stderr = ssh.exec_command('/file remove %s' % rsc_filename)
            # Block until command is finished
            while not stdout.channel.exit_status_ready():
                pass
            stdin.close(); stdout.close(); stderr.close()

            stdin, stdout, stderr = ssh.exec_command('/file remove %s' % backup_filename)
            # Block until command is finished
            while not stdout.channel.exit_status_ready():
                pass
            stdin.close(); stdout.close(); stderr.close()

        if hook:
            # Run hook script
            print("%s (%s) - starting hook %s" % (name, address, hook))
            try:
                returncode = subprocess.call([hook,
                    os.path.join(destdir, rsc_filename),
                    os.path.join(destdir, backup_filename)])
                print("%s (%s) - hook finished with return code %s" %
                        (name, address, returncode))
            except FileNotFoundError as e:
                print("%s (%s) - failed to start hook: %s" % (name, address, e))

        ssh.close()
        print("%s (%s) - backup finished" % (name, address))
        return 0
    except Exception as e:
        print("UNKNOWN ERROR: %s" % e)
        return 2


if __name__ == '__main__':
    config = configparser.ConfigParser()
    config.read(sys.argv[1])

    with Pool(5) as p:
        for section in config.sections():
            address = config.get(section, 'address', fallback=section)
            port = config.getint(section, 'port')
            username = config.get(section, 'username')
            password = config.get(section, 'password')
            filename = config.get(section, 'filename', fallback=section)
            destdir = config.get(section, 'destdir', fallback=os.getcwd())  
            cleanup = config.getboolean(section, 'cleanup')
            hook = config.get(section, 'hook', fallback=None)

            p.apply_async(fetchBackup, (section, address, port, username,
                password, filename, destdir, cleanup, hook))

        p.close()
        p.join()
