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.
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.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")
# 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
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'
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:
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.
"""
- 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)
- 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)
+ 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.
# 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 :
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):
def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
tag=None, **kwargs):
+ """
+ Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
+ So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
+ :param vnf_src_name:
+ :param vnf_dst_name:
+ :param vnf_src_interface:
+ :param vnf_dst_interface:
+ :param tag: vlan tag to be used for this chain (same tag as existing chain)
+ :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
+ :return:
+ """
src_sw = None
src_sw_inport_nr = 0
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')
+ monitor_placement = kwargs.get('monitor_placement').strip()
# put monitor flow at the dst switch
insert_flow = False
if monitor_placement == 'tx' and path.index(current_hop) == 0: # first node:
# 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:
+ elif monitor_placement not in ['rx', 'tx']:
LOG.exception('invalid monitor command: {0}'.format(monitor_placement))
:param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
:param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
: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 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}'.
+ LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
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 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:
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))
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':
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:
- #last switch reached
+ # last switch reached
next_hop = vnf_dst_name
next_node = self.getNodeByName(next_hop)
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
+ kwargs['pathindex'] = i
if self.controller == RemoteController:
## set flow entry via ryu rest api
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
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')
- 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
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')
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)
- 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')
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_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 = ','
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
- 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
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:
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