Cleanup: Environment cleanup mechanisms. Closes #139
[osm/vim-emu.git] / src / emuvim / dcemulator / net.py
index 9ca75f7..cbe668f 100755 (executable)
@@ -1,6 +1,29 @@
 """
-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
 
@@ -12,18 +35,22 @@ import re
 import urllib2
 from functools import partial
 
-from mininet.net import Dockernet
+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
 
-class DCNetwork(Dockernet):
+LOG = logging.getLogger("dcemulator.net")
+LOG.setLevel(logging.DEBUG)
+
+class DCNetwork(Containernet):
     """
-    Wraps the original Mininet/Dockernet class and provides
+    Wraps the original Mininet/Containernet class and provides
     methods to add data centers, switches, etc.
 
     This class is used by topology definition scripts.
@@ -35,19 +62,24 @@ class DCNetwork(Dockernet):
                  dc_emulation_max_mem=512,  # emulation max mem in MB
                  **kwargs):
         """
-        Create an extended version of a Dockernet network
+        Create an extended version of a Containernet network
         :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
         :param kwargs: path through for Mininet parameters
         :return:
         """
+        # members
         self.dcs = {}
+        self.ryu_process = None
+
+        # always cleanup environment before we start the emulator
+        self.killRyu()
+        cleanup()
 
         # call original Docker.__init__ and setup default controller
-        Dockernet.__init__(
+        Containernet.__init__(
             self, switch=OVSKernelSwitch, controller=controller, **kwargs)
 
         # Ryu management
-        self.ryu_process = None
         if controller == RemoteController:
             # start Ryu controller
             self.startRyu(learning_switch=enable_learning)
@@ -86,7 +118,7 @@ class DCNetwork(Dockernet):
         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):
@@ -96,7 +128,7 @@ class DCNetwork(Dockernet):
         """
         assert node1 is not None
         assert node2 is not None
-        logging.debug("addLink: n1=%s n2=%s" % (str(node1), str(node2)))
+        LOG.debug("addLink: n1=%s n2=%s" % (str(node1), str(node2)))
         # ensure type of node1
         if isinstance( node1, basestring ):
             if node1 in self.dcs:
@@ -122,11 +154,11 @@ class DCNetwork(Dockernet):
                 params["params2"]["ip"] = self.getNextIp()
         # ensure that we allow TCLinks between data centers
         # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
-        # see Dockernet issue: https://github.com/mpeuster/dockernet/issues/3
+        # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
         if "cls" not in params:
             params["cls"] = TCLink
 
-        link = Dockernet.addLink(self, node1, node2, **params)
+        link = Containernet.addLink(self, node1, node2, **params)
 
         # try to give container interfaces a default id
         node1_port_id = node1.ports[link.intf1]
@@ -144,7 +176,7 @@ class DCNetwork(Dockernet):
 
         # add edge and assigned port number to graph in both directions between node1 and node2
         # port_id: id given in descriptor (if available, otherwise same as port)
-        # port: portnumber assigned by Dockernet
+        # port: portnumber assigned by Containernet
 
         attr_dict = {}
         # possible weight metrics allowed by TClink class:
@@ -181,14 +213,14 @@ class DCNetwork(Dockernet):
         Wrapper for addDocker method to use custom container class.
         """
         self.DCNetwork_graph.add_node(label)
-        return Dockernet.addDocker(self, label, cls=EmulatorCompute, **params)
+        return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
 
     def removeDocker( self, label, **params ):
         """
         Wrapper for removeDocker method to update graph.
         """
         self.DCNetwork_graph.remove_node(label)
-        return Dockernet.removeDocker(self, label, **params)
+        return Containernet.removeDocker(self, label, **params)
 
     def addSwitch( self, name, add_to_graph=True, **params ):
         """
@@ -196,7 +228,7 @@ class DCNetwork(Dockernet):
         """
         if add_to_graph:
             self.DCNetwork_graph.add_node(name)
-        return Dockernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', **params)
+        return Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', **params)
 
     def getAllContainers(self):
         """
@@ -211,7 +243,7 @@ class DCNetwork(Dockernet):
         # start
         for dc in self.dcs.itervalues():
             dc.start()
-        Dockernet.start(self)
+        Containernet.start(self)
 
     def stop(self):
 
@@ -220,10 +252,10 @@ class DCNetwork(Dockernet):
             self.monitor_agent.stop()
 
         # stop emulator net
-        Dockernet.stop(self)
+        Containernet.stop(self)
 
         # stop Ryu controller
-        self.stopRyu()
+        self.killRyu()
 
 
     def CLI(self):
@@ -235,20 +267,29 @@ class DCNetwork(Dockernet):
         if cmd == 'add-flow':
             ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
             if kwargs.get('bidirectional'):
-                return ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
+                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
+        elif cmd == 'del-flows':
             ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
             if kwargs.get('bidirectional'):
-                return ret + '\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
+                ret = ret + '\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
 
         else:
-            return "Command unknown"
+            ret = "Command unknown"
+
+        return ret
 
 
     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
+        dst_sw = None
+        src_sw_inport_nr = 0
+        dst_sw_outport_nr = 0
+
+        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
@@ -259,10 +300,10 @@ class DCNetwork(Dockernet):
         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
-
                     src_sw_inport_nr = link_dict[link]['dst_port_nr']
                     break
 
@@ -276,7 +317,8 @@ class DCNetwork(Dockernet):
         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']
@@ -289,10 +331,15 @@ class DCNetwork(Dockernet):
             # 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)
 
-        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
@@ -317,9 +364,9 @@ class DCNetwork(Dockernet):
 
             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 ):
-                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
@@ -365,7 +412,6 @@ class DCNetwork(Dockernet):
 
         flow = {}
         flow['dpid'] = int(node.dpid, 16)
-        logging.info('node name:{0}'.format(node.name))
 
         if cookie:
             flow['cookie'] = int(cookie)
@@ -400,14 +446,13 @@ class DCNetwork(Dockernet):
             action['type'] = 'OUTPUT'
             action['port'] = switch_outport_nr
             flow['actions'].append(action)
-            #flow['match'] = self._parse_match(match)
+
         elif cmd == 'del-flows':
-            #del(flow['actions'])
             prefix = 'stats/flowentry/delete'
+
             if cookie:
-                flow['cookie_mask'] = cookie
-            #if cookie is None:
-            #    flow['match'] = self._parse_match(match)
+                # TODO: add cookie_mask as argument
+                flow['cookie_mask'] = int('0xffffffffffffffff', 16)  # need full mask to match complete cookie
 
             action = {}
             action['type'] = 'OUTPUT'
@@ -451,7 +496,7 @@ class DCNetwork(Dockernet):
             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
@@ -469,14 +514,21 @@ class DCNetwork(Dockernet):
         if learning_switch:
             self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
         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)
         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()
+        # ensure its death ;-)
+        Popen(['pkill', '-f', 'ryu-manager'])
 
     def ryu_REST(self, prefix, dpid=None, data=None):
         try:
@@ -485,7 +537,7 @@ class DCNetwork(Dockernet):
             else:
                 url = self.ryu_REST_api + '/' + str(prefix)
             if data:
-                #logging.info('POST: {0}'.format(str(data)))
+                #LOG.info('POST: {0}'.format(str(data)))
                 req = urllib2.Request(url, str(data))
             else:
                 req = urllib2.Request(url)
@@ -493,8 +545,8 @@ class DCNetwork(Dockernet):
             ret = urllib2.urlopen(req).read()
             return ret
         except:
-            logging.info('error url: {0}'.format(str(url)))
-            if data: logging.info('error POST: {0}'.format(str(data)))
+            LOG.info('error url: {0}'.format(str(url)))
+            if data: LOG.info('error POST: {0}'.format(str(data)))
 
     # 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