#!/usr/bin/python

from p4app import P4Mininet
from p4app import P4Mininet, P4Program
from mininet.topo import Topo
from mininet.node import CPULimitedHost
from mininet.link import TCLink
from mininet.net import Mininet
from mininet.log import lg
from mininet.util import dumpNodeConnections, custom
from mininet.cli import CLI

from subprocess import Popen, PIPE
from time import sleep, time
from multiprocessing import Process
from argparse import ArgumentParser

from util.monitor import monitor_devs_ng, monitor_cpu

import shlex
import sys
import os, shutil


class FailureTopo(Topo):
    def __init__(self, n, core_file, edge_file, **opts):
        Topo.__init__(self, **opts)

        switches = []
        core_prog = P4Program(core_file)
        edge_prog = P4Program(edge_file)        

        for i in xrange(1, n+1):
			# Instantiate hosts
			ip = "10.0.%d.%d" %( i,  i)
			mac = '00:00:00:00:%02x:%02x' %( i,  i)
			host = self.addHost('h%d' % i, ip = ip, mac = mac)
			# Instantiate vnfs
			ip = "10.0.%d.%d" %( i,  i+1)
			mac = '00:00:00:00:%02x:%02x' %( i,  i+1)
			vnf = self.addHost("vnf%s" % str(i), ip = ip, mac = mac)
			# Instantiate switches and links
			edge = self.addSwitch('e%d' % i, program=edge_prog)		
			switch = self.addSwitch('s%d' % i, program=core_prog) 
			self.addLink(host, edge)			
			self.addLink(vnf, edge)	
			self.addLink(edge, switch)
			switches.append(switch)
			
        # Connection between core switches 
        switch1 = switches[0]
        switch2 = switches[1]  
        switch3 = switches[2]        
        switch4 = switches[3]
        switch5 = switches[4]     
        switch6 = switches[5]
        switch7 = switches[6]  
        switch8 = switches[7]        
        switch9 = switches[8]
        switch10 = switches[9]                           
        
        self.addLink(switch1, switch2)
        self.addLink(switch1, switch3)
        self.addLink(switch1, switch9)
        self.addLink(switch2, switch3)
        self.addLink(switch2, switch4)
        self.addLink(switch3, switch4)
        self.addLink(switch3, switch7)
        self.addLink(switch3, switch8)
        self.addLink(switch4, switch5)
        self.addLink(switch4, switch6)
        self.addLink(switch4, switch7)
        self.addLink(switch5, switch6)
        self.addLink(switch6, switch7)
        self.addLink(switch6, switch10)
        self.addLink(switch7, switch8)
        self.addLink(switch7, switch10)
        self.addLink(switch8, switch9)
        self.addLink(switch8, switch10)
        self.addLink(switch9, switch10)  


def config_network(bw, method, n, cfgdir):

	core_file = method+"-core.p4"
	edge_file = method+"-edge.p4"
	
	topo = FailureTopo(n, core_file, edge_file)
	link = custom(TCLink, bw=bw)
	net = P4Mininet(program=core_file, topo=topo, link=link)
	net.start()
	
	print 'Switches Configuration'
	
	print 'Switches Configuration'

	for i in range(1, n+1):
		sw = net.get('s%d'% i)	
		ed = net.get('e%d'% i)	
		fname = cfgdir+'/'+method+'-fabric/s%d'% i+'-commands.txt'
		print 'Configuring switch: '+'s%d'% i
		print 'Config filename: '+fname
		with open(fname, 'r') as file:
			cmd = file.read()
		sw.command(cmd)
		
		fname = cfgdir+'/'+method+'-fabric/e%d'% i+'-commands.txt'
		print 'Configuring edge: '+'e%d'% i
		print 'Config filename: '+fname
		with open(fname, 'r') as file:
			cmd = file.read()
		ed.command(cmd)		

		
	return net

def start_vnfs(net):
		
	#start L2 forwarding SF at all VNFs
	for vnf in net.hosts:
		vnfname = vnf.name
		if(vnfname.startswith('vnf')):
			print ('Starting VNF %s on IP %s:' %(vnf.name, vnf.IP()))
			vnf.cmd("chmod +x scripts/forward.py")
			result = vnf.cmd('./scripts/forward.py &')
			print result  

def run_failure_test(net, bw, testdir, testnumber, method):

	print "Starting failure test"	
	
	pmigrationdir = testdir+"/failure_test"+str(testnumber)
	path = os.getcwd()+"/"+pmigrationdir
	
	if(not(os.path.exists(path))):
		os.mkdir(path)

	outputfolder = "%s_bw%s" %(method, bw)
	path = path + "/"+outputfolder		
	
	if(os.path.exists(path)):
		shutil.rmtree(path)

	os.mkdir(path)
	
	#it = 30
	it = 1
	step = int(bw*1000/10)
	initial = 5* step
	limit = initial+step
	#limit = 9*step
	for btraffic in range (initial, limit, step):
	
		itdir = path+"/"+str(btraffic)	
		
		if(os.path.exists(itdir)):
			shutil.rmtree(itdir)

		os.mkdir(itdir)		
	
		for i in range(1,it+1):
			run_failure_test_it(net, itdir, method, i, btraffic)
		
	

def run_failure_test_it(net, path, method, i, btraffic):

	print "Iteration %d of traffic %d Kbps" %(i,btraffic)	

	#Source of UDP traffic
	vms = net.get('h1')
	#Destination of UDP traffic
	vmd = net.get('h6')	
	#Core switches
	s4 = net.get('s4')
	s3 = net.get('s3')
	s5 = net.get('s5')
	s7 = net.get('s7')

	#Start servers
	vmd.cmd('iperf -s -u -l 1200 > /dev/null &')
	
	## Start the bandwidth monitor in the background for destination interface
	filenamerx = path+"/rx.bwm"
	vmd.cmd("bwm-ng -t 1000 -I eth0 -o csv -u bytes -T rate -C ',' > %s &" %filenamerx)
	
	filenametx = path+"/tx.bwm"
	vms.cmd("bwm-ng -t 1000 -I eth0 -o csv -u bytes -T rate -C ',' > %s &" %filenametx)
		
	filenames3 = path+"/s3-eth4.bwm"
	s3.cmd("bwm-ng -t 1000 -I s3-eth4 -o csv -u bytes -T rate -C ',' > %s &" %filenames3)

	filenames5 = path+"/s5-eth2.bwm"
	s5.cmd("bwm-ng -t 1000 -I s5-eth2 -o csv -u bytes -T rate -C ',' > %s &" %filenames5)	

	filenames7 = path+"/s7-eth3.bwm"
	s7.cmd("bwm-ng -t 1000 -I s7-eth3 -o csv -u bytes -T rate -C ',' > %s &" %filenames7)	
		
	result = vms.cmd("netstat -s")
	print result
		
	sleep(10)
		
	#Start UDP flow
	vms.cmd('iperf -c 10.0.6.6 -u -b %sk -l 1200 -t 75 > /dev/null &' % (str(btraffic)))
	
	sleep(30)
	
	#link failure
	net.configLinkStatus('s4','s6','down')
	cmd = 'table_add port_status deflect 5 => 6'
	result = s4.command(cmd)
	print result
	
	sleep(50)
	
	result = vms.cmd("netstat -s")
	print result
		
	os.system("killall -9 iperf")
	sleep(10)
	os.system("killall -9 bwm-ng")
	
	#https://stackoverflow.com/questions/21309020/remove-odd-or-even-lines-from-a-text-file
	#keep odd lines: sed 'n; d' infile > outfile
	#keep even lines: sed '1d; n; d' infile > outfile
	#Remove total lines
	outputrx = path+"/a_"+str(i)+"_rx.bwm"
	vmd.cmd("sed 'n; d' %s > %s " %(filenamerx,outputrx))
	
	outputtx = path+"/a_"+str(i)+"_tx.bwm"
	vms.cmd("sed 'n; d' %s > %s " %(filenametx,outputtx))
	
	outputs3 = path+"/a_"+str(i)+"_s3-eth4.bwm"
	s3.cmd("sed 'n; d' %s > %s " %(filenames3,outputs3))

	outputs5 = path+"/a_"+str(i)+"_s5-eth2.bwm"
	s5.cmd("sed 'n; d' %s > %s " %(filenames5,outputs5))
	
	outputs7 = path+"/a_"+str(i)+"_s7-eth3.bwm"
	s7.cmd("sed 'n; d' %s > %s " %(filenames7,outputs7))	

	#restore link failure
	net.configLinkStatus('s4','s6','up')
	cmd = 'table_delete port_status 0'
	result = s4.command(cmd)
	print result


def run_failure_test_tcp(net, bw, testdir, testnumber, method):

	print "Starting failure test"	
	
	pmigrationdir = testdir+"/failure_test_tcp"+str(testnumber)
	path = os.getcwd()+"/"+pmigrationdir
	
	if(not(os.path.exists(path))):
		os.mkdir(path)

	outputfolder = "%s_bw%s" %(method, bw)
	path = path + "/"+outputfolder		
	
	if(os.path.exists(path)):
		shutil.rmtree(path)

	os.mkdir(path)
	
	#it = 30
	it = 1
	itdir = path+"/"+str(it)	
		
	if(os.path.exists(itdir)):
		shutil.rmtree(itdir)

	os.mkdir(itdir)		
	for i in range(1,it+1):
		run_failure_test_it_tcp(net, itdir, method, i)
		
	

def run_failure_test_it_tcp(net, path, method, i):

	print "Iteration %d" %(i)	

	#Source of UDP traffic
	vms = net.get('h1')
	#Destination of UDP traffic
	vmd = net.get('h6')	
	#Core switches
	s4 = net.get('s4')
	s3 = net.get('s3')
	s5 = net.get('s5')
	s7 = net.get('s7')

	#Start servers
	vmd.cmd('iperf -s -M 1200 > /dev/null &')
	
	## Start the bandwidth monitor in the background for destination interface
	filenamerx = path+"/rx.bwm"
	vmd.cmd("bwm-ng -t 1000 -I eth0 -o csv -u bytes -T rate -C ',' > %s &" %filenamerx)
	
	filenametx = path+"/tx.bwm"
	vms.cmd("bwm-ng -t 1000 -I eth0 -o csv -u bytes -T rate -C ',' > %s &" %filenametx)
		
	filenames3 = path+"/s3-eth4.bwm"
	s3.cmd("bwm-ng -t 1000 -I s3-eth4 -o csv -u bytes -T rate -C ',' > %s &" %filenames3)

	filenames5 = path+"/s5-eth2.bwm"
	s5.cmd("bwm-ng -t 1000 -I s5-eth2 -o csv -u bytes -T rate -C ',' > %s &" %filenames5)	

	filenames7 = path+"/s7-eth3.bwm"
	s7.cmd("bwm-ng -t 1000 -I s7-eth3 -o csv -u bytes -T rate -C ',' > %s &" %filenames7)	
		
	result = vms.cmd("netstat -s")
	print result
		
	sleep(10)
		
	#Start TCPS flow
	vms.cmd('iperf -c 10.0.6.6 -M 1200 -t 75 > /dev/null &')
	
	sleep(30)
	
	#link failure
	net.configLinkStatus('s4','s6','down')
	cmd = 'table_add port_status deflect 5 => 6'
	result = s4.command(cmd)
	print result
	
	sleep(50)
	
	result = vms.cmd("netstat -s")
	print result
		
	os.system("killall -9 iperf")
	sleep(10)
	os.system("killall -9 bwm-ng")
	
	#https://stackoverflow.com/questions/21309020/remove-odd-or-even-lines-from-a-text-file
	#keep odd lines: sed 'n; d' infile > outfile
	#keep even lines: sed '1d; n; d' infile > outfile
	#Remove total lines
	outputrx = path+"/a_"+str(i)+"_rx.bwm"
	vmd.cmd("sed 'n; d' %s > %s " %(filenamerx,outputrx))
	
	outputtx = path+"/a_"+str(i)+"_tx.bwm"
	vms.cmd("sed 'n; d' %s > %s " %(filenametx,outputtx))
	
	outputs3 = path+"/a_"+str(i)+"_s3-eth4.bwm"
	s3.cmd("sed 'n; d' %s > %s " %(filenames3,outputs3))

	outputs5 = path+"/a_"+str(i)+"_s5-eth2.bwm"
	s5.cmd("sed 'n; d' %s > %s " %(filenames5,outputs5))
	
	outputs7 = path+"/a_"+str(i)+"_s7-eth3.bwm"
	s7.cmd("sed 'n; d' %s > %s " %(filenames7,outputs7))

	#restore link failure
	net.configLinkStatus('s4','s6','up')
	cmd = 'table_delete port_status 0'
	result = s4.command(cmd)
	print result


def main():
	lg.setLogLevel('info')  
	
	if len(sys.argv) > 1:
		method = sys.argv[1]
	else:
		method = "method"

	print "Setting-up topology for SFC migration experiment " 
		
	testdir="test" # output directory

	bw=10
	n = 10

	#testnumber=1
	testnumber=2
	cfgdir='cfg'+str(testnumber)
	
	net = config_network(bw, method, n, cfgdir)
	
	method = method+"-fabric"	
	
	start_vnfs(net)
	sleep(10)
	
	os.system("killall -9 iperf")
	os.system("killall -9 ping")
	os.system("killall -9 bwm-ng")

	start = time()
	print "Starting experiment"
		
	run_failure_test(net, bw, testdir, testnumber, method)
	sleep(60)		

	run_failure_test_tcp(net, bw, testdir, testnumber, method)
	sleep(60)		
		
	print('Please wait until the experiment is complete...')    
		
	#CLI(net)
	net.stop()
	end = time()
	print("Experiment took %.3f seconds" % (end - start))


if __name__ == '__main__':
    main()
