#!/usr/bin/env python

from pyeole import ihm
from pyeole import process
from creole.client import CreoleClient
from creole.eosfunc import get_net_devices

import sys
import csv
import logging
import re

LOG_FILE = '/var/log/openvswitch/eole-openvswitch.log'

#= Configure Logger ===
logger = logging.getLogger(__name__)
std_handler  = logging.StreamHandler( sys.stdout )
file_handler = logging.FileHandler( LOG_FILE )

logger.setLevel(       logging.INFO  )
std_handler.setLevel(  logging.INFO )
file_handler.setLevel( logging.INFO  )

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
std_formatter = logging.Formatter('   %(message)s')
std_handler.setFormatter( std_formatter )
file_handler.setFormatter( formatter )

logger.addHandler( std_handler  )
logger.addHandler( file_handler )
#= End Logger ===

class RunCmdError(Exception):
    pass

class ConfigError(Exception):
    pass

OVSVSCTL = 'ovs-vsctl'
OVSDB_NAME = 'Open_vSwitch'
CONF_FILE = '/etc/openvswitch/conf.db'
BCK_CONF_FILE = CONF_FILE + '.bck'

def bck_conf(OVSDB_NAME):
    """Save Open vSwitch database in json format
    """
    cmd = ['ovsdb-client', 'dump', OVSDB_NAME, '--format=json']
    try:
        res = process.system_out(cmd)
        if res[0] == 0:
            with open(BCK_CONF_FILE, 'w') as ovsdb_conf:
                ovsdb_conf.write(res[1])
        else:
            msg = u'Error running command [{0}]'.format(' '.join(cmd))
            raise RunCmdError(msg)
    except Exception as err:
        logger.error(err.message, exc_info=True)

    pass

def ovs_sw_exists(name):
    """ Check if a switch exists (search by name)
    """
    logger.debug("Checking for switch '{0}'...".format(name))
    cmd = [OVSVSCTL, 'list-br']
    try:
        res = process.system_out(cmd)
        if res[0] == 0:
            sws = res[1].split()
            if name in sws:
                return True
        else:
            msg = u'Error running command [{0}]'.format(' '.join(cmd))
            raise RunCmdError(msg)
    except Exception as err:
        logger.error(err.message, exc_info=True)
    return False

def ovs_port_exists(switch, name):
    """ Check if a port exists in a switch

        :param switch: Switch name
        :type switch: `str`
        :param name: Port name
        :type name: `str`
    """
    logger.debug("Checking for port {0} in switch...".format(name, switch))
    cmd = [OVSVSCTL, 'list-ports', switch]
    try:
        res = process.system_out(cmd)
        if res[0] == 0:
            if name in res[1].split():
                return True
        else:
            msg = u'Error running command [{0}]'.format(' '.join(cmd))
            raise RunCmdError(msg)
    except Exception as err:
        logger.error(err.message, exc_info=True)
    return False

def create_switch(name):
    """ Create a new switch

        :param name: virtual switch name
        :type name: `str`
    """
    if ovs_sw_exists(name):
        logger.info("Virtual switch {0} already exist".format(name))
    else:
        cmd = [OVSVSCTL, 'add-br', name]
        try:
            res = process.system_out(cmd)
            if res[0] == 0:
                logger.info("new switch {0} added".format(name))
            else:
                msg = u'Error running command [{0}]'.format(' '.join(cmd))
                raise RunCmdError(msg)
        except Exception as err:
            logger.error(err.message, exc_info=True)


def ovs_port_conf_get(port_name):
    """ Retreive port configuration

        :param name: Port name
        :type name: `str`
    """
    cmd = [OVSVSCTL,'-f', 'csv', 'find', 'Port']
    cmd.append(u'name={0}'.format(port_name))
    try:
        res = process.system_out(cmd)
        if res[0] == 0:
            conf = res[1].split('\n')
            data = list(csv.reader(conf))
            tag = data[1][data[0].index('tag')]
            if tag == "[]":
                tag = 0
            vlan_mode = data[1][data[0].index('vlan_mode')]

            return {'name': port_name, 'tag': tag, 'vlan_mode': vlan_mode}
        else:
            msg = u'Error running command [{0}]'.format(' '.join(cmd))
            raise RunCmdError(msg)
    except Exception as err:
        logger.error(err.message, exc_info=True)


def create_port(swname, portname, iface, vlan, vlanmode):
    cmd = [OVSVSCTL,'--', '--may-exist']
    if iface:
        cmd.extend(['add-port', swname, iface])
        cmd.append('name={0}'.format(portname))
        if vlanmode and vlan:
            if vlanmode == "trunk":
                if re.match("^[0-9,]*$", vlan):
                    cmd.append('trunk={0}'.format(vlan))
                    cmd.append('vlan_mode=trunk')
                else:
                    msg = u"With vlan mode 'trunk' "
                    msg += u"ovs_sw_vlan_tag must contain only digits and commas "
                    msg += u"([0-9] and ',') "
                    msg += u"\n|--> Please run gen_config and correct this value"
                    raise ConfigError(msg)
            elif vlanmode == "native-untagged":
                cmd.append('vlan_mode={0}'.format(vlanmode))
            else:
                cmd.append('vlan_mode={0}'.format(vlanmode))
                cmd.append('tag={0}'.format(vlan))
        elif vlan :
            cmd.append('tag={0}'.format(vlan))

    else:
        cmd.extend(['add-port', swname, portname])
        if vlan:
            cmd.append('tag={0}'.format(vlan))
            if vlanmode:
                cmd.append('vlan_mode={0}'.format(vlanmode))
    logger.debug(cmd)
    try:
        res = process.system_out(cmd)
        if res[0] == 0:
            logger.info("Port {0} created".format(portname))
        else:
            msg = u'error running command [{0}]'.format(' '.join(cmd))
            raise RunCmdError(msg)
    except Exception as err:
        logger.error(err.message, exc_info=True)

def clean_up(swname, iface=None, port=None):
    """ Remove port by interface or by port name, To support updating port configuration we remove
    the port with the interface, this way we can update all parameters
    :param swname: the switch name
    :type swname: `str`
    :type iface: `str`
    :param iface: interface name
    :type port: `str`
    :param port: port name
    """
    cmd = [OVSVSCTL, '--', u'--if-exists']
    target = None
    msg = ""
    if iface:
        cmd.append(u'--with-iface')
        msg = "\tCleaning port for interface"
        target = iface
    elif port:
        msg = "\tCleaning port"
        target = port

    cmd = cmd + [u'del-port', swname, target]

    try:
        if target:
            res = process.system_out(cmd)
            if res[0] == 0:
                logger.info("{0} {1}".format(msg,target))
            else:
                msg = u'error running command [{0}]'.format(' '.join(cmd))
                raise RunCmdError(msg)
    except Exception as err:
        logger.error(err.message, exc_info=True)

def main():
    client = CreoleClient()

    if client.get_creole('activer_openvswitch'):

        swname = client.get_creole('ovs_sw_name')
        zones  = client.get_creole('ovs_sw_zones')
        ifaces = client.get_creole('ovs_sw_zone_iface')
        vlans  = client.get_creole('ovs_sw_vlan_tag')
        tagged = client.get_creole('ovs_sw_tag_mode')

        ihm.print_line(u'Open vSwitch configuration')

        bck_conf(OVSDB_NAME)
        create_switch(swname)

        # Cleanning up all ports with interfaces
        netcards = get_net_devices()
        if client.get_creole('bonding_is_active') == 'oui':
            for nzone in range(int(client.get_creole('nombre_interfaces'))):
                name = client.get_creole(u'nom_zone_eth{0}'.format(nzone))
                if name not in netcards:
                    netcards.append(name)

        for card in netcards:
            clean_up(swname, iface=card)

        cs_zones = {}
        for cpt in range(len(zones)):
            name = zones[cpt]
            zn = { 'iface': client.get_creole(u'nom_zone_{0}'.format(ifaces[cpt])),
                   'vlan' : vlans[cpt],
                   'tag' : tagged[cpt]}
            if name in cs_zones.keys():
                nm = name + str(cpt)
                cs_zones[nm] = [zn]
            else:
                cs_zones[name] = [zn]

        for key in cs_zones.keys():
            for zn in cs_zones[key]:
                try:
                    clean_up(swname, port=key)
                    create_port(swname, key, zn['iface'], zn['vlan'], zn['tag'])
                except ConfigError as e:
                    print("ERROR : {0}".format(e))
                    exit(3)

    else:
        ihm.print_line(u'Open vSwitch disabled')

main()
