#!/usr/bin/python
# -*- coding: utf-8 -*-
# vim: set expandtab:tabstop=4:softtabstop=4:shiftwidth=4:nowrap
# $Id$

import argparse
import json
import logging
import logging.handlers
import pipes
import os
import subprocess
import sys
import time
import urllib2
import yaml

LOGPATH = '/var/log/yandex/dt-canary-deploy.log'

def cmd_str(cmd):
    return ' '.join([pipes.quote(s) for s in cmd])

def set_logger():
    logger = logging.getLogger(__name__)
    logger.setLevel(level=logging.DEBUG)
    formatter = logging.Formatter('%(module)s: %(asctime)s [%(levelname)s] %(message)s')
    handler = logging.handlers.SysLogHandler(address = '/dev/log')
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    ch = logging.StreamHandler()
    ch.setFormatter(formatter)
    logger.addHandler(ch)

    return logger

def run():
    logger = set_logger()

    user = os.environ.get('USER')
    logger.info('deploy started by user {0}'.format(user))

    desc = u'''
    Выложить java-приложение на один хост и проверить его "живость".

    # Получить список хостов, ничего не выкладывать
    dt-canary-deploy java-intapi 1.2345678-1 --list # версия на самом деле не используется, но argparse ограничивает

    # Выложить java-intapi версии 1.2345678-1 на sas2-0290-sas-ppc-java-intapi-13904.gencfg-c.yandex.net
    dt-canary-deploy java-intapi 1.2345678-1 --host sas2-0290-sas-ppc-java-intapi-13904.gencfg-c.yandex.net

    # Даунгрейд на версию 1.1234567-1 (прокинуть --force-yes в скрипт выкладки)
    dt-canary-deploy java-intapi 1.1234567-1 --host sas2-0290-sas-ppc-java-intapi-13904.gencfg-c.yandex.net --force-yes

    Лог выполнения записывается в {0}.
    '''.format(LOGPATH)
    parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('--host', '-H')
    parser.add_argument('app')
    parser.add_argument('version')
    parser.add_argument('--force-yes', action='store_true', help=u'устанавливать пакеты с --force-yes; нужно при даунгрейде')
    parser.add_argument('--no-check-conductor-group', action='store_true', help=u'не проверять, что хост входит в кондукторную группу приложения. Использовать только в исключительных случаях')
    parser.add_argument('--list', action='store_true', help=u'вывести список хостов приложения, ничего не устанавливать')
    args = parser.parse_args()
    apps_conf = yaml.load(open('/etc/yandex-direct/direct-apps.conf.yaml', 'r').read())
    app_conf = apps_conf['apps'][args.app]
    c_groups = app_conf['conductor_groups']
    supported_apps = [a for a in apps_conf['apps'] if 'port' in apps_conf['apps'][a] and apps_conf['apps'][a]['port']]
    if args.app not in supported_apps:
        sys.exit('error: canary deploy supported only for ' + ', '.join(supported_apps))
    port = app_conf['port']
    if 'sv_services' not in app_conf or not app_conf['sv_services']:
        sys.exit('error: missed "sv_services" from app %s config' % (args.app))
    # берём первый из сервисов. Не ожидаем более одного для приложений, требующих канареечную выкладку
    # подробнее: https://st.yandex-team.ru/DIRECTADMIN-7803#5d0cb75ee88a5b001ec0e1b9
    sv_service = app_conf['sv_services'][0]

    hosts = []
    for g in c_groups:
        hosts += urllib2.urlopen('https://c.yandex-team.ru/api/groups2hosts/%s' % g).read().rstrip().split('\n')
    if args.host not in hosts and not (args.no_check_conductor_group or args.list):
        sys.exit("app %s doesn't belong to host %s" % (args.app, args.host))
    if args.list:
        print '\n'.join(hosts)
        sys.exit(0)

    ssh = ['ssh', args.host, 'sudo']

    close_from_balancer_cmd = ['iptruler', 'all', 'down']
    open_to_balancer_cmd = ['iptruler', 'all', 'up']
    logger.info('closing from balancer: %s' % cmd_str(close_from_balancer_cmd))
    subprocess.check_call(ssh + close_from_balancer_cmd)

    deploy_cmd = ['direct-java-deploy.pl', args.app, args.version, '--host', args.host]
    if args.force_yes:
        deploy_cmd.append('--force-yes')
    logger.info('deploying: %s' % cmd_str(deploy_cmd))
    try:
        subprocess.check_call(deploy_cmd)
    except Exception as err:
        logger.critical(str(err))
        raise

    logger.info('sleeping for 90s')
    time.sleep(90)

    errors = 0
    status_cmd = ssh + ['sv', 'status', sv_service]
    logger.info('checking uptime: %s' % cmd_str(status_cmd))
    try:
        uptime = int( subprocess.check_output(status_cmd).rstrip().split(' ')[-1].replace('s', '') )
        if uptime >= 60:
            logger.info('uptime OK')
        else:
            logger.error('uptime too low: {}s'.format(uptime))
            errors += 1
    except subprocess.CalledProcessError as e:
        logger.error('command exited with non-zero code %s' % e.returncode)

    check_alive_cmd = ssh + ['curl', '-s', '-f', 'http://127.0.0.1:%s/alive'%port, '-o', '/dev/null']
    logger.info('checking alive: %s' % cmd_str(check_alive_cmd))
    try:
        subprocess.check_call(check_alive_cmd)
        logger.info('alive OK')
    except subprocess.CalledProcessError as e:
        logger.error('command exited with non-zero code %s' % e.returncode)
        errors += 1

    check_version_cmd = ssh + ['curl', '-s', 'http://127.0.0.1:%s/admin\?action\=version'%port]
    logger.info('checking version: %s' % cmd_str(check_version_cmd))
    try:
        version = json.loads(subprocess.check_output(check_version_cmd))['version']
        if version == args.version:
            logger.info('version OK')
        else:
            logger.error('wrong version: {}'.format(version))
            errors += 1
    except subprocess.CalledProcessError as e:
        logger.error('command exited with non-zero code %s' % e.returncode)
        errors += 1
    
    if errors == 0:
        logger.info('opening to balancer: %s' % cmd_str(open_to_balancer_cmd))
        if subprocess.call(ssh + open_to_balancer_cmd) != 0:
            logger.error('command exited with non-zero code %s' % e.returncode)
            errors += 1

    print
    if errors == 0:
        print u'\033[92mOK\033[0m'
    else:
        print u'\033[91mFAILED\033[0m'
        print u'\033[91m    Хост %s ещё закрыт от балансера.\033[0m После решения проблемы открыть командой:'
        print u'        ' + cmd_str(open_to_balancer_cmd)
    print
    sys.exit(errors)

if __name__ == '__main__':
    run()
