add monitoring optimizations
authorstevenvanrossem <steven.vanrossem@intec.ugent.be>
Mon, 7 Nov 2016 04:52:01 +0000 (05:52 +0100)
committerstevenvanrossem <steven.vanrossem@intec.ugent.be>
Mon, 7 Nov 2016 04:52:01 +0000 (05:52 +0100)
src/emuvim/api/rest/network.py
src/emuvim/api/sonata/dummygatekeeper.py
src/emuvim/dcemulator/net.py
src/emuvim/dcemulator/node.py
src/emuvim/dcemulator/son_emu_simple_switch_13.py
src/emuvim/examples/sonata_y1_demo_topology_1_w_ls_and_sap.py [changed mode: 0644->0755]

index 83fbde7..63696a0 100755 (executable)
@@ -87,6 +87,10 @@ class NetworkAction(Resource):
             bidirectional = data.get("bidirectional")
             cookie = data.get("cookie")
             priority = data.get("priority")
+            skip_vlan_tag = data.get("skip_vlan_tag")
+            monitor = data.get("monitor")
+            monitor_placement = data.get("monitor_placement")
+
             c = net.setChain(
                 vnf_src_name, vnf_dst_name,
                 vnf_src_interface=vnf_src_interface,
@@ -96,7 +100,10 @@ class NetworkAction(Resource):
                 match=match,
                 bidirectional=bidirectional,
                 cookie=cookie,
-                priority=priority)
+                priority=priority,
+                skip_vlan_tag=skip_vlan_tag,
+                monitor=monitor,
+                monitor_placement=monitor_placement)
             # return setChain response
             return str(c), 200
         except Exception as ex:
index 00201f9..735860c 100755 (executable)
@@ -75,6 +75,7 @@ class Gatekeeper(object):
     def __init__(self):
         self.services = dict()
         self.dcs = dict()
+        self.net = None
         self.vnf_counter = 0  # used to generate short names for VNFs (Mininet limitation)
         LOG.info("Create SONATA dummy gatekeeper.")
 
@@ -180,6 +181,9 @@ class Service(object):
         eline_fwd_links = [l for l in vlinks if (l["id"] in fwd_links) and (l["connectivity_type"] == "E-Line")]
         elan_fwd_links = [l for l in vlinks if (l["id"] in fwd_links) and (l["connectivity_type"] == "E-LAN")]
 
+        GK.net.deployed_elines.extend(eline_fwd_links)
+        GK.net.deployed_elans.extend(elan_fwd_links)
+
         # 4a. deploy E-Line links
         # cookie is used as identifier for the flowrules installed by the dummygatekeeper
         # eg. different services get a unique cookie for their flowrules
@@ -228,7 +232,7 @@ class Service(object):
         base = 10
         for link in elan_fwd_links:
 
-            elan=[]
+            elan_vnf_list=[]
 
             # generate lan ip address
             ip = 1
@@ -257,12 +261,11 @@ class Service(object):
 
                         # add this vnf and interface to the E-LAN for tagging
                         network = self.vnfds[vnf_name].get("dc").net  # there should be a cleaner way to find the DCNetwork
-                        elan.append({'name':src_docker_name,'interface':intf_name})
+                        elan_vnf_list.append({'name':src_docker_name,'interface':intf_name})
 
 
             # install the VLAN tags for this E-LAN
-            print(elan)
-            network.setLAN(elan)
+            network.setLAN(elan_vnf_list)
             # increase the base ip address for the next E-LAN
             base += 1
 
@@ -385,6 +388,7 @@ class Service(object):
                 self.package_content_path,
                 make_relative_path(self.manifest.get("entry_service_template")))
             self.nsd = load_yaml(nsd_path)
+            GK.net.deployed_nsds.append(self.nsd)
             LOG.debug("Loaded NSD: %r" % self.nsd.get("name"))
 
     def _load_vnfd(self):
@@ -630,6 +634,7 @@ api.add_resource(Instantiations, '/instantiations')
 
 def start_rest_api(host, port, datacenters=dict()):
     GK.dcs = datacenters
+    GK.net = get_dc_network()
     # start the Flask server (not the best performance but ok for our use case)
     app.run(host=host,
             port=port,
@@ -678,6 +683,14 @@ def generate_subnet_strings(n, start=1, subnet_size=24, ip=0):
         r.append("%d.0.0.%d/%d" % (i, ip, subnet_size))
     return r
 
+def get_dc_network():
+    """
+    retrieve the DCnetwork where this dummygatekeeper (GK) connects to.
+    Assume at least 1 datacenter is connected to this GK, and that all datacenters belong to the same DCNetwork
+    :return:
+    """
+    assert (len(GK.dcs) > 0)
+    return GK.dcs.values()[0].net
 
 if __name__ == '__main__':
     """
index 3d9dcb0..ec0aee5 100755 (executable)
@@ -69,6 +69,12 @@ class DCNetwork(Containernet):
         # members
         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()
@@ -330,8 +336,139 @@ class DCNetwork(Containernet):
             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):
@@ -350,6 +487,31 @@ class DCNetwork(Containernet):
         :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')
         if cmd == 'add-flow' or cmd == 'del-flows':
             ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
@@ -435,10 +597,23 @@ class DCNetwork(Containernet):
         cmd = kwargs.get('cmd')
         vlan = None
         if cmd == 'add-flow':
-            vlan = self.vlans.pop()
-            ##if len(path) > 1:
-            ##    vlan = self.vlans.pop()
-
+            if kwargs.get('tag'):
+                # use pre-defined tag
+                vlan = kwargs.get('tag')
+            else:
+                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)
 
@@ -477,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)
 
-
-
             # 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']
@@ -496,6 +669,12 @@ class DCNetwork(Containernet):
         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:
@@ -509,6 +688,8 @@ class DCNetwork(Containernet):
         if priority:
             flow['priority'] = int(priority)
 
+        flow['table_id'] = table_id
+
         flow['actions'] = []
 
         # possible Ryu actions, match fields:
@@ -518,8 +699,9 @@ class DCNetwork(Containernet):
             if vlan != None:
                 if path.index(current_hop) == 0:  # first node
                     # set vlan tag in ovs instance (to isolate E-LANs)
-                    in_port_name = kwargs.get('switch_inport_name')
-                    self._set_vlan_tag(node, in_port_name, vlan)
+                    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 = {}
@@ -534,8 +716,9 @@ class DCNetwork(Containernet):
 
                 if path.index(current_hop) == len(path) - 1:  # last node
                     # set vlan tag in ovs instance (to isolate E-LANs)
-                    out_port_name = kwargs.get('switch_outport_name')
-                    self._set_vlan_tag(node, out_port_name, vlan)
+                    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
index b88913b..ae658f0 100755 (executable)
@@ -61,6 +61,7 @@ class EmulatorCompute(Docker):
         Helper method to receive information about the virtual networks
         this compute instance is connected to.
         """
+        
         # format list of tuples (name, Ip, MAC, isUp, status)
         return [{'intf_name':str(i), 'ip':i.IP(), 'mac':i.MAC(), 'up':i.isUp(), 'status':i.status()}
                 for i in self.intfList()]
index ce2f544..53d1a2e 100755 (executable)
@@ -44,11 +44,12 @@ class SimpleSwitch13(app_manager.RyuApp):
         # truncated packet data. In that case, we cannot output packets
         # correctly.  The bug has been fixed in OVS v2.1.0.
         match = parser.OFPMatch()
-        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
-                                          ofproto.OFPCML_NO_BUFFER)]
+        #actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
+        #                                  ofproto.OFPCML_NO_BUFFER)]
+        actions = [parser.OFPActionOutput(ofproto.OFPCML_NO_BUFFER)]
         self.add_flow(datapath, 0, match, actions)
 
-    def add_flow(self, datapath, priority, match, actions, buffer_id=None):
+    def add_flow(self, datapath, priority, match, actions, buffer_id=None, table_id=0):
         ofproto = datapath.ofproto
         parser = datapath.ofproto_parser
 
@@ -57,10 +58,10 @@ class SimpleSwitch13(app_manager.RyuApp):
         if buffer_id:
             mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
                                     priority=priority, match=match,
-                                    instructions=inst)
+                                    instructions=inst, table_id=table_id)
         else:
             mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
-                                    match=match, instructions=inst)
+                                    match=match, instructions=inst, table_id=table_id)
         datapath.send_msg(mod)
 
         # new switch detected
@@ -74,7 +75,7 @@ class SimpleSwitch13(app_manager.RyuApp):
         # send NORMAL action for all undefined flows
         ofp_parser = datapath.ofproto_parser
         actions = [ofp_parser.OFPActionOutput(ofproto_v1_3.OFPP_NORMAL)]
-        self.add_flow(datapath, 0, None, actions)
+        self.add_flow(datapath, 0, None, actions, table_id=0)
 
 
     @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)