import os
import sys
import subprocess
import onapp.lib.log as log
if sys.version.startswith('2.4'):
    from onapp.lib.backport import any


"""
Generator() receives a dictionary of the following structure:

data = {
           'invicdn_enabled': True,
           'networks': {
                'x1wdef45we': {
                    'bridge_id':345
                    'cdna':{'mac':'01:16:34:53:fa','ip':'10.0.23.15'},
                    'vm_macs':['01:16:34:54:fa','01:16:34:61:fa']
                },
                'df4sdnh8bo': {
                    'bridge_id':346
                    'cdna':{'mac':'04:e6:73:a1:b2','ip':'63.231.65.18'},
                    'vm_macs':['04:e6:16:57:1c','a5:73:29:48:33']
                }
            }
        }
"""


class Processor:
    def __init__(self, data={}):
        self.data = data
        self.networks = data.get('networks', {})
        self.invicdn_enabled = data.get('invicdn_enabled', False)
        self.atomic_file = os.path.abspath('/home/mq/ebtables.dat')
        self.output_file = os.path.abspath('/home/mq/routing.sh')
        self.all_vm_macs = []
        self.all_cdna_macs = []
        self.script = "#!/bin/bash"
        self.head = ""
        self.body = ""

    '''
    1. Updates ebtables, ip routes, ip rules in runtime
    2. Stores relevant data into /home/mq/routing.sh

    Script
    # ----- HEAD ------
    iptables -A FORWARD -m mark ! --mark 0 -p tcp -m multiport --dports 80,443 -m comment --comment 'OnApp Accelerator' -j ACCEPT # shortcut that assumes no other onapp feature uses iptables/ebtables marking

    ebtables -t nat --atomic-file /tmp/ebtables.dat --atomic-init
    ebtables -t nat --atomic-file /tmp/ebtables.dat -F
    # ----- ---- ------
    ebtables -t nat --atomic-file /tmp/ebtables.dat -N ACCELERATE
    ebtables -t nat --atomic-file /tmp/ebtables.dat -A PREROUTING -p IPv4 --ip-proto tcp --ip-dport 80 --among-dst <all VM MACs> -j ACCELERATE
    ebtables -t nat --atomic-file /tmp/ebtables.dat -A ACCELERATE --among-src <all CDNA MACs> -j ACCEPT
    ebtables -t nat --atomic-file /tmp/ebtables.dat -A PREROUTING -p IPv4 --ip-proto tcp --ip-dport 443 --among-dst <all VM MACs> -j ACCELERATE
    # ----- ---- ------

    # ----- BODY ------
    arping -f -w 1 -I <bridge name> <CDNA IP>
    if [ $? -eq 0 ]; then
      ebtables -t nat --atomic-file /tmp/ebtables.dat -A ACCELERATE --logical-in <bridge name> --among-dst <VM MACs> -j mark --mark-set <10 + bridje ID> --mark-target CONTINUE

      ip rule list | grep ^<10 + bridje ID>
      if [ $? -gt 0 ]; then
        ip rule add fwmark <10 + bridje ID> lookup <10 + bridje ID>
      fi

      ip route list table 10 | grep "default via <CDNA IP>"
      if [ $? -gt 0 ]; then
        ip route add table <10 + bridje ID> default via <CDNA IP> dev <bridge name> onlink # note: check whether the rule already exists, don't add duplicate rules
      fi
    fi
    # ----- ---- ------

    ebtables -t nat --atomic-file /tmp/ebtables.dat --atomic-commit
    '''

    def update(self):
        atomic_file = self.atomic_file

        self.head += '''
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward > /dev/null 2>&1
echo 0 | sudo tee /proc/sys/net/ipv4/conf/all/rp_filter > /dev/null 2>&1

rm -f %(atomic_file)s
sudo ebtables -t nat --atomic-file %(atomic_file)s --atomic-init

# Remove legacy 2.0 rules
while [ $(sudo iptables -L FORWARD -n | fgrep "/* OnApp Accelerator 2.0 */" | wc -l) -gt 0 ]; do
    sudo iptables -D FORWARD -m mark ! --mark 0 -m comment --comment 'OnApp Accelerator 2.0' -j ACCEPT
done

# This rule enables the transparent feature where the accelerator spoofs client
# IP when making a request to the VM
if [ $(sudo iptables -L FORWARD -n | fgrep "/* OnApp Accelerator 2.1 */" | wc -l) -eq 0 ]; then
    sudo iptables -A FORWARD -m mark ! --mark 0 -m physdev ! --physdev-is-bridged -m comment --comment 'OnApp Accelerator 2.1' -j ACCEPT
fi
        ''' % locals()

        if self.invicdn_enabled:
            for net in self.networks.items():
                bridge_name = net[0]
                bridge_id = net[1].get('bridge_id')
                if bridge_id is None:
                    bridge_id = 1

                rule_id = str(int(bridge_id) + 10)
                vm_macs = net[1].get('vm_macs')
                cdna = net[1].get('cdna')
                if (bridge_name is None) or (cdna is None) \
                or (vm_macs is None) or (not any(vm_macs)):
                    continue

                cdna_ip = cdna.get('ip')
                cdna_mac = cdna.get('mac')

                self.all_vm_macs += vm_macs
                self.all_cdna_macs.append(cdna_mac)

                vm_mac_list = ",".join(vm_macs)

                self.body += '''
sudo arping -f -w 1 -I %(bridge_name)s %(cdna_ip)s
if [ $? -eq 0 ]; then
    sudo ebtables -t nat --atomic-file %(atomic_file)s -A ACCELERATE_IN --logical-in %(bridge_name)s --among-dst %(vm_mac_list)s -j mark --mark-set %(rule_id)s --mark-target CONTINUE
    sudo ebtables -t nat --atomic-file %(atomic_file)s -A ACCELERATE_OUT --logical-in %(bridge_name)s --among-src %(vm_mac_list)s -j mark --mark-set %(rule_id)s --mark-target CONTINUE

    sudo ip rule list | grep "lookup %(rule_id)s"
    if [ $? -gt 0 ]; then
      sudo ip rule add fwmark %(rule_id)s lookup %(rule_id)s
    fi

    sudo ip route list table %(rule_id)s | grep "default via %(cdna_ip)s"
    if [ $? -gt 0 ]; then
      sudo ip route replace table %(rule_id)s default via %(cdna_ip)s dev %(bridge_name)s onlink
    fi
fi

echo 0 | sudo tee /proc/sys/net/ipv4/conf/%(bridge_name)s/rp_filter > /dev/null 2>&1
                ''' % locals()

            all_vm_mac_list = ",".join(self.all_vm_macs)
            all_cdna_macs_list = ",".join(self.all_cdna_macs)
            if all_vm_mac_list:

                self.head += '''
sudo ebtables -t nat --atomic-file %(atomic_file)s -N ACCELERATE_IN
sudo ebtables -t nat --atomic-file %(atomic_file)s -N ACCELERATE_OUT
sudo ebtables -t nat --atomic-file %(atomic_file)s -A PREROUTING -p IPv4 --ip-proto tcp --ip-dport 80 --among-dst %(all_vm_mac_list)s -j ACCELERATE_IN
sudo ebtables -t nat --atomic-file %(atomic_file)s -A PREROUTING -p IPv4 --ip-proto tcp --ip-dport 443 --among-dst %(all_vm_mac_list)s -j ACCELERATE_IN
sudo ebtables -t nat --atomic-file %(atomic_file)s -A PREROUTING -p IPv4 --ip-proto tcp --ip-sport 80 --among-src %(all_vm_mac_list)s -j ACCELERATE_OUT
sudo ebtables -t nat --atomic-file %(atomic_file)s -A PREROUTING -p IPv4 --ip-proto tcp --ip-sport 443 --among-src %(all_vm_mac_list)s -j ACCELERATE_OUT
sudo ebtables -t nat --atomic-file %(atomic_file)s -A ACCELERATE_IN --among-src %(all_cdna_macs_list)s -j ACCEPT
sudo ebtables -t nat --atomic-file %(atomic_file)s -A ACCELERATE_OUT --among-dst %(all_cdna_macs_list)s -j ACCEPT
                ''' % locals()
                self.body += '''
sudo ebtables -t nat --atomic-file %(atomic_file)s -A ACCELERATE_IN -j redirect
sudo ebtables -t nat --atomic-file %(atomic_file)s -A ACCELERATE_OUT -j redirect
                ''' % locals()

        self.script += self.head
        self.script += self.body
        self.script += "sudo ebtables -t nat --atomic-file %s --atomic-commit " % atomic_file
        self._write(self.script)
        os.system('chmod +x %s' % self.output_file)
        subprocess.call(self.output_file)

    def cleanup(self):
        self.update()
        bridge_id = self.data.get('bridge_id')
        if bridge_id is None:
            return
        rule_id = str(int(bridge_id) + 10)
        os.system("sudo ip route flush table %s" % rule_id)
        os.system("sudo ip rule del lookup %s" % rule_id)

    def _write(self, script=""):
        try:
            f = open(self.output_file, 'w')

            try:
                f.write(script)
            finally:
                f.close()
        except StandardError:
            log.error("Error while writing %s" % self.output_file)
