OpenStack API: Replaced Flask with WSGI
[osm/vim-emu.git] / src / emuvim / dcemulator / net.py
index 8f627b5..ea9fd1c 100755 (executable)
@@ -14,7 +14,7 @@ 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.
 
 See the License for the specific language governing permissions and
 limitations under the License.
 
-Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
+Neither the name of the SONATA-NFV, Paderborn University
 nor the names of its contributors may be used to endorse or promote
 products derived from this software without specific prior written
 permission.
 nor the names of its contributors may be used to endorse or promote
 products derived from this software without specific prior written
 permission.
@@ -33,6 +33,7 @@ from subprocess import Popen
 import re
 import requests
 import os
 import re
 import requests
 import os
+import json
 
 from mininet.net import Containernet
 from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
 
 from mininet.net import Containernet
 from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
@@ -41,7 +42,7 @@ from mininet.link import TCLink
 from mininet.clean import cleanup
 import networkx as nx
 from emuvim.dcemulator.monitoring import DCNetworkMonitor
 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.node import Datacenter, EmulatorCompute, EmulatorExtSAP
 from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
 
 LOG = logging.getLogger("dcemulator.net")
 from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
 
 LOG = logging.getLogger("dcemulator.net")
@@ -50,6 +51,11 @@ LOG.setLevel(logging.DEBUG)
 # default CPU period used for cpu percentage-based cfs values (microseconds)
 CPU_PERIOD = 1000000
 
 # default CPU period used for cpu percentage-based cfs values (microseconds)
 CPU_PERIOD = 1000000
 
+# default priority setting for added flow-rules
+DEFAULT_PRIORITY = 1000
+# default cookie number for new flow-rules
+DEFAULT_COOKIE = 10
+
 class DCNetwork(Containernet):
     """
     Wraps the original Mininet/Containernet class and provides
 class DCNetwork(Containernet):
     """
     Wraps the original Mininet/Containernet class and provides
@@ -107,7 +113,7 @@ class DCNetwork(Containernet):
         self.DCNetwork_graph = nx.MultiDiGraph()
 
         # initialize pool of vlan tags to setup the SDN paths
         self.DCNetwork_graph = nx.MultiDiGraph()
 
         # initialize pool of vlan tags to setup the SDN paths
-        self.vlans = range(4096)[::-1]
+        self.vlans = range(1, 4095)[::-1]
 
         # link to Ryu REST_API
         ryu_ip = 'localhost'
 
         # link to Ryu REST_API
         ryu_ip = 'localhost'
@@ -202,7 +208,7 @@ class DCNetwork(Containernet):
         edge_attributes = [p for p in params if p in weight_metrics]
         for attr in edge_attributes:
             # if delay: strip ms (need number as weight in graph)
         edge_attributes = [p for p in params if p in weight_metrics]
         for attr in edge_attributes:
             # if delay: strip ms (need number as weight in graph)
-            match = re.search('([0-9]*\.?[0-9]+)', params[attr])
+            match = re.search('([0-9]*\.?[0-9]+)', str(params[attr]))
             if match:
                 attr_number = match.group(1)
             else:
             if match:
                 attr_number = match.group(1)
             else:
@@ -229,20 +235,56 @@ class DCNetwork(Containernet):
 
         return link
 
 
         return link
 
+    def removeLink(self, link=None, node1=None, node2=None):
+        """
+        Remove the link from the Containernet and the networkx graph
+        """
+        if link is not None:
+            node1 = link.intf1.node
+            node2 = link.intf2.node
+        assert node1 is not None
+        assert node2 is not None
+        Containernet.removeLink(self, link=link, node1=node1, node2=node2)
+        # TODO we might decrease the loglevel to debug:
+        try:
+            self.DCNetwork_graph.remove_edge(node2.name, node1.name)
+        except:
+            LOG.warning("%s, %s not found in DCNetwork_graph." % ((node2.name, node1.name)))
+        try:
+            self.DCNetwork_graph.remove_edge(node1.name, node2.name)
+        except:
+            LOG.warning("%s, %s not found in DCNetwork_graph." % ((node1.name, node2.name)))
+
     def addDocker( self, label, **params ):
         """
         Wrapper for addDocker method to use custom container class.
         """
     def addDocker( self, label, **params ):
         """
         Wrapper for addDocker method to use custom container class.
         """
-        self.DCNetwork_graph.add_node(label)
+        self.DCNetwork_graph.add_node(label, type=params.get('type', 'docker'))
         return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
 
         return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
 
-    def removeDocker( self, label, **params ):
+    def removeDocker( self, label, **params):
         """
         Wrapper for removeDocker method to update graph.
         """
         self.DCNetwork_graph.remove_node(label)
         return Containernet.removeDocker(self, label, **params)
 
         """
         Wrapper for removeDocker method to update graph.
         """
         self.DCNetwork_graph.remove_node(label)
         return Containernet.removeDocker(self, label, **params)
 
+    def addExtSAP(self, sap_name, sap_ip, **params):
+        """
+        Wrapper for addExtSAP method to store SAP  also in graph.
+        """
+        # make sure that 'type' is set
+        params['type'] = params.get('type','sap_ext')
+        self.DCNetwork_graph.add_node(sap_name, type=params['type'])
+        return Containernet.addExtSAP(self, sap_name, sap_ip, **params)
+
+    def removeExtSAP(self, sap_name, **params):
+        """
+        Wrapper for removeExtSAP method to remove SAP  also from graph.
+        """
+        self.DCNetwork_graph.remove_node(sap_name)
+        return Containernet.removeExtSAP(self, sap_name)
+
     def addSwitch( self, name, add_to_graph=True, **params ):
         """
         Wrapper for addSwitch method to store switch also in graph.
     def addSwitch( self, name, add_to_graph=True, **params ):
         """
         Wrapper for addSwitch method to store switch also in graph.
@@ -250,7 +292,7 @@ class DCNetwork(Containernet):
 
         # add this switch to the global topology overview
         if add_to_graph:
 
         # add this switch to the global topology overview
         if add_to_graph:
-            self.DCNetwork_graph.add_node(name)
+            self.DCNetwork_graph.add_node(name, type=params.get('type','switch'))
 
         # set the learning switch behavior
         if 'failMode' in params :
 
         # set the learning switch behavior
         if 'failMode' in params :
@@ -260,12 +302,6 @@ class DCNetwork(Containernet):
 
         s = Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
 
 
         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):
         return s
 
     def getAllContainers(self):
@@ -456,6 +492,7 @@ class DCNetwork(Containernet):
                 kwargs['switch_inport_name'] = src_sw_inport_name
                 kwargs['switch_outport_name'] = dst_sw_outport_name
                 kwargs['skip_vlan_tag'] = True
                 kwargs['switch_inport_name'] = src_sw_inport_name
                 kwargs['switch_outport_name'] = dst_sw_outport_name
                 kwargs['skip_vlan_tag'] = True
+                kwargs['pathindex'] = i
 
                 monitor_placement = kwargs.get('monitor_placement').strip()
                 # put monitor flow at the dst switch
 
                 monitor_placement = kwargs.get('monitor_placement').strip()
                 # put monitor flow at the dst switch
@@ -502,6 +539,8 @@ class DCNetwork(Containernet):
         :param priority: custom flowrule priority
         :param monitor: boolean to indicate whether this chain is a monitoring chain
         :param tag: vlan tag to be used for this chain (pre-defined or new one if none is specified)
         :param priority: custom flowrule priority
         :param monitor: boolean to indicate whether this chain is a monitoring chain
         :param tag: vlan tag to be used for this chain (pre-defined or new one if none is specified)
+        :param skip_vlan_tag: boolean to indicate if a vlan tag should be appointed to this flow or not
+        :param path: custom path between the two VNFs (list of switches)
         :return: output log string
         """
 
         :return: output log string
         """
 
@@ -529,10 +568,12 @@ class DCNetwork(Containernet):
                 pass
 
 
                 pass
 
 
-        cmd = kwargs.get('cmd')
+        cmd = kwargs.get('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'):
         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'):
+                if kwargs.get('path') is not None:
+                    kwargs['path'] = list(reversed(kwargs.get('path')))
                 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
 
         else:
                 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
 
         else:
@@ -589,20 +630,21 @@ class DCNetwork(Containernet):
                     dst_sw_outport_name = link_dict[link]['src_port_name']
                     break
 
                     dst_sw_outport_name = link_dict[link]['src_port_name']
                     break
 
-
-        # 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)
+        path = kwargs.get('path')
+        if path is None:
+            # 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))
 
 
         LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
 
@@ -610,7 +652,6 @@ class DCNetwork(Containernet):
         switch_inport_nr = src_sw_inport_nr
 
         # choose free vlan
         switch_inport_nr = src_sw_inport_nr
 
         # 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':
@@ -634,10 +675,10 @@ class DCNetwork(Containernet):
         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)
 
-            if path.index(current_hop) < len(path)-1:
-                next_hop = path[path.index(current_hop)+1]
+            if i < len(path) - 1:
+                next_hop = path[i + 1]
             else:
             else:
-                #last switch reached
+                # last switch reached
                 next_hop = vnf_dst_name
 
             next_node = self.getNodeByName(next_hop)
                 next_hop = vnf_dst_name
 
             next_node = self.getNodeByName(next_hop)
@@ -654,13 +695,14 @@ class DCNetwork(Containernet):
                 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
 
 
                 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
 
 
-           # set of entry via ovs-ofctl
+           # set OpenFlow entry
             if isinstance( current_node, OVSSwitch ):
                 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 isinstance( current_node, OVSSwitch ):
                 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
+                kwargs['pathindex'] = i
 
                 if self.controller == RemoteController:
                     ## set flow entry via ryu rest api
 
                 if self.controller == RemoteController:
                     ## set flow entry via ryu rest api
@@ -674,7 +716,15 @@ class DCNetwork(Containernet):
                 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
                 current_hop = next_hop
 
                 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)
+        flow_options = {
+            'priority':kwargs.get('priority', DEFAULT_PRIORITY),
+            'cookie':kwargs.get('cookie', DEFAULT_COOKIE),
+            'vlan':kwargs['vlan'],
+            'path':kwargs['path'],
+            'match_input':kwargs.get('match')
+        }
+        flow_options_str = json.dumps(flow_options, indent=1)
+        return "success: {2} between {0} and {1} with options: {3}".format(vnf_src_name, vnf_dst_name, cmd, flow_options_str)
 
     def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
         match = 'in_port=%s' % switch_inport_nr
 
     def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
         match = 'in_port=%s' % switch_inport_nr
@@ -683,9 +733,10 @@ class DCNetwork(Containernet):
         match_input = kwargs.get('match')
         cmd = kwargs.get('cmd')
         path = kwargs.get('path')
         match_input = kwargs.get('match')
         cmd = kwargs.get('cmd')
         path = kwargs.get('path')
-        current_hop = kwargs.get('current_hop')
+        index = kwargs.get('pathindex')
+
         vlan = kwargs.get('vlan')
         vlan = kwargs.get('vlan')
-        priority = kwargs.get('priority')
+        priority = kwargs.get('priority', DEFAULT_PRIORITY)
         # flag to not set the ovs port vlan tag
         skip_vlan_tag = kwargs.get('skip_vlan_tag')
         # table id to put this flowentry
         # flag to not set the ovs port vlan tag
         skip_vlan_tag = kwargs.get('skip_vlan_tag')
         # table id to put this flowentry
@@ -714,7 +765,7 @@ class DCNetwork(Containernet):
         if cmd == 'add-flow':
             prefix = 'stats/flowentry/add'
             if vlan != None:
         if cmd == 'add-flow':
             prefix = 'stats/flowentry/add'
             if vlan != None:
-                if path.index(current_hop) == 0:  # first node
+                if index == 0:  # first node
                     # set vlan tag in ovs instance (to isolate E-LANs)
                     if not skip_vlan_tag:
                         in_port_name = kwargs.get('switch_inport_name')
                     # set vlan tag in ovs instance (to isolate E-LANs)
                     if not skip_vlan_tag:
                         in_port_name = kwargs.get('switch_inport_name')
@@ -728,10 +779,11 @@ class DCNetwork(Containernet):
                         action = {}
                         action['type'] = 'SET_FIELD'
                         action['field'] = 'vlan_vid'
                         action = {}
                         action['type'] = 'SET_FIELD'
                         action['field'] = 'vlan_vid'
-                        action['value'] = vlan
+                        # ryu expects the field to be masked
+                        action['value'] = vlan | 0x1000
                         flow['actions'].append(action)
 
                         flow['actions'].append(action)
 
-                if path.index(current_hop) == len(path) - 1:  # last node
+                elif index == 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')
                     # set vlan tag in ovs instance (to isolate E-LANs)
                     if not skip_vlan_tag:
                         out_port_name = kwargs.get('switch_outport_name')
@@ -743,7 +795,7 @@ class DCNetwork(Containernet):
                         action['type'] = 'POP_VLAN'
                         flow['actions'].append(action)
 
                         action['type'] = 'POP_VLAN'
                         flow['actions'].append(action)
 
-                if 0 < path.index(current_hop) < (len(path) - 1):  # middle nodes
+                else:  # middle nodes
                     match += ',dl_vlan=%s' % vlan
 
             # output action must come last
                     match += ',dl_vlan=%s' % vlan
 
             # output action must come last
@@ -779,7 +831,7 @@ class DCNetwork(Containernet):
         match_input = kwargs.get('match')
         cmd = kwargs.get('cmd')
         path = kwargs.get('path')
         match_input = kwargs.get('match')
         cmd = kwargs.get('cmd')
         path = kwargs.get('path')
-        current_hop = kwargs.get('current_hop')
+        index = kwargs.get('pathindex')
         vlan = kwargs.get('vlan')
 
         s = ','
         vlan = kwargs.get('vlan')
 
         s = ','
@@ -791,10 +843,10 @@ class DCNetwork(Containernet):
         if cmd == 'add-flow':
             action = 'action=%s' % switch_outport_nr
             if vlan != None:
         if cmd == 'add-flow':
             action = 'action=%s' % switch_outport_nr
             if vlan != None:
-                if path.index(current_hop) == 0:  # first node
+                if index == 0: # first node
                     action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
                     match = '-O OpenFlow13 ' + match
                     action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
                     match = '-O OpenFlow13 ' + match
-                elif path.index(current_hop) == len(path) - 1:  # last node
+                elif index == len(path) - 1:  # last node
                     match += ',dl_vlan=%s' % vlan
                     action = 'action=strip_vlan,output=%s' % switch_outport_nr
                 else:  # middle nodes
                     match += ',dl_vlan=%s' % vlan
                     action = 'action=strip_vlan,output=%s' % switch_outport_nr
                 else:  # middle nodes
@@ -894,7 +946,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):
+    def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface=None):
+
+        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:
         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:
@@ -904,4 +963,4 @@ class DCNetwork(Containernet):
                     src_sw = connected_sw
                     src_sw_inport_nr = link_dict[link]['dst_port_nr']
                     src_sw_inport_name = link_dict[link]['dst_port_name']
                     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
+                    return src_sw_inport_name