print datacenter connected switch
[osm/vim-emu.git] / src / emuvim / dcemulator / net.py
index c29c786..731331b 100755 (executable)
@@ -1,26 +1,52 @@
 """
 """
-Distributed Cloud Emulator (dcemulator)
-(c) 2015 by Manuel Peuster <manuel.peuster@upb.de>
+Copyright (c) 2015 SONATA-NFV and Paderborn University
+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).
 """
 import logging
 
 import site
 import time
 from subprocess import Popen
 """
 import logging
 
 import site
 import time
 from subprocess import Popen
-import os
 import re
 import re
-import urllib2
-from functools import partial
+import requests
+import os
 
 from mininet.net import Containernet
 from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
 from mininet.cli import CLI
 from mininet.link import TCLink
 
 from mininet.net import Containernet
 from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
 from mininet.cli import CLI
 from mininet.link import TCLink
+from mininet.clean import cleanup
 import networkx as nx
 from emuvim.dcemulator.monitoring import DCNetworkMonitor
 from emuvim.dcemulator.node import Datacenter, EmulatorCompute
 from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
 
 import networkx as nx
 from emuvim.dcemulator.monitoring import DCNetworkMonitor
 from emuvim.dcemulator.node import Datacenter, EmulatorCompute
 from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
 
+LOG = logging.getLogger("dcemulator.net")
+LOG.setLevel(logging.DEBUG)
+
 class DCNetwork(Containernet):
     """
     Wraps the original Mininet/Containernet class and provides
 class DCNetwork(Containernet):
     """
     Wraps the original Mininet/Containernet class and provides
@@ -30,7 +56,7 @@ class DCNetwork(Containernet):
     """
 
     def __init__(self, controller=RemoteController, monitor=False,
     """
 
     def __init__(self, controller=RemoteController, monitor=False,
-                 enable_learning = True,   # in case of RemoteController (Ryu), learning switch behavior can be turned off/on
+                 enable_learning=False, # learning switch behavior of the default ovs switches icw Ryu controller can be turned off/on, neede for E-LAN functionality
                  dc_emulation_max_cpu=1.0,  # fraction of overall CPU time for emulation
                  dc_emulation_max_mem=512,  # emulation max mem in MB
                  **kwargs):
                  dc_emulation_max_cpu=1.0,  # fraction of overall CPU time for emulation
                  dc_emulation_max_mem=512,  # emulation max mem in MB
                  **kwargs):
@@ -40,18 +66,36 @@ class DCNetwork(Containernet):
         :param kwargs: path through for Mininet parameters
         :return:
         """
         :param kwargs: path through for Mininet parameters
         :return:
         """
+        # members
         self.dcs = {}
         self.dcs = {}
+        self.ryu_process = None
+        #list of deployed nsds.E_Lines and E_LANs (uploaded from the dummy gatekeeper)
+        self.deployed_nsds = []
+        self.deployed_elines = []
+        self.deployed_elans = []
+        self.installed_chains = []
+
+
+        # always cleanup environment before we start the emulator
+        self.killRyu()
+        cleanup()
 
         # call original Docker.__init__ and setup default controller
         Containernet.__init__(
             self, switch=OVSKernelSwitch, controller=controller, **kwargs)
 
 
         # call original Docker.__init__ and setup default controller
         Containernet.__init__(
             self, switch=OVSKernelSwitch, controller=controller, **kwargs)
 
+        # default switch configuration
+        enable_ryu_learning = False
+        if enable_learning :
+            self.failMode = 'standalone'
+            enable_ryu_learning = True
+        else:
+            self.failMode = 'secure'
 
         # Ryu management
 
         # Ryu management
-        self.ryu_process = None
         if controller == RemoteController:
             # start Ryu controller
         if controller == RemoteController:
             # start Ryu controller
-            self.startRyu(learning_switch=enable_learning)
+            self.startRyu(learning_switch=enable_ryu_learning)
 
         # add the specified controller
         self.addController('c0', controller=controller)
 
         # add the specified controller
         self.addController('c0', controller=controller)
@@ -63,9 +107,10 @@ class DCNetwork(Containernet):
         self.vlans = range(4096)[::-1]
 
         # link to Ryu REST_API
         self.vlans = range(4096)[::-1]
 
         # link to Ryu REST_API
-        ryu_ip = '0.0.0.0'
+        ryu_ip = 'localhost'
         ryu_port = '8080'
         self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
         ryu_port = '8080'
         self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
+        self.RyuSession = requests.Session()
 
         # monitoring agent
         if monitor:
 
         # monitoring agent
         if monitor:
@@ -87,7 +132,7 @@ class DCNetwork(Containernet):
         dc.net = self  # set reference to network
         self.dcs[label] = dc
         dc.create()  # finally create the data center in our Mininet instance
         dc.net = self  # set reference to network
         self.dcs[label] = dc
         dc.create()  # finally create the data center in our Mininet instance
-        logging.info("added data center: %s" % label)
+        LOG.info("added data center: %s" % label)
         return dc
 
     def addLink(self, node1, node2, **params):
         return dc
 
     def addLink(self, node1, node2, **params):
@@ -97,7 +142,7 @@ class DCNetwork(Containernet):
         """
         assert node1 is not None
         assert node2 is not None
         """
         assert node1 is not None
         assert node2 is not None
-        logging.debug("addLink: n1=%s n2=%s" % (str(node1), str(node2)))
+
         # ensure type of node1
         if isinstance( node1, basestring ):
             if node1 in self.dcs:
         # ensure type of node1
         if isinstance( node1, basestring ):
             if node1 in self.dcs:
@@ -175,6 +220,9 @@ class DCNetwork(Containernet):
         attr_dict2.update(attr_dict)
         self.DCNetwork_graph.add_edge(node2.name, node1.name, attr_dict=attr_dict2)
 
         attr_dict2.update(attr_dict)
         self.DCNetwork_graph.add_edge(node2.name, node1.name, attr_dict=attr_dict2)
 
+        LOG.debug("addLink: n1={0} intf1={1} -- n2={2} intf2={3}".format(
+            str(node1),node1_port_name, str(node2), node2_port_name))
+
         return link
 
     def addDocker( self, label, **params ):
         return link
 
     def addDocker( self, label, **params ):
@@ -195,9 +243,26 @@ class DCNetwork(Containernet):
         """
         Wrapper for addSwitch method to store switch also in graph.
         """
         """
         Wrapper for addSwitch method to store switch also in graph.
         """
+
+        # add this switch to the global topology overview
         if add_to_graph:
             self.DCNetwork_graph.add_node(name)
         if add_to_graph:
             self.DCNetwork_graph.add_node(name)
-        return Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', **params)
+
+        # set the learning switch behavior
+        if 'failMode' in params :
+            failMode = params['failMode']
+        else :
+            failMode = self.failMode
+
+        s = Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
+
+        # set flow entry that enables learning switch behavior (needed to enable E-LAN functionality)
+        #LOG.info('failmode {0}'.format(failMode))
+        #if failMode == 'standalone' :
+        #    LOG.info('add NORMAL')
+        #    s.dpctl('add-flow', 'actions=NORMAL')
+
+        return s
 
     def getAllContainers(self):
         """
 
     def getAllContainers(self):
         """
@@ -224,25 +289,235 @@ class DCNetwork(Containernet):
         Containernet.stop(self)
 
         # stop Ryu controller
         Containernet.stop(self)
 
         # stop Ryu controller
-        self.stopRyu()
+        self.killRyu()
 
 
     def CLI(self):
         CLI(self)
 
 
 
     def CLI(self):
         CLI(self)
 
-    # to remove chain do setChain( src, dst, cmd='del-flows')
+    def setLAN(self, vnf_list):
+        """
+        setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
+
+        :param vnf_list: names of the VNFs in this E-LAN  [{name:,interface:},...]
+        :return:
+        """
+        src_sw = None
+        src_sw_inport_nr = 0
+        src_sw_inport_name = None
+
+        # get a vlan tag for this E-LAN
+        vlan = self.vlans.pop()
+
+        for vnf in vnf_list:
+            vnf_src_name = vnf['name']
+            vnf_src_interface = vnf['interface']
+
+            # check if port is specified (vnf:port)
+            if vnf_src_interface is None:
+                # take first interface by default
+                connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
+                link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
+                vnf_src_interface = link_dict[0]['src_port_id']
+
+            for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
+                link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
+                for link in link_dict:
+                    if (link_dict[link]['src_port_id'] == vnf_src_interface or
+                                link_dict[link]['src_port_name'] == vnf_src_interface):  # Fix: we might also get interface names, e.g, from a son-emu-cli call
+                        # found the right link and connected switch
+                        src_sw = connected_sw
+                        src_sw_inport_nr = link_dict[link]['dst_port_nr']
+                        src_sw_inport_name = link_dict[link]['dst_port_name']
+                        break
+
+            # set the tag on the dc switch interface
+            LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name, vnf_src_interface,vlan))
+            switch_node = self.getNodeByName(src_sw)
+            self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
+
+    def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
+                       tag=None, **kwargs):
+
+        src_sw = None
+        src_sw_inport_nr = 0
+        src_sw_inport_name = None
+        dst_sw = None
+        dst_sw_outport_nr = 0
+        dst_sw_outport_name = None
+
+        LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
+                  vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
+
+        #check if port is specified (vnf:port)
+        if vnf_src_interface is None:
+            # take first interface by default
+            connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
+            link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
+            vnf_src_interface = link_dict[0]['src_port_id']
+
+        for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
+            link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
+            for link in link_dict:
+                if (link_dict[link]['src_port_id'] == vnf_src_interface or
+                        link_dict[link]['src_port_name'] == vnf_src_interface):  # Fix: we might also get interface names, e.g, from a son-emu-cli call
+                    # found the right link and connected switch
+                    src_sw = connected_sw
+                    src_sw_inport_nr = link_dict[link]['dst_port_nr']
+                    src_sw_inport_name = link_dict[link]['dst_port_name']
+                    break
+
+        if vnf_dst_interface is None:
+            # take first interface by default
+            connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
+            link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
+            vnf_dst_interface = link_dict[0]['dst_port_id']
+
+        vnf_dst_name = vnf_dst_name.split(':')[0]
+        for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
+            link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
+            for link in link_dict:
+                if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
+                        link_dict[link]['dst_port_name'] == vnf_dst_interface:  # Fix: we might also get interface names, e.g, from a son-emu-cli call
+                    # found the right link and connected switch
+                    dst_sw = connected_sw
+                    dst_sw_outport_nr = link_dict[link]['src_port_nr']
+                    dst_sw_outport_name = link_dict[link]['src_port_name']
+                    break
+
+        if not tag >= 0:
+            LOG.exception('tag not valid: {0}'.format(tag))
+
+        # get shortest path
+        try:
+            # returns the first found shortest path
+            # if all shortest paths are wanted, use: all_shortest_paths
+            path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
+        except:
+            LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
+                vnf_src_name, vnf_dst_name, src_sw, dst_sw))
+            LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
+            LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
+            for e, v in self.DCNetwork_graph.edges():
+                LOG.debug("%r" % self.DCNetwork_graph[e][v])
+            return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
+
+        LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
+
+        current_hop = src_sw
+        switch_inport_nr = src_sw_inport_nr
+
+        cmd = kwargs.get('cmd')
+
+        #iterate through the path to install the flow-entries
+        for i in range(0,len(path)):
+            current_node = self.getNodeByName(current_hop)
+
+            if path.index(current_hop) < len(path)-1:
+                next_hop = path[path.index(current_hop)+1]
+            else:
+                #last switch reached
+                next_hop = vnf_dst_name
+
+            next_node = self.getNodeByName(next_hop)
+
+            if next_hop == vnf_dst_name:
+                switch_outport_nr = dst_sw_outport_nr
+                LOG.info("end node reached: {0}".format(vnf_dst_name))
+            elif not isinstance( next_node, OVSSwitch ):
+                LOG.info("Next node: {0} is not a switch".format(next_hop))
+                return "Next node: {0} is not a switch".format(next_hop)
+            else:
+                # take first link between switches by default
+                index_edge_out = 0
+                switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
+
+
+           # set of entry via ovs-ofctl
+            if isinstance( current_node, OVSSwitch ):
+                kwargs['vlan'] = tag
+                kwargs['path'] = path
+                kwargs['current_hop'] = current_hop
+                kwargs['switch_inport_name'] = src_sw_inport_name
+                kwargs['switch_outport_name'] = dst_sw_outport_name
+                kwargs['skip_vlan_tag'] = True
+
+                monitor_placement = kwargs.get('monitor_placement')
+                # put monitor flow at the dst switch
+                insert_flow = False
+                if monitor_placement == 'tx' and path.index(current_hop) == 0:  # first node:
+                    insert_flow = True
+                # put monitoring flow at the src switch
+                elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1:  # last node:
+                    insert_flow = True
+                else:
+                    LOG.exception('invalid monitor command: {0}'.format(monitor_placement))
+
+
+                if self.controller == RemoteController and insert_flow:
+                    ## set flow entry via ryu rest api
+                    self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
+                    break
+                elif insert_flow:
+                    ## set flow entry via ovs-ofctl
+                    self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
+                    break
+
+            # take first link between switches by default
+            if isinstance( next_node, OVSSwitch ):
+                switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
+                current_hop = next_hop
+
+        return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
+
+
     def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
     def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
+        """
+        Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
+        Currently the path is found using the default networkx shortest path function.
+        Each chain gets a unique vlan id , so different chains wil not interfere.
+
+        :param vnf_src_name: vnf name (string)
+        :param vnf_dst_name: vnf name (string)
+        :param vnf_src_interface: source interface name  (string)
+        :param vnf_dst_interface: destination interface name  (string)
+        :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
+        :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
+        :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
+        :param priority: custom flowrule priority
+        :return: output log string
+        """
+
+        # special procedure for monitoring flows
+        if kwargs.get('monitor'):
+
+            # check if chain already exists
+            found_chains = [chain_dict for chain_dict in self.installed_chains if
+             (chain_dict['vnf_src_name'] == vnf_src_name and chain_dict['vnf_src_interface'] == vnf_src_interface
+             and chain_dict['vnf_dst_name'] == vnf_dst_name and chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
+
+            if len(found_chains) > 0:
+                # this chain exists, so need an extra monitoring flow
+                # assume only 1 chain per vnf/interface pair
+                LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
+                            format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
+                tag = found_chains[0]['tag']
+                ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
+                                     tag=tag, table_id=0, **kwargs)
+                return ret
+            else:
+                # no chain existing (or E-LAN) -> install normal chain
+                LOG.warning('*** installing monitoring chain without pre-defined chain from {0}:{1} -> {2}:{3}'.
+                            format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
+                pass
+
+
         cmd = kwargs.get('cmd')
         cmd = kwargs.get('cmd')
-        if cmd == 'add-flow':
+        if cmd == 'add-flow' or cmd == 'del-flows':
             ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
             if kwargs.get('bidirectional'):
                 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
 
             ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
             if kwargs.get('bidirectional'):
                 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
 
-        elif cmd == 'del-flows':  # TODO: del-flow to be implemented
-            ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
-            if kwargs.get('bidirectional'):
-                ret = ret + '\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
-
         else:
             ret = "Command unknown"
 
         else:
             ret = "Command unknown"
 
@@ -251,7 +526,16 @@ class DCNetwork(Containernet):
 
     def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
 
 
     def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
 
-        # TODO: this needs to be cleaned up
+        src_sw = None
+        src_sw_inport_nr = 0
+        src_sw_inport_name = None
+        dst_sw = None
+        dst_sw_outport_nr = 0
+        dst_sw_outport_name = None
+
+        LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
+                  vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
+
         #check if port is specified (vnf:port)
         if vnf_src_interface is None:
             # take first interface by default
         #check if port is specified (vnf:port)
         if vnf_src_interface is None:
             # take first interface by default
@@ -262,11 +546,12 @@ class DCNetwork(Containernet):
         for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
             link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
             for link in link_dict:
         for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
             link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
             for link in link_dict:
-                if link_dict[link]['src_port_id'] == vnf_src_interface:
+                if (link_dict[link]['src_port_id'] == vnf_src_interface or
+                        link_dict[link]['src_port_name'] == vnf_src_interface):  # Fix: we might also get interface names, e.g, from a son-emu-cli call
                     # found the right link and connected switch
                     src_sw = connected_sw
                     # found the right link and connected switch
                     src_sw = connected_sw
-
                     src_sw_inport_nr = link_dict[link]['dst_port_nr']
                     src_sw_inport_nr = link_dict[link]['dst_port_nr']
+                    src_sw_inport_name = link_dict[link]['dst_port_name']
                     break
 
         if vnf_dst_interface is None:
                     break
 
         if vnf_dst_interface is None:
@@ -279,10 +564,12 @@ class DCNetwork(Containernet):
         for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
             link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
             for link in link_dict:
         for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
             link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
             for link in link_dict:
-                if link_dict[link]['dst_port_id'] == vnf_dst_interface:
+                if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
+                        link_dict[link]['dst_port_name'] == vnf_dst_interface:  # Fix: we might also get interface names, e.g, from a son-emu-cli call
                     # found the right link and connected switch
                     dst_sw = connected_sw
                     dst_sw_outport_nr = link_dict[link]['src_port_nr']
                     # found the right link and connected switch
                     dst_sw = connected_sw
                     dst_sw_outport_nr = link_dict[link]['src_port_nr']
+                    dst_sw_outport_name = link_dict[link]['src_port_name']
                     break
 
 
                     break
 
 
@@ -292,21 +579,41 @@ class DCNetwork(Containernet):
             # if all shortest paths are wanted, use: all_shortest_paths
             path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
         except:
             # if all shortest paths are wanted, use: all_shortest_paths
             path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
         except:
-            logging.info("No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name))
+            LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
+                vnf_src_name, vnf_dst_name, src_sw, dst_sw))
+            LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
+            LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
+            for e, v in self.DCNetwork_graph.edges():
+                LOG.debug("%r" % self.DCNetwork_graph[e][v])
             return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
 
             return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
 
-        logging.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
+        LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
 
         current_hop = src_sw
         switch_inport_nr = src_sw_inport_nr
 
 
         current_hop = src_sw
         switch_inport_nr = src_sw_inport_nr
 
-        # choose free vlan if path contains more than 1 switch
+        # choose free vlan
+        ## if path contains more than 1 switch
         cmd = kwargs.get('cmd')
         vlan = None
         if cmd == 'add-flow':
         cmd = kwargs.get('cmd')
         vlan = None
         if cmd == 'add-flow':
-            if len(path) > 1:
+            if kwargs.get('tag'):
+                # use pre-defined tag
+                vlan = kwargs.get('tag')
+            else:
                 vlan = self.vlans.pop()
 
                 vlan = self.vlans.pop()
 
+        # store the used vlan tag to identify this chain
+        if not kwargs.get('monitor'):
+            chain_dict = {}
+            chain_dict['vnf_src_name'] = vnf_src_name
+            chain_dict['vnf_dst_name'] = vnf_dst_name
+            chain_dict['vnf_src_interface'] = vnf_src_interface
+            chain_dict['vnf_dst_interface'] = vnf_dst_interface
+            chain_dict['tag'] = vlan
+            self.installed_chains.append(chain_dict)
+
+        #iterate through the path to install the flow-entries
         for i in range(0,len(path)):
             current_node = self.getNodeByName(current_hop)
 
         for i in range(0,len(path)):
             current_node = self.getNodeByName(current_hop)
 
@@ -320,9 +627,9 @@ class DCNetwork(Containernet):
 
             if next_hop == vnf_dst_name:
                 switch_outport_nr = dst_sw_outport_nr
 
             if next_hop == vnf_dst_name:
                 switch_outport_nr = dst_sw_outport_nr
-                logging.info("end node reached: {0}".format(vnf_dst_name))
+                LOG.info("end node reached: {0}".format(vnf_dst_name))
             elif not isinstance( next_node, OVSSwitch ):
             elif not isinstance( next_node, OVSSwitch ):
-                logging.info("Next node: {0} is not a switch".format(next_hop))
+                LOG.info("Next node: {0} is not a switch".format(next_hop))
                 return "Next node: {0} is not a switch".format(next_hop)
             else:
                 # take first link between switches by default
                 return "Next node: {0} is not a switch".format(next_hop)
             else:
                 # take first link between switches by default
@@ -335,6 +642,8 @@ class DCNetwork(Containernet):
                 kwargs['vlan'] = vlan
                 kwargs['path'] = path
                 kwargs['current_hop'] = current_hop
                 kwargs['vlan'] = vlan
                 kwargs['path'] = path
                 kwargs['current_hop'] = current_hop
+                kwargs['switch_inport_name'] = src_sw_inport_name
+                kwargs['switch_outport_name'] = dst_sw_outport_name
 
                 if self.controller == RemoteController:
                     ## set flow entry via ryu rest api
 
                 if self.controller == RemoteController:
                     ## set flow entry via ryu rest api
@@ -343,8 +652,6 @@ class DCNetwork(Containernet):
                     ## set flow entry via ovs-ofctl
                     self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
 
                     ## set flow entry via ovs-ofctl
                     self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
 
-
-
             # take first link between switches by default
             if isinstance( next_node, OVSSwitch ):
                 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
             # take first link between switches by default
             if isinstance( next_node, OVSSwitch ):
                 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
@@ -361,6 +668,13 @@ class DCNetwork(Containernet):
         path = kwargs.get('path')
         current_hop = kwargs.get('current_hop')
         vlan = kwargs.get('vlan')
         path = kwargs.get('path')
         current_hop = kwargs.get('current_hop')
         vlan = kwargs.get('vlan')
+        priority = kwargs.get('priority')
+        # flag to not set the ovs port vlan tag
+        skip_vlan_tag = kwargs.get('skip_vlan_tag')
+        # table id to put this flowentry
+        table_id = kwargs.get('table_id')
+        if not table_id:
+            table_id = 0
 
         s = ','
         if match_input:
 
         s = ','
         if match_input:
@@ -371,7 +685,10 @@ class DCNetwork(Containernet):
 
         if cookie:
             flow['cookie'] = int(cookie)
 
         if cookie:
             flow['cookie'] = int(cookie)
+        if priority:
+            flow['priority'] = int(priority)
 
 
+        flow['table_id'] = table_id
 
         flow['actions'] = []
 
 
         flow['actions'] = []
 
@@ -381,22 +698,37 @@ class DCNetwork(Containernet):
             prefix = 'stats/flowentry/add'
             if vlan != None:
                 if path.index(current_hop) == 0:  # first node
             prefix = 'stats/flowentry/add'
             if vlan != None:
                 if path.index(current_hop) == 0:  # first node
-                    action = {}
-                    action['type'] = 'PUSH_VLAN'  # Push a new VLAN tag if a input frame is non-VLAN-tagged
-                    action['ethertype'] = 33024   # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
-                    flow['actions'].append(action)
-                    action = {}
-                    action['type'] = 'SET_FIELD'
-                    action['field'] = 'vlan_vid'
-                    action['value'] = vlan
-                    flow['actions'].append(action)
-                elif path.index(current_hop) == len(path) - 1:  # last node
-                    match += ',dl_vlan=%s' % vlan
-                    action = {}
-                    action['type'] = 'POP_VLAN'
-                    flow['actions'].append(action)
-                else:  # middle nodes
+                    # set vlan tag in ovs instance (to isolate E-LANs)
+                    if not skip_vlan_tag:
+                        in_port_name = kwargs.get('switch_inport_name')
+                        self._set_vlan_tag(node, in_port_name, vlan)
+                    # set vlan push action if more than 1 switch in the path
+                    if len(path) > 1:
+                        action = {}
+                        action['type'] = 'PUSH_VLAN'  # Push a new VLAN tag if a input frame is non-VLAN-tagged
+                        action['ethertype'] = 33024   # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
+                        flow['actions'].append(action)
+                        action = {}
+                        action['type'] = 'SET_FIELD'
+                        action['field'] = 'vlan_vid'
+                        action['value'] = vlan
+                        flow['actions'].append(action)
+
+                if path.index(current_hop) == len(path) - 1:  # last node
+                    # set vlan tag in ovs instance (to isolate E-LANs)
+                    if not skip_vlan_tag:
+                        out_port_name = kwargs.get('switch_outport_name')
+                        self._set_vlan_tag(node, out_port_name, vlan)
+                    # set vlan pop action if more than 1 switch in the path
+                    if len(path) > 1:
+                        match += ',dl_vlan=%s' % vlan
+                        action = {}
+                        action['type'] = 'POP_VLAN'
+                        flow['actions'].append(action)
+
+                if 0 < path.index(current_hop) < (len(path) - 1):  # middle nodes
                     match += ',dl_vlan=%s' % vlan
                     match += ',dl_vlan=%s' % vlan
+
             # output action must come last
             action = {}
             action['type'] = 'OUTPUT'
             # output action must come last
             action = {}
             action['type'] = 'OUTPUT'
@@ -406,9 +738,8 @@ class DCNetwork(Containernet):
         elif cmd == 'del-flows':
             prefix = 'stats/flowentry/delete'
 
         elif cmd == 'del-flows':
             prefix = 'stats/flowentry/delete'
 
-            # if cookie is given, only delete flows by cookie
-            # do not specify other match -> also other cookies can be matched
             if cookie:
             if cookie:
+                # TODO: add cookie_mask as argument
                 flow['cookie_mask'] = int('0xffffffffffffffff', 16)  # need full mask to match complete cookie
 
             action = {}
                 flow['cookie_mask'] = int('0xffffffffffffffff', 16)  # need full mask to match complete cookie
 
             action = {}
@@ -419,7 +750,12 @@ class DCNetwork(Containernet):
         flow['match'] = self._parse_match(match)
         self.ryu_REST(prefix, data=flow)
 
         flow['match'] = self._parse_match(match)
         self.ryu_REST(prefix, data=flow)
 
+    def _set_vlan_tag(self, node, switch_port, tag):
+        node.vsctl('set', 'port {0} tag={1}'.format(switch_port,tag))
+        LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node.name, switch_port, tag))
+
     def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
     def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
+
         match = 'in_port=%s' % switch_inport_nr
 
         cookie = kwargs.get('cookie')
         match = 'in_port=%s' % switch_inport_nr
 
         cookie = kwargs.get('cookie')
@@ -453,14 +789,18 @@ class DCNetwork(Containernet):
             ofcmd = ''
 
         node.dpctl(cmd, ofcmd)
             ofcmd = ''
 
         node.dpctl(cmd, ofcmd)
-        logging.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
+        LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
                                                                                  switch_outport_nr, cmd))
 
     # start Ryu Openflow controller as Remote Controller for the DCNetwork
     def startRyu(self, learning_switch=True):
         # start Ryu controller with rest-API
         python_install_path = site.getsitepackages()[0]
                                                                                  switch_outport_nr, cmd))
 
     # start Ryu Openflow controller as Remote Controller for the DCNetwork
     def startRyu(self, learning_switch=True):
         # start Ryu controller with rest-API
         python_install_path = site.getsitepackages()[0]
-        ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
+        # ryu default learning switch
+        #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
+        #custom learning switch that installs a default NORMAL action in the ovs switches
+        dir_path = os.path.dirname(os.path.realpath(__file__))
+        ryu_path = dir_path + '/son_emu_simple_switch_13.py'
         ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
         # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
         # Ryu still uses 6633 as default
         ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
         # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
         # Ryu still uses 6633 as default
@@ -470,33 +810,56 @@ class DCNetwork(Containernet):
         FNULL = open("/tmp/ryu.log", 'w')
         if learning_switch:
             self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
         FNULL = open("/tmp/ryu.log", 'w')
         if learning_switch:
             self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
+            LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
+            LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
         else:
         else:
-            # no learning switch
+            # no learning switch, but with rest api
             self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
             self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
+            LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
         time.sleep(1)
 
         time.sleep(1)
 
-    def stopRyu(self):
+    def killRyu(self):
+        """
+        Stop the Ryu controller that might be started by son-emu.
+        :return:
+        """
+        # try it nicely
         if self.ryu_process is not None:
             self.ryu_process.terminate()
             self.ryu_process.kill()
         if self.ryu_process is not None:
             self.ryu_process.terminate()
             self.ryu_process.kill()
+        # ensure its death ;-)
+        Popen(['pkill', '-f', 'ryu-manager'])
 
     def ryu_REST(self, prefix, dpid=None, data=None):
 
     def ryu_REST(self, prefix, dpid=None, data=None):
-        try:
-            if dpid:
-                url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
-            else:
-                url = self.ryu_REST_api + '/' + str(prefix)
-            if data:
-                #logging.info('POST: {0}'.format(str(data)))
-                req = urllib2.Request(url, str(data))
-            else:
-                req = urllib2.Request(url)
 
 
-            ret = urllib2.urlopen(req).read()
+        if dpid:
+            url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
+        else:
+            url = self.ryu_REST_api + '/' + str(prefix)
+        if data:
+            req = self.RyuSession.post(url, json=data)
+        else:
+            req = self.RyuSession.get(url)
+
+
+        # do extra logging if status code is not 200 (OK)
+        if req.status_code is not requests.codes.ok:
+            logging.info(
+                'type {0}  encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
+                                                                                     req.encoding, req.text,
+                                                                                     req.headers, req.history))
+            LOG.info('url: {0}'.format(str(url)))
+            if data: LOG.info('POST: {0}'.format(str(data)))
+            LOG.info('status: {0} reason: {1}'.format(req.status_code, req.reason))
+
+
+        if 'json' in req.headers['content-type']:
+            ret = req.json()
             return ret
             return ret
-        except:
-            logging.info('error url: {0}'.format(str(url)))
-            if data: logging.info('error POST: {0}'.format(str(data)))
+
+        ret = req.text.rstrip()
+        return ret
+
 
     # need to respect that some match fields must be integers
     # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
 
     # need to respect that some match fields must be integers
     # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
@@ -514,3 +877,14 @@ class DCNetwork(Containernet):
                 dict.update({match[0]:m2})
         return dict
 
                 dict.update({match[0]:m2})
         return dict
 
+    def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface):
+        for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
+            link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
+            for link in link_dict:
+                if (link_dict[link]['src_port_id'] == vnf_src_interface or
+                        link_dict[link]['src_port_name'] == vnf_src_interface):  # Fix: we might also get interface names, e.g, from a son-emu-cli call
+                    # found the right link and connected switch
+                    src_sw = connected_sw
+                    src_sw_inport_nr = link_dict[link]['dst_port_nr']
+                    src_sw_inport_name = link_dict[link]['dst_port_name']
+                    return src_sw_inport_name
\ No newline at end of file