From becc7c55f44225979dc706d1e7c3dfc8748cf1c7 Mon Sep 17 00:00:00 2001 From: stevenvanrossem Date: Mon, 7 Nov 2016 05:52:01 +0100 Subject: [PATCH] add monitoring optimizations --- src/emuvim/api/rest/network.py | 9 +- src/emuvim/api/sonata/dummygatekeeper.py | 21 +- src/emuvim/dcemulator/net.py | 203 +++++++++++++++++- src/emuvim/dcemulator/node.py | 1 + .../dcemulator/son_emu_simple_switch_13.py | 13 +- .../sonata_y1_demo_topology_1_w_ls_and_sap.py | 0 6 files changed, 226 insertions(+), 21 deletions(-) mode change 100644 => 100755 src/emuvim/examples/sonata_y1_demo_topology_1_w_ls_and_sap.py diff --git a/src/emuvim/api/rest/network.py b/src/emuvim/api/rest/network.py index 83fbde7..63696a0 100755 --- a/src/emuvim/api/rest/network.py +++ b/src/emuvim/api/rest/network.py @@ -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: diff --git a/src/emuvim/api/sonata/dummygatekeeper.py b/src/emuvim/api/sonata/dummygatekeeper.py index 00201f9..735860c 100755 --- a/src/emuvim/api/sonata/dummygatekeeper.py +++ b/src/emuvim/api/sonata/dummygatekeeper.py @@ -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__': """ diff --git a/src/emuvim/dcemulator/net.py b/src/emuvim/dcemulator/net.py index 3d9dcb0..ec0aee5 100755 --- a/src/emuvim/dcemulator/net.py +++ b/src/emuvim/dcemulator/net.py @@ -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 diff --git a/src/emuvim/dcemulator/node.py b/src/emuvim/dcemulator/node.py index b88913b..ae658f0 100755 --- a/src/emuvim/dcemulator/node.py +++ b/src/emuvim/dcemulator/node.py @@ -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()] diff --git a/src/emuvim/dcemulator/son_emu_simple_switch_13.py b/src/emuvim/dcemulator/son_emu_simple_switch_13.py index ce2f544..53d1a2e 100755 --- a/src/emuvim/dcemulator/son_emu_simple_switch_13.py +++ b/src/emuvim/dcemulator/son_emu_simple_switch_13.py @@ -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) diff --git a/src/emuvim/examples/sonata_y1_demo_topology_1_w_ls_and_sap.py b/src/emuvim/examples/sonata_y1_demo_topology_1_w_ls_and_sap.py old mode 100644 new mode 100755 -- 2.25.1