# -*- coding: utf-8 -*-
'''
Support for LVS (Linux Virtual Server)
'''

# Import python libs

# Import salt libs
import salt.utils
import salt.utils.decorators as decorators
from salt.exceptions import SaltException


__func_alias__ = {
    'list_': 'list'
}


# Cache the output of running which('ipvsadm')
@decorators.memoize
def __detect_os():
    return salt.utils.which('ipvsadm')


def __virtual__():
    '''
    Only load if ipvsadm command exists on the system.
    '''
    if not __detect_os():
        return False

    return 'lvs'


def _build_cmd(**kwargs):
    '''

    Build a well-formatted ipvsadm command based on kwargs.
    '''
    cmd = ''

    if 'service_address' in kwargs:
        if kwargs['service_address']:
            if 'protocol' in kwargs:
                if kwargs['protocol'] == 'tcp':
                    cmd += ' -t {0}'.format(kwargs['service_address'])
                elif kwargs['protocol'] == 'udp':
                    cmd += ' -u {0}'.format(kwargs['service_address'])
                elif kwargs['protocol'] == 'fwmark':
                    cmd += ' -f {0}'.format(kwargs['service_address'])
                else:
                    raise SaltException('Error: Only support tcp, udp and fwmark service protocol')
                del kwargs['protocol']
            else:
                raise SaltException('Error: protocol should specified')
            if 'scheduler' in kwargs:
                if kwargs['scheduler']:
                    cmd += ' -s {0}'.format(kwargs['scheduler'])
                    del kwargs['scheduler']
        else:
            raise SaltException('Error: service_address should specified')
        del kwargs['service_address']

    if 'server_address' in kwargs:
        if kwargs['server_address']:
            cmd += ' -r {0}'.format(kwargs['server_address'])
            if 'packet_forward_method' in kwargs and kwargs['packet_forward_method']:
                if kwargs['packet_forward_method'] == 'dr':
                    cmd += ' -g'
                elif kwargs['packet_forward_method'] == 'tunnel':
                    cmd += ' -i'
                elif kwargs['packet_forward_method'] == 'nat':
                    cmd += ' -m'
                else:
                    raise SaltException('Error: only support dr, tunnel and nat')
                del kwargs['packet_forward_method']
            if 'weight' in kwargs and kwargs['weight']:
                cmd += ' -w {0}'.format(kwargs['weight'])
                del kwargs['weight']
        else:
            raise SaltException('Error: server_address should specified')
        del kwargs['server_address']

    return cmd


def add_service(protocol=None, service_address=None, scheduler='wlc'):
    '''
    Add a virtual service.

    protocol
        The service protocol(only support tcp, udp and fwmark service).

    service_address
        The LVS service address.

    scheduler
        Algorithm for allocating TCP connections and UDP datagrams to real servers.


    CLI Example:

    .. code-block:: bash

        salt '*' lvs.add_service tcp 1.1.1.1:80 rr
    '''

    cmd = '{0} -A {1}'.format(__detect_os(),
                              _build_cmd(protocol=protocol,
                                         service_address=service_address,
                                         scheduler=scheduler))
    out = __salt__['cmd.run_all'](cmd, python_shell=False)

    # A non-zero return code means fail
    if out['retcode']:
        ret = out['stderr'].strip()
    else:
        ret = True
    return ret


def edit_service(protocol=None, service_address=None, scheduler=None):
    '''
    Edit the virtual service.

    protocol
        The service protocol(only support tcp, udp and fwmark service).

    service_address
        The LVS service address.

    scheduler
        Algorithm for allocating TCP connections and UDP datagrams to real servers.


    CLI Example:

    .. code-block:: bash

        salt '*' lvs.edit_service tcp 1.1.1.1:80 rr
    '''

    cmd = '{0} -E {1}'.format(__detect_os(),
                              _build_cmd(protocol=protocol,
                                         service_address=service_address,
                                         scheduler=scheduler))
    out = __salt__['cmd.run_all'](cmd, python_shell=False)

    # A non-zero return code means fail
    if out['retcode']:
        ret = out['stderr'].strip()
    else:
        ret = True
    return ret


def delete_service(protocol=None, service_address=None):
    '''

    Delete the virtual service.

    protocol
        The service protocol(only support tcp, udp and fwmark service).

    service_address
        The LVS service address.


    CLI Example:

    .. code-block:: bash

        salt '*' lvs.delete_service tcp 1.1.1.1:80
    '''

    cmd = '{0} -D {1}'.format(__detect_os(),
                              _build_cmd(protocol=protocol,
                                         service_address=service_address))
    out = __salt__['cmd.run_all'](cmd, python_shell=False)

    # A non-zero return code means fail
    if out['retcode']:
        ret = out['stderr'].strip()
    else:
        ret = True
    return ret


def add_server(protocol=None, service_address=None, server_address=None, packet_forward_method='dr', weight=1, **kwargs):
    '''

    Add a real server to a virtual service.

    protocol
        The service protocol(only support ``tcp``, ``udp`` and ``fwmark`` service).

    service_address
        The LVS service address.

    server_address
        The real server address.

    packet_forward_method
        The LVS packet forwarding method(``dr`` for direct routing, ``tunnel`` for tunneling, ``nat`` for network access translation).

    weight
        The capacity  of a server relative to the others in the pool.


    CLI Example:

    .. code-block:: bash

        salt '*' lvs.add_server tcp 1.1.1.1:80 192.168.0.11:8080 nat 1
    '''

    cmd = '{0} -a {1}'.format(__detect_os(),
                              _build_cmd(protocol=protocol,
                                         service_address=service_address,
                                         server_address=server_address,
                                         packet_forward_method=packet_forward_method,
                                         weight=weight,
                                         **kwargs))
    out = __salt__['cmd.run_all'](cmd, python_shell=False)

    # A non-zero return code means fail
    if out['retcode']:
        ret = out['stderr'].strip()
    else:
        ret = True
    return ret


def edit_server(protocol=None, service_address=None, server_address=None, packet_forward_method=None, weight=None, **kwargs):
    '''

    Edit a real server to a virtual service.

    protocol
        The service protocol(only support ``tcp``, ``udp`` and ``fwmark`` service).

    service_address
        The LVS service address.

    server_address
        The real server address.

    packet_forward_method
        The LVS packet forwarding method(``dr`` for direct routing, ``tunnel`` for tunneling, ``nat`` for network access translation).

    weight
        The capacity  of a server relative to the others in the pool.


    CLI Example:

    .. code-block:: bash

        salt '*' lvs.edit_server tcp 1.1.1.1:80 192.168.0.11:8080 nat 1
    '''

    cmd = '{0} -e {1}'.format(__detect_os(),
                              _build_cmd(protocol=protocol,
                                         service_address=service_address,
                                         server_address=server_address,
                                         packet_forward_method=packet_forward_method,
                                         weight=weight,
                                         **kwargs))
    out = __salt__['cmd.run_all'](cmd, python_shell=False)

    # A non-zero return code means fail
    if out['retcode']:
        ret = out['stderr'].strip()
    else:
        ret = True
    return ret


def delete_server(protocol=None, service_address=None, server_address=None):
    '''

    Delete the realserver from the virtual service.

    protocol
        The service protocol(only support ``tcp``, ``udp`` and ``fwmark`` service).

    service_address
        The LVS service address.

    server_address
        The real server address.


    CLI Example:

    .. code-block:: bash

        salt '*' lvs.delete_server tcp 1.1.1.1:80 192.168.0.11:8080
    '''

    cmd = '{0} -d {1}'.format(__detect_os(),
                              _build_cmd(protocol=protocol,
                                         service_address=service_address,
                                         server_address=server_address))
    out = __salt__['cmd.run_all'](cmd, python_shell=False)

    # A non-zero return code means fail
    if out['retcode']:
        ret = out['stderr'].strip()
    else:
        ret = True
    return ret


def clear():
    '''

    Clear the virtual server table

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.clear
    '''

    cmd = '{0} -C'.format(__detect_os())

    out = __salt__['cmd.run_all'](cmd, python_shell=False)

    # A non-zero return code means fail
    if out['retcode']:
        ret = out['stderr'].strip()
    else:
        ret = True
    return ret


def get_rules():
    '''

    Get the virtual server rules

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.get_rules
    '''

    cmd = '{0} -S -n'.format(__detect_os())

    ret = __salt__['cmd.run'](cmd, python_shell=False)
    return ret


def list_(protocol=None, service_address=None):
    '''

    List the virtual server table if service_address is not specified. If a service_address is selected, list this service only.

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.list
    '''

    if service_address:
        cmd = '{0} -L {1} -n'.format(__detect_os(),
                                  _build_cmd(protocol=protocol,
                                             service_address=service_address))
    else:
        cmd = '{0} -L -n'.format(__detect_os())
    out = __salt__['cmd.run_all'](cmd, python_shell=False)

    # A non-zero return code means fail
    if out['retcode']:
        ret = out['stderr'].strip()
    else:
        ret = out['stdout'].strip()

    return ret


def zero(protocol=None, service_address=None):
    '''

    Zero the packet, byte and rate counters in a service or all services.

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.zero
    '''

    if service_address:
        cmd = '{0} -Z {1}'.format(
            __detect_os(),
            _build_cmd(protocol=protocol, service_address=service_address)
        )
    else:
        cmd = '{0} -Z'.format(__detect_os())
    out = __salt__['cmd.run_all'](cmd, python_shell=False)

    # A non-zero return code means fail
    if out['retcode']:
        ret = out['stderr'].strip()
    else:
        ret = True
    return ret


def check_service(protocol=None, service_address=None, **kwargs):
    '''

    Check the virtual service exists.

    CLI Example:

    .. code-block:: bash

        salt '*' lvs.check_service tcp 1.1.1.1:80
    '''

    cmd = '{0}'.format(_build_cmd(protocol=protocol,
                                   service_address=service_address,
                                   **kwargs))
    # Exact match
    if not kwargs:
        cmd += ' '

    all_rules = get_rules()
    out = all_rules.find(cmd)

    if out != -1:
        ret = True
    else:
        ret = 'Error: service not exists'
    return ret


def check_server(protocol=None, service_address=None, server_address=None, **kwargs):
    '''

    Check the real server exists in the specified service.

    CLI Example:

    .. code-block:: bash

         salt '*' lvs.check_server tcp 1.1.1.1:80 192.168.0.11:8080
    '''

    cmd = '{0}'.format(_build_cmd(protocol=protocol,
                                   service_address=service_address,
                                   server_address=server_address,
                                   **kwargs))
    # Exact match
    if not kwargs:
        cmd += ' '

    all_rules = get_rules()
    out = all_rules.find(cmd)

    if out != -1:
        ret = True
    else:
        ret = 'Error: server not exists'
    return ret
