3b4dd5442fa4ae444e058390fe8b25dbfb2c284d
2 Copyright (c) 2015 SONATA-NFV and Paderborn University
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
9 http://www.apache.org/licenses/LICENSE-2.0
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
17 Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
18 nor the names of its contributors may be used to endorse or promote
19 products derived from this software without specific prior written
22 This work has been performed in the framework of the SONATA project,
23 funded by the European Commission under Grant number 671517 through
24 the Horizon 2020 and 5G-PPP programmes. The authors would like to
25 acknowledge the contributions of their colleagues of the SONATA
26 partner consortium (www.sonata-nfv.eu).
32 from subprocess
import Popen
37 from mininet
.net
import Containernet
38 from mininet
.node
import Controller
, DefaultController
, OVSSwitch
, OVSKernelSwitch
, Docker
, RemoteController
39 from mininet
.cli
import CLI
40 from mininet
.link
import TCLink
41 from mininet
.clean
import cleanup
43 from emuvim
.dcemulator
.monitoring
import DCNetworkMonitor
44 from emuvim
.dcemulator
.node
import Datacenter
, EmulatorCompute
45 from emuvim
.dcemulator
.resourcemodel
import ResourceModelRegistrar
47 LOG
= logging
.getLogger("dcemulator.net")
48 LOG
.setLevel(logging
.DEBUG
)
50 # default CPU period used for cpu percentage-based cfs values (microseconds)
53 class DCNetwork(Containernet
):
55 Wraps the original Mininet/Containernet class and provides
56 methods to add data centers, switches, etc.
58 This class is used by topology definition scripts.
61 def __init__(self
, controller
=RemoteController
, monitor
=False,
62 enable_learning
=False, # learning switch behavior of the default ovs switches icw Ryu controller can be turned off/on, needed for E-LAN functionality
63 dc_emulation_max_cpu
=1.0, # fraction of overall CPU time for emulation
64 dc_emulation_max_mem
=512, # emulation max mem in MB
67 Create an extended version of a Containernet network
68 :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
69 :param kwargs: path through for Mininet parameters
74 self
.ryu_process
= None
75 #list of deployed nsds.E_Lines and E_LANs (uploaded from the dummy gatekeeper)
76 self
.deployed_nsds
= []
77 self
.deployed_elines
= []
78 self
.deployed_elans
= []
79 self
.installed_chains
= []
82 # always cleanup environment before we start the emulator
86 # call original Docker.__init__ and setup default controller
87 Containernet
.__init
__(
88 self
, switch
=OVSKernelSwitch
, controller
=controller
, **kwargs
)
90 # default switch configuration
91 enable_ryu_learning
= False
93 self
.failMode
= 'standalone'
94 enable_ryu_learning
= True
96 self
.failMode
= 'secure'
99 if controller
== RemoteController
:
100 # start Ryu controller
101 self
.startRyu(learning_switch
=enable_ryu_learning
)
103 # add the specified controller
104 self
.addController('c0', controller
=controller
)
106 # graph of the complete DC network
107 self
.DCNetwork_graph
= nx
.MultiDiGraph()
109 # initialize pool of vlan tags to setup the SDN paths
110 self
.vlans
= range(4096)[::-1]
112 # link to Ryu REST_API
115 self
.ryu_REST_api
= 'http://{0}:{1}'.format(ryu_ip
, ryu_port
)
116 self
.RyuSession
= requests
.Session()
120 self
.monitor_agent
= DCNetworkMonitor(self
)
122 self
.monitor_agent
= None
124 # initialize resource model registrar
125 self
.rm_registrar
= ResourceModelRegistrar(
126 dc_emulation_max_cpu
, dc_emulation_max_mem
)
127 self
.cpu_period
= CPU_PERIOD
129 def addDatacenter(self
, label
, metadata
={}, resource_log_path
=None):
131 Create and add a logical cloud data center to the network.
133 if label
in self
.dcs
:
134 raise Exception("Data center label already exists: %s" % label
)
135 dc
= Datacenter(label
, metadata
=metadata
, resource_log_path
=resource_log_path
)
136 dc
.net
= self
# set reference to network
138 dc
.create() # finally create the data center in our Mininet instance
139 LOG
.info("added data center: %s" % label
)
142 def addLink(self
, node1
, node2
, **params
):
144 Able to handle Datacenter objects as link
147 assert node1
is not None
148 assert node2
is not None
150 # ensure type of node1
151 if isinstance( node1
, basestring
):
152 if node1
in self
.dcs
:
153 node1
= self
.dcs
[node1
].switch
154 if isinstance( node1
, Datacenter
):
156 # ensure type of node2
157 if isinstance( node2
, basestring
):
158 if node2
in self
.dcs
:
159 node2
= self
.dcs
[node2
].switch
160 if isinstance( node2
, Datacenter
):
162 # try to give containers a default IP
163 if isinstance( node1
, Docker
):
164 if "params1" not in params
:
165 params
["params1"] = {}
166 if "ip" not in params
["params1"]:
167 params
["params1"]["ip"] = self
.getNextIp()
168 if isinstance( node2
, Docker
):
169 if "params2" not in params
:
170 params
["params2"] = {}
171 if "ip" not in params
["params2"]:
172 params
["params2"]["ip"] = self
.getNextIp()
173 # ensure that we allow TCLinks between data centers
174 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
175 # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
176 if "cls" not in params
:
177 params
["cls"] = TCLink
179 link
= Containernet
.addLink(self
, node1
, node2
, **params
)
181 # try to give container interfaces a default id
182 node1_port_id
= node1
.ports
[link
.intf1
]
183 if isinstance(node1
, Docker
):
184 if "id" in params
["params1"]:
185 node1_port_id
= params
["params1"]["id"]
186 node1_port_name
= link
.intf1
.name
188 node2_port_id
= node2
.ports
[link
.intf2
]
189 if isinstance(node2
, Docker
):
190 if "id" in params
["params2"]:
191 node2_port_id
= params
["params2"]["id"]
192 node2_port_name
= link
.intf2
.name
195 # add edge and assigned port number to graph in both directions between node1 and node2
196 # port_id: id given in descriptor (if available, otherwise same as port)
197 # port: portnumber assigned by Containernet
200 # possible weight metrics allowed by TClink class:
201 weight_metrics
= ['bw', 'delay', 'jitter', 'loss']
202 edge_attributes
= [p
for p
in params
if p
in weight_metrics
]
203 for attr
in edge_attributes
:
204 # if delay: strip ms (need number as weight in graph)
205 match
= re
.search('([0-9]*\.?[0-9]+)', params
[attr
])
207 attr_number
= match
.group(1)
210 attr_dict
[attr
] = attr_number
213 attr_dict2
= {'src_port_id': node1_port_id
, 'src_port_nr': node1
.ports
[link
.intf1
],
214 'src_port_name': node1_port_name
,
215 'dst_port_id': node2_port_id
, 'dst_port_nr': node2
.ports
[link
.intf2
],
216 'dst_port_name': node2_port_name
}
217 attr_dict2
.update(attr_dict
)
218 self
.DCNetwork_graph
.add_edge(node1
.name
, node2
.name
, attr_dict
=attr_dict2
)
220 attr_dict2
= {'src_port_id': node2_port_id
, 'src_port_nr': node2
.ports
[link
.intf2
],
221 'src_port_name': node2_port_name
,
222 'dst_port_id': node1_port_id
, 'dst_port_nr': node1
.ports
[link
.intf1
],
223 'dst_port_name': node1_port_name
}
224 attr_dict2
.update(attr_dict
)
225 self
.DCNetwork_graph
.add_edge(node2
.name
, node1
.name
, attr_dict
=attr_dict2
)
227 LOG
.debug("addLink: n1={0} intf1={1} -- n2={2} intf2={3}".format(
228 str(node1
),node1_port_name
, str(node2
), node2_port_name
))
232 def addDocker( self
, label
, **params
):
234 Wrapper for addDocker method to use custom container class.
236 self
.DCNetwork_graph
.add_node(label
)
237 return Containernet
.addDocker(self
, label
, cls
=EmulatorCompute
, **params
)
239 def removeDocker( self
, label
, **params
):
241 Wrapper for removeDocker method to update graph.
243 self
.DCNetwork_graph
.remove_node(label
)
244 return Containernet
.removeDocker(self
, label
, **params
)
246 def addSwitch( self
, name
, add_to_graph
=True, **params
):
248 Wrapper for addSwitch method to store switch also in graph.
251 # add this switch to the global topology overview
253 self
.DCNetwork_graph
.add_node(name
)
255 # set the learning switch behavior
256 if 'failMode' in params
:
257 failMode
= params
['failMode']
259 failMode
= self
.failMode
261 s
= Containernet
.addSwitch(self
, name
, protocols
='OpenFlow10,OpenFlow12,OpenFlow13', failMode
=failMode
, **params
)
263 # set flow entry that enables learning switch behavior (needed to enable E-LAN functionality)
264 #LOG.info('failmode {0}'.format(failMode))
265 #if failMode == 'standalone' :
266 # LOG.info('add NORMAL')
267 # s.dpctl('add-flow', 'actions=NORMAL')
271 def getAllContainers(self
):
273 Returns a list with all containers within all data centers.
276 for dc
in self
.dcs
.itervalues():
277 all_containers
+= dc
.listCompute()
278 return all_containers
282 for dc
in self
.dcs
.itervalues():
284 Containernet
.start(self
)
288 # stop the monitor agent
289 if self
.monitor_agent
is not None:
290 self
.monitor_agent
.stop()
293 Containernet
.stop(self
)
295 # stop Ryu controller
302 def setLAN(self
, vnf_list
):
304 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
306 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
311 src_sw_inport_name
= None
313 # get a vlan tag for this E-LAN
314 vlan
= self
.vlans
.pop()
317 vnf_src_name
= vnf
['name']
318 vnf_src_interface
= vnf
['interface']
320 # check if port is specified (vnf:port)
321 if vnf_src_interface
is None:
322 # take first interface by default
323 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_src_name
)[0]
324 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
325 vnf_src_interface
= link_dict
[0]['src_port_id']
327 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_src_name
):
328 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
329 for link
in link_dict
:
330 if (link_dict
[link
]['src_port_id'] == vnf_src_interface
or
331 link_dict
[link
]['src_port_name'] == vnf_src_interface
): # Fix: we might also get interface names, e.g, from a son-emu-cli call
332 # found the right link and connected switch
333 src_sw
= connected_sw
334 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
335 src_sw_inport_name
= link_dict
[link
]['dst_port_name']
338 # set the tag on the dc switch interface
339 LOG
.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name
, vnf_src_interface
,vlan
))
340 switch_node
= self
.getNodeByName(src_sw
)
341 self
._set
_vlan
_tag
(switch_node
, src_sw_inport_name
, vlan
)
343 def _addMonitorFlow(self
, vnf_src_name
, vnf_dst_name
, vnf_src_interface
=None, vnf_dst_interface
=None,
348 src_sw_inport_name
= None
350 dst_sw_outport_nr
= 0
351 dst_sw_outport_name
= None
353 LOG
.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
354 vnf_src_name
, vnf_src_interface
, vnf_dst_name
, vnf_dst_interface
)
356 #check if port is specified (vnf:port)
357 if vnf_src_interface
is None:
358 # take first interface by default
359 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_src_name
)[0]
360 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
361 vnf_src_interface
= link_dict
[0]['src_port_id']
363 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_src_name
):
364 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
365 for link
in link_dict
:
366 if (link_dict
[link
]['src_port_id'] == vnf_src_interface
or
367 link_dict
[link
]['src_port_name'] == vnf_src_interface
): # Fix: we might also get interface names, e.g, from a son-emu-cli call
368 # found the right link and connected switch
369 src_sw
= connected_sw
370 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
371 src_sw_inport_name
= link_dict
[link
]['dst_port_name']
374 if vnf_dst_interface
is None:
375 # take first interface by default
376 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_dst_name
)[0]
377 link_dict
= self
.DCNetwork_graph
[connected_sw
][vnf_dst_name
]
378 vnf_dst_interface
= link_dict
[0]['dst_port_id']
380 vnf_dst_name
= vnf_dst_name
.split(':')[0]
381 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_dst_name
):
382 link_dict
= self
.DCNetwork_graph
[connected_sw
][vnf_dst_name
]
383 for link
in link_dict
:
384 if link_dict
[link
]['dst_port_id'] == vnf_dst_interface
or \
385 link_dict
[link
]['dst_port_name'] == vnf_dst_interface
: # Fix: we might also get interface names, e.g, from a son-emu-cli call
386 # found the right link and connected switch
387 dst_sw
= connected_sw
388 dst_sw_outport_nr
= link_dict
[link
]['src_port_nr']
389 dst_sw_outport_name
= link_dict
[link
]['src_port_name']
393 LOG
.exception('tag not valid: {0}'.format(tag
))
397 # returns the first found shortest path
398 # if all shortest paths are wanted, use: all_shortest_paths
399 path
= nx
.shortest_path(self
.DCNetwork_graph
, src_sw
, dst_sw
, weight
=kwargs
.get('weight'))
401 LOG
.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
402 vnf_src_name
, vnf_dst_name
, src_sw
, dst_sw
))
403 LOG
.debug("Graph nodes: %r" % self
.DCNetwork_graph
.nodes())
404 LOG
.debug("Graph edges: %r" % self
.DCNetwork_graph
.edges())
405 for e
, v
in self
.DCNetwork_graph
.edges():
406 LOG
.debug("%r" % self
.DCNetwork_graph
[e
][v
])
407 return "No path could be found between {0} and {1}".format(vnf_src_name
, vnf_dst_name
)
409 LOG
.info("Path between {0} and {1}: {2}".format(vnf_src_name
, vnf_dst_name
, path
))
412 switch_inport_nr
= src_sw_inport_nr
414 cmd
= kwargs
.get('cmd')
416 #iterate through the path to install the flow-entries
417 for i
in range(0,len(path
)):
418 current_node
= self
.getNodeByName(current_hop
)
420 if path
.index(current_hop
) < len(path
)-1:
421 next_hop
= path
[path
.index(current_hop
)+1]
424 next_hop
= vnf_dst_name
426 next_node
= self
.getNodeByName(next_hop
)
428 if next_hop
== vnf_dst_name
:
429 switch_outport_nr
= dst_sw_outport_nr
430 LOG
.info("end node reached: {0}".format(vnf_dst_name
))
431 elif not isinstance( next_node
, OVSSwitch
):
432 LOG
.info("Next node: {0} is not a switch".format(next_hop
))
433 return "Next node: {0} is not a switch".format(next_hop
)
435 # take first link between switches by default
437 switch_outport_nr
= self
.DCNetwork_graph
[current_hop
][next_hop
][index_edge_out
]['src_port_nr']
440 # set of entry via ovs-ofctl
441 if isinstance( current_node
, OVSSwitch
):
443 kwargs
['path'] = path
444 kwargs
['current_hop'] = current_hop
445 kwargs
['switch_inport_name'] = src_sw_inport_name
446 kwargs
['switch_outport_name'] = dst_sw_outport_name
447 kwargs
['skip_vlan_tag'] = True
449 monitor_placement
= kwargs
.get('monitor_placement')
450 # put monitor flow at the dst switch
452 if monitor_placement
== 'tx' and path
.index(current_hop
) == 0: # first node:
454 # put monitoring flow at the src switch
455 elif monitor_placement
== 'rx' and path
.index(current_hop
) == len(path
) - 1: # last node:
458 LOG
.exception('invalid monitor command: {0}'.format(monitor_placement
))
461 if self
.controller
== RemoteController
and insert_flow
:
462 ## set flow entry via ryu rest api
463 self
._set
_flow
_entry
_ryu
_rest
(current_node
, switch_inport_nr
, switch_outport_nr
, **kwargs
)
466 ## set flow entry via ovs-ofctl
467 self
._set
_flow
_entry
_dpctl
(current_node
, switch_inport_nr
, switch_outport_nr
, **kwargs
)
470 # take first link between switches by default
471 if isinstance( next_node
, OVSSwitch
):
472 switch_inport_nr
= self
.DCNetwork_graph
[current_hop
][next_hop
][0]['dst_port_nr']
473 current_hop
= next_hop
475 return "path {2} between {0} and {1}".format(vnf_src_name
, vnf_dst_name
, cmd
)
478 def setChain(self
, vnf_src_name
, vnf_dst_name
, vnf_src_interface
=None, vnf_dst_interface
=None, **kwargs
):
480 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
481 Currently the path is found using the default networkx shortest path function.
482 Each chain gets a unique vlan id , so different chains wil not interfere.
484 :param vnf_src_name: vnf name (string)
485 :param vnf_dst_name: vnf name (string)
486 :param vnf_src_interface: source interface name (string)
487 :param vnf_dst_interface: destination interface name (string)
488 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
489 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
490 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
491 :param priority: custom flowrule priority
492 :return: output log string
495 # special procedure for monitoring flows
496 if kwargs
.get('monitor'):
498 # check if chain already exists
499 found_chains
= [chain_dict
for chain_dict
in self
.installed_chains
if
500 (chain_dict
['vnf_src_name'] == vnf_src_name
and chain_dict
['vnf_src_interface'] == vnf_src_interface
501 and chain_dict
['vnf_dst_name'] == vnf_dst_name
and chain_dict
['vnf_dst_interface'] == vnf_dst_interface
)]
503 if len(found_chains
) > 0:
504 # this chain exists, so need an extra monitoring flow
505 # assume only 1 chain per vnf/interface pair
506 LOG
.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
507 format(vnf_src_name
, vnf_src_interface
, vnf_dst_name
, vnf_dst_interface
))
508 tag
= found_chains
[0]['tag']
509 ret
= self
._addMonitorFlow
(vnf_src_name
, vnf_dst_name
, vnf_src_interface
, vnf_dst_interface
,
510 tag
=tag
, table_id
=0, **kwargs
)
513 # no chain existing (or E-LAN) -> install normal chain
514 LOG
.warning('*** installing monitoring chain without pre-defined chain from {0}:{1} -> {2}:{3}'.
515 format(vnf_src_name
, vnf_src_interface
, vnf_dst_name
, vnf_dst_interface
))
519 cmd
= kwargs
.get('cmd')
520 if cmd
== 'add-flow' or cmd
== 'del-flows':
521 ret
= self
._chainAddFlow
(vnf_src_name
, vnf_dst_name
, vnf_src_interface
, vnf_dst_interface
, **kwargs
)
522 if kwargs
.get('bidirectional'):
523 ret
= ret
+'\n' + self
._chainAddFlow
(vnf_dst_name
, vnf_src_name
, vnf_dst_interface
, vnf_src_interface
, **kwargs
)
526 ret
= "Command unknown"
531 def _chainAddFlow(self
, vnf_src_name
, vnf_dst_name
, vnf_src_interface
=None, vnf_dst_interface
=None, **kwargs
):
535 src_sw_inport_name
= None
537 dst_sw_outport_nr
= 0
538 dst_sw_outport_name
= None
540 LOG
.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
541 vnf_src_name
, vnf_src_interface
, vnf_dst_name
, vnf_dst_interface
)
543 #check if port is specified (vnf:port)
544 if vnf_src_interface
is None:
545 # take first interface by default
546 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_src_name
)[0]
547 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
548 vnf_src_interface
= link_dict
[0]['src_port_id']
550 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_src_name
):
551 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
552 for link
in link_dict
:
553 if (link_dict
[link
]['src_port_id'] == vnf_src_interface
or
554 link_dict
[link
]['src_port_name'] == vnf_src_interface
): # Fix: we might also get interface names, e.g, from a son-emu-cli call
555 # found the right link and connected switch
556 src_sw
= connected_sw
557 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
558 src_sw_inport_name
= link_dict
[link
]['dst_port_name']
561 if vnf_dst_interface
is None:
562 # take first interface by default
563 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_dst_name
)[0]
564 link_dict
= self
.DCNetwork_graph
[connected_sw
][vnf_dst_name
]
565 vnf_dst_interface
= link_dict
[0]['dst_port_id']
567 vnf_dst_name
= vnf_dst_name
.split(':')[0]
568 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_dst_name
):
569 link_dict
= self
.DCNetwork_graph
[connected_sw
][vnf_dst_name
]
570 for link
in link_dict
:
571 if link_dict
[link
]['dst_port_id'] == vnf_dst_interface
or \
572 link_dict
[link
]['dst_port_name'] == vnf_dst_interface
: # Fix: we might also get interface names, e.g, from a son-emu-cli call
573 # found the right link and connected switch
574 dst_sw
= connected_sw
575 dst_sw_outport_nr
= link_dict
[link
]['src_port_nr']
576 dst_sw_outport_name
= link_dict
[link
]['src_port_name']
582 # returns the first found shortest path
583 # if all shortest paths are wanted, use: all_shortest_paths
584 path
= nx
.shortest_path(self
.DCNetwork_graph
, src_sw
, dst_sw
, weight
=kwargs
.get('weight'))
586 LOG
.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
587 vnf_src_name
, vnf_dst_name
, src_sw
, dst_sw
))
588 LOG
.debug("Graph nodes: %r" % self
.DCNetwork_graph
.nodes())
589 LOG
.debug("Graph edges: %r" % self
.DCNetwork_graph
.edges())
590 for e
, v
in self
.DCNetwork_graph
.edges():
591 LOG
.debug("%r" % self
.DCNetwork_graph
[e
][v
])
592 return "No path could be found between {0} and {1}".format(vnf_src_name
, vnf_dst_name
)
594 LOG
.info("Path between {0} and {1}: {2}".format(vnf_src_name
, vnf_dst_name
, path
))
597 switch_inport_nr
= src_sw_inport_nr
600 ## if path contains more than 1 switch
601 cmd
= kwargs
.get('cmd')
603 if cmd
== 'add-flow':
604 if kwargs
.get('tag'):
605 # use pre-defined tag
606 vlan
= kwargs
.get('tag')
608 vlan
= self
.vlans
.pop()
610 # store the used vlan tag to identify this chain
611 if not kwargs
.get('monitor'):
613 chain_dict
['vnf_src_name'] = vnf_src_name
614 chain_dict
['vnf_dst_name'] = vnf_dst_name
615 chain_dict
['vnf_src_interface'] = vnf_src_interface
616 chain_dict
['vnf_dst_interface'] = vnf_dst_interface
617 chain_dict
['tag'] = vlan
618 self
.installed_chains
.append(chain_dict
)
620 #iterate through the path to install the flow-entries
621 for i
in range(0,len(path
)):
622 current_node
= self
.getNodeByName(current_hop
)
624 if path
.index(current_hop
) < len(path
)-1:
625 next_hop
= path
[path
.index(current_hop
)+1]
628 next_hop
= vnf_dst_name
630 next_node
= self
.getNodeByName(next_hop
)
632 if next_hop
== vnf_dst_name
:
633 switch_outport_nr
= dst_sw_outport_nr
634 LOG
.info("end node reached: {0}".format(vnf_dst_name
))
635 elif not isinstance( next_node
, OVSSwitch
):
636 LOG
.info("Next node: {0} is not a switch".format(next_hop
))
637 return "Next node: {0} is not a switch".format(next_hop
)
639 # take first link between switches by default
641 switch_outport_nr
= self
.DCNetwork_graph
[current_hop
][next_hop
][index_edge_out
]['src_port_nr']
644 # set of entry via ovs-ofctl
645 if isinstance( current_node
, OVSSwitch
):
646 kwargs
['vlan'] = vlan
647 kwargs
['path'] = path
648 kwargs
['current_hop'] = current_hop
649 kwargs
['switch_inport_name'] = src_sw_inport_name
650 kwargs
['switch_outport_name'] = dst_sw_outport_name
652 if self
.controller
== RemoteController
:
653 ## set flow entry via ryu rest api
654 self
._set
_flow
_entry
_ryu
_rest
(current_node
, switch_inport_nr
, switch_outport_nr
, **kwargs
)
656 ## set flow entry via ovs-ofctl
657 self
._set
_flow
_entry
_dpctl
(current_node
, switch_inport_nr
, switch_outport_nr
, **kwargs
)
659 # take first link between switches by default
660 if isinstance( next_node
, OVSSwitch
):
661 switch_inport_nr
= self
.DCNetwork_graph
[current_hop
][next_hop
][0]['dst_port_nr']
662 current_hop
= next_hop
664 return "path {2} between {0} and {1}".format(vnf_src_name
, vnf_dst_name
, cmd
)
666 def _set_flow_entry_ryu_rest(self
, node
, switch_inport_nr
, switch_outport_nr
, **kwargs
):
667 match
= 'in_port=%s' % switch_inport_nr
669 cookie
= kwargs
.get('cookie')
670 match_input
= kwargs
.get('match')
671 cmd
= kwargs
.get('cmd')
672 path
= kwargs
.get('path')
673 current_hop
= kwargs
.get('current_hop')
674 vlan
= kwargs
.get('vlan')
675 priority
= kwargs
.get('priority')
676 # flag to not set the ovs port vlan tag
677 skip_vlan_tag
= kwargs
.get('skip_vlan_tag')
678 # table id to put this flowentry
679 table_id
= kwargs
.get('table_id')
685 match
= s
.join([match
, match_input
])
688 flow
['dpid'] = int(node
.dpid
, 16)
691 flow
['cookie'] = int(cookie
)
693 flow
['priority'] = int(priority
)
695 flow
['table_id'] = table_id
699 # possible Ryu actions, match fields:
700 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
701 if cmd
== 'add-flow':
702 prefix
= 'stats/flowentry/add'
704 if path
.index(current_hop
) == 0: # first node
705 # set vlan tag in ovs instance (to isolate E-LANs)
706 if not skip_vlan_tag
:
707 in_port_name
= kwargs
.get('switch_inport_name')
708 self
._set
_vlan
_tag
(node
, in_port_name
, vlan
)
709 # set vlan push action if more than 1 switch in the path
712 action
['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
713 action
['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
714 flow
['actions'].append(action
)
716 action
['type'] = 'SET_FIELD'
717 action
['field'] = 'vlan_vid'
718 action
['value'] = vlan
719 flow
['actions'].append(action
)
721 if path
.index(current_hop
) == len(path
) - 1: # last node
722 # set vlan tag in ovs instance (to isolate E-LANs)
723 if not skip_vlan_tag
:
724 out_port_name
= kwargs
.get('switch_outport_name')
725 self
._set
_vlan
_tag
(node
, out_port_name
, vlan
)
726 # set vlan pop action if more than 1 switch in the path
728 match
+= ',dl_vlan=%s' % vlan
730 action
['type'] = 'POP_VLAN'
731 flow
['actions'].append(action
)
733 if 0 < path
.index(current_hop
) < (len(path
) - 1): # middle nodes
734 match
+= ',dl_vlan=%s' % vlan
736 # output action must come last
738 action
['type'] = 'OUTPUT'
739 action
['port'] = switch_outport_nr
740 flow
['actions'].append(action
)
742 elif cmd
== 'del-flows':
743 prefix
= 'stats/flowentry/delete'
746 # TODO: add cookie_mask as argument
747 flow
['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
750 action
['type'] = 'OUTPUT'
751 action
['port'] = switch_outport_nr
752 flow
['actions'].append(action
)
754 flow
['match'] = self
._parse
_match
(match
)
755 self
.ryu_REST(prefix
, data
=flow
)
757 def _set_vlan_tag(self
, node
, switch_port
, tag
):
758 node
.vsctl('set', 'port {0} tag={1}'.format(switch_port
,tag
))
759 LOG
.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node
.name
, switch_port
, tag
))
761 def _set_flow_entry_dpctl(self
, node
, switch_inport_nr
, switch_outport_nr
, **kwargs
):
763 match
= 'in_port=%s' % switch_inport_nr
765 cookie
= kwargs
.get('cookie')
766 match_input
= kwargs
.get('match')
767 cmd
= kwargs
.get('cmd')
768 path
= kwargs
.get('path')
769 current_hop
= kwargs
.get('current_hop')
770 vlan
= kwargs
.get('vlan')
774 cookie
= 'cookie=%s' % cookie
775 match
= s
.join([cookie
, match
])
777 match
= s
.join([match
, match_input
])
778 if cmd
== 'add-flow':
779 action
= 'action=%s' % switch_outport_nr
781 if path
.index(current_hop
) == 0: # first node
782 action
= ('action=mod_vlan_vid:%s' % vlan
) + (',output=%s' % switch_outport_nr
)
783 match
= '-O OpenFlow13 ' + match
784 elif path
.index(current_hop
) == len(path
) - 1: # last node
785 match
+= ',dl_vlan=%s' % vlan
786 action
= 'action=strip_vlan,output=%s' % switch_outport_nr
788 match
+= ',dl_vlan=%s' % vlan
789 ofcmd
= s
.join([match
, action
])
790 elif cmd
== 'del-flows':
795 node
.dpctl(cmd
, ofcmd
)
796 LOG
.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node
.name
, switch_inport_nr
,
797 switch_outport_nr
, cmd
))
799 # start Ryu Openflow controller as Remote Controller for the DCNetwork
800 def startRyu(self
, learning_switch
=True):
801 # start Ryu controller with rest-API
802 python_install_path
= site
.getsitepackages()[0]
803 # ryu default learning switch
804 #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
805 #custom learning switch that installs a default NORMAL action in the ovs switches
806 dir_path
= os
.path
.dirname(os
.path
.realpath(__file__
))
807 ryu_path
= dir_path
+ '/son_emu_simple_switch_13.py'
808 ryu_path2
= python_install_path
+ '/ryu/app/ofctl_rest.py'
809 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
810 # Ryu still uses 6633 as default
811 ryu_option
= '--ofp-tcp-listen-port'
813 ryu_cmd
= 'ryu-manager'
814 FNULL
= open("/tmp/ryu.log", 'w')
816 self
.ryu_process
= Popen([ryu_cmd
, ryu_path
, ryu_path2
, ryu_option
, ryu_of_port
], stdout
=FNULL
, stderr
=FNULL
)
817 LOG
.debug('starting ryu-controller with {0}'.format(ryu_path
))
818 LOG
.debug('starting ryu-controller with {0}'.format(ryu_path2
))
820 # no learning switch, but with rest api
821 self
.ryu_process
= Popen([ryu_cmd
, ryu_path2
, ryu_option
, ryu_of_port
], stdout
=FNULL
, stderr
=FNULL
)
822 LOG
.debug('starting ryu-controller with {0}'.format(ryu_path2
))
827 Stop the Ryu controller that might be started by son-emu.
831 if self
.ryu_process
is not None:
832 self
.ryu_process
.terminate()
833 self
.ryu_process
.kill()
834 # ensure its death ;-)
835 Popen(['pkill', '-f', 'ryu-manager'])
837 def ryu_REST(self
, prefix
, dpid
=None, data
=None):
840 url
= self
.ryu_REST_api
+ '/' + str(prefix
) + '/' + str(dpid
)
842 url
= self
.ryu_REST_api
+ '/' + str(prefix
)
844 req
= self
.RyuSession
.post(url
, json
=data
)
846 req
= self
.RyuSession
.get(url
)
849 # do extra logging if status code is not 200 (OK)
850 if req
.status_code
is not requests
.codes
.ok
:
852 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req
.headers
['content-type'],
853 req
.encoding
, req
.text
,
854 req
.headers
, req
.history
))
855 LOG
.info('url: {0}'.format(str(url
)))
856 if data
: LOG
.info('POST: {0}'.format(str(data
)))
857 LOG
.info('status: {0} reason: {1}'.format(req
.status_code
, req
.reason
))
860 if 'json' in req
.headers
['content-type']:
864 ret
= req
.text
.rstrip()
868 # need to respect that some match fields must be integers
869 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
870 def _parse_match(self
, match
):
871 matches
= match
.split(',')
877 m2
= int(match
[1], 0)
881 dict.update({match
[0]:m2
})
884 def find_connected_dc_interface(self
, vnf_src_name
, vnf_src_interface
):
885 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_src_name
):
886 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
887 for link
in link_dict
:
888 if (link_dict
[link
]['src_port_id'] == vnf_src_interface
or
889 link_dict
[link
]['src_port_name'] == vnf_src_interface
): # Fix: we might also get interface names, e.g, from a son-emu-cli call
890 # found the right link and connected switch
891 src_sw
= connected_sw
892 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
893 src_sw_inport_name
= link_dict
[link
]['dst_port_name']
894 return src_sw_inport_name