#! /usr/bin/env python
# @author wtie@cisco.com
import logging
import os
import time
import itertools
import ansible.runner
from logging.handlers import SysLogHandler
from neutronclient.common.exceptions import NeutronClientException
from neutronclient.v2_0 import client


LOG = logging.getLogger('neutron-l3-evacuate')


def setup_logging(enable_debug):
    level = logging.INFO
    if enable_debug:
        level = logging.DEBUG
    logging.basicConfig(level=level, date_fmt='%m-%d %H:%M')
    syslog = SysLogHandler(address='/dev/log')
    syslog.setLevel(level)
    syslog_formatter = logging.Formatter('%(name)s: %(levelname)s %(message)s')
    syslog.setFormatter(syslog_formatter)
    LOG.addHandler(syslog)


def log_info(action, msg):
    LOG.info("[%-12s] - %s" % (action.upper(), msg))


def log_warn(action, msg):
    LOG.warning("[%-12s] - %s" % (action.upper(), msg))


def log_error(action, msg):
    LOG.error("[%-12s] - %s" % (action.upper(), msg))


def log_debug(action, msg):
    LOG.debug("[%-12s] - %s" % (action.upper(), msg))

# RemoteRunners - How to connect to remote server for checking


class RemoteRunner(object):

    def run(self, host, cmd):
        log_debug("run cmd", "run remote cmd [%s]: %s" % (host, cmd))
        rc, stdout, stderr = self.remote_exec(host, cmd)
        if rc != 0:
            return (False, stderr)
        else:
            return (True, stdout)


class AnsibleRemoteRunner(RemoteRunner):

    def remote_exec(self, host, cmd):
        results = ansible.runner.Runner(
            run_hosts=[host],
            module_name='shell',
            module_args=" ".join(cmd),
            timeout=12
        ).run()
        if host in results['contacted']:
            return (results['contacted'][host]['rc'],
                    results['contacted'][host]['stdout'],
                    results['contacted'][host]['stderr'])
        else:
            return (1, None, results['dark'][host])

    def service_exec(self, host, service, action):
        results = ansible.runner.Runner(
            run_hosts=[host],
            module_name='service',
            module_args={'name': service, 'state': action},
            timeout=12
        ).run()
        if host in results['contacted']:
            return (results['contacted'][host]['state'],
                    results['contacted'][host]['changed'])
        else:
            return (1, None, results['dark'][host])


# Pickers - How to select the destination for one router
class Picker(object):

    def __init__(self, neutron, src_agent):
        self.client = neutron
        agents = neutron.list_agents(agent_type='L3 agent',
                                     admin_state_up=True,
                                     alive=True).get('agents')
        self._src_agent = src_agent
        self.dest = {}
        for agent in agents:
            if agent['alive'] and agent['admin_state_up'] and \
                    agent['id'] != self._src_agent['id']:
                self.dest[agent['id']] = {}
                self.dest[agent['id']]['agent'] = agent
                self.dest[agent['id']]['routers'] = []
        self._dest_cycle = itertools.cycle(self.dest.keys())
        self.src_router_count = None

    def has_next_for_agent(self, agent):
        return len(self.dest[agent['id']]['routers']) > 0

    def get_next_for_agent(self, agent):
        if self.has_next_for_agent(agent):
            return (self.dest[agent['id']]['agent'],
                    self.dest[agent['id']]['routers'].pop())
        else:
            return (None, None)

    def get_next(self):
        candidate = len(self.dest)
        while candidate > 0:
            agent_id = self._dest_cycle.next()
            candidate -= 1
            if len(self.dest[agent_id]['routers']) > 0:
                return (self.dest[agent_id]['agent'],
                        self.dest[agent_id]['routers'].pop())
            else:
                continue
        return (None, None)

    def has_next(self):
        for agent_router in self.dest.values():
            if len(agent_router['routers']) > 0:
                return True
            else:
                continue
        return False


class BalancePicker(Picker):

    def init(self):
        routers = self.client.list_routers_on_l3_agent(
            self._src_agent['id']).get('routers', [])
        self.src_router_count = len(routers)
        totals = {}
        for agent_id in self.dest.keys():
            totals[agent_id] = self.dest[agent_id][
                'agent']['configurations']['routers']
        for router in routers:
            agent_id = min(
                totals.keys(), key=lambda agent_id: totals[agent_id])
            self.dest[agent_id]['routers'].append(router)
            totals[agent_id] += 1
        return len(routers)


class CyclePicker(Picker):

    def init(self):
        routers = self.client.list_routers_on_l3_agent(
            self._src_agent['id']).get('routers', [])
        self.src_router_count = len(routers)
        for router in routers:
            agent_id = self._dest_cycle.next()
            self.dest[agent_id]['routers'].append(router)
        return len(routers)


# Evacuator - How to migate routers
class L3AgentEvacuator(object):

    def __init__(self, **kwargs):
        if "insecure" in kwargs and kwargs['insecure'] is True:
            self._insecure_client = True
        else:
            self._insecure_client = False
        self._setup_neutron_client()
        if 'agent' not in kwargs and 'target' not in kwargs:
            raise Exception("Missing target hostname or agent id")
        else:
            target_agent_id_1 = None
            target_agent_id_2 = None
            if 'agent' in kwargs and kwargs['agent']:
                agent = kwargs['agent']
                target_agent_id_1 = self._get_agent_id(agent)
            if 'target' in kwargs and kwargs['target']:
                target = kwargs['target']
                target_agent_id_2 = self._get_agent_id(target)

            if target_agent_id_1 and target_agent_id_2:
                if target_agent_id_1 == target_agent_id_2:
                    agent_id = target_agent_id_1
                else:
                    raise Exception("target hostname not match agent id")
            elif target_agent_id_1:
                agent_id = target_agent_id_1
            elif target_agent_id_2:
                agent_id = target_agent_id_2
            else:
                raise Exception("Invalid target hostname or agent id")
        self._src_agent = self._neutron.show_agent(agent_id).get('agent', {})

        if 'stopl3' in kwargs and kwargs['stopl3'] is True:
            self._stop_agent_after_evacuate = True
        else:
            self._stop_agent_after_evacuate = False