"""
Copyright (c) 2015 SONATA-NFV
ALL RIGHTS RESERVED.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
nor the names of its contributors may be used to endorse or promote
products derived from this software without specific prior written
permission.

This work has been performed in the framework of the SONATA project,
funded by the European Commission under Grant number 671517 through
the Horizon 2020 and 5G-PPP programmes. The authors would like to
acknowledge the contributions of their colleagues of the SONATA
partner consortium (www.sonata-nfv.eu).
"""
"""
Test suite to automatically test emulator functionalities.
Directly interacts with the emulator through the Mininet-like
Python API.

Does not test API endpoints. This is done in separated test suites.
"""

import time
import unittest
from emuvim.dcemulator.node import EmulatorCompute
from emuvim.test.base import SimpleTestTopology
from mininet.node import RemoteController


#@unittest.skip("disabled topology tests for development")
class testEmulatorTopology( SimpleTestTopology ):
    """
    Tests to check the topology API of the emulator.
    """

    def testSingleDatacenter(self):
        """
        Create a single data center and add check if its switch is up
        by using manually added hosts. Tests especially the
        data center specific addLink method.
        """
        # create network
        self.createNet(nswitches=0, ndatacenter=1, nhosts=2, ndockers=0)
        # setup links
        self.net.addLink(self.dc[0], self.h[0])
        self.net.addLink(self.h[1], self.dc[0])
        # start Mininet network
        self.startNet()
        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 0)
        self.assertTrue(len(self.net.hosts) == 2)
        self.assertTrue(len(self.net.switches) == 1)
        # check connectivity by using ping
        self.assertTrue(self.net.ping([self.h[0], self.h[1]]) <= 0.0)
        # stop Mininet network
        self.stopNet()

    #@unittest.skip("disabled to test if CI fails because this is the first test.")
    def testMultipleDatacenterDirect(self):
        """
        Create a two data centers and interconnect them.
        """
        # create network
        self.createNet(nswitches=0, ndatacenter=2, nhosts=2, ndockers=0)
        # setup links
        self.net.addLink(self.dc[0], self.h[0])
        self.net.addLink(self.h[1], self.dc[1])
        self.net.addLink(self.dc[0], self.dc[1])
        # start Mininet network
        self.startNet()
        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 0)
        self.assertTrue(len(self.net.hosts) == 2)
        self.assertTrue(len(self.net.switches) == 2)
        # check connectivity by using ping
        self.assertTrue(self.net.ping([self.h[0], self.h[1]]) <= 0.0)
        # stop Mininet network
        self.stopNet()

    def testMultipleDatacenterWithIntermediateSwitches(self):
        """
        Create a two data centers and interconnect them with additional
        switches between them.
        """
        # create network
        self.createNet(
            nswitches=3, ndatacenter=2, nhosts=2, ndockers=0,
            autolinkswitches=True)
        # setup links
        self.net.addLink(self.dc[0], self.h[0])
        self.net.addLink(self.h[1], self.dc[1])
        self.net.addLink(self.dc[0], self.s[0])
        self.net.addLink(self.s[2], self.dc[1])
        # start Mininet network
        self.startNet()
        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 0)
        self.assertTrue(len(self.net.hosts) == 2)
        self.assertTrue(len(self.net.switches) == 5)
        # check connectivity by using ping
        self.assertTrue(self.net.ping([self.h[0], self.h[1]]) <= 0.0)
        # stop Mininet network
        self.stopNet()

class testEmulatorNetworking( SimpleTestTopology ):

    def testSDNChainingSingleService_withLearning(self):
        """
        Create a two data centers and interconnect them with additional
        switches between them.
        Uses Ryu SDN controller.
        Connect the Docker hosts to different datacenters and setup the links between.
        """
        # create network
        self.createNet(
            nswitches=3, ndatacenter=2, nhosts=0, ndockers=0,
            autolinkswitches=True,
            controller=RemoteController,
            enable_learning=True)
        # setup links
        self.net.addLink(self.dc[0], self.s[0])
        self.net.addLink(self.s[2], self.dc[1])
        # start Mininet network
        self.startNet()

        # add compute resources
        vnf1 = self.dc[0].startCompute("vnf1", network=[{'id':'intf1', 'ip':'10.0.10.1/24'}])
        vnf2 = self.dc[1].startCompute("vnf2", network=[{'id':'intf2', 'ip':'10.0.10.2/24'}])
        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 2)
        self.assertTrue(len(self.net.hosts) == 2)
        self.assertTrue(len(self.net.switches) == 5)
        # check status
        # check get status
        s1 = self.dc[0].containers.get("vnf1").getStatus()
        print s1
        self.assertTrue(s1["name"] == "vnf1")
        self.assertTrue(s1["state"]["Running"])
        self.assertTrue(s1["network"][0]['intf_name'] == 'intf1')
        self.assertTrue(s1["network"][0]['ip'] == '10.0.10.1/24')

        s2 = self.dc[1].containers.get("vnf2").getStatus()
        print s2
        self.assertTrue(s2["name"] == "vnf2")
        self.assertTrue(s2["state"]["Running"])
        self.assertTrue(s2["network"][0]['intf_name'] == 'intf2')
        self.assertTrue(s2["network"][0]['ip'] == '10.0.10.2/24')

        # should be connected because learning = True
        self.assertTrue(self.net.ping([vnf1, vnf2]) <= 0.0)
        # setup links
        self.net.setChain('vnf1', 'vnf2', 'intf1', 'intf2', bidirectional=True, cmd='add-flow')
        # should still be connected
        self.assertTrue(self.net.ping([vnf1, vnf2]) <= 0.0)
        # stop Mininet network
        self.stopNet()

    def testSDNChainingSingleService(self):
        """
        Create a two data centers and interconnect them with additional
        switches between them.
        Uses Ryu SDN controller.
        Connect the Docker hosts to different datacenters and setup the links between.
        """
        # create network
        self.createNet(
            nswitches=3, ndatacenter=2, nhosts=0, ndockers=0,
            autolinkswitches=True,
            controller=RemoteController,
            enable_learning=False)
        # setup links
        self.net.addLink(self.dc[0], self.s[0])
        self.net.addLink(self.s[2], self.dc[1])
        # start Mininet network
        self.startNet()

        # add compute resources
        vnf1 = self.dc[0].startCompute("vnf1", network=[{'id':'intf1', 'ip':'10.0.10.1/24'}])
        vnf2 = self.dc[1].startCompute("vnf2", network=[{'id':'intf2', 'ip':'10.0.10.2/24'}])
        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 2)
        self.assertTrue(len(self.net.hosts) == 2)
        self.assertTrue(len(self.net.switches) == 5)
        # check status
        # check get status
        s1 = self.dc[0].containers.get("vnf1").getStatus()
        print s1
        self.assertTrue(s1["name"] == "vnf1")
        self.assertTrue(s1["state"]["Running"])
        self.assertTrue(s1["network"][0]['intf_name'] == 'intf1')
        self.assertTrue(s1["network"][0]['ip'] == '10.0.10.1/24')

        s2 = self.dc[1].containers.get("vnf2").getStatus()
        print s2
        self.assertTrue(s2["name"] == "vnf2")
        self.assertTrue(s2["state"]["Running"])
        self.assertTrue(s2["network"][0]['intf_name'] == 'intf2')
        self.assertTrue(s2["network"][0]['ip'] == '10.0.10.2/24')

        # should be not not yet connected
        self.assertTrue(self.net.ping([vnf1, vnf2]) > 0.0)
        # setup links
        self.net.setChain('vnf1', 'vnf2', 'intf1', 'intf2', bidirectional=True, cmd='add-flow')
        # check connectivity by using ping
        self.assertTrue(self.net.ping([vnf1, vnf2]) <= 0.0)
        # stop Mininet network
        self.stopNet()

    def testSDNChainingMultiService(self):
        """
        Create a two data centers and interconnect them with additional
        switches between them.
        Uses Ryu SDN controller.
        Setup 2 services and setup isolated paths between them
        Delete only the first service, and check that other one still works
        """
        # create network
        self.createNet(
            nswitches=3, ndatacenter=2, nhosts=0, ndockers=0,
            autolinkswitches=True,
            controller=RemoteController,
            enable_learning=False)
        # setup links
        self.net.addLink(self.dc[0], self.s[0])
        self.net.addLink(self.s[2], self.dc[1])
        # start Mininet network
        self.startNet()

        ## First Service
        # add compute resources
        vnf1 = self.dc[0].startCompute("vnf1", network=[{'id': 'intf1', 'ip': '10.0.10.1/24'}])
        vnf2 = self.dc[1].startCompute("vnf2", network=[{'id': 'intf2', 'ip': '10.0.10.2/24'}])
        # setup links
        self.net.setChain('vnf1', 'vnf2', 'intf1', 'intf2', bidirectional=True, cmd='add-flow', cookie=1)
        # check connectivity by using ping
        self.assertTrue(self.net.ping([vnf1, vnf2]) <= 0.0)

        ## Second Service
        # add compute resources
        vnf11 = self.dc[0].startCompute("vnf11", network=[{'id': 'intf1', 'ip': '10.0.20.1/24'}])
        vnf22 = self.dc[1].startCompute("vnf22", network=[{'id': 'intf2', 'ip': '10.0.20.2/24'}])

        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 4)
        self.assertTrue(len(self.net.hosts) == 4)
        self.assertTrue(len(self.net.switches) == 5)

        # setup links
        self.net.setChain('vnf11', 'vnf22', 'intf1', 'intf2', bidirectional=True, cmd='add-flow', cookie=2)
        # check connectivity by using ping
        self.assertTrue(self.net.ping([vnf11, vnf22]) <= 0.0)
        # check first service cannot ping second service
        self.assertTrue(self.net.ping([vnf1, vnf22]) > 0.0)
        self.assertTrue(self.net.ping([vnf2, vnf11]) > 0.0)

        # delete the first service chain
        self.net.setChain('vnf1', 'vnf2', 'intf1', 'intf2', bidirectional=True, cmd='del-flows', cookie=1)
        # check connectivity of first service is down
        self.assertTrue(self.net.ping([vnf1, vnf2]) > 0.0)
        #time.sleep(100)
        # check connectivity of second service is still up
        self.assertTrue(self.net.ping([vnf11, vnf22]) <= 0.0)

        # stop Mininet network
        self.stopNet()

#@unittest.skip("disabled compute tests for development")
class testEmulatorCompute( SimpleTestTopology ):
    """
    Tests to check the emulator's API to add and remove
    compute resources at runtime.
    """

    def testAddSingleComputeSingleDC(self):
        """
        Adds a single compute instance to
        a single DC and checks its connectivity with a
        manually added host.
        """
        # create network
        self.createNet(nswitches=0, ndatacenter=1, nhosts=1, ndockers=0)
        # setup links
        self.net.addLink(self.dc[0], self.h[0])
        # start Mininet network
        self.startNet()
        # add compute resources
        vnf1 = self.dc[0].startCompute("vnf1")
        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 1)
        self.assertTrue(len(self.net.hosts) == 2)
        self.assertTrue(len(self.net.switches) == 1)
        # check compute list result
        self.assertTrue(len(self.dc[0].listCompute()) == 1)
        self.assertTrue(isinstance(self.dc[0].listCompute()[0], EmulatorCompute))
        self.assertTrue(self.dc[0].listCompute()[0].name == "vnf1")
        # check connectivity by using ping
        self.assertTrue(self.net.ping([self.h[0], vnf1]) <= 0.0)
        # stop Mininet network
        self.stopNet()

    def testRemoveSingleComputeSingleDC(self):
        """
        Test stop method for compute instances.
        Check that the instance is really removed.
        """
        # create network
        self.createNet(nswitches=0, ndatacenter=1, nhosts=1, ndockers=0)
        # setup links
        self.net.addLink(self.dc[0], self.h[0])
        # start Mininet network
        self.startNet()
        # add compute resources
        vnf1 = self.dc[0].startCompute("vnf1")
        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 1)
        self.assertTrue(len(self.net.hosts) == 2)
        self.assertTrue(len(self.net.switches) == 1)
        # check compute list result
        self.assertTrue(len(self.dc[0].listCompute()) == 1)
        # check connectivity by using ping
        self.assertTrue(self.net.ping([self.h[0], vnf1]) <= 0.0)
        # remove compute resources
        self.dc[0].stopCompute("vnf1")
        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 0)
        self.assertTrue(len(self.net.hosts) == 1)
        self.assertTrue(len(self.net.switches) == 1)
        # check compute list result
        self.assertTrue(len(self.dc[0].listCompute()) == 0)
        # stop Mininet network
        self.stopNet()

    def testGetStatusSingleComputeSingleDC(self):
        """
        Check if the getStatus functionality of EmulatorCompute
        objects works well.
        """
        # create network
        self.createNet(nswitches=0, ndatacenter=1, nhosts=1, ndockers=0)
        # setup links
        self.net.addLink(self.dc[0], self.h[0])
        # start Mininet network
        self.startNet()
        # add compute resources
        vnf1 = self.dc[0].startCompute("vnf1")
        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 1)
        self.assertTrue(len(self.net.hosts) == 2)
        self.assertTrue(len(self.net.switches) == 1)
        # check compute list result
        self.assertTrue(len(self.dc[0].listCompute()) == 1)
        self.assertTrue(isinstance(self.dc[0].listCompute()[0], EmulatorCompute))
        self.assertTrue(self.dc[0].listCompute()[0].name == "vnf1")
        # check connectivity by using ping
        self.assertTrue(self.net.ping([self.h[0], vnf1]) <= 0.0)
        # check get status
        s = self.dc[0].containers.get("vnf1").getStatus()
        self.assertTrue(s["name"] == "vnf1")
        self.assertTrue(s["state"]["Running"])
        # stop Mininet network
        self.stopNet()

    def testConnectivityMultiDC(self):
        """
        Test if compute instances started in different data centers
        are able to talk to each other.
        """
        # create network
        self.createNet(
            nswitches=3, ndatacenter=2, nhosts=0, ndockers=0,
            autolinkswitches=True)
        # setup links
        self.net.addLink(self.dc[0], self.s[0])
        self.net.addLink(self.dc[1], self.s[2])
        # start Mininet network
        self.startNet()
        # add compute resources
        vnf1 = self.dc[0].startCompute("vnf1")
        vnf2 = self.dc[1].startCompute("vnf2")
        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 2)
        self.assertTrue(len(self.net.hosts) == 2)
        self.assertTrue(len(self.net.switches) == 5)
        # check compute list result
        self.assertTrue(len(self.dc[0].listCompute()) == 1)
        self.assertTrue(len(self.dc[1].listCompute()) == 1)
        # check connectivity by using ping
        self.assertTrue(self.net.ping([vnf1, vnf2]) <= 0.0)
        # stop Mininet network
        self.stopNet()

    def testInterleavedAddRemoveMultiDC(self):
        """
        Test multiple, interleaved add and remove operations and ensure
        that always all expected compute instances are reachable.
        """
                # create network
        self.createNet(
            nswitches=3, ndatacenter=2, nhosts=0, ndockers=0,
            autolinkswitches=True)
        # setup links
        self.net.addLink(self.dc[0], self.s[0])
        self.net.addLink(self.dc[1], self.s[2])
        # start Mininet network
        self.startNet()
        # add compute resources
        vnf1 = self.dc[0].startCompute("vnf1")
        vnf2 = self.dc[1].startCompute("vnf2")
        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 2)
        self.assertTrue(len(self.net.hosts) == 2)
        self.assertTrue(len(self.net.switches) == 5)
        # check compute list result
        self.assertTrue(len(self.dc[0].listCompute()) == 1)
        self.assertTrue(len(self.dc[1].listCompute()) == 1)
        # check connectivity by using ping
        self.assertTrue(self.net.ping([vnf1, vnf2]) <= 0.0)
        # remove compute resources
        self.dc[0].stopCompute("vnf1")
        # check number of running nodes
        self.assertTrue(len(self.getContainernetContainers()) == 1)
        self.assertTrue(len(self.net.hosts) == 1)
        self.assertTrue(len(self.net.switches) == 5)
        # check compute list result
        self.assertTrue(len(self.dc[0].listCompute()) == 0)
        self.assertTrue(len(self.dc[1].listCompute()) == 1)
        # add compute resources
        vnf3 = self.dc[0].startCompute("vnf3")
        vnf4 = self.dc[0].startCompute("vnf4")
        # check compute list result
        self.assertTrue(len(self.dc[0].listCompute()) == 2)
        self.assertTrue(len(self.dc[1].listCompute()) == 1)
        self.assertTrue(self.net.ping([vnf3, vnf2]) <= 0.0)
        self.assertTrue(self.net.ping([vnf4, vnf2]) <= 0.0)
        # remove compute resources
        self.dc[0].stopCompute("vnf3")
        self.dc[0].stopCompute("vnf4")
        self.dc[1].stopCompute("vnf2")
        # check compute list result
        self.assertTrue(len(self.dc[0].listCompute()) == 0)
        self.assertTrue(len(self.dc[1].listCompute()) == 0)
        # stop Mininet network
        self.stopNet()

if __name__ == '__main__':
    unittest.main()
