#!/usr/bin/env python
# -*- coding: utf-8 -*-
# TODO: 3 stop command-line jobs too, via kshm api?
import cgi
import fcntl
import logging
import logging.handlers
import multiprocessing
import os
import re
import shutil
import signal
import socket
import subprocess
import sys
import threading
from functools import wraps
from pathlib import Path

import fnmatch
import traceback
import yaml
from yandextank.common.util import Status
from yandextank.core.tankcore import LockError, Lock, VERSION
from yandextank.core.tankworker import TankWorker
from yandextank.validator.validator import ValidationError

try:
    import simplejson as json
except ImportError:
    import json

from pkg_resources import resource_filename
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer, HTTPStatus
from urllib.parse import urlparse

from .util import DictToXml, DirectOutput, NotFoundError, run_subprocess_and_wait
from .config_tweaker import PhantomToPandora
from .config import Config

try:
    from library.python import resource as rs

    pip = False
except ImportError:
    pip = True

LOGGER = logging.getLogger('tankapi')

_SINGLE_WORKER = threading.Lock()


def _single_worker(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        with _SINGLE_WORKER:
            return f(*args, **kwargs)

    return decorated


def get_resource(path):
    if not pip and path in rs.iterkeys(prefix='resfs/file/load/projects/tankapi_server/tankapi/'):
        return rs.find(path)
    else:
        with open(path, 'r') as f:
            return f.read()


# @for_all_methods(timeit(1), exclude=['serve_forever'])
class LunaparkAPIServer(BaseHTTPRequestHandler):
    """
        Class with generic handlers for web API
    """

    PATH_TESTS = '/api/v1/tests/'
    PATH_TANK = '/api/v1/tank/'
    PATH_ADB = '/api/v1/devices.json'
    # aggregating proxy
    PATH_PROXY_JOB = '/proxy/api/job/'
    PATH_PROXY_TASK = '/proxy/api/task/'
    PATH_PROXY_MONITORING = '/proxy/api/monitoring/receiver/'
    PATH_PROXY = '/proxy/'
    PATH_TESTS_START = '/api/v1/tests/start'
    PATH_TESTS_PREPARE = '/api/v1/tests/prepare'
    PATH_TESTS_RUN = '/api/v1/tests/run'

    HELP_URLS = ('/', '/help', '/help/')

    def log_error(self, fmt, *args):
        LOGGER.error(fmt % args)

    def log_message(self, fmt, *args):
        LOGGER.info(fmt % args)

    def response_help_page(self):
        """
            Вернуть страницу помощи
        """
        LOGGER.info('Show help page')
        self.send_response(HTTPStatus.OK)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(bytes(get_resource(resource_filename(__name__, 'data/tank_api_help.html')), 'utf8'))

    def response_200(self, message, response_type):
        LOGGER.debug('Show message type %s: %s', response_type, message)
        self.send_response(HTTPStatus.OK)
        self.send_header('Content-Type', response_type)
        self.end_headers()
        self.wfile.write(bytes(str(message), 'utf8'))

    def response_400(self, message):
        self.send_response(HTTPStatus.BAD_REQUEST)
        self.end_headers()
        self.wfile.write(bytes(str(message), 'utf8'))

    @property
    def requested_content_type(self):
        parsed = urlparse(self.path)
        splitted = os.path.splitext(parsed.path)
        return splitted[1][1:]

    def _convert_to_json(self, message):
        LOGGER.debug('Convert to json message: %s' % message)
        json_message = json.dumps(message, sort_keys=True, indent=4)
        return json_message

    def _convert_to_xml(self, message):
        LOGGER.debug('Convert to xml message: %s' % message)
        xml_message = ''
        try:
            xml_message = DictToXml({'result': message}).display()
        except Exception:
            LOGGER.error(traceback.format_exc())
        LOGGER.debug('xml message %s' % xml_message)
        return xml_message

    def convert_message(self, message, requested_content_type):
        """
            Convert message to requested format and set content-type header
        """
        if requested_content_type == 'xml':
            return self._convert_to_xml(message), 'text/xml'

        if requested_content_type == 'json':
            return self._convert_to_json(message), 'application/json'

        if requested_content_type == 'txt':
            return str(message), 'text/plain'

        LOGGER.warn("Unknown mime-type sent as-is: %s", requested_content_type)
        return str(message), requested_content_type

    def do_GET(self):
        """
            GET requests processor
            Calls method according to requested URL or shows API description
        """
        try:
            if self.path in self.HELP_URLS:
                self.response_help_page()
                return
            client = {
                'address': self.client_address[0],
                'port': self.client_address[1],
                'user-agent': self.headers.get('user-agent', '')
            }
            if self.path.startswith('/favicon.ico'):
                return self.response_200('', 'text/plain')
            elif self.path.startswith(self.PATH_TANK):
                res = self.server.handle_tank(self.path[len(self.PATH_TANK):])
            elif self.path.startswith(self.PATH_TESTS):
                res = self.server.handle_tests(self.path[len(
                    self.PATH_TESTS):], client)
            elif self.path.startswith(self.PATH_ADB):
                res = self.server.handle_devices(self.path[len(self.PATH_ADB):])
            elif self.path.startswith(self.PATH_PROXY_JOB):
                res = self.server.proxy.handle_job_get(self.path[len(
                    self.PATH_PROXY_JOB):])
                self.response_200(res, 'text/plain')
                return
            elif self.path.startswith(self.PATH_PROXY):
                res = self.server.proxy.handle_proxy_get(self.path[len(
                    self.PATH_PROXY):])
                self.response_200(res, 'text/plain')
                return
            else:
                raise NotFoundError()

            res['success'] = True
            res['error'] = ''

            content, content_type = self.convert_message(res, self.requested_content_type)
            self.response_200(content, content_type)
        except NotFoundError as e:
            LOGGER.error('Incorrect url. Page not found, url-path: %s' % self.path)
            self.send_error(HTTPStatus.NOT_FOUND, str(e))
            return
        except DirectOutput as e:
            LOGGER.info('Send file: %s' % e)
            self.send_response(HTTPStatus.OK)
            self.send_header('Content-type', e.mimetype)
            self.end_headers()
            e.write_to(self.wfile)
        except Exception as e:
            LOGGER.error("GET error:\n", exc_info=True)
            res = {'success': False, 'error': '%s' % e}
            content, content_type = self.convert_message(res, self.requested_content_type)
            self.response_200(content, content_type)

    def do_POST(self):
        try:
            mtype = self.requested_content_type
            length = int(self.headers.get('content-length'))
            res = {}
            if self.path.startswith(self.PATH_PROXY_JOB):
                try:
                    res = self.server.proxy.handle_job_post(
                        self.path[len(self.PATH_PROXY_JOB):], self.headers,
                        self.rfile.read(length))
                except Exception:
                    LOGGER.warn("Interrupting test: %s", traceback.format_exc())
                    self.response_400("Interrupted by API")
            elif self.path.startswith(self.PATH_PROXY_MONITORING):
                res = self.server.proxy.handle_job_mon_post(
                    self.path[len(self.PATH_PROXY_MONITORING):], self.headers, self.rfile.read(length)
                )
                mtype = 'txt'
            elif self.path.startswith(self.PATH_TESTS_START):
                res = self.server.handle_start_test(self.path, self.headers, self.rfile)
            elif self.path.startswith(self.PATH_TESTS_PREPARE):
                res = self.server.handle_test_prepare(self.path, self.headers, self.rfile)
            elif self.path.startswith(self.PATH_TESTS_RUN):
                res = self.server.handle_test_run()
            else:
                raise NotFoundError(self.path)

            content, content_type = self.convert_message(res, mtype)
            self.response_200(content, content_type)
        except NotFoundError as e:
            LOGGER.error('Incorrect url. Page not found, url-path: %s' % self.path)
            self.send_error(404, str(e))
            return
        except Exception as e:
            LOGGER.error("POST error: %s", traceback.format_exc())
            res = {'success': False, 'error': '%s' % e}
            content, content_type = self.convert_message(res, self.requested_content_type)
            self.response_200(content, content_type)

    @staticmethod
    def serve_forever(host, port, ipv6, tests_dir, lock_dir):
        APIServer((host, port), LunaparkAPIServer, ipv6, tests_dir, lock_dir).serve_forever()


# ==============================================================================


def parse_cfg_field(field_item):
    if isinstance(field_item, list):
        return {os.path.basename(item.filename): item.file.read() for item in field_item}
    else:
        return {os.path.basename(field_item.filename): field_item.file.read()}


# @for_all_methods(timeit(1))
class APIServer(ThreadingHTTPServer):
    '''
    Class with application logic
    '''

    CONFIG_FIELD = 'load.conf'
    AMMO_FIELD = 'ammo'
    OPTIONS_FIELD = 'options'
    CFG_PATCH_FIELD = 'cfg_patch'
    LOCAL_FILE = 'local_file'

    API_TESTS_BASE = '/api/v1/tests/'
    LOCK_DIR = os.getenv('LOCK_DIR', '/var/lock')

    def __init__(self,
                 server_address,
                 handler_class,
                 ipv6,
                 tests_dir,
                 lock_dir,
                 bind_and_activate=True):
        if ipv6:
            APIServer.address_family = socket.AF_INET6
            if server_address[0] == '':
                server_address = ("::", server_address[1])
        ThreadingHTTPServer.__init__(self,
                                     server_address,
                                     handler_class,
                                     bind_and_activate=bind_and_activate)
        if not os.path.exists(tests_dir):
            os.makedirs(tests_dir)
        self.tests_dir = tests_dir
        self.lock_dir = lock_dir or self.LOCK_DIR
        self.tank_worker = None
        self._tank_worker_start_shooting_event = None
        LOGGER.info("Logic object created")

    def handle_tank(self, path):
        splitted = os.path.splitext(path)
        if splitted[0] == 'status':
            return self.get_tank_status()
        else:
            raise NotFoundError()

    def handle_devices(self):
        """
        adb devices handler
        :returns: list of attached to tank host android devices
        """
        result = {}
        try:
            devices = []
            p = subprocess.Popen(['adb devices'], shell=True, stdout=subprocess.PIPE)
            adb_results = p.stdout.read().decode('utf8').split('\n')[1:]
            for device in adb_results:
                dev_id = device.split('\t')[0]
                if dev_id:
                    devices.append(dev_id)
            result['devices'] = devices
        except Exception as exc:
            result['error'] = 'unable to check adb devices %s' % exc
        return result

    def handle_tests(self, path, client):
        parts = path.split('/')
        basename = os.path.splitext(path)[0]
        if basename == 'stop':
            if self.tank_worker:
                LOGGER.info('Client {} is stopping test'.format(client))
                return self.stop_test()
            else:
                raise RuntimeError("No test running")
        else:
            test_id = parts[0]
            test_id = self.__check_test_id(test_id)
            if os.path.splitext(parts[1])[0] == 'status':
                return self.get_test_status(test_id)
            elif parts[1] == 'logs':
                raise DirectOutput(self.get_test_log(test_id, parts[2]))
            elif os.path.splitext(parts[1])[0] == 'logs':
                return self.get_test_logs(test_id)
            else:
                raise NotFoundError()

    def save_cgi_field_item(self, item):
        path = os.path.join(self.tests_dir, os.path.basename(item.filename))
        with open(path, 'wb') as f:
            shutil.copyfileobj(item.file, f)  # default buffer size is 64 * 1024

        return path

    def collect_file_from_local_fs(self, item):
        source = item.filename
        file_name = Path(source).name
        destination = os.path.join(self.tests_dir, file_name)
        method_requested = item.headers.get('method', 'move')
        if method_requested in ('copy', 'cp'):
            shutil.copyfile(source, destination)
        else:
            shutil.move(source, destination)
        return destination

    def save_configs(self, field_item):
        """
        :returns: list of paths
        """
        if isinstance(field_item, list):
            return [self.save_cgi_field_item(item) for item in field_item]
        else:
            return [self.save_cgi_field_item(field_item)]

    def is_converted_phantom_to_pandora(self, cfg_paths, options):
        for path in cfg_paths:
            with open(path, 'r+b') as f:
                conf = f.read()
                converted_conf, is_converted = PhantomToPandora().convert(conf, options)
                f.seek(0)
                f.write(converted_conf)

        return is_converted

    def is_experiment(self, cfg_paths, options):
        experiment = Config().init_config()
        for option in options:
            if 'uploader.task' in option:
                opt = re.split(r'=', option.strip())
                if opt[1] in experiment['experiment']['tasks']:
                    return True

        for path in cfg_paths:
            with open(path, 'rb') as f:
                conf = f.read()
                try:
                    conf_yaml = yaml.safe_load(conf)
                    if experiment and conf_yaml['uploader']['task'] in experiment['experiment']['tasks']:
                        LOGGER.info('Task %s take part in experiment' % conf_yaml['uploader']['task'])
                        return True

                except (KeyError, TypeError, yaml.YAMLError):
                    pass

        return False

    def handle_test_prepare(self, path, headers, rfile):
        LOGGER.info('User-Agent: {}'.format(headers['User-Agent']))
        LOGGER.debug('Headers: %s' % headers)
        ctype, pdict = cgi.parse_header(headers.get('content-type'))
        LOGGER.debug('ctype %s, pdict %s' % (ctype, pdict))
        if not ctype == 'multipart/form-data':
            raise Exception('Incorrect content_type %s.' % ctype)
        if not os.path.exists('/tmp'):
            os.mkdir('/tmp')
            LOGGER.info("Directory /tmp created ")
        form_data = cgi.FieldStorage(
            fp=rfile,
            headers=headers,
            environ={'REQUEST_METHOD': 'POST',
                     'CONTENT_TYPE': headers['Content-Type'], })
        cfg_paths = None
        ammo_path = None
        options = []
        patches = []
        files = []
        for field in form_data.keys():
            field_item = form_data[field]
            if field == self.CONFIG_FIELD:
                cfg_paths = self.save_configs(field_item)
            elif field == self.AMMO_FIELD and field_item.filename:
                ammo_path = self.save_cgi_field_item(field_item)
            elif field == self.OPTIONS_FIELD:
                options = [f.file.read() for f in field_item] \
                    if isinstance(field_item, list) \
                    else [field_item.file.read()]
            elif field == self.CFG_PATCH_FIELD:
                patches = [f.file.read() for f in field_item] \
                    if isinstance(field_item, list) \
                    else [field_item.file.read()]
            elif field == self.LOCAL_FILE:
                items = field_item if isinstance(field_item, (list, tuple)) else [field_item]
                for item in items:
                    files.append(self.collect_file_from_local_fs(item))
            elif field_item.filename:
                files.append(self.save_cgi_field_item(field_item))
        if cfg_paths is None:
            raise RuntimeError('Error: load.conf is empty')

        if self.is_experiment(cfg_paths, options):
            if self.is_converted_phantom_to_pandora(cfg_paths, options):
                LOGGER.info('Experiment: Phantom config coverted to Pandora')

        api_message = self.start_test(files, cfg_paths, ammo_path, options, patches)
        return api_message

    def handle_test_run(self):
        self._tank_worker_start_shooting_event.set()
        return self.tank_worker.get_status()

    def handle_start_test(self, path, headers, rfile):
        ans = self.handle_test_prepare(path, headers, rfile)
        self._tank_worker_start_shooting_event.set()
        return ans

    @_single_worker
    def start_test(self, files, cfg_paths, ammo_path, cfg_options, patches):
        # todo:
        if self.__is_test_session_running():
            return {
                'success': False,
                'error': "Another test is already running"}

        overwrite_options = {
            'core': {
                'artifacts_base_dir': self.tests_dir,
                'lock_dir': self.lock_dir
            },
            'phantom': {
                'cache_dir': os.path.join(self.tests_dir, 'stpd-cache')
            },
            'console': {'enabled': False}
        }
        if ammo_path:
            overwrite_options['phantom'].update({
                'use_caching': False,
                'ammofile': os.path.basename(ammo_path)
            })
        patches = [yaml.dump(overwrite_options)] + patches
        LOGGER.info('Starting test')
        try:
            self._tank_worker_start_shooting_event = multiprocessing.Event()
            self.tank_worker = TankWorker(cfg_paths, cfg_options, patches, files=files, ammo_file=ammo_path,
                                          run_shooting_event=self._tank_worker_start_shooting_event)
            self.tank_worker.collect_files()
            self.tank_worker.go_to_test_folder()
        except (ValidationError, LockError) as e:
            return {
                'success': False,
                'error': str(e)}
        self.tank_worker.start()
        return {'success': True, 'id': self.tank_worker.test_id}

    def get_test_logs(self, test_id):
        """
            Get filenames for artifacts of specified test
        """
        test_dir = os.path.abspath(os.path.join(self.tests_dir, test_id))
        files_in_test_dir = []
        for filename in os.listdir(test_dir):
            if not os.path.isdir(os.path.join(test_dir, filename)):
                files_in_test_dir.append(filename)
        result = {'files': files_in_test_dir}
        return result

    def get_test_log(self, test_id, name):
        filename = os.path.join(self.tests_dir, test_id, name)
        if not os.path.exists(filename):
            raise NotFoundError("File not found: %s/%s" % (test_id, name))
        return filename

    def __check_test_id(self, test_id):
        """
            Check if test with supplied test_id exists
            If test is missing throws exception
        """
        if not os.path.exists(self.tests_dir):
            raise Exception('Tests directory %s does not exist.' %
                            self.tests_dir)
        test_dir = os.path.join(self.tests_dir, str(test_id))
        if not os.path.exists(test_dir):
            raise NotFoundError('Test #%s does not exist. ' % test_id)
        return str(test_id)

    def stop_test(self):
        """
            Force stop current test
        """
        res = {}
        if self.tank_worker:
            res['id'] = self.tank_worker.test_id
            self.tank_worker.stop()
        return res

    def _is_active_test(self, test_id):
        if self.tank_worker and self.tank_worker.test_id == test_id and self.tank_worker.status != Status.TEST_FINISHED:
            return True
        return False

    def get_test_status(self, test_id):
        """ Get info about running or completed test """
        result = {'status_code': 'NO TEST',
                  'left_time': None,
                  'exit_code': None,
                  'lunapark_id': None,
                  'tank_msg': None,
                  'lunapark_url': None,
                  'test_id': test_id}
        test_dir = os.path.join(self.tests_dir, str(test_id))
        LOGGER.debug("API test dir: %s", test_dir)

        if self._is_active_test(test_id):
            LOGGER.debug("Active test detected")
            result = self.tank_worker.get_status()
        elif not os.path.exists(test_dir):
            result['status_code'] = Status.TEST_NOT_FOUND
        else:
            LOGGER.debug("Finished test detected")
            finish_status_file = os.path.join(test_dir, TankWorker.FINISH_FILENAME)
            if os.path.exists(finish_status_file):
                with open(finish_status_file) as f:
                    result = yaml.load(f, Loader=yaml.FullLoader) or {}
            else:
                msg = '{} file not found'.format(finish_status_file)
                LOGGER.warning(msg)
                result['status_code'] = Status.TEST_FINISHED
                result['tank_msg'] = msg

        LOGGER.info(result)
        return result

    def __get_tank_hostname(self):
        return socket.getfqdn()

    def __is_test_session_running(self):
        if self.tank_worker and self.tank_worker.is_alive():
            return self.tank_worker.status != Status.TEST_FINISHED
        else:
            LOGGER.info("Checking if tank is available")
            return bool(Lock.is_locked(self.lock_dir))

    def __is_test_session_preparing(self):
        return (self.tank_worker and self.tank_worker.is_alive() and
                self.tank_worker.status == Status.TEST_PREPARING)

    def __get_fs_usage(self):
        result = {}
        process = run_subprocess_and_wait(
            'df -l -BG -x fuse -x tmpfs -x devtmpfs',
            shell=False)
        fs_info = process.stdout.read().decode('utf8').split('\n')
        for fs_item in fs_info[1:]:
            if fs_item:
                # разделяем по пробелам и получаем нужные данные по файловой
                # системе
                fs_item_info = [i for i in fs_item.split(' ') if i.strip()]
                result[fs_item_info[0]] = {
                    'size': fs_item_info[1],
                    'used': fs_item_info[2],
                    'avail': fs_item_info[3],
                    'use_p': fs_item_info[4],
                    'mount': fs_item_info[5],
                }
        return result

    def __get_users_activity(self):
        """
            Get info about users logged in via ssh
        """
        result = []
        process = run_subprocess_and_wait("w -h | awk '{print $1, $5}'",
                                          shell=True)
        users_activity_info = process.stdout.read().decode('utf8').split('\n')
        for item in users_activity_info:
            if item.strip():
                user_info = item.split(' ')
                if user_info[1] == '.':
                    result.append({user_info[0]: 'not'})
                else:
                    result.append({user_info[0]: user_info[1]})
        return result

    def __get_processes(self):
        """
            Get info about top CPU comsuming processes
        """

        process = run_subprocess_and_wait(
            "top -n1 -d2 -b | grep -A 5 'PID' | "
            "tail -n6 | awk '{print $1, $2, $9, $12}'",
            shell=True)
        processes_info = process.stdout.readlines()
        if len(processes_info) > 0:
            processes_info.pop(0)
        result = []
        for item in processes_info:
            if item:
                command_info = item.decode('utf8').strip().split(' ')
                result.append({
                    'command': command_info[3],
                    'user': command_info[1],
                    'cpu': float(command_info[2].replace(',', '.')),
                    # FIXME replace is a workaround for trusty's top
                    'pid': int(command_info[0])
                })
        return result

    def __get_lunapark_ids(self):
        jobs = []
        for filename in os.listdir(self.lock_dir):
            if fnmatch.fnmatch(filename, 'lunapark_*.lock'):
                full_name = os.path.join(self.lock_dir, filename)
                LOGGER.debug("Getting job info from lock file: %s", full_name)

                try:
                    with open(full_name, 'r') as f:
                        info = yaml.load(f, Loader=yaml.FullLoader)
                    jobs.append(info.get('uploader', {}).get('meta', {}).get('jobno'))
                except Exception as exc:
                    LOGGER.warn("Failed to load info from lock %s: %s", full_name, exc)
        return jobs

    def get_tank_status(self):
        """
            Returns tank status
            @return: dict with the following fields:
                "is_testing" - is there a running test on this tank True/False
                "is_preparing" - is there a test in prepairing stage
                                 on this tank True/False
                "left_time" - estimated completion time for running test,
                              in seconds
                "name": - tank hostname
                "users_activity" - dict with info about logged via ssh users
                                   format: { login: time_since_last_action }
                "processes" - dict with info about top CPU processes
                "fs_use" - dict with info about file system usage
        """
        is_testing = self.__is_test_session_running()
        if is_testing:
            if self.tank_worker:
                test_id = self.tank_worker.test_id
            else:
                test_id = Lock.running_ids()
        else:
            test_id = None
        result = {"is_testing": is_testing,
                  "current_test": test_id,
                  "is_preparing": self.__is_test_session_preparing(),
                  "name": self.__get_tank_hostname(),
                  "users_activity": self.__get_users_activity(),
                  "processes": self.__get_processes(),
                  "fs_use": self.__get_fs_usage(),
                  "lunapark_ids": self.__get_lunapark_ids(),
                  "meta_tests": [],
                  "version": 'YandexTank/{}'.format(VERSION)}
        return result


def start_lunapark_api_server(host, port, ipv6, tests_dir, lock_dir):
    LOGGER.info("Run tank API server, host: %s, port: %s", host, port)
    if ipv6:
        LOGGER.info("Using IPv6")
    LunaparkAPIServer.serve_forever(host, int(port), ipv6, os.path.abspath(tests_dir),
                                    os.path.abspath(lock_dir))


def init_logger():
    """
        Initial logger setup
        Set logging level for stdout
    """
    LOGGER.setLevel(logging.DEBUG)

    fmt_verbose = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s %(filename)s:%(lineno)d\t%(message)s")
    stderr_hdl = logging.StreamHandler(sys.stderr)
    stderr_hdl.setFormatter(fmt_verbose)
    stderr_hdl.setLevel(logging.WARN)

    LOGGER.addHandler(stderr_hdl)

    logging.info("Logging stream done")
    logging.debug("Debug msg 0")


def add_file_handler_to_logger(log_path,
                               max_file_path=200,
                               log_chains_number=3):
    LOGGER.info("Add a file logger handler, file: %s, max file "
                "chain size: %s, chains number: %s" % (log_path, max_file_path, log_chains_number))
    logs_file_handler = logging.handlers.RotatingFileHandler(
        log_path,
        maxBytes=1024 * 1024 * int(max_file_path),
        backupCount=log_chains_number)
    logs_file_handler.setLevel(logging.DEBUG if os.environ.get("DEBUG", 0) else
                               logging.INFO)
    formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s %(filename)s:%(lineno)d\t%(message)s")
    logs_file_handler.setFormatter(formatter)

    LOGGER.addHandler(logs_file_handler)
    LOGGER.info("Logging file done: %s", log_path)


def get_parameters():
    sys.path.append('/usr/share/tankapi')
    import server_defaults

    from optparse import OptionParser

    parser = OptionParser()
    parser.add_option('--host',
                      type='string',
                      action='store',
                      default=server_defaults.host,
                      help='Specify host for API server')
    parser.add_option('-p',
                      '--port',
                      type='int',
                      action='store',
                      default=server_defaults.port,
                      help='Port for tank API server.')
    parser.add_option('-w',
                      '--workdir',
                      type='string',
                      action='store',
                      default=server_defaults.workdir,
                      help='Path to tank API server workdir.')
    parser.add_option('-d',
                      '--daemonize',
                      action='store_true',
                      default=server_defaults.daemonize,
                      help='Run tank API server in background')

    parser.add_option('-6',
                      '--ipv6',
                      action='store_true',
                      default=server_defaults.ipv6,
                      help='use ipv6?')

    parser.add_option('-l',
                      '--lock-dir',
                      action='store',
                      default=server_defaults.lock_dir,
                      help='Directory to store lock file')

    script_options = parser.parse_args()[0]

    return (script_options.host, script_options.port, script_options.workdir,
            script_options.daemonize, script_options.ipv6, script_options.lock_dir)


class Daemon(object):
    def __init__(self,
                 pidfile_name,
                 stdin='/dev/null',
                 stdout='/dev/null',
                 stderr='/dev/null'):

        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.pidfile_name = pidfile_name
        self.pidfile = open(pidfile_name, 'a')

        def cleanup_handler(signum, frame):
            try:
                fcntl.lockf(self.pidfile, fcntl.LOCK_UN)
            except IOError as err:
                print(err)
                os._exit(1)
            self.pidfile.close()
            os.remove(self.pidfile_name)
            os._exit(0)

        signal.signal(signal.SIGTERM, cleanup_handler)

    def daemonize(self, ch_to="/"):
        try:
            pid = os.fork()
            if pid > 0:
                # exit first parent
                os._exit(0)
        except OSError as err:
            sys.stderr.write("fork #1 failed: %d (%s)\n" %
                             (err.errno, err.strerror))
            sys.exit(1)

        # decouple from parent environment
        os.chdir(ch_to)
        os.setsid()
        os.umask(0)

        # do second fork
        try:
            pid = os.fork()
            if pid > 0:
                # exit from second parent
                os._exit(0)
        except OSError as err:
            sys.stderr.write("fork #2 failed: %d (%s)\n" %
                             (err.errno, err.strerror))
            sys.exit(1)

        # redirect standard file descriptors
        sys.stdout.flush()
        sys.stderr.flush()
        si = open(self.stdin, 'r')
        so = open(self.stdout, 'a+')
        se = open(self.stderr, 'a+', 0)
        os.dup2(si.fileno(), sys.stdin.fileno())
        os.dup2(so.fileno(), sys.stdout.fileno())
        os.dup2(se.fileno(), sys.stderr.fileno())

        pid = str(os.getpid())
        try:
            fcntl.lockf(self.pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
        except IOError:
            raise

        self.pidfile.truncate(0)
        self.pidfile.write("%s" % pid)
        self.pidfile.flush()
        os.fsync(self.pidfile.fileno())


def detect_ipv6():
    """ detect if local machine has external ipv6 address
    returns: bool"""
    try:
        hostname = socket.getfqdn()
        lookup = socket.getaddrinfo(hostname, 80)
        # ipv6 from here: https://en.wikipedia.org/wiki/IPv6_address
        local_ips = ['::1', '127.0.0.1', '0:0:0:0:0:0:0:1', 'fe80::']
        for line in lookup:
            family, _, _, _, ip = line
            if family == socket.AF_INET6 and ip[0] not in local_ips:
                LOGGER.info('Found external ipv6 on machine: %s', ip[0])
                return True
    except:
        LOGGER.error('Unable to automatically find external ipv6, continue w/ defaults')
        return False
    LOGGER.warning('ipv6 external address not found, continue w/ v4')
    return False
