#!/usr/bin/env python

from pyeole import ihm
from pyeole import process
from creole.client import CreoleClient
from tempfile import mkstemp

import csv
from io import StringIO
import sys
import os
import csv
import logging
import re

LOG_FILE = '/var/log/one/eole-one-node.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.DEBUG)

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

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


class RunCmdError(Exception):
    pass


class OneClient():

    def __init__(self, user):
        self.user = None
        self.auth = None
        self.root = '/var/lib/one'

        if user:
            self.user = user
        else:
            self.user = 'oneadmin'

        user_info = process.system_out(['getent', 'passwd', user])[1]
        if user_info:
            self.root = user_info.split(':')[5]

        command = ['cat', u'{0}/.one/one_auth'.format(self.root)]
        res = process.system_out(command)
        if res[0] == 0:
            self.auth = res[1].split(':')

    def __run_cmd__(self, cmd):
        command = list(cmd)
        command.extend(['--user', self.auth[0]])
        command.extend(['--password', self.auth[1]])

        res = process.system_out(command)
        if res[0] == 0:
            if 'list' in cmd:
                out_lines = []
                if res:
                    csv_io = StringIO(unicode(res[1]))
                    rows =  csv.reader(csv_io, delimiter=',')
                    for row in rows:
                        if not row or row[0] == 'ID':
                            next
                        else:
                            out_lines.append(row)
                return out_lines
            else:
                return res
        else:
            return False

    def get_hosts(self):
        """ get the list of hosts
        """
        cmd = ['onehost', 'list', '--csv']
        cmd.extend(['-l', 'ID,NAME'])
        res = self.__run_cmd__(cmd)
        return(res)

    def get_clusters(self):
        """ get the cluster list
        """
        cmd = ['onecluster', 'list', '--csv']
        cmd.extend(['-l', 'ID,NAME'])
        return self.__run_cmd__(cmd)

    def get_networks(self):
        """ get the virtual network list
        """
        cmd = ['onevnet', 'list', '--csv']
        cmd.extend(['-l', 'ID,NAME'])
        return self.__run_cmd__(cmd)

    def get_cluster_id_by_name(self, name):
        cmd = ['onecluster', 'list', '--csv']
        cmd.extend(['-f', 'NAME={0}'.format(name)])
        res = self.__run_cmd__(cmd)
        ID = res[0][0]
        return ID

    def get_vnet_id_by_name(self, name):
        cmd = ['onevnet', 'list', '--csv']
        cmd.extend(['-f', 'NAME={0}'.format(name)])
        res = self.__run_cmd__(cmd)
        ID = res[0][0]
        return ID

    def create_network(self, templatefile, cluster, vnet_name):
        """ Create a network
        """
        cmd = ['onevnet', 'create']
        cmd.extend(['--user', self.auth[0]])
        cmd.extend(['--password', self.auth[1][:-1]])
        #cmd.extend(['-c', cluster])
        cmd.append(templatefile)
        res = process.system_out(cmd)
        if res[0] == 0:
            clt_id = self.get_cluster_id_by_name(cluster)
            vnet_id = self.get_vnet_id_by_name(vnet_name)
            res = self.__run_cmd__(['onecluster', 'addvnet', clt_id, vnet_id])
            os.remove(templatefile)
            if not res:
                print("Error attaching {0} vnet to {1} cluster".format(vnet_name, cluster))
                return False
            else:
                return True
        else:
            logger.error("Creation of virtual network with template {0} failed".format(templatefile))
            return False

    def update_network(self, templatefile, cluster, vnet_name):
        """ Update a network
        """
        vnet_id = self.get_vnet_id_by_name(vnet_name)
        cmd = ['onevnet', 'update']
        cmd.extend(['--user', self.auth[0]])
        cmd.extend(['--password', self.auth[1][:-1]])
        cmd.extend([vnet_id, templatefile])

        res = process.system_out(cmd)
        if res[0] == 0:
            os.remove(templatefile)
            return True
        else:
            logger.error("Update of virtual network with template {0} failed".format(templatefile))
            return False

    def delete_network(self, vnet_id):
        cmd = ['onevnet', 'delete']
        cmd.extend(['--user', self.auth[0]])
        cmd.extend(['--password', self.auth[1][:-1]])
        cmd.append(vnet_id)

        res = process.system_out(cmd)
        if res[0] == 0:
            ihm.print_line("Network {0} deleted".format(vnet_id))
            return True
        else:
            logger.error("Error deleting network {0}".format(vnet_id))
            ihm.print_line("Error deleting network {0}".format(vnet_id))
            return False


class OneNetwork():
    def create(self, one_client):
        tmpl_file = self.create_template()
        if one_client.create_network(tmpl_file, self.cluster, self.zone):
            ihm.print_line("Virtual network {0} created".format(self.zone))
            return True
        else:
            ihm.print_line("Error Creating virtual network {0}".format(self.zone))
            return False

    def update(self, one_client):
        tmpl_file = self.create_template(True)
        if one_client.update_network(tmpl_file, self.cluster, self.zone):
            ihm.print_line("Virtual network {0} updated".format(self.zone))
            return True
        else:
            ihm.print_line("Error Updating virtual network {0}".format(self.zone))
            return False

    def manage(self, one_client):
        found = False
        vnet = one_client.get_networks()
        network_name = self.zone
        for net in vnet:
            if network_name in net:
                found = True
                break

        if not found:
            return self.create(one_client)
        else:
            return self.update(one_client)

class OneNetworkL3(OneNetwork):
    def __init__(self, net_info, cluster):
        self.swname = net_info[0]
        self.zone = u'{0}{1}'.format(net_info[10], net_info[1])
        self.vlan = net_info[2]
        self.vnet_addr = net_info[3]
        self.vnet_mask = net_info[4]
        self.vnet_gw = net_info[5]
        self.vnet_rg_start = net_info[6]
        self.vnet_rg_size = net_info[7]
        self.vnet_dns = net_info[8]
        self.vnet_trunk = net_info[9]
        self.cluster = cluster

    def create_template(self, update=False):
        fd, tmp_path = mkstemp(prefix='oneVnet-')
        template = open(tmp_path, 'w')
        template.write('NAME = "{0}"\n'.format(self.zone))
        template.write('VN_MAD = ovswitch\n')
        if (update is False):
            if self.vnet_rg_start and self.vnet_rg_size:
                template.write('AR=[\n')
                template.write('TYPE = "IP4",\n')
                template.write('IP = "{0}",\n'.format(self.vnet_rg_start))
                template.write('SIZE = "{0}"\n'.format(self.vnet_rg_size))
                template.write(']\n')
            else:
                template.write('TYPE = FIXED\n')

        if self.vlan:
            template.write('VLAN = yes\n')
            template.write('VLAN_ID = {0}\n'.format(self.vlan))

        if self.vnet_trunk:
            template.write('VLAN_TAGGED_ID = {0}\n'.format(self.vnet_trunk))

        template.write('BRIDGE = {0}\n'.format(self.swname))
        template.write('NETWORK_ADDRESS = {0}\n'.format(self.vnet_addr))
        template.write('NETWORK_MASK = {0}\n'.format(self.vnet_mask))
        template.write('GATEWAY = {0}\n'.format(self.vnet_gw))
        template.write('DNS = {0}\n'.format(self.vnet_dns))
        template.close()
        return tmp_path


class OneNetworkL2(OneNetwork):
    def __init__(self, net_info, cluster):
        self.swname = net_info[0]
        self.zone = u'{0}{1}'.format(net_info[6], net_info[1])
        self.net_size = net_info[2]
        self.first_mac = net_info[3]
        self.tag = net_info[4]
        self.trunk = net_info[5]
        self.cluster = cluster

    def create_template(self,update=False):
        fd, tmp_path = mkstemp(prefix='oneVnet-')
        template = open(tmp_path, 'w')
        template.write('NAME = "{0}"\n'.format(self.zone))
        template.write('VN_MAD = ovswitch\n')

        if self.tag:
            template.write('VLAN = yes\n')
            template.write('VLAN_ID = "{0}"\n'.format(self.tag))

        if self.trunk:
            template.write('VLAN_TAGGED_ID = "{0}""\n'.format(self.trunk))

        template.write('BRIDGE = {0}\n'.format(self.swname))
        if update is False and self.net_size:
            template.write("AR=[\n")
            template.write('  TYPE = "ETHER",\n')

            if self.first_mac:
                template.write('  MAC = "{0}",\n'.format(self.first_mac))

            template.write('  SIZE = "{0}"\n'.format(self.net_size))
            template.write("]\n")
        template.close()
        return tmp_path

def main():
    client = CreoleClient()
    one_client = OneClient('oneadmin')
    networks = []
    # ref https://dev-eole.ac-dijon.fr/issues/16797
    # cluster = client.get_creole('one_cluster_name')
    cluster = "default"
    swname = client.get_creole('ovs_sw_name')
    zones = client.get_creole('vnets')
    vlans = client.get_creole('vnet_vlan_tag')
    vnet_addr = client.get_creole('vnet_network_addr')
    vnet_mask = client.get_creole('vnet_network_mask')
    vnet_dns = client.get_creole('vnet_network_dns')
    vnet_gw = client.get_creole('vnet_network_gw')
    vnet_rg_start = client.get_creole('vnet_range_start')
    vnet_rg_size = client.get_creole('vnet_range_size')
    vnet_trunk = client.get_creole('vnet_vlan_trunk')

    l2_vnet = client.get_creole('l2_vnets')
    l2_vnet_size = client.get_creole('l2_vnet_size')
    l2_vnet_vlan_tag = client.get_creole('l2_vnet_vlan_tag')
    l2_vnet_vlan_trunk = client.get_creole('l2_vnet_vlan_trunk')
    l2_vnet_first_mac = client.get_creole('l2_vnet_first_mac')

    net_prefix = "CR_"

    processed = []
    for cpt in range(len(zones)):
        if zones[cpt] not in processed:
            info = []
            info.append(swname)
            info.append(zones[cpt])
            info.append(vlans[cpt])
            info.append(vnet_addr[cpt])
            info.append(vnet_mask[cpt])
            info.append(vnet_gw[cpt])
            info.append(vnet_rg_start[cpt])
            info.append(vnet_rg_size[cpt])
            info.append(vnet_dns[cpt])
            info.append(vnet_trunk[cpt])
            info.append(net_prefix)
            networks.append(OneNetworkL3(info, cluster))
            processed.append(zones[cpt])

    for i in range(len(l2_vnet)):
        if l2_vnet[i] not in processed:
            net_info = []
            net_info.append(swname)
            net_info.append(l2_vnet[i])
            net_info.append(l2_vnet_size[i])
            net_info.append(l2_vnet_first_mac[i])
            net_info.append(l2_vnet_vlan_tag[i])
            net_info.append(l2_vnet_vlan_trunk[i])
            net_info.append(net_prefix)
            networks.append(OneNetworkL2(net_info, cluster))
            processed.append(l2_vnet[i])

    if client.get_creole('activer_openvswitch'):
        for network in networks:
            if not network.manage(one_client):
                exit(1)
    else:
        ihm.print_line(u'Open vSwitch disabled no need to configure virtual networks')

    networks = one_client.get_networks()
    for net in networks:
        name = net[1]
        if name.startswith(net_prefix):
            if not name[3:] in zones and not name[3:] in l2_vnet:
                one_client.delete_network(net[0])

    exit(0)

main()
