X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=src%2Femuvim%2Fdcemulator%2Fnet.py;h=248dc3481e61bc3337541eb6023a090f30d0af65;hb=a5d41da5c5e8cd4108c4acddb91b8d4b609e171c;hp=633ec6bd060858787f9605094587340ee205e8ac;hpb=d806ed7ca29fa8d4f07dbed383378ecb1c353c13;p=osm%2Fvim-emu.git diff --git a/src/emuvim/dcemulator/net.py b/src/emuvim/dcemulator/net.py index 633ec6b..248dc34 100755 --- a/src/emuvim/dcemulator/net.py +++ b/src/emuvim/dcemulator/net.py @@ -9,8 +9,8 @@ import time from subprocess import Popen import os import re - - +import urllib2 +from functools import partial from mininet.net import Dockernet from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController @@ -57,6 +57,14 @@ class DCNetwork(Dockernet): # graph of the complete DC network self.DCNetwork_graph = nx.MultiDiGraph() + # initialize pool of vlan tags to setup the SDN paths + self.vlans = range(4096)[::-1] + + # link to Ryu REST_API + ryu_ip = '0.0.0.0' + ryu_port = '8080' + self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port) + # monitoring agent if monitor: self.monitor_agent = DCNetworkMonitor(self) @@ -221,23 +229,37 @@ class DCNetwork(Dockernet): CLI(self) # to remove chain do setChain( src, dst, cmd='del-flows') - def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, cmd='add-flow', weight=None): + def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs): + cmd = kwargs.get('cmd') + 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) + + 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'): + return ret + '\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs) + + else: + return "Command unknown" + + + 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 #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'] - #logging.info('vnf_src_if: {0}'.format(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: - #logging.info("here1: {0},{1}".format(link_dict[link],vnf_src_interface)) if link_dict[link]['src_port_id'] == vnf_src_interface: # found the right link and connected switch - #logging.info("conn_sw: {2},{0},{1}".format(link_dict[link]['src_port_id'], vnf_src_interface, connected_sw)) src_sw = connected_sw src_sw_inport_nr = link_dict[link]['dst_port_nr'] @@ -261,23 +283,29 @@ class DCNetwork(Dockernet): # get shortest path - #path = nx.shortest_path(self.DCNetwork_graph, vnf_src_name, vnf_dst_name) 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=weight) + 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)) 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)) - #current_hop = vnf_src_name current_hop = src_sw 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': + if len(path) > 1: + vlan = self.vlans.pop() + 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: @@ -298,33 +326,122 @@ class DCNetwork(Dockernet): switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr'] - #logging.info("add flow in switch: {0} in_port: {1} out_port: {2}".format(current_node.name, switch_inport_nr, switch_outport_nr)) - # set of entry via ovs-ofctl - # TODO use rest API of ryu to set flow entries to correct dpid - # TODO this only sets port in to out, no match, so this will give trouble when multiple services are deployed... - # TODO need multiple matches to do this (VLAN tags) + # set of entry via ovs-ofctl if isinstance( current_node, OVSSwitch ): - match = 'in_port=%s' % switch_inport_nr - - if cmd=='add-flow': - action = 'action=%s' % switch_outport_nr - s = ',' - ofcmd = s.join([match,action]) - elif cmd=='del-flows': - ofcmd = match - else: - ofcmd='' - - current_node.dpctl(cmd, ofcmd) - logging.info("add flow in switch: {0} in_port: {1} out_port: {2}".format(current_node.name, switch_inport_nr, - switch_outport_nr)) + kwargs['vlan'] = vlan + kwargs['path'] = path + kwargs['current_hop'] = current_hop + ## set flow entry via ovs-ofctl + #self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs) + ## set flow entry via ryu rest api + self._set_flow_entry_ryu_rest(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'] current_hop = next_hop - return "path added between {0} and {1}".format(vnf_src_name, vnf_dst_name) - #return "destination node: {0} not reached".format(vnf_dst_name) + return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd) + + def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs): + match = 'in_port=%s' % switch_inport_nr + + cookie = kwargs.get('cookie') + match_input = kwargs.get('match') + cmd = kwargs.get('cmd') + path = kwargs.get('path') + current_hop = kwargs.get('current_hop') + vlan = kwargs.get('vlan') + + s = ',' + if match_input: + match = s.join([match, match_input]) + + flow = {} + flow['dpid'] = int(node.dpid, 16) + if cookie: + flow['cookie'] = int(cookie) + + + flow['actions'] = [] + + # possible Ryu actions, match fields: + # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry + if cmd == 'add-flow': + prefix = 'stats/flowentry/add' + action = {} + action['type'] = 'OUTPUT' + action['port'] = switch_outport_nr + flow['actions'].append(action) + 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 + 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 + match += ',dl_vlan=%s' % vlan + #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) + + action = {} + action['type'] = 'OUTPUT' + action['port'] = switch_outport_nr + flow['actions'].append(action) + + flow['match'] = self._parse_match(match) + self.ryu_REST(prefix, data=flow) + + 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_input = kwargs.get('match') + cmd = kwargs.get('cmd') + path = kwargs.get('path') + current_hop = kwargs.get('current_hop') + vlan = kwargs.get('vlan') + + s = ',' + if cookie: + cookie = 'cookie=%s' % cookie + match = s.join([cookie, match]) + if match_input: + match = s.join([match, match_input]) + if cmd == 'add-flow': + action = 'action=%s' % switch_outport_nr + if vlan != None: + if path.index(current_hop) == 0: # first node + 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 + match += ',dl_vlan=%s' % vlan + action = 'action=strip_vlan,output=%s' % switch_outport_nr + else: # middle nodes + match += ',dl_vlan=%s' % vlan + ofcmd = s.join([match, action]) + elif cmd == 'del-flows': + ofcmd = match + else: + ofcmd = '' + + node.dpctl(cmd, ofcmd) + logging.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): @@ -348,3 +465,33 @@ class DCNetwork(Dockernet): self.ryu_process.terminate() self.ryu_process.kill() + def ryu_REST(self, prefix, dpid=None, data=None): + 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() + 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 + def _parse_match(self, match): + matches = match.split(',') + dict = {} + for m in matches: + match = m.split('=') + if len(match) == 2: + try: + m2 = int(match[1], 0) + except: + m2 = match[1] + + dict.update({match[0]:m2}) + return dict +